Compare commits

...

7 Commits

Author SHA1 Message Date
Vladislav Khorev
e04fcbb9bd Clean up 2026-05-01 20:26:50 +03:00
Vladislav Khorev
519d780b3c Clean up 2026-05-01 20:01:06 +03:00
Vladislav Khorev
16d250a51d merge 2026-05-01 19:03:13 +03:00
Vladislav Khorev
5231b50aab Merge branch 'witcher001-path' into witcher001 2026-05-01 16:53:25 +03:00
8b0a18a6cf corrected collision of tree 2026-05-01 16:01:32 +06:00
Vladislav Khorev
b98631a8c0 Fixing stuff 2026-04-30 11:08:06 +03:00
cebbb8bb5d Added collision 2026-04-26 22:18:42 +06:00
76 changed files with 1790 additions and 9106998 deletions

BIN
audio/background.wav (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

View File

@ -52,6 +52,7 @@ add_library(main SHARED
../../../../src/planet/PlanetObject.cpp
../../../../src/planet/StoneObject.cpp
../../../../src/render/FrameBuffer.cpp
../../../../src/render/ShadowMap.cpp
../../../../src/render/Renderer.cpp
../../../../src/render/ShaderManager.cpp
../../../../src/render/TextureManager.cpp

View File

@ -175,6 +175,7 @@ set(EMSCRIPTEN_LINK_FLAGS
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/loading.png@resources/loading.png"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/shaders@resources/shaders"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start.lua@resources/start.lua"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start2.lua@resources/start2.lua"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../audio@audio"
)

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16)
project(space-game001 LANGUAGES C CXX)
project(witcher001 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -12,9 +12,9 @@ option(FULLSCREEN "Launch the game in fullscreen mode" OFF)
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake)
# ===========================================
# Основной проект space-game001
# Основной проект witcher001
# ===========================================
add_executable(space-game001
add_executable(witcher001
../src/main.cpp
../src/Game.cpp
../src/Game.h
@ -32,8 +32,6 @@ add_executable(space-game001
../src/TextModel.h
../src/AudioPlayerAsync.cpp
../src/AudioPlayerAsync.h
../src/BoneAnimatedModel.cpp
../src/BoneAnimatedModel.h
../src/BoneAnimatedModelNew.cpp
../src/BoneAnimatedModelNew.h
../src/render/OpenGlExtensions.cpp
@ -42,8 +40,6 @@ add_executable(space-game001
../src/utils/Utils.h
../src/SparkEmitter.cpp
../src/SparkEmitter.h
../src/utils/Perlin.cpp
../src/utils/Perlin.h
../src/utils/TaskManager.cpp
../src/utils/TaskManager.h
../src/render/FrameBuffer.cpp
@ -85,21 +81,21 @@ add_executable(space-game001
)
# Установка проекта по умолчанию для Visual Studio
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT space-game001)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT witcher001)
# include-пути проекта
target_include_directories(space-game001 PRIVATE
target_include_directories(witcher001 PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
)
set_target_properties(space-game001 PROPERTIES
OUTPUT_NAME "space-game001"
set_target_properties(witcher001 PROPERTIES
OUTPUT_NAME "witcher001"
)
# Определения препроцессора:
# PNG_ENABLED включает код PNG в TextureManager
# SDL_MAIN_HANDLED отключает переопределение main -> SDL_main
target_compile_definitions(space-game001 PRIVATE
target_compile_definitions(witcher001 PRIVATE
WIN32_LEAN_AND_MEAN
PNG_ENABLED
SDL_MAIN_HANDLED
@ -108,14 +104,14 @@ target_compile_definitions(space-game001 PRIVATE
)
if(FULLSCREEN)
target_compile_definitions(space-game001 PRIVATE FULLSCREEN)
target_compile_definitions(witcher001 PRIVATE FULLSCREEN)
endif()
# Линкуем с SDL2main, если он вообще установлен
target_link_libraries(space-game001 PRIVATE SDL2main_external_lib)
target_link_libraries(witcher001 PRIVATE SDL2main_external_lib)
# Линкуем сторонние библиотеки
target_link_libraries(space-game001 PRIVATE
target_link_libraries(witcher001 PRIVATE
SDL2_external_lib
libpng_external_lib
zlib_external_lib
@ -130,7 +126,7 @@ target_link_libraries(space-game001 PRIVATE
# Линкуем OpenGL (Windows)
if(WIN32)
target_link_libraries(space-game001 PRIVATE opengl32)
target_link_libraries(witcher001 PRIVATE opengl32)
endif()
# ===========================================
@ -140,19 +136,19 @@ if (WIN32)
# SDL2: в Debug - SDL2d.dll, в Release - SDL2.dll
set(SDL2_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2_INSTALL_DIR}/bin/SDL2d.dll,${SDL2_INSTALL_DIR}/bin/SDL2.dll>")
set(SDL2_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:space-game001>/SDL2d.dll,$<TARGET_FILE_DIR:space-game001>/SDL2.dll>")
set(SDL2_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:witcher001>/SDL2d.dll,$<TARGET_FILE_DIR:witcher001>/SDL2.dll>")
set(LIBZIP_DLL_SRC "$<IF:$<CONFIG:Debug>,${LIBZIP_BASE_DIR}-Debug/bin/zip.dll,${LIBZIP_BASE_DIR}-Release/bin/zip.dll>")
set(ZLIB_DLL_SRC "$<IF:$<CONFIG:Debug>,${ZLIB_INSTALL_DIR}/bin/zd.dll,${ZLIB_INSTALL_DIR}/bin/z.dll>")
set(ZLIB_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:space-game001>/zd.dll,$<TARGET_FILE_DIR:space-game001>/z.dll>")
set(ZLIB_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:witcher001>/zd.dll,$<TARGET_FILE_DIR:witcher001>/z.dll>")
set(SDL2TTF_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>")
set(SDL2MIXER_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2MIXER_BASE_DIR}-Debug/bin/SDL2_mixerd.dll,${SDL2MIXER_BASE_DIR}-Release/bin/SDL2_mixer.dll>")
add_custom_command(TARGET space-game001 POST_BUILD
add_custom_command(TARGET witcher001 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying DLLs to output folder..."
# Копируем SDL2 (целевое имя всегда SDL2.dll)
@ -163,7 +159,7 @@ if (WIN32)
# Копируем LIBZIP (целевое имя всегда zip.dll)
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${LIBZIP_DLL_SRC}"
"$<TARGET_FILE_DIR:space-game001>/zip.dll"
"$<TARGET_FILE_DIR:witcher001>/zip.dll"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${ZLIB_DLL_SRC}"
@ -171,11 +167,11 @@ if (WIN32)
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2TTF_DLL_SRC}"
"$<TARGET_FILE_DIR:space-game001>"
"$<TARGET_FILE_DIR:witcher001>"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-$<CONFIG>/bin/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
"$<TARGET_FILE_DIR:space-game001>/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
"$<TARGET_FILE_DIR:witcher001>/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
)
endif()
@ -191,12 +187,12 @@ set(RUNTIME_RESOURCE_DIRS
# Копируем ресурсы и шейдеры в папку exe и в корень build/
foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS)
add_custom_command(TARGET space-game001 POST_BUILD
add_custom_command(TARGET witcher001 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..."
# 1) туда, где лежит exe (build/Debug, build/Release и т.п.)
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}"
"$<TARGET_FILE_DIR:space-game001>/${resdir}"
"$<TARGET_FILE_DIR:witcher001>/${resdir}"
# 2) в корень build, если захочешь запускать из этой папки
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}"

View File

@ -3,175 +3,472 @@
"agentRadius": 0.45,
"floorY": 0.0,
"objectPadding": 0.25,
"boundaryPadding": 0.35,
"areas": [
{
"name": "main_corridor",
"available": true,
"polygon": [
[-2.2, 0.8],
[2.2, 0.8],
[2.2, -40.8],
[-2.2, -40.8]
[
-2.2,
0.8
],
[
2.2,
0.8
],
[
2.2,
-40.8
],
[
-2.2,
-40.8
]
]
},
{
"name": "left_door_05",
"available": true,
"polygon": [
[-3.4, -4.2],
[-2.0, -4.2],
[-2.0, -5.8],
[-3.4, -5.8]
[
-3.4,
-4.2
],
[
-2.0,
-4.2
],
[
-2.0,
-5.8
],
[
-3.4,
-5.8
]
]
},
{
"name": "right_door_05",
"available": true,
"polygon": [
[2.0, -4.2],
[3.4, -4.2],
[3.4, -5.8],
[2.0, -5.8]
[
2.0,
-4.2
],
[
3.4,
-4.2
],
[
3.4,
-5.8
],
[
2.0,
-5.8
]
]
},
{
"name": "left_room_05",
"available": true,
"polygon": [
[-8.8, -1.0],
[-3.0, -1.0],
[-3.0, -9.0],
[-8.8, -9.0]
[
-8.8,
-1.0
],
[
-3.0,
-1.0
],
[
-3.0,
-9.0
],
[
-8.8,
-9.0
]
]
},
{
"name": "right_room_05",
"available": true,
"polygon": [
[3.0, -1.0],
[8.8, -1.0],
[8.8, -9.0],
[3.0, -9.0]
[
3.0,
-1.0
],
[
8.8,
-1.0
],
[
8.8,
-9.0
],
[
3.0,
-9.0
]
]
},
{
"name": "left_door_15",
"available": true,
"polygon": [
[-3.4, -14.2],
[-2.0, -14.2],
[-2.0, -15.8],
[-3.4, -15.8]
[
-3.4,
-14.2
],
[
-2.0,
-14.2
],
[
-2.0,
-15.8
],
[
-3.4,
-15.8
]
]
},
{
"name": "right_door_15",
"available": true,
"polygon": [
[2.0, -14.2],
[3.4, -14.2],
[3.4, -15.8],
[2.0, -15.8]
[
2.0,
-14.2
],
[
3.4,
-14.2
],
[
3.4,
-15.8
],
[
2.0,
-15.8
]
]
},
{
"name": "left_room_15",
"available": true,
"polygon": [
[-8.8, -11.0],
[-3.0, -11.0],
[-3.0, -19.0],
[-8.8, -19.0]
[
-8.8,
-11.0
],
[
-3.0,
-11.0
],
[
-3.0,
-19.0
],
[
-8.8,
-19.0
]
]
},
{
"name": "right_room_15",
"available": true,
"polygon": [
[3.0, -11.0],
[8.8, -11.0],
[8.8, -19.0],
[3.0, -19.0]
[
3.0,
-11.0
],
[
8.8,
-11.0
],
[
8.8,
-19.0
],
[
3.0,
-19.0
]
]
},
{
"name": "left_door_25",
"available": true,
"polygon": [
[-3.4, -24.2],
[-2.0, -24.2],
[-2.0, -25.8],
[-3.4, -25.8]
[
-3.4,
-24.2
],
[
-2.0,
-24.2
],
[
-2.0,
-25.8
],
[
-3.4,
-25.8
]
]
},
{
"name": "right_door_25",
"available": true,
"polygon": [
[2.0, -24.2],
[3.4, -24.2],
[3.4, -25.8],
[2.0, -25.8]
[
2.0,
-24.2
],
[
3.4,
-24.2
],
[
3.4,
-25.8
],
[
2.0,
-25.8
]
]
},
{
"name": "left_room_25",
"available": true,
"polygon": [
[-8.8, -21.0],
[-3.0, -21.0],
[-3.0, -29.0],
[-8.8, -29.0]
[
-8.8,
-21.0
],
[
-3.0,
-21.0
],
[
-3.0,
-29.0
],
[
-8.8,
-29.0
]
]
},
{
"name": "right_room_25",
"available": true,
"polygon": [
[3.0, -21.0],
[8.8, -21.0],
[8.8, -29.0],
[3.0, -29.0]
[
3.0,
-21.0
],
[
8.8,
-21.0
],
[
8.8,
-29.0
],
[
3.0,
-29.0
]
]
},
{
"name": "left_door_35",
"available": true,
"polygon": [
[-3.4, -34.2],
[-2.0, -34.2],
[-2.0, -35.8],
[-3.4, -35.8]
[
-3.4,
-34.2
],
[
-2.0,
-34.2
],
[
-2.0,
-35.8
],
[
-3.4,
-35.8
]
]
},
{
"name": "right_door_35",
"available": true,
"polygon": [
[2.0, -34.2],
[3.4, -34.2],
[3.4, -35.8],
[2.0, -35.8]
[
2.0,
-34.2
],
[
3.4,
-34.2
],
[
3.4,
-35.8
],
[
2.0,
-35.8
]
]
},
{
"name": "left_room_35",
"available": true,
"polygon": [
[-8.8, -31.0],
[-3.0, -31.0],
[-3.0, -39.0],
[-8.8, -39.0]
[
-8.8,
-31.0
],
[
-3.0,
-31.0
],
[
-3.0,
-39.0
],
[
-8.8,
-39.0
]
]
},
{
"name": "right_room_35",
"available": true,
"polygon": [
[3.0, -31.0],
[8.8, -31.0],
[8.8, -39.0],
[3.0, -39.0]
[
3.0,
-31.0
],
[
8.8,
-31.0
],
[
8.8,
-39.0
],
[
3.0,
-39.0
]
]
}
],
"obstacles": [
{
"name": "firebox",
"polygon": [
[
-2.49468,
-32.87888
],
[
-2.05776,
-32.87888
],
[
-2.05776,
-31.51618
],
[
-2.49468,
-31.51618
]
]
},
{
"name": "bench",
"polygon": [
[
-2.54485,
-9.31999
],
[
-2.51786,
-9.3557
],
[
-2.48369,
-9.37177
],
[
-2.45574,
-9.38357
],
[
-2.17907,
-9.39566
],
[
-1.80829,
-9.40749
],
[
-1.7521,
-9.36413
],
[
-1.66462,
-9.28728
],
[
-1.65801,
-9.14512
],
[
-1.65114,
-7.22169
],
[
-1.65535,
-6.51944
],
[
-1.72626,
-6.42306
],
[
-1.76837,
-6.39498
],
[
-2.47166,
-6.39746
],
[
-2.5309,
-6.44135
],
[
-2.53691,
-7.18464
]
]
}
]

View File

@ -8,10 +8,668 @@
"name": "main_corridor",
"available": true,
"polygon": [
[-200, 200],
[200, 200],
[200, -200],
[-200, -200]
[
-200,
200
],
[
200,
200
],
[
200,
-200
],
[
-200,
-200
]
]
}
],
"obstacles": [
{
"name": "door",
"polygon": [
[
-5.2,
7.08001
],
[
-5.19904,
7.07025
],
[
-5.19619,
7.06087
],
[
-5.19157,
7.05223
],
[
-5.18536,
7.04465
],
[
-5.17778,
7.03843
],
[
-5.16913,
7.03381
],
[
-5.15975,
7.03097
],
[
-5.15,
7.03001
],
[
-3.75,
7.13001
],
[
-3.625,
7.43001
],
[
-3.625,
8.83001
],
[
-3.75,
9.13001
],
[
-5.15,
9.23001
],
[
-5.15975,
9.22905
],
[
-5.16913,
9.2262
],
[
-5.17778,
9.22158
],
[
-5.18536,
9.21536
],
[
-5.19157,
9.20778
],
[
-5.19619,
9.19914
],
[
-5.19904,
9.18976
],
[
-5.2,
9.18001
]
]
},
{
"name": "inai",
"polygon": [
[
-3.75,
-15.0
],
[
3.75,
-15.0
],
[
3.75,
15.0
],
[
-3.75,
15.0
]
]
},
{
"name": "tree001",
"polygon": [
[
10.45,
12.0
],
[
10.38971,
12.225
],
[
10.225,
12.38971
],
[
10.0,
12.45
],
[
9.775,
12.38971
],
[
9.61029,
12.225
],
[
9.55,
12.0
],
[
9.61029,
11.775
],
[
9.775,
11.61029
],
[
10.0,
11.55
],
[
10.225,
11.61029
],
[
10.38971,
11.775
]
]
},
{
"name": "tree002",
"polygon": [
[
-11.55,
19.0
],
[
-11.61029,
19.225
],
[
-11.775,
19.38971
],
[
-12.0,
19.45
],
[
-12.225,
19.38971
],
[
-12.38971,
19.225
],
[
-12.45,
19.0
],
[
-12.38971,
18.775
],
[
-12.225,
18.61029
],
[
-12.0,
18.55
],
[
-11.775,
18.61029
],
[
-11.61029,
18.775
]
]
},
{
"name": "tree003",
"polygon": [
[
-11.55,
8.0
],
[
-11.61029,
8.225
],
[
-11.775,
8.38971
],
[
-12.0,
8.45
],
[
-12.225,
8.38971
],
[
-12.38971,
8.225
],
[
-12.45,
8.0
],
[
-12.38971,
7.775
],
[
-12.225,
7.61029
],
[
-12.0,
7.55
],
[
-11.775,
7.61029
],
[
-11.61029,
7.775
]
]
},
{
"name": "tree004",
"polygon": [
[
-11.55,
0.0
],
[
-11.61029,
0.225
],
[
-11.775,
0.38971
],
[
-12.0,
0.45
],
[
-12.225,
0.38971
],
[
-12.38971,
0.225
],
[
-12.45,
0.0
],
[
-12.38971,
-0.225
],
[
-12.225,
-0.38971
],
[
-12.0,
-0.45
],
[
-11.775,
-0.38971
],
[
-11.61029,
-0.225
]
]
},
{
"name": "tree005",
"polygon": [
[
-11.55,
-8.0
],
[
-11.61029,
-7.775
],
[
-11.775,
-7.61029
],
[
-12.0,
-7.55
],
[
-12.225,
-7.61029
],
[
-12.38971,
-7.775
],
[
-12.45,
-8.0
],
[
-12.38971,
-8.225
],
[
-12.225,
-8.38971
],
[
-12.0,
-8.45
],
[
-11.775,
-8.38971
],
[
-11.61029,
-8.225
]
]
},
{
"name": "tree006",
"polygon": [
[
8.94915,
-2.59884
],
[
8.88886,
-2.37384
],
[
8.72415,
-2.20913
],
[
8.49915,
-2.14884
],
[
8.27415,
-2.20913
],
[
8.10944,
-2.37384
],
[
8.04915,
-2.59884
],
[
8.10944,
-2.82384
],
[
8.27415,
-2.98855
],
[
8.49915,
-3.04884
],
[
8.72415,
-2.98855
],
[
8.88886,
-2.82384
]
]
},
{
"name": "tree007",
"polygon": [
[
15.0436,
5.3401
],
[
14.98331,
5.5651
],
[
14.8186,
5.72981
],
[
14.5936,
5.7901
],
[
14.3686,
5.72981
],
[
14.20389,
5.5651
],
[
14.1436,
5.3401
],
[
14.20389,
5.1151
],
[
14.3686,
4.95039
],
[
14.5936,
4.8901
],
[
14.8186,
4.95039
],
[
14.98331,
5.1151
]
]
},
{
"name": "tree008",
"polygon": [
[
24.3795,
9.00583
],
[
24.31921,
9.23083
],
[
24.1545,
9.39554
],
[
23.9295,
9.45583
],
[
23.7045,
9.39554
],
[
23.53979,
9.23083
],
[
23.4795,
9.00583
],
[
23.53979,
8.78083
],
[
23.7045,
8.61612
],
[
23.9295,
8.55583
],
[
24.1545,
8.61612
],
[
24.31921,
8.78083
]
]
},
{
"name": "tree009",
"polygon": [
[
30.2628,
-1.45278
],
[
30.20251,
-1.22778
],
[
30.0378,
-1.06307
],
[
29.8128,
-1.00278
],
[
29.5878,
-1.06307
],
[
29.42309,
-1.22778
],
[
29.3628,
-1.45278
],
[
29.42309,
-1.67778
],
[
29.5878,
-1.84249
],
[
29.8128,
-1.90278
],
[
30.0378,
-1.84249
],
[
30.20251,
-1.67778
]
]
},
{
"name": "tree010",
"polygon": [
[
33.6271,
14.609
],
[
33.56681,
14.834
],
[
33.4021,
14.99871
],
[
33.1771,
15.059
],
[
32.9521,
14.99871
],
[
32.78739,
14.834
],
[
32.7271,
14.609
],
[
32.78739,
14.384
],
[
32.9521,
14.21929
],
[
33.1771,
14.159
],
[
33.4021,
14.21929
],
[
33.56681,
14.384
]
]
}
]

BIN
resources/w/Zombie_BaseColor.png (Stored with Git LFS)

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

BIN
resources/w/exterior/Segmented_Plane.png (Stored with Git LFS)

Binary file not shown.

View File

@ -1,37 +0,0 @@
===Vertices (Split by UV/Normal): 16
V 0: Pos(-33.333332, -100.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 0.0)
V 1: Pos(-100.0, -33.333332, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.333333)
V 2: Pos(-100.0, -100.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.0)
V 3: Pos(33.333336, -100.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 0.0)
V 4: Pos(-33.333332, -33.333332, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 0.333333)
V 5: Pos(100.0, -100.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.0)
V 6: Pos(33.333336, -33.333332, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 0.333333)
V 7: Pos(-100.0, 33.333336, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 0.666667)
V 8: Pos(-33.333332, 33.333336, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 0.666667)
V 9: Pos(100.0, -33.333332, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.333333)
V 10: Pos(33.333336, 33.333336, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 0.666667)
V 11: Pos(-100.0, 100.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.0, 1.0)
V 12: Pos(-33.333332, 100.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.333333, 1.0)
V 13: Pos(100.0, 33.333336, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 0.666667)
V 14: Pos(33.333336, 100.0, 0.0) Norm(0.0, 0.0, 1.0) UV(0.666667, 1.0)
V 15: Pos(100.0, 100.0, 0.0) Norm(0.0, 0.0, 1.0) UV(1.0, 1.0)
===Triangles (Indices): 18
Tri: 0 1 2
Tri: 3 4 0
Tri: 5 6 3
Tri: 4 7 1
Tri: 6 8 4
Tri: 9 10 6
Tri: 8 11 7
Tri: 10 12 8
Tri: 13 14 10
Tri: 0 4 1
Tri: 3 6 4
Tri: 5 9 6
Tri: 4 8 7
Tri: 6 10 8
Tri: 9 13 10
Tri: 8 12 11
Tri: 10 14 12
Tri: 13 15 14

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +0,0 @@
===Vertices (Split by UV/Normal): 41
V 0: Pos(0.02, 0.15, 0.0) Norm(0.577338, 0.577356, -0.577357) UV(0.77503, 0.750033)
V 1: Pos(-0.02, -0.15, 0.0) Norm(-0.577338, -0.577356, -0.577357) UV(0.808365, 0.500022)
V 2: Pos(-0.02, 0.15, 0.0) Norm(-0.577347, 0.577357, -0.577347) UV(0.808365, 0.750033)
V 3: Pos(0.02, -0.15, 0.6) Norm(0.577338, -0.577356, 0.577357) UV(0.866701, 0.496276)
V 4: Pos(-0.02, 0.15, 0.6) Norm(-0.577338, 0.577356, 0.577357) UV(0.833366, 0.746287)
V 5: Pos(-0.02, -0.15, 0.6) Norm(-0.577347, -0.577357, 0.577347) UV(0.833366, 0.496276)
V 6: Pos(0.02, -0.15, 0.0) Norm(0.577347, -0.577357, -0.577347) UV(0.833366, 0.496276)
V 7: Pos(-0.02, -0.15, 0.6) Norm(-0.577347, -0.577357, 0.577347) UV(0.866701, 0.0)
V 8: Pos(-0.02, -0.15, 0.0) Norm(-0.577338, -0.577356, -0.577357) UV(0.866701, 0.496276)
V 9: Pos(0.02, 0.15, 0.0) Norm(0.577338, 0.577356, -0.577357) UV(0.500022, 0.5)
V 10: Pos(0.02, -0.15, 0.6) Norm(0.577338, -0.577356, 0.577357) UV(0.750033, 0.0)
V 11: Pos(0.02, -0.15, 0.0) Norm(0.577347, -0.577357, -0.577347) UV(0.750033, 0.5)
V 12: Pos(-0.02, 0.15, 0.0) Norm(-0.577347, 0.577357, -0.577347) UV(0.77503, 0.500022)
V 13: Pos(0.02, 0.15, 0.6) Norm(0.577347, 0.577357, 0.577347) UV(0.808365, 0.0)
V 14: Pos(0.02, 0.15, 0.0) Norm(0.577338, 0.577356, -0.577357) UV(0.808365, 0.500022)
V 15: Pos(-0.02, -0.15, 0.0) Norm(-0.577338, -0.577356, -0.577357) UV(0.500022, 1.0)
V 16: Pos(-0.02, 0.15, 0.6) Norm(-0.577338, 0.577356, 0.577357) UV(0.750033, 0.5)
V 17: Pos(-0.02, 0.15, 0.0) Norm(-0.577347, 0.577357, -0.577347) UV(0.750033, 1.0)
V 18: Pos(0.015, 0.15, 0.6) Norm(0.583227, 0.576373, -0.572398) UV(0.8667, 0.250011)
V 19: Pos(-0.015, -0.15, 0.6) Norm(-0.570792, -0.648582, -0.503526) UV(0.891702, 0.0)
V 20: Pos(-0.015, 0.15, 0.6) Norm(-0.583229, 0.576375, -0.572394) UV(0.891702, 0.250011)
V 21: Pos(0.0, 0.05, 1.4) Norm(0.0, -0.417006, 0.908904) UV(0.166674, 0.0)
V 22: Pos(-0.015, 0.15, 0.6) Norm(-0.583229, 0.576375, -0.572394) UV(0.250011, 0.666784)
V 23: Pos(-0.015, -0.15, 0.6) Norm(-0.570792, -0.648582, -0.503526) UV(0.0, 0.666784)
V 24: Pos(0.015, 0.15, 0.6) Norm(0.583227, 0.576373, -0.572398) UV(0.250011, 0.666784)
V 25: Pos(0.0, 0.05, 1.4) Norm(0.0, -0.417006, 0.908904) UV(0.333348, 0.0)
V 26: Pos(0.015, -0.15, 0.6) Norm(0.570794, -0.648585, -0.50352) UV(0.500022, 0.666784)
V 27: Pos(-0.015, -0.15, 0.6) Norm(-0.570792, -0.648582, -0.503526) UV(0.77503, 0.68218)
V 28: Pos(0.015, -0.15, 0.6) Norm(0.570794, -0.648585, -0.50352) UV(0.750033, 0.681722)
V 29: Pos(0.0, 0.05, 1.4) Norm(0.0, -0.417006, 0.908904) UV(0.77503, 0.0)
V 30: Pos(-0.015, 0.15, 0.6) Norm(-0.583229, 0.576375, -0.572394) UV(0.808365, 0.666696)
V 31: Pos(0.0, 0.15, 1.4) Norm(0.0, 0.536928, 0.843628) UV(0.820865, 0.0)
V 32: Pos(0.015, 0.15, 0.6) Norm(0.583227, 0.576373, -0.572398) UV(0.833366, 0.666696)
V 33: Pos(0.02, -0.15, 0.0) Norm(0.577347, -0.577357, -0.577347) UV(0.77503, 0.500022)
V 34: Pos(0.02, 0.15, 0.6) Norm(0.577347, 0.577357, 0.577347) UV(0.866701, 0.746287)
V 35: Pos(0.02, -0.15, 0.6) Norm(0.577338, -0.577356, 0.577357) UV(0.833366, 0.0)
V 36: Pos(0.02, 0.15, 0.6) Norm(0.577347, 0.577357, 0.577347) UV(0.500022, 0.0)
V 37: Pos(-0.02, 0.15, 0.6) Norm(-0.577338, 0.577356, 0.577357) UV(0.77503, 0.0)
V 38: Pos(-0.02, -0.15, 0.6) Norm(-0.577347, -0.577357, 0.577347) UV(0.500022, 0.5)
V 39: Pos(0.015, -0.15, 0.6) Norm(0.570794, -0.648585, -0.50352) UV(0.8667, 0.0)
V 40: Pos(0.0, 0.15, 1.4) Norm(0.0, 0.536928, 0.843628) UV(0.250011, 0.0)
===Triangles (Indices): 20
Tri: 0 1 2
Tri: 3 4 5
Tri: 6 7 8
Tri: 9 10 11
Tri: 12 13 14
Tri: 15 16 17
Tri: 18 19 20
Tri: 21 22 23
Tri: 24 25 26
Tri: 27 28 29
Tri: 30 31 32
Tri: 0 33 1
Tri: 3 34 4
Tri: 6 35 7
Tri: 9 36 10
Tri: 12 37 13
Tri: 15 38 16
Tri: 18 39 19
Tri: 21 40 22
Tri: 24 40 25

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
resources/w/zombie002.anim (Stored with Git LFS)

Binary file not shown.

File diff suppressed because it is too large Load Diff

BIN
resources/w/zombie_idle001.anim (Stored with Git LFS)

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,103 +0,0 @@
#pragma once
#include "render/Renderer.h"
#include <unordered_map>
namespace ZL
{
constexpr int MAX_BONE_COUNT = 6;
constexpr int MAX_GPU_BONES = 64;
struct Bone
{
Vector3f boneStartWorld;
float boneLength;
Matrix4f boneMatrixWorld;
int parent;
std::vector<int> children;
};
struct BoneWeight
{
int boneIndex = -1;
float weight = 0;
};
struct AnimationKeyFrame
{
int frame;
std::vector<Bone> bones;
};
struct Animation
{
std::vector<AnimationKeyFrame> keyFrames;
};
struct GpuBoneData {
std::vector<Vector4f> boneIndices0; // bone indices 0-3 per vertex (as float)
std::vector<Vector2f> boneIndices1; // bone indices 4-5 per vertex
std::vector<Vector4f> boneWeights0; // bone weights 0-3 per vertex
std::vector<Vector2f> boneWeights1; // bone weights 4-5 per vertex
bool prepared = false;
void PrepareGpuSkinningData(const std::vector<std::array<BoneWeight, MAX_BONE_COUNT>>& verticesBoneWeight);
};
struct BoneSystem
{
VertexDataStruct mesh;
VertexDataStruct startMesh;
std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> verticesBoneWeight;
Matrix4f armatureMatrix;
std::vector<Bone> startBones;
std::vector<Bone> currentBones;
std::vector<std::string> boneNames;
std::vector<Animation> animations;
int startingFrame = 0;
void LoadFromFile(const std::string& fileName, const std::string& ZIPFileName = "");
void LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName = "");
void Interpolate(int frame);
int findBoneIndex(const std::string& name) const;
// GPU skinning: compute skinning matrices without modifying the mesh
//void ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const;
};
struct GpuSkinningShaderData {
GpuBoneData gpuBoneData;
// GPU skinning data (lazily initialized)
VertexRenderStruct bindPoseMutable;
std::shared_ptr<VBOHolder> boneIndices0VBO;
std::shared_ptr<VBOHolder> boneIndices1VBO;
std::shared_ptr<VBOHolder> boneWeights0VBO;
std::shared_ptr<VBOHolder> boneWeights1VBO;
bool gpuSkinningPrepared = false;
std::vector<Eigen::Matrix4f> skinningMatrices;
void prepareGpuSkinningVBOs(BoneSystem& model);
void RenderVBO(Renderer& renderer);
void ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, int frame);
};
struct BoneAnimationData {
BoneSystem model;
float currentFrame = 0.f;
int lastFrame = -1;
int totalFrames = 1;
GpuSkinningShaderData gpuSkinningShaderData;
};
};

View File

@ -13,7 +13,21 @@ namespace ZL
using std::max;
#endif
extern int getIndexByValue(const std::string& name, const std::vector<std::string>& words);
int getIndexByValue(const std::string& name, const std::vector<std::string>& words)
{
for (int i = 0; i < words.size(); i++)
{
if (words[i] == name)
{
return i;
}
}
std::cout << "Bone name not found: " << name << std::endl;
throw std::runtime_error("Bone name not found: " + name);
return -1;
}
static std::string trimRight(const std::string& s)
{
@ -23,6 +37,42 @@ namespace ZL
return s.substr(0, end);
}
void GpuBoneData::PrepareGpuSkinningData(const std::vector<std::array<BoneWeight, MAX_BONE_COUNT>>& verticesBoneWeight)
{
size_t vertexCount = verticesBoneWeight.size();
boneIndices0.resize(vertexCount);
boneIndices1.resize(vertexCount);
boneWeights0.resize(vertexCount);
boneWeights1.resize(vertexCount);
for (size_t i = 0; i < vertexCount; i++)
{
boneIndices0[i] = Vector4f(
static_cast<float>(max(0, verticesBoneWeight[i][0].boneIndex)),
static_cast<float>(max(0, verticesBoneWeight[i][1].boneIndex)),
static_cast<float>(max(0, verticesBoneWeight[i][2].boneIndex)),
static_cast<float>(max(0, verticesBoneWeight[i][3].boneIndex))
);
boneIndices1[i] = Vector2f(
static_cast<float>(max(0, verticesBoneWeight[i][4].boneIndex)),
static_cast<float>(max(0, verticesBoneWeight[i][5].boneIndex))
);
boneWeights0[i] = Vector4f(
verticesBoneWeight[i][0].weight,
verticesBoneWeight[i][1].weight,
verticesBoneWeight[i][2].weight,
verticesBoneWeight[i][3].weight
);
boneWeights1[i] = Vector2f(
verticesBoneWeight[i][4].weight,
verticesBoneWeight[i][5].weight
);
}
prepared = true;
}
void BoneSystemNew::LoadFromFile(const std::string& fileName, const std::string& ZIPFileName)
{
std::ifstream filestream;

View File

@ -1,9 +1,48 @@
#pragma once
#include "BoneAnimatedModel.h"
#include "render/Renderer.h"
#include <unordered_map>
namespace ZL
{
constexpr int MAX_BONE_COUNT = 6;
constexpr int MAX_GPU_BONES = 64;
struct Bone
{
Eigen::Vector3f boneStartWorld;
float boneLength;
Eigen::Matrix4f boneMatrixWorld;
int parent;
std::vector<int> children;
};
struct BoneWeight
{
int boneIndex = -1;
float weight = 0;
};
struct AnimationKeyFrame
{
int frame;
std::vector<Bone> bones;
};
struct Animation
{
std::vector<AnimationKeyFrame> keyFrames;
};
struct GpuBoneData {
std::vector<Vector4f> boneIndices0; // bone indices 0-3 per vertex (as float)
std::vector<Vector2f> boneIndices1; // bone indices 4-5 per vertex
std::vector<Vector4f> boneWeights0; // bone weights 0-3 per vertex
std::vector<Vector2f> boneWeights1; // bone weights 4-5 per vertex
bool prepared = false;
void PrepareGpuSkinningData(const std::vector<std::array<BoneWeight, MAX_BONE_COUNT>>& verticesBoneWeight);
};
struct MeshBoneData
{
VertexDataStruct mesh;

View File

@ -78,6 +78,43 @@ void Character::setPathPlanner(PathPlanner planner) {
pathPlanner = std::move(planner);
}
bool Character::isMoving() const
{
Eigen::Vector3f toTarget = walkTarget - position;
toTarget.y() = 0.f;
return !pathWaypoints.empty() || toTarget.norm() > WALK_THRESHOLD;
}
Eigen::Vector3f Character::getCurrentNavigationTarget() const
{
if (!pathWaypoints.empty() && currentWaypointIndex < pathWaypoints.size()) {
return pathWaypoints[currentWaypointIndex];
}
return walkTarget;
}
void Character::forceReplan()
{
if (!pathPlanner) {
return;
}
const Eigen::Vector3f normalizedTarget(requestedWalkTarget.x(), 0.f, requestedWalkTarget.z());
pathWaypoints = pathPlanner(position, normalizedTarget);
currentWaypointIndex = 0;
if (!pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : pathWaypoints) {
waypoint.y() = 0.f;
}
walkTarget = pathWaypoints.back();
return;
}
walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z());
onArrivedCallback = nullptr;
}
void Character::setTexture(std::shared_ptr<ZL::Texture> texture)
{
for (auto& animEntry : animations) {
@ -90,15 +127,7 @@ void Character::setTexture(std::shared_ptr<ZL::Texture> texture)
void Character::setTexture(const std::string& meshName, std::shared_ptr<Texture> tex) {
meshTextures[meshName] = std::move(tex);
}
/*
void Character::setTexture(std::shared_ptr<Texture> tex) {
for (auto& animEntry : animations) {
for (const auto& name : animEntry.second.model.meshNamesOrdered) {
meshTextures[name] = tex;
}
}
}
*/
AnimationState Character::resolveActiveState() const {
if (animations.count(currentState)) return currentState;
@ -137,14 +166,6 @@ void Character::update(int64_t deltaMs) {
deltaMs = static_cast<int64_t>(static_cast<float>(deltaMs) * speedMultiplier);
}
//weaponInitialRotation = Eigen::AngleAxisf(x/180.0, Eigen::Vector3f::UnitZ()).toRotationMatrix();
//weaponInitialRotation = Eigen::AngleAxisf(-M_PI*0.5, Eigen::Vector3f::UnitZ()).toRotationMatrix();
//weaponInitialPosition = Eigen::Vector3f(0, 0.09, 0.016);
/*if (deltaMs > 200)
{
deltaMs = 200;
}*/
Eigen::Vector3f activeTarget;
Eigen::Vector3f lookTarget;
@ -336,14 +357,8 @@ void Character::update(int64_t deltaMs) {
resetAnim = false;
anim.currentFrame = 0;
}
//19
int prevFrame = anim.currentFrame;
anim.currentFrame += static_cast<float>(deltaMs) / 24.f;
/*
if (npcId == "ghost_01x")
{
std::cout << "Current animation frame: " << anim.currentFrame << " / " << anim.totalFrames << " -- " << anim.lastFrame << std::endl;
}*/
if (static_cast<int>(anim.currentFrame) >= 20 && currentState == AnimationState::STAND_TO_ACTION)
{
@ -399,25 +414,21 @@ void Character::update(int64_t deltaMs) {
if (currentState == AnimationState::STAND_TO_ACTION)
{
currentState = AnimationState::ACTION_IDLE;
//resetAnim = true;
}
if (currentState == AnimationState::ACTION_TO_STAND)
{
currentState = AnimationState::STAND;
//resetAnim = true;
}
if (currentState == AnimationState::ACTION_ATTACK)
{
currentState = AnimationState::ACTION_IDLE;
//resetAnim = true;
attack = 0;
}
if (currentState == AnimationState::ACTION_TO_DEATH)
{
currentState = AnimationState::DEATH_IDLE;
//resetAnim = true;
}
}

View File

@ -53,6 +53,16 @@ public:
void drawShadowDepth(Renderer& renderer);
void drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
// Character-to-character collision (XZ-plane). Used by Location to keep
// player/NPCs from walking through each other.
float collisionRadius = 0.45f;
// Movement/path introspection used for dynamic replanning.
bool isMoving() const;
const Eigen::Vector3f& getRequestedWalkTarget() const { return requestedWalkTarget; }
Eigen::Vector3f getCurrentNavigationTarget() const;
void forceReplan();
// attackDirection is a world-space horizontal vector pointing from the
// attacker toward this character — i.e. the direction the hit pushes

View File

@ -1,6 +1,5 @@
#include "Game.h"
#include "AnimatedModel.h"
#include "BoneAnimatedModel.h"
#include "utils/Utils.h"
#include "render/OpenGlExtensions.h"
#include <iostream>
@ -662,16 +661,16 @@ namespace ZL
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
switch (event.key.keysym.sym) {
case SDLK_1:
if (audioPlayer) audioPlayer->playSoundAsync("audio/background.wav");
//if (audioPlayer) audioPlayer->playSoundAsync("audio/background.wav");
break;
case SDLK_2:
if (audioPlayer) audioPlayer->playMusicAsync("audio/lullaby-music-vol20-186394--online-audio-convert.com.ogg");
//if (audioPlayer) audioPlayer->playMusicAsync("audio/lullaby-music-vol20-186394--online-audio-convert.com.ogg");
break;
case SDLK_3:
if (audioPlayer) audioPlayer->stopMusicAsync();
//if (audioPlayer) audioPlayer->stopMusicAsync();
break;
case SDLK_f:
currentLocation->dialogueSystem.startDialogue("test_choice_dialogue");
//currentLocation->dialogueSystem.startDialogue("test_choice_dialogue");
break;
case SDLK_e:

View File

@ -1,6 +1,5 @@
#pragma once
#include "Character.h"
#include "BoneAnimatedModel.h"
#include "render/Renderer.h"
#include "Environment.h"
#include "render/TextureManager.h"
@ -107,9 +106,6 @@ namespace ZL {
void updatePinchZoom();
void endPinch();
int countNonUiPointers() const;
void handleDown(int64_t fingerId, int mx, int my);
void handleUp(int64_t fingerId, int mx, int my);
void handleMotion(int64_t fingerId, int mx, int my);
void setupQuestJournalUi();
void toggleQuestJournal();

View File

@ -23,6 +23,23 @@ namespace ZL
// on_npc_interact callback fires and the conversation begins.
static constexpr float NPC_TALK_DISTANCE = 1.25f;
static float distancePointToSegmentXZ(const Eigen::Vector3f& p,
const Eigen::Vector3f& a,
const Eigen::Vector3f& b)
{
const Eigen::Vector2f p2(p.x(), p.z());
const Eigen::Vector2f a2(a.x(), a.z());
const Eigen::Vector2f b2(b.x(), b.z());
const Eigen::Vector2f ab = b2 - a2;
const float abLenSq = ab.squaredNorm();
if (abLenSq <= 1e-8f) {
return (p2 - a2).norm();
}
const float t = std::clamp((p2 - a2).dot(ab) / abLenSq, 0.0f, 1.0f);
const Eigen::Vector2f closest = a2 + ab * t;
return (p2 - closest).norm();
}
Location::Location(Renderer& iRenderer, Inventory& iInventory)
: renderer(iRenderer)
, inventory(iInventory)
@ -181,38 +198,51 @@ namespace ZL
void Location::setupNavigation(const std::string& navigationJsonPath)
{
std::vector<PathFinder::ObstacleMesh> obstacles;
obstacles.reserve(gameObjects.size() + interactiveObjects.size());
for (const auto& item : gameObjects) {
const LoadedGameObject& gameObj = item.second;
obstacles.push_back({ &gameObj.mesh.data, Eigen::Vector3f::Zero() });
}
for (const InteractiveObject& intObj : interactiveObjects) {
if (!intObj.isActive) {
continue;
}
obstacles.push_back({ &intObj.mesh.data, intObj.position });
}
navigation.build(obstacles, navigationJsonPath, CONST_ZIP_FILE);
// Static navigation blockers are defined in the navigation JSON as polygons.
// NPC + player are handled as dynamic obstacles, so they are intentionally NOT in JSON.
navigation.build({}, navigationJsonPath, CONST_ZIP_FILE);
#ifdef SHOW_PATH
buildDebugNavMeshes();
#endif
auto planner = [this](const Eigen::Vector3f& start, const Eigen::Vector3f& end) {
return navigation.findPath(start, end);
static constexpr float kDynamicObstacleInfluenceDist = 6.0f;
auto makePlanner = [this](const Character* self) {
return [this, self](const Eigen::Vector3f& start, const Eigen::Vector3f& end) {
std::vector<PathFinder::DynamicObstacle> dynamicObstacles;
dynamicObstacles.reserve(npcs.size() + 1);
const auto addCharacter = [&](const Character* other) {
if (!other || other == self) return;
if (other->hp <= 0.f) return;
if (distancePointToSegmentXZ(other->position, start, end) > kDynamicObstacleInfluenceDist) {
return;
}
PathFinder::DynamicObstacle obs;
obs.position = Eigen::Vector3f(other->position.x(), navigation.getFloorY(), other->position.z());
obs.radius = (std::max)(0.0f, other->collisionRadius);
dynamicObstacles.push_back(obs);
};
addCharacter(player.get());
for (const auto& npc : npcs) {
addCharacter(npc.get());
}
return navigation.findPath(start, end, dynamicObstacles);
};
};
if (player) {
player->setPathPlanner(planner);
player->setPathPlanner(makePlanner(player.get()));
}
for (auto& npc : npcs) {
if (npc) {
npc->setPathPlanner(planner);
npc->setPathPlanner(makePlanner(npc.get()));
}
}
}
@ -641,6 +671,156 @@ namespace ZL
return navigation.setAreaAvailable(areaName, available);
}
void Location::resolveCharacterCollisions()
{
std::vector<Character*> characters;
characters.reserve(npcs.size() + 1);
if (player) {
characters.push_back(player.get());
}
for (auto& npc : npcs) {
if (npc) {
characters.push_back(npc.get());
}
}
if (characters.size() < 2) {
return;
}
static constexpr int kIterations = 3;
static constexpr float kMinSeparationEps = 1e-4f;
for (int iter = 0; iter < kIterations; ++iter) {
for (size_t i = 0; i < characters.size(); ++i) {
for (size_t j = i + 1; j < characters.size(); ++j) {
Character* a = characters[i];
Character* b = characters[j];
if (!a || !b) continue;
if (a->hp <= 0.f || b->hp <= 0.f) continue;
const float minDist = a->collisionRadius + b->collisionRadius;
if (minDist <= 0.f) continue;
const Eigen::Vector2f pa(a->position.x(), a->position.z());
const Eigen::Vector2f pb(b->position.x(), b->position.z());
const Eigen::Vector2f delta = pb - pa;
const float dist = delta.norm();
if (dist >= minDist) {
continue;
}
Eigen::Vector2f normal(1.f, 0.f);
if (dist > kMinSeparationEps) {
normal = delta / dist;
}
const float penetration = (minDist - dist);
const float push = penetration * 0.5f;
Eigen::Vector3f newA = a->position;
Eigen::Vector3f newB = b->position;
newA.x() -= normal.x() * push;
newA.z() -= normal.y() * push;
newA.y() = 0.f;
newB.x() += normal.x() * push;
newB.z() += normal.y() * push;
newB.y() = 0.f;
if (navigation.isReady()) {
const bool aOk = navigation.isWalkable(newA);
const bool bOk = navigation.isWalkable(newB);
if (aOk && bOk) {
a->position = newA;
b->position = newB;
}
else if (aOk && !bOk) {
a->position = newA;
}
else if (!aOk && bOk) {
b->position = newB;
}
}
else {
a->position = newA;
b->position = newB;
}
}
}
}
}
void Location::updateDynamicReplans(int64_t deltaMs)
{
static constexpr float kMovedEps = 0.05f;
static constexpr float kReplanTriggerDist = 1.1f;
static constexpr int64_t kReplanCooldownMs = 300;
for (auto it = replanCooldownRemainingMs.begin(); it != replanCooldownRemainingMs.end();) {
it->second -= deltaMs;
if (it->second <= 0) {
it = replanCooldownRemainingMs.erase(it);
}
else {
++it;
}
}
std::vector<Character*> characters;
characters.reserve(npcs.size() + 1);
if (player) characters.push_back(player.get());
for (auto& npc : npcs) if (npc) characters.push_back(npc.get());
std::vector<Character*> movers;
movers.reserve(characters.size());
for (Character* c : characters) {
if (!c) continue;
auto it = lastCharacterPositions.find(c);
if (it == lastCharacterPositions.end()) {
lastCharacterPositions[c] = c->position;
continue;
}
const Eigen::Vector3f prev = it->second;
const float moved = (c->position - prev).norm();
it->second = c->position;
if (moved > kMovedEps) {
movers.push_back(c);
}
}
if (movers.empty()) {
return;
}
for (Character* mover : movers) {
if (!mover) continue;
for (Character* walker : characters) {
if (!walker || walker == mover) continue;
if (walker->hp <= 0.f) continue;
if (!walker->isMoving()) continue;
if (replanCooldownRemainingMs.find(walker) != replanCooldownRemainingMs.end()) {
continue;
}
const Eigen::Vector3f nextTarget = walker->getCurrentNavigationTarget();
const float distToSegment = distancePointToSegmentXZ(mover->position, walker->position, nextTarget);
if (distToSegment > kReplanTriggerDist) {
continue;
}
walker->forceReplan();
replanCooldownRemainingMs[walker] = kReplanCooldownMs;
}
}
}
void Location::update(int64_t delta)
{
if (player) {
@ -652,6 +832,9 @@ namespace ZL
npc->update(delta);
}
resolveCharacterCollisions();
updateDynamicReplans(delta);
// Check if player reached target interactive object
if (targetInteractiveObject && player) {
float distToObject = (player->position - targetInteractiveObject->position).norm();

View File

@ -13,6 +13,8 @@
#include "dialogue/DialogueSystem.h"
#include "SparkEmitter.h"
#include <functional>
#include <cstdint>
#include <unordered_map>
namespace ZL
{
@ -112,6 +114,13 @@ namespace ZL
protected:
Renderer& renderer;
Inventory& inventory;
private:
void resolveCharacterCollisions();
void updateDynamicReplans(int64_t deltaMs);
std::unordered_map<Character*, Eigen::Vector3f> lastCharacterPositions;
std::unordered_map<Character*, int64_t> replanCooldownRemainingMs;
};
} // namespace ZL

View File

@ -10,219 +10,6 @@
namespace ZL
{
VertexDataStruct LoadFromTextFile(const std::string& fileName, const std::string& ZIPFileName)
{
VertexDataStruct result;
std::ifstream filestream;
std::istringstream zipStream;
if (!ZIPFileName.empty())
{
std::vector<char> fileData = readFileFromZIP(fileName, ZIPFileName);
std::string fileContents(fileData.begin(), fileData.end());
zipStream.str(fileContents);
}
else
{
filestream.open(fileName);
}
// Создаем ссылку f на нужный поток после этого код ниже остается без изменений
std::istream& f = (!ZIPFileName.empty()) ? static_cast<std::istream&>(zipStream) : static_cast<std::istream&>(filestream);
//Skip first 5 lines
std::string tempLine;
std::getline(f, tempLine);
static const std::regex pattern_count(R"(\d+)");
static const std::regex pattern_float(R"([-]?\d+\.\d+)");
static const std::regex pattern_int(R"([-]?\d+)");
std::smatch match;
int numberVertices;
if (std::regex_search(tempLine, match, pattern_count)) {
std::string number_str = match.str();
numberVertices = std::stoi(number_str);
}
else {
std::cout << "No number found in the input string: " << tempLine << std::endl;
throw std::runtime_error("No number found in the input string.");
}
std::vector<Vector3f> vertices;
vertices.resize(numberVertices);
for (int i = 0; i < numberVertices; i++)
{
std::getline(f, tempLine);
std::vector<float> floatValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
vertices[i] = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
}
std::cout << "UV Coordinates" << std::endl;
std::getline(f, tempLine); //===UV Coordinates:
std::getline(f, tempLine); //triangle count
int numberTriangles;
if (std::regex_search(tempLine, match, pattern_count)) {
std::string number_str = match.str();
numberTriangles = std::stoi(number_str);
}
else {
std::cout << "-No number found in the input string: " << tempLine << std::endl;
throw std::runtime_error("-No number found in the input string.");
}
// Now process UVs
std::vector<std::array<Vector2f, 3>> uvCoords;
uvCoords.resize(numberTriangles);
for (int i = 0; i < numberTriangles; i++)
{
std::getline(f, tempLine); //Face 0
int uvCount;
std::getline(f, tempLine);
if (std::regex_search(tempLine, match, pattern_count)) {
std::string number_str = match.str();
uvCount = std::stoi(number_str);
}
else {
std::cout << "2 No number found in the input string: " << tempLine << std::endl;
throw std::runtime_error("2 No number found in the input string.");
}
if (uvCount != 3)
{
std::cout << "UV count is not 3 for triangle " << i << ": " << uvCount << std::endl;
throw std::runtime_error("more than 3 uvs");
}
std::vector<float> floatValues;
for (int j = 0; j < 3; j++)
{
std::getline(f, tempLine); //UV <Vector (-0.3661, -1.1665)>
auto b = tempLine.cbegin();
auto e = tempLine.cend();
floatValues.clear();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
if (floatValues.size() != 2)
{
std::cout << "UV count is not 2: " << j << " " << floatValues.size() << std::endl;
throw std::runtime_error("more than 2 uvs---");
}
uvCoords[i][j] = Vector2f{ floatValues[0],floatValues[1] };
}
}
//std::cout << "Normals go" << std::endl;
std::getline(f, tempLine); //===Normals:
std::vector<Vector3f> normals;
normals.resize(numberVertices);
for (int i = 0; i < numberVertices; i++)
{
std::getline(f, tempLine);
std::vector<float> floatValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_float)) {
floatValues.push_back(std::stof(match.str()));
b = match.suffix().first;
}
normals[i] = Vector3f{ floatValues[0], floatValues[1], floatValues[2] };
}
//std::cout << "Triangles go:" << std::endl;
std::getline(f, tempLine); //===Triangles: 3974
std::vector<std::array<int, 3>> triangles;
triangles.resize(numberTriangles);
for (int i = 0; i < numberTriangles; i++)
{
std::getline(f, tempLine);
std::vector<int> intValues;
auto b = tempLine.cbegin();
auto e = tempLine.cend();
while (std::regex_search(b, e, match, pattern_int)) {
intValues.push_back(std::stoi(match.str()));
b = match.suffix().first;
}
triangles[i] = { intValues[0], intValues[1], intValues[2] };
}
std::cout << "Process vertices" << std::endl;
// Now let's process vertices
for (int i = 0; i < numberTriangles; i++)
{
result.PositionData.push_back(vertices[triangles[i][0]]);
result.PositionData.push_back(vertices[triangles[i][1]]);
result.PositionData.push_back(vertices[triangles[i][2]]);
result.TexCoordData.push_back(uvCoords[i][0]);
result.TexCoordData.push_back(uvCoords[i][1]);
result.TexCoordData.push_back(uvCoords[i][2]);
}
//Swap from Blender format to OpenGL format
for (int i = 0; i < result.PositionData.size(); i++)
{
Vector3f tempVec = result.PositionData[i];
result.PositionData[i](0) = tempVec(1);
result.PositionData[i](1) = tempVec(2);
result.PositionData[i](2) = tempVec(0);
}
return result;
}
VertexDataStruct LoadFromTextFile02(const std::string& fileName, const std::string& ZIPFileName)
{
VertexDataStruct result;

View File

@ -6,6 +6,5 @@
namespace ZL
{
VertexDataStruct LoadFromTextFile(const std::string& fileName, const std::string& ZIPFileName = "");
VertexDataStruct LoadFromTextFile02(const std::string& fileName, const std::string& ZIPFileName = "");
}

View File

@ -205,10 +205,6 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
nameRenderer->drawText(model.speaker, nameX, nameY, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
}
// const std::string wrappedBody = wrapText(model.visibleText, 90);
// bodyRenderer->drawText(wrappedBody, bodyX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f });
// const std::string wrappedBody = wrapText(model.visibleText, 56);
// bodyRenderer->drawText(wrappedBody, bodyX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f });
const float bodyTextScale = 1.0f;
const float bodyMaxWidthPx = textboxRect.w - 48.0f;

View File

@ -7,14 +7,9 @@
#include "external/nlohmann/json.hpp"
#include "TextModel.h"
#include "InteractiveObject.h"
//#include "Character.h"
//#include "render/TextureManager.h"
namespace ZL {
//struct Texture;
//struct VertexRenderStruct;
//class Renderer;
class Character;
struct GameObjectData {

View File

@ -38,6 +38,52 @@ namespace {
return polygon;
}
float distancePointToSegment2D(const Eigen::Vector2f& p,
const Eigen::Vector2f& a,
const Eigen::Vector2f& b)
{
const Eigen::Vector2f ab = b - a;
const float abLenSq = ab.squaredNorm();
if (abLenSq <= 1e-8f) {
return (p - a).norm();
}
float t = (p - a).dot(ab) / abLenSq;
t = (std::max)(0.0f, (std::min)(1.0f, t));
const Eigen::Vector2f proj = a + t * ab;
return (p - proj).norm();
}
Eigen::Vector2f closestPointOnSegment2D(const Eigen::Vector2f& p,
const Eigen::Vector2f& a,
const Eigen::Vector2f& b)
{
const Eigen::Vector2f ab = b - a;
const float abLenSq = ab.squaredNorm();
if (abLenSq <= 1e-8f) {
return a;
}
float t = (p - a).dot(ab) / abLenSq;
t = (std::max)(0.0f, (std::min)(1.0f, t));
return a + t * ab;
}
float distancePointToPolygonEdges(const Eigen::Vector2f& p,
const std::vector<Eigen::Vector2f>& polygon)
{
if (polygon.size() < 2) {
return (std::numeric_limits<float>::max)();
}
float best = (std::numeric_limits<float>::max)();
for (size_t i = 0; i < polygon.size(); ++i) {
const Eigen::Vector2f& a = polygon[i];
const Eigen::Vector2f& b = polygon[(i + 1) % polygon.size()];
best = (std::min)(best, distancePointToSegment2D(p, a, b));
}
return best;
}
}
void PathFinder::build(const std::vector<ObstacleMesh>& obstacleMeshes,
@ -74,8 +120,8 @@ std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
Cell startCell;
Cell endCell;
if (!findNearestWalkableCell(start, startCell) ||
!findNearestWalkableCell(end, endCell)) {
if (!findNearestWalkableCell(start, startCell, walkable) ||
!findNearestWalkableCell(end, endCell, walkable)) {
return {};
}
@ -121,7 +167,7 @@ std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
for (const auto& offset : offsets) {
Cell next{ currentCell.x + offset[0], currentCell.z + offset[1] };
if (!isCellWalkable(next)) {
if (!isCellWalkable(next, walkable)) {
continue;
}
@ -129,7 +175,7 @@ std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
if (diagonal) {
Cell horizontal{ currentCell.x + offset[0], currentCell.z };
Cell vertical{ currentCell.x, currentCell.z + offset[1] };
if (!isCellWalkable(horizontal) || !isCellWalkable(vertical)) {
if (!isCellWalkable(horizontal, walkable) || !isCellWalkable(vertical, walkable)) {
continue;
}
}
@ -160,7 +206,161 @@ std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
}
}
std::reverse(cells.begin(), cells.end());
cells = smoothCells(cells);
cells = smoothCells(cells, walkable);
std::vector<Eigen::Vector3f> path;
path.reserve(cells.size());
for (const Cell& cell : cells) {
path.push_back(cellCenter(cell));
}
if (!path.empty() && (path.front() - Eigen::Vector3f(start.x(), floorY, start.z())).norm() < cellSize * 0.75f) {
path.erase(path.begin());
}
if (!path.empty()) {
Cell requestedEndCell;
if (worldToCell(end, requestedEndCell) &&
requestedEndCell.x == endCell.x &&
requestedEndCell.z == endCell.z) {
path.back() = Eigen::Vector3f(end.x(), floorY, end.z());
}
}
return path;
}
std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<DynamicObstacle>& dynamicObstacles) const
{
if (!ready || walkable.empty()) {
return {};
}
std::vector<unsigned char> walkableGrid = walkable;
if (!dynamicObstacles.empty()) {
for (const DynamicObstacle& obstacle : dynamicObstacles) {
const float radius = obstacle.radius + agentRadius;
if (radius <= 0.0f) continue;
const float minWorldX = obstacle.position.x() - radius;
const float maxWorldX = obstacle.position.x() + radius;
const float minWorldZ = obstacle.position.z() - radius;
const float maxWorldZ = obstacle.position.z() + radius;
const int minCellX = (std::max)(0, static_cast<int>(std::floor((minWorldX - minX) / cellSize)));
const int maxCellX = (std::min)(gridWidth - 1, static_cast<int>(std::floor((maxWorldX - minX) / cellSize)));
const int minCellZ = (std::max)(0, static_cast<int>(std::floor((minWorldZ - minZ) / cellSize)));
const int maxCellZ = (std::min)(gridDepth - 1, static_cast<int>(std::floor((maxWorldZ - minZ) / cellSize)));
const float radiusSq = radius * radius;
for (int z = minCellZ; z <= maxCellZ; ++z) {
for (int x = minCellX; x <= maxCellX; ++x) {
const Cell cell{ x, z };
const Eigen::Vector3f center = cellCenter(cell);
const float dx = center.x() - obstacle.position.x();
const float dz = center.z() - obstacle.position.z();
if (dx * dx + dz * dz <= radiusSq) {
const int idx = indexOf(cell);
if (idx >= 0 && idx < static_cast<int>(walkableGrid.size())) {
walkableGrid[static_cast<size_t>(idx)] = 0;
}
}
}
}
}
}
Cell startCell;
Cell endCell;
if (!findNearestWalkableCell(start, startCell, walkableGrid) ||
!findNearestWalkableCell(end, endCell, walkableGrid)) {
return {};
}
if (startCell.x == endCell.x && startCell.z == endCell.z) {
return { cellCenter(endCell) };
}
struct QueueNode {
int index = 0;
float priority = 0.0f;
bool operator<(const QueueNode& other) const
{
return priority > other.priority;
}
};
const int cellCount = gridWidth * gridDepth;
std::vector<float> cost(static_cast<size_t>(cellCount), INF_COST);
std::vector<int> cameFrom(static_cast<size_t>(cellCount), -1);
std::priority_queue<QueueNode> open;
const int startIndex = indexOf(startCell);
const int endIndex = indexOf(endCell);
cost[static_cast<size_t>(startIndex)] = 0.0f;
open.push({ startIndex, 0.0f });
static const int offsets[8][2] = {
{ 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 },
{ 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 }
};
while (!open.empty()) {
const QueueNode current = open.top();
open.pop();
if (current.index == endIndex) {
break;
}
const Cell currentCell{ current.index % gridWidth, current.index / gridWidth };
for (const auto& offset : offsets) {
Cell next{ currentCell.x + offset[0], currentCell.z + offset[1] };
if (!isCellWalkable(next, walkableGrid)) {
continue;
}
const bool diagonal = offset[0] != 0 && offset[1] != 0;
if (diagonal) {
Cell horizontal{ currentCell.x + offset[0], currentCell.z };
Cell vertical{ currentCell.x, currentCell.z + offset[1] };
if (!isCellWalkable(horizontal, walkableGrid) || !isCellWalkable(vertical, walkableGrid)) {
continue;
}
}
const int nextIndex = indexOf(next);
const float stepCost = diagonal ? 1.41421356f : 1.0f;
const float newCost = cost[static_cast<size_t>(current.index)] + stepCost;
if (newCost >= cost[static_cast<size_t>(nextIndex)]) {
continue;
}
cost[static_cast<size_t>(nextIndex)] = newCost;
cameFrom[static_cast<size_t>(nextIndex)] = current.index;
const float priority = newCost + distanceCells(next, endCell);
open.push({ nextIndex, priority });
}
}
if (cameFrom[static_cast<size_t>(endIndex)] == -1) {
return {};
}
std::vector<Cell> cells;
for (int current = endIndex; current != -1; current = cameFrom[static_cast<size_t>(current)]) {
cells.push_back({ current % gridWidth, current / gridWidth });
if (current == startIndex) {
break;
}
}
std::reverse(cells.begin(), cells.end());
cells = smoothCells(cells, walkableGrid);
std::vector<Eigen::Vector3f> path;
path.reserve(cells.size());
@ -206,7 +406,7 @@ bool PathFinder::setAreaAvailable(const std::string& areaName, bool available)
bool PathFinder::isWalkable(const Eigen::Vector3f& point) const
{
Cell cell;
return worldToCell(point, cell) && isCellWalkable(cell);
return worldToCell(point, cell) && isCellWalkable(cell, walkable);
}
void PathFinder::loadConfig(const std::string& configPath, const std::string& zipPath)
@ -215,7 +415,9 @@ void PathFinder::loadConfig(const std::string& configPath, const std::string& zi
agentRadius = 0.45f;
floorY = 0.0f;
objectPadding = 0.25f;
boundaryPadding = 0.0f;
areas.clear();
obstaclePolygons.clear();
try {
std::string content;
@ -236,6 +438,7 @@ void PathFinder::loadConfig(const std::string& configPath, const std::string& zi
agentRadius = root.value("agentRadius", agentRadius);
floorY = root.value("floorY", floorY);
objectPadding = root.value("objectPadding", objectPadding);
boundaryPadding = root.value("boundaryPadding", boundaryPadding);
if (!root.contains("areas") || !root["areas"].is_array()) {
std::cerr << "[nav] Navigation config has no 'areas' array: " << configPath << "\n";
@ -259,6 +462,18 @@ void PathFinder::loadConfig(const std::string& configPath, const std::string& zi
areas.push_back(area);
}
if (root.contains("obstacles") && root["obstacles"].is_array()) {
for (const auto& item : root["obstacles"]) {
ObstaclePolygon obstacle;
obstacle.name = item.value("name", "");
obstacle.polygon = readPolygon(item);
if (obstacle.polygon.size() < 3) {
continue;
}
obstaclePolygons.push_back(std::move(obstacle));
}
}
} catch (const std::exception& e) {
std::cerr << "[nav] Failed to load config '" << configPath << "': " << e.what() << "\n";
}
@ -266,6 +481,7 @@ void PathFinder::loadConfig(const std::string& configPath, const std::string& zi
cellSize = (std::max)(cellSize, 0.1f);
agentRadius = (std::max)(agentRadius, 0.0f);
objectPadding = (std::max)(objectPadding, 0.0f);
boundaryPadding = (std::max)(boundaryPadding, 0.0f);
}
void PathFinder::resetGridBounds()
@ -304,6 +520,12 @@ void PathFinder::resetGridBounds()
}
}
for (const ObstaclePolygon& obstacle : obstaclePolygons) {
for (const Eigen::Vector2f& point : obstacle.polygon) {
includePoint(point.x(), point.y());
}
}
const float padding = cellSize * 2.0f + agentRadius + objectPadding;
minX -= padding;
minZ -= padding;
@ -319,20 +541,75 @@ void PathFinder::rebuildWalkableGrid()
walkable.assign(static_cast<size_t>(gridWidth * gridDepth), 0);
markAvailableAreasWalkable();
markObstacleMeshesBlocked();
markObstaclePolygonsBlocked();
}
void PathFinder::markAvailableAreasWalkable()
{
const auto insideAvailableArea = [this](const Eigen::Vector2f& point) {
for (const NavigationArea& area : areas) {
if (!area.available) {
continue;
}
if (pointInPolygon(point.x(), point.y(), area.polygon)) {
return true;
}
}
return false;
};
const auto externalBoundaryDistance = [this, &insideAvailableArea](const Eigen::Vector2f& point) {
if (boundaryPadding <= 0.0f) {
return (std::numeric_limits<float>::max)();
}
const float sampleOffset = (std::max)(cellSize * 0.25f, 0.05f);
float best = (std::numeric_limits<float>::max)();
for (const NavigationArea& area : areas) {
if (!area.available || area.polygon.size() < 2) {
continue;
}
for (size_t i = 0; i < area.polygon.size(); ++i) {
const Eigen::Vector2f& a = area.polygon[i];
const Eigen::Vector2f& b = area.polygon[(i + 1) % area.polygon.size()];
const Eigen::Vector2f edge = b - a;
if (edge.squaredNorm() <= 1e-8f) {
continue;
}
const Eigen::Vector2f closest = closestPointOnSegment2D(point, a, b);
Eigen::Vector2f normal(-edge.y(), edge.x());
normal.normalize();
const bool sideAInside = insideAvailableArea(closest + normal * sampleOffset);
const bool sideBInside = insideAvailableArea(closest - normal * sampleOffset);
if (sideAInside && sideBInside) {
continue;
}
best = (std::min)(best, distancePointToSegment2D(point, a, b));
}
}
return best;
};
for (int z = 0; z < gridDepth; ++z) {
for (int x = 0; x < gridWidth; ++x) {
const Cell cell{ x, z };
const Eigen::Vector3f center = cellCenter(cell);
const Eigen::Vector2f point(center.x(), center.z());
for (const NavigationArea& area : areas) {
if (!area.available) {
continue;
}
if (pointInPolygon(center.x(), center.z(), area.polygon)) {
if (boundaryPadding > 0.0f && externalBoundaryDistance(point) < boundaryPadding) {
continue;
}
walkable[static_cast<size_t>(indexOf(cell))] = 1;
break;
}
@ -341,8 +618,51 @@ void PathFinder::markAvailableAreasWalkable()
}
}
void PathFinder::markObstaclePolygonsBlocked()
{
if (obstaclePolygons.empty()) {
return;
}
const float padding = agentRadius + objectPadding;
for (int z = 0; z < gridDepth; ++z) {
for (int x = 0; x < gridWidth; ++x) {
const Cell cell{ x, z };
const int idx = indexOf(cell);
if (idx < 0 || idx >= static_cast<int>(walkable.size())) {
continue;
}
if (!walkable[static_cast<size_t>(idx)]) {
continue;
}
const Eigen::Vector3f center3 = cellCenter(cell);
const Eigen::Vector2f center(center3.x(), center3.z());
for (const ObstaclePolygon& obstacle : obstaclePolygons) {
if (pointInPolygon(center.x(), center.y(), obstacle.polygon)) {
walkable[static_cast<size_t>(idx)] = 0;
break;
}
if (padding > 0.0f &&
distancePointToPolygonEdges(center, obstacle.polygon) <= padding) {
walkable[static_cast<size_t>(idx)] = 0;
break;
}
}
}
}
}
void PathFinder::markObstacleMeshesBlocked()
{
if (!obstaclePolygons.empty()) {
// Prefer JSON-defined obstacle polygons over mesh-derived blockers.
// Mesh blockers tend to over-block (e.g. tree crowns) and are harder to author/tune.
return;
}
const float padding = agentRadius + objectPadding;
for (const ObstacleMesh& obstacle : obstacles) {
@ -406,7 +726,16 @@ bool PathFinder::isInsideGrid(const Cell& cell) const
bool PathFinder::isCellWalkable(const Cell& cell) const
{
return isInsideGrid(cell) && walkable[static_cast<size_t>(indexOf(cell))] != 0;
return isCellWalkable(cell, walkable);
}
bool PathFinder::isCellWalkable(const Cell& cell, const std::vector<unsigned char>& walkableGrid) const
{
const int idx = indexOf(cell);
return isInsideGrid(cell) &&
idx >= 0 &&
idx < static_cast<int>(walkableGrid.size()) &&
walkableGrid[static_cast<size_t>(idx)] != 0;
}
int PathFinder::indexOf(const Cell& cell) const
@ -415,6 +744,11 @@ int PathFinder::indexOf(const Cell& cell) const
}
bool PathFinder::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out) const
{
return findNearestWalkableCell(point, out, walkable);
}
bool PathFinder::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out, const std::vector<unsigned char>& walkableGrid) const
{
Cell origin;
if (!worldToCell(point, origin)) {
@ -424,7 +758,7 @@ bool PathFinder::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out
origin.z = (std::min)((std::max)(z, 0), gridDepth - 1);
}
if (isCellWalkable(origin)) {
if (isCellWalkable(origin, walkableGrid)) {
out = origin;
return true;
}
@ -438,7 +772,7 @@ bool PathFinder::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out
}
Cell candidate{ origin.x + dx, origin.z + dz };
if (isCellWalkable(candidate)) {
if (isCellWalkable(candidate, walkableGrid)) {
out = candidate;
return true;
}
@ -450,6 +784,11 @@ bool PathFinder::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out
}
bool PathFinder::hasLineOfSight(const Cell& from, const Cell& to) const
{
return hasLineOfSight(from, to, walkable);
}
bool PathFinder::hasLineOfSight(const Cell& from, const Cell& to, const std::vector<unsigned char>& walkableGrid) const
{
const Eigen::Vector3f a = cellCenter(from);
const Eigen::Vector3f b = cellCenter(to);
@ -460,7 +799,7 @@ bool PathFinder::hasLineOfSight(const Cell& from, const Cell& to) const
const float t = static_cast<float>(i) / static_cast<float>(steps);
const Eigen::Vector3f p = a * (1.0f - t) + b * t;
Cell cell;
if (!worldToCell(p, cell) || !isCellWalkable(cell)) {
if (!worldToCell(p, cell) || !isCellWalkable(cell, walkableGrid)) {
return false;
}
}
@ -469,6 +808,11 @@ bool PathFinder::hasLineOfSight(const Cell& from, const Cell& to) const
}
std::vector<PathFinder::Cell> PathFinder::smoothCells(const std::vector<Cell>& cells) const
{
return smoothCells(cells, walkable);
}
std::vector<PathFinder::Cell> PathFinder::smoothCells(const std::vector<Cell>& cells, const std::vector<unsigned char>& walkableGrid) const
{
if (cells.size() <= 2) {
return cells;
@ -480,7 +824,7 @@ std::vector<PathFinder::Cell> PathFinder::smoothCells(const std::vector<Cell>& c
while (anchor < cells.size() - 1) {
size_t next = cells.size() - 1;
while (next > anchor + 1 && !hasLineOfSight(cells[anchor], cells[next])) {
while (next > anchor + 1 && !hasLineOfSight(cells[anchor], cells[next], walkableGrid)) {
--next;
}

View File

@ -14,6 +14,16 @@ public:
Eigen::Vector3f offset = Eigen::Vector3f::Zero();
};
struct ObstaclePolygon {
std::string name;
std::vector<Eigen::Vector2f> polygon;
};
struct DynamicObstacle {
Eigen::Vector3f position = Eigen::Vector3f::Zero();
float radius = 0.0f;
};
struct Cell {
int x = 0;
int z = 0;
@ -32,6 +42,10 @@ public:
std::vector<Eigen::Vector3f> findPath(const Eigen::Vector3f& start,
const Eigen::Vector3f& end) const;
std::vector<Eigen::Vector3f> findPath(const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<DynamicObstacle>& dynamicObstacles) const;
bool setAreaAvailable(const std::string& areaName, bool available);
bool isReady() const { return ready; }
bool isWalkable(const Eigen::Vector3f& point) const;
@ -44,6 +58,7 @@ private:
float agentRadius = 0.45f;
float floorY = 0.0f;
float objectPadding = 0.25f;
float boundaryPadding = 0.0f;
float minX = 0.0f;
float minZ = 0.0f;
@ -54,6 +69,7 @@ private:
std::string loadedConfigPath;
std::string loadedZipPath;
std::vector<ObstacleMesh> obstacles;
std::vector<ObstaclePolygon> obstaclePolygons;
std::vector<unsigned char> walkable;
std::vector<NavigationArea> areas;
@ -62,6 +78,7 @@ private:
void rebuildWalkableGrid();
void markAvailableAreasWalkable();
void markObstacleMeshesBlocked();
void markObstaclePolygonsBlocked();
bool worldToCell(const Eigen::Vector3f& point, Cell& out) const;
Eigen::Vector3f cellCenter(const Cell& cell) const;
@ -74,6 +91,11 @@ private:
std::vector<Cell> smoothCells(const std::vector<Cell>& cells) const;
static bool pointInPolygon(float x, float z, const std::vector<Eigen::Vector2f>& polygon);
bool isCellWalkable(const Cell& cell, const std::vector<unsigned char>& walkableGrid) const;
bool findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out, const std::vector<unsigned char>& walkableGrid) const;
bool hasLineOfSight(const Cell& from, const Cell& to, const std::vector<unsigned char>& walkableGrid) const;
std::vector<Cell> smoothCells(const std::vector<Cell>& cells, const std::vector<unsigned char>& walkableGrid) const;
};
} // namespace ZL

View File

@ -9,10 +9,9 @@ namespace ZL {
GLuint fbo = 0;
GLuint textureID = 0;
int width, height;
bool useMipmaps; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD>
bool useMipmaps;
public:
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> false
FrameBuffer(int w, int h, bool useMipmaps = false);
~FrameBuffer();
@ -22,7 +21,6 @@ namespace ZL {
void Bind();
void Unbind();
// <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
void GenerateMipmaps();
GLuint getTextureID() const { return textureID; }

View File

@ -14,8 +14,6 @@ namespace ZL {
GLuint shaderProgram;
std::unordered_map<std::string, GLuint> uniformList;
//std::unordered_map<std::string, std::pair<bool, size_t>> UniformList;
std::map<std::string, GLuint> attribList;

View File

@ -298,34 +298,6 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
rowHeight = max(rowHeight, gb.height);
}
// // Проходим по стандартным ASCII символам
// for (unsigned char c = 32; c < 128; ++c) {
//
// FT_Load_Char(face, c, FT_LOAD_RENDER);
// TextureDataStruct glyphData;
// glyphData.width = face->glyph->bitmap.width;
// glyphData.height = face->glyph->bitmap.rows;
// glyphData.format = TextureDataStruct::R8;
// glyphData.mipmap = TextureDataStruct::NONE;
// // Копируем буфер FreeType в вектор данных
// size_t dataSize = glyphData.width * glyphData.height;
// glyphData.data.assign(face->glyph->bitmap.buffer, face->glyph->bitmap.buffer + dataSize);
// // Теперь создание текстуры — это одна строка!
// auto tex = std::make_shared<Texture>(glyphData);
//GlyphInfo g;
// g.texture = tex;
// g.size = Eigen::Vector2f((float)face->glyph->bitmap.width, (float)face->glyph->bitmap.rows);
// g.bearing = Eigen::Vector2f((float)face->glyph->bitmap_left, (float)face->glyph->bitmap_top);
// // Advance во FreeType измеряется в 1/64 пикселя
// g.advance = (unsigned int)face->glyph->advance.x;
// glyphs.emplace((char)c, g);
// }
// Создаём Texture из atlasData (R8)
TextureDataStruct atlasTex;
atlasTex.width = atlasWidth;
@ -466,12 +438,9 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
// Примечание: для текста лучше использовать GL_DYNAMIC_DRAW,
// но RefreshVBO сейчас жестко зашит на GL_STATIC_DRAW.
// Для UI это обычно не критично, если строк не тысячи.
// textMesh.AssignFrom(textData);
// 4. Рендеринг
r->shaderManager.PushShader(shaderName);
//r->PushMatrix();
//r->LoadIdentity();
// Матрица проекции — используем виртуальные проекционные размеры,
// чтобы координаты текста были независимы от физического разрешения экрана.
@ -498,24 +467,7 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//for (size_t i = 0; i < text.length(); ++i) {
// auto it = glyphs.find(text[i]);
// if (it == glyphs.end()) continue;
// glBindTexture(GL_TEXTURE_2D, it->second.texture->getTexID());
// // Отрисовываем по 6 вершин за раз
// // Нам нужно вручную биндить VBO, так как DrawVertexRenderStruct рисует всё сразу
// glBindBuffer(GL_ARRAY_BUFFER, textMesh.positionVBO->getBuffer());
// r->VertexAttribPointer3fv("vPosition", 0, (const char*)(i * 6 * sizeof(Vector3f)));
// glBindBuffer(GL_ARRAY_BUFFER, textMesh.texCoordVBO->getBuffer());
// r->VertexAttribPointer2fv("vTexCoord", 0, (const char*)(i * 6 * sizeof(Vector2f)));
// glDrawArrays(GL_TRIANGLES, 0, 6);
//}
r->DrawVertexRenderStruct(cached.mesh);
//r->PopMatrix();
glDisable(GL_BLEND);

View File

@ -12,7 +12,6 @@
namespace ZL {
struct GlyphInfo {
// std::shared_ptr<Texture> texture; // Texture for glyph
Eigen::Vector2f uv; // u,v координата левого верхнего угла в атласе (0..1)
Eigen::Vector2f uvSize; // ширина/высота в UV (0..1)
@ -42,10 +41,6 @@ private:
std::unordered_map<uint32_t, GlyphInfo> glyphs;
// OpenGL objects for a dynamic quad
//unsigned int vao = 0;
//unsigned int vbo = 0;
// единый атлас для всех глифов
std::shared_ptr<Texture> atlasTexture;
size_t atlasWidth = 0;

View File

@ -1,73 +0,0 @@
#include "Perlin.h"
#include <cmath>
#include <numeric>
#include <random>
#include <algorithm>
namespace ZL {
PerlinNoise::PerlinNoise() {
p.resize(256);
std::iota(p.begin(), p.end(), 0);
// Перемешиваем для случайности (можно задать seed)
std::default_random_engine engine(77777);
std::shuffle(p.begin(), p.end(), engine);
p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения
}
PerlinNoise::PerlinNoise(uint64_t seed) {
p.resize(256);
std::iota(p.begin(), p.end(), 0);
// Перемешиваем для случайности (используем переданный seed)
std::default_random_engine engine(static_cast<unsigned int>(seed));
std::shuffle(p.begin(), p.end(), engine);
p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения
}
float PerlinNoise::fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
float PerlinNoise::lerp(float t, float a, float b) { return a + t * (b - a); }
float PerlinNoise::grad(int hash, float x, float y, float z) {
int h = hash & 15;
float u = h < 8 ? x : y;
float v = h < 4 ? y : (h == 12 || h == 14 ? x : z);
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
float PerlinNoise::noise(float x, float y, float z) {
int X = (int)floor(x) & 255;
int Y = (int)floor(y) & 255;
int Z = (int)floor(z) & 255;
x -= floor(x);
y -= floor(y);
z -= floor(z);
float u = fade(x);
float v = fade(y);
float w = fade(z);
int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z;
int B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;
return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), grad(p[BA], x - 1, y, z)),
lerp(u, grad(p[AB], x, y - 1, z), grad(p[BB], x - 1, y - 1, z))),
lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)),
lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1))));
}
float PerlinNoise::getSurfaceHeight(Eigen::Vector3f pos, float noiseCoeff) {
// Частота шума (чем больше, тем больше "холмов")
float frequency = 7.0f;
// Получаем значение шума (обычно от -1 до 1)
float noiseValue = noise(pos(0) * frequency, pos(1) * frequency, pos(2) * frequency);
// Масштабируем: хотим отклонение от 1.0 до 1.1 (примерно)
float height = 1.0f + (noiseValue * noiseCoeff);
return height;
}
} // namespace ZL

View File

@ -1,24 +0,0 @@
#pragma once
#include <vector>
#include <cstdint>
#include <Eigen/Dense>
namespace ZL {
class PerlinNoise {
std::vector<int> p;
public:
PerlinNoise();
PerlinNoise(uint64_t seed);
float fade(float t);
float lerp(float t, float a, float b);
float grad(int hash, float x, float y, float z);
float noise(float x, float y, float z);
float getSurfaceHeight(Eigen::Vector3f pos, float noiseCoeff);
};
} // namespace ZL