Compare commits

...

101 Commits

Author SHA1 Message Date
Till Wübbers
d6e0cbf41c more tutorial text 2025-06-26 11:12:10 +02:00
Asuro
f95d9dee91 final build ig 2025-06-21 12:31:46 +02:00
Asuro
3c0af71470 puzzle 2025-06-21 12:21:14 +02:00
Asuro
7e89d93a7d bridge 2025-06-21 12:10:53 +02:00
Asuro
05cf88d986 fixes 2025-06-21 11:06:05 +02:00
Asuro
d6bec9e870 rotation fix 2025-06-21 10:57:36 +02:00
Asuro
3ccbbf493f walls 2025-06-21 03:27:25 +02:00
Asuro
b47a0cf841 test puzzles 2025-06-21 00:26:44 +02:00
Asuro
db47297ea4 less sticky movement 2025-06-21 00:26:37 +02:00
Asuro
6461b442de (kinda) fix ui offset 2025-06-21 00:13:51 +02:00
Asuro
146bf4aa22 color changes 2025-06-21 00:04:33 +02:00
Asuro
42c5b55f95 fix texture rotation, add tutorial popup 2025-06-20 23:34:32 +02:00
Asuro
d7fc6b781e ui hack 2025-06-20 15:42:05 +02:00
Asuro
e15cd79e04 fix collision 2025-06-20 15:41:52 +02:00
Asuro
4e00355dbe working collision! 2025-06-20 05:15:35 +02:00
Asuro
ffcc5bd134 fix heightmaps 2025-06-20 03:00:42 +02:00
Till Wübbers
67c1489da0 heightmap wip 2025-06-18 00:28:40 +02:00
Till Wübbers
59b8eea3a7 heightmap previews 2025-06-13 13:10:31 +02:00
Till Wübbers
a936222711 new debug message 2025-06-01 14:26:13 +02:00
Asuro
3af10d120b landscape 2025-06-01 03:17:29 +02:00
Asuro
6c8bead6ab new dithering 2025-06-01 03:17:21 +02:00
Asuro
faa36dd679 fix dithergen 2025-06-01 02:06:32 +02:00
Asuro
196d119338 slight entity rework (todo: memory corruption >.<) 2025-05-31 01:39:34 +02:00
Asuro
383c6f975b fix asset upgrade for arrays 2025-05-31 00:20:23 +02:00
Till Wübbers
4b230be2a8 half assed bugfixing 2025-05-29 17:33:14 +02:00
Till W
87ce032833 wip 2025-05-26 18:04:51 +02:00
Till W
cac2ef3330 new puzzle tool 2025-05-25 13:19:50 +02:00
Till W
410f401aef flags enum stuff 2025-05-25 13:07:11 +02:00
Till W
0c2d10d631 fix upgrading native type changes 2025-05-25 10:34:28 +02:00
Till W
18e7085aeb build notes 2025-05-23 01:57:32 +02:00
Asuro
bd2962fc38 refactor 2025-05-21 02:48:36 +02:00
Asuro
15dc65530d reset 2025-05-19 18:41:10 +02:00
Asuro
70db6ca2aa solved status ui 2025-05-19 18:20:05 +02:00
Asuro
1a8be39c19 border shader 2025-05-19 18:19:43 +02:00
Asuro
ec7a709570 solved not solved textures 2025-05-19 18:19:21 +02:00
Asuro
5cdc7d720e socket connection direction wip 2025-05-16 01:50:19 +02:00
Asuro
79fe91981b small fixes 2025-05-16 00:26:19 +02:00
Asuro
0d91ec1ebb working sockets 2025-05-11 22:32:50 +02:00
Asuro
1616704c50 fix upgrades one more time 2025-05-10 19:09:59 +02:00
Asuro
5d1db591b7 fix log highlighting 2025-05-10 14:44:25 +02:00
Till Wübbers
234a9b1732 correctly destroy stuff 2025-04-29 09:52:19 +02:00
Till Wübbers
91e9566747 make imgui optional-ish 2025-04-29 08:46:05 +02:00
Till Wübbers
02c40aeea6 more fixes 2025-04-29 08:01:26 +02:00
Till Wübbers
171e25ac76 fix memory arena alloc 2025-04-28 10:03:09 +02:00
Till Wübbers
32d89d8f77 logging improvements 2025-04-28 08:23:16 +02:00
Till Wübbers
4cdd80977c wip 2025-04-27 12:00:22 +02:00
Asuro
d75e5627f9 stuff 2025-04-13 01:46:36 +02:00
Asuro
adbe518c6e fix enum upgrades 2025-04-13 00:24:03 +02:00
Till Wübbers
c9db7e7e8f smol fix 2025-04-07 19:26:35 +02:00
Asuro
d252da6359 puzzles idk 2025-04-07 18:04:59 +02:00
Asuro
3fd8937b25 drag available cards 2025-04-07 17:58:48 +02:00
Asuro
fd2654c944 multiple puzzles 2025-04-07 16:57:41 +02:00
Asuro
d0f9051af7 puzzle data 2025-04-07 16:57:28 +02:00
Asuro
e0016817dd card assets 2025-04-07 01:32:07 +02:00
Asuro
c7edebaeb8 upgradeable types! 2025-04-06 22:27:01 +02:00
Asuro
158d59e09e new save format 2025-04-05 18:10:43 +02:00
Asuro
ab5c8a489f generate metadata in code 2025-04-04 20:39:31 +02:00
Asuro
12c546b6dc save ui settings & toggle ui 2025-03-31 18:12:23 +02:00
Asuro
ae069c4949 puzzle ui positioning 2025-03-31 17:51:57 +02:00
Asuro
5a3739db0a show game tablet 2025-03-31 07:08:23 +02:00
Asuro
4ba65713ef move transform to generated data 2025-03-31 02:01:14 +02:00
Asuro
29a3aaf241 puzzle ui 2025-03-30 20:47:09 +02:00
Asuro
c244b997c1 moving more stuff around 2025-03-30 20:22:16 +02:00
Asuro
6d170be57a include cleanup 2025-03-30 20:17:32 +02:00
Asuro
052fc2cc07 move vec to generated 2025-03-30 19:45:48 +02:00
Asuro
b006d14197 card dragging 2025-03-30 03:00:23 +02:00
Asuro
e5076b0c3b it works! 2025-03-30 00:05:52 +01:00
Asuro
3ce1acc633 wip card clicking 2025-03-29 22:27:34 +01:00
Asuro
cd03f89465 flip quad uvs 2025-03-29 17:49:10 +01:00
Asuro
39164bffa5 move imgui helper files 2025-03-29 17:27:17 +01:00
Asuro
ef7833caa6 fix camera matrix hacks 2025-03-29 17:10:26 +01:00
Asuro
dbc65fec9a toggle stats 2025-03-29 16:20:45 +01:00
Till Wübbers
cdf6a83af6 cleanup 2025-03-29 06:22:14 +01:00
Till Wübbers
77a6a0a510 fps display 2025-03-29 05:37:07 +01:00
Till Wübbers
fc67ff62be cleanup 2025-03-29 05:10:13 +01:00
Asuro
365805537c texture loading! 2025-03-28 04:28:06 +01:00
Asuro
f56ffdaa13 shader changes & screen quads 2025-03-28 00:11:56 +01:00
Asuro
26ba03b7fe parent to camera 2025-03-27 22:38:31 +01:00
Asuro
90d4c3df1b more logging stuff 2025-03-27 22:17:58 +01:00
Asuro
2ff08d7258 show log in game 2025-03-27 17:36:55 +01:00
Asuro
24a724a021 fix mesh indexing 2025-03-27 16:52:34 +01:00
Asuro
aafa5e966e fixing stuff 2025-03-24 18:30:59 +01:00
Asuro
fa93abe1ec serialization stuff 2025-03-24 16:35:49 +01:00
Asuro
551796e7ad generate type data 2025-03-20 20:18:40 +01:00
Asuro
e16c9e0a2f fixed puzzle solver! 2025-03-17 18:33:09 +01:00
Asuro
ffd2e45d81 working puzzle editor? 2025-03-17 17:59:39 +01:00
Till Wübbers
93bb1553be wip 2025-03-16 21:03:41 +01:00
Till Wübbers
ebf29b058a release build setup 2025-03-16 02:00:33 +01:00
Till Wübbers
442fd23a0b finally fix laptop build 2025-03-16 01:01:29 +01:00
Till Wübbers
a0af22cea6 fixing cmake bs 2025-03-15 23:33:58 +01:00
Asuro
a5e32c414e tracing 2025-03-14 22:07:44 +01:00
Asuro
a3e22b7b05 tracy 2025-03-14 22:07:32 +01:00
Asuro
f14add70b6 card rotation 2025-03-14 20:10:13 +01:00
Asuro
de88190c47 simplify puzzle data and auto run codegen 2025-03-14 01:33:38 +01:00
Asuro
97146664f2 puzzle ids 2025-03-13 22:47:23 +01:00
Asuro
155339917d static puzzle data! 2025-03-13 00:41:38 +01:00
Asuro
f3f994fd8b add hashes 2025-03-12 22:29:52 +01:00
Asuro
ffad8f8460 loading works!! 2025-03-11 01:19:13 +01:00
Asuro
c7377c3452 save works! 2025-03-10 22:45:15 +01:00
Asuro
ff00119e5b minidef refactor 2025-03-10 15:16:48 +01:00
Asuro
e2b94b826d default values 2025-03-10 03:12:35 +01:00
155 changed files with 9434 additions and 1681 deletions

4
.gitattributes vendored
View File

@@ -4,3 +4,7 @@
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.blend filter=lfs diff=lfs merge=lfs -text
*.pzl filter=lfs diff=lfs merge=lfs -text
*.exe filter=lfs diff=lfs merge=lfs -text
*.glb filter=lfs diff=lfs merge=lfs -text
*.ktx filter=lfs diff=lfs merge=lfs -text

3
.gitmodules vendored
View File

@@ -11,3 +11,6 @@
path = src/dependency/imgui
url = https://github.com/ocornut/imgui
branch = docking
[submodule "src/dependency/tracy"]
path = src/dependency/tracy
url = https://github.com/wolfpld/tracy

BIN
assets/blender/Channels.blend LFS Normal file

Binary file not shown.

BIN
assets/blender/cards.blend LFS Normal file

Binary file not shown.

BIN
assets/blender/landscape.blend LFS Normal file

Binary file not shown.

BIN
assets/blender/tablet.blend LFS Normal file

Binary file not shown.

BIN
assets/textures/bridge.png LFS Normal file

Binary file not shown.

BIN
assets/textures/concrete_tile.png LFS Normal file

Binary file not shown.

BIN
assets/textures/notsolved.png LFS Normal file

Binary file not shown.

BIN
assets/textures/reset.png LFS Normal file

Binary file not shown.

BIN
assets/textures/solved.png LFS Normal file

Binary file not shown.

BIN
assets/textures/test.png LFS Normal file

Binary file not shown.

BIN
assets/textures/w corner long.png LFS Normal file

Binary file not shown.

BIN
assets/textures/w straight.png LFS Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
src/.gitattributes vendored
View File

@@ -1 +0,0 @@
*.glb filter=lfs diff=lfs merge=lfs -text

1
src/.gitignore vendored
View File

@@ -33,5 +33,6 @@
# build dirs
cmake-build/
cmake-build-release/
out/
.vs/

View File

@@ -2,12 +2,14 @@ cmake_minimum_required(VERSION 3.10)
project(PuzGameProj)
if (MSVC)
else()
add_compile_options(-g -gcodeview)
add_link_options(-fuse-ld=lld -g -Wl,--pdb=)
endif()
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_compile_definitions("$<$<CONFIG:DEBUG>:DEBUG>")
# set the output directory for built objects.
# This makes sure that the dynamic library goes into the build directory automatically.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
@@ -24,15 +26,34 @@ add_subdirectory("${CMAKE_SOURCE_DIR}/dependency/minidef")
# Engine
file(GLOB_RECURSE sources_engine engine/*.cpp engine/*.h)
add_executable(PuzGameEngine ${sources_engine})
target_compile_definitions(PuzGameEngine PUBLIC "_AMD64_")
set_property(TARGET PuzGameEngine PROPERTY CXX_STANDARD 17)
target_include_directories(PuzGameEngine PUBLIC)
# Game
file(GLOB_RECURSE sources_game game/*.cpp game/*.h gen/*.cpp gen/*.h)
add_custom_command(
COMMAND "${CMAKE_SOURCE_DIR}/../tools/minidef.exe" "${CMAKE_SOURCE_DIR}/game/mini.def" "${CMAKE_SOURCE_DIR}/gen/"
DEPENDS "${CMAKE_SOURCE_DIR}/game/mini.def"
OUTPUT "${CMAKE_SOURCE_DIR}/gen/Generated.h" "${CMAKE_SOURCE_DIR}/gen/Generated.cpp"
COMMENT "Genrating from minidef"
)
file(GLOB_RECURSE sources_game game/*.cpp game/*.h gen/Generated.cpp gen/Generated.h gen/Def.h gen/Def.cpp)
file(GLOB source_singleheader dependency/tinygltf/stb_image.h dependency/tinygltf/stb_image_write.h dependency/tinygltf/json.hpp dependency/tinygltf/tiny_gltf.h)
add_library(PuzGame SHARED ${sources_game} ${source_singleheader} ${imgui_sources} ${imgui_backend_sdl})
file(GLOB sources_imgui_helper dependency/imgui-bgfx/imgui-helper.h dependency/imgui-bgfx/imgui-helper.cpp)
add_library(PuzGame SHARED ${sources_game} ${source_singleheader} ${imgui_sources} ${imgui_backend_sdl} ${sources_imgui_helper})
set_property(TARGET PuzGame PROPERTY CXX_STANDARD 17)
target_include_directories(PuzGame PUBLIC dependency/imgui)
target_include_directories(PuzGame PUBLIC dependency/tracy/public)
target_include_directories(PuzGame PUBLIC dependency/imgui-bgfx)
# Profiling
option(TRACY_ENABLE "" OFF)
if (TRACY_ENABLE)
option(TRACY_ON_DEMAND "" ON)
set(TRACY_DELAYED_INIT ON)
set(TRACY_MANUAL_LIFETIME ON)
add_subdirectory("${CMAKE_SOURCE_DIR}/dependency/tracy")
endif()
# SDL
add_subdirectory("${CMAKE_SOURCE_DIR}/dependency/SDL" EXCLUDE_FROM_ALL)
@@ -43,6 +64,11 @@ SET(BGFX_BUILD_EXAMPLES OFF)
add_subdirectory("${CMAKE_SOURCE_DIR}/dependency/bgfx.cmake")
# Link
if (TRACY_ENABLE)
target_link_libraries(PuzGame bx bimg bgfx SDL3::SDL3 Tracy::TracyClient)
target_link_libraries(PuzGameEngine bx SDL3::SDL3 Tracy::TracyClient)
else()
target_link_libraries(PuzGame bx bimg bgfx SDL3::SDL3)
target_link_libraries(PuzGameEngine bx SDL3::SDL3)
endif()
set_target_properties(PuzGame PROPERTIES OUTPUT_NAME "PuzGame2")

10
src/assetcompile.ps1 Normal file
View File

@@ -0,0 +1,10 @@
$texturecPath = "..\tools\texturec.exe"
$textureAssetDir = "..\assets\textures"
$outputBaseDir = ".\textures"
Get-ChildItem -Path $textureAssetDir -File | ForEach-Object {
$outName = Join-Path -Path $outputBaseDir -ChildPath ($_.BaseName + ".ktx")
Write-Host $_.FullName $outName
& "$texturecPath" -f $_.FullName -o $outName -t RGBA8
}

View File

@@ -1 +0,0 @@
cmake --build cmake-build

1
src/build_release.ps1 Normal file
View File

@@ -0,0 +1 @@
cmake --build cmake-build-release --config Release

1
src/buildnotes.txt Normal file
View File

@@ -0,0 +1 @@
https://github.com/mstorsjo/llvm-mingw/releases

View File

@@ -1,4 +1,6 @@
cd dependency/bgfx.cmake
cmake -G "Ninja" -S . -B cmake-build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DBGFX_BUILD_TOOLS=OFF -DBGFX_BUILD_EXAMPLES=OFF
cmake -G "Ninja" -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DBGFX_BUILD_TOOLS=OFF -DBGFX_BUILD_EXAMPLES=OFF
cd ..\..
cmake -G "Ninja" -S . -B cmake-build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake -G "Ninja" -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

2
src/debug.ps1 Normal file
View File

@@ -0,0 +1,2 @@
.\build.ps1
& raddbg.exe --project:../tools/radsession.rad --auto_run -q

View File

@@ -1,4 +1,6 @@
SDL/*
# !SDL/src
# bgfx.cmake/*
bgfx.cmake/*
tinygltf/*
imgui/*
iconfontheaders/*
tracy/*

View File

@@ -1,9 +1,15 @@
cmake_minimum_required(VERSION 3.10)
project(MiniDefProj)
add_executable(minidef "${CMAKE_CURRENT_SOURCE_DIR}/src/MiniDef.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/Logging.cpp")
file(GLOB headers "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")
file(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_executable(minidef ${headers} ${sources})
set_property(TARGET minidef PROPERTY CXX_STANDARD 17)
target_include_directories(minidef PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../bgfx.cmake/bx/include/"
"${CMAKE_CURRENT_SOURCE_DIR}/../bgfx.cmake/bx/include/compat/msvc/"
)
target_link_libraries(minidef bx)
target_compile_definitions(minidef PRIVATE "$<$<CONFIG:DEBUG>:BX_CONFIG_DEBUG=1>$<$<CONFIG:RELEASE>:BX_CONFIG_DEBUG=0>")
target_compile_definitions(minidef PUBLIC "_AMD64_")

View File

@@ -1,18 +0,0 @@
#pragma once
#include <cstdint>
#define LOG(line, fmt, ...) Logging::Log(Logging::ELogType::Log, line, fmt, ##__VA_ARGS__)
#define LOG_WARN(line, fmt, ...) Logging::Log(Logging::ELogType::Warn, line, fmt, ##__VA_ARGS__)
#define LOG_ERROR(line, fmt, ...) Logging::Log(Logging::ELogType::Error, line, fmt, ##__VA_ARGS__)
struct Logging
{
enum class ELogType
{
Log,
Warn,
Error,
};
static void Log(ELogType logType, uint32_t line, const char* format, ...);
};

View File

@@ -0,0 +1,643 @@
#include "CppGen.h"
#include "Logging.h"
#include "TypeDef.h"
#include "bx/string.h"
#include <bx/file.h>
#include <cstdarg>
#include <memoryapi.h>
#include <winnt.h>
#define WIN32_LEAN_AND_MEAN
#include "Windows.h" // IWYU pragma: keep
namespace WriteTemplates
{
constexpr char FileHeaderStart[] =
R"END(#pragma once
#include <cstdint>
namespace Gen
{
struct Serializer;
struct Deserializer;
)END";
constexpr char FileCppStart[] = R"END(#include "Def.h"
#include "Generated.h"
namespace Gen
{
)END";
constexpr char StructHeader2[] =
R"END( struct %s
{
static constexpr uint16_t TypeIdx = %u;
)END";
constexpr char StructField4[] =
R"END( %s %s%s = %s;
)END";
constexpr char StructEnd[] =
R"END( };
)END";
constexpr char EnumHeader4[] =
R"END( struct %s
{
static constexpr uint16_t TypeIdx = %u;
static constexpr int32_t EntryCount = %u;
enum Enum : %s
{
)END";
constexpr char EnumField1[] =
R"END( %s,
)END";
constexpr char EnumFieldNumbered2[] =
R"END( %s = %s,
)END";
constexpr char EnumNamesStart2[] =
R"END( };
static constexpr char %s[EntryCount][%u]
{
)END";
constexpr char EnumNamesEntry1[] =
R"END( "%s",
)END";
constexpr char EnumNamesEnd[] =
R"END( };
};
)END";
constexpr char EnumFlagOperators12[] =
R"END( inline %s::Enum operator| (const %s::Enum& a, const %s::Enum& b)
{
return a | b;
}
inline %s::Enum operator& (const %s::Enum& a, const %s::Enum& b)
{
return a & b;
}
inline %s::Enum operator|= (%s::Enum& a, const %s::Enum& b)
{
return a |= b;
}
inline %s::Enum operator&= (%s::Enum& a, const %s::Enum& b)
{
return a &= b;
}
)END";
constexpr char FileEnd[] =
R"END(}
)END";
constexpr char SaveFuncHeader1[] =
R"END( bool Save(const %s* obj, uint32_t count, Serializer& serializer);
)END";
constexpr char SaveFuncBodyStart1[] = R"END( bool Save(const %s* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
)END";
constexpr char SaveFuncBodyType3[] = R"END( isOk = Save(%sobj[i].%s, %u, serializer) && isOk;
)END";
constexpr char SaveFuncBodyNative[] = R"END( isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
)END";
constexpr char SaveFuncBodyEnum1[] = R"END( auto val = (%s)obj[i];
isOk = Save(&val, 1, serializer) && isOk;
)END";
constexpr char SaveFuncBodyEnd[] = R"END( }
return isOk;
}
)END";
constexpr char LoadFuncHeader1[] =
R"END( bool Load(%s* obj, uint32_t count, Deserializer& serializer);
)END";
constexpr char LoadFuncBodyStart1[] = R"END( bool Load(%s* obj, uint32_t count, Deserializer& serializer)
{
)END";
constexpr char LoadFuncBodySetupStart2[] =
R"END( const char* typeName = Meta::Metadata.TypeDefinitions[%s::TypeIdx].Name;
// Quick match
int32_t matchedHashIdx =
serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[%s::TypeIdx].Hash);
if (matchedHashIdx >= 0)
{
assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
)END";
constexpr char LoadFuncBodyQuickLoad2[] = R"END( isOk = Load(%sobj[i].%s, %u, serializer) && isOk;
)END";
constexpr char LoadFuncBodySetupEnd[] = R"END( }
// if we're not ok here, something went really wrong
assert(isOk);
return isOk;
}
// Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
*obj = {};
int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
if (nameMatchIdx < 0)
{
// Name match failed, caller has to handle this and potentially skip some bytes
return false;
}
// Successfully matched name, but we need to follow the definition of the file now!
const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];
// Figure out new member mapping
uint64_t WriteDestinations[64];
for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
{
WriteDestinations[i] = UINT64_MAX;
}
for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
{
const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
)END";
constexpr char LoadFuncBodyMemberCheck4[] =
R"END( if (bx::strCmp(memberName, "%s") == 0 && bx::strCmp(memberTypeName, "%s") == 0)
{
WriteDestinations[i] = offsetof(%s, %s);
}
)END";
constexpr char LoadFuncBodyTypeUpgradeStart[] = R"END( }
// Start reading in file order, skipping things that we don't know by name and type
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);
for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
{
const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
if (WriteDestinations[j] == UINT64_MAX)
{
// Unknown member name or type changed
uint16_t count = bx::max(1, matchedDef.ChildArraySizes[matchedDef.ChildIndices[j]]);
serializer.Skip(childDef.Size * count);
continue;
}
)END";
constexpr char LoadFuncBodyTypeUpgradeMember3[] = R"END( if (bx::strCmp(memberName, "%s") == 0)
{
auto* fieldPtr = reinterpret_cast<%s*>(objBasePtr + WriteDestinations[j]);
uint16_t wantedCount = %u;
uint16_t existingCount = matchedDef.ChildArraySizes[j];
isOk = Load(fieldPtr, bx::min(wantedCount, existingCount), serializer) && isOk;
if (existingCount > wantedCount) serializer.Skip((existingCount - wantedCount) * childDef.Size);
continue;
}
)END";
constexpr char LoadFuncBodyTypeUpgradeEnd[] = R"END( assert(false);
}
}
assert(isOk);
return isOk;
}
)END";
constexpr char LoadFuncBodyNative[] = R"END( bool isOk = true;
for (int32_t i = 0; i < count; ++i)
{
isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
}
return isOk;
}
)END";
constexpr char LoadFuncBodyEnum2[] = R"END( bool isOk = true;
for (int32_t i = 0; i < count; ++i)
{
%s& val = (%s&)obj[i];
isOk = Load(&val, 1, serializer) && isOk;
}
return isOk;
}
)END";
constexpr char MetadataStart1[] = R"END(
namespace Meta {
constexpr uint16_t CurrentMetaVersion = 1;
struct StrRef
{
uint16_t Offset;
uint16_t Size;
};
struct TypeDef
{
uint32_t Size = 0;
uint32_t Hash = 0;
char Name[64]{"Dummy"};
uint16_t ChildCount = 0;
uint16_t ChildIndices[64]{0};
uint16_t ChildArraySizes[64]{0};
StrRef MemberNameIndices[64]{0};
};
struct MetadataTable
{
TypeDef TypeDefinitions[%u]
{
)END";
// TODO: Sizeof is wrong!! we don't use padding!!
constexpr char MetadataTypeEntry7[] = R"END( TypeDef{sizeof(%s), %u, "%s", %u, {%s}, {%s}, {%s}},
)END";
constexpr char MetadataEnd1[] = R"END( };
char MemberNameBuffer[64*64*64]{"%s"};
};
constexpr MetadataTable Metadata;
}
)END";
} // namespace WriteTemplates
void CppFileWriter::InitBuffer(WriteBuffer& buf)
{
if (buf.Data != nullptr)
{
LOG_ERROR(0, "Multiple writes not supported yet!");
return;
}
buf.Data =
reinterpret_cast<char*>(VirtualAlloc(nullptr, BufferRequestSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
if (buf.Data == nullptr)
{
LOG_ERROR(0, "Failed to allocate write memory!");
return;
}
buf.WrittenBytes = 0;
}
bool CppFileWriter::WriteBufferToDisk(const bx::FilePath& path, const WriteBuffer& buf)
{
bx::Error error;
bx::FileWriter writer;
LOG(0, "Writing to %s", path.getCPtr());
if (!writer.open(path, false, &error))
{
LOG_ERROR(0, "Failed to open output file: %s", error.getMessage().getCPtr());
return false;
}
writer.write(buf.Data, buf.WrittenBytes, &error);
if (!error.isOk())
{
LOG_ERROR(0, "Failed to write to output file: %s", error.getMessage().getCPtr());
}
writer.close();
LOG(0, "Finished writing!");
return true;
}
void CppFileWriter::WriteInternal(WriteBuffer& buf, const char* templateStr, va_list args)
{
if (buf.Data == nullptr)
{
LOG_ERROR(0, "Wrote too early!");
return;
}
buf.WrittenBytes += bx::vsnprintf(&buf.Data[buf.WrittenBytes], BufferRequestSize, templateStr, args);
}
void CppFileWriter::PrintTypeName(
char* buf, int32_t bufSize, Def::TypeRef type, const Def::DefinitionFile& definitions, PrintFlags flags)
{
if (buf == nullptr || bufSize == 0 || !IsValid(type))
{
LOG_ERROR(0, "Invalid PrintTypeName call!");
return;
}
if (type.FieldKind == Def::EFieldType::DefinedClass)
{
auto& t = definitions.Types[type.TypeIdx];
if (((uint32_t)t.TypeFlags & (uint32_t)Def::ETypeFlags::IsNative) &&
((uint32_t)flags & (uint32_t)PrintFlags::PrintNativeTypeName))
{
bx::strCopy(buf, bufSize, t.NativeCName);
}
else
{
bx::strCopy(buf, bufSize, t.Name);
}
}
else if (type.FieldKind == Def::EFieldType::DefinedEnum)
{
bx::strCopy(buf, bufSize, definitions.Enums[type.TypeIdx].Name);
if ((int32_t)flags & (int32_t)PrintFlags::PrintFullEnumName)
{
bx::strCat(buf, bufSize, "::Enum");
}
}
}
void CppFileWriter::WriteEnums(const Def::DefinitionFile& definitions)
{
for (int32_t enumIdx = 0; enumIdx < definitions.EnumCount; ++enumIdx)
{
const Def::Enum& e = definitions.Enums[enumIdx];
bool isFlagsEnum = (uint32_t)e.EnumFlags & (uint32_t)Def::EEnumFlags::FlagsEnum;
if (!IsValid(e.EnumType))
{
LOG_ERROR(0, "Invalid enum type (enum %i)", enumIdx);
continue;
}
char enumTypeNameBuf[Def::MaxNameLength]{0};
PrintTypeName(enumTypeNameBuf, sizeof(enumTypeNameBuf), e.EnumType, definitions);
Write(WriteTemplates::EnumHeader4, e.Name, definitions.TypeCount + enumIdx, e.EntryCount, enumTypeNameBuf);
for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx)
{
if (isFlagsEnum)
{
char numBuf[64] = "0";
if (entryIdx > 0)
{
bx::snprintf(numBuf, sizeof(numBuf), "1 << %u", entryIdx - 1);
}
Write(WriteTemplates::EnumFieldNumbered2, e.EntryNames[entryIdx], numBuf);
}
else
{
Write(WriteTemplates::EnumField1, e.EntryNames[entryIdx]);
}
}
Write(WriteTemplates::EnumNamesStart2, "EntryNames", Def::MaxNameLength);
for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx)
{
Write(WriteTemplates::EnumNamesEntry1, e.EntryNames[entryIdx]);
}
for (int32_t extraIdx = 0; extraIdx < e.ExtraStringFieldCount; ++extraIdx)
{
Write(WriteTemplates::EnumNamesStart2, e.ExtraStringFieldNames[extraIdx], Def::MaxNameLength);
for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx)
{
Write(WriteTemplates::EnumNamesEntry1, e.ExtraStringFields[entryIdx][extraIdx]);
}
}
Write(WriteTemplates::EnumNamesEnd);
if (isFlagsEnum)
{
Write(WriteTemplates::EnumFlagOperators12,
e.Name,
e.Name,
e.Name,
e.Name,
e.Name,
e.Name,
e.Name,
e.Name,
e.Name,
e.Name,
e.Name,
e.Name);
}
}
}
void CppFileWriter::WriteTypes(const Def::DefinitionFile& definitions)
{
for (int32_t typeIdx = 0; typeIdx < definitions.TypeCount; ++typeIdx)
{
const Def::Type& t = definitions.Types[typeIdx];
if ((uint32_t)t.TypeFlags & (uint32_t)Def::ETypeFlags::IsNative) continue;
Write(WriteTemplates::StructHeader2, t.Name, typeIdx);
for (int32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx)
{
char Type[64]{0};
PrintTypeName(Type, sizeof(Type), t.FieldTypes[fieldIdx], definitions);
char Array[32]{0};
uint32_t ArraySize = t.FieldArraySizes[fieldIdx];
if (ArraySize > 0 && ArraySize != UINT32_MAX)
{
bx::snprintf(Array, sizeof(Array), "[%u]", ArraySize);
}
Write(WriteTemplates::StructField4, Type, t.FieldNames[fieldIdx], Array, t.FieldValues[fieldIdx]);
}
Write(WriteTemplates::StructEnd);
}
}
void CppFileWriter::WriteSaveLoadMethods(const Def::DefinitionFile& definitions)
{
char nameBuf[64]{0};
char fieldBuf[64]{0};
for (int32_t enumIdx = 0; enumIdx < definitions.EnumCount; ++enumIdx)
{
const Def::Enum& e = definitions.Enums[enumIdx];
if (!IsValid(e.EnumType))
{
LOG_ERROR(0, "Invalid enum!");
continue;
}
bx::snprintf(nameBuf, sizeof(nameBuf), "%s::Enum", e.Name);
PrintTypeName(fieldBuf, sizeof(fieldBuf), e.EnumType, definitions);
Write(WriteTemplates::SaveFuncHeader1, nameBuf);
Write(WriteTemplates::LoadFuncHeader1, nameBuf);
WriteCpp(WriteTemplates::SaveFuncBodyStart1, nameBuf);
WriteCpp(WriteTemplates::SaveFuncBodyEnum1, fieldBuf);
WriteCpp(WriteTemplates::SaveFuncBodyEnd);
WriteCpp(WriteTemplates::LoadFuncBodyStart1, nameBuf);
WriteCpp(WriteTemplates::LoadFuncBodyEnum2, fieldBuf, fieldBuf);
}
for (uint16_t typeIdx = 0; typeIdx < definitions.TypeCount; ++typeIdx)
{
const Def::Type& t = definitions.Types[typeIdx];
char typeName[Def::MaxNameLength]{0};
PrintTypeName(typeName, sizeof(typeName), {typeIdx, Def::EFieldType::DefinedClass}, definitions);
Write(WriteTemplates::SaveFuncHeader1, typeName);
Write(WriteTemplates::LoadFuncHeader1, typeName);
WriteCpp(WriteTemplates::SaveFuncBodyStart1, typeName);
if ((int32_t)t.TypeFlags & (int32_t)Def::ETypeFlags::IsNative)
{
WriteCpp(WriteTemplates::SaveFuncBodyNative);
}
else
{
for (int32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx)
{
WriteCpp(WriteTemplates::SaveFuncBodyType3,
t.FieldArraySizes[fieldIdx] > 0 ? "" : "&",
t.FieldNames[fieldIdx],
bx::max(1, t.FieldArraySizes[fieldIdx]));
}
}
WriteCpp(WriteTemplates::SaveFuncBodyEnd);
WriteCpp(WriteTemplates::LoadFuncBodyStart1, typeName);
if ((int32_t)t.TypeFlags & (int32_t)Def::ETypeFlags::IsNative)
{
WriteCpp(WriteTemplates::LoadFuncBodyNative);
}
else
{
WriteCpp(WriteTemplates::LoadFuncBodySetupStart2, typeName, typeName);
for (int32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx)
{
WriteCpp(WriteTemplates::LoadFuncBodyQuickLoad2,
t.FieldArraySizes[fieldIdx] > 0 ? "" : "&",
t.FieldNames[fieldIdx],
bx::max(1, t.FieldArraySizes[fieldIdx]));
}
WriteCpp(WriteTemplates::LoadFuncBodySetupEnd);
char fieldTypeName[Def::MaxNameLength]{0};
for (int32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx)
{
const char* fieldName = t.FieldNames[fieldIdx];
// This prints native type name, seems wrong??
// Havent i already fixed this???
PrintTypeName(
fieldTypeName, sizeof(fieldTypeName), t.FieldTypes[fieldIdx], definitions, PrintFlags::None);
WriteCpp(WriteTemplates::LoadFuncBodyMemberCheck4, fieldName, fieldTypeName, typeName, fieldName);
}
WriteCpp(WriteTemplates::LoadFuncBodyTypeUpgradeStart);
for (uint32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx)
{
const char* fieldName = t.FieldNames[fieldIdx];
PrintTypeName(fieldTypeName, sizeof(fieldTypeName), t.FieldTypes[fieldIdx], definitions);
WriteCpp(WriteTemplates::LoadFuncBodyTypeUpgradeMember3,
fieldName,
fieldTypeName,
bx::max(1, t.FieldArraySizes[fieldIdx]));
}
WriteCpp(WriteTemplates::LoadFuncBodyTypeUpgradeEnd);
}
}
}
namespace
{
char MemberNameBuffer[64 * 64 * 64]{0};
}
void CppFileWriter::WriteMetadata(const Def::DefinitionFile& definitions)
{
uint32_t memberNameBufferIdx = 0;
Write(WriteTemplates::MetadataStart1, definitions.TypeCount + definitions.EnumCount);
for (uint16_t i = 0; i < definitions.TypeCount; ++i)
{
auto& type = definitions.Types[i];
char fieldStr[256]{0};
for (int32_t j = 0; j < type.FieldCount; ++j)
{
if (j != 0) bx::strCat(fieldStr, sizeof(fieldStr), ", ");
char numBuf[8]{0};
int32_t idx = type.FieldTypes[j].TypeIdx;
if (type.FieldTypes[j].FieldKind == Def::EFieldType::DefinedEnum) idx += definitions.TypeCount;
bx::snprintf(numBuf, sizeof(numBuf), "%u", idx);
bx::strCat(fieldStr, sizeof(fieldStr), numBuf);
}
char typeStr[Def::MaxNameLength]{0};
PrintTypeName(typeStr, sizeof(typeStr), {i, Def::EFieldType::DefinedClass}, definitions);
char memberIdxStr[256]{0};
for (int32_t j = 0; j < type.FieldCount; ++j)
{
if (j != 0) bx::strCat(memberIdxStr, sizeof(memberIdxStr), ", ");
char numBuf[16]{0};
bx::snprintf(numBuf, sizeof(numBuf), "{%u, %u}", memberNameBufferIdx, bx::strLen(type.FieldNames[j]));
bx::strCat(memberIdxStr, sizeof(memberIdxStr), numBuf);
memberNameBufferIdx += bx::strCopy(&MemberNameBuffer[memberNameBufferIdx],
sizeof(MemberNameBuffer) - memberNameBufferIdx,
type.FieldNames[j]);
}
char arrStr[256]{0};
for (int32_t j = 0; j < type.FieldCount; ++j)
{
if (j != 0) bx::strCat(arrStr, sizeof(arrStr), ", ");
char numBuf[16]{0};
bx::snprintf(numBuf, sizeof(numBuf), "%u", type.FieldArraySizes[j]);
bx::strCat(arrStr, sizeof(arrStr), numBuf);
}
Write(WriteTemplates::MetadataTypeEntry7,
typeStr,
type.Hash,
type.Name,
type.FieldCount,
fieldStr,
arrStr,
memberIdxStr);
}
for (uint16_t i = 0; i < definitions.EnumCount; ++i)
{
auto& enumType = definitions.Enums[i];
char typeStr[Def::MaxNameLength]{0};
PrintTypeName(typeStr, sizeof(typeStr), {i, Def::EFieldType::DefinedEnum}, definitions);
Write(WriteTemplates::MetadataTypeEntry7, typeStr, enumType.Hash, enumType.Name, 0, "", "", "");
}
Write(WriteTemplates::MetadataEnd1, MemberNameBuffer);
}
void CppFileWriter::GenerateCpp(const bx::FilePath& outDir, const Def::DefinitionFile& definitions)
{
LOG(0, "Generating output files...");
InitBuffer(HeaderWrite);
InitBuffer(CppWrite);
Write(WriteTemplates::FileHeaderStart);
WriteCpp(WriteTemplates::FileCppStart);
WriteEnums(definitions);
WriteTypes(definitions);
WriteSaveLoadMethods(definitions);
WriteMetadata(definitions);
Write(WriteTemplates::FileEnd);
WriteCpp(WriteTemplates::FileEnd);
// std::filesystem::create_directory(outDir.getCPtr());
bx::FilePath writePathHeader = outDir;
bx::FilePath writePathCpp = outDir;
writePathHeader.join("Generated.h");
writePathCpp.join("Generated.cpp");
WriteBufferToDisk(writePathHeader, HeaderWrite);
WriteBufferToDisk(writePathCpp, CppWrite);
}
void CppFileWriter::Write(const char* templateStr, ...)
{
va_list args;
va_start(args, templateStr);
WriteInternal(HeaderWrite, templateStr, args);
va_end(args);
}
void CppFileWriter::WriteCpp(const char* templateStr, ...)
{
va_list args;
va_start(args, templateStr);
WriteInternal(CppWrite, templateStr, args);
va_end(args);
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include "TypeDef.h"
#include "bx/filepath.h"
struct WriteBuffer
{
char* Data = nullptr;
uint64_t WrittenBytes = 0;
};
enum class PrintFlags : uint32_t
{
None = 0,
PrintFullEnumName = 1 << 0,
PrintNativeTypeName = 1 << 1,
};
struct CppFileWriter
{
private:
static constexpr size_t BufferRequestSize = 1024 * 1024 * 1024;
WriteBuffer HeaderWrite;
WriteBuffer CppWrite;
void InitBuffer(WriteBuffer& buf);
bool WriteBufferToDisk(const bx::FilePath& path, const WriteBuffer& buf);
void WriteInternal(WriteBuffer& buf, const char* templateStr, va_list args);
public:
void PrintTypeName(char* buf,
int32_t bufSize,
Def::TypeRef type,
const Def::DefinitionFile& definitions,
PrintFlags flags = (PrintFlags)((uint32_t)PrintFlags::PrintFullEnumName |
(uint32_t)PrintFlags::PrintNativeTypeName));
void WriteEnums(const Def::DefinitionFile& definitions);
void WriteTypes(const Def::DefinitionFile& definitions);
void WriteSaveLoadMethods(const Def::DefinitionFile& definitions);
void WriteMetadata(const Def::DefinitionFile& definitions);
void GenerateCpp(const bx::FilePath& outDir, const Def::DefinitionFile& definitions);
void Write(const char* templateStr, ...);
void WriteCpp(const char* templateStr, ...);
};

View File

@@ -1,2 +0,0 @@
#pragma once
#include <cstdint>

View File

@@ -0,0 +1,260 @@
#include "Def.h"
#include "Generated.h"
namespace Gen
{
bool Save(const KnownType::Enum* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
auto val = (uint8_t)obj[i];
isOk = Save(&val, 1, serializer) && isOk;
}
return isOk;
}
bool Load(KnownType::Enum* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
uint8_t& val = (uint8_t&)obj[i];
isOk = Load(&val, 1, serializer) && isOk;
}
return isOk;
}
bool Save(const int8_t* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(int8_t* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const int16_t* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(int16_t* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const int32_t* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(int32_t* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const int64_t* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(int64_t* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const uint8_t* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(uint8_t* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const uint16_t* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(uint16_t* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const uint32_t* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(uint32_t* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const uint64_t* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(uint64_t* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const bool* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(bool* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const float* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(float* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const double* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(double* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const char* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Load(char* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
}
return isOk;
}
bool Save(const Test* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
}
return isOk;
}
bool Load(Test* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
}
return isOk;
}
bool Save(const Texture* obj, uint32_t count, Serializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
isOk = Save(&obj[i].T, 1, serializer) && isOk;
}
return isOk;
}
bool Load(Texture* obj, uint32_t count, Deserializer& serializer)
{
bool isOk = true;
for (uint32_t i = 0; i < count; ++i)
{
isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
isOk = Load(&obj[i].T, 1, serializer) && isOk;
}
return isOk;
}
}

View File

@@ -1,23 +1,15 @@
#pragma once
#include "Def.h"
#include <cstdint>
namespace Generated
namespace Gen
{
struct Test
{
uint32_t Number = {};
};
struct Texture
{
uint32_t Width = {};
uint32_t Height = {};
char StrTest[3] = {};
Test T = {};
};
struct Serializer;
struct Deserializer;
struct KnownType
{
static constexpr uint16_t EnumIdx = 0;
static constexpr int32_t EntryCount = 12;
enum class Enum : int32_t
enum Enum : uint8_t
{
i8,
i16,
@@ -63,4 +55,91 @@ namespace Generated
"char",
};
};
struct Test
{
static constexpr uint16_t TypeIdx = 12;
uint32_t Number = {};
};
struct Texture
{
static constexpr uint16_t TypeIdx = 13;
uint32_t Width = {};
uint32_t Height = {};
char StrTest[3] = {};
Test T = {};
};
bool Save(const KnownType::Enum* obj, uint32_t count, Serializer& serializer);
bool Load(KnownType::Enum* obj, uint32_t count, Deserializer& serializer);
bool Save(const int8_t* obj, uint32_t count, Serializer& serializer);
bool Load(int8_t* obj, uint32_t count, Deserializer& serializer);
bool Save(const int16_t* obj, uint32_t count, Serializer& serializer);
bool Load(int16_t* obj, uint32_t count, Deserializer& serializer);
bool Save(const int32_t* obj, uint32_t count, Serializer& serializer);
bool Load(int32_t* obj, uint32_t count, Deserializer& serializer);
bool Save(const int64_t* obj, uint32_t count, Serializer& serializer);
bool Load(int64_t* obj, uint32_t count, Deserializer& serializer);
bool Save(const uint8_t* obj, uint32_t count, Serializer& serializer);
bool Load(uint8_t* obj, uint32_t count, Deserializer& serializer);
bool Save(const uint16_t* obj, uint32_t count, Serializer& serializer);
bool Load(uint16_t* obj, uint32_t count, Deserializer& serializer);
bool Save(const uint32_t* obj, uint32_t count, Serializer& serializer);
bool Load(uint32_t* obj, uint32_t count, Deserializer& serializer);
bool Save(const uint64_t* obj, uint32_t count, Serializer& serializer);
bool Load(uint64_t* obj, uint32_t count, Deserializer& serializer);
bool Save(const bool* obj, uint32_t count, Serializer& serializer);
bool Load(bool* obj, uint32_t count, Deserializer& serializer);
bool Save(const float* obj, uint32_t count, Serializer& serializer);
bool Load(float* obj, uint32_t count, Deserializer& serializer);
bool Save(const double* obj, uint32_t count, Serializer& serializer);
bool Load(double* obj, uint32_t count, Deserializer& serializer);
bool Save(const char* obj, uint32_t count, Serializer& serializer);
bool Load(char* obj, uint32_t count, Deserializer& serializer);
bool Save(const Test* obj, uint32_t count, Serializer& serializer);
bool Load(Test* obj, uint32_t count, Deserializer& serializer);
bool Save(const Texture* obj, uint32_t count, Serializer& serializer);
bool Load(Texture* obj, uint32_t count, Deserializer& serializer);
namespace Meta {
struct TypeDef
{
uint32_t Size = 0;
uint32_t Hash = 0;
char Name[64]{"Dummy"};
uint16_t ChildCount = 0;
uint16_t ChildIndices[64]{0};
};
struct EnumDef
{
uint32_t Size = 0;
uint32_t Hash = 0;
};
struct MetadataTable
{
TypeDef TypeDefinitions[14]
{
TypeDef{sizeof(int8_t), 0, "i8", 0, {}},
TypeDef{sizeof(int16_t), 1, "i16", 0, {}},
TypeDef{sizeof(int32_t), 2, "i32", 0, {}},
TypeDef{sizeof(int64_t), 3, "i64", 0, {}},
TypeDef{sizeof(uint8_t), 4, "u8", 0, {}},
TypeDef{sizeof(uint16_t), 5, "u16", 0, {}},
TypeDef{sizeof(uint32_t), 6, "u32", 0, {}},
TypeDef{sizeof(uint64_t), 7, "u64", 0, {}},
TypeDef{sizeof(bool), 8, "b", 0, {}},
TypeDef{sizeof(float), 9, "f32", 0, {}},
TypeDef{sizeof(double), 10, "f64", 0, {}},
TypeDef{sizeof(char), 11, "str", 0, {}},
TypeDef{sizeof(Test), 273256278, "Test", 1, {6}},
TypeDef{sizeof(Texture), 992460010, "Texture", 4, {6, 6, 11, 12}},
};
EnumDef EnumDefinitions[1]
{
EnumDef{sizeof(KnownType::Enum), 2983807453},
};
};
constexpr MetadataTable Metadata;
}
}

View File

@@ -1,163 +1,13 @@
#include "CppGen.h"
#include "Gen/Generated.h"
#include "Logging.h"
#include "MiniDef.h"
#include "TypeDef.h"
#include "bx/hash.h"
#include "bx/string.h"
#include <bx/filepath.h>
#include <ctype.h>
#include <filesystem>
namespace WriteTemplates
{
constexpr char FileHeader[] =
R"END(#pragma once
#include "Def.h"
namespace Generated
{
)END";
constexpr char StructHeader1[] =
R"END( struct %s
{
)END";
constexpr char StructField4[] =
R"END( %s %s%s = %s;
)END";
constexpr char StructEnd[] =
R"END( };
)END";
constexpr char EnumHeader3[] =
R"END( struct %s
{
static constexpr int32_t EntryCount = %u;
enum class Enum : %s
{
)END";
constexpr char EnumField1[] =
R"END( %s,
)END";
constexpr char EnumNamesStart2[] =
R"END( };
static constexpr char %s[EntryCount][%u]
{
)END";
constexpr char EnumNamesEntry1[] =
R"END( "%s",
)END";
constexpr char EnumNamesEnd[] =
R"END( };
};
)END";
constexpr char FileEnd[] =
R"END(}
)END";
} // namespace WriteTemplates
void DefinitionFile::PrintTypeName(char* buf, int32_t bufSize, const Def::FieldType& type)
{
if (type.FieldKind == Def::EFieldType::Native)
{
if (int32_t(type.Native) < Generated::KnownType::EntryCount)
{
bx::strCopy(buf, bufSize, Generated::KnownType::CName[size_t(type.Native)]);
}
else
{
LOG_ERROR(0, "Unknown native type index: %u", type.Native);
}
}
else if (type.FieldKind == Def::EFieldType::DefinedClass)
{
bx::strCopy(buf, bufSize, Types[type.TypeIdx].Name);
}
else if (type.FieldKind == Def::EFieldType::DefinedEnum)
{
bx::strCopy(buf, bufSize, Enums[type.TypeIdx].Name);
}
}
void DefinitionFile::WriteEnums(TemplateWriter& Template)
{
for (int32_t enumIdx = 0; enumIdx < EnumCount; ++enumIdx)
{
Def::Enum& e = Enums[enumIdx];
Template.Write(
WriteTemplates::EnumHeader3, e.Name, e.EntryCount, Generated::KnownType::CName[(int32_t)e.EnumType.Native]);
for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx)
{
Template.Write(WriteTemplates::EnumField1, e.EntryNames[entryIdx]);
}
Template.Write(WriteTemplates::EnumNamesStart2, "EntryNames", Def::MaxNameLength);
for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx)
{
Template.Write(WriteTemplates::EnumNamesEntry1, e.EntryNames[entryIdx]);
}
for (int32_t extraIdx = 0; extraIdx < e.ExtraStringFieldCount; ++extraIdx)
{
Template.Write(WriteTemplates::EnumNamesStart2, e.ExtraStringFieldNames[extraIdx], Def::MaxNameLength);
for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx)
{
Template.Write(WriteTemplates::EnumNamesEntry1, e.ExtraStringFields[entryIdx][extraIdx]);
}
}
Template.Write(WriteTemplates::EnumNamesEnd);
}
}
void DefinitionFile::WriteTypes(TemplateWriter& Template)
{
for (int32_t typeIdx = 0; typeIdx < TypeCount; ++typeIdx)
{
Def::Type& t = Types[typeIdx];
Template.Write(WriteTemplates::StructHeader1, t.Name);
for (int32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx)
{
char Type[64]{0};
PrintTypeName(Type, sizeof(Type), t.FieldTypes[fieldIdx]);
char Array[32]{0};
uint32_t ArraySize = t.FieldArraySizes[fieldIdx];
if (ArraySize > 0)
{
bx::snprintf(Array, sizeof(Array), "[%u]", ArraySize);
}
Template.Write(WriteTemplates::StructField4, Type, t.FieldNames[fieldIdx], Array, "{}");
}
Template.Write(WriteTemplates::StructEnd);
}
}
void DefinitionFile::GenerateCpp(const bx::FilePath& outDir)
{
LOG(0, "Generating...");
bx::Error error;
bx::FileWriter writer;
bx::FilePath writePath = outDir;
std::filesystem::create_directory(outDir.getCPtr());
writePath.join("Generated.h");
writer.open(writePath, false, &error);
TemplateWriter Template{writer, error};
Template.Write(WriteTemplates::FileHeader);
WriteEnums(Template);
WriteTypes(Template);
Template.Write(WriteTemplates::FileEnd);
writer.close();
}
bool Parser::CmpAdvance(bx::StringView expect, Result& Res, bool RequireGap)
{
@@ -264,6 +114,8 @@ Parser::Result Parser::Parse()
{
ReadPtr = &Buffer[0];
Parser::Result Res = Parser::OK;
CHECK(LoadNativeTypes());
while (Res == Parser::OK)
{
Res = SkipWhitespace();
@@ -286,6 +138,7 @@ Parser::Result Parser::Parse()
Parser::Result Parser::HandleFileStart()
{
Result Res = OK;
if (CmpAdvance("type", Res, true) && Res == OK)
{
return HandleType();
@@ -302,22 +155,41 @@ Parser::Result Parser::HandleFileStart()
return Error;
}
Parser::Result Parser::LoadNativeTypes()
{
for (int32_t i = 0; i < Gen::KnownType::EntryCount; ++i)
{
auto& t = Definitions.Types[i];
t.Hash = i; // TODO!
bx::strCopy(t.Name, sizeof(t.Name), Gen::KnownType::EntryNames[i]);
bx::strCopy(t.NativeCName, sizeof(t.NativeCName), Gen::KnownType::CName[i]);
t.TypeFlags = Def::ETypeFlags::IsNative;
++Definitions.TypeCount;
}
return OK;
}
Parser::Result Parser::HandleType()
{
Result Res = OK;
if (!Stack.Push(TokenType::TypeKeyword)) return Error;
CHECK(SkipWhitespace());
if (Definitions.TypeCount >= DefinitionFile::MaxTypes)
if (Definitions.TypeCount >= Def::DefinitionFile::MaxTypes)
{
LOG_ERROR(Line, "Too many types!");
ErrorLine();
return Error;
}
Def::Type& t = Definitions.Types[Definitions.TypeCount];
CHECK(ReadName(t.Name));
for (int32_t i = 0; i < Def::MaxFields; ++i)
{
bx::strCopy(t.FieldValues[i], sizeof(t.FieldValues[i]), "{}");
}
CHECK(ReadName(t.Name));
CHECK(SkipWhitespace());
CHECK(ExpectChar("{"));
CHECK(SkipWhitespace());
@@ -336,7 +208,7 @@ Parser::Result Parser::HandleType()
bool NewLine = false;
CHECK(SkipWhitespace(&NewLine));
if (!NewLine)
while (!NewLine)
{
Result Res;
if (CmpAdvance("Arr", Res, true) && Res == Result::OK)
@@ -345,9 +217,37 @@ Parser::Result Parser::HandleType()
CHECK(ReadUint(t.FieldArraySizes[t.FieldCount]));
CHECK(ExpectChar(")"));
}
else if (CmpAdvance("ArrDynamic", Res, true) && Res == Result::OK)
{
CHECK(ExpectChar("("));
CHECK(ReadUint(t.FieldArraySizes[t.FieldCount]));
t.FieldFlags[t.FieldCount] = Def::ETypeFieldFlags::DynamicArray;
CHECK(ExpectChar(")"));
}
else if (CmpAdvance("Default", Res, true) && Res == OK)
{
CHECK(ExpectChar("("));
CHECK(ExpectChar("\""));
int32_t Remaining = bx::min(GetRemaining(), (int32_t)BX_COUNTOF(Def::Type::FieldValues[0]));
for (int32_t i = 0; i < Remaining; ++i)
{
if (*ReadPtr != '\\' && CmpAdvance("\"", Res)) break;
t.FieldValues[t.FieldCount][i] = *ReadPtr;
ReadPtr++;
}
if (Res != OK) return Res;
CHECK(ExpectChar(")"));
}
else
{
LOG_ERROR(Line, "Unknown token!");
ErrorLine();
return Error;
}
CHECK(SkipWhitespace(&NewLine));
}
CHECK(SkipWhitespace());
t.FieldCount++;
}
Definitions.TypeCount++;
@@ -360,7 +260,7 @@ Parser::Result Parser::HandleEnum()
if (!Stack.Push(TokenType::EnumKeyword)) return Error;
CHECK(SkipWhitespace());
if (Definitions.EnumCount >= DefinitionFile::MaxTypes)
if (Definitions.EnumCount >= Def::DefinitionFile::MaxTypes)
{
LOG_ERROR(Line, "Too many enums!");
ErrorLine();
@@ -373,12 +273,21 @@ Parser::Result Parser::HandleEnum()
if (CmpAdvance("(", Res) && Res == OK)
{
CHECK(SkipWhitespace());
Def::FieldType field;
CHECK(ReadNativeFieldType(field));
CHECK(ReadDefinedFieldType(e.EnumType));
CHECK(SkipWhitespace());
CHECK(ExpectChar(")"));
CHECK(SkipWhitespace());
}
else
{
e.EnumType = {(uint16_t)Gen::KnownType::i32, Def::EFieldType::DefinedClass};
}
if (CmpAdvance("Flags", Res) && Res == OK)
{
e.EnumFlags = (Def::EEnumFlags)((uint32_t)e.EnumFlags | (uint32_t)Def::EEnumFlags::FlagsEnum);
CHECK(SkipWhitespace());
}
CHECK(ExpectChar("{"));
CHECK(SkipWhitespace());
@@ -462,29 +371,13 @@ Parser::Result Parser::ReadTypeToken()
{
Result Res = OK;
Def::Type& t = Definitions.Types[Definitions.TypeCount];
if (ReadNativeFieldType(t.FieldTypes[t.FieldCount]) == OK) return OK;
if (ReadDefinedFieldType(t.FieldTypes[t.FieldCount]) == OK) return OK;
LOG_ERROR(Line, "Unknown type token!");
ErrorLine();
return Error;
}
Parser::Result Parser::ReadNativeFieldType(Def::FieldType& FieldT)
{
Result Res = OK;
for (int32_t i = 0; i < Generated::KnownType::EntryCount; ++i)
{
if (CmpAdvance(Generated::KnownType::EntryNames[i], Res, true) && Res == OK)
{
FieldT.FieldKind = Def::EFieldType::Native;
FieldT.Native = Generated::KnownType::Enum(i);
return OK;
}
}
return Error;
}
Parser::Result Parser::ReadDefinedFieldType(Def::FieldType& FieldT)
Parser::Result Parser::ReadDefinedFieldType(Def::TypeRef& FieldT)
{
Result Res = OK;
for (uint16_t i = 0; i < Definitions.TypeCount; ++i)
@@ -529,9 +422,8 @@ Parser::Result Parser::ReadOptionalEnumValues(Def::Enum& Enum, int32_t EntryIdx)
int32_t Remaining = GetRemaining();
for (int32_t i = 0; i < Remaining; ++i)
{
if (CmpAdvance("\"", Res)) break;
Enum.ExtraStringFields[extraIdx][EntryIdx][i] = *ReadPtr;
Enum.ExtraStringFields[EntryIdx][extraIdx][i] = *ReadPtr;
ReadPtr++;
}
if (Res != OK) return Res;
@@ -552,22 +444,65 @@ int32_t Parser::GetRemaining()
{
return &Buffer[BufferSize] - ReadPtr;
}
char WriteBuffer[1024 * 1024]{0};
void TemplateWriter::Write(const char* Template, ...)
uint32_t Parser::CalculateTypeHash(const Def::Type& t)
{
va_list ArgList;
va_start(ArgList, Template);
int32_t Count = bx::vsnprintf(WriteBuffer, sizeof(WriteBuffer), Template, ArgList);
va_end(ArgList);
bx::write(&Writer, WriteBuffer, Count, &Error);
bx::HashMurmur2A hash;
hash.begin();
for (int32_t i = 0; i < t.FieldCount; ++i)
{
hash.add(t.FieldNames[i]);
hash.add(t.FieldArraySizes[i]);
Def::EFieldType fieldType = t.FieldTypes[i].FieldKind;
if (fieldType == Def::EFieldType::DefinedClass)
{
Def::Type& dependType = Definitions.Types[t.FieldTypes[i].TypeIdx];
if (dependType.Hash == 0)
{
CalculateTypeHash(dependType);
}
hash.add(dependType.Hash);
}
else if (fieldType == Def::EFieldType::DefinedEnum)
{
hash.add(Definitions.Enums[t.FieldTypes[i].TypeIdx].Hash);
}
else
{
LOG_ERROR(Line, "TODO!");
}
}
return hash.end();
}
void Parser::CalculateHashes()
{
for (int32_t i = 0; i < Definitions.EnumCount; ++i)
{
Def::Enum& e = Definitions.Enums[i];
if (!IsValid(e.EnumType))
{
LOG_ERROR(0, "Invalid enum type at idx %i", i);
continue;
}
bx::HashMurmur2A hash;
hash.begin();
hash.add(Definitions.Types[e.EnumType.TypeIdx].Hash); // TODO: add enum entries?
e.Hash = hash.end();
}
for (int32_t i = 0; i < Definitions.TypeCount; ++i)
{
Def::Type& t = Definitions.Types[i];
if (t.Hash == 0)
{
t.Hash = CalculateTypeHash(t);
}
}
}
Parser TestParser;
Parser FileParser;
CppFileWriter Writer;
int main(int argc, const char** argv)
{
LOG(0, "Hello");
if (argc != 3)
{
LOG(0, "invalid number of arguments!");
@@ -584,8 +519,9 @@ int main(int argc, const char** argv)
return 1;
}
TestParser.BufferSize = bx::read(&reader, TestParser.Buffer, Parser::MaxBufferSize, &error);
Parser::Result Res = TestParser.Parse();
LOG(0, "Reading from %s", defPath.getCPtr());
FileParser.BufferSize = bx::read(&reader, FileParser.Buffer, Parser::MaxBufferSize, &error);
Parser::Result Res = FileParser.Parse();
if (Res == Parser::OK)
{
@@ -597,7 +533,9 @@ int main(int argc, const char** argv)
return 1;
}
LOG(0, "Finished parsing!");
TestParser.Definitions.GenerateCpp(outPath);
FileParser.CalculateHashes();
Writer.GenerateCpp(outPath, FileParser.Definitions);
return 0;
}

View File

@@ -1,12 +1,11 @@
#pragma once
#include "bx/string.h"
#include <bx/file.h>
#include <cctype>
#include <cstdint>
#include <string>
#include "Gen/Generated.h"
#include "Logging.h"
#include "bx/string.h"
#include "TypeDef.h"
enum class TokenType : uint8_t
{
@@ -50,73 +49,6 @@ struct ParseStack
}
};
class TemplateWriter
{
private:
bx::WriterI& Writer;
bx::Error& Error;
public:
TemplateWriter(bx::WriterI& W, bx::Error& E) : Writer(W), Error(E)
{
}
void Write(const char* Template, ...);
};
namespace Def
{
constexpr int32_t MaxNameLength = 64;
constexpr int32_t MaxFields = 64;
constexpr int32_t MaxExtraEnumFields = 1;
enum class EFieldType
{
Native,
DefinedClass,
DefinedEnum
};
struct FieldType
{
EFieldType FieldKind = EFieldType::Native;
Generated::KnownType::Enum Native = Generated::KnownType::Enum::i32;
uint16_t TypeIdx = UINT16_MAX;
};
struct Type
{
int32_t FieldCount = 0;
FieldType FieldTypes[MaxFields];
char FieldNames[MaxFields][MaxNameLength];
uint32_t FieldArraySizes[MaxFields]{0};
char Name[MaxNameLength]{0};
};
struct Enum
{
FieldType EnumType;
int32_t EntryCount = 0;
char EntryNames[MaxFields][MaxNameLength];
int32_t ExtraStringFieldCount = 0;
char ExtraStringFieldNames[MaxExtraEnumFields][MaxNameLength];
char ExtraStringFields[MaxFields][MaxExtraEnumFields][MaxNameLength];
char Name[MaxNameLength]{0};
};
} // namespace Def
struct DefinitionFile
{
static constexpr int32_t MaxTypes = 256;
void PrintTypeName(char* buf, int32_t bufSize, const Def::FieldType& type);
void WriteEnums(TemplateWriter& Template);
void WriteTypes(TemplateWriter& Template);
void GenerateCpp(const bx::FilePath& outDir);
Def::Type Types[MaxTypes];
uint16_t TypeCount = 0;
Def::Enum Enums[MaxTypes];
int32_t EnumCount = 0;
};
class Parser
{
public:
@@ -139,11 +71,12 @@ class Parser
char* ReadPtr = &Buffer[0];
uint32_t Line = 0;
DefinitionFile Definitions;
Def::DefinitionFile Definitions;
ParseStack Stack;
public:
Result Parse();
void CalculateHashes();
private:
int32_t GetRemaining();
@@ -153,15 +86,17 @@ class Parser
Result ExpectChar(bx::StringView expect);
Result Advance(int32_t Amount);
Result HandleFileStart();
Result LoadNativeTypes();
Result HandleType();
Result HandleEnum();
Result ReadName(char* Target);
Result ReadUint(uint32_t& Out);
Result ReadNativeFieldType(Def::FieldType& FieldT);
Result ReadDefinedFieldType(Def::FieldType& FieldT);
Result ReadDefinedFieldType(Def::TypeRef& FieldT);
Result ReadTypeToken();
Result ReadOptionalEnumValues(Def::Enum& Enum, int32_t EntryIdx);
uint32_t CalculateTypeHash(const Def::Type& t);
void ErrorLine()
{
char line[64]{0};

View File

@@ -0,0 +1,79 @@
#pragma once
#include <cstdint>
namespace Def
{
constexpr int32_t MaxNameLength = 64;
constexpr int32_t MaxFields = 64;
constexpr int32_t MaxExtraEnumFields = 2;
enum class EFieldType : uint8_t
{
DefinedClass,
DefinedEnum
};
enum class ETypeFlags : uint32_t
{
None = 0,
IsNative = 1 << 0,
};
enum class ETypeFieldFlags : uint32_t
{
None = 0,
DynamicArray = 1 << 0,
};
enum class EEnumFlags : uint32_t
{
None = 0,
FlagsEnum = 1,
};
struct TypeRef
{
uint16_t TypeIdx = UINT16_MAX;
EFieldType FieldKind = EFieldType::DefinedClass;
};
inline bool IsValid(TypeRef ref)
{
return ref.TypeIdx != UINT16_MAX;
}
struct Type
{
int32_t FieldCount = 0;
TypeRef FieldTypes[MaxFields];
char FieldNames[MaxFields][MaxNameLength];
uint32_t FieldArraySizes[MaxFields]{0};
char FieldValues[MaxFields][128]{0};
ETypeFlags TypeFlags = ETypeFlags::None;
ETypeFieldFlags FieldFlags[MaxFields]{ETypeFieldFlags::None};
char Name[MaxNameLength]{0};
char NativeCName[MaxNameLength]{0};
uint32_t Hash = 0;
};
struct Enum
{
TypeRef EnumType;
int32_t EntryCount = 0;
char EntryNames[MaxFields][MaxNameLength];
int32_t ExtraStringFieldCount = 0;
char ExtraStringFieldNames[MaxExtraEnumFields][MaxNameLength];
char ExtraStringFields[MaxFields][MaxExtraEnumFields][MaxNameLength];
char Name[MaxNameLength]{0};
EEnumFlags EnumFlags = EEnumFlags::None;
uint32_t Hash = 0;
};
struct DefinitionFile
{
static constexpr int32_t MaxTypes = 256;
Def::Type Types[MaxTypes];
uint16_t TypeCount = 0;
Def::Enum Enums[MaxTypes];
int32_t EnumCount = 0;
};
} // namespace Def

1
src/dependency/tracy Submodule

Submodule src/dependency/tracy added at 5d542dc09f

View File

@@ -96,16 +96,19 @@ struct SharedDevData
char ShaderLog[2048]{0};
};
struct MemoryArena
{
uint8_t* Base = nullptr;
uint64_t Used = 0;
uint64_t MaxSize = 0;
uint64_t LastAllocSize = 0;
};
struct GameData
{
void* PermanentStorage = nullptr;
uint64_t PermanentStorageSize = 0;
void* EntityStorage = nullptr;
uint64_t EntityStorageSize = 0;
void* TransientStorage = nullptr;
uint64_t TransientStorageSize = 0;
MemoryArena PermanentArena;
MemoryArena EntityArena;
MemoryArena TransientArena;
};
struct SharedData

View File

@@ -1,3 +1,4 @@
#include "bx/filepath.h"
#include <cstdlib>
#include <cwchar>
#include <fstream>
@@ -14,9 +15,7 @@
#include "Shared.h"
#include "Window.h"
// #define VISUAL_STUDIO
#ifdef VISUAL_STUDIO
#ifdef _MSC_VER
constexpr const char* DLLPath = "PuzGame.dll";
constexpr const wchar_t* DLLWatch = L"PuzGame2.dll";
#else
@@ -24,6 +23,10 @@ constexpr const char* DLLPath = "libPuzGame.dll";
constexpr const wchar_t* DLLWatch = L"libPuzGame2.dll";
#endif
constexpr uint64_t KB = 1024LLU;
constexpr uint64_t MB = 1024LLU * 1024LLU;
constexpr uint64_t GB = 1024LLU * 1024LLU * 1024LLU;
namespace
{
bx::AllocatorI* defaultAllocator = new bx::DefaultAllocator{};
@@ -99,6 +102,10 @@ void FileChangeCheck(HANDLE dirHandle, FileChangeType changeType, const wchar_t*
{
if (compName == nullptr || compName[0] == 0 || wcscmp(notifyData->FileName, compName) == 0)
{
char buf[1024]{0};
wcstombs(buf, notifyData->FileName, sizeof(buf));
printf("file change of type %u: %s\n", changeType, buf);
FileChangeNotification notif;
wcscpy(notif.FileName, notifyData->FileName);
if (changeType == FileChangeType::DLL)
@@ -107,23 +114,25 @@ void FileChangeCheck(HANDLE dirHandle, FileChangeType changeType, const wchar_t*
}
else if (changeType == FileChangeType::Shader)
{
std::system("shadercompile.bat > shadercompile.log");
printf("shader source change!\n");
std::system("powershell.exe .\\shadercompile.ps1 > shadercompile.log");
Sleep(1000);
std::ifstream shaderLogFile("shadercompile.log");
if (shaderLogFile.is_open())
{
shaderLogFile.seekg(0, std::ios::end);
uint32_t size = bx::min(BX_COUNTOF(Shared.Dev.ShaderLog), shaderLogFile.tellg());
uint32_t size = bx::min(BX_COUNTOF(Shared.Dev.ShaderLog) - 1, shaderLogFile.tellg());
shaderLogFile.seekg(0);
shaderLogFile.read(Shared.Dev.ShaderLog, size);
shaderLogFile.close();
Shared.Dev.ShaderLog[size] = 0;
}
}
else if (changeType == FileChangeType::CompiledShader)
{
printf("compiled shader change!\n");
DevData.FileWatcher.ShaderQueue.push(&notif);
}
printf("detected file change of type %u!\n", changeType);
}
}
@@ -180,40 +189,52 @@ bool ReloadDLL()
{
FreeLibrary(DevData.GameLib);
}
#ifdef VISUAL_STUDIO
if (!CopyFile("PuzGame2.dll", "PuzGame.dll", false))
{
printf("Failed to copy game DLL!\n");
return false;
}
char exePath[1024];
GetModuleFileName(nullptr, exePath, BX_COUNTOF(exePath));
bx::FilePath exeFilePath{exePath};
bx::FilePath exeDir{exeFilePath.getPath()};
bx::FilePath libPath = exeDir;
#ifdef _MSC_VER
libPath.join("PuzGame.dll");
#else
if (!CopyFile("cmake-build\\libPuzGame2.dll", "cmake-build\\libPuzGame.dll", false))
libPath.join("libPuzGame.dll");
#endif
bx::FilePath lib2Path = exeDir;
#ifdef _MSC_VER
lib2Path.join("PuzGame2.dll");
#else
lib2Path.join("libPuzGame2.dll");
#endif
if (!CopyFile(lib2Path.getCPtr(), libPath.getCPtr(), false))
{
printf("Failed to copy game DLL!\n");
DWORD err = GetLastError();
printf("[%lu] Failed to copy game DLL from %s to %s!\n", err, libPath.getCPtr(), lib2Path.getCPtr());
return false;
}
#endif
HMODULE gameLibReloaded = LoadLibraryEx(DLLPath, NULL, 0);
if (gameLibReloaded == NULL)
{
printf("Failed to load game DLL from %s!\n", DLLPath);
DWORD err = GetLastError();
printf("[%lu] Failed to load game DLL from %s!\n", err, DLLPath);
return false;
}
DevData.GameLib = gameLibReloaded;
#ifdef VISUAL_STUDIO
Startup StartupReloaded = (Startup)GetProcAddress(DevData.GameLib, "?Setup@Game@@YAXPEAX@Z");
#ifdef _MSC_VER
Startup StartupReloaded = (Startup)GetProcAddress(DevData.GameLib, "?Setup@Game@@YAXAEAUSharedData@@@Z");
#else
Startup StartupReloaded = (Startup)GetProcAddress(DevData.GameLib, "_ZN4Game5SetupER10SharedData");
#endif
if (StartupReloaded == NULL)
{
printf("Failed to load startup function from game DLL!\n");
DWORD err = GetLastError();
printf("[%lu] Failed to load startup function from game DLL!\n", err);
return false;
}
#ifdef VISUAL_STUDIO
#ifdef _MSC_VER
Update UpdateReloaded = (Update)GetProcAddress(DevData.GameLib, "?Update@Game@@YAXXZ");
#else
Update UpdateReloaded = (Update)GetProcAddress(DevData.GameLib, "_ZN4Game6UpdateEv");
@@ -224,7 +245,7 @@ bool ReloadDLL()
return false;
}
#ifdef VISUAL_STUDIO
#ifdef _MSC_VER
Shutdown ShutdownReloaded = (Shutdown)GetProcAddress(DevData.GameLib, "?Shutdown@Game@@YAXXZ");
#else
Shutdown ShutdownReloaded = (Shutdown)GetProcAddress(DevData.GameLib, "_ZN4Game8ShutdownEv");
@@ -243,6 +264,15 @@ bool ReloadDLL()
return true;
}
void InitMemoryArena(MemoryArena& arena, uint64_t size)
{
arena.MaxSize = size;
arena.Base = reinterpret_cast<uint8_t*>(
VirtualAllocEx(GetCurrentProcess(), NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
arena.Used = 0;
arena.LastAllocSize = 0;
}
int main()
{
char PathBuf[512]{0};
@@ -262,7 +292,12 @@ int main()
DevData.FileWatcher.ShaderWatcher.ChangeType = FileChangeType::Shader;
DevData.FileWatcher.CompiledShaderWatcher.ChangeType = FileChangeType::CompiledShader;
bx::strCopy(DevData.FileWatcher.DLLWatcher.DirPath, sizeof(DevData.FileWatcher.DLLWatcher.DirPath), "cmake-build");
char exePath[1024];
GetModuleFileName(nullptr, exePath, BX_COUNTOF(exePath));
bx::FilePath exeFilePath{exePath};
bx::FilePath exeDir{exeFilePath.getPath()};
bx::strCopy(
DevData.FileWatcher.DLLWatcher.DirPath, sizeof(DevData.FileWatcher.DLLWatcher.DirPath), exeDir.getCPtr());
bx::strCopy(
DevData.FileWatcher.ShaderWatcher.DirPath, sizeof(DevData.FileWatcher.ShaderWatcher.DirPath), "game/shaders");
bx::strCopy(DevData.FileWatcher.CompiledShaderWatcher.DirPath,
@@ -279,15 +314,9 @@ int main()
HANDLE compiledShaderThread =
CreateThread(NULL, 0, FileWatcherThread, &DevData.FileWatcher.CompiledShaderWatcher, 0, &fileWatcherThreadId);
Shared.Game.PermanentStorageSize = 1024 * 1024;
Shared.Game.PermanentStorage = VirtualAllocEx(
GetCurrentProcess(), NULL, Shared.Game.PermanentStorageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
Shared.Game.EntityStorageSize = 1024 * 1024;
Shared.Game.EntityStorage = VirtualAllocEx(
GetCurrentProcess(), NULL, Shared.Game.EntityStorageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
Shared.Game.TransientStorageSize = 1024 * 1024 * 100;
Shared.Game.TransientStorage = VirtualAllocEx(
GetCurrentProcess(), NULL, Shared.Game.TransientStorageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
InitMemoryArena(Shared.Game.PermanentArena, MB);
InitMemoryArena(Shared.Game.EntityArena, MB);
InitMemoryArena(Shared.Game.TransientArena, 2 * GB);
StartupFunc(Shared);
bool isRunning = true;

158
src/game/Entity.h Normal file
View File

@@ -0,0 +1,158 @@
#pragma once
#include "../gen/Generated.h"
#include "Log.h"
#include "Puzzle.h" // TODO: remove
#include "bgfx/bgfx.h"
#include "rendering/Rendering.h"
#include <cstdint>
#include <typeinfo>
#define ENTITY_HANDLE(X) \
struct X \
{ \
uint16_t Idx = UINT16_MAX; \
}; \
inline bool IsValid(X h) \
{ \
return h.Idx != UINT16_MAX; \
}
namespace Game
{
int32_t GetNextRenderID();
struct EntityRenderData
{
Gen::Vec4 DotColor{1.0f, 1.0f, 1.0f, 1.0f};
Gen::Vec4 BaseColor{0.0f, 0.0f, 0.0f, 1.0f};
Gen::Transform Transform;
Gen::EMaterial::Enum MaterialHandle = Gen::EMaterial::UI;
Gen::TextureHandle TextureHandle;
Gen::ModelHandle ModelH;
bool Visible = true;
int32_t RenderID = 0;
void Render(const Model* models, const Material* materials, const Texture* textures);
void LoadFromSaved(const Gen::SavedEntityRenderData& saved);
};
ENTITY_HANDLE(CubeHandle);
struct Cube
{
int32_t TestX = -1;
int32_t TestY = -1;
EntityRenderData EData;
void Setup();
void Update();
};
ENTITY_HANDLE(TestEntityHandle);
struct TestEntity
{
EntityRenderData EData;
void Setup();
};
ENTITY_HANDLE(PuzzleTileEntityHandle);
struct PuzzleTileEntity
{
EntityRenderData EData;
};
ENTITY_HANDLE(PuzzleTileCoverHandle);
struct PuzzleTileCover
{
EntityRenderData EData;
};
ENTITY_HANDLE(UIQuadEntityHandle);
struct UIQuadEntity
{
EntityRenderData EData;
Gen::Vec3 UIPos;
float UIRot = 0.0f;
};
ENTITY_HANDLE(LevelEntityHandle);
struct LevelEntity
{
EntityRenderData EData;
};
class IEntityManager
{
public:
virtual bool Setup(uint8_t*& ptr, bool forceReset) = 0;
};
template <typename T, typename HandleT, uint32_t C> class EntityManager : public IEntityManager
{
public:
uint16_t Count = 0;
T* Data = nullptr;
uint32_t EntitySize = 0;
T InvalidObject{};
bool IsEnabled = true;
public:
// Returns true if size changed
bool Setup(uint8_t*& ptr, bool forceReset)
{
bool changed = false;
if (EntitySize != sizeof(T) || forceReset)
{
Count = 0;
changed = true;
}
EntitySize = sizeof(T);
Data = reinterpret_cast<T*>(ptr);
ptr += C * EntitySize;
return changed;
}
HandleT New()
{
if (Data == nullptr)
{
ERROR_ONCE("Accessed EntityManager before setup!");
return {};
}
if (Count >= C)
{
ERROR_ONCE("Too many entities!");
return {};
}
Data[Count] = {};
Data[Count].EData.RenderID = GetNextRenderID();
HandleT H;
H.Idx = Count;
++Count;
return H;
}
T& Get(HandleT handle)
{
if (handle.Idx >= Count)
{
ERROR_ONCE("OOB Access!");
return InvalidObject;
}
return Data[handle.Idx];
}
void Render(const Model* models, const Material* materials, const Texture* textures)
{
if (!IsEnabled) return;
bgfx::setMarker(typeid(T).name());
for (uint16_t i = 0; i < Count; ++i)
{
Get({i}).EData.Render(models, materials, textures);
}
}
};
typedef EntityManager<UIQuadEntity, UIQuadEntityHandle, Puzzle::Config::MaxTilesInPuzzle * 2> UIQuadEntityManager;
} // namespace Game

515
src/game/Gen.cpp Normal file
View File

@@ -0,0 +1,515 @@
#include "Gen.h"
#include "bx/math.h"
namespace Gen
{
bool IsValid(const ModelHandle& h)
{
return h.ModelIdx != UINT16_MAX && IsValid(h.Asset);
}
bool IsValid(const AssetHandle& h)
{
return h.Idx != UINT32_MAX;
}
bool IsValid(const TextureHandle& h)
{
return h.TextureIdx != UINT16_MAX && IsValid(h.Asset);
}
bool operator==(const AssetHandle& lhs, const AssetHandle& rhs)
{
return lhs.Idx == rhs.Idx;
}
bool operator==(const ModelHandle& lhs, const ModelHandle& rhs)
{
if (!IsValid(lhs) || !IsValid(rhs)) return IsValid(lhs) == IsValid(rhs);
return lhs.ModelIdx == rhs.ModelIdx && lhs.Asset == rhs.Asset;
}
PuzPos operator+=(PuzPos lhs, const PuzPos& rhs)
{
lhs.X += rhs.X;
lhs.Y += rhs.Y;
return lhs;
}
PuzPos operator+(PuzPos lhs, const PuzPos& rhs)
{
PuzPos res = lhs;
res.X += rhs.X;
res.Y += rhs.Y;
return res;
}
PuzPos operator-=(PuzPos lhs, const PuzPos& rhs)
{
lhs.X -= rhs.X;
lhs.Y -= rhs.Y;
return lhs;
}
PuzPos operator-(PuzPos lhs, const PuzPos& rhs)
{
PuzPos res = lhs;
res.X += rhs.X;
res.Y += rhs.Y;
return res;
}
// Vec4
Vec4& operator+=(Vec4& lhs, const Vec4& rhs)
{
lhs.x += rhs.x;
lhs.y += rhs.y;
lhs.z += rhs.z;
lhs.w += rhs.w;
return lhs;
}
Vec4 operator+(const Vec4& lhs, const Vec4& rhs)
{
Vec4 out = lhs;
return out += rhs;
}
Vec4& operator+=(Vec4& lhs, float rhs)
{
lhs.x += rhs;
lhs.y += rhs;
lhs.z += rhs;
lhs.w += rhs;
return lhs;
}
Vec4 operator+(Vec4& lhs, float rhs)
{
Vec4 out = lhs;
return out += rhs;
}
Vec4& operator-=(Vec4& lhs, const Vec4& rhs)
{
lhs.x -= rhs.x;
lhs.y -= rhs.y;
lhs.z -= rhs.z;
lhs.w -= rhs.w;
return lhs;
}
Vec4 operator-(const Vec4& lhs, const Vec4& rhs)
{
Vec4 out = lhs;
return out -= rhs;
}
Vec4& operator-=(Vec4& lhs, float rhs)
{
lhs.x -= rhs;
lhs.y -= rhs;
lhs.z -= rhs;
lhs.w -= rhs;
return lhs;
}
Vec4 operator-(const Vec4& lhs, float rhs)
{
Vec4 out = lhs;
return out -= rhs;
}
Vec4& operator*=(Vec4& lhs, float rhs)
{
lhs.x *= rhs;
lhs.y *= rhs;
lhs.z *= rhs;
lhs.w *= rhs;
return lhs;
}
Vec4 operator*(const Vec4& lhs, float rhs)
{
Vec4 out = lhs;
return out *= rhs;
}
Vec4& operator/=(Vec4& lhs, float rhs)
{
lhs.x /= rhs;
lhs.y /= rhs;
lhs.z /= rhs;
lhs.w /= rhs;
return lhs;
}
Vec4 operator/(const Vec4& lhs, float rhs)
{
Vec4 out = lhs;
return out /= rhs;
}
// Vec3
Vec3& operator+=(Vec3& lhs, const Vec3& rhs)
{
lhs.x += rhs.x;
lhs.y += rhs.y;
lhs.z += rhs.z;
return lhs;
}
Vec3 operator+(const Vec3& lhs, const Vec3& rhs)
{
Vec3 out = lhs;
return out += rhs;
}
Vec3& operator+=(Vec3& lhs, float rhs)
{
lhs.x += rhs;
lhs.y += rhs;
lhs.z += rhs;
return lhs;
}
Vec3 operator+(Vec3& lhs, float rhs)
{
Vec3 out = lhs;
return out += rhs;
}
Vec3& operator-=(Vec3& lhs, const Vec3& rhs)
{
lhs.x -= rhs.x;
lhs.y -= rhs.y;
lhs.z -= rhs.z;
return lhs;
}
Vec3 operator-(const Vec3& lhs, const Vec3& rhs)
{
Vec3 out = lhs;
return out -= rhs;
}
Vec3& operator-=(Vec3& lhs, float rhs)
{
lhs.x -= rhs;
lhs.y -= rhs;
lhs.z -= rhs;
return lhs;
}
Vec3 operator-(const Vec3& lhs, float rhs)
{
Vec3 out = lhs;
return out -= rhs;
}
Vec3& operator*=(Vec3& lhs, float rhs)
{
lhs.x *= rhs;
lhs.y *= rhs;
lhs.z *= rhs;
return lhs;
}
Vec3 operator*(const Vec3& lhs, float rhs)
{
Vec3 out = lhs;
return out *= rhs;
}
Vec3& operator/=(Vec3& lhs, float rhs)
{
lhs.x /= rhs;
lhs.y /= rhs;
lhs.z /= rhs;
return lhs;
}
Vec3 operator/(const Vec3& lhs, float rhs)
{
Vec3 out = lhs;
return out /= rhs;
}
// Vec2
Vec2& operator+=(Vec2& lhs, const Vec2& rhs)
{
lhs.x += rhs.x;
lhs.y += rhs.y;
return lhs;
}
Vec2 operator+(const Vec2& lhs, const Vec2& rhs)
{
Vec2 out = lhs;
return out += rhs;
}
Vec2& operator+=(Vec2& lhs, float rhs)
{
lhs.x += rhs;
lhs.y += rhs;
return lhs;
}
Vec2 operator+(Vec2& lhs, float rhs)
{
Vec2 out = lhs;
return out += rhs;
}
Vec2& operator-=(Vec2& lhs, const Vec2& rhs)
{
lhs.x -= rhs.x;
lhs.y -= rhs.y;
return lhs;
}
Vec2 operator-(const Vec2& lhs, const Vec2& rhs)
{
Vec2 out = lhs;
return out -= rhs;
}
Vec2& operator-=(Vec2& lhs, float rhs)
{
lhs.x -= rhs;
lhs.y -= rhs;
return lhs;
}
Vec2 operator-(const Vec2& lhs, float rhs)
{
Vec2 out = lhs;
return out -= rhs;
}
Vec2& operator*=(Vec2& lhs, float rhs)
{
lhs.x *= rhs;
lhs.y *= rhs;
return lhs;
}
Vec2 operator*(const Vec2& lhs, float rhs)
{
Vec2 out = lhs;
return out *= rhs;
}
Vec2& operator/=(Vec2& lhs, float rhs)
{
lhs.x /= rhs;
lhs.y /= rhs;
return lhs;
}
Vec2 operator/(const Vec2& lhs, float rhs)
{
Vec2 out = lhs;
return out /= rhs;
}
Mat4 Inverse(const Mat4& mat)
{
Mat4 result;
bx::mtxInverse(result.M, &mat.M[0]);
return result;
}
Mat4 Transpose(const Mat4& mat)
{
Mat4 result;
bx::mtxTranspose(result.M, &mat.M[0]);
return result;
}
Vec4 Mul(const Mat4& mat, const Vec4& vec)
{
Vec4 out;
bx::vec4MulMtx(&out.x, &vec.x, &mat.M[0]);
return out;
}
Vec3 EulerFromRotation(const Mat4& rotation)
{
const float r32 = rotation.M[9];
const float r33 = rotation.M[10];
float x = bx::atan2(r32, r33);
float y = bx::atan2(-rotation.M[8], bx::sqrt(r32 * r32 + r33 * r33));
float z = bx::atan2(rotation.M[4], rotation.M[0]);
return {x, y, z};
}
Mat4 RotationFromEuler(const Vec3& euler)
{
Mat4 mat;
bx::Quaternion quat = bx::fromEuler({euler.x, euler.y, euler.z});
bx::mtxFromQuaternion(mat.M, quat);
return mat;
}
Mat4 RotationFromQuaternion(const Vec4& quaternion)
{
Mat4 mat;
bx::Quaternion quat{quaternion.x, quaternion.y, quaternion.z, quaternion.w};
bx::mtxFromQuaternion(mat.M, quat);
return mat;
}
float Magnitude(const Vec4& vec)
{
return bx::sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z + vec.w * vec.w);
}
float Magnitude(const Vec3& vec)
{
return bx::sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
}
float Magnitude(const Vec2& vec)
{
return bx::sqrt(vec.x * vec.x + vec.y * vec.y);
}
Vec4 Normalized(const Vec4& vec)
{
Vec4 res = vec;
return res /= Magnitude(vec);
}
Vec3 Normalized(const Vec3& vec)
{
Vec3 res = vec;
return res /= Magnitude(vec);
}
Vec2 Normalized(const Vec2& vec)
{
Vec2 res = vec;
return res /= Magnitude(vec);
}
float DotProduct(Vec3 a, Vec3 b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
Vec3 CrossProduct(Vec3 a, Vec3 b)
{
float x = a.y * b.z - a.z * b.y;
float y = a.z * b.x - a.x * b.z;
float z = a.x * b.y - a.y * b.x;
return {x, y, z};
}
Vec3 CrossProductFromPlane(Vec3 a, Vec3 b, Vec3 c)
{
// TODO: normalize might not be necessary
Vec3 lineA = Normalized(b - a);
Vec3 lineB = Normalized(c - a);
return CrossProduct(lineA, lineB);
}
bool RayPlaneIntersect(Vec3 l1, Vec3 l2, Vec3 p1, Vec3 p2, Vec3 p3, Vec3& out)
{
// thanks to Paul Bourke and Bryan Hanson
// l1,l2 constitute the line. P1,P2,P3 constitute the plane
out = {};
Vec3 N = CrossProductFromPlane(p1, p2, p3); // N is the normal of the plane
float n = DotProduct(N, Vec3{p3.x - l1.x, p3.y - l1.y, p3.z - l1.z});
Vec3 LbMinusLa = Vec3{l2.x - l1.x, l2.y - l1.y, l2.z - l1.z};
float d = DotProduct(N, LbMinusLa);
if (d == 0) return false; // Line is parallel to or in the plane
float u = n / d;
if ((u >= 0.0) && (u <= 1.0))
{ // Plane is between the two points
} // can be used for checking but does not influence the outcome
out = Vec3{l1.x + u * LbMinusLa.x, l1.y + u * LbMinusLa.y, l1.z + u * LbMinusLa.z};
return true;
}
bool RayTriangleIntersect(Vec3 l1, Vec3 l2, Vec3 p1, Vec3 p2, Vec3 p3, Vec3& out)
{
const float EPSILON = 1e-6f;
Vec3 dir = l2 - l1; // Ray direction
Vec3 edge1 = p2 - p1;
Vec3 edge2 = p3 - p1;
Vec3 h = CrossProduct(dir, edge2);
float a = DotProduct(edge1, h);
if (bx::abs(a) < EPSILON) return false; // Ray is parallel to the triangle
float f = 1.0f / a;
Vec3 s = l1 - p1;
float u = f * DotProduct(s, h);
if (u < 0.0f || u > 1.0f) return false;
Vec3 q = CrossProduct(s, edge1);
float v = f * DotProduct(dir, q);
if (v < 0.0f || u + v > 1.0f) return false;
float t = f * DotProduct(edge2, q);
if (t > EPSILON)
{
out = l1 + dir * t;
return true;
}
return false;
}
void Translate(Transform& trans, Vec3 offset)
{
trans.Position += Vec3{offset.x, offset.y, offset.z};
}
void TranslateLocal(Transform& trans, Vec3 offset)
{
Vec3 localOffset = GlobalToLocalDirection(trans, offset);
trans.Position += Vec3{localOffset.x, localOffset.y, localOffset.z};
}
void Rotate(Transform& trans, Vec3 rotation)
{
float rot[16]{0};
bx::mtxRotateXYZ(rot, rotation.x, rotation.y, rotation.z);
float temp[16]{0};
bx::mtxMul(temp, rot, trans.Rotation.M);
bx::memCopy(trans.Rotation.M, temp, sizeof(temp));
}
void RotateLocal(Transform& trans, Vec3 rotation)
{
float rot[16]{0};
bx::mtxRotateXYZ(rot, rotation.x, rotation.y, rotation.z);
float temp[16]{0};
bx::mtxMul(temp, trans.Rotation.M, rot);
bx::memCopy(trans.Rotation.M, temp, sizeof(temp));
}
Vec3 AxisRight(const Mat4& trans)
{
return {trans.M[0], trans.M[1], trans.M[2]};
}
Vec3 AxisUp(const Mat4& trans)
{
return {trans.M[4], trans.M[5], trans.M[6]};
}
Vec3 AxisForward(const Mat4& trans)
{
return {trans.M[8], trans.M[9], trans.M[10]};
}
void UpdateMatrix(Transform& trans)
{
Mat4 pos;
Mat4 scale;
bx::mtxTranslate(pos.M, trans.Position.x, trans.Position.y, trans.Position.z);
bx::mtxScale(scale.M, trans.Scale.x, trans.Scale.y, trans.Scale.z);
Mat4 temp;
bx::mtxMul(temp.M, scale.M, trans.Rotation.M);
bx::mtxMul(trans.M.M, temp.M, pos.M);
bx::mtxInverse(trans.MI.M, trans.M.M);
}
Vec3 GlobalToLocalDirection(Transform& trans, Vec3 global)
{
UpdateMatrix(trans);
float in[4]{global.x, global.y, global.z, 0.0f};
float out[4]{0.0f};
bx::vec4MulMtx(out, in, Transpose(trans.MI).M);
return {out[0], out[1], out[2]};
}
Vec3 GlobalToLocalPoint(Transform& trans, Vec3 global)
{
UpdateMatrix(trans);
float in[4]{global.x, global.y, global.z, 1.0f};
float out[4]{0.0f};
bx::vec4MulMtx(out, in, trans.MI.M);
return {out[0], out[1], out[2]};
}
Vec3 LocalToGlobalDirection(Transform& trans, Vec3 local)
{
UpdateMatrix(trans);
float in[4]{local.x, local.y, local.z, 0.0f};
float out[4]{0.0f};
bx::vec4MulMtx(out, in, Transpose(trans.M).M);
return {out[0], out[1], out[2]};
}
Vec3 LocalToGlobalPoint(Transform& trans, Vec3 local)
{
UpdateMatrix(trans);
float in[4]{local.x, local.y, local.z, 1.0f};
float out[4]{0.0f};
bx::vec4MulMtx(out, in, trans.M.M);
return {out[0], out[1], out[2]};
}
} // namespace Gen

98
src/game/Gen.h Normal file
View File

@@ -0,0 +1,98 @@
#pragma once
#include "../gen/Generated.h"
namespace Gen
{
bool IsValid(const AssetHandle& h);
bool IsValid(const ModelHandle& h);
bool IsValid(const TextureHandle& h);
bool operator==(const AssetHandle& lhs, const AssetHandle& rhs);
bool operator==(const ModelHandle& lhs, const ModelHandle& rhs);
PuzPos operator+=(PuzPos lhs, const PuzPos& rhs);
PuzPos operator+(PuzPos lhs, const PuzPos& rhs);
Vec4& operator+=(Vec4& lhs, const Vec4& rhs);
Vec4 operator+(const Vec4& lhs, const Vec4& rhs);
Vec4& operator+=(Vec4& lhs, float rhs);
Vec4 operator+(Vec4& lhs, float rhs);
Vec4& operator-=(Vec4& lhs, const Vec4& rhs);
Vec4 operator-(const Vec4& lhs, const Vec4& rhs);
Vec4& operator-=(Vec4& lhs, float rhs);
Vec4 operator-(const Vec4& lhs, float rhs);
Vec4& operator*=(Vec4& lhs, float rhs);
Vec4 operator*(const Vec4& lhs, float rhs);
Vec4& operator/=(Vec4& lhs, float rhs);
Vec4 operator/(const Vec4& lhs, float rhs);
Vec3& operator+=(Vec3& lhs, const Vec3& rhs);
Vec3 operator+(const Vec3& lhs, const Vec3& rhs);
Vec3& operator+=(Vec3& lhs, float rhs);
Vec3 operator+(Vec3& lhs, float rhs);
Vec3& operator-=(Vec3& lhs, const Vec3& rhs);
Vec3 operator-(const Vec3& lhs, const Vec3& rhs);
Vec3& operator-=(Vec3& lhs, float rhs);
Vec3 operator-(const Vec3& lhs, float rhs);
Vec3& operator*=(Vec3& lhs, float rhs);
Vec3 operator*(const Vec3& lhs, float rhs);
Vec3& operator/=(Vec3& lhs, float rhs);
Vec3 operator/(const Vec3& lhs, float rhs);
Vec2& operator+=(Vec2& lhs, const Vec2& rhs);
Vec2 operator+(const Vec2& lhs, const Vec2& rhs);
Vec2& operator+=(Vec2& lhs, float rhs);
Vec2 operator+(Vec2& lhs, float rhs);
Vec2& operator-=(Vec2& lhs, const Vec2& rhs);
Vec2 operator-(const Vec2& lhs, const Vec2& rhs);
Vec2& operator-=(Vec2& lhs, float rhs);
Vec2 operator-(const Vec2& lhs, float rhs);
Vec2& operator*=(Vec2& lhs, float rhs);
Vec2 operator*(const Vec2& lhs, float rhs);
Vec2& operator/=(Vec2& lhs, float rhs);
Vec2 operator/(const Vec2& lhs, float rhs);
float Magnitude(const Vec4& vec);
float Magnitude(const Vec3& vec);
float Magnitude(const Vec2& vec);
Vec4 Normalized(const Vec4& vec);
Vec3 Normalized(const Vec3& vec);
Vec2 Normalized(const Vec2& vec);
Mat4 Inverse(const Mat4& mat);
Mat4 Transpose(const Mat4& mat);
Vec4 Mul(const Mat4& mat, const Vec4& vec);
Vec3 EulerFromRotation(const Mat4& rotation);
Mat4 RotationFromEuler(const Vec3& euler);
Mat4 RotationFromQuaternion(const Vec4& quaternion);
float DotProduct(Vec3 a, Vec3 b);
Vec3 CrossProduct(Vec3 a, Vec3 b);
Vec3 CrossProductFromPlane(Vec3 a, Vec3 b, Vec3 c);
bool RayPlaneIntersect(Vec3 l1, Vec3 l2, Vec3 p1, Vec3 p2, Vec3 p3, Vec3& out);
bool RayTriangleIntersect(Vec3 l1, Vec3 l2, Vec3 p1, Vec3 p2, Vec3 p3, Vec3& out);
void Translate(Transform& trans, Vec3 offset);
void TranslateLocal(Transform& trans, Vec3 offset);
void Rotate(Transform& trans, Vec3 rotation);
void RotateLocal(Transform& trans, Vec3 rotation);
Vec3 LocalToGlobalPoint(Transform& trans, Vec3 local);
Vec3 LocalToGlobalDirection(Transform& trans, Vec3 local);
Vec3 GlobalToLocalPoint(Transform& trans, Vec3 global);
Vec3 GlobalToLocalDirection(Transform& trans, Vec3 global);
Vec3 AxisRight(const Mat4& mat);
Vec3 AxisUp(const Mat4& mat);
Vec3 AxisForward(const Mat4& mat);
void UpdateMatrix(Transform& trans);
} // namespace Gen

View File

@@ -1,87 +1,18 @@
#include "../engine/Shared.h"
#include "Global.h"
#include "Instance.h"
#include "bx/bx.h"
#include "bx/math.h"
#include <cassert>
#include <cstdint>
using namespace Gen;
namespace
{
SharedData* SharedInstance = nullptr;
Game::GameInstance* GameInst = nullptr;
} // namespace
void Transform::CreateTransform(float* out, bx::Vec3 pos, bx::Quaternion rot, bx::Vec3 scale)
{
if (out == nullptr) return;
float rMat[16]{0};
float tMat[16]{0};
float sMat[16]{0};
bx::mtxFromQuaternion(rMat, bx::Quaternion{rot.x, rot.y, rot.z, rot.w});
bx::mtxTranslate(tMat, pos.x, pos.y, pos.z);
bx::mtxScale(sMat, scale.x, scale.y, scale.z);
float buf[16]{0};
bx::mtxMul(buf, rMat, sMat);
bx::mtxMul(out, buf, tMat);
}
void Transform::Translate(Vec3 offset)
{
Position = bx::add(Position, {offset.x, offset.y, offset.z});
}
void Transform::TranslateLocal(Vec3 offset)
{
Vec3 localOffset = GlobalToLocalDirection(offset);
Position = bx::add(Position, {localOffset.x, localOffset.y, localOffset.z});
}
void Transform::Rotate(bx::Vec3 rotation)
{
float rot[16]{0};
bx::mtxRotateXYZ(rot, rotation.x, rotation.y, rotation.z);
float temp[16]{0};
bx::mtxMul(temp, rot, Rotation.M);
bx::memCopy(Rotation.M, temp, sizeof(temp));
}
void Transform::RotateLocal(bx::Vec3 rotation)
{
float rot[16]{0};
bx::mtxRotateXYZ(rot, rotation.x, rotation.y, rotation.z);
float temp[16]{0};
bx::mtxMul(temp, Rotation.M, rot);
bx::memCopy(Rotation.M, temp, sizeof(temp));
}
Vec3 Transform::Right() const
{
return {M.M[0], M.M[1], M.M[2]};
}
Vec3 Transform::Up() const
{
return {M.M[4], M.M[5], M.M[6]};
}
Vec3 Transform::Forward() const
{
return {M.M[8], M.M[9], M.M[10]};
}
void Transform::UpdateMatrix()
{
Mat4 pos;
Mat4 scale;
bx::mtxTranslate(pos.M, Position.x, Position.y, Position.z);
bx::mtxScale(scale.M, Scale.x, Scale.y, Scale.z);
Mat4 temp;
bx::mtxMul(temp.M, scale.M, Rotation.M);
bx::mtxMul(M.M, pos.M, temp.M);
bx::mtxInverse(MI.M, M.M);
}
namespace Game
{
SharedData& GetShared()
@@ -106,69 +37,36 @@ namespace Game
GameInst = &instance;
}
void* AllocateScratch(size_t byteCount, size_t align)
uint8_t* AllocateScratch(uint64_t byteCount, uint32_t align)
{
size_t offset = GetInstance().UsedScratchAmount;
uint8_t* base = static_cast<uint8_t*>(GetShared().Game.TransientStorage);
uint8_t* current = base + offset;
size_t offsetAligned = ((offset + align - 1) / align) * align;
uint8_t* ptrAligned = base + offsetAligned;
size_t newOffset = offsetAligned + byteCount;
if (newOffset > GetShared().Game.TransientStorageSize) return nullptr;
GetInstance().UsedScratchAmount = newOffset;
return reinterpret_cast<void*>(ptrAligned);
assert(align <= 64); // The alignment of the arena limits the alignment that can be specified here!
auto& arena = GetShared().Game.TransientArena;
uint64_t offsetAligned = ((arena.Used + align - 1) / align) * align;
uint8_t* ptrAligned = arena.Base + offsetAligned;
uint64_t newOffset = offsetAligned + byteCount;
if (newOffset > arena.MaxSize) return nullptr;
arena.Used = newOffset;
arena.LastAllocSize = byteCount;
return ptrAligned;
}
bool ResizeLastScratchAlloc(uint64_t newByteCount)
{
auto& arena = GetShared().Game.TransientArena;
if (newByteCount > arena.LastAllocSize)
{
LOG_ERROR("Can't resize to more than previous size!");
return false;
}
arena.Used -= arena.LastAllocSize;
arena.Used += newByteCount;
return true;
}
void ResetScratch()
{
auto& arena = GetShared().Game.TransientArena;
arena.Used = 0;
arena.LastAllocSize = 0;
}
} // namespace Game
Vec3 Transform::GlobalToLocalDirection(Vec3 global)
{
UpdateMatrix();
float in[4]{global.x, global.y, global.z, 0.0f};
float out[4]{0.0f};
bx::vec4MulMtx(out, in, MI.M);
return {out[0], out[1], out[2]};
}
bx::Vec3 Transform::GlobalToLocalPoint(bx::Vec3 global)
{
UpdateMatrix();
float in[4]{global.x, global.y, global.z, 1.0f};
float out[4]{0.0f};
bx::vec4MulMtx(out, in, MI.M);
return {out[0], out[1], out[2]};
}
Vec3 Transform::LocalToGlobalDirection(Vec3 local)
{
UpdateMatrix();
float in[4]{local.x, local.y, local.z, 0.0f};
float out[4]{0.0f};
Mat4 test = M.Transpose();
bx::vec4MulMtx(out, in, test.M);
return {out[0], out[1], out[2]};
}
bx::Vec3 Transform::LocalToGlobalPoint(bx::Vec3 local)
{
UpdateMatrix();
float in[4]{local.x, local.y, local.z, 1.0f};
float out[4]{0.0f};
bx::vec4MulMtx(out, in, M.M);
return {out[0], out[1], out[2]};
}
void Transform::SetPosition(Vec3 pos)
{
Position = {pos.x, pos.y, pos.z};
}
Vec3 Transform::GetPosition()
{
return {Position.x, Position.y, Position.z};
}
Mat4 Mat4::Inverse()
{
Mat4 result;
bx::mtxInverse(result.M, M);
return result;
}
Mat4 Mat4::Transpose()
{
Mat4 result;
bx::mtxTranspose(result.M, M);
return result;
}

View File

@@ -1,186 +1,6 @@
#pragma once
#include "bx/math.h"
#include <cfloat>
struct Vec2
{
float x = 0.0f;
float y = 0.0f;
Vec2& operator+=(const Vec2& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
friend Vec2 operator+(Vec2 lhs, const Vec2& rhs)
{
lhs += rhs;
return lhs;
}
Vec2& operator-=(const Vec2& rhs)
{
x -= rhs.x;
y -= rhs.y;
return *this;
}
friend Vec2 operator-(Vec2 lhs, const Vec2& rhs)
{
lhs -= rhs;
return lhs;
}
Vec2& operator*=(const float rhs)
{
x *= rhs;
y *= rhs;
return *this;
}
friend Vec2 operator*(Vec2 lhs, const float rhs)
{
lhs *= rhs;
return lhs;
}
Vec2& operator/=(const float rhs)
{
x /= rhs;
y /= rhs;
return *this;
}
friend Vec2 operator/(Vec2 lhs, const float rhs)
{
lhs /= rhs;
return lhs;
}
float Magnitude()
{
return bx::sqrt(x * x + y * y);
}
Vec2 Normalize()
{
float mag = Magnitude();
if (mag < FLT_EPSILON) return {};
return {x / mag, y / mag};
}
};
struct Vec3
{
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
Vec3& operator+=(const Vec3& rhs)
{
x += rhs.x;
y += rhs.y;
z += rhs.z;
return *this;
}
friend Vec3 operator+(Vec3 lhs, const Vec3& rhs)
{
lhs += rhs;
return lhs;
}
Vec3& operator-=(const Vec3& rhs)
{
x -= rhs.x;
y -= rhs.y;
z -= rhs.z;
return *this;
}
friend Vec3 operator-(Vec3 lhs, const Vec3& rhs)
{
lhs -= rhs;
return lhs;
}
Vec3& operator*=(const float rhs)
{
x *= rhs;
y *= rhs;
z *= rhs;
return *this;
}
friend Vec3 operator*(Vec3 lhs, const float rhs)
{
lhs *= rhs;
return lhs;
}
Vec3& operator/=(const float rhs)
{
x /= rhs;
y /= rhs;
z /= rhs;
return *this;
}
friend Vec3 operator/(Vec3 lhs, const float rhs)
{
lhs /= rhs;
return lhs;
}
float Magnitude()
{
return bx::sqrt(x * x + y * y + z * z);
}
Vec3 Normalize()
{
float mag = Magnitude();
if (mag < FLT_EPSILON) return {};
return {x / mag, y / mag, z / mag};
}
};
struct Vec4
{
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float w = 0.0f;
};
struct Mat3
{
// clang-format off
float M[9]{
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
};
// clang-format on
};
struct Mat4
{
// clang-format off
float M[16]{
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
};
// clang-format on
Mat4 Inverse();
Mat4 Transpose();
};
#include <cstdint>
inline int32_t SetFlags(int32_t in, int32_t flags)
{
@@ -203,32 +23,6 @@ inline bool GetFlag(int32_t in, int32_t flags)
return (in & flags) > 0;
}
struct Transform
{
Mat4 M;
Mat4 MI;
bx::Vec3 Position{0.0f, 0.0f, 0.0f};
Mat4 Rotation;
bx::Vec3 Scale{1.0f, 1.0f, 1.0f};
static void CreateTransform(float* out, bx::Vec3 pos, bx::Quaternion rot, bx::Vec3 scale);
void Translate(Vec3 offset);
void TranslateLocal(Vec3 offset);
void Rotate(bx::Vec3 rotation);
void RotateLocal(bx::Vec3 rotation);
bx::Vec3 LocalToGlobalPoint(bx::Vec3 local);
Vec3 LocalToGlobalDirection(Vec3 local);
bx::Vec3 GlobalToLocalPoint(bx::Vec3 global);
Vec3 GlobalToLocalDirection(Vec3 global);
Vec3 Right() const;
Vec3 Up() const;
Vec3 Forward() const;
void SetPosition(Vec3 pos);
Vec3 GetPosition();
const float* GetPtr();
void UpdateMatrix();
};
struct SharedData;
namespace Game
@@ -239,5 +33,7 @@ namespace Game
void SetShared(SharedData& instance);
GameInstance& GetInstance();
void SetInstance(GameInstance& instance);
void* AllocateScratch(size_t byteCount, size_t align = 16);
uint8_t* AllocateScratch(uint64_t byteCount, uint32_t align = 16);
bool ResizeLastScratchAlloc(uint64_t newByteCount);
void ResetScratch();
} // namespace Game

View File

@@ -2,17 +2,22 @@
#include "Global.h"
#include "Input.h"
#include "imgui.h"
#include "imgui_internal.h"
namespace Game
{
using namespace Gen;
bool IsKeyboardAllowed()
{
if (GImGui == nullptr) return true;
auto& IO = ImGui::GetIO();
return !IO.WantCaptureKeyboard || GetFlag(IO.ConfigFlags, ImGuiConfigFlags_NoKeyboard);
}
bool IsMouseAllowed()
{
if (GImGui == nullptr) return true;
auto& IO = ImGui::GetIO();
return !IO.WantCaptureMouse || GetFlag(IO.ConfigFlags, ImGuiConfigFlags_NoMouse);
}
@@ -56,4 +61,11 @@ namespace Game
if (!IsMouseAllowed()) return {};
return {GetShared().Window.MouseDeltaX, GetShared().Window.MouseDeltaY};
}
Vec2 GetMousePos()
{
// TODO: fix this!!
if (GImGui == nullptr) return {};
ImVec2 pos = ImGui::GetMousePos();
return {pos.x, pos.y};
}
} // namespace Game

View File

@@ -1,5 +1,5 @@
#pragma once
#include "Global.h"
#include "../gen/Generated.h"
enum class MouseButton
{
@@ -393,5 +393,6 @@ namespace Game
bool GetMouseButton(MouseButton button);
bool GetMouseButtonPressedNow(MouseButton button);
bool GetMouseButtonReleasedNow(MouseButton button);
Vec2 GetMouseMovement();
Gen::Vec2 GetMouseMovement();
Gen::Vec2 GetMousePos();
} // namespace Game

View File

@@ -17,6 +17,12 @@ namespace Game
Game,
};
enum class InteractionMode
{
Walk,
ReadTablet,
};
struct Time
{
double Now = 0.0;
@@ -28,28 +34,43 @@ namespace Game
struct PlayerData
{
Transform FreeflyCamTransform;
Gen::Transform PlayerCamTransform;
Gen::Transform FreeflyCamTransform;
Gen::Mat4 Projection;
Gen::Mat4 ProjectionInverse;
float FreeflyXRot = 0.0f;
float FreeflyYRot = 0.0f;
float WalkXRot = 0.0f;
float WalkYRot = 0.0f;
Transform PlayerCamTransform;
CameraMode CameraM = CameraMode::Freefly;
CameraMode CameraM = CameraMode::Walk;
InputMode InputM = InputMode::Game;
InteractionMode InteractionM = InteractionMode::Walk;
float MouseSensitivity = 1.0f;
float MovementSpeed = 10.0f;
Gen::SavedPlayerConfig Config;
};
struct InstanceDebugData
{
uint16_t SelectedDebugLevel = UINT16_MAX;
static constexpr uint32_t MaxAssets = 128;
uint16_t SelectedDebugLevel = 0;
uint64_t ImguiIniSize = 0;
char ImguiIni[4096]{0};
uint32_t AssetCount = 0;
Gen::AssetHandle AssetHandles[MaxAssets]{0};
char AssetHandlePaths[MaxAssets][128];
bool ShowImguiDemo = false;
bool DebugBreakIDEnabled = false;
int DebugBreakID = -1;
uint8_t DebugCardRotation = 0;
bool ShortenLogFileNames = true;
bool ShowStats = true;
};
struct GameInstance
{
bool IsInitialized = false;
uint64_t Size = sizeof(GameInstance);
uint64_t UsedScratchAmount = 0;
Time Time;
PlayerData Player;
Level GameLevel;

View File

@@ -1,30 +1,55 @@
#include "../gen/Def.h"
#include "Gen.h"
#include "Global.h"
#include "Input.h"
#include "Instance.h"
#include "Level.h"
#include "Log.h"
#include "Puzzle.h"
#include "UI.h"
#include "bx/bx.h"
#include "bx/debug.h"
#include "rendering/Rendering.h"
#include "SDL3/SDL_mouse.h"
#include "bgfx/bgfx.h"
#include "bx/error.h"
#include "bx/file.h"
#include "bx/filepath.h"
#include "bx/string.h"
#include "imgui.h"
#include "rendering/Rendering.h"
#include <SDL3/SDL.h>
#include <bx/math.h>
#include <cstdint>
#include <tracy/Tracy.hpp>
using namespace Gen;
namespace Game
{
void EntityRenderData::Render(const Model* models, const Material* materials)
void EntityRenderData::Render(const Model* models, const Material* materials, const Texture* textures)
{
if (ModelHandle == UINT16_MAX || MaterialHandle == UINT16_MAX) return;
auto& debug = GetInstance().DebugData;
if ((int32_t)debug.DebugBreakID == RenderID && debug.DebugBreakIDEnabled)
{
bx::debugBreak();
debug.DebugBreakIDEnabled = false;
}
if (models == nullptr || materials == nullptr || textures == nullptr) return;
if (!Gen::IsValid(ModelH) || MaterialHandle >= EMaterial::EntryCount) return;
if (!Visible) return;
auto& rendering = GameRendering::Get();
Transform.UpdateMatrix();
UpdateMatrix(Transform);
bgfx::setTransform(Transform.M.M);
const Model& currentModel = models[ModelHandle];
const Material& currentMaterial = materials[MaterialHandle];
const Model& currentModel = models[ModelH.ModelIdx];
const Material& currentMaterial = materials[(uint16_t)MaterialHandle];
if (!isValid(currentModel.IndexBuffer) || !isValid(currentModel.VertexBuffer)) return;
if (!isValid(currentMaterial.Shader)) return;
bgfx::setVertexBuffer(0, currentModel.VertexBuffer);
bgfx::setIndexBuffer(currentModel.IndexBuffer);
bgfx::setState(currentMaterial.State);
@@ -33,84 +58,249 @@ namespace Game
timeValues[0] = GetInstance().Time.Now;
float texInfo[4]{0.0f};
texInfo[0] = currentMaterial.Textures[0].Info.width;
texInfo[1] = currentMaterial.Textures[0].Info.height;
texInfo[0] = textures[0].Info.width;
texInfo[1] = textures[0].Info.height;
texInfo[2] = rendering.DitherTextures.DitherTexWH;
texInfo[3] = rendering.DitherTextures.DitherTexDepth;
bgfx::setTexture(0, currentMaterial.Textures[0].SamplerHandle, currentMaterial.Textures[0].Handle);
bgfx::UniformHandle sampler = rendering.DefaultSampler;
bgfx::TextureHandle tex = rendering.Textures[0].RenderHandle;
if (IsValid(TextureHandle))
{
sampler = textures[TextureHandle.TextureIdx].SamplerHandle;
tex = textures[TextureHandle.TextureIdx].RenderHandle;
texInfo[0] = textures[TextureHandle.TextureIdx].Info.width;
texInfo[1] = textures[TextureHandle.TextureIdx].Info.height;
}
bgfx::setTexture(0, sampler, tex);
bgfx::setTexture(1, rendering.DitherTextures.DitherSampler, rendering.DitherTextures.FinalTex);
bgfx::setTexture(2, rendering.DitherTextures.RampSampler, rendering.DitherTextures.RampTex);
bgfx::setUniform(currentMaterial.Uniforms[Material::UTime], timeValues);
bgfx::setUniform(currentMaterial.Uniforms[Material::UDotColor], TestColor);
bgfx::setUniform(currentMaterial.Uniforms[Material::UDotColor], &DotColor.x);
bgfx::setUniform(currentMaterial.Uniforms[Material::UTexInfo], texInfo);
bgfx::setUniform(currentMaterial.Uniforms[Material::UBaseColor], &BaseColor.x);
bgfx::submit(currentMaterial.ViewID, currentMaterial.Shader);
}
void EntityRenderData::LoadFromSaved(const Gen::SavedEntityRenderData& saved)
{
DotColor = saved.HighlightColor;
BaseColor = saved.BaseColor;
Transform = saved.TF;
// TODO: fix handle indices
MaterialHandle = saved.Material;
TextureHandle = saved.Texture;
ModelH = saved.Model;
Visible = saved.Visible;
}
namespace
{
void UpdatePlayerInputMode()
{
bool IsGaming = GetInstance().Player.InputM == InputMode::Game;
SDL_SetWindowRelativeMouseMode(GetShared().Window.SDLWindow, IsGaming);
bool captureMouse = IsGaming && GetInstance().Player.InteractionM == InteractionMode::Walk;
SDL_SetWindowRelativeMouseMode(GetShared().Window.SDLWindow, captureMouse);
auto& rendering = GameRendering::Get();
if (rendering.SetupData.UseImgui)
{
auto& IO = ImGui::GetIO();
IO.ConfigFlags = FlagBool(IO.ConfigFlags, ImGuiConfigFlags_NoMouse | ImGuiConfigFlags_NoKeyboard, IsGaming);
GameRendering::Get().UIVisible = IsGaming ? UIVisibilityState::Game : UIVisibilityState::Debug;
IO.ConfigFlags =
FlagBool(IO.ConfigFlags, ImGuiConfigFlags_NoMouse | ImGuiConfigFlags_NoKeyboard, captureMouse);
}
rendering.UIVisible = IsGaming ? UIVisibilityState::Game : UIVisibilityState::Debug;
}
} // namespace
void Level::Setup(GameData& data)
{
LOG("Level setup");
void* storagePtr = data.EntityStorage;
uint8_t* storagePtr = data.EntityArena.Base;
bool needReset = false;
needReset |= Cubes.Setup(storagePtr, needReset);
needReset |= Tests.Setup(storagePtr, needReset);
needReset |= PuzzleTiles.Setup(storagePtr, needReset);
needReset |= PuzzleTileCovers.Setup(storagePtr, needReset);
needReset |= UIQuads.Setup(storagePtr, needReset);
needReset |= LevelEntities.Setup(storagePtr, needReset);
PuzzleData.Setup();
Puzzle::Setup();
UIQuads.Count = 0;
PuzzleTiles.Count = 0;
if (Cubes.Count == 0)
bx::Error err;
bx::DirectoryReader dirIter;
bx::FileInfo info;
bx::FilePath puzzleDirPath{Puzzle::PuzzleFileDir};
if (dirIter.open(puzzleDirPath, &err))
{
for (uint32_t yy = 0; yy < 11; ++yy)
while (true)
{
for (uint32_t xx = 0; xx < 11; ++xx)
int32_t readCount = dirIter.read(&info, sizeof(info), &err);
if (readCount == 0) break;
if (err.isOk())
{
Cube& c = Cubes.Get(Cubes.New());
c.TestX = xx;
c.TestY = yy;
c.Setup();
if (info.type != bx::FileType::File) continue;
bx::StringView pathEnd = info.filePath.getExt();
if (bx::strCmpI(pathEnd, ".pzl") != 0) continue;
bx::FilePath fullPath = puzzleDirPath;
fullPath.join(info.filePath);
LOG("Loading %s", fullPath.getCPtr());
Gen::Deserializer ser;
Gen::PuzzleData dataBuf;
if (ser.Init(fullPath, "PZZL") && ser.ReadT(dataBuf))
{
if (dataBuf.ID >= BX_COUNTOF(Puzzles))
{
LOG_ERROR("Puzzle ID out of bounds: %u", dataBuf.ID);
ser.Finish();
continue;
}
Puzzles[dataBuf.ID].Data = dataBuf;
Puzzles[dataBuf.ID].Setup();
}
else
{
LOG_WARN("Failed to load puzzle!");
}
ser.Finish();
}
else
{
LOG_ERROR("Failed parsing file name at %s:\n%s", puzzleDirPath.getCPtr(), err.getMessage());
break;
}
}
Cubes.New(); // Floor
}
if (Tests.Count == 0)
else
{
Tests.Get(Tests.New()).Setup();
LOG_ERROR("Failed to open puzzle dir at %s:\n%s", puzzleDirPath.getCPtr(), err.getMessage().getCPtr());
}
if (PuzzleTiles.Count == 0)
if (!IsValid(PlayerOutsideViewCube))
{
for (uint32_t puzI = 0; puzI < BX_COUNTOF(Puzzles); ++puzI)
PlayerOutsideViewCube = Cubes.New();
Cubes.Get(PlayerOutsideViewCube).Setup();
}
{
Puzzles[puzI].Setup();
Deserializer d;
d.Init("game/data/static/uiconfig.dat", "UICO");
d.ReadT(GetInstance().Player.Config);
d.Finish();
}
UIQuads.Count = 0;
PuzzleTiles.Count = 0;
PuzzleTileCovers.Count = 0;
for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i)
{
if (Puzzles[i].Data.ID != UINT16_MAX)
{
Puzzles[i].Setup();
}
}
PuzzleUI.Setup();
ReloadLevelEntities();
UpdatePlayerInputMode();
for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i)
{
Puzzles[i].WorldPosition = {0.0f, 0.0f, i * 50.0f};
}
}
bool IsInPuzzle(WorldPuzzle& puz, Vec3 worldPos)
{
Vec3 offsetToPuzzle = worldPos - puz.WorldPosition + (Vec3{0.5f, 0.0f, 0.5f} * Puzzle::Config::CardScaleWorld);
Vec3 scaledOffset = offsetToPuzzle / Puzzle::Config::CardScaleWorld;
int32_t offsetX = (int32_t)bx::floor(scaledOffset.x);
int32_t offsetY = (int32_t)bx::floor(scaledOffset.z);
return (offsetX >= 0 && offsetX < puz.Data.WidthTiles / 2 && offsetY >= -1 &&
offsetY < puz.Data.HeightTiles / 2);
}
bool IsOnGround(Level& level, Vec3 worldPos)
{
for (auto& puz : level.Puzzles)
{
Vec3 offsetToPuzzle =
worldPos - puz.WorldPosition + (Vec3{0.5f, 0.0f, 0.5f} * Puzzle::Config::CardScaleWorld);
Vec3 scaledOffset = offsetToPuzzle / Puzzle::Config::CardScaleWorld;
int32_t offsetX = (int32_t)bx::floor(scaledOffset.x);
int32_t offsetY = puz.Data.HeightTiles / 2 - (int32_t)bx::floor(scaledOffset.z) - 1;
float fracOffsetX = scaledOffset.x - offsetX;
float fracOffsetY = scaledOffset.z - bx::floor(scaledOffset.z);
if (offsetX >= 0 && offsetX < puz.Data.WidthTiles / 2 && offsetY >= 0 && offsetY < puz.Data.HeightTiles / 2)
{
auto& card = puz.Data.PlacedCards[offsetY * Puzzle::Config::MaxPuzzleSizeCards + offsetX];
if (card.RefCard.Idx == UINT16_MAX)
{
return true;
}
auto& refCard = Puzzle::GetStaticPuzzleData().Cards[card.RefCard.Idx];
if (!IsValid(refCard.BaseModelHandle))
{
LOG_WARN("missing base model! @ %i %i", offsetX, offsetY);
return true;
}
auto& heightmap = GameRendering::Get().Models[refCard.BaseModelHandle.ModelIdx].Height;
int32_t xPos = (int32_t)(fracOffsetX * heightmap.Width);
int32_t yPos = (int32_t)(fracOffsetY * heightmap.Height);
uint8_t height = 0;
switch (card.Rotation)
{
case 0:
height = heightmap.Values[yPos * heightmap.Width + xPos];
break;
case 1:
height = heightmap.Values[xPos * heightmap.Width + (heightmap.Height - yPos - 1)];
break;
case 2:
height =
heightmap
.Values[(heightmap.Height - yPos - 1) * heightmap.Width + (heightmap.Width - xPos - 1)];
break;
default:
height = heightmap.Values[(heightmap.Height - xPos - 1) * heightmap.Width + yPos];
break;
}
return height >= 110 && height <= 125;
}
if (offsetX == 1 && offsetY == puz.Data.HeightTiles / Puzzle::Config::CardSize)
{
if (puz.IsSolved)
{
return true;
}
return true; // TODO!
}
}
return false;
}
void Level::Update()
{
ZoneScopedN("Level update");
START_PERF();
PlayerData& player = GetInstance().Player;
// Input
float delta = GetInstance().Time.Delta;
delta = 1.0f / 144.0f;
constexpr float moveSpeed = 10.0f;
constexpr float rotSpeed = 0.6f;
float moveSpeed = player.MovementSpeed;
float rotSpeed = player.MouseSensitivity;
float forwardInput = (GetKey(ScanCode::W) ? 1.0f : 0.0f) + (GetKey(ScanCode::S) ? -1.0f : 0.0f);
float rightInput = (GetKey(ScanCode::D) ? 1.0f : 0.0f) + (GetKey(ScanCode::A) ? -1.0f : 0.0f);
@@ -118,7 +308,7 @@ namespace Game
moveInput = bx::normalize(moveInput);
bx::Vec3 inputVec = {moveInput.x * delta * moveSpeed, 0.0f, moveInput.y * delta * moveSpeed};
Vec2 mouseMovement = GetMouseMovement();
bx::Vec3 rotInput = {mouseMovement.y * delta * rotSpeed, mouseMovement.x * delta * rotSpeed, 0.0f};
bx::Vec3 rotInput = {mouseMovement.y * delta * -rotSpeed, mouseMovement.x * delta * -rotSpeed, 0.0f};
if (GetKeyPressedNow(ScanCode::F1))
{
@@ -144,23 +334,57 @@ namespace Game
{
player.FreeflyXRot += rotInput.x;
player.FreeflyYRot += rotInput.y;
bx::mtxRotateY(player.FreeflyCamTransform.Rotation.M, player.FreeflyYRot);
player.FreeflyCamTransform.RotateLocal({player.FreeflyXRot, 0.0f, 0.0f});
bx::mtxRotateXYZ(player.FreeflyCamTransform.Rotation.M, player.FreeflyXRot, player.FreeflyYRot, 0.0f);
}
player.FreeflyCamTransform.TranslateLocal({0.0f, 0.0f, -inputVec.z});
player.FreeflyCamTransform.TranslateLocal({-inputVec.x, 0.0f, 0.0f});
TranslateLocal(player.FreeflyCamTransform, {0.0f, 0.0f, inputVec.z});
TranslateLocal(player.FreeflyCamTransform, {inputVec.x, 0.0f, 0.0f});
}
else if (player.CameraM == CameraMode::Walk)
{
player.PlayerCamTransform.TranslateLocal({0.0f, 0.0f, -inputVec.z});
player.PlayerCamTransform.TranslateLocal({-inputVec.x, 0.0f, 0.0f});
player.PlayerCamTransform.Position.y = -3.0f;
auto newTransform = player.PlayerCamTransform;
// Global and local are inverted because camera
Vec3 globalInput = GlobalToLocalDirection(newTransform, {inputVec.x, 0.0f, inputVec.z});
Translate(newTransform, globalInput);
newTransform.Position.y = 3.0f;
if (IsOnGround(*this, newTransform.Position))
{
player.PlayerCamTransform = newTransform;
}
else
{
auto newTransform = player.PlayerCamTransform;
Translate(newTransform, {globalInput.x, 0.0f, 0.0f});
newTransform.Position.y = 3.0f;
if (IsOnGround(*this, newTransform.Position))
{
player.PlayerCamTransform = newTransform;
}
else
{
auto newTransform = player.PlayerCamTransform;
Translate(newTransform, {0.0f, 0.0f, globalInput.z});
newTransform.Position.y = 3.0f;
if (IsOnGround(*this, newTransform.Position))
{
player.PlayerCamTransform = newTransform;
}
}
}
player.WalkXRot += rotInput.x;
player.WalkYRot += rotInput.y;
bx::mtxRotateY(player.PlayerCamTransform.Rotation.M, player.WalkYRot);
player.PlayerCamTransform.RotateLocal({player.WalkXRot, 0.0f, 0.0f});
bx::mtxRotateXYZ(player.PlayerCamTransform.Rotation.M, player.WalkXRot, player.WalkYRot, 0.0f);
}
if (GetKeyPressedNow(ScanCode::SPACE))
{
player.InteractionM = player.InteractionM == InteractionMode::ReadTablet ? InteractionMode::Walk
: InteractionMode::ReadTablet;
UpdatePlayerInputMode();
}
// Cubes
@@ -170,129 +394,235 @@ namespace Game
}
// Puzzle tiles
Puzzle::PuzzleSolver solver;
uint16_t activeIdx = GetInstance().DebugData.SelectedDebugLevel;
for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i)
{
Puzzles[i].Update();
if (IsInPuzzle(Puzzles[i], player.PlayerCamTransform.Position))
{
activeIdx = i;
}
Puzzles[i].IsActive = activeIdx == i;
Puzzles[i].Update();
Puzzles[i].IsSolved = solver.IsPuzzleSolved(Puzzles[i].Data);
}
PuzzleUI.Update(Puzzles[activeIdx].Data, Puzzles[activeIdx].IsSolved);
END_PERF(GetShared().Window.PerfCounters, PerfCounterType::GameLevelUpdate, GetShared().Window.FrameCounter);
}
void Level::Render(uint16_t viewId, const Model* models, const Material* materials)
void Level::Render(uint16_t viewId, const Model* models, const Material* materials, const Texture* textures)
{
ZoneScopedN("Level Render");
auto& shared = GetShared();
auto& player = GetInstance().Player;
float proj[16];
bx::mtxProj(proj,
bx::mtxProj(&player.Projection.M[0],
75.0f,
float(shared.Window.WindowWidth) / float(shared.Window.WindowHeight),
0.1f,
1000.0f,
bgfx::getCaps()->homogeneousDepth);
bx::mtxInverse(&player.ProjectionInverse.M[0], &player.Projection.M[0]);
auto& player = GetInstance().Player;
if (player.CameraM == CameraMode::Freefly)
bool isFreefly = player.CameraM == CameraMode::Freefly;
Cubes.Get(PlayerOutsideViewCube).EData.Visible = isFreefly;
if (isFreefly)
{
player.FreeflyCamTransform.UpdateMatrix();
bgfx::setViewTransform(viewId, player.FreeflyCamTransform.M.M, proj);
UpdateMatrix(player.FreeflyCamTransform);
bgfx::setViewTransform(viewId, player.FreeflyCamTransform.MI.M, &player.Projection.M[0]);
}
else
{
player.PlayerCamTransform.UpdateMatrix();
bgfx::setViewTransform(viewId, player.PlayerCamTransform.M.M, proj);
bgfx::dbgTextPrintf(1, 0, 0b1100, " ");
UpdateMatrix(player.PlayerCamTransform);
bgfx::setViewTransform(viewId, player.PlayerCamTransform.MI.M, &player.Projection.M[0]);
}
// Cubes.Render(models, materials);
Tests.Render(models, materials);
PuzzleTiles.Render(models, materials);
UIQuads.Render(models, materials);
bgfx::touch(viewId);
Cubes.Render(models, materials, textures);
Tests.Render(models, materials, textures);
PuzzleTiles.Render(models, materials, textures);
PuzzleTileCovers.Render(models, materials, textures);
if (player.InteractionM == InteractionMode::ReadTablet)
{
UIQuads.Render(models, materials, textures);
}
LevelEntities.Render(models, materials, textures);
}
void Cube::Setup()
{
EData.MaterialHandle = 0;
EData.ModelHandle = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf");
EData.MaterialHandle = EMaterial::UI;
EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf");
}
void Cube::Update()
{
if (TestX >= 0 && TestY >= 0)
{
double globalTime = GetInstance().Time.Now;
double time = TestY <= 5 ? globalTime * 1.0f : 0.0f;
float scale = 1.0f + TestX * 0.4f;
EData.Transform.Position = bx::Vec3{TestX * 2.0f, TestY * 2.0f, 0.0f};
EData.Transform.Scale = {scale, scale, scale};
}
else
{
EData.Transform.Position = {0.0f, -1.0f, 0.0f};
EData.Transform.Scale = {100.0f, 1.0f, 100.0f};
EData.TestColor[0] = 0.3f;
EData.TestColor[1] = 0.325f;
EData.TestColor[2] = 0.3f;
}
EData.Transform.Position = GetInstance().Player.PlayerCamTransform.Position;
EData.Transform.Rotation = GetInstance().Player.PlayerCamTransform.Rotation;
EData.Transform.Scale = {0.2f, 0.2f, 0.2f};
}
void TestEntity::Setup()
{
EData.MaterialHandle = 0;
EData.ModelHandle = GameRendering::Get().GetModelHandleFromPath("models/zurg.gltf");
EData.Transform.Position = {0.0f, 0.0f, 0.0f};
EData.TestColor[0] = 0.0f;
EData.MaterialHandle = EMaterial::Default;
EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf");
}
void WorldPuzzle::Setup()
{
Data.PlacedCardCount = 16;
for (int32_t i = 0; i < 16; ++i)
{
Data.PlacedCards[i].RefCard = {0};
Data.PlacedCards[i].Position = {int8_t(i % 4), int8_t(i / 4)};
}
for (uint32_t cardI = 0; cardI < Data.PlacedCardCount; ++cardI)
{
const Puzzle::PlacedPuzzleCard& card = Data.PlacedCards[cardI];
Level& level = GetInstance().GameLevel;
TileHandles[cardI] = level.PuzzleTiles.New();
UIPlacedCards[cardI] = level.UIQuads.New();
for (int32_t i = 0; i < Puzzle::Config::MaxCardsInPuzzle; ++i)
{
TileHandles[i] = level.PuzzleTiles.New();
PuzzleTileEntity& tile = level.PuzzleTiles.Get(TileHandles[i]);
tile.EData.MaterialHandle = EMaterial::Default;
bx::Vec3 Pos = {
WorldPosition.x + card.Position.X * WorldCardSize.x,
WorldPosition.y,
WorldPosition.z + card.Position.Y * WorldCardSize.y,
};
PuzzleTileEntity& tile = level.PuzzleTiles.Get(TileHandles[cardI]);
tile.EData.Transform.Position = Pos;
tile.EData.MaterialHandle = 0;
UIQuadEntity& quad = level.UIQuads.Get(UIPlacedCards[cardI]);
quad.EData.MaterialHandle = 0;
quad.EData.ModelHandle = GameRendering::Get().GetModelHandleFromPath("models/plane.glb");
for (int32_t j = 0; j < Puzzle::Config::MaxCoversInTile; ++j)
{
int32_t idx = i * Puzzle::Config::MaxCoversInTile + j;
CoverHandles[idx] = level.PuzzleTileCovers.New();
PuzzleTileCover& cover = level.PuzzleTileCovers.Get(CoverHandles[idx]);
cover.EData.Visible = false;
}
}
for (int32_t i = 0; i < BX_COUNTOF(EndHandles); ++i)
{
EndHandles[i] = level.PuzzleTiles.New();
PuzzleTileEntity& tile = level.PuzzleTiles.Get(EndHandles[i]);
tile.EData.MaterialHandle = EMaterial::Default;
}
WallHandle = level.PuzzleTiles.New();
PuzzleTileEntity& wHandle = level.PuzzleTiles.Get(WallHandle);
wHandle.EData.MaterialHandle = EMaterial::Default;
wHandle.EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/GateWall.glb");
DoorHandle = level.PuzzleTiles.New();
PuzzleTileEntity& dHandle = level.PuzzleTiles.Get(DoorHandle);
dHandle.EData.MaterialHandle = EMaterial::Default;
dHandle.EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/GateDoor.glb");
IsSetup = true;
LOG("finished setup!");
}
Vec3 PuzPosToWorldPos(const PuzzleData& data, int32_t x, int32_t y)
{
return {
(float)x * Puzzle::Config::CardScaleWorld,
-5.0f,
(float)(data.HeightTiles / 2 - y - 1) * Puzzle::Config::CardScaleWorld,
};
}
void WorldPuzzle::Update()
{
Transform& camTransform = GetInstance().Player.PlayerCamTransform;
Vec3 cameraPos = camTransform.GetPosition() * -1;
Level& level = GetInstance().GameLevel;
auto& staticCards = Puzzle::GetStaticPuzzleData().Cards;
auto& visuals = Puzzle::GetStaticPuzzleData().Visuals;
for (int32_t cardI = 0; cardI < Data.PlacedCardCount; ++cardI)
// Board
for (int8_t y = 0; y < Data.HeightTiles / Puzzle::Config::CardSize; ++y)
{
Puzzle::PlacedPuzzleCard& card = Data.PlacedCards[cardI];
if (!card.RefCard.IsValid()) continue;
const Puzzle::StaticPuzzleCard& cData = Puzzle::StaticPuzzleData::Get().GetCard(card.RefCard);
level.PuzzleTiles.Get(TileHandles[cardI]).EData.ModelHandle = cData.ModelHandle;
for (int8_t x = 0; x < Data.WidthTiles / Puzzle::Config::CardSize; ++x)
{
int32_t cardIdx = y * Puzzle::Config::MaxPuzzleSizeCards + x;
Gen::PlacedPuzzleCard& card = Data.PlacedCards[cardIdx];
auto& tile = level.PuzzleTiles.Get(TileHandles[cardIdx]);
auto& quad = level.UIQuads.Get(UIPlacedCards[cardI]);
Vec3 fw = {0, -1, 0};
// quad.EData.Transform.SetPosition(cameraPos + Vec3{0, -2, 0});
quad.EData.Transform.SetPosition({});
quad.EData.Transform.Rotation = camTransform.Rotation;
bool isValid = Puzzle::IsValid(card.RefCard);
auto& staticCard = isValid ? staticCards[card.RefCard.Idx] : staticCards[0];
// World Tile
tile.EData.Visible = true;
tile.EData.ModelH = staticCard.BaseModelHandle;
tile.EData.TextureHandle = staticCard.ModelTextureHandle;
tile.EData.DotColor = visuals.TileDotColor;
tile.EData.BaseColor = visuals.TileBaseColor;
Vec3 cardPos = PuzPosToWorldPos(Data, card.Position.X, card.Position.Y);
if (!isValid)
{
cardPos = PuzPosToWorldPos(Data, x, y);
}
tile.EData.Transform.Position = cardPos + WorldPosition;
bx::mtxRotateY(tile.EData.Transform.Rotation.M, card.Rotation * bx::kPi * -0.5f);
// Covers
if (IsValid(staticCard.BaseModelHandle))
{
auto& model = GameRendering::Get().Models[staticCard.BaseModelHandle.ModelIdx];
for (int32_t i = 0; i < model.SocketCount; ++i)
{
auto& cover =
level.PuzzleTileCovers.Get(CoverHandles[cardIdx * Puzzle::Config::MaxCoversInTile + i]);
cover.EData.Visible = IsActive;
cover.EData.ModelH = staticCard.Sockets[i].Model;
cover.EData.Transform = tile.EData.Transform;
cover.EData.MaterialHandle = EMaterial::Default;
cover.EData.BaseColor = {0.2f, 0.1f, 0.7f, 1.0f};
cover.EData.DotColor = {0.2f, 0.2f, 0.8f, 1.0f};
Gen::TranslateLocal(cover.EData.Transform, model.Sockets[i].Pos);
Gen::RotateLocal(cover.EData.Transform, Gen::EulerFromRotation(model.Sockets[i].Rot));
}
}
}
}
// End
for (int32_t i = 0; i < BX_COUNTOF(EndHandles); ++i)
{
auto& tile = level.PuzzleTiles.Get(EndHandles[i]);
if (i < Data.WidthTiles / 2)
{
tile.EData.Visible = true;
tile.EData.ModelH = staticCards[0].BaseModelHandle;
tile.EData.TextureHandle = staticCards[0].ModelTextureHandle;
tile.EData.DotColor = visuals.TileDotColor;
tile.EData.BaseColor = visuals.TileBaseColor + Vec4{0.1f, 0.1f, 0.1f, 0.0f};
tile.EData.Transform.Position = WorldPosition + Vec3{
i * Puzzle::Config::CardScaleWorld,
-5.0f,
(float)Data.HeightTiles / Puzzle::Config::CardSize *
Puzzle::Config::CardScaleWorld,
};
}
else
{
tile.EData.Visible = false;
}
}
auto& wall = level.PuzzleTiles.Get(WallHandle);
wall.EData.Visible = true;
wall.EData.Transform.Position =
WorldPosition + Vec3{0.0f, 0.0f, Data.HeightTiles * 5.0f} + Vec3{30.0f, -5.0f, 0.2f};
auto& door = level.PuzzleTiles.Get(DoorHandle);
door.EData.Visible = !IsSolved;
door.EData.Transform.Position =
WorldPosition + Vec3{0.0f, 0.0f, Data.HeightTiles * 5.0f} + Vec3{30.0f, -5.0f, 0.2f};
}
void Level::ReloadLevelEntities()
{
LevelEntities.Count = 0;
for (int32_t i = 0; i < BX_COUNTOF(BackgroundEntityHandles); ++i)
{
BackgroundEntityHandles[i] = LevelEntities.New();
auto& levelBgEntity = LevelEntities.Get(BackgroundEntityHandles[i]);
levelBgEntity.EData.LoadFromSaved(GetInstance().Player.Config.BackgroundLevelRenderData[i]);
}
}
int32_t GetNextRenderID()
{
static int32_t RenderIDCounter = 0;
RenderIDCounter++;
return RenderIDCounter;
}
} // namespace Game

View File

@@ -1,137 +1,29 @@
#pragma once
#include "../engine/Shared.h"
#include "Global.h"
#include "Log.h"
#include "Entity.h"
#include "Puzzle.h"
#include "UI.h"
#include "rendering/Rendering.h"
#include <bgfx/bgfx.h>
#include <cstdint>
#define ENTITY_HANDLE(X) \
struct X \
{ \
uint16_t Idx = UINT16_MAX; \
};
namespace Game
{
struct EntityRenderData
{
float TestColor[4]{1.0f, 1.0f, 1.0f, 1.0f};
Transform Transform;
uint16_t MaterialHandle = UINT16_MAX;
uint16_t ModelHandle = UINT16_MAX;
bool Visible = true;
void Render(const Model* models, const Material* materials);
};
ENTITY_HANDLE(CubeHandle);
struct Cube
{
int32_t TestX = -1;
int32_t TestY = -1;
EntityRenderData EData;
void Setup();
void Update();
};
ENTITY_HANDLE(TestEntityHandle);
struct TestEntity
{
EntityRenderData EData;
void Setup();
};
ENTITY_HANDLE(PuzzleTileEntityHandle);
struct PuzzleTileEntity
{
EntityRenderData EData;
};
ENTITY_HANDLE(UIQuadEntityHandle);
struct UIQuadEntity
{
EntityRenderData EData;
};
class IEntityManager
{
public:
virtual bool Setup(void*& ptr, bool forceReset) = 0;
};
template <typename T, typename HandleT, uint32_t C> class EntityManager : public IEntityManager
{
public:
uint16_t Count = 0;
T* Data = nullptr;
uint32_t EntitySize = 0;
T InvalidObject{};
public:
// Returns true if size changed
bool Setup(void*& ptr, bool forceReset)
{
bool changed = false;
if (EntitySize != sizeof(T) || forceReset)
{
Count = 0;
changed = true;
}
EntitySize = sizeof(T);
Data = reinterpret_cast<T*>(ptr);
ptr = (uint8_t*)ptr + (C * EntitySize);
return changed;
}
HandleT New()
{
if (Data == nullptr)
{
ERROR_ONCE("Accessed EntityManager before setup!");
return {};
}
if (Count >= C)
{
ERROR_ONCE("Too many entities!");
return {};
}
Data[Count] = {};
HandleT H;
H.Idx = Count;
++Count;
return H;
}
T& Get(HandleT handle)
{
if (handle.Idx > Count)
{
ERROR_ONCE("OOB Access!");
return InvalidObject;
}
return Data[handle.Idx];
}
void Render(const Model* models, const Material* materials)
{
for (uint16_t i = 0; i < Count; ++i)
{
Get({i}).EData.Render(models, materials);
}
}
};
struct WorldPuzzle
{
static constexpr Vec2 WorldCardSize{10.0f, 10.0f};
Puzzle::PuzzleData Data;
Vec3 WorldPosition;
static constexpr Gen::Vec2 WorldCardSize{10.0f, 10.0f};
Gen::PuzzleData Data;
Gen::Vec3 WorldPosition;
PuzzleTileEntityHandle TileHandles[Puzzle::Config::MaxCardsInPuzzle];
UIQuadEntityHandle UIPlacedCards[Puzzle::Config::MaxCardsInPuzzle];
PuzzleTileCoverHandle CoverHandles[Puzzle::Config::MaxCardsInPuzzle * Puzzle::Config::MaxCoversInTile];
PuzzleTileEntityHandle EndHandles[Puzzle::Config::MaxPuzzleSizeCards];
PuzzleTileEntityHandle WallHandle;
PuzzleTileEntityHandle DoorHandle;
bool IsSetup = false;
bool IsActive = false;
bool IsSolved = false;
void Setup();
void Update();
@@ -142,16 +34,23 @@ namespace Game
public:
EntityManager<Cube, CubeHandle, 1024> Cubes;
EntityManager<TestEntity, TestEntityHandle, 32> Tests;
EntityManager<PuzzleTileEntity, PuzzleTileEntityHandle, 1024> PuzzleTiles;
EntityManager<UIQuadEntity, UIQuadEntityHandle, 1024> UIQuads;
EntityManager<PuzzleTileEntity, PuzzleTileEntityHandle, Puzzle::Config::MaxTilesTotal> PuzzleTiles;
EntityManager<PuzzleTileCover, PuzzleTileCoverHandle, Puzzle::Config::MaxCoversTotal> PuzzleTileCovers;
UIQuadEntityManager UIQuads;
EntityManager<LevelEntity, LevelEntityHandle, 64> LevelEntities;
CubeHandle PlayerOutsideViewCube;
LevelEntityHandle BackgroundEntityHandles[16];
public:
Puzzle::StaticPuzzleData PuzzleData;
WorldPuzzle Puzzles[1];
Gen::StaticPuzzleData PuzzleData;
WorldPuzzle Puzzles[Puzzle::Config::MaxVisiblePuzzles];
WorldPuzzleUI PuzzleUI;
public:
void Setup(GameData& data);
void Update();
void Render(uint16_t ViewID, const Model* models, const Material* materials);
void Render(uint16_t ViewID, const Model* models, const Material* materials, const Texture* textures);
void ReloadLevelEntities();
};
} // namespace Game

View File

@@ -1,10 +1,13 @@
#include "Log.h"
#include "bx/handlealloc.h"
#include "bx/hash.h"
#include "bx/timer.h"
#include <bx/string.h>
#define WIN32_LEAN_AND_MEAN
#include "Windows.h"
#include "Instance.h"
#define ESC "\x1B["
#define YELLOW ESC "33m"
#define RED ESC "31m"
@@ -12,9 +15,12 @@
namespace
{
char LineBuffer[1024]{0};
char OutBuffer[1024]{0};
char LineBuffer[LogInternal::MaxLineSize]{0};
char OutBuffer[LogInternal::MaxLineSize]{0};
char OutBufferUI[LogInternal::MaxLineSize]{0};
bx::HandleHashMapT<1024> OnceMap;
LogHistory History;
constexpr char LogFormat[]{"%s\n"};
constexpr char WarnFormat[]{YELLOW "%s" END "\n"};
constexpr char ErrorFormat[]{RED "%s" END "\n"};
@@ -31,8 +37,18 @@ void Log(ELogType logType, const char* file, uint32_t line, const char* format,
bx::snprintf(LineBuffer, sizeof(LineBuffer), LineFormat, format);
bx::vprintf(LineBuffer, args);
bx::vsnprintf(OutBuffer, sizeof(OutBuffer), LineBuffer, args);
bx::vsnprintf(OutBufferUI, sizeof(OutBufferUI), format, args);
va_end(args);
OutputDebugStringA(OutBuffer);
bx::strCopy(&History.LogBuffer[History.WriteIdx * LogInternal::MaxLineSize], LogInternal::MaxLineSize, OutBufferUI);
History.WriteTime[History.WriteIdx] = bx::getHPCounter();
History.WriteType[History.WriteIdx] = logType;
bx::strCopy(&History.FileBuffer[History.WriteIdx * LogInternal::MaxLineSize], LogInternal::MaxLineSize, file);
History.LineBuffer[History.WriteIdx] = line;
++History.WriteIdx;
if (History.WriteIdx >= LogInternal::LogHistorySize) History.WriteIdx = 0;
}
bool WasLogged(ELogType logType, const char* file, uint32_t line, const char* format)
@@ -48,3 +64,8 @@ bool WasLogged(ELogType logType, const char* file, uint32_t line, const char* fo
OnceMap.insert(hash, {});
return false;
}
LogHistory& GetLogHistory()
{
return History;
}

View File

@@ -27,5 +27,22 @@ enum class ELogType
Error,
};
namespace LogInternal
{
constexpr int32_t MaxLineSize = 1024;
constexpr int32_t LogHistorySize = 1024;
} // namespace LogInternal
struct LogHistory
{
char LogBuffer[LogInternal::MaxLineSize * LogInternal::LogHistorySize]{0};
char FileBuffer[LogInternal::MaxLineSize * LogInternal::LogHistorySize]{0};
uint32_t LineBuffer[LogInternal::LogHistorySize]{0};
int32_t WriteIdx = 0;
int64_t WriteTime[LogInternal::LogHistorySize]{0};
ELogType WriteType[LogInternal::LogHistorySize]{ELogType::Log};
};
void Log(ELogType logType, const char* file, uint32_t line, const char* format, ...);
bool WasLogged(ELogType logType, const char* file, uint32_t line, const char* format);
LogHistory& GetLogHistory();

View File

@@ -1,13 +1,20 @@
#include "Gen.h"
#include "Global.h"
#include "Log.h"
#include "Mesh.h"
#include "bgfx/bgfx.h"
#include "bx/bx.h"
#include "bx/error.h"
#include "bx/file.h"
#include "bx/filepath.h"
#include "bx/hash.h"
#include "bx/string.h"
#include "bx/timer.h"
#include "rendering/Rendering.h"
#include "Instance.h"
#include <cstdint>
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
@@ -17,6 +24,7 @@ namespace Game
{
bool LoadMesh(Model& mesh, const char* path, bool isBinary)
{
bx::strCopy(mesh.Name, sizeof(mesh.Name), path);
mesh.VertLayout.begin()
.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
.add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float)
@@ -54,23 +62,23 @@ namespace Game
tinygltf::Primitive primitive = model.meshes[0].primitives[0];
{
tinygltf::Accessor accessor = model.accessors.at(primitive.indices);
tinygltf::BufferView bufferView = model.bufferViews.at(accessor.bufferView);
tinygltf::Buffer buffer = model.buffers[bufferView.buffer];
const bgfx::Memory* ibMem = bgfx::alloc(bufferView.byteLength);
bx::memCopy(ibMem->data, &buffer.data.at(bufferView.byteOffset), bufferView.byteLength);
tinygltf::Accessor indexAccessor = model.accessors.at(primitive.indices);
tinygltf::BufferView indexBufferView = model.bufferViews.at(indexAccessor.bufferView);
int32_t indexStride = sizeof(uint16_t);
tinygltf::Buffer indexBuffer = model.buffers[indexBufferView.buffer];
const bgfx::Memory* ibMem = bgfx::alloc(indexBufferView.byteLength);
bx::memCopy(ibMem->data, &indexBuffer.data.at(indexBufferView.byteOffset), indexBufferView.byteLength);
mesh.IndexBuffer = bgfx::createIndexBuffer(ibMem);
}
{
tinygltf::Accessor posAccessor = model.accessors.at(primitive.attributes.at("POSITION"));
tinygltf::Accessor normalAccessor = model.accessors.at(primitive.attributes.at("NORMAL"));
tinygltf::Accessor uvAccessor = model.accessors.at(primitive.attributes.at("TEXCOORD_0"));
tinygltf::BufferView posBufferView = model.bufferViews[posAccessor.bufferView];
tinygltf::BufferView normalBufferView = model.bufferViews[normalAccessor.bufferView];
tinygltf::BufferView uvBufferView = model.bufferViews[uvAccessor.bufferView];
int posStride = posAccessor.ByteStride(posBufferView);
int normalStride = normalAccessor.ByteStride(normalBufferView);
int uvStride = uvAccessor.ByteStride(uvBufferView);
int32_t posStride = posAccessor.ByteStride(posBufferView);
int32_t normalStride = normalAccessor.ByteStride(normalBufferView);
int32_t uvStride = uvAccessor.ByteStride(uvBufferView);
tinygltf::Buffer posBuffer = model.buffers[posBufferView.buffer];
tinygltf::Buffer normalBuffer = model.buffers[normalBufferView.buffer];
tinygltf::Buffer uvBuffer = model.buffers[uvBufferView.buffer];
@@ -88,7 +96,113 @@ namespace Game
bx::memCopy(&v.uv_x, &uvBuffer.data.at(uvBufferView.byteOffset + i * uvStride), uvStride);
}
mesh.VertexBuffer = bgfx::createVertexBuffer(vbMem, mesh.VertLayout);
constexpr float SIZE_LIMIT = 1000.0f;
mesh.MinPos = {SIZE_LIMIT, SIZE_LIMIT, SIZE_LIMIT};
mesh.MaxPos = {-SIZE_LIMIT, -SIZE_LIMIT, -SIZE_LIMIT};
bx::memSet(mesh.Height.Values, 0, BX_COUNTOF(mesh.Height.Values));
int64_t startTime = bx::getHPCounter();
for (int32_t i = 0; i < vertexCount; ++i)
{
Gen::Vec3* pos =
reinterpret_cast<Gen::Vec3*>(&posBuffer.data[posBufferView.byteOffset + i * posStride]);
if (pos->x < mesh.MinPos.x) mesh.MinPos.x = pos->x;
if (pos->y < mesh.MinPos.y) mesh.MinPos.y = pos->y;
if (pos->z < mesh.MinPos.z) mesh.MinPos.z = pos->z;
if (pos->x > mesh.MaxPos.x) mesh.MaxPos.x = pos->x;
if (pos->y > mesh.MaxPos.y) mesh.MaxPos.y = pos->y;
if (pos->z > mesh.MaxPos.z) mesh.MaxPos.z = pos->z;
}
LOG("min/max: %lli", bx::getHPCounter() - startTime);
mesh.MinPos = {-5.0f, -5.0f, -5.0f};
mesh.MaxPos = {5.0f, 5.0f, 5.0f};
mesh.Size = mesh.MaxPos - mesh.MinPos;
startTime = bx::getHPCounter();
for (uint32_t v = 0; v < HeightMap::Height; ++v)
{
float vPos = mesh.MinPos.z + (float)v / HeightMap::Height * mesh.Size.z;
for (uint32_t u = 0; u < HeightMap::Width; ++u)
{
float uPos = mesh.MinPos.x + (float)u / HeightMap::Width * mesh.Size.x;
Gen::Vec3 rayStart = {uPos, -100.0f, vPos};
Gen::Vec3 rayEnd = {uPos, 100.0f, vPos};
Gen::Vec3 ptOut;
for (int16_t i = 0; i < indexBufferView.byteLength; i += indexStride * 3)
{
uint16_t* idxA = reinterpret_cast<uint16_t*>(&indexBuffer.data[indexBufferView.byteOffset + i]);
uint16_t* idxB = reinterpret_cast<uint16_t*>(
&indexBuffer.data[indexBufferView.byteOffset + i + 1 * indexStride]);
uint16_t* idxC = reinterpret_cast<uint16_t*>(
&indexBuffer.data[indexBufferView.byteOffset + i + 2 * indexStride]);
Gen::Vec3* triA =
reinterpret_cast<Gen::Vec3*>(&posBuffer.data[posBufferView.byteOffset + *idxA * posStride]);
Gen::Vec3* triB =
reinterpret_cast<Gen::Vec3*>(&posBuffer.data[posBufferView.byteOffset + *idxB * posStride]);
Gen::Vec3* triC =
reinterpret_cast<Gen::Vec3*>(&posBuffer.data[posBufferView.byteOffset + *idxC * posStride]);
if (Gen::RayTriangleIntersect(rayStart, rayEnd, *triA, *triB, *triC, ptOut))
{
float len = ptOut.y - rayStart.y;
uint8_t val = (uint8_t)(len / mesh.Size.y * UINT8_MAX);
int32_t idx = v * HeightMap::Width + u;
if (mesh.Height.Values[idx] < val)
{
mesh.Height.Values[idx] = val;
}
if (len < 0.0f)
{
LOG_ONCE("%f / %f = %u", len, mesh.Size.y, val);
}
}
}
}
}
LOG("heightmap: %lli", bx::getHPCounter() - startTime);
}
const bgfx::Memory* mem = bgfx::makeRef(&mesh.Height.Values[0], sizeof(mesh.Height.Values));
mesh.HeightMapTexture =
bgfx::createTexture2D(HeightMap::Width, HeightMap::Height, false, 1, bgfx::TextureFormat::R8, 0, mem);
for (auto& node : model.nodes)
{
if (bx::strFindI(node.name.c_str(), "_slot_").getLength() > 0)
{
if (mesh.SocketCount >= mesh.MaxSocketCount)
{
LOG_WARN("Too many sockets on mesh!");
break;
}
auto& socket = mesh.Sockets[mesh.SocketCount];
if (node.translation.size() >= 3)
{
socket.Pos.x = node.translation[0];
socket.Pos.y = node.translation[1];
socket.Pos.z = node.translation[2];
}
if (node.rotation.size() >= 4)
{
socket.Rot = Gen::RotationFromQuaternion({static_cast<float>(node.rotation[0]),
static_cast<float>(node.rotation[1]),
static_cast<float>(node.rotation[2]),
static_cast<float>(node.rotation[3])});
}
if (node.matrix.size() >= 0)
{
LOG_WARN("TODO: support matrix!");
}
bx::strCopy(&socket.Name[0], socket.MaxSocketNameLength, node.name.c_str());
++mesh.SocketCount;
}
}
return true;
}
@@ -130,6 +244,7 @@ namespace Game
}
LOG("Found %u models!", modelFilePathCount);
auto& inst = GetInstance();
int32_t writeI = 0;
for (int32_t i = 0; i < modelFilePathCount; ++i)
{
@@ -138,7 +253,16 @@ namespace Game
Model& mod = models[writeI];
if (LoadMesh(mod, fullPath.getCPtr(), modelFileIsBinary[i]))
{
mod.AssetHandle = CrcPath(fullPath.getCPtr());
mod.Handle.Asset.Idx = CrcPath(fullPath.getCPtr());
mod.Handle.ModelIdx = writeI;
if (inst.DebugData.AssetCount < inst.DebugData.MaxAssets)
{
inst.DebugData.AssetHandles[inst.DebugData.AssetCount] = mod.Handle.Asset;
bx::strCopy(inst.DebugData.AssetHandlePaths[inst.DebugData.AssetCount],
sizeof(inst.DebugData.AssetHandlePaths[inst.DebugData.AssetCount]),
fullPath.getCPtr());
++inst.DebugData.AssetCount;
}
++writeI;
}
else

View File

@@ -1,82 +1,177 @@
#include "../gen/Def.h"
#include "Gen.h"
#include "Global.h"
#include "Log.h"
#include "Puzzle.h"
#include "bx/string.h"
#include "imgui.h"
#include "rendering/Rendering.h"
#include "bx/bx.h"
#include "bx/string.h"
#include "imgui.h"
#include <cassert>
namespace
{
static constexpr Puzzle::PuzPos Dirs[4]{
using namespace Gen;
static constexpr PuzPos Dirs[4]{
{-1, 0},
{0, -1},
{0, 1},
{1, 0},
};
Puzzle::StaticPuzzleData* StaticDataInstance = nullptr;
StaticPuzzleData StaticData;
StaticPuzzleCard InvalidCard;
PlacedPuzzleCard InvalidPlacedCard;
} // namespace
namespace Puzzle
{
void StaticPuzzleData::Setup()
constexpr float UIPuzBoxSize = 26;
using namespace Gen;
void Setup()
{
StaticDataInstance = this;
LOG("Setting up static puzzle data");
for (int32_t i = 0; i < BX_COUNTOF(Cards); ++i)
{
Cards[i].ModelHandle = Game::GameRendering::Get().GetModelHandleFromPath("models/w straight.glb");
}
LoadStaticPuzzleData();
}
StaticPuzzleData& StaticPuzzleData::Get()
StaticPuzzleData& GetStaticPuzzleData()
{
assert(StaticDataInstance != nullptr);
return *StaticDataInstance;
}
const StaticPuzzleCard& StaticPuzzleData::GetCard(StaticPuzzleCardHandle H) const
{
assert(H.IsValid());
return Cards[H.Idx];
return StaticData;
}
bool PuzzleNode::HasElement(PuzzleElementType search) const
void LoadStaticPuzzleData()
{
for (int32_t i = 0; i < Config::MaxElementsPerTile; ++i)
Deserializer ser;
if (ser.Init("game/data/static/puzzle.dat", "SPUZ") && ser.ReadT(StaticData))
{
if (PlacedTypes[i] == search) return true;
LOG("Successfully loaded static puzzle data!");
}
return false;
ser.Finish();
}
bool PuzzleNode::IsEmpty() const
void SaveStaticPuzzleData()
{
for (int32_t i = 0; i < Config::MaxElementsPerTile; ++i)
auto& data = GetStaticPuzzleData();
Serializer ser;
if (ser.Init("game/data/static/puzzle.dat", "SPUZ") && ser.WriteT(GetStaticPuzzleData()))
{
if (PlacedTypes[i] != PuzzleElementType::None) return false;
LOG("Successfully saved static puzzle data!");
}
return true;
ser.Finish();
}
const StaticPuzzleCard& GetCard(StaticPuzzleCardHandle H)
{
if (H.Idx == UINT16_MAX)
{
LOG_ERROR("Invalid static card handle!");
return InvalidCard;
}
return GetStaticPuzzleData().Cards[H.Idx];
}
uint8_t PuzzleCardStack::GetRemainingCount()
bool IsValid(StaticPuzzleCardHandle h)
{
assert(MaxAvailableCount >= UsedCount);
return MaxAvailableCount - UsedCount;
return h.Idx != UINT16_MAX;
}
const PuzzleNode& PuzzleData::GetNodeAt(PuzPos pos) const
const char* GetShortNodeName(const PuzzleElementType::Enum node)
{
assert(pos.X < Config::MaxPuzzleSizeCards && pos.Y < Config::MaxPuzzleSizeCards && pos.X >= 0 && pos.Y >= 0);
return PlacedNodes[pos.Y * Config::MaxPuzzleSizeCards + pos.X];
return PuzzleElementType::ShortName[node];
}
PuzzleElementType PuzzleData::GetElementAt(ElemPos pos) const
uint8_t GetRemainingCount(const PuzzleCardStack& stack)
{
assert(pos.ElemIdx < Config::MaxElementsPerTile);
const PuzzleNode& node = GetNodeAt(pos.Position);
return node.PlacedTypes[pos.ElemIdx];
assert(stack.MaxAvailableCount >= stack.UsedCount);
return stack.MaxAvailableCount - stack.UsedCount;
}
PuzzleElementType::Enum GetNodeAt(const PuzzleData& puz, PuzPos pos)
{
assert(pos.X < Puzzle::Config::MaxPuzzleSizeTiles && pos.Y < Puzzle::Config::MaxPuzzleSizeTiles && pos.X >= 0 &&
pos.Y >= 0);
int32_t cardIdxX = pos.X / Puzzle::Config::CardSize;
int32_t cardIdxY = pos.Y / Puzzle::Config::CardSize;
auto& card = puz.PlacedCards[cardIdxY * Puzzle::Config::MaxPuzzleSizeCards + cardIdxX];
int32_t offsetX = pos.X - (cardIdxX * Config::CardSize);
int32_t offsetY = pos.Y - (cardIdxY * Config::CardSize);
if (offsetX >= 0 && offsetX < Puzzle::Config::CardSize && offsetY >= 0 && offsetY < Puzzle::Config::CardSize &&
IsValid(card.RefCard))
{
PuzzleElementType::Enum cardVal = GetCardNodeAt(GetCard(card.RefCard), card.Rotation, offsetX, offsetY);
if (cardVal != PuzzleElementType::None) return cardVal;
}
return puz.BackgroundTiles[pos.Y * Puzzle::Config::MaxPuzzleSizeTiles + pos.X];
}
PuzzleElementType::Enum GetInitialNodeAt(const PuzzleData& puz, PuzPos pos)
{
assert(pos.X < Puzzle::Config::MaxPuzzleSizeTiles && pos.Y < Puzzle::Config::MaxPuzzleSizeTiles && pos.X >= 0 &&
pos.Y >= 0);
int32_t cardIdxX = pos.X / Puzzle::Config::CardSize;
int32_t cardIdxY = pos.Y / Puzzle::Config::CardSize;
auto& card = puz.InitialPlacedCards[cardIdxY * Puzzle::Config::MaxPuzzleSizeCards + cardIdxX];
int32_t offsetX = pos.X - (cardIdxX * Config::CardSize);
int32_t offsetY = pos.Y - (cardIdxY * Config::CardSize);
if (offsetX >= 0 && offsetX < Puzzle::Config::CardSize && offsetY >= 0 && offsetY < Puzzle::Config::CardSize &&
IsValid(card.RefCard))
{
PuzzleElementType::Enum cardVal = GetCardNodeAt(GetCard(card.RefCard), card.Rotation, offsetX, offsetY);
if (cardVal != PuzzleElementType::None) return cardVal;
}
return puz.BackgroundTiles[pos.Y * Puzzle::Config::MaxPuzzleSizeTiles + pos.X];
}
PuzzleElementType::Enum GetCardNodeAt(const StaticPuzzleCard& card, uint8_t rotation, int8_t x, int8_t y)
{
assert(x >= 0 && x < Puzzle::Config::CardSize);
assert(y >= 0 && y < Puzzle::Config::CardSize);
uint8_t arrX = x;
uint8_t arrY = y;
if (rotation == 1)
{
arrX = y;
arrY = Config::CardSize - x - 1;
}
else if (rotation == 2)
{
arrX = Config::CardSize - x - 1;
arrY = Config::CardSize - y - 1;
}
else if (rotation == 3)
{
arrX = Config::CardSize - y - 1;
arrY = x;
}
return card.Elements[arrY * Puzzle::Config::CardSize + arrX];
}
PuzzleElementType::Enum& EditCardNodeAt(StaticPuzzleCard& card, uint8_t rotation, int8_t x, int8_t y)
{
assert(x >= 0 && x < Puzzle::Config::CardSize);
assert(y >= 0 && y < Puzzle::Config::CardSize);
uint8_t arrX = x;
uint8_t arrY = y;
if (rotation == 1)
{
arrX = y;
arrY = Config::CardSize - x - 1;
}
else if (rotation == 2)
{
arrX = Config::CardSize - x - 1;
arrY = Config::CardSize - y - 1;
}
else if (rotation == 3)
{
arrX = Config::CardSize - y - 1;
arrY = x;
}
return card.Elements[arrY * Puzzle::Config::CardSize + arrX];
}
bool PuzzleSolver::IsPuzzleSolved(const PuzzleData& puzzle)
@@ -93,38 +188,53 @@ namespace Puzzle
return IsSolved;
}
bool PuzzleSolver::IsExitSatisfied(const PuzzleData& puzzle, ElemPos pos)
bool PuzzleSolver::IsExitSatisfied(const PuzzleData& puzzle, PuzPos pos)
{
const PuzzleNode& goalNode = puzzle.GetNodeAt(pos.Position);
PuzzleElementType goalType = puzzle.GetElementAt(pos);
PuzzleElementType::Enum goalType = GetNodeAt(puzzle, pos);
if (goalType == PuzzleElementType::None)
{
WARN_ONCE("TODO!");
return false;
}
uint32_t currentPositionQueueIdx = 0;
uint32_t positionQueueCount = 0;
PuzPos positionQueue[Config::MaxTilesInPuzzle];
PuzPos positionQueue[Puzzle::Config::MaxTilesInPuzzle];
positionQueue[0] = pos.Position;
positionQueue[0] = pos;
positionQueueCount++;
while (positionQueueCount > 0)
while (currentPositionQueueIdx < positionQueueCount)
{
assert(currentPositionQueueIdx < Config::MaxTilesInPuzzle);
assert(currentPositionQueueIdx < Puzzle::Config::MaxTilesInPuzzle);
PuzPos currentPos = positionQueue[currentPositionQueueIdx];
for (PuzPos dir : Dirs)
{
PuzPos nextPos = currentPos + dir;
if (!(nextPos.X >= 0 && nextPos.Y >= 0 && nextPos.X < puzzle.WidthTiles &&
nextPos.Y < puzzle.HeightTiles))
continue;
if (IsValidGoalConnection(puzzle, nextPos, currentPos, goalType))
{
const PuzzleNode& node = puzzle.GetNodeAt(nextPos);
for (PuzzleElementType e : node.PlacedTypes)
{
if (IsValidSource(e, goalType))
if (IsValidSource(GetNodeAt(puzzle, nextPos), goalType))
{
return true;
}
bool found = false;
for (int32_t i = 0; i < positionQueueCount; ++i)
{
if (positionQueue[i].X == nextPos.X && positionQueue[i].Y == nextPos.Y)
{
found = true;
break;
}
}
if (!found)
{
positionQueue[positionQueueCount] = nextPos;
positionQueueCount++;
}
}
}
currentPositionQueueIdx++;
}
return false;
@@ -133,47 +243,229 @@ namespace Puzzle
bool PuzzleSolver::IsValidGoalConnection(const PuzzleData& puzzle,
PuzPos flowFrom,
PuzPos flowTo,
PuzzleElementType goalType)
PuzzleElementType::Enum goalType)
{
const PuzzleNode& from = puzzle.GetNodeAt(flowFrom);
const PuzzleNode& to = puzzle.GetNodeAt(flowTo);
auto from = GetNodeAt(puzzle, flowFrom);
auto to = GetNodeAt(puzzle, flowTo);
if (goalType == PuzzleElementType::WaterGoal)
{
return from.HasElement(PuzzleElementType::WaterIn) || from.HasElement(PuzzleElementType::WaterChannel) ||
from.HasElement(PuzzleElementType::WaterGoal);
return from == PuzzleElementType::WaterIn || from == PuzzleElementType::WaterChannel ||
from == PuzzleElementType::WaterGoal || from == PuzzleElementType::Bridge;
}
else if (goalType == PuzzleElementType::ElectricGoal)
{
return from.HasElement(PuzzleElementType::ElectricIn) || from.HasElement(PuzzleElementType::ElectricGoal) ||
from.IsEmpty();
return from == PuzzleElementType::ElectricIn || from == PuzzleElementType::ElectricGoal ||
from == PuzzleElementType::None;
}
assert(false);
// assert(false);
return false;
}
bool PuzzleSolver::IsValidSource(PuzzleElementType sourceType, PuzzleElementType goalType)
bool PuzzleSolver::IsValidSource(PuzzleElementType::Enum sourceType, PuzzleElementType::Enum goalType)
{
return (sourceType == PuzzleElementType::WaterIn && goalType == PuzzleElementType::WaterGoal) ||
(sourceType == PuzzleElementType::ElectricIn && goalType == PuzzleElementType::ElectricGoal);
}
bool PuzzleData::RenderDebugUI()
void DrawCard(const StaticPuzzleCard& card, uint8_t rotation, ImVec2 pos)
{
bool dataChanged = false;
auto& drawList = *ImGui::GetWindowDrawList();
for (int32_t y = 0; y < Puzzle::Config::CardSize; ++y)
{
for (int32_t x = 0; x < Puzzle::Config::CardSize; ++x)
{
ImGui::SetCursorScreenPos({pos.x + x * UIPuzBoxSize, pos.y + y * UIPuzBoxSize});
ImGui::Text("%s", GetShortNodeName(GetCardNodeAt(card, rotation, x, y)));
}
}
for (int32_t y = 0; y <= Puzzle::Config::CardSize; ++y)
{
ImVec2 linePos = {pos.x, pos.y + y * UIPuzBoxSize};
drawList.AddLine(linePos,
{linePos.x + Puzzle::Config::CardSize * UIPuzBoxSize, linePos.y},
y % Puzzle::Config::CardSize == 0 ? 0xFFFFFFFF : 0x11FFFFFF);
}
for (int32_t x = 0; x <= Puzzle::Config::CardSize; ++x)
{
ImVec2 linePos = {pos.x + x * UIPuzBoxSize, pos.y};
drawList.AddLine(linePos,
{linePos.x, linePos.y + Puzzle::Config::CardSize * UIPuzBoxSize},
x % Puzzle::Config::CardSize == 0 ? 0xFFFFFFFF : 0x11FFFFFF);
}
ImGui::SetCursorScreenPos(pos);
ImGui::InvisibleButton("cardbn",
{Puzzle::Config::CardSize * UIPuzBoxSize, Puzzle::Config::CardSize * UIPuzBoxSize});
}
void RotateCard(PlacedPuzzleCard& card)
{
card.Rotation += 1;
if (card.Rotation >= 4) card.Rotation = 0;
}
void WritePuzzleFilePath(char* buf, int32_t bufSize, uint16_t puzID)
{
bx::snprintf(buf, bufSize, "%s/%u.pzl", Puzzle::PuzzleFileDir, puzID);
}
PlacedPuzzleCard& GetCardAt(PuzzleData& obj, PuzPos pos)
{
if (pos.X < 0 || pos.X >= Config::MaxPuzzleSizeCards || pos.Y < 0 || pos.Y >= Config::MaxPuzzleSizeCards)
{
LOG_ERROR("Invalid card access at %i %i!!", pos.X, pos.Y);
return InvalidPlacedCard;
}
return obj.PlacedCards[pos.Y * Config::MaxPuzzleSizeCards + pos.X];
}
PlacedPuzzleCard& GetInitialCardAt(PuzzleData& obj, PuzPos pos)
{
if (pos.X < 0 || pos.X >= Config::MaxPuzzleSizeCards || pos.Y < 0 || pos.Y >= Config::MaxPuzzleSizeCards)
{
LOG_ERROR("Invalid card access at %i %i!!", pos.X, pos.Y);
return InvalidPlacedCard;
}
return obj.InitialPlacedCards[pos.Y * Config::MaxPuzzleSizeCards + pos.X];
}
bool ReturnPlacedCard(PuzzleData& obj, PuzPos targetPos)
{
PlacedPuzzleCard& placedCard = GetCardAt(obj, targetPos);
if (IsValid(placedCard.RefCard))
{
if (GetFlag(placedCard.Flags, PlacedPuzzleCardFlags::Locked))
{
LOG_WARN("Card at %i %i is locked!", targetPos.X, targetPos.Y);
return false;
}
bool found = false;
for (int32_t i = 0; i < obj.AvailableCardCount; ++i)
{
if (obj.AvailableCards[i].RefCard.Idx == placedCard.RefCard.Idx && obj.AvailableCards[i].UsedCount > 0)
{
obj.AvailableCards[i].UsedCount--;
found = true;
break;
}
}
if (!found)
{
LOG_ERROR("Failed to find available card to return placed card of type %u to!", placedCard.RefCard.Idx);
return false;
}
placedCard.RefCard = {};
}
return true;
}
bool DragAvailableCardTo(PuzzleData& obj, PuzPos targetPos, int32_t availIdx, uint8_t rotation)
{
if (availIdx >= obj.AvailableCardCount)
{
LOG_ERROR("Invalid drag with avail idx %i!", availIdx);
return false;
}
if (!ReturnPlacedCard(obj, targetPos)) return false;
auto& draggedCard = obj.AvailableCards[availIdx];
draggedCard.UsedCount++;
PlacedPuzzleCard& placedCard = GetCardAt(obj, targetPos);
placedCard.RefCard = draggedCard.RefCard;
placedCard.Flags = (PlacedPuzzleCardFlags::Enum)ClearFlags(placedCard.Flags, PlacedPuzzleCardFlags::Locked);
placedCard.Position = targetPos;
placedCard.Rotation = rotation;
return true;
}
void RecalculateInitialAvailable(PuzzleData& obj)
{
for (int32_t i = 0; i < obj.AvailableCardCount; ++i)
{
obj.AvailableCards[i].UsedCount = obj.AvailableCards[i].MaxAvailableCount;
}
for (int32_t y = 0; y < obj.HeightTiles; ++y)
{
for (int32_t x = 0; x < obj.WidthTiles; ++x)
{
auto& placedCard = obj.PlacedCards[y * Puzzle::Config::MaxPuzzleSizeCards + x];
if (Puzzle::IsValid(placedCard.RefCard))
{
bool found = false;
for (int32_t i = 0; i < obj.AvailableCardCount; ++i)
{
if (obj.AvailableCards[i].RefCard.Idx == placedCard.RefCard.Idx)
{
found = true;
obj.AvailableCards[i].MaxAvailableCount++;
}
}
if (!found)
{
if (obj.AvailableCardCount == Puzzle::Config::MaxAvailableStacks)
{
LOG_ERROR("Read limit of available card stacks!");
break;
}
obj.AvailableCards[obj.AvailableCardCount] = {};
obj.AvailableCards[obj.AvailableCardCount].RefCard = placedCard.RefCard;
obj.AvailableCards[obj.AvailableCardCount].MaxAvailableCount = 1;
}
}
}
}
}
bool RenderDebugUI(PuzzleData& obj)
{
bool isVisible = true;
if (ImGui::Begin("Puzzle", &isVisible))
{
ImGui::Text("%s", PuzzleName);
if (ImGui::Button("Delete Puzzle"))
{
char filepath[128]{0};
WritePuzzleFilePath(filepath, sizeof(filepath), obj.ID);
bx::remove(filepath);
obj.ID = UINT16_MAX;
ImGui::End();
return false;
}
ImGui::SameLine();
if (ImGui::Button("Reset"))
{
ResetPuzzle(obj);
}
ImGui::SameLine();
if (ImGui::Button("Save"))
{
ResetPuzzle(obj);
char path[128]{0};
WritePuzzleFilePath(path, sizeof(path), obj.ID);
Serializer ser;
if (ser.Init(path, "PZZL") && ser.WriteT(obj))
{
LOG("Saved to %s", path);
}
else
{
LOG_ERROR("Failed to save to %s", path);
}
ser.Finish();
}
int32_t W = S.WidthTiles;
int32_t H = S.HeightTiles;
int32_t val = obj.ID;
if (ImGui::InputInt("ID", &val))
{
obj.ID = val;
}
ImGui::InputText("Name", obj.PuzzleName, sizeof(obj.PuzzleName));
int32_t W = obj.WidthTiles;
int32_t H = obj.HeightTiles;
ImGui::PushID("width");
ImGui::SetNextItemWidth(40);
if (ImGui::DragInt("", &W, 0.3f, 0, Config::MaxPuzzleSizeTiles))
if (ImGui::DragInt("", &W, 0.3f, 0, Puzzle::Config::MaxPuzzleSizeTiles))
{
S.WidthTiles = uint8_t(W);
dataChanged = true;
obj.WidthTiles = uint8_t(W);
}
ImGui::PopID();
ImGui::SameLine();
@@ -181,22 +473,195 @@ namespace Puzzle
ImGui::SameLine();
ImGui::SetNextItemWidth(40);
ImGui::PushID("height");
if (ImGui::DragInt("", &H, 0.3f, 0, Config::MaxPuzzleSizeTiles))
if (ImGui::DragInt("", &H, 0.3f, 0, Puzzle::Config::MaxPuzzleSizeTiles))
{
S.HeightTiles = uint8_t(H);
dataChanged = true;
obj.HeightTiles = uint8_t(H);
}
ImGui::PopID();
ImVec2 puzCursorStart = ImGui::GetCursorScreenPos();
auto& drawList = *ImGui::GetWindowDrawList();
obj.GoalPositionCount = 0;
for (int32_t y = 0; y < obj.HeightTiles; ++y)
{
ImGui::PushID(y);
for (int32_t x = 0; x < obj.WidthTiles; ++x)
{
ImGui::PushID(x);
PuzPos nodePos = {int8_t(x), int8_t(y)};
auto node = GetInitialNodeAt(obj, nodePos);
if (node == PuzzleElementType::WaterGoal)
{
obj.GoalPositions[obj.GoalPositionCount] = nodePos;
obj.GoalPositionCount++;
}
ImVec2 pos = ImVec2{puzCursorStart.x + x * UIPuzBoxSize + 5, puzCursorStart.y + y * UIPuzBoxSize};
ImGui::SetCursorScreenPos(pos);
ImGui::Text("%s", GetShortNodeName(node));
ImGui::SetCursorScreenPos(pos);
if (x % Puzzle::Config::CardSize == 0 && y % Puzzle::Config::CardSize == 0)
{
PuzPos cardPos = {int8_t(x / Config::CardSize), int8_t(y / Config::CardSize)};
PlacedPuzzleCard& placedCard = GetInitialCardAt(obj, cardPos);
bool isLocked = GetFlag(placedCard.Flags, PlacedPuzzleCardFlags::Locked);
ImGui::InvisibleButton("bn", {UIPuzBoxSize * 2, UIPuzBoxSize * 2});
if (ImGui::IsItemClicked(ImGuiMouseButton_Right))
{
placedCard.Rotation += 1;
if (placedCard.Rotation >= 4) placedCard.Rotation = 0;
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Middle))
{
if (ImGui::IsKeyDown(ImGuiKey_LeftShift))
{
placedCard = {};
}
else
{
placedCard.Flags =
(PlacedPuzzleCardFlags::Enum)(placedCard.Flags ^ PlacedPuzzleCardFlags::Locked);
RecalculateInitialAvailable(obj);
}
}
if (!isLocked && IsValid(placedCard.RefCard))
{
ImVec2 s = {puzCursorStart.x + x * UIPuzBoxSize, puzCursorStart.y + y * UIPuzBoxSize};
drawList.AddRectFilled(s,
{s.x + UIPuzBoxSize * Puzzle::Config::CardSize,
s.y + UIPuzBoxSize * Puzzle::Config::CardSize},
0x33FFFFFF);
}
if (ImGui::BeginDragDropTarget())
{
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("cardtype", 0))
{
uint32_t CardIdx = *reinterpret_cast<uint32_t*>(payload->Data);
placedCard = {};
placedCard.RefCard = {(uint16_t)CardIdx};
placedCard.Rotation = 0;
placedCard.Position = cardPos;
placedCard.Flags = (PlacedPuzzleCardFlags::Enum)SetFlags(placedCard.Flags,
PlacedPuzzleCardFlags::Locked);
RecalculateInitialAvailable(obj);
}
ImGui::EndDragDropTarget();
}
}
ImGui::PopID();
}
ImGui::End();
if (dataChanged)
{
char path[128]{0};
bx::snprintf(path, sizeof(path), "game/data/%s.pzl", PuzzleName);
SerializeStruct(&S, sizeof(S), path);
ImGui::PopID();
}
for (int32_t y = 0; y <= obj.HeightTiles; ++y)
{
ImVec2 linePos = {puzCursorStart.x, puzCursorStart.y + y * UIPuzBoxSize};
drawList.AddLine(linePos,
{linePos.x + obj.WidthTiles * UIPuzBoxSize, linePos.y},
y % Puzzle::Config::CardSize == 0 ? 0xFFFFFFFF : 0x11FFFFFF);
}
for (int32_t x = 0; x <= obj.WidthTiles; ++x)
{
ImVec2 linePos = {puzCursorStart.x + x * UIPuzBoxSize, puzCursorStart.y};
drawList.AddLine(linePos,
{linePos.x, linePos.y + obj.HeightTiles * UIPuzBoxSize},
x % Puzzle::Config::CardSize == 0 ? 0xFFFFFFFF : 0x11FFFFFF);
}
ImVec2 pos = ImVec2{puzCursorStart.x + obj.WidthTiles * UIPuzBoxSize + 5,
puzCursorStart.y + obj.HeightTiles * UIPuzBoxSize};
ImGui::SetCursorScreenPos(pos);
ImGui::NewLine();
ImGui::Separator();
PuzzleSolver solver;
ImGui::Text("Solved: %s", solver.IsPuzzleSolved(obj) ? "yes!" : "no.");
bool bAvailOpen =
ImGui::TreeNodeEx("Available Cards", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed);
if (ImGui::BeginDragDropTarget())
{
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("cardtype", 0))
{
uint32_t CardIdx = *reinterpret_cast<uint32_t*>(payload->Data);
bool found = false;
for (int32_t i = 0; i < obj.AvailableCardCount; ++i)
{
if (obj.AvailableCards[i].RefCard.Idx == CardIdx)
{
obj.AvailableCards[i].MaxAvailableCount++;
found = true;
break;
}
}
if (!found)
{
if (obj.AvailableCardCount == BX_COUNTOF(obj.AvailableCards))
{
LOG_ERROR("Available card limit reached!");
}
else
{
auto& Card = obj.AvailableCards[obj.AvailableCardCount];
Card.RefCard.Idx = CardIdx;
Card.MaxAvailableCount = 1;
obj.AvailableCardCount++;
}
}
}
ImGui::EndDragDropTarget();
}
if (bAvailOpen)
{
ImVec2 startPos = ImGui::GetCursorScreenPos();
for (uint32_t i = 0; i < obj.AvailableCardCount; ++i)
{
ImVec2 localPos = {startPos.x + i * 60.0f, startPos.y};
ImGui::SetCursorScreenPos(localPos);
ImGui::PushID(i);
auto& card = obj.AvailableCards[i];
DrawCard(GetCard(card.RefCard), 0, ImGui::GetCursorScreenPos());
int displayCount = card.MaxAvailableCount - card.UsedCount;
ImGui::SetCursorScreenPos({localPos.x, localPos.y + 55.0f});
ImGui::SetNextItemWidth(35);
ImGui::PushID("carc");
if (ImGui::DragInt("", &displayCount, 0.25f))
{
int diff = displayCount - (card.MaxAvailableCount - card.UsedCount);
card.MaxAvailableCount = bx::max(0, card.MaxAvailableCount + diff);
}
ImGui::PopID();
ImGui::SameLine(0, 3);
if (ImGui::Button("x"))
{
if (i < obj.AvailableCardCount - 1)
{
obj.AvailableCards[i] = obj.AvailableCards[obj.AvailableCardCount - 1];
}
obj.AvailableCardCount--;
}
ImGui::PopID();
}
ImGui::TreePop();
}
}
ImGui::End();
return isVisible;
}
void ResetPuzzle(PuzzleData& puz)
{
for (int32_t i = 0; i < puz.AvailableCardCount; ++i)
{
puz.AvailableCards[i].UsedCount = 0;
}
for (int32_t i = 0; i < BX_COUNTOF(puz.PlacedCards); ++i)
{
puz.PlacedCards[i] = puz.InitialPlacedCards[i];
}
}
} // namespace Puzzle

View File

@@ -1,143 +1,66 @@
#pragma once
#include "Serial.h"
#include <cstdint>
#include <imgui.h>
#include "../../gen/Generated.h"
#include "../gen/Generated.h"
namespace Puzzle
{
using namespace Gen;
constexpr const char* PuzzleFileDir = "game/data/puzzles/";
struct Config
{
static constexpr uint32_t MaxVisiblePuzzles = 3;
static constexpr uint32_t CardSize = 2;
static constexpr uint32_t NodesPerCard = CardSize * CardSize;
static constexpr uint32_t MaxElementsPerTile = 4;
static constexpr uint32_t MaxPuzzleSizeCards = 16;
static constexpr uint32_t MaxCardsInPuzzle = MaxPuzzleSizeCards * MaxPuzzleSizeCards;
static constexpr uint32_t MaxPuzzleSizeTiles = 16 * CardSize;
static constexpr uint32_t MaxTilesInPuzzle = MaxPuzzleSizeTiles * MaxPuzzleSizeTiles;
static constexpr uint32_t MaxTilesTotal =
MaxTilesInPuzzle * MaxVisiblePuzzles + MaxPuzzleSizeCards * MaxVisiblePuzzles + 64;
static constexpr uint32_t MaxAvailableStacks = 16;
static constexpr uint32_t MaxGoalPositions = 16;
static constexpr float CardScaleWorld = 10.0f;
static constexpr uint32_t MaxCoversInTile = 8;
static constexpr uint32_t MaxCoversTotal = MaxCoversInTile * MaxTilesTotal;
};
struct PuzPos
{
int8_t X = 0;
int8_t Y = 0;
PuzPos& operator+=(const PuzPos& rhs)
{
X += rhs.X;
Y += rhs.Y;
return *this;
}
friend PuzPos operator+(PuzPos lhs, const PuzPos& rhs)
{
lhs += rhs;
return lhs;
}
};
struct ElemPos
{
PuzPos Position;
uint8_t ElemIdx = 0;
};
enum class PuzzleElementType : uint8_t
{
None,
WaterIn,
WaterGoal,
WaterChannel,
ElectricIn,
ElectricGoal,
Blocked,
Bridge,
};
struct PuzzleNode
{
PuzzleElementType PlacedTypes[Config::MaxElementsPerTile]{PuzzleElementType::None};
bool HasElement(PuzzleElementType search) const;
bool IsEmpty() const;
};
struct StaticPuzzleCard
{
PuzzleNode Nodes[Config::NodesPerCard];
uint16_t ModelHandle = 0;
};
struct StaticPuzzleCardHandle
{
uint16_t Idx = UINT16_MAX;
bool IsValid()
{
return Idx != UINT16_MAX;
}
};
struct StaticPuzzleData
{
StaticPuzzleCard Cards[64];
void Setup();
static StaticPuzzleData& Get();
const StaticPuzzleCard& GetCard(StaticPuzzleCardHandle H) const;
};
StaticPuzzleData& GetStaticPuzzleData();
void LoadStaticPuzzleData();
void SaveStaticPuzzleData();
const StaticPuzzleCard& GetCard(StaticPuzzleCardHandle H);
bool IsValid(StaticPuzzleCardHandle h);
uint8_t GetRemainingCount(const PuzzleCardStack& stack);
PuzzleElementType::Enum GetNodeAt(const PuzzleData& puz, PuzPos pos);
PuzzleElementType::Enum GetInitialNodeAt(const PuzzleData& puz, PuzPos pos);
PuzzleElementType::Enum GetCardNodeAt(const StaticPuzzleCard& card, uint8_t rotation, int8_t x, int8_t y);
PuzzleElementType::Enum& EditCardNodeAt(StaticPuzzleCard& card, uint8_t rotation, int8_t x, int8_t y);
void DrawCard(const StaticPuzzleCard& card, uint8_t rotation, ImVec2 pos);
void RotateCard(PlacedPuzzleCard& card);
struct PlacedPuzzleCard
{
StaticPuzzleCardHandle RefCard;
PuzPos Position;
uint8_t Rotation = 0;
bool IsLocked = false;
};
struct PuzzleCardStack
{
StaticPuzzleCardHandle RefCard;
uint8_t MaxAvailableCount = 0;
uint8_t UsedCount = 0;
uint8_t GetRemainingCount();
};
struct PuzzleData
{
SER_HEADER(1, "PZZL");
Generated::PuzzleData S;
uint32_t AvailableCardCount = 0;
PuzzleCardStack AvailableCards[Config::MaxAvailableStacks];
uint32_t PlacedCardCount = 0;
PlacedPuzzleCard PlacedCards[Config::MaxCardsInPuzzle];
// Indexed by board position
PuzzleNode PlacedNodes[Config::MaxTilesInPuzzle];
uint32_t GoalPositionCount = 0;
ElemPos GoalPositions[Config::MaxGoalPositions];
const PuzzleNode& GetNodeAt(PuzPos pos) const;
PuzzleElementType GetElementAt(ElemPos pos) const;
char PuzzleName[32]{"Unnamed"};
bool RenderDebugUI();
};
PlacedPuzzleCard& GetCardAt(PuzzleData& obj, PuzPos pos);
PlacedPuzzleCard& GetInitialCardAt(PuzzleData& obj, PuzPos pos);
// TODO: targetPos is of type CardPos
bool ReturnPlacedCard(PuzzleData& obj, PuzPos targetPos);
// TODO: targetPos is of type CardPos
bool DragAvailableCardTo(PuzzleData& obj, PuzPos targetPos, int32_t availIdx, uint8_t rotation);
void ResetPuzzle(PuzzleData& puz);
struct PuzzleSolver
{
bool IsPuzzleSolved(const PuzzleData& puzzle);
bool IsExitSatisfied(const PuzzleData& puzzle, ElemPos pos);
bool IsExitSatisfied(const PuzzleData& puzzle, PuzPos pos);
// This assumes flowFrom is already verified to be connected.
bool IsValidGoalConnection(const PuzzleData& puzzle,
PuzPos flowFrom,
PuzPos flowTo,
PuzzleElementType goalType);
bool IsValidSource(PuzzleElementType sourceType, PuzzleElementType goalType);
PuzzleElementType::Enum goalType);
bool IsValidSource(PuzzleElementType::Enum sourceType, PuzzleElementType::Enum goalType);
};
bool RenderDebugUI(PuzzleData& obj);
} // namespace Puzzle

View File

@@ -1,32 +0,0 @@
#include "Global.h"
#include "Log.h"
#include "Serial.h"
#include "bx/bx.h"
#include "bx/file.h"
#include "bx/filepath.h"
void SerializeStruct(void* data, uint64_t size, const char* path)
{
bx::Error err;
bx::FilePath filePath{path};
bx::FileWriter writer;
if (writer.open(filePath, false, &err))
{
if (!writer.write(data, size, &err))
{
LOG_ERROR("Failed to write to file %s: %s", path, err.getMessage().getCPtr());
writer.close();
return;
}
}
else
{
LOG_ERROR("Failed to open file %s: %s", path, err.getMessage().getCPtr());
return;
}
LOG("Successful serialization");
}
void DeserializeStruct(void* data, SerializationHeader& expectedHeader, const char* path)
{
}

View File

@@ -1,32 +0,0 @@
#include <cstdint>
struct SerializationHeader
{
uint8_t _ID0 = 0;
uint8_t _ID1 = 0;
uint8_t _ID2 = 0;
uint8_t _ID3 = 0;
uint8_t HeaderVersion = 0;
uint8_t VersionNum = 0;
uint8_t Reserved0 = 0;
uint8_t Reserved1 = 0;
};
#define SER_HEADER(Version, FCC) \
SerializationHeader __Header{ \
FCC[0], \
FCC[1], \
FCC[2], \
FCC[3], \
1, \
Version, \
0, \
0, \
}; \
bool VersionMatches(uint8_t headerVersion, uint8_t versionNum) \
{ \
return headerVersion == 1 && versionNum == Version; \
}
void SerializeStruct(void* data, uint64_t size, const char* path);
void DeserializeStruct(void* data, SerializationHeader& expectedHeader, const char* path);

View File

@@ -1,11 +1,16 @@
#include "Global.h"
#include "Input.h"
#include "Instance.h"
#include "Log.h"
#include "Setup.h"
#include "Tools.h"
#include "rendering/Rendering.h"
#include "bx/bx.h"
#include "bx/timer.h"
#include "rendering/Rendering.h"
#ifdef TRACY_ENABLE
#include <client/TracyProfiler.hpp>
#endif
#include <tracy/Tracy.hpp>
namespace Game
{
@@ -23,38 +28,45 @@ namespace Game
void Setup(SharedData& shared)
{
LOG("Game Setup Start!");
#ifdef TRACY_ENABLE
LOG("Tracy is enabled");
tracy::StartupProfiler();
#endif
if (shared.Game.PermanentStorage == nullptr)
if (shared.Game.PermanentArena.Base == nullptr)
{
LOG_ERROR("Game memory not initialized!!");
return;
}
if (shared.Game.EntityStorage == nullptr)
if (shared.Game.EntityArena.Base == nullptr)
{
LOG_ERROR("Entity memory not initialized!");
return;
}
if (shared.Game.PermanentStorageSize < sizeof(GameInstance))
if (shared.Game.PermanentArena.MaxSize < sizeof(GameInstance))
{
LOG_ERROR("Game memory too small! %u < %u", shared.Game.PermanentStorageSize, sizeof(GameInstance));
LOG_ERROR("Game memory too small! %u < %u", shared.Game.PermanentArena.MaxSize, sizeof(GameInstance));
return;
}
GameInstance& instance = *reinterpret_cast<GameInstance*>(shared.Game.PermanentStorage);
GameInstance& instance = *reinterpret_cast<GameInstance*>(shared.Game.PermanentArena.Base);
if (sizeof(GameInstance) != instance.Size)
{
LOG_WARN("Game instance size changed, resetting!");
instance = {};
}
instance.UsedScratchAmount = 0;
SetShared(shared);
SetInstance(instance);
SetupInstance.Rendering.Setup();
ResetScratch();
Puzzle::LoadStaticPuzzleData();
SetupInstance.Rendering.Setup({});
instance.GameLevel.Setup(shared.Game);
instance.IsInitialized = true;
}
void Update()
{
{
ZoneScopedN("TimeUpdate");
auto& inst = GetInstance();
int64_t newNowHP = bx::getHPCounter() - inst.Time.StartTime;
inst.Time.DeltaHP = newNowHP - inst.Time.NowHP;
@@ -63,25 +75,28 @@ namespace Game
inst.Time.Delta = (double)inst.Time.DeltaHP / bx::getHPFrequency();
GetShared().Window.PerfCounters[(int32_t)PerfCounterType::GameDelta].Write(inst.Time.DeltaHP,
GetShared().Window.FrameCounter);
if (GetKeyPressedNow(ScanCode::R))
{
GetInstance().Size = 0;
Shutdown();
Setup(GetShared());
}
SetupInstance.Rendering.Update();
{
ZoneScopedN("MouseDeltaUpdate");
auto& win = GetShared().Window;
win.MouseDeltaX = 0.0f;
win.MouseDeltaY = 0.0f;
bx::memCopy(win.LastHeldScanCodes, win.HeldScanCodes, sizeof(win.HeldScanCodes));
bx::memCopy(win.LastHeldMouseButtons, win.HeldMouseButtons, sizeof(win.HeldMouseButtons));
}
Tools::MeasureFrameEnd();
FrameMark;
}
void Shutdown()
{
LOG("Shutdown");
SetupInstance.Rendering.Shutdown();
#ifdef TRACY_ENABLE
tracy::ShutdownProfiler();
#endif
}
} // namespace Game

760
src/game/Tools.cpp Normal file
View File

@@ -0,0 +1,760 @@
#include "../gen/Def.h"
#include "Gen.h"
#include "Global.h"
#include "Instance.h"
#include "Log.h"
#include "Mesh.h"
#include "Puzzle.h"
#include "Tools.h"
#include "bgfx/bgfx.h"
#include "bx/filepath.h"
#include "bx/string.h"
#include "bx/timer.h"
#include "rendering/Rendering.h"
#include <imgui.h>
#include <tracy/Tracy.hpp>
using namespace Gen;
namespace
{
constexpr int32_t FrameTimeBufSize = 512;
int64_t FrameTimes[FrameTimeBufSize]{0};
int32_t FrameTimeIdx = 0;
} // namespace
namespace Tools
{
const char* GetAssetPath(Gen::AssetHandle assetHandle)
{
const auto& inst = Game::GetInstance();
for (int32_t j = 0; j < inst.DebugData.AssetCount; ++j)
{
if (inst.DebugData.AssetHandles[j] == assetHandle)
{
return inst.DebugData.AssetHandlePaths[j];
}
}
return "---";
}
bool EntityDataSettings(Gen::SavedEntityRenderData& data)
{
ImGui::PushID(&data);
bool changed = false;
changed |= ModelDropdown(data.Model);
changed |= MaterialDropdown(data.Material);
changed |= TextureDropdown(data.Texture);
changed |= TransformUI(data.TF);
changed |= ImGui::Checkbox("Visible", &data.Visible);
changed |= ImGui::ColorEdit4("Color 1", &data.BaseColor.x);
changed |= ImGui::ColorEdit4("Color 2", &data.HighlightColor.x);
ImGui::PopID();
return changed;
}
bool ModelDropdown(Gen::ModelHandle& modelHandle, const char* title)
{
bool changed = false;
auto& R = Game::GameRendering::Get();
const char* assetName = GetAssetPath(modelHandle.Asset);
if (ImGui::BeginCombo(title, assetName))
{
for (int32_t i = 0; i < R.ModelCount; ++i)
{
if (ImGui::Selectable(GetAssetPath(R.Models[i].Handle.Asset), i == modelHandle.ModelIdx))
{
modelHandle = R.Models[i].Handle;
changed = true;
}
}
ImGui::EndCombo();
}
return changed;
}
bool TextureDropdown(Gen::TextureHandle& texHandle, const char* title)
{
bool changed = false;
auto& R = Game::GameRendering::Get();
const char* name = GetAssetPath(texHandle.Asset);
if (ImGui::BeginCombo(title, name))
{
for (int32_t i = 0; i < R.MaxTextures; ++i)
{
if (!IsValid(R.Textures[i].TexHandle)) continue;
ImGui::PushID(i);
ImVec2 pos = ImGui::GetCursorScreenPos();
if (ImGui::Selectable("", i == texHandle.TextureIdx, ImGuiSelectableFlags_AllowOverlap, {0, 64}))
{
texHandle = R.Textures[i].TexHandle;
changed = true;
}
ImGui::SetCursorScreenPos(pos);
ImGui::Image(R.Textures[i].RenderHandle.idx, {64, 64});
ImGui::SameLine();
ImGui::Text("%s", GetAssetPath(R.Textures[i].TexHandle.Asset));
ImGui::PopID();
}
ImGui::EndCombo();
}
return changed;
}
bool MaterialDropdown(Gen::EMaterial::Enum& material)
{
bool changed = false;
const char* selectedText = "---";
if (material < Gen::EMaterial::EntryCount)
{
selectedText = Gen::EMaterial::EntryNames[material];
}
if (ImGui::BeginCombo("Material", selectedText))
{
for (int32_t i = 0; i < Gen::EMaterial::EntryCount; ++i)
{
if (ImGui::Selectable(Gen::EMaterial::EntryNames[i], i == material))
{
material = (Gen::EMaterial::Enum)i;
changed = true;
}
}
ImGui::EndCombo();
}
return changed;
}
constexpr const char* UnitStrings[]{
"b",
"kb",
"mb",
"gb",
};
const char* GetUnitString(uint64_t byteCount, uint64_t& outCount)
{
outCount = byteCount;
int32_t strIdx = 0;
for (int32_t i = 0; i < BX_COUNTOF(UnitStrings); ++i)
{
if (outCount < 1024) break;
++strIdx;
outCount /= 1024;
}
return UnitStrings[strIdx];
}
void ProgressBar(const char* title, uint64_t current, uint64_t max)
{
ImGui::PushID(title);
float percent = static_cast<double>(current) / static_cast<double>(max);
ImVec2 startPos = ImGui::GetCursorScreenPos();
char content[128]{0};
uint64_t currentUnit = 0;
const char* currentUnitStr = GetUnitString(current, currentUnit);
uint64_t maxUnit = 0;
const char* maxUnitStr = GetUnitString(max, maxUnit);
bx::snprintf(content,
sizeof(content),
"%s: %u %s/%u %s (%.2f%%)",
title,
currentUnit,
currentUnitStr,
maxUnit,
maxUnitStr,
percent);
ImGui::Text("%s", content);
ImGui::PopID();
}
bool TransformUI(Gen::Transform& transform)
{
bool changed = false;
changed |= ImGui::DragFloat3("Pos", &transform.Position.x, 0.1f);
Vec3 euler = EulerFromRotation(transform.Rotation);
if (ImGui::DragFloat3("Rot", &euler.x, 0.1f))
{
transform.Rotation = RotationFromEuler(euler);
changed = true;
}
changed |= ImGui::DragFloat3("Scale", &transform.Scale.x, 0.01f);
return changed;
}
void RenderLogUI()
{
auto& time = Game::GetInstance().Time;
auto& debug = Game::GetInstance().DebugData;
if (ImGui::Begin("Log"))
{
ImGui::Checkbox("Shorten File Names", &debug.ShortenLogFileNames);
ImGui::BeginTable("tbl",
4,
ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_RowBg);
ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_NoResize);
ImGui::TableSetupColumn("Log");
ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_NoResize);
ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_NoResize);
ImGui::TableHeadersRow();
auto& logs = GetLogHistory();
int32_t lineCount = bx::min(100, LogInternal::LogHistorySize);
for (int32_t i = 0; i < lineCount; ++i)
{
int32_t idx = logs.WriteIdx - i - 1;
if (idx < 0) idx += LogInternal::LogHistorySize;
const char* line = &logs.LogBuffer[idx * LogInternal::MaxLineSize];
if (line[0] != 0)
{
int64_t timeOffset = logs.WriteTime[idx] - time.StartTime;
double writeTime = (double)timeOffset / bx::getHPFrequency();
uint32_t fileLine = logs.LineBuffer[idx];
const char* filePath = &logs.FileBuffer[idx * LogInternal::MaxLineSize];
const char* filePathRes =
debug.ShortenLogFileNames ? bx::FilePath{filePath}.getFileName().getPtr() : filePath;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%.01f", writeTime);
ImGui::TableNextColumn();
ImGui::Text("%s", line);
ImGui::SetItemTooltip("%f\n%s%s:%u", writeTime, line, filePath, fileLine);
ImGui::TableNextColumn();
ImGui::Text("%u", fileLine);
ImGui::TableNextColumn();
ImGui::Text("%s", filePathRes);
if (i > 0) ImGui::PopStyleColor(2);
ImVec4 bgCol = {0.0f, 0.0f, 0.0f, 1.0f};
if (logs.WriteType[idx] == ELogType::Warn)
bgCol = {0.2f, 0.2f, 0.0f, 1.0f};
else if (logs.WriteType[idx] == ELogType::Error)
bgCol = {0.2f, 0.0f, 0.0f, 1.0f};
ImGui::PushStyleColor(ImGuiCol_TableRowBg, bgCol);
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, bgCol);
}
}
ImGui::EndTable();
if (lineCount > 0) ImGui::PopStyleColor(2);
}
ImGui::End();
}
void RenderRenderSettingsUI(Game::GameRendering& rendering)
{
auto& time = Game::GetInstance().Time;
auto& shared = Game::GetShared();
auto& debug = Game::GetInstance().DebugData;
auto& level = Game::GetInstance().GameLevel;
auto& player = Game::GetInstance().Player;
if (ImGui::Begin("Rendering"))
{
if (rendering.LastShaderLoadTime >= 0.0f)
{
ImGui::TextColored({0.2f, 0.9f, 0.2f, 1.0f},
"Shader loaded %.0f seconds ago",
time.Now - rendering.LastShaderLoadTime);
}
else
{
ImGui::TextColored({0.9f, 0.2f, 0.2f, 1.0f}, "Shader load Failiure!");
}
ImGui::SameLine();
if (ImGui::Button("Reload Level"))
{
level = {};
level.Setup(shared.Game);
}
ImGui::DragFloat3("Player Pos", &player.PlayerCamTransform.Position.x);
ImGui::DragFloat3("Puz0 Pos", &level.Puzzles[0].WorldPosition.x);
ImGui::SliderFloat("Mouse Sensitivity", &player.MouseSensitivity, 0.1f, 5.0f);
ImGui::SliderFloat("Player Speed", &player.MovementSpeed, 1.0f, 30.0f);
ImGui::Checkbox("Show ImGui Demo", &debug.ShowImguiDemo);
ImGui::Checkbox("Show Stats", &debug.ShowStats);
if (debug.ShowImguiDemo) ImGui::ShowDemoWindow(&debug.ShowImguiDemo);
ImGui::Separator();
if (ImGui::TreeNodeEx("Entity Groups"))
{
ImGui::Checkbox("Cubes", &level.Cubes.IsEnabled);
ImGui::Checkbox("Tests", &level.Tests.IsEnabled);
ImGui::Checkbox("PuzzleTiles", &level.PuzzleTiles.IsEnabled);
ImGui::Checkbox("UIQuads", &level.UIQuads.IsEnabled);
ImGui::Checkbox("Level", &level.LevelEntities.IsEnabled);
ImGui::TreePop();
}
bool uiconfigChanged = false;
if (ImGui::TreeNodeEx("Game Tablet"))
{
uiconfigChanged |= Tools::EntityDataSettings(player.Config.TabletBackgroundRenderData);
ImGui::Separator();
ImGui::Text("Status");
uiconfigChanged |= Tools::EntityDataSettings(player.Config.TabletStatusRenderData);
uiconfigChanged |= Tools::TextureDropdown(player.Config.TabletStatusSolvedTexture, "Solved Texture");
uiconfigChanged |=
Tools::TextureDropdown(player.Config.TabletStatusNotSolvedTexture, "Not Solved Texture");
ImGui::Separator();
ImGui::Text("Reset");
uiconfigChanged |= Tools::EntityDataSettings(player.Config.TabletResetRenderData);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Background Level"))
{
for (int32_t i = 0; i < BX_COUNTOF(player.Config.BackgroundLevelRenderData); ++i)
{
if (i > 0) ImGui::Separator();
uiconfigChanged |= Tools::EntityDataSettings(player.Config.BackgroundLevelRenderData[i]);
}
ImGui::TreePop();
}
if (uiconfigChanged)
{
Serializer s;
s.Init("game/data/static/uiconfig.dat", "UICO");
s.WriteT(player.Config);
s.Finish();
level.PuzzleUI.Reset();
level.ReloadLevelEntities();
}
if (ImGui::TreeNodeEx("Dithering"))
{
if (ImGui::Button("Dithergen"))
{
DitherGen(rendering.DitherTextures, rendering.DitherRecursion);
}
ImGui::SameLine();
ImGui::SliderInt("Recursion", &rendering.DitherRecursion, 1, 4);
ImGui::Text("%ux%ux%u",
rendering.DitherTextures.DitherTexWH,
rendering.DitherTextures.DitherTexWH,
rendering.DitherTextures.DitherTexDepth);
if (!isValid(rendering.DitherTextures.PreviewTex))
{
ImGui::Text("Invalid Texture");
}
else
{
ImGui::Image(
rendering.DitherTextures.PreviewTex.idx,
{(float)rendering.DitherTextures.DitherTexWH,
(float)rendering.DitherTextures.DitherTexWH * rendering.DitherTextures.DitherTexDepth});
}
if (isValid(rendering.DitherTextures.RampTex))
{
ImGui::Image(rendering.DitherTextures.RampTex.idx,
{BX_COUNTOF(rendering.DitherTextures.BrightnessRamp), 8});
}
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Shader log"))
{
ImGui::TextWrapped("%s", Game::GetShared().Dev.ShaderLog);
ImGui::TreePop();
}
}
ImGui::End();
}
void RenderTexturesUI(Game::GameRendering& rendering)
{
if (ImGui::Begin("Textures"))
{
if (ImGui::Button("Reload"))
{
rendering.LoadTextures();
}
for (int32_t i = 0; i < rendering.MaxTextures; ++i)
{
if (!isValid(rendering.Textures[i].RenderHandle)) continue;
ImGui::Text("%i", i);
float width = bx::min<float>(ImGui::GetContentRegionAvail().x, rendering.Textures[i].Info.width);
float height = bx::min<float>(ImGui::GetContentRegionAvail().x, rendering.Textures[i].Info.height);
ImGui::Image(rendering.Textures[i].RenderHandle.idx, {width, height});
}
}
ImGui::End();
}
void RenderModelsUI(Game::GameRendering& rendering)
{
if (ImGui::Begin("Models"))
{
if (ImGui::Button("Reload"))
{
LoadModels(rendering.Models, rendering.ModelCount);
}
for (int32_t i = 0; i < rendering.ModelCount; ++i)
{
auto& mdl = rendering.Models[i];
if (bgfx::isValid(mdl.HeightMapTexture))
{
ImGui::Text("%s", mdl.Name);
ImGui::Image(mdl.HeightMapTexture.idx,
ImVec2{(float)Game::HeightMap::Height, (float)Game::HeightMap::Width});
}
else
{
ImGui::Text("Invalid Handle!");
}
ImGui::Spacing();
}
}
ImGui::End();
}
void RenderPuzzlesUI()
{
auto& debug = Game::GetInstance().DebugData;
auto& level = Game::GetInstance().GameLevel;
if (ImGui::Begin("Puzzles"))
{
char nameBuf[64]{0};
for (int32_t i = 0; i < BX_COUNTOF(level.Puzzles); ++i)
{
ImGui::PushID(i);
auto& puzzleData = level.Puzzles[i].Data;
bool isSelected = debug.SelectedDebugLevel == i;
ImGui::PushID("selectable");
bx::snprintf(nameBuf, sizeof(nameBuf), "%u: %s", i, puzzleData.PuzzleName);
if (ImGui::Selectable(nameBuf, isSelected))
{
debug.SelectedDebugLevel = isSelected ? UINT16_MAX : i;
}
ImGui::DragFloat3("Pos", &level.Puzzles[i].WorldPosition.x);
ImGui::PopID();
ImGui::PopID();
}
}
ImGui::End();
if (debug.SelectedDebugLevel < BX_COUNTOF(level.Puzzles))
{
if (!Puzzle::RenderDebugUI(level.Puzzles[debug.SelectedDebugLevel].Data))
{
debug.SelectedDebugLevel = UINT16_MAX;
}
}
}
void RenderCardsUI(Game::GameRendering& rendering)
{
auto& debug = Game::GetInstance().DebugData;
if (ImGui::Begin("Cards"))
{
Gen::StaticPuzzleData& staticData = Puzzle::GetStaticPuzzleData();
if (ImGui::Button("Save"))
{
Puzzle::SaveStaticPuzzleData();
}
ImGui::SameLine();
if (ImGui::Button("Reload"))
{
Puzzle::LoadStaticPuzzleData();
}
ImGui::Separator();
ImGui::ColorEdit3("Disabled Tint", &staticData.Visuals.DisabledCardTint.x);
ImGui::ColorEdit3("Tile Base Color", &staticData.Visuals.TileBaseColor.x);
ImGui::ColorEdit3("Tile Dot Color", &staticData.Visuals.TileDotColor.x);
for (int32_t i = 0; i < BX_COUNTOF(staticData.Cards); ++i)
{
ImGui::Separator();
Gen::StaticPuzzleCard& card = staticData.Cards[i];
ImGui::PushID(i);
char cardName[64]{0};
bx::snprintf(cardName, sizeof(cardName), "%i", i);
ImGui::Selectable(cardName);
if (ImGui::BeginDragDropSource())
{
Puzzle::DrawCard(card, debug.DebugCardRotation, ImGui::GetCursorScreenPos());
ImGui::SetDragDropPayload("cardtype", &i, sizeof(i));
ImGui::EndDragDropSource();
}
Tools::ModelDropdown(card.BaseModelHandle);
Tools::TextureDropdown(card.ModelTextureHandle, "World Texture");
Tools::TextureDropdown(card.BoardTextureHandle, "UI Texture");
if (IsValid(card.BaseModelHandle))
{
auto& mdl = rendering.Models[card.BaseModelHandle.ModelIdx];
if (mdl.SocketCount > 0 && ImGui::TreeNodeEx("Slots"))
{
for (int32_t sIdx = 0; sIdx < mdl.SocketCount; ++sIdx)
{
Tools::ModelDropdown(card.Sockets[sIdx].Model, mdl.Sockets[sIdx].Name);
int val = card.Sockets[sIdx].ConnectionDirection;
ImGui::PushID(sIdx);
if (ImGui::Combo("Connection Direction", &val, "North\0East\0South\0West\0"))
{
card.Sockets[sIdx].ConnectionDirection = val;
}
ImGui::PopID();
}
ImGui::TreePop();
}
}
ImGui::Text("Card");
for (int8_t y = 0; y < Puzzle::Config::CardSize; ++y)
{
ImGui::PushID(y);
for (int8_t x = 0; x < Puzzle::Config::CardSize; ++x)
{
if (x > 0) ImGui::SameLine();
ImGui::PushID(x);
auto& node = Puzzle::EditCardNodeAt(card, 0, x, y);
if (ImGui::Button(Gen::PuzzleElementType::ShortName[node], {26, 24}))
{
int32_t newVal = int32_t(node) + 1;
if (newVal >= Gen::PuzzleElementType::EntryCount)
{
newVal = 0;
}
node = Gen::PuzzleElementType::Enum(newVal);
}
ImGui::PopID();
}
ImGui::PopID();
}
ImGui::PopID();
}
}
ImGui::End();
}
void RenderEntitiesUI()
{
auto& level = Game::GetInstance().GameLevel;
if (ImGui::Begin("Entities"))
{
if (ImGui::TreeNodeEx("UIQuads"))
{
ImGui::Text("Count: %u", level.UIQuads.Count);
for (uint16_t i = 0; i < level.UIQuads.Count; ++i)
{
ImGui::Separator();
ImGui::PushID(i);
ImGui::Text("%u", i);
ImGui::SameLine();
auto& quad = level.UIQuads.Get({i});
ImGui::Checkbox("Visible", &quad.EData.Visible);
TextureDropdown(quad.EData.TextureHandle);
MaterialDropdown(quad.EData.MaterialHandle);
ImGui::DragFloat3("Pos", &quad.EData.Transform.Position.x);
ImGui::DragFloat3("UI Pos", &quad.UIPos.x);
ImGui::Text("RenderID: %i", quad.EData.RenderID);
ImGui::PopID();
}
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Level"))
{
ImGui::Text("Count: %u", level.LevelEntities.Count);
for (uint16_t i = 0; i < level.LevelEntities.Count; ++i)
{
ImGui::Separator();
ImGui::PushID(i);
ImGui::Text("%u", i);
ImGui::SameLine();
auto& levelEnt = level.LevelEntities.Get({i});
ImGui::Checkbox("Visible", &levelEnt.EData.Visible);
TextureDropdown(levelEnt.EData.TextureHandle);
MaterialDropdown(levelEnt.EData.MaterialHandle);
ImGui::DragFloat3("Pos", &levelEnt.EData.Transform.Position.x);
ImGui::Text("RenderID: %i", levelEnt.EData.RenderID);
ImGui::PopID();
}
ImGui::TreePop();
}
}
ImGui::End();
}
void RenderStatsUI()
{
auto& time = Game::GetInstance().Time;
auto& debug = Game::GetInstance().DebugData;
if (debug.ShowStats)
{
ImGui::SetNextWindowPos({0, 0});
if (ImGui::Begin("Stats",
nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_AlwaysAutoResize))
{
if (Game::GetInstance().Player.CameraM == Game::CameraMode::Freefly)
{
ImGui::PushStyleColor(ImGuiCol_Text, {0.8f, 0.1f, 0.1f, 1.0f});
ImGui::Text("NOCLIP");
ImGui::PopStyleColor();
}
ImGui::Text("Delta: %.01fms", time.Delta * 1000);
ImGui::Text("FPS: %.0f", 1.0 / time.Delta);
constexpr ImVec2 FpsPlotSize{200, 60};
if (ImGui::BeginChild("FpsPlot", FpsPlotSize, 0, ImGuiWindowFlags_NoInputs))
{
auto& drawList = *ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetWindowPos();
drawList.AddRectFilled(pos, {pos.x + FpsPlotSize.x, pos.y + FpsPlotSize.y}, 0x22222233);
float scale = 1000.0f;
for (int32_t i = 0; i < FrameTimeBufSize; ++i)
{
int32_t idx = FrameTimeIdx - i - 1;
int32_t prevIdx = idx - 1;
if (idx < 0) idx += FrameTimeBufSize;
if (prevIdx < 0) prevIdx += FrameTimeBufSize;
if (FrameTimes[idx] == 0 || FrameTimes[prevIdx] == 0) continue;
int64_t frameTime = FrameTimes[idx] - FrameTimes[prevIdx];
double frameTimeSec = (double)frameTime / bx::getHPFrequency();
drawList.AddLine(
{pos.x + (FpsPlotSize.x - i - 1), pos.y + FpsPlotSize.y},
{pos.x + (FpsPlotSize.x - i - 1), pos.y + FpsPlotSize.y - (float)frameTimeSec * scale},
0xFFFFFFFF);
}
}
ImGui::EndChild();
}
ImGui::End();
}
}
void RenderDebugUI(Game::GameRendering& rendering)
{
if (!rendering.SetupData.UseImgui) return;
ZoneScopedN("DebugUI");
auto& time = Game::GetInstance().Time;
auto& shared = Game::GetShared();
auto& debug = Game::GetInstance().DebugData;
auto& level = Game::GetInstance().GameLevel;
auto& player = Game::GetInstance().Player;
if (ImGui::Begin("Hilfe"))
{
if (ImGui::Button("Spiel Neustarten"))
{
player.PlayerCamTransform.Position = {0.0f, 3.0f, 0.0f};
for (int32_t i = 0; i < BX_COUNTOF(level.Puzzles); ++i)
{
Puzzle::ResetPuzzle(level.Puzzles[i].Data);
}
}
ImGui::SameLine();
if (ImGui::Button("Zurück zum Anfang"))
{
player.PlayerCamTransform.Position = {0.0f, 3.0f, 0.0f};
}
ImGui::Separator();
ImGui::Text("Anleitung:");
ImGui::Text("Bewege dich mit");
ImGui::SameLine();
ImGui::TextColored({1.0f, 0.6f, 0.6f, 1.0f}, "W, A, S, D");
ImGui::SameLine();
ImGui::Text("und schau dich um mit der");
ImGui::SameLine();
ImGui::TextColored({1.0f, 0.6f, 0.6f, 1.0f}, "Maus.");
ImGui::Text("Drücke");
ImGui::SameLine();
ImGui::TextColored({1.0f, 0.6f, 0.6f, 1.0f}, "Leertaste");
ImGui::SameLine();
ImGui::Text("um den Spielplan zu öffnen.");
ImGui::Text("Auf dem Spielplan kannst du Karten verschieben.");
ImGui::Text("Mit");
ImGui::SameLine();
ImGui::TextColored({1.0f, 0.6f, 0.6f, 1.0f}, "Rechter Maustaste");
ImGui::SameLine();
ImGui::Text("kannst du Karten drehen.");
ImGui::Text("");
ImGui::Text("Dein Ziel: Verbinde die Pumpe mit dem Abfluss, um das");
ImGui::Text("Tor zum nächsten Level zu öffnen!");
ImGui::Text("");
auto& inflowTexture = rendering.Textures[10];
auto& outflowTexture = rendering.Textures[9];
auto& connectionTexture = rendering.Textures[8];
ImGui::Text("Pumpe (Wasserquelle):");
ImGui::Image(inflowTexture.RenderHandle.idx, {64.0f, 64.0f});
ImGui::Text("Abfluss (Ziel):");
ImGui::Image(outflowTexture.RenderHandle.idx, {64.0f, 64.0f});
ImGui::Text("Verbindung:");
ImGui::Image(connectionTexture.RenderHandle.idx, {64.0f, 64.0f});
}
ImGui::End();
if (rendering.UIVisible == Game::UIVisibilityState::Debug)
{
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right))
{
debug.DebugCardRotation++;
if (debug.DebugCardRotation >= 4) debug.DebugCardRotation = 0;
}
if (ImGui::Begin("Debug"))
{
ImGui::Checkbox("ImGui Demo", &debug.ShowImguiDemo);
ImGui::SliderFloat("Font Scale", &ImGui::GetIO().FontGlobalScale, 0.5f, 4.0f);
ImGui::Checkbox("Break on ID", &debug.DebugBreakIDEnabled);
ImGui::SameLine();
ImGui::PushID("@#$");
ImGui::InputInt("", &debug.DebugBreakID);
ImGui::PopID();
ImGui::Separator();
ImGui::Text("Arenas");
ProgressBar("Permanent", shared.Game.PermanentArena.Used, shared.Game.PermanentArena.MaxSize);
ProgressBar("Entity", shared.Game.EntityArena.Used, shared.Game.EntityArena.MaxSize);
ProgressBar("Transient", shared.Game.TransientArena.Used, shared.Game.TransientArena.MaxSize);
}
ImGui::End();
RenderLogUI();
RenderRenderSettingsUI(rendering);
RenderTexturesUI(rendering);
RenderModelsUI(rendering);
RenderPuzzlesUI();
RenderCardsUI(rendering);
RenderEntitiesUI();
}
RenderStatsUI();
}
void MeasureFrameEnd()
{
FrameTimes[FrameTimeIdx] = bx::getHPCounter();
++FrameTimeIdx;
if (FrameTimeIdx >= FrameTimeBufSize) FrameTimeIdx = 0;
}
} // namespace Tools

14
src/game/Tools.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include "../gen/Generated.h"
#include "rendering/Rendering.h"
namespace Tools
{
bool EntityDataSettings(Gen::SavedEntityRenderData& data);
bool ModelDropdown(Gen::ModelHandle& modelHandle, const char* title = "Model");
bool TextureDropdown(Gen::TextureHandle& texHandle, const char* title = "Texture");
bool MaterialDropdown(Gen::EMaterial::Enum& material);
bool TransformUI(Gen::Transform& transform);
void RenderDebugUI(Game::GameRendering& rendering);
void MeasureFrameEnd();
} // namespace Tools

382
src/game/UI.cpp Normal file
View File

@@ -0,0 +1,382 @@
#include "UI.h"
#include "Entity.h"
#include "Gen.h"
#include "Global.h"
#include "Input.h"
#include "Instance.h"
#include "Level.h"
#include "Puzzle.h"
#include "bx/math.h"
using namespace Gen;
namespace
{
Game::StaticUIData StaticData;
}
namespace Game
{
UIQuadEntityHandle NewQuad(UIQuadEntityManager& manager,
const Gen::SavedEntityRenderData& loadData,
UIQuadEntityHandle oldHandle)
{
UIQuadEntityHandle h = oldHandle;
if (!IsValid(h))
{
h = manager.New();
if (!IsValid(h)) return h;
}
UIQuadEntity& entity = manager.Get(h);
entity.EData.LoadFromSaved(loadData);
entity.UIPos = entity.EData.Transform.Position;
return h;
}
void UpdateQuad(UIQuadEntityManager& manager, UIQuadEntityHandle handle)
{
if (!IsValid(handle)) return;
UIQuadEntity& entity = manager.Get(handle);
entity.EData.Transform.Position = StaticData.UITransform.Position;
entity.EData.Transform.Rotation = StaticData.UITransform.Rotation;
TranslateLocal(entity.EData.Transform, entity.UIPos);
Rotate(entity.EData.Transform, Vec3{bx::kPi * 0.5f, 0.0f, (1.0f - 0 * 0.5f) * bx::kPi});
Rotate(entity.EData.Transform, {0.0f, entity.UIRot, 0.0f});
}
Vec3 GetMousePosWorld()
{
auto& window = GetShared().Window;
Vec2 mousePos = GetMousePos();
mousePos.x = mousePos.x / window.WindowWidth;
mousePos.y = mousePos.y / window.WindowHeight;
mousePos *= 2.0f;
mousePos -= 1.0f;
Vec4 mousePosView = {mousePos.x, -mousePos.y, 0.0f, 1.0f};
Vec4 mousePosCam4 = Mul(GetInstance().Player.ProjectionInverse, mousePosView);
Vec3 mousePosCam = Vec3{
mousePosCam4.x /= mousePosCam4.w,
mousePosCam4.y /= mousePosCam4.w,
mousePosCam4.z /= mousePosCam4.w,
};
return LocalToGlobalPoint(GetInstance().Player.PlayerCamTransform, mousePosCam);
}
bool IsQuadHovered(Transform& quadTransform, Vec3 mousePosWorld, Vec3& outQuadPlaneIntersectPos)
{
Vec3 quadPosWorld = quadTransform.Position;
Vec3 quadXWorld = LocalToGlobalPoint(quadTransform, {1, 0, 0});
Vec3 quadZWorld = LocalToGlobalPoint(quadTransform, {0, 0, 1});
if (RayPlaneIntersect(GetInstance().Player.PlayerCamTransform.Position,
mousePosWorld,
quadPosWorld,
quadXWorld,
quadZWorld,
outQuadPlaneIntersectPos))
{
Vec3 quadSpaceIntersect = GlobalToLocalPoint(quadTransform, outQuadPlaneIntersectPos);
if (quadSpaceIntersect.x >= -1.0f && quadSpaceIntersect.x <= 1.0f && quadSpaceIntersect.z >= -1.0f &&
quadSpaceIntersect.z <= 1.0f)
{
return true;
}
}
return false;
}
void WorldPuzzleUI::Setup()
{
auto& level = GetInstance().GameLevel;
auto& player = GetInstance().Player;
TabletHandle = NewQuad(level.UIQuads, player.Config.TabletBackgroundRenderData);
SolvedQuad = NewQuad(level.UIQuads, player.Config.TabletStatusRenderData);
ResetQuad = NewQuad(level.UIQuads, player.Config.TabletResetRenderData);
for (int32_t i = 0; i < Puzzle::Config::MaxCardsInPuzzle; ++i)
{
UIPlacedCards[i] = level.UIQuads.New();
auto& quad = level.UIQuads.Get(UIPlacedCards[i]);
quad.EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/plane.glb");
quad.EData.MaterialHandle = EMaterial::UI;
}
for (int32_t i = 0; i < Puzzle::Config::MaxAvailableStacks * UIAvailableCardMaxStackPreview; ++i)
{
UIAvailableCards[i] = level.UIQuads.New();
auto& quad = level.UIQuads.Get(UIAvailableCards[i]);
quad.EData.MaterialHandle = EMaterial::UI;
quad.EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/plane.glb");
}
}
Vec3 CalcBoardOffset(const Gen::PuzzleData& puzData)
{
return Vec3{
(float)(puzData.WidthTiles) / 2.0f - 0.5f,
(float)(puzData.HeightTiles) / 2.0f - 0.5f,
0.0f,
} *
WorldPuzzleUI::UICardScale * WorldPuzzleUI::UICardScale;
}
Vec3 CardPosToUIPos(const Gen::PuzzleData& data, int32_t cardX, int32_t cardY)
{
return Vec3{(float)cardX, (float)(data.HeightTiles / 2 - cardY - 1), -0.3f} * WorldPuzzleUI::UICardOffset *
WorldPuzzleUI::UICardScale -
CalcBoardOffset(data);
}
void UIPosToCardPos(const Gen::PuzzleData& data, Vec3 uiPos, int32_t& cardXOut, int32_t& cardYOut)
{
Vec3 boardOffset = CalcBoardOffset(data) / WorldPuzzleUI::UICardScale;
Vec3 boardPos = GlobalToLocalPoint(StaticData.UITransform, uiPos);
Vec3 boardTilePos = (boardPos + boardOffset) / WorldPuzzleUI::UICardOffset;
cardXOut = (int32_t)bx::round(boardTilePos.x);
cardYOut = data.HeightTiles / 2 - (int32_t)bx::round(boardTilePos.y) - 1;
}
void WorldPuzzleUI::UpdateAvailableCards(Gen::PuzzleData& Data)
{
auto& level = GetInstance().GameLevel;
auto& staticCards = Puzzle::GetStaticPuzzleData().Cards;
Vec3 quadPlaneIntersectPos;
Vec3 mousePosWorld = GetMousePosWorld();
for (int32_t stackIdx = 0; stackIdx < Puzzle::Config::MaxAvailableStacks; ++stackIdx)
{
auto& card = Data.AvailableCards[stackIdx];
for (int32_t cardIdx = 0; cardIdx < UIAvailableCardMaxStackPreview; cardIdx++)
{
auto h = UIAvailableCards[stackIdx * UIAvailableCardMaxStackPreview + cardIdx];
auto& quad = level.UIQuads.Get(h);
int32_t remaining = (int32_t)card.MaxAvailableCount - (int32_t)card.UsedCount;
if (stackIdx < Data.AvailableCardCount && cardIdx < remaining)
{
quad.UIPos = Vec3{cardIdx * 0.05f + stackIdx * 1.2f,
4.2f + (cardIdx % 2 == 0 ? 0.02f : 0.0f),
cardIdx * 0.001f - 0.3f} *
UICardOffset * UICardScale;
UpdateQuad(level.UIQuads, h);
quad.EData.Visible = true;
quad.EData.TextureHandle = Puzzle::IsValid(card.RefCard)
? staticCards[card.RefCard.Idx].BoardTextureHandle
: Gen::TextureHandle{0};
quad.EData.Transform.Scale = {UICardScale, UICardScale, UICardScale};
quad.EData.DotColor = {1.0f, 1.0f, 1.0f, 1.0f};
quad.EData.BaseColor = {1.0f, 1.0f, 1.0f, 1.0f};
if (cardIdx == 0)
{
if (IsQuadHovered(quad.EData.Transform, mousePosWorld, quadPlaneIntersectPos) &&
DraggedAvailableCardIdx == UINT16_MAX && DraggedCard.X == -1 &&
GetMouseButtonPressedNow(MouseButton::Left))
{
DraggedAvailableCardIdx = stackIdx;
}
if (DraggedAvailableCardIdx == stackIdx)
{
Vec3 dragPos = quadPlaneIntersectPos;
dragPos += StaticData.ZAxis * -0.01f;
quad.EData.Transform.Position = dragPos;
int32_t xPos;
int32_t yPos;
UIPosToCardPos(Data, quadPlaneIntersectPos, xPos, yPos);
if (!GetMouseButton(MouseButton::Left))
{
if (xPos >= 0 && xPos < Data.WidthTiles / Puzzle::Config::CardSize && yPos >= 0 &&
yPos < Data.HeightTiles / Puzzle::Config::CardSize)
{
Gen::PuzPos targetCardPos = {(int8_t)xPos, (int8_t)yPos};
Gen::PlacedPuzzleCard& targetCard =
Data.PlacedCards[yPos * Puzzle::Config::MaxPuzzleSizeCards + xPos];
if (!Puzzle::IsValid(targetCard.RefCard) || targetCard.RefCard.Idx == 0)
{
Puzzle::DragAvailableCardTo(Data, targetCardPos, DraggedAvailableCardIdx, 0);
}
}
DraggedAvailableCardIdx = UINT16_MAX;
}
}
}
}
else
{
quad.EData.Visible = false;
}
}
}
}
void WorldPuzzleUI::UpdateBoardCards(Gen::PuzzleData& Data)
{
auto& level = GetInstance().GameLevel;
auto& staticCards = Puzzle::GetStaticPuzzleData().Cards;
Vec3 quadPlaneIntersectPos;
Vec3 boardOffset = CalcBoardOffset(Data);
for (int8_t y = 0; y < Data.HeightTiles / Puzzle::Config::CardSize; ++y)
{
for (int8_t x = 0; x < Data.WidthTiles / Puzzle::Config::CardSize; ++x)
{
// UI Quad
int32_t cardIdx = y * Puzzle::Config::MaxPuzzleSizeCards + x;
PlacedPuzzleCard& card = Data.PlacedCards[cardIdx];
bool isValid = Puzzle::IsValid(card.RefCard);
bool isLocked = GetFlag(card.Flags, PlacedPuzzleCardFlags::Locked);
auto& quad = level.UIQuads.Get(UIPlacedCards[cardIdx]);
quad.UIPos = CardPosToUIPos(Data, card.Position.X, card.Position.Y);
quad.UIRot = card.Rotation * bx::kPi * -0.5f;
UpdateQuad(level.UIQuads, UIPlacedCards[cardIdx]);
quad.EData.Visible = isValid;
quad.EData.TextureHandle =
isValid ? staticCards[card.RefCard.Idx].BoardTextureHandle : Gen::TextureHandle{};
quad.EData.DotColor =
isLocked ? Puzzle::GetStaticPuzzleData().Visuals.DisabledCardTint : Vec4{1.0f, 1.0f, 1.0f, 1.0f};
quad.EData.Transform.Scale = {UICardScale, UICardScale, UICardScale};
if (isValid && IsQuadHovered(quad.EData.Transform, StaticData.MousePosWorld, quadPlaneIntersectPos))
{
if (!isLocked && DraggedCard.X == -1 && DraggedAvailableCardIdx == UINT16_MAX)
{
if (GetMouseButtonPressedNow(MouseButton::Left))
{
DraggedCard.X = x;
DraggedCard.Y = y;
}
if (GetMouseButtonPressedNow(MouseButton::Right))
{
Puzzle::RotateCard(card);
}
}
}
if (DraggedCard.X == x && DraggedCard.Y == y)
{
Vec3 dragPos = quadPlaneIntersectPos;
dragPos += StaticData.ZAxis * -0.01f;
quad.EData.Transform.Position = dragPos;
int32_t xPos;
int32_t yPos;
UIPosToCardPos(Data, quadPlaneIntersectPos, xPos, yPos);
Gen::PuzPos srcCardPos = {(int8_t)DraggedCard.X, (int8_t)DraggedCard.Y};
Gen::PlacedPuzzleCard& srcCard =
Data.PlacedCards[srcCardPos.Y * Puzzle::Config::MaxPuzzleSizeCards + srcCardPos.X];
if (GetMouseButtonPressedNow(MouseButton::Right))
{
Puzzle::RotateCard(srcCard);
}
if (!GetMouseButton(MouseButton::Left))
{
Gen::PuzPos targetCardPos = {(int8_t)xPos, (int8_t)yPos};
if (xPos >= 0 && xPos < Data.WidthTiles / Puzzle::Config::CardSize && yPos >= 0 &&
yPos < Data.HeightTiles / Puzzle::Config::CardSize)
{
PlacedPuzzleCard srcCardCopy = srcCard;
Gen::PlacedPuzzleCard& targetCard =
Data.PlacedCards[yPos * Puzzle::Config::MaxPuzzleSizeCards + xPos];
bool canBeReplaced = !Puzzle::IsValid(targetCard.RefCard) || targetCard.RefCard.Idx == 0;
if (canBeReplaced && Puzzle::ReturnPlacedCard(Data, srcCardPos))
{
int32_t foundIdx = -1;
for (int32_t availCardIdx = 0; availCardIdx < Data.AvailableCardCount; ++availCardIdx)
{
if (Data.AvailableCards[availCardIdx].RefCard.Idx == srcCardCopy.RefCard.Idx)
{
foundIdx = availCardIdx;
break;
}
}
if (foundIdx >= 0)
{
Puzzle::DragAvailableCardTo(Data, targetCardPos, foundIdx, srcCard.Rotation);
}
else
{
LOG_ERROR("NOTFOUND: %u", srcCardCopy.RefCard.Idx);
}
}
}
else
{
Puzzle::ReturnPlacedCard(Data, srcCardPos);
}
DraggedCard.X = -1;
DraggedCard.Y = -1;
}
}
}
}
}
void WorldPuzzleUI::Update(Gen::PuzzleData& Data, bool IsPuzzleSolved)
{
auto& level = GetInstance().GameLevel;
auto& player = GetInstance().Player;
Transform& camTransform = player.PlayerCamTransform;
UpdateMatrix(camTransform);
// UI Tablet
if (IsValid(TabletHandle))
{
auto& tablet = level.UIQuads.Get(TabletHandle);
tablet.EData.Transform.Rotation = camTransform.Rotation;
Rotate(tablet.EData.Transform, {0.5f * bx::kPi, 0.0f, 0.0f});
tablet.EData.Transform.Position = camTransform.Position + AxisForward(camTransform.M) * 1.0f;
StaticData.UITransform = tablet.EData.Transform;
tablet.EData.Transform.Position += {0.0f, 0.0f, 0.01f};
}
StaticData.UITransform.Rotation = camTransform.Rotation;
StaticData.ZAxis = AxisForward(StaticData.UITransform.M);
StaticData.UITransform.Position += StaticData.ZAxis * -0.01f;
StaticData.MousePosWorld = GetMousePosWorld();
// NOLINTBEGIN
Vec2 uiOffset = Vec2{static_cast<float>(Data.WidthTiles / Puzzle::Config::CardSize - 1),
static_cast<float>(Data.HeightTiles / Puzzle::Config::CardSize - 1)};
// NOLINTEND
uiOffset *= -UICardOffset * 0.5f;
auto& solvedQuad = level.UIQuads.Get(SolvedQuad);
solvedQuad.EData.Visible = true;
solvedQuad.EData.TextureHandle = IsPuzzleSolved ? GetInstance().Player.Config.TabletStatusSolvedTexture
: GetInstance().Player.Config.TabletStatusNotSolvedTexture;
UpdateQuad(level.UIQuads, SolvedQuad);
auto& resetQuad = level.UIQuads.Get(ResetQuad);
resetQuad.EData.Visible = true;
UpdateQuad(level.UIQuads, ResetQuad);
Vec3 hoverPosWorld;
if (GetMouseButtonPressedNow(MouseButton::Left) &&
IsQuadHovered(resetQuad.EData.Transform, StaticData.MousePosWorld, hoverPosWorld))
{
Puzzle::ResetPuzzle(Data);
}
UpdateAvailableCards(Data);
UpdateBoardCards(Data);
}
void WorldPuzzleUI::Reset()
{
auto& level = GetInstance().GameLevel;
auto& config = GetInstance().Player.Config;
TabletHandle = NewQuad(level.UIQuads, config.TabletBackgroundRenderData, TabletHandle);
SolvedQuad = NewQuad(level.UIQuads, config.TabletStatusRenderData, SolvedQuad);
ResetQuad = NewQuad(level.UIQuads, config.TabletResetRenderData, ResetQuad);
}
} // namespace Game

44
src/game/UI.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include "../gen/Generated.h"
#include "Entity.h"
#include "Puzzle.h"
namespace Game
{
struct StaticUIData
{
Gen::Transform UITransform;
Gen::Vec3 ZAxis;
Gen::Vec3 MousePosWorld;
};
struct WorldPuzzleUI
{
static constexpr float UICardScale = 0.05f;
static constexpr float UICardOffset = 2.1f * UICardScale;
static constexpr int32_t UIAvailableCardMaxStackPreview = 3;
UIQuadEntityHandle TabletHandle;
UIQuadEntityHandle SolvedQuad;
UIQuadEntityHandle ResetQuad;
UIQuadEntityHandle UIPlacedCards[Puzzle::Config::MaxCardsInPuzzle];
UIQuadEntityHandle UIAvailableCards[Puzzle::Config::MaxAvailableStacks * UIAvailableCardMaxStackPreview];
Gen::PuzPos DraggedCard{-1, -1};
uint16_t DraggedAvailableCardIdx = UINT16_MAX;
void Setup();
void UpdateAvailableCards(Gen::PuzzleData& Data);
void UpdateBoardCards(Gen::PuzzleData& Data);
void Update(Gen::PuzzleData& Data, bool IsPuzzleSolved);
void Reset();
};
UIQuadEntityHandle NewQuad(UIQuadEntityManager& manager,
const Gen::SavedEntityRenderData& loadData,
UIQuadEntityHandle oldHandle = {});
void UpdateQuad(UIQuadEntityManager& manager, UIQuadEntityHandle handle);
Gen::Vec3 GetMousePosWorld();
bool IsQuadHovered(Gen::Transform& quadTransform, Gen::Vec3 mousePosWorld, Gen::Vec3& outQuadPlaneIntersectPos);
} // namespace Game

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/game/data/puzzles/0.pzl LFS Normal file

Binary file not shown.

BIN
src/game/data/puzzles/1.pzl LFS Normal file

Binary file not shown.

BIN
src/game/data/puzzles/2.pzl LFS Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,41 +1,123 @@
type Vec2
{
f32 x
f32 y
}
type Vec3
{
f32 x
f32 y
f32 z
}
type Vec4
{
f32 x
f32 y
f32 z
f32 w
}
type Mat3
{
f32 M Arr(9) Default("{
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f
}")
}
type Mat4
{
f32 M Arr(16) Default("{
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
}")
}
type Transform
{
Mat4 M
Mat4 MI
Vec3 Position
Mat4 Rotation
Vec3 Scale Default("{1.0f, 1.0f, 1.0f}")
}
type AssetHandle
{
u32 Idx Default("UINT32_MAX")
}
type ModelHandle
{
u16 ModelIdx Default("UINT16_MAX")
AssetHandle Asset
}
type TextureHandle
{
u16 TextureIdx Default("UINT16_MAX")
AssetHandle Asset
}
type PuzPos
{
i8 X
i8 Y
}
type ElemPos
{
PuzPos Position
u8 ElemIdx
}
enum PuzzleElementType(u8)
{
None GameName("Empty")
WaterIn GameName("Water Source")
WaterGoal GameName("Water Goal")
WaterChannel GameName("Water Channel")
ElectricIn GameName("Electricity Source")
ElectricGoal GameName("Electricity Goal")
Blocked GameName("Blocked")
Bridge GameName("Bridge")
None GameName("Empty") ShortName(" ")
WaterIn GameName("Water Source") ShortName("~+")
WaterGoal GameName("Water Goal") ShortName("~!")
WaterChannel GameName("Water Channel") ShortName("~")
ElectricIn GameName("Electricity Source") ShortName("e+")
ElectricGoal GameName("Electricity Goal") ShortName("e!")
Blocked GameName("Blocked") ShortName("B")
Bridge GameName("Bridge") ShortName("#")
}
type PuzzleNode
type CardSocket
{
PuzzleElementType PlacedTypes
ModelHandle Model
u8 ConnectionDirection
}
type StaticPuzzleCard
{
PuzzleNode Nodes Arr(8)
u16 ModelHandle
PuzzleElementType Elements Arr(4)
ModelHandle BaseModelHandle
ModelHandle NorthCoverHandle
ModelHandle EastCoverHandle
ModelHandle SouthCoverHandle
ModelHandle WestCoverHandle
CardSocket Sockets Arr(16)
TextureHandle ModelTextureHandle
TextureHandle BoardTextureHandle
}
type StaticPuzzleCardHandle
{
u16 Idx
u16 Idx Default("UINT16_MAX")
}
type PuzzleVisualSettings
{
Vec4 TileBaseColor
Vec4 TileDotColor
Vec3 Test
Vec4 DisabledCardTint
}
type StaticPuzzleData
{
StaticPuzzleCard Cards Arr(64)
PuzzleVisualSettings Visuals
}
type PuzzleCardStack
@@ -45,23 +127,58 @@ type PuzzleCardStack
u8 UsedCount
}
enum PlacedPuzzleCardFlags(u8) Flags
{
None
Locked
}
type PlacedPuzzleCard
{
StaticPuzzleCardHandle RefCard
PuzPos Position
u8 Rotation
b IsLocked
PlacedPuzzleCardFlags Flags
}
type PuzzleData
{
u16 ID
str PuzzleName Arr(64)
u8 WidthTiles
u8 HeightTiles
u32 AvailableCardCount
PuzzleCardStack AvailableCards Arr(8)
u32 PlacedCardCount
PlacedPuzzleCard PlacedCards Arr(8)
PuzzleNode PlacedNodes Arr(8)
PuzzleCardStack AvailableCards Arr(16)
PlacedPuzzleCard PlacedCards Arr(256)
PlacedPuzzleCard InitialPlacedCards Arr(256)
PuzzleElementType BackgroundTiles Arr(1024)
u32 GoalPositionCount
ElemPos GoalPositions
PuzPos GoalPositions Arr(16)
}
enum EMaterial
{
Default
UI
}
type SavedEntityRenderData
{
Vec4 BaseColor
Vec4 HighlightColor
Transform TF
EMaterial Material
TextureHandle Texture
ModelHandle Model
b Visible
}
type SavedPlayerConfig
{
SavedEntityRenderData TabletBackgroundRenderData
SavedEntityRenderData TabletStatusRenderData
TextureHandle TabletStatusNotSolvedTexture
TextureHandle TabletStatusSolvedTexture
SavedEntityRenderData TabletResetRenderData
SavedEntityRenderData BackgroundLevelRenderData Arr(16)
}

View File

@@ -0,0 +1,134 @@
#include "../Gen.h"
#include "../Log.h"
#include "Dither.h"
#include "bx/math.h"
using namespace Gen;
void DitherGen(DitherData& data, int32_t recursion)
{
data.Points[0] = {0.0f, 0.0f};
data.Points[1] = {0.5f, 0.5f};
data.Points[2] = {0.5f, 0.0f};
data.Points[3] = {0.0f, 0.5f};
data.PointCount = 4;
for (int32_t i = 0; i < data.BrightnessBucketCount; ++i)
data.BrightnessBuckets[i] = 0;
// add "subdivided" beyer matrix layers
for (int32_t recursionLevel = 0; recursionLevel < recursion - 1; ++recursionLevel)
{
int32_t startCount = data.PointCount;
float offset = bx::pow(0.5f, recursionLevel + 1);
for (int32_t i = 1; i < 4; ++i)
{
for (int32_t j = 0; j < startCount; ++j)
{
data.Points[data.PointCount] = data.Points[j] + data.Points[i] * offset;
data.PointCount++;
}
}
}
// Texture info setup
uint64_t dotsPerSide = bx::round(bx::pow(2, recursion));
data.DitherTexDepth = dotsPerSide * dotsPerSide;
data.DitherTexWH = 16 * dotsPerSide;
uint64_t texPixelCount = data.DitherTexWH * data.DitherTexWH * data.DitherTexDepth;
if (BX_COUNTOF(DitherData::DitherTex) < texPixelCount)
{
LOG_ERROR("Too many pixels: %llu", texPixelCount);
return;
}
// What does this do?
float invRes = 1.0f / data.DitherTexWH;
for (int32_t z = 0; z < data.DitherTexDepth; ++z)
{
int32_t dotCount = z + 1;
float dotArea = 0.5f / dotCount;
float dotRadius = bx::sqrt(dotArea / bx::kPi);
int32_t zOffset = z * data.DitherTexWH * data.DitherTexWH;
for (int32_t y = 0; y < data.DitherTexWH; ++y)
{
int32_t yOffset = y * data.DitherTexWH;
for (int32_t x = 0; x < data.DitherTexWH; ++x)
{
Vec2 point{(x + 0.5f) * invRes, (y + 0.5f) * invRes};
float dist = bx::kFloatInfinity;
for (int32_t i = 0; i < dotCount; ++i)
{
Vec2 vec = point - data.Points[i];
float wrapX = bx::wrap(vec.x + 0.5f, 1.0f) - 0.5f;
float wrapY = bx::wrap(vec.y + 0.5f, 1.0f) - 0.5f;
Vec2 wrappedVec = {wrapX, wrapY};
float curDist = Magnitude(wrappedVec);
dist = bx::min(dist, curDist);
}
dist = dist / (dotRadius * 2.4f);
float val = bx::clamp(1.0f - dist, 0.0f, 1.0f);
data.DitherTex[x + yOffset + zOffset] = Vec4{val, val, val, 1.0f};
int32_t bucket = bx::clamp(
uint32_t(val * DitherData::BrightnessBucketCount), 0, DitherData::BrightnessBucketCount - 1);
data.BrightnessBuckets[bucket] += 1;
}
}
}
// Brightness ramp
int32_t sum = 0;
for (int32_t i = 0; i < data.BrightnessBucketCount; ++i)
{
sum += data.BrightnessBuckets[data.BrightnessBucketCount - 1 - i];
data.BrightnessRamp[i + 1] = sum / (float)texPixelCount;
}
// Upload textures
CleanupDitherData(data);
const bgfx::Memory* memPreview = bgfx::makeRef(data.DitherTex, texPixelCount * sizeof(Vec4));
const bgfx::Memory* memFinal = bgfx::makeRef(data.DitherTex, texPixelCount * sizeof(Vec4));
const bgfx::Memory* memRamp = bgfx::makeRef(data.BrightnessRamp, sizeof(data.BrightnessRamp));
data.PreviewTex = bgfx::createTexture2D(data.DitherTexWH,
data.DitherTexWH * data.DitherTexDepth,
false,
false,
bgfx::TextureFormat::RGBA32F,
0,
memPreview);
data.FinalTex = bgfx::createTexture3D(
data.DitherTexWH, data.DitherTexWH, data.DitherTexDepth, false, bgfx::TextureFormat::RGBA32F, 0, memFinal);
data.RampTex =
bgfx::createTexture2D(BX_COUNTOF(data.BrightnessRamp), 1, false, 1, bgfx::TextureFormat::R32F, 0, memRamp);
if (!isValid(data.DitherSampler))
{
data.DitherSampler = bgfx::createUniform("s_ditherSampler", bgfx::UniformType::Sampler);
}
if (!isValid(data.RampSampler))
{
data.RampSampler = bgfx::createUniform("s_rampSampler", bgfx::UniformType::Sampler);
}
}
void CleanupDitherData(DitherData& data)
{
if (isValid(data.PreviewTex))
{
bgfx::destroy(data.PreviewTex);
data.PreviewTex = BGFX_INVALID_HANDLE;
}
if (isValid(data.FinalTex))
{
bgfx::destroy(data.FinalTex);
data.FinalTex = BGFX_INVALID_HANDLE;
}
if (isValid(data.RampTex))
{
bgfx::destroy(data.RampTex);
data.RampTex = BGFX_INVALID_HANDLE;
}
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include "../../gen/Generated.h"
#include <bgfx/bgfx.h>
#include <imgui.h>
struct DitherData
{
static constexpr uint32_t BrightnessBucketCount = 256;
Gen::Vec2 Points[4096];
uint32_t PointCount = 0;
Gen::Vec4 DitherTex[256 * 256 * 64];
uint32_t DitherTexWH = 0;
uint32_t DitherTexDepth = 0;
int32_t BrightnessBuckets[BrightnessBucketCount];
float BrightnessRamp[BrightnessBucketCount + 1];
bgfx::TextureHandle PreviewTex = BGFX_INVALID_HANDLE;
bgfx::TextureHandle FinalTex = BGFX_INVALID_HANDLE;
bgfx::TextureHandle RampTex = BGFX_INVALID_HANDLE;
bgfx::UniformHandle DitherSampler = BGFX_INVALID_HANDLE;
bgfx::UniformHandle RampSampler = BGFX_INVALID_HANDLE;
ImTextureID PreviewID = 0;
};
void DitherGen(DitherData& data, int32_t recursion);
void CleanupDitherData(DitherData& data);

Some files were not shown because too many files have changed in this diff Show More