merge
This commit is contained in:
commit
3d3c3b2a36
@ -36,41 +36,24 @@ add_executable(space-game001
|
|||||||
../src/utils/Utils.h
|
../src/utils/Utils.h
|
||||||
../src/SparkEmitter.cpp
|
../src/SparkEmitter.cpp
|
||||||
../src/SparkEmitter.h
|
../src/SparkEmitter.h
|
||||||
# ../src/planet/PlanetObject.cpp
|
|
||||||
# ../src/planet/PlanetObject.h
|
|
||||||
# ../src/planet/PlanetData.cpp
|
|
||||||
# ../src/planet/PlanetData.h
|
|
||||||
../src/utils/Perlin.cpp
|
../src/utils/Perlin.cpp
|
||||||
../src/utils/Perlin.h
|
../src/utils/Perlin.h
|
||||||
../src/utils/TaskManager.cpp
|
../src/utils/TaskManager.cpp
|
||||||
../src/utils/TaskManager.h
|
../src/utils/TaskManager.h
|
||||||
# ../src/planet/StoneObject.cpp
|
|
||||||
# ../src/planet/StoneObject.h
|
|
||||||
../src/render/FrameBuffer.cpp
|
../src/render/FrameBuffer.cpp
|
||||||
../src/render/FrameBuffer.h
|
../src/render/FrameBuffer.h
|
||||||
../src/render/ShadowMap.cpp
|
../src/render/ShadowMap.cpp
|
||||||
../src/render/ShadowMap.h
|
../src/render/ShadowMap.h
|
||||||
../src/UiManager.cpp
|
../src/UiManager.cpp
|
||||||
../src/UiManager.h
|
../src/UiManager.h
|
||||||
../src/Projectile.h
|
# ../src/Projectile.h
|
||||||
../src/Projectile.cpp
|
# ../src/Projectile.cpp
|
||||||
# ../src/network/NetworkInterface.h
|
|
||||||
# ../src/network/LocalClient.h
|
|
||||||
# ../src/network/LocalClient.cpp
|
|
||||||
# ../src/network/ClientState.h
|
|
||||||
# ../src/network/ClientState.cpp
|
|
||||||
# ../src/network/WebSocketClient.h
|
|
||||||
# ../src/network/WebSocketClient.cpp
|
|
||||||
# ../src/network/WebSocketClientBase.h
|
|
||||||
# ../src/network/WebSocketClientBase.cpp
|
|
||||||
# ../src/network/WebSocketClientEmscripten.h
|
|
||||||
# ../src/network/WebSocketClientEmscripten.cpp
|
|
||||||
../src/render/TextRenderer.h
|
../src/render/TextRenderer.h
|
||||||
../src/render/TextRenderer.cpp
|
../src/render/TextRenderer.cpp
|
||||||
../src/MenuManager.h
|
../src/MenuManager.h
|
||||||
../src/MenuManager.cpp
|
../src/MenuManager.cpp
|
||||||
# ../src/Space.h
|
../src/Location.h
|
||||||
# ../src/Space.cpp
|
../src/Location.cpp
|
||||||
../src/GameConstants.h
|
../src/GameConstants.h
|
||||||
../src/GameConstants.cpp
|
../src/GameConstants.cpp
|
||||||
../src/ScriptEngine.h
|
../src/ScriptEngine.h
|
||||||
@ -115,8 +98,6 @@ target_compile_definitions(space-game001 PRIVATE
|
|||||||
SDL_MAIN_HANDLED
|
SDL_MAIN_HANDLED
|
||||||
# DEBUG_LIGHT
|
# DEBUG_LIGHT
|
||||||
# SHOW_PATH
|
# SHOW_PATH
|
||||||
# NETWORK
|
|
||||||
# SIMPLIFIED
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Линкуем с SDL2main, если он вообще установлен
|
# Линкуем с SDL2main, если он вообще установлен
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
-- NPC PATROL WAYPOINTS
|
-- NPC PATROL WAYPOINTS
|
||||||
-- ============================================
|
-- ============================================
|
||||||
|
|
||||||
|
|
||||||
local function step3()
|
local function step3()
|
||||||
game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1)
|
game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1)
|
||||||
end
|
end
|
||||||
@ -32,7 +33,6 @@ end
|
|||||||
|
|
||||||
function on_npc_interact(npc_index)
|
function on_npc_interact(npc_index)
|
||||||
print("[Lua] NPC interaction! Index: " .. tostring(npc_index))
|
print("[Lua] NPC interaction! Index: " .. tostring(npc_index))
|
||||||
|
|
||||||
if npc_index == 1 then
|
if npc_index == 1 then
|
||||||
game_api.start_dialogue("test_line_dialogue")
|
game_api.start_dialogue("test_line_dialogue")
|
||||||
else
|
else
|
||||||
|
|||||||
@ -757,70 +757,6 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
prepared = true;
|
prepared = true;
|
||||||
|
|
||||||
/*
|
|
||||||
if (startBones.size() > MAX_GPU_BONES)
|
|
||||||
{
|
|
||||||
std::cout << "Warning: model has " << startBones.size()
|
|
||||||
<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void BoneSystem::ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const
|
|
||||||
{
|
|
||||||
int startingKeyFrame = -1;
|
|
||||||
for (size_t i = 0; i < animations[0].keyFrames.size() - 1; i++)
|
|
||||||
{
|
|
||||||
int oldFrame = animations[0].keyFrames[i].frame;
|
|
||||||
int nextFrame = animations[0].keyFrames[i + 1].frame;
|
|
||||||
if (frame >= oldFrame && frame < nextFrame)
|
|
||||||
{
|
|
||||||
startingKeyFrame = static_cast<int>(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startingKeyFrame == -1)
|
|
||||||
{
|
|
||||||
outMatrices.resize(startBones.size());
|
|
||||||
for (auto& m : outMatrices) m = Matrix4f::Identity();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int modifiedFrameNumber = frame - animations[0].keyFrames[startingKeyFrame].frame;
|
|
||||||
int diffFrames = animations[0].keyFrames[startingKeyFrame + 1].frame - animations[0].keyFrames[startingKeyFrame].frame;
|
|
||||||
float t = (modifiedFrameNumber + 0.f) / diffFrames;
|
|
||||||
|
|
||||||
const std::vector<Bone>& oneFrameBones = animations[0].keyFrames[startingKeyFrame].bones;
|
|
||||||
const std::vector<Bone>& nextFrameBones = animations[0].keyFrames[startingKeyFrame + 1].bones;
|
|
||||||
|
|
||||||
outMatrices.resize(startBones.size());
|
|
||||||
|
|
||||||
for (size_t i = 0; i < startBones.size(); i++)
|
|
||||||
{
|
|
||||||
Vector3f interpPos;
|
|
||||||
interpPos(0) = oneFrameBones[i].boneStartWorld(0) + t * (nextFrameBones[i].boneStartWorld(0) - oneFrameBones[i].boneStartWorld(0));
|
|
||||||
interpPos(1) = oneFrameBones[i].boneStartWorld(1) + t * (nextFrameBones[i].boneStartWorld(1) - oneFrameBones[i].boneStartWorld(1));
|
|
||||||
interpPos(2) = oneFrameBones[i].boneStartWorld(2) + t * (nextFrameBones[i].boneStartWorld(2) - oneFrameBones[i].boneStartWorld(2));
|
|
||||||
|
|
||||||
Matrix3f oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
|
|
||||||
Matrix3f nextFrameBonesMatrix = nextFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
|
|
||||||
|
|
||||||
Eigen::Quaternionf q1 = Eigen::Quaternionf(oneFrameBonesMatrix).normalized();
|
|
||||||
Eigen::Quaternionf q2 = Eigen::Quaternionf(nextFrameBonesMatrix).normalized();
|
|
||||||
Eigen::Quaternionf result = q1.slerp(t, q2);
|
|
||||||
|
|
||||||
Matrix3f boneMatrixWorld3 = result.toRotationMatrix();
|
|
||||||
|
|
||||||
Matrix4f currentBoneMatrixWorld4 = Eigen::Matrix4f::Identity();
|
|
||||||
currentBoneMatrixWorld4.block<3, 3>(0, 0) = boneMatrixWorld3;
|
|
||||||
currentBoneMatrixWorld4.block<3, 1>(0, 3) = interpPos;
|
|
||||||
|
|
||||||
Matrix4f startBoneMatrixWorld4 = animations[0].keyFrames[0].bones[i].boneMatrixWorld;
|
|
||||||
Matrix4f invertedStartBoneMatrixWorld4 = startBoneMatrixWorld4.inverse();
|
|
||||||
|
|
||||||
outMatrices[i] = currentBoneMatrixWorld4 * invertedStartBoneMatrixWorld4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BoneSystem::Interpolate(int frame)
|
void BoneSystem::Interpolate(int frame)
|
||||||
@ -897,13 +833,6 @@ namespace ZL
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*std::cout << "m=" << skinningMatrixForEachBone[5] << std::endl;
|
|
||||||
|
|
||||||
std::cout << "Start Mesh data " << std::endl;
|
|
||||||
std::cout << startMesh.PositionData[0] << std::endl;
|
|
||||||
std::cout << startMesh.PositionData[10] << std::endl;
|
|
||||||
std::cout << startMesh.PositionData[100] << std::endl;
|
|
||||||
*/
|
|
||||||
for (int i = 0; i < mesh.PositionData.size(); i++)
|
for (int i = 0; i < mesh.PositionData.size(); i++)
|
||||||
{
|
{
|
||||||
Vector4f originalPos = {
|
Vector4f originalPos = {
|
||||||
@ -926,30 +855,10 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
vMoved = true;
|
vMoved = true;
|
||||||
finalPos = finalPos + (skinningMatrixForEachBone[verticesBoneWeight[i][j].boneIndex] * originalPos) * verticesBoneWeight[i][j].weight;
|
finalPos = finalPos + (skinningMatrixForEachBone[verticesBoneWeight[i][j].boneIndex] * originalPos) * verticesBoneWeight[i][j].weight;
|
||||||
|
|
||||||
/*if (i == 100)
|
|
||||||
{
|
|
||||||
std::cout << "bone index=" << verticesBoneWeight[i][j].boneIndex << std::endl;
|
|
||||||
std::cout << "weight=" << verticesBoneWeight[i][j].weight << std::endl;
|
|
||||||
std::cout << "skinning matrix=" << skinningMatrixForEachBone[verticesBoneWeight[i][j].boneIndex] << std::endl;
|
|
||||||
std::cout << "original pos=" << originalPos.transpose() << std::endl;
|
|
||||||
std::cout << "final pos=" << finalPos.transpose() << std::endl;
|
|
||||||
std::cout << "-----------------" << std::endl;
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if (i == 100)
|
|
||||||
{
|
|
||||||
std::cout << originalPos << std::endl;
|
|
||||||
std::cout << finalPos << std::endl;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*if (abs(finalPos(0) - originalPos(0)) > 1 || abs(finalPos(1) - originalPos(1)) > 1 || abs(finalPos(2) - originalPos(2)) > 1)
|
|
||||||
{
|
|
||||||
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if (!vMoved)
|
if (!vMoved)
|
||||||
{
|
{
|
||||||
finalPos = originalPos;
|
finalPos = originalPos;
|
||||||
@ -962,5 +871,126 @@ namespace ZL
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GpuSkinningShaderData::prepareGpuSkinningVBOs(BoneSystem& model) {
|
||||||
|
if (gpuSkinningPrepared)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpuBoneData.PrepareGpuSkinningData(model.verticesBoneWeight);
|
||||||
|
|
||||||
|
// Upload bind-pose mesh (static, done once)
|
||||||
|
bindPoseMutable.AssignFrom(model.startMesh);
|
||||||
|
|
||||||
|
auto& gpu = gpuBoneData;
|
||||||
|
|
||||||
|
boneIndices0VBO = std::make_shared<VBOHolder>();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices0.size() * sizeof(Eigen::Vector4f),
|
||||||
|
gpu.boneIndices0.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
boneIndices1VBO = std::make_shared<VBOHolder>();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer());
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices1.size() * sizeof(Eigen::Vector2f),
|
||||||
|
gpu.boneIndices1.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
boneWeights0VBO = std::make_shared<VBOHolder>();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer());
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights0.size() * sizeof(Eigen::Vector4f),
|
||||||
|
gpu.boneWeights0.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
boneWeights1VBO = std::make_shared<VBOHolder>();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer());
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights1.size() * sizeof(Eigen::Vector2f),
|
||||||
|
gpu.boneWeights1.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
gpuSkinningPrepared = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuSkinningShaderData::RenderVBO(Renderer& renderer)
|
||||||
|
{
|
||||||
|
// Bind position and texcoord VBOs
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.positionVBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
|
||||||
|
|
||||||
|
if (bindPoseMutable.texCoordVBO) {
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.texCoordVBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind bone index VBOs
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
|
||||||
|
|
||||||
|
// Bind bone weight VBOs
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(bindPoseMutable.data.PositionData.size()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuSkinningShaderData::ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, int frame)
|
||||||
|
{
|
||||||
|
int startingKeyFrame = -1;
|
||||||
|
for (size_t i = 0; i < keyFrames.size() - 1; i++)
|
||||||
|
{
|
||||||
|
int oldFrame = keyFrames[i].frame;
|
||||||
|
int nextFrame = keyFrames[i + 1].frame;
|
||||||
|
if (frame >= oldFrame && frame < nextFrame)
|
||||||
|
{
|
||||||
|
startingKeyFrame = static_cast<int>(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
skinningMatrices.resize(startBones.size());
|
||||||
|
|
||||||
|
if (startingKeyFrame == -1)
|
||||||
|
{
|
||||||
|
for (auto& m : skinningMatrices) m = Matrix4f::Identity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int modifiedFrameNumber = frame - keyFrames[startingKeyFrame].frame;
|
||||||
|
int diffFrames = keyFrames[startingKeyFrame + 1].frame - keyFrames[startingKeyFrame].frame;
|
||||||
|
float t = (modifiedFrameNumber + 0.f) / diffFrames;
|
||||||
|
|
||||||
|
const std::vector<Bone>& oneFrameBones = keyFrames[startingKeyFrame].bones;
|
||||||
|
const std::vector<Bone>& nextFrameBones = keyFrames[startingKeyFrame + 1].bones;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < startBones.size(); i++)
|
||||||
|
{
|
||||||
|
Vector3f interpPos;
|
||||||
|
interpPos(0) = oneFrameBones[i].boneStartWorld(0) + t * (nextFrameBones[i].boneStartWorld(0) - oneFrameBones[i].boneStartWorld(0));
|
||||||
|
interpPos(1) = oneFrameBones[i].boneStartWorld(1) + t * (nextFrameBones[i].boneStartWorld(1) - oneFrameBones[i].boneStartWorld(1));
|
||||||
|
interpPos(2) = oneFrameBones[i].boneStartWorld(2) + t * (nextFrameBones[i].boneStartWorld(2) - oneFrameBones[i].boneStartWorld(2));
|
||||||
|
|
||||||
|
Matrix3f oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
|
||||||
|
Matrix3f nextFrameBonesMatrix = nextFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
|
||||||
|
|
||||||
|
Eigen::Quaternionf q1 = Eigen::Quaternionf(oneFrameBonesMatrix).normalized();
|
||||||
|
Eigen::Quaternionf q2 = Eigen::Quaternionf(nextFrameBonesMatrix).normalized();
|
||||||
|
Eigen::Quaternionf result = q1.slerp(t, q2);
|
||||||
|
|
||||||
|
Matrix3f boneMatrixWorld3 = result.toRotationMatrix();
|
||||||
|
|
||||||
|
Matrix4f currentBoneMatrixWorld4 = Eigen::Matrix4f::Identity();
|
||||||
|
currentBoneMatrixWorld4.block<3, 3>(0, 0) = boneMatrixWorld3;
|
||||||
|
currentBoneMatrixWorld4.block<3, 1>(0, 3) = interpPos;
|
||||||
|
|
||||||
|
Matrix4f startBoneMatrixWorld4 = keyFrames[0].bones[i].boneMatrixWorld;
|
||||||
|
Matrix4f invertedStartBoneMatrixWorld4 = startBoneMatrixWorld4.inverse();
|
||||||
|
|
||||||
|
skinningMatrices[i] = currentBoneMatrixWorld4 * invertedStartBoneMatrixWorld4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -12,8 +12,6 @@ namespace ZL
|
|||||||
Vector3f boneStartWorld;
|
Vector3f boneStartWorld;
|
||||||
float boneLength;
|
float boneLength;
|
||||||
Matrix4f boneMatrixWorld;
|
Matrix4f boneMatrixWorld;
|
||||||
// boneVector = boneLength * (0, 1, 0) <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
// Then multiply by boneMatrixWorld <20> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
|
|
||||||
int parent;
|
int parent;
|
||||||
std::vector<int> children;
|
std::vector<int> children;
|
||||||
@ -59,22 +57,18 @@ namespace ZL
|
|||||||
std::vector<Animation> animations;
|
std::vector<Animation> animations;
|
||||||
int startingFrame = 0;
|
int startingFrame = 0;
|
||||||
|
|
||||||
GpuBoneData gpuBoneData;
|
|
||||||
|
|
||||||
void LoadFromFile(const std::string& fileName, const std::string& ZIPFileName = "");
|
void LoadFromFile(const std::string& fileName, const std::string& ZIPFileName = "");
|
||||||
void LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName = "");
|
void LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName = "");
|
||||||
|
|
||||||
void Interpolate(int frame);
|
void Interpolate(int frame);
|
||||||
|
|
||||||
// GPU skinning: compute skinning matrices without modifying the mesh
|
// GPU skinning: compute skinning matrices without modifying the mesh
|
||||||
void ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const;
|
//void ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BoneAnimationData {
|
|
||||||
BoneSystem model;
|
struct GpuSkinningShaderData {
|
||||||
float currentFrame = 0.f;
|
GpuBoneData gpuBoneData;
|
||||||
int lastFrame = -1;
|
|
||||||
int totalFrames = 1;
|
|
||||||
|
|
||||||
// GPU skinning data (lazily initialized)
|
// GPU skinning data (lazily initialized)
|
||||||
VertexRenderStruct bindPoseMutable;
|
VertexRenderStruct bindPoseMutable;
|
||||||
@ -84,9 +78,23 @@ namespace ZL
|
|||||||
std::shared_ptr<VBOHolder> boneWeights1VBO;
|
std::shared_ptr<VBOHolder> boneWeights1VBO;
|
||||||
bool gpuSkinningPrepared = false;
|
bool gpuSkinningPrepared = false;
|
||||||
std::vector<Eigen::Matrix4f> skinningMatrices;
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -27,9 +27,6 @@ void Character::loadBinaryAnimation(AnimationState state, const std::string& fil
|
|||||||
data.totalFrames = 1;
|
data.totalFrames = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*void Character::setTexture(std::shared_ptr<Texture> tex) {
|
|
||||||
texture = tex;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
void Character::setTarget(const Eigen::Vector3f& target,
|
void Character::setTarget(const Eigen::Vector3f& target,
|
||||||
std::function<void()> onArrived) {
|
std::function<void()> onArrived) {
|
||||||
@ -259,7 +256,7 @@ void Character::update(int64_t deltaMs) {
|
|||||||
|
|
||||||
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
|
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
|
||||||
if (useGpuSkinning) {
|
if (useGpuSkinning) {
|
||||||
anim.model.ComputeSkinningMatrices(static_cast<int>(anim.currentFrame), anim.skinningMatrices);
|
anim.gpuSkinningShaderData.ComputeSkinningMatrices(anim.model.startBones, anim.model.animations[0].keyFrames, static_cast<int>(anim.currentFrame));
|
||||||
} else {
|
} else {
|
||||||
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
|
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
|
||||||
}
|
}
|
||||||
@ -301,43 +298,6 @@ void Character::draw(Renderer& renderer) {
|
|||||||
renderer.shaderManager.PopShader();
|
renderer.shaderManager.PopShader();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Character::prepareGpuSkinningVBOs(BoneAnimationData& anim) {
|
|
||||||
if (anim.gpuSkinningPrepared)
|
|
||||||
{
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
anim.model.gpuBoneData.PrepareGpuSkinningData(anim.model.verticesBoneWeight);
|
|
||||||
|
|
||||||
// Upload bind-pose mesh (static, done once)
|
|
||||||
anim.bindPoseMutable.AssignFrom(anim.model.startMesh);
|
|
||||||
|
|
||||||
auto& gpu = anim.model.gpuBoneData;
|
|
||||||
|
|
||||||
anim.boneIndices0VBO = std::make_shared<VBOHolder>();
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices0.size() * sizeof(Eigen::Vector4f),
|
|
||||||
gpu.boneIndices0.data(), GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
anim.boneIndices1VBO = std::make_shared<VBOHolder>();
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices1.size() * sizeof(Eigen::Vector2f),
|
|
||||||
gpu.boneIndices1.data(), GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
anim.boneWeights0VBO = std::make_shared<VBOHolder>();
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights0.size() * sizeof(Eigen::Vector4f),
|
|
||||||
gpu.boneWeights0.data(), GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
anim.boneWeights1VBO = std::make_shared<VBOHolder>();
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights1.size() * sizeof(Eigen::Vector2f),
|
|
||||||
gpu.boneWeights1.data(), GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
anim.gpuSkinningPrepared = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Character::drawGpuSkinning(Renderer& renderer) {
|
void Character::drawGpuSkinning(Renderer& renderer) {
|
||||||
AnimationState drawState = resolveActiveState();
|
AnimationState drawState = resolveActiveState();
|
||||||
auto it = animations.find(drawState);
|
auto it = animations.find(drawState);
|
||||||
@ -347,14 +307,15 @@ void Character::drawGpuSkinning(Renderer& renderer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto& anim = it->second;
|
auto& anim = it->second;
|
||||||
prepareGpuSkinningVBOs(anim);
|
anim.gpuSkinningShaderData.prepareGpuSkinningVBOs(anim.model);
|
||||||
|
|
||||||
if (anim.skinningMatrices.empty())
|
if (anim.gpuSkinningShaderData.skinningMatrices.empty())
|
||||||
{
|
{
|
||||||
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
|
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
|
||||||
anim.model.ComputeSkinningMatrices(
|
|
||||||
anim.model.animations[0].keyFrames[0].frame, anim.skinningMatrices);
|
anim.gpuSkinningShaderData.ComputeSkinningMatrices(anim.model.startBones, anim.model.animations[0].keyFrames, static_cast<int>(anim.currentFrame));
|
||||||
if (anim.skinningMatrices.empty()) return;
|
|
||||||
|
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) return;
|
||||||
}
|
}
|
||||||
static const std::string skinningShaderName = "skinning";
|
static const std::string skinningShaderName = "skinning";
|
||||||
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
||||||
@ -374,51 +335,56 @@ void Character::drawGpuSkinning(Renderer& renderer) {
|
|||||||
|
|
||||||
// Upload bone skinning matrices
|
// Upload bone skinning matrices
|
||||||
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
||||||
static_cast<int>(anim.skinningMatrices.size()), false,
|
static_cast<int>(anim.gpuSkinningShaderData.skinningMatrices.size()), false,
|
||||||
anim.skinningMatrices[0].data());
|
anim.gpuSkinningShaderData.skinningMatrices[0].data());
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||||
|
|
||||||
// Bind VAO (desktop only)
|
// Bind VAO (desktop only)
|
||||||
#ifndef EMSCRIPTEN
|
#ifndef EMSCRIPTEN
|
||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
if (anim.bindPoseMutable.vao) {
|
if (anim.gpuSkinningShaderData.bindPoseMutable.vao) {
|
||||||
glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
|
glBindVertexArray(anim.gpuSkinningShaderData.bindPoseMutable.vao->getBuffer());
|
||||||
renderer.shaderManager.EnableVertexAttribArrays();
|
renderer.shaderManager.EnableVertexAttribArrays();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Bind position and texcoord VBOs
|
anim.gpuSkinningShaderData.RenderVBO(renderer);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.positionVBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
|
|
||||||
|
|
||||||
if (anim.bindPoseMutable.texCoordVBO) {
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.texCoordVBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind bone index VBOs
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
|
|
||||||
|
|
||||||
// Bind bone weight VBOs
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
|
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(anim.bindPoseMutable.data.PositionData.size()));
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
renderer.PopProjectionMatrix();
|
renderer.PopProjectionMatrix();
|
||||||
renderer.shaderManager.PopShader();
|
renderer.shaderManager.PopShader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Character::prepareGpuSkinning()
|
||||||
|
{
|
||||||
|
AnimationState drawState = resolveActiveState();
|
||||||
|
auto it = animations.find(drawState);
|
||||||
|
if (it == animations.end())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& anim = it->second;
|
||||||
|
anim.gpuSkinningShaderData.prepareGpuSkinningVBOs(anim.model);
|
||||||
|
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) {
|
||||||
|
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
anim.gpuSkinningShaderData.ComputeSkinningMatrices(anim.model.startBones, anim.model.animations[0].keyFrames, static_cast<int>(anim.currentFrame));
|
||||||
|
|
||||||
|
if (anim.gpuSkinningShaderData.skinningMatrices.empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Shadow depth pass ====================
|
// ==================== Shadow depth pass ====================
|
||||||
|
|
||||||
void Character::drawShadowDepth(Renderer& renderer) {
|
void Character::drawShadowDepth(Renderer& renderer) {
|
||||||
@ -455,14 +421,7 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
|
|||||||
auto it = animations.find(drawState);
|
auto it = animations.find(drawState);
|
||||||
if (it == animations.end()) return;
|
if (it == animations.end()) return;
|
||||||
|
|
||||||
auto& anim = it->second;
|
if (!prepareGpuSkinning()) return;
|
||||||
prepareGpuSkinningVBOs(anim);
|
|
||||||
if (anim.skinningMatrices.empty()) {
|
|
||||||
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
|
|
||||||
anim.model.ComputeSkinningMatrices(
|
|
||||||
anim.model.animations[0].keyFrames[0].frame, anim.skinningMatrices);
|
|
||||||
if (anim.skinningMatrices.empty()) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const std::string shadowSkinningShader = "shadow_depth_skinning";
|
static const std::string shadowSkinningShader = "shadow_depth_skinning";
|
||||||
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
||||||
@ -477,36 +436,10 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
|
|||||||
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
|
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
|
||||||
|
|
||||||
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
||||||
static_cast<int>(anim.skinningMatrices.size()), false,
|
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
|
||||||
anim.skinningMatrices[0].data());
|
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
|
||||||
|
|
||||||
#ifndef EMSCRIPTEN
|
it->second.gpuSkinningShaderData.RenderVBO(renderer);
|
||||||
#ifndef __ANDROID__
|
|
||||||
if (anim.bindPoseMutable.vao) {
|
|
||||||
glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
|
|
||||||
renderer.shaderManager.EnableVertexAttribArrays();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.positionVBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
|
|
||||||
|
|
||||||
if (anim.bindPoseMutable.texCoordVBO) {
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.texCoordVBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
|
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(anim.bindPoseMutable.data.PositionData.size()));
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
renderer.shaderManager.PopShader();
|
renderer.shaderManager.PopShader();
|
||||||
@ -565,14 +498,7 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
|
|||||||
auto it = animations.find(drawState);
|
auto it = animations.find(drawState);
|
||||||
if (it == animations.end() || !texture) return;
|
if (it == animations.end() || !texture) return;
|
||||||
|
|
||||||
auto& anim = it->second;
|
if (!prepareGpuSkinning()) return;
|
||||||
prepareGpuSkinningVBOs(anim);
|
|
||||||
if (anim.skinningMatrices.empty()) {
|
|
||||||
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
|
|
||||||
anim.model.ComputeSkinningMatrices(
|
|
||||||
anim.model.animations[0].keyFrames[0].frame, anim.skinningMatrices);
|
|
||||||
if (anim.skinningMatrices.empty()) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const std::string skinningShadowShader = "skinning_shadow";
|
static const std::string skinningShadowShader = "skinning_shadow";
|
||||||
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
||||||
@ -598,44 +524,12 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
|
|||||||
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
|
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
|
||||||
|
|
||||||
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
||||||
static_cast<int>(anim.skinningMatrices.size()), false,
|
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
|
||||||
anim.skinningMatrices[0].data());
|
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||||
|
|
||||||
#ifndef EMSCRIPTEN
|
it->second.gpuSkinningShaderData.RenderVBO(renderer);
|
||||||
#ifndef __ANDROID__
|
|
||||||
if (anim.bindPoseMutable.vao) {
|
|
||||||
glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
|
|
||||||
renderer.shaderManager.EnableVertexAttribArrays();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.positionVBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
|
|
||||||
|
|
||||||
if (anim.bindPoseMutable.texCoordVBO) {
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.texCoordVBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind normals for diffuse lighting (skinning_shadow shader skins them)
|
|
||||||
if (anim.bindPoseMutable.normalVBO) {
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.normalVBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer3fv("vNormal", 0, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
|
|
||||||
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
|
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(anim.bindPoseMutable.data.PositionData.size()));
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
renderer.PopProjectionMatrix();
|
renderer.PopProjectionMatrix();
|
||||||
|
|||||||
@ -97,8 +97,8 @@ private:
|
|||||||
// if the requested state has no loaded animation.
|
// if the requested state has no loaded animation.
|
||||||
AnimationState resolveActiveState() const;
|
AnimationState resolveActiveState() const;
|
||||||
|
|
||||||
// GPU skinning: prepare per-animation VBOs (called lazily on first draw)
|
bool prepareGpuSkinning();
|
||||||
void prepareGpuSkinningVBOs(BoneAnimationData& anim);
|
|
||||||
// GPU skinning: draw using shader-based skinning
|
// GPU skinning: draw using shader-based skinning
|
||||||
void drawGpuSkinning(Renderer& renderer);
|
void drawGpuSkinning(Renderer& renderer);
|
||||||
// Shadow: draw into depth map (no texture, no projection push)
|
// Shadow: draw into depth map (no texture, no projection push)
|
||||||
|
|||||||
759
src/Game.cpp
759
src/Game.cpp
@ -1,7 +1,6 @@
|
|||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include "AnimatedModel.h"
|
#include "AnimatedModel.h"
|
||||||
#include "BoneAnimatedModel.h"
|
#include "BoneAnimatedModel.h"
|
||||||
#include "planet/PlanetData.h"
|
|
||||||
#include "utils/Utils.h"
|
#include "utils/Utils.h"
|
||||||
#include "render/OpenGlExtensions.h"
|
#include "render/OpenGlExtensions.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -144,111 +143,12 @@ namespace ZL
|
|||||||
|
|
||||||
std::cout << "Load resurces step 4" << std::endl;
|
std::cout << "Load resurces step 4" << std::endl;
|
||||||
|
|
||||||
roomTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE));
|
currentLocation = std::make_unique<Location>(renderer, inventory);
|
||||||
roomMesh.data = LoadFromTextFile02("resources/w/room001.txt", CONST_ZIP_FILE);
|
|
||||||
roomMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI*0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
currentLocation->setup();
|
||||||
roomMesh.RefreshVBO();
|
|
||||||
std::cout << "Load resurces step 5" << std::endl;
|
std::cout << "Load resurces step 5" << std::endl;
|
||||||
|
|
||||||
// Load static game objects
|
|
||||||
gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
|
|
||||||
|
|
||||||
// Load interactive objects
|
|
||||||
interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
|
|
||||||
|
|
||||||
auto violaTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));
|
|
||||||
|
|
||||||
// Player (Viola)
|
|
||||||
player = std::make_unique<Character>();
|
|
||||||
/*
|
|
||||||
player->loadAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.txt", CONST_ZIP_FILE);
|
|
||||||
player->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.txt", CONST_ZIP_FILE);
|
|
||||||
*/
|
|
||||||
player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE);
|
|
||||||
player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE);
|
|
||||||
player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE);
|
|
||||||
player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE);
|
|
||||||
player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE);
|
|
||||||
player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE);
|
|
||||||
|
|
||||||
player->setTexture(violaTexture);
|
|
||||||
player->walkSpeed = 3.0f;
|
|
||||||
player->rotationSpeed = 8.0f;
|
|
||||||
|
|
||||||
player->modelScale = 1.f;
|
|
||||||
player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
|
|
||||||
//Eigen::Quaternionf::Identity();
|
|
||||||
/*Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5f, Eigen::Vector3f::UnitX())) * */
|
|
||||||
/*Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitZ()));*/
|
|
||||||
|
|
||||||
player->canAttack = true;
|
|
||||||
player->isPlayer = true;
|
|
||||||
std::cout << "Load resurces step 9" << std::endl;
|
|
||||||
|
|
||||||
// Load NPCs from JSON
|
|
||||||
npcs = GameObjectLoader::loadAndCreateNpcs("resources/config2/npcs.json", CONST_ZIP_FILE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
auto defaultTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/default_skin001.png", CONST_ZIP_FILE));
|
|
||||||
|
|
||||||
auto npc01 = std::make_unique<Character>();
|
|
||||||
npc01->loadAnimation(AnimationState::STAND, "resources/w/default_idle002.txt", CONST_ZIP_FILE);
|
|
||||||
npc01->loadAnimation(AnimationState::WALK, "resources/w/default_walk001.txt", CONST_ZIP_FILE);
|
|
||||||
//npc01->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", CONST_ZIP_FILE);
|
|
||||||
//npc01->loadAnimation(AnimationState::WALK, "resources/walkviola_uv010.txt", CONST_ZIP_FILE);
|
|
||||||
npc01->setTexture(defaultTexture);
|
|
||||||
npc01->walkSpeed = 1.5f;
|
|
||||||
npc01->rotationSpeed = 8.0f;
|
|
||||||
npc01->modelScale = 0.01f;
|
|
||||||
//npc01->modelScale = 0.1f;
|
|
||||||
npc01->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 10" << std::endl;
|
|
||||||
npc01->position = Eigen::Vector3f(0.f, 0.f, -10.f);
|
|
||||||
npc01->setTarget(npc01->position);
|
|
||||||
npcs.push_back(std::move(npc01));
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
auto ghostTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE));
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 11" << std::endl;
|
|
||||||
auto npc02 = std::make_unique<Character>();
|
|
||||||
/*
|
|
||||||
npc02->loadAnimation(AnimationState::STAND, "resources/w/default_float001.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::WALK, "resources/w/default_float001.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
|
|
||||||
*/
|
|
||||||
npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE);
|
|
||||||
npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE);
|
|
||||||
npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE);
|
|
||||||
npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE);
|
|
||||||
npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
|
|
||||||
npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
|
|
||||||
//npc02->loadAnimation(AnimationState::STAND, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
|
|
||||||
//npc02->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", CONST_ZIP_FILE);
|
|
||||||
npc02->setTexture(ghostTexture);
|
|
||||||
npc02->walkSpeed = 1.5f;
|
|
||||||
npc02->rotationSpeed = 8.0f;
|
|
||||||
npc02->modelScale = 0.01f;
|
|
||||||
//npc02->modelScale = 0.1f;
|
|
||||||
npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
|
|
||||||
|
|
||||||
npc02->position = Eigen::Vector3f(0.f, 0.f, -20.f);
|
|
||||||
npc02->setTarget(npc02->position);
|
|
||||||
npc02->canAttack = true;
|
|
||||||
npc02->attackTarget = player.get();
|
|
||||||
|
|
||||||
npcs.push_back(std::move(npc02));
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 12" << std::endl;
|
std::cout << "Load resurces step 12" << std::endl;
|
||||||
|
|
||||||
// Shadow mapping shaders
|
// Shadow mapping shaders
|
||||||
@ -264,28 +164,7 @@ namespace ZL
|
|||||||
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Create shadow map (2048x2048, ortho size 40, near 0.1, far 100)
|
|
||||||
shadowMap = std::make_unique<ShadowMap>(2048, 40.0f, 0.1f, 100.0f);
|
|
||||||
shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f));
|
|
||||||
std::cout << "Shadow map initialized" << std::endl;
|
|
||||||
|
|
||||||
setupNavigation();
|
|
||||||
|
|
||||||
loadingCompleted = true;
|
|
||||||
|
|
||||||
dialogueSystem.init(renderer, CONST_ZIP_FILE);
|
|
||||||
dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
|
|
||||||
/*dialogueSystem.addTriggerZone({
|
|
||||||
"ghost_room_trigger",
|
|
||||||
"test_line_dialogue",
|
|
||||||
Eigen::Vector3f(0.0f, 0.0f, -8.5f),
|
|
||||||
2.0f,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
});*/
|
|
||||||
|
|
||||||
scriptEngine.init(this);
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 13" << std::endl;
|
std::cout << "Load resurces step 13" << std::endl;
|
||||||
|
|
||||||
// Load UI with inventory button
|
// Load UI with inventory button
|
||||||
@ -298,10 +177,25 @@ namespace ZL
|
|||||||
|
|
||||||
menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) {
|
menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) {
|
||||||
std::cout << "[UI] Inventory button clicked" << std::endl;
|
std::cout << "[UI] Inventory button clicked" << std::endl;
|
||||||
//scriptEngine.callItemPickupCallback("toggle_inventory");
|
this->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
|
||||||
scriptEngine.showInventory(this);
|
this->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
|
||||||
//scriptEngine
|
this->inventoryOpen = true;
|
||||||
//scriptEngine
|
|
||||||
|
// Update UI with current items
|
||||||
|
const auto& items = this->inventory.getItems();
|
||||||
|
std::string itemText;
|
||||||
|
|
||||||
|
if (items.empty()) {
|
||||||
|
itemText = "Inventory (Empty)";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
||||||
|
for (size_t i = 0; i < items.size(); ++i) {
|
||||||
|
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->menuManager.uiManager.setText("inventory_items_text", itemText);
|
||||||
});
|
});
|
||||||
|
|
||||||
menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
|
menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
|
||||||
@ -314,152 +208,9 @@ namespace ZL
|
|||||||
catch (const std::exception& e) {
|
catch (const std::exception& e) {
|
||||||
std::cerr << "Failed to load UI: " << e.what() << std::endl;
|
std::cerr << "Failed to load UI: " << e.what() << std::endl;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void Game::setupNavigation()
|
loadingCompleted = true;
|
||||||
{
|
|
||||||
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, "resources/config2/navigation.json", CONST_ZIP_FILE);
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
|
||||||
buildDebugNavMeshes();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto planner = [this](const Eigen::Vector3f& start, const Eigen::Vector3f& end) {
|
|
||||||
return navigation.findPath(start, end);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (player) {
|
|
||||||
player->setPathPlanner(planner);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& npc : npcs) {
|
|
||||||
if (npc) {
|
|
||||||
npc->setPathPlanner(planner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
|
||||||
void Game::buildDebugNavMeshes()
|
|
||||||
{
|
|
||||||
debugNavMeshes.clear();
|
|
||||||
const auto& areas = navigation.getAreas();
|
|
||||||
float y = navigation.getFloorY() + 0.02f;
|
|
||||||
Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
|
||||||
|
|
||||||
for (const auto& area : areas) {
|
|
||||||
if (area.polygon.size() < 3) continue;
|
|
||||||
VertexRenderStruct mesh;
|
|
||||||
mesh.data = CreatePolygonFloor(area.polygon, y, red);
|
|
||||||
mesh.RefreshVBO();
|
|
||||||
debugNavMeshes.push_back(std::move(mesh));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::drawDebugNavigation()
|
|
||||||
{
|
|
||||||
renderer.shaderManager.PushShader("defaultColor");
|
|
||||||
renderer.SetMatrix();
|
|
||||||
for (const auto& mesh : debugNavMeshes) {
|
|
||||||
renderer.DrawVertexRenderStruct(mesh);
|
|
||||||
}
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
renderer.SetMatrix();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
InteractiveObject* Game::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
|
|
||||||
if (interactiveObjects.empty()) {
|
|
||||||
std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
|
|
||||||
std::cout << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl;
|
|
||||||
std::cout << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
|
|
||||||
|
|
||||||
float closestDistance = FLT_MAX;
|
|
||||||
InteractiveObject* closestObject = nullptr;
|
|
||||||
|
|
||||||
for (auto& intObj : interactiveObjects) {
|
|
||||||
std::cout << "[RAYCAST] Checking object: " << intObj.name << " (active: " << intObj.isActive << ")" << std::endl;
|
|
||||||
|
|
||||||
if (!intObj.isActive) {
|
|
||||||
std::cout << "[RAYCAST] -> Object inactive, skipping" << std::endl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
|
|
||||||
<< intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
|
|
||||||
|
|
||||||
Eigen::Vector3f toObject = intObj.position - rayOrigin;
|
|
||||||
std::cout << "[RAYCAST] Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl;
|
|
||||||
|
|
||||||
float distanceAlongRay = toObject.dot(rayDir);
|
|
||||||
std::cout << "[RAYCAST] Distance along ray: " << distanceAlongRay << std::endl;
|
|
||||||
|
|
||||||
if (distanceAlongRay < 0.1f) {
|
|
||||||
std::cout << "[RAYCAST] -> Object behind camera, skipping" << std::endl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
|
|
||||||
float distToObject = (closestPointOnRay - intObj.position).norm();
|
|
||||||
|
|
||||||
std::cout << "[RAYCAST] Distance to object: " << distToObject
|
|
||||||
<< " (interaction radius: " << intObj.interactionRadius << ")" << std::endl;
|
|
||||||
|
|
||||||
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
|
|
||||||
std::cout << "[RAYCAST] *** HIT DETECTED! ***" << std::endl;
|
|
||||||
closestDistance = distanceAlongRay;
|
|
||||||
closestObject = &intObj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closestObject) {
|
|
||||||
std::cout << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->name << " ***" << std::endl;
|
|
||||||
} else {
|
|
||||||
std::cout << "[RAYCAST] No objects hit" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return closestObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
Character* Game::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) {
|
|
||||||
Character* closestNpc = nullptr;
|
|
||||||
float closestDist = maxDistance;
|
|
||||||
|
|
||||||
for (auto& npc : npcs) {
|
|
||||||
Eigen::Vector3f toNpc = npc->position - rayOrigin;
|
|
||||||
float distAlongRay = toNpc.dot(rayDir);
|
|
||||||
if (distAlongRay < 0.1f) continue;
|
|
||||||
|
|
||||||
Eigen::Vector3f closestPoint = rayOrigin + rayDir * distAlongRay;
|
|
||||||
float distToNpc = (closestPoint - npc->position).norm();
|
|
||||||
|
|
||||||
float radius = npc->modelScale * 50.0f;
|
|
||||||
|
|
||||||
if (distToNpc <= radius && distAlongRay < closestDist) {
|
|
||||||
closestDist = distAlongRay;
|
|
||||||
closestNpc = npc.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return closestNpc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::drawUI()
|
void Game::drawUI()
|
||||||
@ -474,7 +225,10 @@ namespace ZL
|
|||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
|
|
||||||
menuManager.uiManager.draw(renderer);
|
menuManager.uiManager.draw(renderer);
|
||||||
dialogueSystem.draw(renderer);
|
if (currentLocation)
|
||||||
|
{
|
||||||
|
currentLocation->dialogueSystem.draw(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
renderer.shaderManager.PopShader();
|
renderer.shaderManager.PopShader();
|
||||||
@ -484,222 +238,7 @@ namespace ZL
|
|||||||
|
|
||||||
CheckGlError();
|
CheckGlError();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::drawGame()
|
|
||||||
{
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
||||||
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
|
|
||||||
renderer.shaderManager.PushShader(defaultShaderName);
|
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
|
||||||
|
|
||||||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
|
||||||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
|
||||||
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
|
||||||
renderer.PushMatrix();
|
|
||||||
|
|
||||||
renderer.LoadIdentity();
|
|
||||||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
|
||||||
//renderer.TranslateMatrix({ 0, -6.f, 0 });
|
|
||||||
|
|
||||||
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
|
|
||||||
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
||||||
const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
|
|
||||||
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(roomMesh);
|
|
||||||
|
|
||||||
for (auto& [name, gameObj] : gameObjects) {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(gameObj.mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
glBindTexture(GL_TEXTURE_2D, fireboxTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(fireboxMesh);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, inaiTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(inaiMesh);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(benchMesh);
|
|
||||||
*/
|
|
||||||
for (auto& intObj : interactiveObjects) {
|
|
||||||
if (intObj.isActive) {
|
|
||||||
intObj.draw(renderer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player) player->draw(renderer);
|
|
||||||
for (auto& npc : npcs) npc->draw(renderer);
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
|
||||||
drawDebugNavigation();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
|
||||||
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::drawShadowDepthPass()
|
|
||||||
{
|
|
||||||
if (!shadowMap) return;
|
|
||||||
|
|
||||||
const Eigen::Vector3f& sceneCenter = player ? player->position : Eigen::Vector3f::Zero();
|
|
||||||
shadowMap->updateLightSpaceMatrix(sceneCenter);
|
|
||||||
|
|
||||||
shadowMap->bind();
|
|
||||||
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
glDepthFunc(GL_LESS);
|
|
||||||
|
|
||||||
// Use front-face culling during depth pass to reduce shadow acne on lit faces
|
|
||||||
//glCullFace(GL_FRONT);
|
|
||||||
glEnable(GL_CULL_FACE);
|
|
||||||
|
|
||||||
renderer.shaderManager.PushShader("shadow_depth");
|
|
||||||
|
|
||||||
// Set up light's orthographic projection
|
|
||||||
const Eigen::Matrix4f& lightProj = shadowMap->getLightProjectionMatrix();
|
|
||||||
const Eigen::Matrix4f& lightView = shadowMap->getLightViewMatrix();
|
|
||||||
|
|
||||||
// Push the light's projection matrix via the 6-param ortho overload won't
|
|
||||||
// match our pre-computed matrix. Instead, use the raw stack approach:
|
|
||||||
// push a dummy projection then overwrite via PushSpecialMatrix-style.
|
|
||||||
// Simpler: push ortho then push the light view as modelview.
|
|
||||||
renderer.PushProjectionMatrix(
|
|
||||||
-40.0f, 40.0f,
|
|
||||||
-40.0f, 40.0f,
|
|
||||||
0.1f, 100.0f);
|
|
||||||
|
|
||||||
const Eigen::Vector3f& lightDir = shadowMap->getLightDirection();
|
|
||||||
Eigen::Vector3f lightPos = sceneCenter - lightDir * 50.0f;
|
|
||||||
Eigen::Vector3f up(0.0f, 1.0f, 0.0f);
|
|
||||||
if (std::abs(lightDir.dot(up)) > 0.99f) {
|
|
||||||
up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the light view matrix and push it
|
|
||||||
renderer.PushSpecialMatrix(lightView);
|
|
||||||
|
|
||||||
// Draw static geometry
|
|
||||||
renderer.DrawVertexRenderStruct(roomMesh);
|
|
||||||
|
|
||||||
for (auto& [name, gameObj] : gameObjects) {
|
|
||||||
renderer.DrawVertexRenderStruct(gameObj.mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& intObj : interactiveObjects) {
|
|
||||||
if (intObj.isActive && intObj.texture) {
|
|
||||||
renderer.PushMatrix();
|
|
||||||
renderer.TranslateMatrix(intObj.position);
|
|
||||||
renderer.DrawVertexRenderStruct(intObj.mesh);
|
|
||||||
renderer.PopMatrix();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw characters (they handle their own skinning shader switch internally)
|
|
||||||
if (player) player->drawShadowDepth(renderer);
|
|
||||||
for (auto& npc : npcs) npc->drawShadowDepth(renderer);
|
|
||||||
|
|
||||||
renderer.PopMatrix(); // light view
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
|
|
||||||
//glCullFace(GL_BACK);
|
|
||||||
glDisable(GL_CULL_FACE);
|
|
||||||
|
|
||||||
shadowMap->unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::drawGameWithShadows()
|
|
||||||
{
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
||||||
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
#ifdef DEBUG_LIGHT
|
|
||||||
// Debug mode: render from the light's point of view using the plain
|
|
||||||
// textured shader so we can see what the shadow map "sees".
|
|
||||||
renderer.shaderManager.PushShader(defaultShaderName);
|
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
|
||||||
|
|
||||||
renderer.PushProjectionMatrix(
|
|
||||||
-40.0f, 40.0f,
|
|
||||||
-40.0f, 40.0f,
|
|
||||||
0.1f, 1000.0f);
|
|
||||||
|
|
||||||
renderer.PushSpecialMatrix(shadowMap->getLightViewMatrix());
|
|
||||||
|
|
||||||
#else
|
|
||||||
static const std::string shadowShaderName = "default_shadow";
|
|
||||||
|
|
||||||
renderer.shaderManager.PushShader(shadowShaderName);
|
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
|
||||||
renderer.RenderUniform1i("uShadowMap", 1);
|
|
||||||
|
|
||||||
// Bind shadow map texture to unit 1
|
|
||||||
glActiveTexture(GL_TEXTURE1);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthTexture());
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
|
|
||||||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
|
||||||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
|
||||||
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
|
||||||
renderer.PushMatrix();
|
|
||||||
|
|
||||||
renderer.LoadIdentity();
|
|
||||||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
|
||||||
|
|
||||||
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
|
|
||||||
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
||||||
const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
|
|
||||||
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
|
|
||||||
|
|
||||||
// Capture the camera view matrix and compute uLightFromCamera
|
|
||||||
cameraViewMatrix = renderer.GetCurrentModelViewMatrix();
|
|
||||||
Eigen::Matrix4f cameraViewInverse = cameraViewMatrix.inverse();
|
|
||||||
Eigen::Matrix4f lightFromCamera = shadowMap->getLightSpaceMatrix() * cameraViewInverse;
|
|
||||||
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
|
||||||
|
|
||||||
// Light direction in camera space for diffuse lighting
|
|
||||||
Eigen::Vector3f lightDirCamera = cameraViewMatrix.block<3,3>(0,0) * shadowMap->getLightDirection();
|
|
||||||
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(roomMesh);
|
|
||||||
|
|
||||||
for (auto& [name, gameObj] : gameObjects) {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(gameObj.mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& intObj : interactiveObjects) {
|
|
||||||
if (intObj.isActive) {
|
|
||||||
intObj.draw(renderer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_LIGHT
|
|
||||||
// In debug-light mode characters use the plain shaders (draw normally
|
|
||||||
// but from the light's viewpoint — projection/view already on stack).
|
|
||||||
if (player) player->draw(renderer);
|
|
||||||
for (auto& npc : npcs) npc->draw(renderer);
|
|
||||||
#else
|
|
||||||
// Characters use their own shadow-aware shaders
|
|
||||||
if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
|
||||||
for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::drawScene() {
|
void Game::drawScene() {
|
||||||
glViewport(0, 0, Environment::width, Environment::height);
|
glViewport(0, 0, Environment::width, Environment::height);
|
||||||
if (!loadingCompleted) {
|
if (!loadingCompleted) {
|
||||||
@ -707,11 +246,19 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (shadowMap) {
|
if (currentLocation)
|
||||||
drawShadowDepthPass();
|
{
|
||||||
drawGameWithShadows();
|
if (currentLocation->shadowMap) {
|
||||||
} else {
|
currentLocation->drawShadowDepthPass();
|
||||||
drawGame();
|
currentLocation->drawGameWithShadows();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentLocation->drawGame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ??? Main menu???
|
||||||
}
|
}
|
||||||
drawUI();
|
drawUI();
|
||||||
}
|
}
|
||||||
@ -773,41 +320,11 @@ namespace ZL
|
|||||||
|
|
||||||
lastTickCount = newTickCount;
|
lastTickCount = newTickCount;
|
||||||
|
|
||||||
if (player) {
|
if (currentLocation)
|
||||||
player->update(delta);
|
{
|
||||||
dialogueSystem.update(static_cast<int>(delta), player->position);
|
currentLocation->update(delta);
|
||||||
}
|
}
|
||||||
for (auto& npc : npcs) npc->update(delta);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Check if player reached target interactive object
|
|
||||||
if (targetInteractiveObject && player) {
|
|
||||||
float distToObject = (player->position - targetInteractiveObject->position).norm();
|
|
||||||
|
|
||||||
// If player is close enough to pick up the item
|
|
||||||
if (distToObject <= targetInteractiveObject->interactionRadius + 1.0f) {
|
|
||||||
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
|
|
||||||
std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->id << std::endl;
|
|
||||||
|
|
||||||
// Call custom activate function if specified, otherwise use fallback
|
|
||||||
try {
|
|
||||||
if (!targetInteractiveObject->activateFunctionName.empty()) {
|
|
||||||
std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->activateFunctionName << std::endl;
|
|
||||||
scriptEngine.callActivateFunction(targetInteractiveObject->activateFunctionName);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::cout << "[PICKUP] Using fallback callback" << std::endl;
|
|
||||||
scriptEngine.callItemPickupCallback(targetInteractiveObject->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const std::exception& e) {
|
|
||||||
std::cerr << "[PICKUP] Error calling function: " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
targetInteractiveObject = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,97 +412,16 @@ namespace ZL
|
|||||||
std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
|
std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
|
||||||
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||||
|
|
||||||
player->attackTarget = nullptr;
|
|
||||||
if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) {
|
if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) {
|
||||||
std::cout << "[CLICK] UI handled, skipping character movement" << std::endl;
|
std::cout << "[CLICK] UI handled, skipping character movement" << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate ray for picking
|
if (currentLocation)
|
||||||
if (dialogueSystem.blocksGameplayInput()) {
|
{
|
||||||
dialogueSystem.handlePointerReleased(static_cast<float>(mx), Environment::projectionHeight - static_cast<float>(my));
|
currentLocation->handleDown(ZL::UiManager::MOUSE_FINGER_ID, event.button.x, event.button.y, mx, my);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
///.....
|
||||||
// Unproject click to ground plane (y=0) for Viola's walk target
|
|
||||||
float ndcX = 2.0f * event.button.x / Environment::width - 1.0f;
|
|
||||||
float ndcY = 1.0f - 2.0f * event.button.y / Environment::height;
|
|
||||||
float aspect = (float)Environment::width / (float)Environment::height;
|
|
||||||
float tanHalfFov = tan(CAMERA_FOV_Y * 0.5f);
|
|
||||||
|
|
||||||
float cosAzim = cos(cameraAzimuth), sinAzim = sin(cameraAzimuth);
|
|
||||||
float cosIncl = cos(cameraInclination), sinIncl = sin(cameraInclination);
|
|
||||||
|
|
||||||
Eigen::Vector3f camRight(cosAzim, 0.f, sinAzim);
|
|
||||||
Eigen::Vector3f camForward(sinAzim * cosIncl, -sinIncl, -cosAzim * cosIncl);
|
|
||||||
Eigen::Vector3f camUp(sinAzim * sinIncl, cosIncl, -cosAzim * sinIncl);
|
|
||||||
const Eigen::Vector3f& playerPos = player ? player->position : Eigen::Vector3f::Zero();
|
|
||||||
Eigen::Vector3f camPos = playerPos + Eigen::Vector3f(-sinAzim * cosIncl, sinIncl, cosAzim * cosIncl) * Environment::zoom;
|
|
||||||
|
|
||||||
Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();
|
|
||||||
|
|
||||||
std::cout << "[CLICK] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl;
|
|
||||||
std::cout << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
|
|
||||||
|
|
||||||
// First check if we clicked on interactive object
|
|
||||||
InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir);
|
|
||||||
if (clickedObject && player && clickedObject->isActive) {
|
|
||||||
std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->name << " ***" << std::endl;
|
|
||||||
std::cout << "[CLICK] Object position: (" << clickedObject->position.x() << ", "
|
|
||||||
<< clickedObject->position.y() << ", " << clickedObject->position.z() << ")" << std::endl;
|
|
||||||
std::cout << "[CLICK] Player position: (" << player->position.x() << ", "
|
|
||||||
<< player->position.y() << ", " << player->position.z() << ")" << std::endl;
|
|
||||||
|
|
||||||
targetInteractiveObject = clickedObject;
|
|
||||||
player->setTarget(clickedObject->position);
|
|
||||||
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
// Check if we clicked on an NPC
|
|
||||||
Character* clickedNpc = raycastNpcs(camPos, rayDir);
|
|
||||||
if (clickedNpc && player) {
|
|
||||||
float distance = (player->position - clickedNpc->position).norm();
|
|
||||||
int npcIndex = -1;
|
|
||||||
for (size_t i = 0; i < npcs.size(); ++i) {
|
|
||||||
if (npcs[i].get() == clickedNpc) {
|
|
||||||
npcIndex = static_cast<int>(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (npcIndex != -1) {
|
|
||||||
if (distance <= clickedNpc->interactionRadius) {
|
|
||||||
std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl;
|
|
||||||
scriptEngine.callNpcInteractCallback(npcIndex);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::cout << "[CLICK] Too far from NPC (distance " << distance
|
|
||||||
<< " > " << clickedNpc->interactionRadius << ")" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clickedNpc->canAttack)
|
|
||||||
{
|
|
||||||
player->attackTarget = clickedNpc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (rayDir.y() < -0.001f && player) {
|
|
||||||
// Otherwise, unproject click to ground plane for Viola's walk target
|
|
||||||
float t = -camPos.y() / rayDir.y();
|
|
||||||
Eigen::Vector3f hit = camPos + rayDir * t;
|
|
||||||
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
|
|
||||||
|
|
||||||
if (player->currentState == AnimationState::STAND || player->currentState == AnimationState::WALK)
|
|
||||||
{
|
|
||||||
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::cout << "[CLICK] No valid target found" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::cout << "========================================\n" << std::endl;
|
|
||||||
} else {
|
} else {
|
||||||
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||||
}
|
}
|
||||||
@ -995,9 +431,19 @@ namespace ZL
|
|||||||
rightMouseDown = true;
|
rightMouseDown = true;
|
||||||
lastMouseX = event.button.x;
|
lastMouseX = event.button.x;
|
||||||
lastMouseY = event.button.y;
|
lastMouseY = event.button.y;
|
||||||
|
if (currentLocation)
|
||||||
|
{
|
||||||
|
currentLocation->rightMouseDown = true;
|
||||||
|
currentLocation->lastMouseX = event.button.x;
|
||||||
|
currentLocation->lastMouseY = event.button.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
rightMouseDown = false;
|
rightMouseDown = false;
|
||||||
|
if (currentLocation)
|
||||||
|
{
|
||||||
|
currentLocation->rightMouseDown = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1005,28 +451,10 @@ namespace ZL
|
|||||||
int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
|
int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
|
||||||
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
|
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
|
||||||
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||||
|
if (currentLocation)
|
||||||
if (dialogueSystem.blocksGameplayInput()) {
|
{
|
||||||
dialogueSystem.handlePointerMoved(
|
currentLocation->handleMotion(ZL::UiManager::MOUSE_FINGER_ID, event.motion.x, event.motion.y, mx, my);
|
||||||
static_cast<float>(mx),
|
}
|
||||||
Environment::projectionHeight - static_cast<float>(my)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rightMouseDown) {
|
|
||||||
int dx = event.motion.x - lastMouseX;
|
|
||||||
int dy = event.motion.y - lastMouseY;
|
|
||||||
lastMouseX = event.motion.x;
|
|
||||||
lastMouseY = event.motion.y;
|
|
||||||
|
|
||||||
const float sensitivity = 0.005f;
|
|
||||||
cameraAzimuth += dx * sensitivity;
|
|
||||||
cameraInclination += dy * sensitivity;
|
|
||||||
|
|
||||||
const float minInclination = M_PI * 30.f / 180.f;
|
|
||||||
const float maxInclination = M_PI * 0.5f;
|
|
||||||
cameraInclination = max(minInclination, min(maxInclination, cameraInclination));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_MOUSEWHEEL) {
|
if (event.type == SDL_MOUSEWHEEL) {
|
||||||
@ -1042,36 +470,28 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_KEYDOWN && dialogueSystem.handleKeyDown(event.key.keysym.sym)) {
|
|
||||||
continue;
|
if (event.type == SDL_KEYDOWN) {
|
||||||
|
if (currentLocation && currentLocation->dialogueSystem.handleKeyDown(event.key.keysym.sym))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
|
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
|
||||||
switch (event.key.keysym.sym) {
|
switch (event.key.keysym.sym) {
|
||||||
case SDLK_f:
|
case SDLK_f:
|
||||||
dialogueSystem.startDialogue("test_choice_dialogue");
|
currentLocation->dialogueSystem.startDialogue("test_choice_dialogue");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_e:
|
case SDLK_e:
|
||||||
dialogueSystem.startDialogue("test_cutscene_pan_dialogue");
|
currentLocation->dialogueSystem.startDialogue("test_cutscene_pan_dialogue");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_p:
|
case SDLK_p:
|
||||||
if (player->battle_state == 0)
|
|
||||||
{
|
|
||||||
player->battle_state = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
player->battle_state = 0;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_l:
|
case SDLK_l:
|
||||||
if (player->attack == 0)
|
|
||||||
{
|
|
||||||
player->attack = 1;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SDLK_RETURN:
|
case SDLK_RETURN:
|
||||||
|
|
||||||
@ -1101,21 +521,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_KEYUP) {
|
if (event.type == SDL_KEYUP) {
|
||||||
/*benchMesh.data.Move({-x, -y, 0});
|
|
||||||
if (event.key.keysym.sym == SDLK_a) {
|
|
||||||
x = x - 0.1;
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_d) {
|
|
||||||
x = x + 0.1;
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_w) {
|
|
||||||
y = y - 0.1;
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_s) {
|
|
||||||
y = y + 0.1;
|
|
||||||
}
|
|
||||||
benchMesh.data.Move({ x, y, 0 });
|
|
||||||
benchMesh.RefreshVBO();*/
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -1160,25 +566,4 @@ namespace ZL
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Game::requestDialogueStart(const std::string& dialogueId)
|
|
||||||
{
|
|
||||||
return dialogueSystem.startDialogue(dialogueId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::setDialogueFlag(const std::string& flag, int value)
|
|
||||||
{
|
|
||||||
dialogueSystem.setFlag(flag, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Game::getDialogueFlag(const std::string& flag) const
|
|
||||||
{
|
|
||||||
return dialogueSystem.getFlag(flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Game::setNavigationAreaAvailable(const std::string& areaName, bool available)
|
|
||||||
{
|
|
||||||
return navigation.setAreaAvailable(areaName, available);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
73
src/Game.h
73
src/Game.h
@ -5,9 +5,7 @@
|
|||||||
#include "Environment.h"
|
#include "Environment.h"
|
||||||
#include "render/TextureManager.h"
|
#include "render/TextureManager.h"
|
||||||
#include "SparkEmitter.h"
|
#include "SparkEmitter.h"
|
||||||
#include "planet/PlanetObject.h"
|
|
||||||
#include "UiManager.h"
|
#include "UiManager.h"
|
||||||
#include "Projectile.h"
|
|
||||||
#include "utils/TaskManager.h"
|
#include "utils/TaskManager.h"
|
||||||
#include "items/GameObjectLoader.h"
|
#include "items/GameObjectLoader.h"
|
||||||
#include "items/Item.h"
|
#include "items/Item.h"
|
||||||
@ -19,12 +17,9 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <render/TextRenderer.h>
|
#include <render/TextRenderer.h>
|
||||||
#include "MenuManager.h"
|
#include "MenuManager.h"
|
||||||
#include "ScriptEngine.h"
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "dialogue/DialogueSystem.h"
|
|
||||||
#include "render/ShadowMap.h"
|
|
||||||
#include "navigation/PathFinder.h"
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include "Location.h"
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
@ -39,87 +34,40 @@ namespace ZL {
|
|||||||
void render();
|
void render();
|
||||||
|
|
||||||
bool shouldExit() const { return Environment::exitGameLoop; }
|
bool shouldExit() const { return Environment::exitGameLoop; }
|
||||||
bool requestDialogueStart(const std::string& dialogueId);
|
|
||||||
void setDialogueFlag(const std::string& flag, int value);
|
|
||||||
int getDialogueFlag(const std::string& flag) const;
|
|
||||||
bool setNavigationAreaAvailable(const std::string& areaName, bool available);
|
|
||||||
|
|
||||||
Renderer renderer;
|
Renderer renderer;
|
||||||
TaskManager taskManager;
|
TaskManager taskManager;
|
||||||
MainThreadHandler mainThreadHandler;
|
MainThreadHandler mainThreadHandler;
|
||||||
//std::unique_ptr<INetworkClient> networkClient;
|
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> loadingTexture;
|
std::shared_ptr<Texture> loadingTexture;
|
||||||
VertexRenderStruct loadingMesh;
|
VertexRenderStruct loadingMesh;
|
||||||
bool loadingCompleted = false;
|
bool loadingCompleted = false;
|
||||||
|
|
||||||
|
std::shared_ptr<Location> currentLocation;
|
||||||
|
|
||||||
std::shared_ptr<Texture> roomTexture;
|
|
||||||
VertexRenderStruct roomMesh;
|
|
||||||
|
|
||||||
std::unordered_map<std::string, LoadedGameObject> gameObjects;
|
|
||||||
|
|
||||||
/*
|
|
||||||
std::shared_ptr<Texture> fireboxTexture;
|
|
||||||
VertexRenderStruct fireboxMesh;
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> inaiTexture;
|
|
||||||
VertexRenderStruct inaiMesh;
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> benchTexture;
|
|
||||||
VertexRenderStruct benchMesh;
|
|
||||||
*/
|
|
||||||
std::vector<InteractiveObject> interactiveObjects;
|
|
||||||
Inventory inventory;
|
Inventory inventory;
|
||||||
InteractiveObject* pickedUpObject = nullptr;
|
InteractiveObject* pickedUpObject = nullptr;
|
||||||
|
|
||||||
// Interactive object targeting
|
|
||||||
InteractiveObject* targetInteractiveObject = nullptr;
|
|
||||||
bool inventoryOpen = false;
|
bool inventoryOpen = false;
|
||||||
|
|
||||||
std::unique_ptr<Character> player;
|
|
||||||
std::vector<std::unique_ptr<Character>> npcs;
|
|
||||||
|
|
||||||
float cameraAzimuth = 0.0f;
|
|
||||||
float cameraInclination = M_PI * 30.f / 180.f;
|
|
||||||
|
|
||||||
// Public access for ScriptEngine
|
|
||||||
MenuManager menuManager;
|
MenuManager menuManager;
|
||||||
ScriptEngine scriptEngine;
|
|
||||||
PathFinder navigation;
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
|
||||||
std::vector<VertexRenderStruct> debugNavMeshes;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool rightMouseDown = false;
|
bool rightMouseDown = false;
|
||||||
int lastMouseX = 0;
|
int lastMouseX = 0;
|
||||||
int lastMouseY = 0;
|
int lastMouseY = 0;
|
||||||
|
|
||||||
static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f;
|
|
||||||
|
|
||||||
int64_t getSyncTimeMs();
|
int64_t getSyncTimeMs();
|
||||||
void processTickCount();
|
void processTickCount();
|
||||||
void drawScene();
|
void drawScene();
|
||||||
void drawUI();
|
void drawUI();
|
||||||
void drawGame();
|
|
||||||
void drawLoading();
|
void drawLoading();
|
||||||
void drawShadowDepthPass();
|
|
||||||
void drawGameWithShadows();
|
|
||||||
void setupNavigation();
|
|
||||||
void handleDown(int64_t fingerId, int mx, int my);
|
void handleDown(int64_t fingerId, int mx, int my);
|
||||||
void handleUp(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 handleMotion(int64_t fingerId, int mx, int my);
|
||||||
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
|
|
||||||
Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f);
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
|
||||||
void buildDebugNavMeshes();
|
|
||||||
void drawDebugNavigation();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
static Game* s_instance;
|
static Game* s_instance;
|
||||||
static void onResourcesZipLoaded(const char* filename);
|
static void onResourcesZipLoaded(const char* filename);
|
||||||
@ -128,18 +76,9 @@ namespace ZL {
|
|||||||
|
|
||||||
int64_t newTickCount;
|
int64_t newTickCount;
|
||||||
int64_t lastTickCount;
|
int64_t lastTickCount;
|
||||||
uint32_t connectingStartTicks = 0;
|
|
||||||
static constexpr uint32_t CONNECTING_TIMEOUT_MS = 10000;
|
|
||||||
|
|
||||||
static const size_t CONST_TIMER_INTERVAL = 10;
|
static const size_t CONST_TIMER_INTERVAL = 10;
|
||||||
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
||||||
|
|
||||||
//MenuManager menuManager;
|
|
||||||
Dialogue::DialogueSystem dialogueSystem;
|
|
||||||
//ScriptEngine scriptEngine;
|
|
||||||
|
|
||||||
std::unique_ptr<ShadowMap> shadowMap;
|
|
||||||
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
661
src/Location.cpp
Normal file
661
src/Location.cpp
Normal file
@ -0,0 +1,661 @@
|
|||||||
|
#include "Location.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
#include "render/OpenGlExtensions.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include "render/TextureManager.h"
|
||||||
|
#include "TextModel.h"
|
||||||
|
#include <random>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "GameConstants.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
extern const char* CONST_ZIP_FILE;
|
||||||
|
|
||||||
|
static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f;
|
||||||
|
|
||||||
|
Location::Location(Renderer& iRenderer, Inventory& iInventory)
|
||||||
|
: renderer(iRenderer)
|
||||||
|
, inventory(iInventory)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::setup()
|
||||||
|
{
|
||||||
|
roomTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE));
|
||||||
|
roomMesh.data = LoadFromTextFile02("resources/w/room001.txt", CONST_ZIP_FILE);
|
||||||
|
roomMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
|
roomMesh.RefreshVBO();
|
||||||
|
|
||||||
|
// Load static game objects
|
||||||
|
gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
|
||||||
|
|
||||||
|
// Load interactive objects
|
||||||
|
interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
|
||||||
|
|
||||||
|
|
||||||
|
auto playerTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE));
|
||||||
|
|
||||||
|
player = std::make_unique<Character>();
|
||||||
|
|
||||||
|
player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE);
|
||||||
|
|
||||||
|
player->setTexture(playerTexture);
|
||||||
|
player->walkSpeed = 3.0f;
|
||||||
|
player->rotationSpeed = 8.0f;
|
||||||
|
|
||||||
|
player->modelScale = 1.f;
|
||||||
|
player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
|
||||||
|
|
||||||
|
player->canAttack = true;
|
||||||
|
player->isPlayer = true;
|
||||||
|
std::cout << "Load resurces step 9" << std::endl;
|
||||||
|
|
||||||
|
// Load NPCs from JSON
|
||||||
|
npcs = GameObjectLoader::loadAndCreateNpcs("resources/config2/npcs.json", CONST_ZIP_FILE);
|
||||||
|
|
||||||
|
auto ghostTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE));
|
||||||
|
|
||||||
|
std::cout << "Load resurces step 11" << std::endl;
|
||||||
|
auto npc02 = std::make_unique<Character>();
|
||||||
|
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->setTexture(ghostTexture);
|
||||||
|
npc02->walkSpeed = 1.5f;
|
||||||
|
npc02->rotationSpeed = 8.0f;
|
||||||
|
npc02->modelScale = 0.01f;
|
||||||
|
//npc02->modelScale = 0.1f;
|
||||||
|
npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
|
||||||
|
|
||||||
|
npc02->position = Eigen::Vector3f(0.f, 0.f, -20.f);
|
||||||
|
npc02->setTarget(npc02->position);
|
||||||
|
npc02->canAttack = true;
|
||||||
|
npc02->attackTarget = player.get();
|
||||||
|
|
||||||
|
npcs.push_back(std::move(npc02));
|
||||||
|
|
||||||
|
// Create shadow map (2048x2048, ortho size 40, near 0.1, far 100)
|
||||||
|
shadowMap = std::make_unique<ShadowMap>(2048, 40.0f, 0.1f, 100.0f);
|
||||||
|
shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f));
|
||||||
|
std::cout << "Shadow map initialized" << std::endl;
|
||||||
|
|
||||||
|
setupNavigation();
|
||||||
|
|
||||||
|
scriptEngine.init(this, &inventory);
|
||||||
|
|
||||||
|
|
||||||
|
dialogueSystem.init(renderer, CONST_ZIP_FILE);
|
||||||
|
dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
|
||||||
|
/*dialogueSystem.addTriggerZone({
|
||||||
|
"ghost_room_trigger",
|
||||||
|
"test_line_dialogue",
|
||||||
|
Eigen::Vector3f(0.0f, 0.0f, -8.5f),
|
||||||
|
2.0f,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
});*/
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::setupNavigation()
|
||||||
|
{
|
||||||
|
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, "resources/config2/navigation.json", CONST_ZIP_FILE);
|
||||||
|
|
||||||
|
#ifdef SHOW_PATH
|
||||||
|
buildDebugNavMeshes();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto planner = [this](const Eigen::Vector3f& start, const Eigen::Vector3f& end) {
|
||||||
|
return navigation.findPath(start, end);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
player->setPathPlanner(planner);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& npc : npcs) {
|
||||||
|
if (npc) {
|
||||||
|
npc->setPathPlanner(planner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SHOW_PATH
|
||||||
|
void Location::buildDebugNavMeshes()
|
||||||
|
{
|
||||||
|
debugNavMeshes.clear();
|
||||||
|
const auto& areas = navigation.getAreas();
|
||||||
|
float y = navigation.getFloorY() + 0.02f;
|
||||||
|
Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
for (const auto& area : areas) {
|
||||||
|
if (area.polygon.size() < 3) continue;
|
||||||
|
VertexRenderStruct mesh;
|
||||||
|
mesh.data = CreatePolygonFloor(area.polygon, y, red);
|
||||||
|
mesh.RefreshVBO();
|
||||||
|
debugNavMeshes.push_back(std::move(mesh));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::drawDebugNavigation()
|
||||||
|
{
|
||||||
|
renderer.shaderManager.PushShader("defaultColor");
|
||||||
|
renderer.SetMatrix();
|
||||||
|
for (const auto& mesh : debugNavMeshes) {
|
||||||
|
renderer.DrawVertexRenderStruct(mesh);
|
||||||
|
}
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
renderer.SetMatrix();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
InteractiveObject* Location::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
|
||||||
|
if (interactiveObjects.empty()) {
|
||||||
|
std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
|
||||||
|
std::cout << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl;
|
||||||
|
std::cout << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
float closestDistance = FLT_MAX;
|
||||||
|
InteractiveObject* closestObject = nullptr;
|
||||||
|
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
std::cout << "[RAYCAST] Checking object: " << intObj.name << " (active: " << intObj.isActive << ")" << std::endl;
|
||||||
|
|
||||||
|
if (!intObj.isActive) {
|
||||||
|
std::cout << "[RAYCAST] -> Object inactive, skipping" << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
|
||||||
|
<< intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
|
||||||
|
|
||||||
|
Eigen::Vector3f toObject = intObj.position - rayOrigin;
|
||||||
|
std::cout << "[RAYCAST] Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
float distanceAlongRay = toObject.dot(rayDir);
|
||||||
|
std::cout << "[RAYCAST] Distance along ray: " << distanceAlongRay << std::endl;
|
||||||
|
|
||||||
|
if (distanceAlongRay < 0.1f) {
|
||||||
|
std::cout << "[RAYCAST] -> Object behind camera, skipping" << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
|
||||||
|
float distToObject = (closestPointOnRay - intObj.position).norm();
|
||||||
|
|
||||||
|
std::cout << "[RAYCAST] Distance to object: " << distToObject
|
||||||
|
<< " (interaction radius: " << intObj.interactionRadius << ")" << std::endl;
|
||||||
|
|
||||||
|
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
|
||||||
|
std::cout << "[RAYCAST] *** HIT DETECTED! ***" << std::endl;
|
||||||
|
closestDistance = distanceAlongRay;
|
||||||
|
closestObject = &intObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestObject) {
|
||||||
|
std::cout << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->name << " ***" << std::endl;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "[RAYCAST] No objects hit" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
Character* Location::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) {
|
||||||
|
Character* closestNpc = nullptr;
|
||||||
|
float closestDist = maxDistance;
|
||||||
|
|
||||||
|
for (auto& npc : npcs) {
|
||||||
|
Eigen::Vector3f toNpc = npc->position - rayOrigin;
|
||||||
|
float distAlongRay = toNpc.dot(rayDir);
|
||||||
|
if (distAlongRay < 0.1f) continue;
|
||||||
|
|
||||||
|
Eigen::Vector3f closestPoint = rayOrigin + rayDir * distAlongRay;
|
||||||
|
float distToNpc = (closestPoint - npc->position).norm();
|
||||||
|
|
||||||
|
float radius = npc->modelScale * 50.0f;
|
||||||
|
|
||||||
|
if (distToNpc <= radius && distAlongRay < closestDist) {
|
||||||
|
closestDist = distAlongRay;
|
||||||
|
closestNpc = npc.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestNpc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Location::drawGame()
|
||||||
|
{
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
renderer.PushMatrix();
|
||||||
|
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||||
|
//renderer.TranslateMatrix({ 0, -6.f, 0 });
|
||||||
|
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
|
const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
|
||||||
|
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(roomMesh);
|
||||||
|
|
||||||
|
for (auto& [name, gameObj] : gameObjects) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(gameObj.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
glBindTexture(GL_TEXTURE_2D, fireboxTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(fireboxMesh);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, inaiTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(inaiMesh);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(benchMesh);
|
||||||
|
*/
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
if (intObj.isActive) {
|
||||||
|
intObj.draw(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player) player->draw(renderer);
|
||||||
|
for (auto& npc : npcs) npc->draw(renderer);
|
||||||
|
|
||||||
|
#ifdef SHOW_PATH
|
||||||
|
drawDebugNavigation();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::drawShadowDepthPass()
|
||||||
|
{
|
||||||
|
if (!shadowMap) return;
|
||||||
|
|
||||||
|
const Eigen::Vector3f& sceneCenter = player ? player->position : Eigen::Vector3f::Zero();
|
||||||
|
shadowMap->updateLightSpaceMatrix(sceneCenter);
|
||||||
|
|
||||||
|
shadowMap->bind();
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDepthFunc(GL_LESS);
|
||||||
|
|
||||||
|
// Use front-face culling during depth pass to reduce shadow acne on lit faces
|
||||||
|
//glCullFace(GL_FRONT);
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader("shadow_depth");
|
||||||
|
|
||||||
|
// Set up light's orthographic projection
|
||||||
|
const Eigen::Matrix4f& lightProj = shadowMap->getLightProjectionMatrix();
|
||||||
|
const Eigen::Matrix4f& lightView = shadowMap->getLightViewMatrix();
|
||||||
|
|
||||||
|
// Push the light's projection matrix via the 6-param ortho overload won't
|
||||||
|
// match our pre-computed matrix. Instead, use the raw stack approach:
|
||||||
|
// push a dummy projection then overwrite via PushSpecialMatrix-style.
|
||||||
|
// Simpler: push ortho then push the light view as modelview.
|
||||||
|
renderer.PushProjectionMatrix(
|
||||||
|
-40.0f, 40.0f,
|
||||||
|
-40.0f, 40.0f,
|
||||||
|
0.1f, 100.0f);
|
||||||
|
|
||||||
|
const Eigen::Vector3f& lightDir = shadowMap->getLightDirection();
|
||||||
|
Eigen::Vector3f lightPos = sceneCenter - lightDir * 50.0f;
|
||||||
|
Eigen::Vector3f up(0.0f, 1.0f, 0.0f);
|
||||||
|
if (std::abs(lightDir.dot(up)) > 0.99f) {
|
||||||
|
up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the light view matrix and push it
|
||||||
|
renderer.PushSpecialMatrix(lightView);
|
||||||
|
|
||||||
|
// Draw static geometry
|
||||||
|
renderer.DrawVertexRenderStruct(roomMesh);
|
||||||
|
|
||||||
|
for (auto& [name, gameObj] : gameObjects) {
|
||||||
|
renderer.DrawVertexRenderStruct(gameObj.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
if (intObj.isActive && intObj.texture) {
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix(intObj.position);
|
||||||
|
renderer.DrawVertexRenderStruct(intObj.mesh);
|
||||||
|
renderer.PopMatrix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw characters (they handle their own skinning shader switch internally)
|
||||||
|
if (player) player->drawShadowDepth(renderer);
|
||||||
|
for (auto& npc : npcs) npc->drawShadowDepth(renderer);
|
||||||
|
|
||||||
|
renderer.PopMatrix(); // light view
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
|
||||||
|
//glCullFace(GL_BACK);
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
shadowMap->unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::drawGameWithShadows()
|
||||||
|
{
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
#ifdef DEBUG_LIGHT
|
||||||
|
// Debug mode: render from the light's point of view using the plain
|
||||||
|
// textured shader so we can see what the shadow map "sees".
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
|
||||||
|
renderer.PushProjectionMatrix(
|
||||||
|
-40.0f, 40.0f,
|
||||||
|
-40.0f, 40.0f,
|
||||||
|
0.1f, 1000.0f);
|
||||||
|
|
||||||
|
renderer.PushSpecialMatrix(shadowMap->getLightViewMatrix());
|
||||||
|
|
||||||
|
#else
|
||||||
|
static const std::string shadowShaderName = "default_shadow";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(shadowShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.RenderUniform1i("uShadowMap", 1);
|
||||||
|
|
||||||
|
// Bind shadow map texture to unit 1
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthTexture());
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
renderer.PushMatrix();
|
||||||
|
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||||
|
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
|
const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
|
||||||
|
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
|
||||||
|
|
||||||
|
// Capture the camera view matrix and compute uLightFromCamera
|
||||||
|
cameraViewMatrix = renderer.GetCurrentModelViewMatrix();
|
||||||
|
Eigen::Matrix4f cameraViewInverse = cameraViewMatrix.inverse();
|
||||||
|
Eigen::Matrix4f lightFromCamera = shadowMap->getLightSpaceMatrix() * cameraViewInverse;
|
||||||
|
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
||||||
|
|
||||||
|
// Light direction in camera space for diffuse lighting
|
||||||
|
Eigen::Vector3f lightDirCamera = cameraViewMatrix.block<3, 3>(0, 0) * shadowMap->getLightDirection();
|
||||||
|
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(roomMesh);
|
||||||
|
|
||||||
|
for (auto& [name, gameObj] : gameObjects) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(gameObj.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
if (intObj.isActive) {
|
||||||
|
intObj.draw(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_LIGHT
|
||||||
|
// In debug-light mode characters use the plain shaders (draw normally
|
||||||
|
// but from the light's viewpoint — projection/view already on stack).
|
||||||
|
if (player) player->draw(renderer);
|
||||||
|
for (auto& npc : npcs) npc->draw(renderer);
|
||||||
|
#else
|
||||||
|
// Characters use their own shadow-aware shaders
|
||||||
|
if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
||||||
|
for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Location::setNavigationAreaAvailable(const std::string& areaName, bool available)
|
||||||
|
{
|
||||||
|
return navigation.setAreaAvailable(areaName, available);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::update(int64_t delta)
|
||||||
|
{
|
||||||
|
if (player) {
|
||||||
|
player->update(delta);
|
||||||
|
dialogueSystem.update(static_cast<int>(delta), player->position);
|
||||||
|
}
|
||||||
|
for (auto& npc : npcs) npc->update(delta);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Check if player reached target interactive object
|
||||||
|
if (targetInteractiveObject && player) {
|
||||||
|
float distToObject = (player->position - targetInteractiveObject->position).norm();
|
||||||
|
|
||||||
|
// If player is close enough to pick up the item
|
||||||
|
if (distToObject <= targetInteractiveObject->interactionRadius + 1.0f) {
|
||||||
|
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
|
||||||
|
std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->id << std::endl;
|
||||||
|
|
||||||
|
// Call custom activate function if specified, otherwise use fallback
|
||||||
|
try {
|
||||||
|
if (!targetInteractiveObject->activateFunctionName.empty()) {
|
||||||
|
std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->activateFunctionName << std::endl;
|
||||||
|
scriptEngine.callActivateFunction(targetInteractiveObject->activateFunctionName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "[PICKUP] Using fallback callback" << std::endl;
|
||||||
|
scriptEngine.callItemPickupCallback(targetInteractiveObject->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
std::cerr << "[PICKUP] Error calling function: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetInteractiveObject = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
||||||
|
{
|
||||||
|
// Calculate ray for picking
|
||||||
|
if (dialogueSystem.blocksGameplayInput()) {
|
||||||
|
dialogueSystem.handlePointerReleased(static_cast<float>(mx), Environment::projectionHeight - static_cast<float>(my));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player->attackTarget = nullptr;
|
||||||
|
|
||||||
|
// Unproject click to ground plane (y=0) for Viola's walk target
|
||||||
|
float ndcX = 2.0f * eventX / Environment::width - 1.0f;
|
||||||
|
float ndcY = 1.0f - 2.0f * eventY / Environment::height;
|
||||||
|
float aspect = (float)Environment::width / (float)Environment::height;
|
||||||
|
float tanHalfFov = tan(CAMERA_FOV_Y * 0.5f);
|
||||||
|
|
||||||
|
float cosAzim = cos(cameraAzimuth), sinAzim = sin(cameraAzimuth);
|
||||||
|
float cosIncl = cos(cameraInclination), sinIncl = sin(cameraInclination);
|
||||||
|
|
||||||
|
Eigen::Vector3f camRight(cosAzim, 0.f, sinAzim);
|
||||||
|
Eigen::Vector3f camForward(sinAzim * cosIncl, -sinIncl, -cosAzim * cosIncl);
|
||||||
|
Eigen::Vector3f camUp(sinAzim * sinIncl, cosIncl, -cosAzim * sinIncl);
|
||||||
|
const Eigen::Vector3f& playerPos = player ? player->position : Eigen::Vector3f::Zero();
|
||||||
|
Eigen::Vector3f camPos = playerPos + Eigen::Vector3f(-sinAzim * cosIncl, sinIncl, cosAzim * cosIncl) * Environment::zoom;
|
||||||
|
|
||||||
|
Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();
|
||||||
|
|
||||||
|
std::cout << "[CLICK] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl;
|
||||||
|
std::cout << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
// First check if we clicked on interactive object
|
||||||
|
InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir);
|
||||||
|
if (clickedObject && player && clickedObject->isActive) {
|
||||||
|
std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->name << " ***" << std::endl;
|
||||||
|
std::cout << "[CLICK] Object position: (" << clickedObject->position.x() << ", "
|
||||||
|
<< clickedObject->position.y() << ", " << clickedObject->position.z() << ")" << std::endl;
|
||||||
|
std::cout << "[CLICK] Player position: (" << player->position.x() << ", "
|
||||||
|
<< player->position.y() << ", " << player->position.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
targetInteractiveObject = clickedObject;
|
||||||
|
player->setTarget(clickedObject->position);
|
||||||
|
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
// Check if we clicked on an NPC
|
||||||
|
Character* clickedNpc = raycastNpcs(camPos, rayDir);
|
||||||
|
if (clickedNpc && player) {
|
||||||
|
float distance = (player->position - clickedNpc->position).norm();
|
||||||
|
int npcIndex = -1;
|
||||||
|
for (size_t i = 0; i < npcs.size(); ++i) {
|
||||||
|
if (npcs[i].get() == clickedNpc) {
|
||||||
|
npcIndex = static_cast<int>(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (npcIndex != -1) {
|
||||||
|
if (distance <= clickedNpc->interactionRadius) {
|
||||||
|
std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl;
|
||||||
|
scriptEngine.callNpcInteractCallback(npcIndex);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "[CLICK] Too far from NPC (distance " << distance
|
||||||
|
<< " > " << clickedNpc->interactionRadius << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clickedNpc->canAttack)
|
||||||
|
{
|
||||||
|
player->attackTarget = clickedNpc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (rayDir.y() < -0.001f && player) {
|
||||||
|
// Otherwise, unproject click to ground plane for Viola's walk target
|
||||||
|
float t = -camPos.y() / rayDir.y();
|
||||||
|
Eigen::Vector3f hit = camPos + rayDir * t;
|
||||||
|
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
if (player->currentState == AnimationState::STAND || player->currentState == AnimationState::WALK)
|
||||||
|
{
|
||||||
|
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "[CLICK] No valid target found" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "========================================\n" << std::endl;
|
||||||
|
}
|
||||||
|
void Location::handleUp(int64_t fingerId, int mx, int my)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
void Location::handleMotion(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
||||||
|
{
|
||||||
|
if (dialogueSystem.blocksGameplayInput()) {
|
||||||
|
dialogueSystem.handlePointerMoved(
|
||||||
|
static_cast<float>(mx),
|
||||||
|
Environment::projectionHeight - static_cast<float>(my)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightMouseDown) {
|
||||||
|
int dx = eventX - lastMouseX;
|
||||||
|
int dy = eventY - lastMouseY;
|
||||||
|
lastMouseX = eventX;
|
||||||
|
lastMouseY = eventY;
|
||||||
|
|
||||||
|
const float sensitivity = 0.005f;
|
||||||
|
cameraAzimuth += dx * sensitivity;
|
||||||
|
cameraInclination += dy * sensitivity;
|
||||||
|
|
||||||
|
const float minInclination = M_PI * 30.f / 180.f;
|
||||||
|
const float maxInclination = M_PI * 0.5f;
|
||||||
|
cameraInclination = max(minInclination, min(maxInclination, cameraInclination));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Location::requestDialogueStart(const std::string& dialogueId)
|
||||||
|
{
|
||||||
|
return dialogueSystem.startDialogue(dialogueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Location::setDialogueFlag(const std::string& flag, int value)
|
||||||
|
{
|
||||||
|
dialogueSystem.setFlag(flag, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Location::getDialogueFlag(const std::string& flag) const
|
||||||
|
{
|
||||||
|
return dialogueSystem.getFlag(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
81
src/Location.h
Normal file
81
src/Location.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "render/Renderer.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include "render/TextureManager.h"
|
||||||
|
#include "items/GameObjectLoader.h"
|
||||||
|
#include "items/InteractiveObject.h"
|
||||||
|
#include "navigation/PathFinder.h"
|
||||||
|
#include "render/ShadowMap.h"
|
||||||
|
|
||||||
|
#include "ScriptEngine.h"
|
||||||
|
#include "dialogue/DialogueSystem.h"
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
|
||||||
|
class Location
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Location(Renderer& iRenderer, Inventory& iInventory);
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> roomTexture;
|
||||||
|
VertexRenderStruct roomMesh;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, LoadedGameObject> gameObjects;
|
||||||
|
|
||||||
|
std::vector<InteractiveObject> interactiveObjects;
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<Character> player;
|
||||||
|
std::vector<std::unique_ptr<Character>> npcs;
|
||||||
|
|
||||||
|
float cameraAzimuth = 0.0f;
|
||||||
|
float cameraInclination = M_PI * 30.f / 180.f;
|
||||||
|
|
||||||
|
PathFinder navigation;
|
||||||
|
|
||||||
|
std::unique_ptr<ShadowMap> shadowMap;
|
||||||
|
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
|
||||||
|
InteractiveObject* targetInteractiveObject = nullptr;
|
||||||
|
|
||||||
|
ScriptEngine scriptEngine;
|
||||||
|
Dialogue::DialogueSystem dialogueSystem;
|
||||||
|
|
||||||
|
#ifdef SHOW_PATH
|
||||||
|
std::vector<VertexRenderStruct> debugNavMeshes;
|
||||||
|
void buildDebugNavMeshes();
|
||||||
|
void drawDebugNavigation();
|
||||||
|
#endif
|
||||||
|
bool rightMouseDown = false;
|
||||||
|
int lastMouseX = 0;
|
||||||
|
int lastMouseY = 0;
|
||||||
|
|
||||||
|
|
||||||
|
void setup();
|
||||||
|
void setupNavigation();
|
||||||
|
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
|
||||||
|
Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f);
|
||||||
|
|
||||||
|
void drawGame();
|
||||||
|
void drawShadowDepthPass();
|
||||||
|
void drawGameWithShadows();
|
||||||
|
|
||||||
|
bool setNavigationAreaAvailable(const std::string& areaName, bool available);
|
||||||
|
|
||||||
|
void update(int64_t deltaMs);
|
||||||
|
|
||||||
|
void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
||||||
|
void handleUp(int64_t fingerId, int mx, int my);
|
||||||
|
void handleMotion(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
||||||
|
|
||||||
|
bool requestDialogueStart(const std::string& dialogueId);
|
||||||
|
void setDialogueFlag(const std::string& flag, int value);
|
||||||
|
int getDialogueFlag(const std::string& flag) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Renderer& renderer;
|
||||||
|
Inventory& inventory;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
@ -2,6 +2,7 @@
|
|||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include "Location.h"
|
||||||
|
|
||||||
#define SOL_ALL_SAFETIES_ON 1
|
#define SOL_ALL_SAFETIES_ON 1
|
||||||
#include <sol/sol.hpp>
|
#include <sol/sol.hpp>
|
||||||
@ -15,7 +16,7 @@ namespace ZL {
|
|||||||
ScriptEngine::ScriptEngine() = default;
|
ScriptEngine::ScriptEngine() = default;
|
||||||
ScriptEngine::~ScriptEngine() = default;
|
ScriptEngine::~ScriptEngine() = default;
|
||||||
|
|
||||||
void ScriptEngine::init(Game* game) {
|
void ScriptEngine::init(Location* game, Inventory* inventory) {
|
||||||
impl = std::make_unique<Impl>();
|
impl = std::make_unique<Impl>();
|
||||||
sol::state& lua = impl->lua;
|
sol::state& lua = impl->lua;
|
||||||
|
|
||||||
@ -49,13 +50,15 @@ namespace ZL {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// pickup_item(object_name)
|
// pickup_item(object_name)
|
||||||
api.set_function("pickup_item", [game](const std::string& objectName) {
|
api.set_function("pickup_item", [game, inventory](const std::string& objectName) {
|
||||||
|
|
||||||
|
|
||||||
std::cout << "[script] pickup_item: " << objectName << std::endl;
|
std::cout << "[script] pickup_item: " << objectName << std::endl;
|
||||||
|
|
||||||
for (auto& intObj : game->interactiveObjects) {
|
for (auto& intObj : game->interactiveObjects) {
|
||||||
if (intObj.id == objectName && intObj.isActive) {
|
if (intObj.id == objectName && intObj.isActive) {
|
||||||
// Add item to inventory
|
// Add item to inventory
|
||||||
game->inventory.addItem(intObj.dropItem);
|
inventory->addItem(intObj.dropItem);
|
||||||
|
|
||||||
// Deactivate object
|
// Deactivate object
|
||||||
intObj.isActive = false;
|
intObj.isActive = false;
|
||||||
@ -68,51 +71,18 @@ namespace ZL {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// show_inventory()
|
|
||||||
api.set_function("show_inventory", [game]() {
|
|
||||||
std::cout << "[script] show_inventory called" << std::endl;
|
|
||||||
|
|
||||||
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
|
|
||||||
game->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
|
|
||||||
game->inventoryOpen = true;
|
|
||||||
|
|
||||||
// Update UI with current items
|
|
||||||
const auto& items = game->inventory.getItems();
|
|
||||||
std::string itemText;
|
|
||||||
|
|
||||||
if (items.empty()) {
|
|
||||||
itemText = "Inventory (Empty)";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
|
||||||
for (size_t i = 0; i < items.size(); ++i) {
|
|
||||||
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
game->menuManager.uiManager.setText("inventory_items_text", itemText);
|
|
||||||
});
|
|
||||||
|
|
||||||
// hide_inventory()
|
|
||||||
api.set_function("hide_inventory", [game]() {
|
|
||||||
std::cout << "[script] hide_inventory called" << std::endl;
|
|
||||||
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
|
|
||||||
game->menuManager.uiManager.setNodeVisible("close_inventory_button", false);
|
|
||||||
game->inventoryOpen = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// add_item(item_id, name, description, icon)
|
// add_item(item_id, name, description, icon)
|
||||||
api.set_function("add_item", [game](const std::string& id, const std::string& name,
|
api.set_function("add_item", [game, inventory](const std::string& id, const std::string& name,
|
||||||
const std::string& description, const std::string& icon) {
|
const std::string& description, const std::string& icon) {
|
||||||
std::cout << "[script] add_item: " << name << std::endl;
|
std::cout << "[script] add_item: " << name << std::endl;
|
||||||
Item newItem(id, name, description, icon);
|
Item newItem(id, name, description, icon);
|
||||||
game->inventory.addItem(newItem);
|
inventory->addItem(newItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove_item(item_id)
|
// remove_item(item_id)
|
||||||
api.set_function("remove_item", [game](const std::string& id) {
|
api.set_function("remove_item", [game, inventory](const std::string& id) {
|
||||||
std::cout << "[script] remove_item: " << id << std::endl;
|
std::cout << "[script] remove_item: " << id << std::endl;
|
||||||
game->inventory.removeItem(id);
|
inventory->removeItem(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// deactivate_interactive_object(object_name)
|
// deactivate_interactive_object(object_name)
|
||||||
@ -129,13 +99,13 @@ namespace ZL {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// get_inventory_count()
|
// get_inventory_count()
|
||||||
api.set_function("get_inventory_count", [game]() {
|
api.set_function("get_inventory_count", [inventory]() {
|
||||||
return game->inventory.getCount();
|
return inventory->getCount();
|
||||||
});
|
});
|
||||||
|
|
||||||
// has_item(item_id)
|
// has_item(item_id)
|
||||||
api.set_function("has_item", [game](const std::string& id) {
|
api.set_function("has_item", [inventory](const std::string& id) {
|
||||||
return game->inventory.hasItem(id);
|
return inventory->hasItem(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
api.set_function("start_dialogue",
|
api.set_function("start_dialogue",
|
||||||
@ -164,7 +134,7 @@ namespace ZL {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// receive_npc_gift(npc_index)
|
// receive_npc_gift(npc_index)
|
||||||
api.set_function("receive_npc_gift", [game](int npcIndex) {
|
api.set_function("receive_npc_gift", [game, inventory](int npcIndex) {
|
||||||
std::cout << "[script] receive_npc_gift: npc index " << npcIndex << std::endl;
|
std::cout << "[script] receive_npc_gift: npc index " << npcIndex << std::endl;
|
||||||
|
|
||||||
auto& npcs = game->npcs;
|
auto& npcs = game->npcs;
|
||||||
@ -188,7 +158,7 @@ namespace ZL {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game->inventory.addItem(npc->giftItem);
|
inventory->addItem(npc->giftItem);
|
||||||
npc->giftReceived = true;
|
npc->giftReceived = true;
|
||||||
|
|
||||||
std::cout << "[script] Received gift from " << npc->npcName << ": "
|
std::cout << "[script] Received gift from " << npc->npcName << ": "
|
||||||
@ -284,31 +254,4 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::showInventory(Game* game) {
|
|
||||||
std::cout << "[script] toggle_inventory called" << std::endl;
|
|
||||||
|
|
||||||
bool isVisible = game->menuManager.uiManager.getNodeVisible("inventory_items_panel");
|
|
||||||
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", !isVisible);
|
|
||||||
game->menuManager.uiManager.setNodeVisible("close_inventory_button", !isVisible);
|
|
||||||
|
|
||||||
game->inventoryOpen = !isVisible;
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
const auto& items = game->inventory.getItems();
|
|
||||||
std::string itemText;
|
|
||||||
|
|
||||||
if (items.empty()) {
|
|
||||||
itemText = "Inventory (Empty)";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
|
||||||
for (size_t i = 0; i < items.size(); ++i) {
|
|
||||||
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
game->menuManager.uiManager.setText("inventory_items_text", itemText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
class Game;
|
class Location;
|
||||||
|
class Inventory;
|
||||||
|
|
||||||
class ScriptEngine {
|
class ScriptEngine {
|
||||||
public:
|
public:
|
||||||
@ -12,7 +13,7 @@ public:
|
|||||||
~ScriptEngine();
|
~ScriptEngine();
|
||||||
|
|
||||||
// Must be called once, after the Game's NPCs are ready.
|
// Must be called once, after the Game's NPCs are ready.
|
||||||
void init(Game* game);
|
void init(Location* game, Inventory* inventory);
|
||||||
|
|
||||||
// Execute a Lua script file. Logs errors to stderr.
|
// Execute a Lua script file. Logs errors to stderr.
|
||||||
void runScript(const std::string& path);
|
void runScript(const std::string& path);
|
||||||
@ -21,7 +22,6 @@ public:
|
|||||||
|
|
||||||
void callActivateFunction(const std::string& functionName);
|
void callActivateFunction(const std::string& functionName);
|
||||||
|
|
||||||
void showInventory(Game* game);
|
|
||||||
void callNpcInteractCallback(int npcIndex);
|
void callNpcInteractCallback(int npcIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
2353
src/Space.cpp
2353
src/Space.cpp
File diff suppressed because it is too large
Load Diff
204
src/Space.h
204
src/Space.h
@ -1,204 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "render/Renderer.h"
|
|
||||||
#include "Environment.h"
|
|
||||||
#include "render/TextureManager.h"
|
|
||||||
#include "SparkEmitter.h"
|
|
||||||
#include "planet/PlanetObject.h"
|
|
||||||
#include "UiManager.h"
|
|
||||||
#include "Projectile.h"
|
|
||||||
#include "utils/TaskManager.h"
|
|
||||||
#include "network/NetworkInterface.h"
|
|
||||||
#include <queue>
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
#include <render/TextRenderer.h>
|
|
||||||
#include "MenuManager.h"
|
|
||||||
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
|
|
||||||
struct BoxCoords
|
|
||||||
{
|
|
||||||
Vector3f pos;
|
|
||||||
Matrix3f m;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class Space {
|
|
||||||
public:
|
|
||||||
Space(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler, std::unique_ptr<INetworkClient>& iNetworkClient, MenuManager& iMenuManager);
|
|
||||||
~Space();
|
|
||||||
|
|
||||||
void setup();
|
|
||||||
void update();
|
|
||||||
|
|
||||||
Renderer& renderer;
|
|
||||||
TaskManager& taskManager;
|
|
||||||
MainThreadHandler& mainThreadHandler;
|
|
||||||
std::unique_ptr<INetworkClient>& networkClient;
|
|
||||||
MenuManager& menuManager;
|
|
||||||
|
|
||||||
|
|
||||||
public:
|
|
||||||
void processTickCount(int64_t newTickCount, int64_t delta);
|
|
||||||
void drawScene();
|
|
||||||
void drawCubemap(float skyPercent);
|
|
||||||
void drawShip();
|
|
||||||
void drawBoxes();
|
|
||||||
void drawBoxesLabels();
|
|
||||||
void drawRemoteShips();
|
|
||||||
void drawRemoteShipsLabels();
|
|
||||||
void fireProjectiles();
|
|
||||||
|
|
||||||
void handleDown(int mx, int my);
|
|
||||||
void handleUp(int mx, int my);
|
|
||||||
void handleMotion(int mx, int my);
|
|
||||||
|
|
||||||
|
|
||||||
std::vector<BoxCoords> boxCoordsArr;
|
|
||||||
std::vector<VertexRenderStruct> boxRenderArr;
|
|
||||||
|
|
||||||
std::vector<std::string> boxLabels;
|
|
||||||
std::unique_ptr<TextRenderer> textRenderer;
|
|
||||||
|
|
||||||
std::unordered_map<int, ClientState> remotePlayerStates;
|
|
||||||
std::unordered_map<int, SparkEmitter> remoteShipSparkEmitters;
|
|
||||||
|
|
||||||
float newShipVelocity = 0;
|
|
||||||
|
|
||||||
static const size_t CONST_TIMER_INTERVAL = 10;
|
|
||||||
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> sparkTexture;
|
|
||||||
std::shared_ptr<Texture> spaceshipTexture;
|
|
||||||
std::shared_ptr<Texture> cubemapTexture;
|
|
||||||
VertexDataStruct spaceshipBase;
|
|
||||||
VertexRenderStruct spaceship;
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> cargoTexture;
|
|
||||||
VertexDataStruct cargoBase;
|
|
||||||
VertexRenderStruct cargo;
|
|
||||||
|
|
||||||
VertexRenderStruct cubemap;
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> boxTexture;
|
|
||||||
VertexDataStruct boxBase;
|
|
||||||
|
|
||||||
SparkEmitter sparkEmitter;
|
|
||||||
SparkEmitter sparkEmitterCargo;
|
|
||||||
SparkEmitter projectileEmitter;
|
|
||||||
SparkEmitter explosionEmitter;
|
|
||||||
PlanetObject planetObject;
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Projectile>> projectiles;
|
|
||||||
std::shared_ptr<Texture> projectileTexture;
|
|
||||||
float projectileCooldownMs = 500.0f;
|
|
||||||
int64_t lastProjectileFireTime = 0;
|
|
||||||
int maxProjectiles = 500;
|
|
||||||
//std::vector<Vector3f> shipLocalEmissionPoints;
|
|
||||||
|
|
||||||
|
|
||||||
bool shipAlive = true;
|
|
||||||
bool gameOver = false;
|
|
||||||
bool firePressed = false;
|
|
||||||
std::vector<bool> boxAlive;
|
|
||||||
float shipCollisionRadius = 15.0f;
|
|
||||||
float boxCollisionRadius = 2.0f;
|
|
||||||
bool showExplosion = false;
|
|
||||||
uint64_t lastExplosionTime = 0;
|
|
||||||
const uint64_t explosionDurationMs = 500;
|
|
||||||
|
|
||||||
bool serverBoxesApplied = false;
|
|
||||||
bool nearPickupBox = false;
|
|
||||||
|
|
||||||
static constexpr float MAX_DIST_SQ = 10000.f * 10000.f;
|
|
||||||
static constexpr float FADE_START = 6000.f;
|
|
||||||
static constexpr float FADE_RANGE = 4000.f;
|
|
||||||
static constexpr float BASE_SCALE = 140.f;
|
|
||||||
static constexpr float PERSPECTIVE_K = 0.05f; // Tune
|
|
||||||
static constexpr float MIN_SCALE = 0.4f;
|
|
||||||
static constexpr float MAX_SCALE = 0.8f;
|
|
||||||
static constexpr float CLOSE_DIST = 600.0f;
|
|
||||||
|
|
||||||
std::unordered_set<int> deadRemotePlayers;
|
|
||||||
int playerScore = 0;
|
|
||||||
int prevPlayerScore = 0;
|
|
||||||
bool wasConnectedToServer = false;
|
|
||||||
|
|
||||||
bool playerListVisible = false;
|
|
||||||
int manualTrackedTargetId = -1;
|
|
||||||
|
|
||||||
static constexpr float TARGET_MAX_DIST = 50000.0f;
|
|
||||||
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
|
|
||||||
|
|
||||||
// --- Target HUD (brackets + offscreen arrow) ---
|
|
||||||
int trackedTargetId = -1;
|
|
||||||
bool targetWasVisible = false;
|
|
||||||
float targetAcquireAnim = 0.0f; // 0..1 схлопывание (0 = далеко, 1 = на месте)
|
|
||||||
|
|
||||||
// временный меш для HUD (будем перезаливать VBO маленькими порциями)
|
|
||||||
VertexRenderStruct hudTempMesh;
|
|
||||||
|
|
||||||
// helpers
|
|
||||||
void drawTargetHud(); // рисует рамку или стрелку
|
|
||||||
int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????)
|
|
||||||
|
|
||||||
void resetPlayerState();
|
|
||||||
void clearTextRendererCache();
|
|
||||||
void updateShowPlayersButtonState();
|
|
||||||
bool showPlayersButtonEnabled = false;
|
|
||||||
|
|
||||||
// Player list overlay
|
|
||||||
void buildAndShowPlayerList();
|
|
||||||
void closePlayerList();
|
|
||||||
void rebuildPlayerListIfVisible();
|
|
||||||
void clearPlayerListIfVisible();
|
|
||||||
std::shared_ptr<UiNode> buildPlayerListRoot();
|
|
||||||
|
|
||||||
void updateSparkEmitters(float deltaMs);
|
|
||||||
void prepareSparkEmittersForDraw();
|
|
||||||
void drawShipSparkEmitters();
|
|
||||||
|
|
||||||
// Crosshair HUD
|
|
||||||
struct CrosshairConfig {
|
|
||||||
bool enabled = true;
|
|
||||||
int refW = 1280;
|
|
||||||
int refH = 720;
|
|
||||||
|
|
||||||
float scaleMul = 1.0f;
|
|
||||||
|
|
||||||
Eigen::Vector3f color = { 1.f, 1.f, 1.f };
|
|
||||||
float alpha = 1.0f; // cl_crosshairalpha
|
|
||||||
float thicknessPx = 2.0f; // cl_crosshairthickness
|
|
||||||
float gapPx = 10.0f;
|
|
||||||
|
|
||||||
float topLenPx = 14.0f;
|
|
||||||
float topAngleDeg = 90.0f;
|
|
||||||
|
|
||||||
struct Arm { float lenPx; float angleDeg; };
|
|
||||||
std::vector<Arm> arms;
|
|
||||||
};
|
|
||||||
|
|
||||||
CrosshairConfig crosshairCfg;
|
|
||||||
bool crosshairCfgLoaded = false;
|
|
||||||
|
|
||||||
// кеш геометрии
|
|
||||||
VertexRenderStruct crosshairMesh;
|
|
||||||
bool crosshairMeshValid = false;
|
|
||||||
int crosshairLastW = 0, crosshairLastH = 0;
|
|
||||||
float crosshairLastAlpha = -1.0f;
|
|
||||||
float crosshairLastThickness = -1.0f;
|
|
||||||
float crosshairLastGap = -1.0f;
|
|
||||||
float crosshairLastScaleMul = -1.0f;
|
|
||||||
|
|
||||||
bool loadCrosshairConfig(const std::string& path);
|
|
||||||
void rebuildCrosshairMeshIfNeeded();
|
|
||||||
void drawCrosshair();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace ZL
|
|
||||||
@ -1,455 +0,0 @@
|
|||||||
#include "PlanetData.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <numeric>
|
|
||||||
#include <cmath>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
Matrix3f GetRotationForTriangle(const Triangle& tri);
|
|
||||||
|
|
||||||
const float PlanetData::PLANET_RADIUS = 20000.f;
|
|
||||||
const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f };
|
|
||||||
|
|
||||||
// --- Константы диапазонов (перенесены из PlanetObject.cpp) ---
|
|
||||||
|
|
||||||
VertexID generateEdgeID(const VertexID& id1, const VertexID& id2) {
|
|
||||||
return id1 < id2 ? id1 + "_" + id2 : id2 + "_" + id1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вспомогательная функция для проекции (локальная)
|
|
||||||
static Vector3f projectPointOnPlane(const Vector3f& P, const Vector3f& A, const Vector3f& B, const Vector3f& C) {
|
|
||||||
Vector3f AB = B + A * (-1.0f);
|
|
||||||
Vector3f AC = C + A * (-1.0f);
|
|
||||||
Vector3f N = AB.cross(AC).normalized();
|
|
||||||
Vector3f AP = P + A * (-1.0f);
|
|
||||||
float distance = N.dot(AP);
|
|
||||||
return P + N * (-distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
PlanetData::PlanetData()
|
|
||||||
: perlin(77777)
|
|
||||||
, colorPerlin(123123)
|
|
||||||
//, currentLod(0)
|
|
||||||
{
|
|
||||||
// currentLod = planetMeshLods.size() - 1; // Start with max LOD
|
|
||||||
/*
|
|
||||||
initialVertexMap = {
|
|
||||||
{{ 0.0f, 1.0f, 0.0f}, "A"},
|
|
||||||
{{ 0.0f, -1.0f, 0.0f}, "B"},
|
|
||||||
{{ 1.0f, 0.0f, 0.0f}, "C"},
|
|
||||||
{{-1.0f, 0.0f, 0.0f}, "D"},
|
|
||||||
{{ 0.0f, 0.0f, 1.0f}, "E"},
|
|
||||||
{{ 0.0f, 0.0f, -1.0f}, "F"}
|
|
||||||
};*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlanetData::init() {
|
|
||||||
for (int i = 0; i < planetMeshLods.size(); i++) {
|
|
||||||
//planetMeshLods[i] = generateSphere(i, 0.01f);
|
|
||||||
planetMeshLods[i] = generateSphere(i, 0.f);
|
|
||||||
planetMeshLods[i].Scale(PLANET_RADIUS);
|
|
||||||
planetMeshLods[i].Move(PLANET_CENTER_OFFSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
planetAtmosphereLod = generateSphere(5, 0);
|
|
||||||
planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03);
|
|
||||||
planetAtmosphereLod.Move(PLANET_CENTER_OFFSET);
|
|
||||||
|
|
||||||
const auto& lodLevel = getLodLevel();
|
|
||||||
|
|
||||||
for (size_t i = 0; i < lodLevel.triangles.size(); i++)
|
|
||||||
{
|
|
||||||
if (i % 100 == 0)
|
|
||||||
{
|
|
||||||
PlanetCampObject campObject;
|
|
||||||
|
|
||||||
campObject.position = (lodLevel.triangles[i].data[0] +
|
|
||||||
lodLevel.triangles[i].data[1] +
|
|
||||||
lodLevel.triangles[i].data[2]) / 3.0f;
|
|
||||||
|
|
||||||
auto newM = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitX())).toRotationMatrix();
|
|
||||||
|
|
||||||
campObject.rotation = GetRotationForTriangle(lodLevel.triangles[i]).inverse() * newM;
|
|
||||||
campObjects.push_back(campObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const LodLevel& PlanetData::getLodLevel() const {
|
|
||||||
return planetMeshLods.at(MAX_LOD_LEVELS-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const LodLevel& PlanetData::getAtmosphereLod() const {
|
|
||||||
return planetAtmosphereLod;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
int PlanetData::getCurrentLodIndex() const {
|
|
||||||
return currentLod;
|
|
||||||
}
|
|
||||||
|
|
||||||
int PlanetData::getMaxLodIndex() const {
|
|
||||||
return static_cast<int>(planetMeshLods.size() - 1);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::pair<float, float> PlanetData::calculateZRange(float dToPlanetSurface) {
|
|
||||||
|
|
||||||
|
|
||||||
float currentZNear;
|
|
||||||
float currentZFar;
|
|
||||||
float alpha;
|
|
||||||
|
|
||||||
if (dToPlanetSurface > 2000)
|
|
||||||
{
|
|
||||||
currentZNear = 1000;
|
|
||||||
currentZFar = currentZNear * 100;
|
|
||||||
}
|
|
||||||
else if (dToPlanetSurface > 1200)
|
|
||||||
{
|
|
||||||
currentZNear = 500;
|
|
||||||
currentZFar = currentZNear * 100;
|
|
||||||
}
|
|
||||||
else if (dToPlanetSurface > 650)
|
|
||||||
{
|
|
||||||
currentZNear = 250;
|
|
||||||
currentZFar = currentZNear * 100;
|
|
||||||
}
|
|
||||||
else if (dToPlanetSurface > 160)
|
|
||||||
{
|
|
||||||
currentZNear = 125;
|
|
||||||
currentZFar = currentZNear * 150;
|
|
||||||
}
|
|
||||||
else if (dToPlanetSurface > 100)
|
|
||||||
{
|
|
||||||
currentZNear = 65;
|
|
||||||
currentZFar = currentZNear * 170;
|
|
||||||
}
|
|
||||||
else if (dToPlanetSurface > 40)
|
|
||||||
{
|
|
||||||
currentZNear = 32;
|
|
||||||
currentZFar = 10000.f;
|
|
||||||
}
|
|
||||||
else if (dToPlanetSurface > 20)
|
|
||||||
{
|
|
||||||
currentZNear = 16;
|
|
||||||
currentZFar = 5000.f;
|
|
||||||
}
|
|
||||||
else if (dToPlanetSurface > 5)
|
|
||||||
{
|
|
||||||
currentZNear = 8;
|
|
||||||
currentZFar = 2500.f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentZNear = 4;
|
|
||||||
currentZFar = 1250.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { currentZNear, currentZFar };
|
|
||||||
}
|
|
||||||
|
|
||||||
float PlanetData::distanceToPlanetSurfaceFast(const Vector3f& viewerPosition)
|
|
||||||
{
|
|
||||||
Vector3f shipLocalPosition = viewerPosition - PLANET_CENTER_OFFSET;
|
|
||||||
|
|
||||||
return sqrt(shipLocalPosition.squaredNorm()) - PLANET_RADIUS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::vector<int> PlanetData::getBestTriangleUnderCamera(const Vector3f& viewerPosition) {
|
|
||||||
const LodLevel& finalLod = planetMeshLods[MAX_LOD_LEVELS - 1]; // Работаем с текущим активным LOD
|
|
||||||
Vector3f targetDir = (viewerPosition - PLANET_CENTER_OFFSET).normalized();
|
|
||||||
|
|
||||||
int bestTriangle = -1;
|
|
||||||
float maxDot = -1.0f;
|
|
||||||
|
|
||||||
// Шаг 1: Быстрый поиск ближайшего треугольника по "центроиду"
|
|
||||||
// Чтобы не проверять все, можно проверять каждый N-й или использовать
|
|
||||||
// предварительно вычисленные центры для LOD0, чтобы сузить круг.
|
|
||||||
// Но для надежности пройдемся по массиву (для 5-6 подразделений это быстро)
|
|
||||||
for (int i = 0; i < (int)finalLod.triangles.size(); ++i) {
|
|
||||||
// Вычисляем примерное направление на треугольник
|
|
||||||
Vector3f triDir = (finalLod.triangles[i].data[0] +
|
|
||||||
finalLod.triangles[i].data[1] +
|
|
||||||
finalLod.triangles[i].data[2]).normalized();
|
|
||||||
|
|
||||||
float dot = targetDir.dot(triDir);
|
|
||||||
if (dot > maxDot) {
|
|
||||||
maxDot = dot;
|
|
||||||
bestTriangle = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestTriangle == -1) return {};
|
|
||||||
|
|
||||||
return { bestTriangle };
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<int> PlanetData::getTrianglesUnderCameraNew2(const Vector3f& viewerPosition) {
|
|
||||||
const LodLevel& finalLod = planetMeshLods[MAX_LOD_LEVELS - 1];
|
|
||||||
Vector3f shipLocal = viewerPosition - PLANET_CENTER_OFFSET;
|
|
||||||
float currentDist = shipLocal.norm();
|
|
||||||
Vector3f targetDir = shipLocal.normalized();
|
|
||||||
|
|
||||||
// Желаемый радиус покрытия на поверхности планеты (в метрах/единицах движка)
|
|
||||||
// Подбери это значение так, чтобы камни вокруг корабля всегда были видны.
|
|
||||||
const float desiredCoverageRadius = 3000.0f;
|
|
||||||
|
|
||||||
// Вычисляем порог косинуса на основе желаемого радиуса и текущего расстояния.
|
|
||||||
// Чем мы дальше (currentDist больше), тем меньше должен быть угол отклонения
|
|
||||||
// от нормали, чтобы захватить ту же площадь.
|
|
||||||
float angle = atan2(desiredCoverageRadius, currentDist);
|
|
||||||
float searchThreshold = cos(angle);
|
|
||||||
|
|
||||||
// Ограничитель, чтобы не захватить всю планету или вообще ничего
|
|
||||||
searchThreshold = std::clamp(searchThreshold, 0.90f, 0.9999f);
|
|
||||||
|
|
||||||
std::vector<int> result;
|
|
||||||
for (int i = 0; i < (int)finalLod.triangles.size(); ++i) {
|
|
||||||
// Используем центроид (можно кэшировать в LodLevel для скорости)
|
|
||||||
Vector3f triDir = (finalLod.triangles[i].data[0] +
|
|
||||||
finalLod.triangles[i].data[1] +
|
|
||||||
finalLod.triangles[i].data[2]).normalized();
|
|
||||||
|
|
||||||
if (targetDir.dot(triDir) > searchThreshold) {
|
|
||||||
result.push_back(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.empty()) return getBestTriangleUnderCamera(viewerPosition);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Triangle> PlanetData::subdivideTriangles(const std::vector<Triangle>& input, float noiseCoeff) {
|
|
||||||
std::vector<Triangle> output;
|
|
||||||
|
|
||||||
for (const auto& t : input) {
|
|
||||||
// Вершины и их ID
|
|
||||||
const Vector3f& a = t.data[0];
|
|
||||||
const Vector3f& b = t.data[1];
|
|
||||||
const Vector3f& c = t.data[2];
|
|
||||||
const VertexID& id_a = t.ids[0];
|
|
||||||
const VertexID& id_b = t.ids[1];
|
|
||||||
const VertexID& id_c = t.ids[2];
|
|
||||||
|
|
||||||
// 1. Вычисляем середины (координаты)
|
|
||||||
Vector3f m_ab = ((a + b) * 0.5f).normalized();
|
|
||||||
Vector3f m_bc = ((b + c) * 0.5f).normalized();
|
|
||||||
Vector3f m_ac = ((a + c) * 0.5f).normalized();
|
|
||||||
|
|
||||||
//Vector3f pm_ab = m_ab * perlin.getSurfaceHeight(m_ab, noiseCoeff);
|
|
||||||
//Vector3f pm_bc = m_bc * perlin.getSurfaceHeight(m_bc, noiseCoeff);
|
|
||||||
//Vector3f pm_ac = m_ac * perlin.getSurfaceHeight(m_ac, noiseCoeff);
|
|
||||||
|
|
||||||
Vector3f pm_ab = m_ab;
|
|
||||||
Vector3f pm_bc = m_bc;
|
|
||||||
Vector3f pm_ac = m_ac;
|
|
||||||
|
|
||||||
// 2. Вычисляем ID новых вершин
|
|
||||||
VertexID id_mab = generateEdgeID(id_a, id_b);
|
|
||||||
VertexID id_mbc = generateEdgeID(id_b, id_c);
|
|
||||||
VertexID id_mac = generateEdgeID(id_a, id_c);
|
|
||||||
|
|
||||||
// 3. Формируем 4 новых треугольника
|
|
||||||
output.emplace_back(Triangle{ {a, pm_ab, pm_ac}, {id_a, id_mab, id_mac} }); // 0
|
|
||||||
output.emplace_back(Triangle{ {pm_ab, b, pm_bc}, {id_mab, id_b, id_mbc} }); // 1
|
|
||||||
output.emplace_back(Triangle{ {pm_ac, pm_bc, c}, {id_mac, id_mbc, id_c} }); // 2
|
|
||||||
output.emplace_back(Triangle{ {pm_ab, pm_bc, pm_ac}, {id_mab, id_mbc, id_mac} }); // 3
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
LodLevel PlanetData::createLodLevel(const std::vector<Triangle>& geometry) {
|
|
||||||
LodLevel result;
|
|
||||||
result.triangles = geometry;
|
|
||||||
|
|
||||||
size_t vertexCount = geometry.size() * 3;
|
|
||||||
|
|
||||||
result.VertexIDs.reserve(vertexCount);
|
|
||||||
|
|
||||||
for (const auto& t : geometry) {
|
|
||||||
for (int i = 0; i < 3; ++i) {
|
|
||||||
result.VertexIDs.push_back(t.ids[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlanetData::recalculateMeshAttributes(LodLevel& lod)
|
|
||||||
{
|
|
||||||
size_t vertexCount = lod.triangles.size() * 3;
|
|
||||||
|
|
||||||
lod.vertexData.PositionData.clear();
|
|
||||||
lod.vertexData.NormalData.clear();
|
|
||||||
lod.vertexData.TexCoordData.clear();
|
|
||||||
lod.vertexData.TangentData.clear();
|
|
||||||
lod.vertexData.BinormalData.clear();
|
|
||||||
lod.vertexData.ColorData.clear();
|
|
||||||
|
|
||||||
lod.vertexData.PositionData.reserve(vertexCount);
|
|
||||||
lod.vertexData.NormalData.reserve(vertexCount);
|
|
||||||
lod.vertexData.TexCoordData.reserve(vertexCount);
|
|
||||||
lod.vertexData.TangentData.reserve(vertexCount);
|
|
||||||
lod.vertexData.BinormalData.reserve(vertexCount);
|
|
||||||
lod.vertexData.ColorData.reserve(vertexCount);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const std::array<Vector2f, 3> triangleUVs = {
|
|
||||||
Vector2f(0.5f, 1.0f),
|
|
||||||
Vector2f(0.0f, 0.0f),
|
|
||||||
Vector2f(1.0f, 0.0f)
|
|
||||||
};
|
|
||||||
|
|
||||||
const Vector3f colorPinkish = { 1.0f, 0.8f, 0.82f }; // Слегка розоватый
|
|
||||||
const Vector3f colorYellowish = { 1.0f, 1.0f, 0.75f }; // Слегка желтоватый
|
|
||||||
|
|
||||||
const float colorFrequency = 4.0f; // Масштаб пятен
|
|
||||||
|
|
||||||
|
|
||||||
for (const auto& t : lod.triangles) {
|
|
||||||
// --- Вычисляем локальный базис треугольника (как в GetRotationForTriangle) ---
|
|
||||||
Vector3f vA = t.data[0];
|
|
||||||
Vector3f vB = t.data[1];
|
|
||||||
Vector3f vC = t.data[2];
|
|
||||||
|
|
||||||
Vector3f x_axis = (vC - vB).normalized(); // Направление U
|
|
||||||
|
|
||||||
Vector3f edge1 = vB - vA;
|
|
||||||
Vector3f edge2 = vC - vA;
|
|
||||||
Vector3f z_axis = edge1.cross(edge2).normalized(); // Нормаль плоскости
|
|
||||||
|
|
||||||
// Проверка направления нормали наружу (от центра планеты)
|
|
||||||
Vector3f centerToTri = (vA + vB + vC).normalized();
|
|
||||||
if (z_axis.dot(centerToTri) < 0) {
|
|
||||||
z_axis = z_axis * -1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3f y_axis = z_axis.cross(x_axis).normalized(); // Направление V
|
|
||||||
|
|
||||||
for (int i = 0; i < 3; ++i) {
|
|
||||||
lod.vertexData.PositionData.push_back(t.data[i]);
|
|
||||||
lod.vertexData.NormalData.push_back(z_axis);
|
|
||||||
lod.vertexData.TexCoordData.push_back(triangleUVs[i]);
|
|
||||||
lod.vertexData.TangentData.push_back(x_axis);
|
|
||||||
lod.vertexData.BinormalData.push_back(y_axis);
|
|
||||||
|
|
||||||
// Используем один шум для выбора между розовым и желтым
|
|
||||||
Vector3f dir = t.data[i].normalized();
|
|
||||||
float blendFactor = colorPerlin.noise(
|
|
||||||
dir(0) * colorFrequency,
|
|
||||||
dir(1) * colorFrequency,
|
|
||||||
dir(2) * colorFrequency
|
|
||||||
);
|
|
||||||
|
|
||||||
// Приводим шум из диапазона [-1, 1] в [0, 1]
|
|
||||||
blendFactor = blendFactor * 0.5f + 0.5f;
|
|
||||||
|
|
||||||
// Линейная интерполяция между двумя цветами
|
|
||||||
Vector3f finalColor;
|
|
||||||
|
|
||||||
finalColor = colorPinkish + blendFactor * (colorYellowish - colorPinkish);
|
|
||||||
|
|
||||||
lod.vertexData.ColorData.push_back(finalColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LodLevel PlanetData::generateSphere(int subdivisions, float noiseCoeff) {
|
|
||||||
const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
|
|
||||||
|
|
||||||
// 12 базовых вершин икосаэдра
|
|
||||||
std::vector<Vector3f> icosaVertices = {
|
|
||||||
{-1, t, 0}, { 1, t, 0}, {-1, -t, 0}, { 1, -t, 0},
|
|
||||||
{ 0, -1, t}, { 0, 1, t}, { 0, -1, -t}, { 0, 1, -t},
|
|
||||||
{ t, 0, -1}, { t, 0, 1}, {-t, 0, -1}, {-t, 0, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Нормализуем вершины
|
|
||||||
for (auto& v : icosaVertices) v = v.normalized();
|
|
||||||
|
|
||||||
// 20 граней икосаэдра
|
|
||||||
struct IndexedTri { int v1, v2, v3; };
|
|
||||||
std::vector<IndexedTri> faces = {
|
|
||||||
{0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11},
|
|
||||||
{1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
|
|
||||||
{3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
|
|
||||||
{4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Triangle> geometry;
|
|
||||||
for (auto& f : faces) {
|
|
||||||
Triangle tri;
|
|
||||||
tri.data[0] = icosaVertices[f.v1];
|
|
||||||
tri.data[1] = icosaVertices[f.v2];
|
|
||||||
tri.data[2] = icosaVertices[f.v3];
|
|
||||||
|
|
||||||
// Генерируем ID для базовых вершин (можно использовать их координаты)
|
|
||||||
for (int i = 0; i < 3; ++i) {
|
|
||||||
tri.ids[i] = std::to_string(tri.data[i](0)) + "_" +
|
|
||||||
std::to_string(tri.data[i](1)) + "_" +
|
|
||||||
std::to_string(tri.data[i](2));
|
|
||||||
}
|
|
||||||
geometry.push_back(tri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Разбиваем N раз
|
|
||||||
for (int i = 0; i < subdivisions; i++) {
|
|
||||||
geometry = subdivideTriangles(geometry, 0.0f); // Шум пока игнорируем
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Создаем LodLevel и заполняем топологию (v2tMap)
|
|
||||||
LodLevel lodLevel = createLodLevel(geometry);
|
|
||||||
|
|
||||||
// Пересобираем v2tMap (она критична для релаксации)
|
|
||||||
lodLevel.v2tMap.clear();
|
|
||||||
for (size_t i = 0; i < geometry.size(); ++i) {
|
|
||||||
for (int j = 0; j < 3; ++j) {
|
|
||||||
lodLevel.v2tMap[geometry[i].ids[j]].push_back((int)i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Применяем итеративную релаксацию (Lloyd-like)
|
|
||||||
// 5-10 итераций достаточно для отличной сетки
|
|
||||||
applySphericalRelaxation(lodLevel, 8);
|
|
||||||
|
|
||||||
// 6. Накладываем шум и обновляем атрибуты
|
|
||||||
// ... (твой код наложения шума через Perlin)
|
|
||||||
|
|
||||||
recalculateMeshAttributes(lodLevel);
|
|
||||||
return lodLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void PlanetData::applySphericalRelaxation(LodLevel& lod, int iterations) {
|
|
||||||
for (int iter = 0; iter < iterations; ++iter) {
|
|
||||||
std::map<VertexID, Vector3f> newPositions;
|
|
||||||
|
|
||||||
for (auto const& [vID, connectedTris] : lod.v2tMap) {
|
|
||||||
Vector3f centroid(0, 0, 0);
|
|
||||||
|
|
||||||
// Находим среднюю точку среди центров всех соседних треугольников
|
|
||||||
for (int triIdx : connectedTris) {
|
|
||||||
const auto& tri = lod.triangles[triIdx];
|
|
||||||
Vector3f faceCenter = (tri.data[0] + tri.data[1] + tri.data[2]) * (1.0f / 3.0f);
|
|
||||||
centroid = centroid + faceCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
centroid = centroid * (1.0f / (float)connectedTris.size());
|
|
||||||
|
|
||||||
// Проецируем обратно на единичную сферу
|
|
||||||
newPositions[vID] = centroid.normalized();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Синхронизируем данные в треугольниках
|
|
||||||
for (auto& tri : lod.triangles) {
|
|
||||||
for (int i = 0; i < 3; ++i) {
|
|
||||||
tri.data[i] = newPositions[tri.ids[i]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "utils/Perlin.h"
|
|
||||||
#include "render/Renderer.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <array>
|
|
||||||
#include <string>
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
|
|
||||||
using VertexID = std::string;
|
|
||||||
using V2TMap = std::map<VertexID, std::vector<int>>;
|
|
||||||
|
|
||||||
struct Vector3fComparator {
|
|
||||||
bool operator()(const Eigen::Vector3f& a, const Eigen::Vector3f& b) const {
|
|
||||||
// Лексикографическое сравнение (x, затем y, затем z)
|
|
||||||
if (a.x() != b.x()) return a.x() < b.x();
|
|
||||||
if (a.y() != b.y()) return a.y() < b.y();
|
|
||||||
return a.z() < b.z();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
VertexID generateEdgeID(const VertexID& id1, const VertexID& id2);
|
|
||||||
|
|
||||||
constexpr static int MAX_LOD_LEVELS = 6;
|
|
||||||
|
|
||||||
struct Triangle
|
|
||||||
{
|
|
||||||
std::array<Vector3f, 3> data;
|
|
||||||
std::array<VertexID, 3> ids;
|
|
||||||
|
|
||||||
Triangle()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Triangle(Vector3f p1, Vector3f p2, Vector3f p3)
|
|
||||||
: data{ p1, p2, p3 }
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Triangle(std::array<Vector3f, 3> idata, std::array<VertexID, 3> iids)
|
|
||||||
: data{ idata }
|
|
||||||
, ids{ iids }
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LodLevel
|
|
||||||
{
|
|
||||||
std::vector<Triangle> triangles;
|
|
||||||
VertexDataStruct vertexData;
|
|
||||||
std::vector<VertexID> VertexIDs;
|
|
||||||
V2TMap v2tMap;
|
|
||||||
|
|
||||||
void Scale(float s)
|
|
||||||
{
|
|
||||||
vertexData.Scale(s);
|
|
||||||
for (auto& t : triangles) {
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
t.data[i] = t.data[i] * s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Move(Vector3f pos)
|
|
||||||
{
|
|
||||||
vertexData.Move(pos);
|
|
||||||
for (auto& t : triangles) {
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
t.data[i] = t.data[i] + pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PlanetCampObject
|
|
||||||
{
|
|
||||||
Vector3f position;
|
|
||||||
Matrix3f rotation;
|
|
||||||
|
|
||||||
std::array<Vector3f, 5> platformPos = {
|
|
||||||
Vector3f{ 0.f, 0.f,-38.f },
|
|
||||||
Vector3f{ 20.f, 0.f,-18.f },
|
|
||||||
Vector3f{ 20.f, 0.f,-58.f },
|
|
||||||
Vector3f{ -20.f, 0.f,-58.f },
|
|
||||||
Vector3f{ -20.f, 0.f,-18.f }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class PlanetData {
|
|
||||||
public:
|
|
||||||
static const float PLANET_RADIUS;
|
|
||||||
static const Vector3f PLANET_CENTER_OFFSET;
|
|
||||||
|
|
||||||
private:
|
|
||||||
PerlinNoise perlin;
|
|
||||||
PerlinNoise colorPerlin;
|
|
||||||
|
|
||||||
std::array<LodLevel, MAX_LOD_LEVELS> planetMeshLods;
|
|
||||||
LodLevel planetAtmosphereLod;
|
|
||||||
|
|
||||||
//int currentLod; // Логический текущий уровень детализации
|
|
||||||
|
|
||||||
//std::map<Vector3f, VertexID, Vector3fComparator> initialVertexMap;
|
|
||||||
|
|
||||||
// Внутренние методы генерации
|
|
||||||
std::vector<Triangle> subdivideTriangles(const std::vector<Triangle>& inputTriangles, float noiseCoeff);
|
|
||||||
LodLevel createLodLevel(const std::vector<Triangle>& triangles);
|
|
||||||
void recalculateMeshAttributes(LodLevel& lod);
|
|
||||||
LodLevel generateSphere(int subdivisions, float noiseCoeff);
|
|
||||||
public:
|
|
||||||
PlanetData();
|
|
||||||
|
|
||||||
void init();
|
|
||||||
const LodLevel& getLodLevel() const;
|
|
||||||
const LodLevel& getAtmosphereLod() const;
|
|
||||||
|
|
||||||
// Логика
|
|
||||||
std::pair<float, float> calculateZRange(float distanceToSurface);
|
|
||||||
float distanceToPlanetSurfaceFast(const Vector3f& viewerPosition);
|
|
||||||
|
|
||||||
// Возвращает индексы треугольников, видимых камерой
|
|
||||||
std::vector<int> getBestTriangleUnderCamera(const Vector3f& viewerPosition);
|
|
||||||
std::vector<int> getTrianglesUnderCameraNew2(const Vector3f& viewerPosition);
|
|
||||||
|
|
||||||
void applySphericalRelaxation(LodLevel& lod, int iterations);
|
|
||||||
|
|
||||||
std::vector<PlanetCampObject> campObjects;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ZL
|
|
||||||
@ -1,611 +0,0 @@
|
|||||||
#include "PlanetObject.h"
|
|
||||||
#include <random>
|
|
||||||
#include <cmath>
|
|
||||||
#include "render/OpenGlExtensions.h"
|
|
||||||
#include "Environment.h"
|
|
||||||
#include "StoneObject.h"
|
|
||||||
#include "utils/TaskManager.h"
|
|
||||||
#include "TextModel.h"
|
|
||||||
#include "GameConstants.h"
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
extern float x;
|
|
||||||
|
|
||||||
#if defined EMSCRIPTEN || defined __ANDROID__
|
|
||||||
using std::min;
|
|
||||||
using std::max;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern const char* CONST_ZIP_FILE;
|
|
||||||
|
|
||||||
Matrix3f GetRotationForTriangle(const Triangle& tri) {
|
|
||||||
|
|
||||||
|
|
||||||
Vector3f vA = tri.data[0];
|
|
||||||
Vector3f vB = tri.data[1];
|
|
||||||
Vector3f vC = tri.data[2];
|
|
||||||
|
|
||||||
// 1. Вычисляем ось X (горизонталь).
|
|
||||||
Vector3f x_axis = (vC - vB).normalized();
|
|
||||||
|
|
||||||
|
|
||||||
// 2. Вычисляем нормаль (ось Z).
|
|
||||||
// Порядок cross product (AB x AC) определит "лицевую" сторону.
|
|
||||||
Vector3f edge1 = vB - vA;
|
|
||||||
Vector3f edge2 = vC - vA;
|
|
||||||
Vector3f z_axis = edge1.cross(edge2).normalized();
|
|
||||||
|
|
||||||
// 3. Вычисляем ось Y (вертикаль).
|
|
||||||
// В ортонормированном базисе Y всегда перпендикулярна Z и X.
|
|
||||||
Vector3f y_axis = z_axis.cross(x_axis).normalized();
|
|
||||||
|
|
||||||
// 4. Формируем прямую матрицу поворота (Rotation/World Matrix).
|
|
||||||
Matrix3f rot;
|
|
||||||
|
|
||||||
// Столбец 0: Ось X
|
|
||||||
rot.data()[0] = x_axis.data()[0];
|
|
||||||
rot.data()[3] = x_axis.data()[1];
|
|
||||||
rot.data()[6] = x_axis.data()[2];
|
|
||||||
|
|
||||||
// Столбец 1: Ось Y
|
|
||||||
rot.data()[1] = y_axis.data()[0];
|
|
||||||
rot.data()[4] = y_axis.data()[1];
|
|
||||||
rot.data()[7] = y_axis.data()[2];
|
|
||||||
|
|
||||||
// Столбец 2: Ось Z
|
|
||||||
rot.data()[2] = z_axis.data()[0];
|
|
||||||
rot.data()[5] = z_axis.data()[1];
|
|
||||||
rot.data()[8] = z_axis.data()[2];
|
|
||||||
|
|
||||||
return rot;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlanetObject::PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler)
|
|
||||||
: renderer(iRenderer),
|
|
||||||
taskManager(iTaskManager),
|
|
||||||
mainThreadHandler(iMainThreadHandler)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlanetObject::init() {
|
|
||||||
// 1. Инициализируем данные (генерация мешей)
|
|
||||||
planetData.init();
|
|
||||||
|
|
||||||
// 2. Забираем данные для VBO
|
|
||||||
// Берем максимальный LOD для начальной отрисовки
|
|
||||||
planetRenderStruct.data = planetData.getLodLevel().vertexData;
|
|
||||||
|
|
||||||
//planetRenderStruct.data.PositionData.resize(9);
|
|
||||||
planetRenderStruct.RefreshVBO();
|
|
||||||
|
|
||||||
|
|
||||||
sandTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/sand2.png", CONST_ZIP_FILE));
|
|
||||||
stoneTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/rockdark3.png", CONST_ZIP_FILE));
|
|
||||||
|
|
||||||
// Атмосфера
|
|
||||||
planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData;
|
|
||||||
if (planetAtmosphereRenderStruct.data.PositionData.size() > 0)
|
|
||||||
{
|
|
||||||
planetAtmosphereRenderStruct.RefreshVBO();
|
|
||||||
}
|
|
||||||
|
|
||||||
planetStones = CreateStoneGroupData(778, planetData.getLodLevel());
|
|
||||||
|
|
||||||
//stonesToRender = planetStones.inflate(planetStones.allInstances.size());
|
|
||||||
stonesToRender.resize(planetStones.allInstances.size());
|
|
||||||
planetStones.initStatuses();
|
|
||||||
|
|
||||||
stoneToBake = planetStones.inflateOne(0, 0.75);
|
|
||||||
|
|
||||||
/*
|
|
||||||
campPlatform.data = LoadFromTextFile02("resources/platform1.txt", CONST_ZIP_FILE);
|
|
||||||
campPlatform.RefreshVBO();
|
|
||||||
|
|
||||||
campPlatformTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/platform_base.png", CONST_ZIP_FILE));
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PlanetObject::update(float deltaTimeMs) {
|
|
||||||
|
|
||||||
// 1. Проверка порога движения (оптимизация из текущего кода)
|
|
||||||
float movementThreshold = 1.0f;
|
|
||||||
if ((Environment::shipState.position - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold
|
|
||||||
&& !triangleIndicesToDraw.empty()) {
|
|
||||||
//processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastUpdatePos = Environment::shipState.position;
|
|
||||||
|
|
||||||
// 2. Получаем список видимых треугольников
|
|
||||||
auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipState.position);
|
|
||||||
std::sort(newIndices.begin(), newIndices.end());
|
|
||||||
|
|
||||||
// 3. Анализируем, что нужно загрузить
|
|
||||||
for (int triIdx : newIndices) {
|
|
||||||
if (planetStones.statuses[triIdx] == ChunkStatus::Empty) {
|
|
||||||
// Помечаем, чтобы не спамить задачами
|
|
||||||
planetStones.statuses[triIdx] = ChunkStatus::Generating;
|
|
||||||
|
|
||||||
// Отправляем тяжелую математику в TaskManager
|
|
||||||
taskManager.EnqueueBackgroundTask([this, triIdx]() {
|
|
||||||
// Выполняется в фоновом потоке: только генерация геометрии в RAM
|
|
||||||
float scaleModifier = 1.0f;
|
|
||||||
VertexDataStruct generatedData = planetStones.inflateOneDataOnly(triIdx, scaleModifier);
|
|
||||||
|
|
||||||
// Передаем задачу на загрузку в GPU в очередь главного потока
|
|
||||||
this->mainThreadHandler.EnqueueMainThreadTask([this, triIdx, data = std::move(generatedData)]() mutable {
|
|
||||||
// Проверяем актуальность: если треугольник всё еще в списке видимых
|
|
||||||
auto it = std::find(triangleIndicesToDraw.begin(), triangleIndicesToDraw.end(), triIdx);
|
|
||||||
if (it != triangleIndicesToDraw.end()) {
|
|
||||||
stonesToRender[triIdx].data = std::move(data);
|
|
||||||
stonesToRender[triIdx].RefreshVBO(); // OpenGL вызов
|
|
||||||
planetStones.statuses[triIdx] = ChunkStatus::Live;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Если уже не нужен — просто сбрасываем статус
|
|
||||||
planetStones.statuses[triIdx] = ChunkStatus::Empty;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Очистка (Unload)
|
|
||||||
// Если статус Live, но треугольника нет в новых индексах — освобождаем GPU память
|
|
||||||
for (size_t i = 0; i < planetStones.statuses.size(); ++i) {
|
|
||||||
if (planetStones.statuses[i] == ChunkStatus::Live) {
|
|
||||||
bool isVisible = std::binary_search(newIndices.begin(), newIndices.end(), (int)i);
|
|
||||||
if (!isVisible) {
|
|
||||||
// Очищаем данные и VBO (деструкторы shared_ptr в VertexRenderStruct сделают glDeleteBuffers)
|
|
||||||
stonesToRender[i].data.PositionData.clear();
|
|
||||||
stonesToRender[i].vao.reset();
|
|
||||||
stonesToRender[i].positionVBO.reset();
|
|
||||||
stonesToRender[i].normalVBO.reset();
|
|
||||||
stonesToRender[i].tangentVBO.reset();
|
|
||||||
stonesToRender[i].binormalVBO.reset();
|
|
||||||
stonesToRender[i].colorVBO.reset();
|
|
||||||
stonesToRender[i].texCoordVBO.reset();
|
|
||||||
|
|
||||||
planetStones.statuses[i] = ChunkStatus::Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
triangleIndicesToDraw = std::move(newIndices);
|
|
||||||
|
|
||||||
// 5. Выполняем одну задачу из очереди OpenGL (RefreshVBO и т.д.)
|
|
||||||
//processMainThreadTasks();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PlanetObject::bakeStoneTexture(Renderer& renderer) {
|
|
||||||
glViewport(0, 0, 512, 512);
|
|
||||||
glClearColor(0,0,0, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
||||||
|
|
||||||
|
|
||||||
static const std::string planetBakeShaderName = "planetBake";
|
|
||||||
|
|
||||||
renderer.shaderManager.PushShader(planetBakeShaderName);
|
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
|
||||||
|
|
||||||
Triangle tr = planetData.getLodLevel().triangles[0];
|
|
||||||
|
|
||||||
// 1. Получаем матрицу вращения (оси в столбцах)
|
|
||||||
Matrix3f mr = GetRotationForTriangle(tr);
|
|
||||||
|
|
||||||
// 2. Трансформируем вершины в локальное пространство экрана, чтобы найти габариты
|
|
||||||
// Используем MultMatrixVector(Matrix, Vector).
|
|
||||||
// Если ваша функция считает V * M, то передайте Inverse(mr).
|
|
||||||
Vector3f rA = mr * tr.data[0];
|
|
||||||
Vector3f rB = mr * tr.data[1];
|
|
||||||
Vector3f rC = mr * tr.data[2];
|
|
||||||
|
|
||||||
// 3. Вычисляем реальные границы треугольника после поворота
|
|
||||||
float minX = min(rA(0), min(rB(0), rC(0)));
|
|
||||||
float maxX = max(rA(0), max(rB(0), rC(0)));
|
|
||||||
float minY = min(rA(1), min(rB(1), rC(1)));
|
|
||||||
float maxY = max(rA(1), max(rB(1), rC(1)));
|
|
||||||
|
|
||||||
|
|
||||||
// Находим центр и размеры
|
|
||||||
float width = maxX - minX;
|
|
||||||
float height = maxY - minY;
|
|
||||||
float centerX = (minX + maxX) * 0.5f;
|
|
||||||
float centerY = (minY + maxY) * 0.5f;
|
|
||||||
|
|
||||||
//width = width * 0.995;
|
|
||||||
//height = height * 0.995;
|
|
||||||
|
|
||||||
renderer.PushProjectionMatrix(
|
|
||||||
centerX - width*0.5, centerX + width * 0.5,
|
|
||||||
centerY - height * 0.5, centerY + height * 0.5,
|
|
||||||
150, 200000);
|
|
||||||
|
|
||||||
renderer.PushMatrix();
|
|
||||||
renderer.LoadIdentity();
|
|
||||||
// Сдвигаем камеру по Z
|
|
||||||
renderer.TranslateMatrix(Vector3f{ 0, 0, -45000 });
|
|
||||||
|
|
||||||
// Применяем вращение
|
|
||||||
renderer.RotateMatrix(mr);
|
|
||||||
|
|
||||||
|
|
||||||
// Извлекаем нормаль треугольника (это 3-й столбец нашей матрицы вращения)
|
|
||||||
Vector3f planeNormal = mr.col(2);
|
|
||||||
|
|
||||||
renderer.RenderUniform3fv("uPlanePoint", tr.data[0].data());
|
|
||||||
renderer.RenderUniform3fv("uPlaneNormal", planeNormal.data());
|
|
||||||
renderer.RenderUniform1f("uMaxHeight", StoneParams::BASE_SCALE * 1.1f); // Соответствует BASE_SCALE + perturbation
|
|
||||||
|
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
|
|
||||||
glEnable(GL_CULL_FACE);
|
|
||||||
glCullFace(GL_BACK); // Отсекаем задние грани
|
|
||||||
if (stoneToBake.data.PositionData.size() > 0)
|
|
||||||
{
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(stoneToBake);
|
|
||||||
CheckGlError();
|
|
||||||
}
|
|
||||||
glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер
|
|
||||||
renderer.PopMatrix();
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
CheckGlError();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PlanetObject::draw(Renderer& renderer) {
|
|
||||||
|
|
||||||
{
|
|
||||||
if (stoneMapFB == nullptr)
|
|
||||||
{
|
|
||||||
//stoneMapFB = std::make_unique<FrameBuffer>(512, 512, true);
|
|
||||||
stoneMapFB = std::make_unique<FrameBuffer>(512, 512);
|
|
||||||
}
|
|
||||||
stoneMapFB->Bind();
|
|
||||||
|
|
||||||
bakeStoneTexture(renderer);
|
|
||||||
|
|
||||||
stoneMapFB->Unbind();
|
|
||||||
|
|
||||||
stoneMapFB->GenerateMipmaps();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
glViewport(0, 0, Environment::width, Environment::height);
|
|
||||||
//--------------------------
|
|
||||||
|
|
||||||
|
|
||||||
drawPlanet(renderer);
|
|
||||||
drawStones(renderer);
|
|
||||||
//drawCamp(renderer);
|
|
||||||
glClear(GL_DEPTH_BUFFER_BIT);
|
|
||||||
drawAtmosphere(renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlanetObject::drawPlanet(Renderer& renderer)
|
|
||||||
{
|
|
||||||
static const std::string planetLandShaderName = "planetLand";
|
|
||||||
|
|
||||||
static const std::string textureUniformName = "Texture";
|
|
||||||
|
|
||||||
renderer.shaderManager.PushShader(planetLandShaderName);
|
|
||||||
|
|
||||||
|
|
||||||
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
|
|
||||||
auto zRange = planetData.calculateZRange(dist);
|
|
||||||
const float currentZNear = zRange.first;
|
|
||||||
const float currentZFar = zRange.second;
|
|
||||||
|
|
||||||
// 2. Применяем динамическую матрицу проекции
|
|
||||||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
|
||||||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
|
||||||
currentZNear, currentZFar);
|
|
||||||
|
|
||||||
renderer.PushMatrix();
|
|
||||||
renderer.LoadIdentity();
|
|
||||||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
|
||||||
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
|
||||||
|
|
||||||
renderer.TranslateMatrix(-Environment::shipState.position);
|
|
||||||
|
|
||||||
const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
|
|
||||||
|
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
|
||||||
renderer.RenderUniform1i("BakedTexture", 1);
|
|
||||||
|
|
||||||
Triangle tr = planetData.getLodLevel().triangles[0]; // Берем базовый треугольник
|
|
||||||
Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании
|
|
||||||
|
|
||||||
// Позиция камеры (корабля) в мире
|
|
||||||
renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data());
|
|
||||||
|
|
||||||
//renderer.RenderUniform1f("uHeightScale", 0.08f);
|
|
||||||
renderer.RenderUniform1f("uHeightScale", 0.0f);
|
|
||||||
|
|
||||||
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
|
|
||||||
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
|
|
||||||
|
|
||||||
// Направление на солнце в мировом пространстве
|
|
||||||
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
|
|
||||||
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
|
|
||||||
|
|
||||||
// Направление от центра планеты к игроку для расчета дня/ночи
|
|
||||||
Vector3f playerDirWorld = Environment::shipState.position.normalized();
|
|
||||||
renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());
|
|
||||||
|
|
||||||
// Тот же фактор освещенности игрока
|
|
||||||
float playerLightFactor = playerDirWorld.dot(-sunDirWorld);
|
|
||||||
playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f);
|
|
||||||
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
|
|
||||||
|
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE1);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID());
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID());
|
|
||||||
//glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID());
|
|
||||||
|
|
||||||
renderer.DrawVertexRenderStruct(planetRenderStruct);
|
|
||||||
CheckGlError();
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
CheckGlError();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlanetObject::drawStones(Renderer& renderer)
|
|
||||||
{
|
|
||||||
static const std::string planetStoneShaderName = "planetStone";
|
|
||||||
|
|
||||||
renderer.shaderManager.PushShader(planetStoneShaderName);
|
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
|
||||||
|
|
||||||
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
|
|
||||||
auto zRange = planetData.calculateZRange(dist);
|
|
||||||
const float currentZNear = zRange.first;
|
|
||||||
const float currentZFar = zRange.second;
|
|
||||||
|
|
||||||
|
|
||||||
// 2. Применяем динамическую матрицу проекции
|
|
||||||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
|
||||||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
|
||||||
currentZNear, currentZFar);
|
|
||||||
|
|
||||||
renderer.PushMatrix();
|
|
||||||
renderer.LoadIdentity();
|
|
||||||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
|
||||||
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
|
||||||
renderer.TranslateMatrix(-Environment::shipState.position);
|
|
||||||
|
|
||||||
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
|
|
||||||
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
|
|
||||||
renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data());
|
|
||||||
//std::cout << "uViewPos" << Environment::shipState.position << std::endl;
|
|
||||||
// PlanetObject.cpp, метод drawStones
|
|
||||||
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
|
|
||||||
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
|
|
||||||
|
|
||||||
Vector3f playerDirWorld = Environment::shipState.position.normalized();
|
|
||||||
float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
|
|
||||||
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
|
|
||||||
|
|
||||||
glEnable(GL_CULL_FACE);
|
|
||||||
glCullFace(GL_BACK);
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());
|
|
||||||
|
|
||||||
/*
|
|
||||||
for (int i : triangleIndicesToDraw)
|
|
||||||
//for (int i = 0; i < stonesToRender.size(); i++)
|
|
||||||
{
|
|
||||||
if (stonesToRender[i].data.PositionData.size() > 0)
|
|
||||||
{
|
|
||||||
renderer.DrawVertexRenderStruct(stonesToRender[i]);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
for (int i : triangleIndicesToDraw) {
|
|
||||||
// КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ:
|
|
||||||
// Проверяем, что данные не просто существуют, а загружены в GPU
|
|
||||||
if (planetStones.statuses[i] == ChunkStatus::Live) {
|
|
||||||
// Дополнительная проверка на наличие данных (на всякий случай)
|
|
||||||
if (stonesToRender[i].data.PositionData.size() > 0) {
|
|
||||||
renderer.DrawVertexRenderStruct(stonesToRender[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Если статус Generating или Empty — мы просто пропускаем этот индекс.
|
|
||||||
// Камни появятся на экране плавно, как только отработает TaskManager и RefreshVBO.
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckGlError();
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glDisable(GL_CULL_FACE);
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
CheckGlError();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlanetObject::drawAtmosphere(Renderer& renderer) {
|
|
||||||
static const std::string defaultShaderName = "defaultAtmosphere";
|
|
||||||
//glClear(GL_DEPTH_BUFFER_BIT);
|
|
||||||
glDepthMask(GL_FALSE);
|
|
||||||
|
|
||||||
renderer.shaderManager.PushShader(defaultShaderName);
|
|
||||||
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
|
|
||||||
auto zRange = planetData.calculateZRange(dist);
|
|
||||||
float currentZNear = zRange.first;
|
|
||||||
float currentZFar = zRange.second;
|
|
||||||
|
|
||||||
if (currentZNear < 200)
|
|
||||||
{
|
|
||||||
currentZNear = 200;
|
|
||||||
currentZFar = currentZNear * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Применяем динамическую матрицу проекции
|
|
||||||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
|
||||||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
|
||||||
currentZNear, currentZFar);
|
|
||||||
|
|
||||||
renderer.PushMatrix();
|
|
||||||
renderer.LoadIdentity();
|
|
||||||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
|
||||||
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
|
||||||
renderer.TranslateMatrix(-Environment::shipState.position);
|
|
||||||
|
|
||||||
const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
|
|
||||||
|
|
||||||
Vector3f color = { 0.0, 0.5, 1.0 };
|
|
||||||
|
|
||||||
renderer.RenderUniform3fv("uColor", color.data());
|
|
||||||
|
|
||||||
renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data());
|
|
||||||
|
|
||||||
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
|
|
||||||
|
|
||||||
// В начале drawAtmosphere или как uniform
|
|
||||||
//float time = SDL_GetTicks() / 1000.0f;
|
|
||||||
//renderer.RenderUniform1f("uTime", time);
|
|
||||||
|
|
||||||
// В методе PlanetObject::drawAtmosphere
|
|
||||||
Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
|
|
||||||
|
|
||||||
// Получаем текущую матрицу вида (ModelView без трансляции объекта)
|
|
||||||
// В вашем движке это Environment::inverseShipMatrix
|
|
||||||
Matrix3f viewMatrix2 = Environment::inverseShipMatrix;
|
|
||||||
|
|
||||||
// Трансформируем вектор света в пространство вида
|
|
||||||
Vector3f viewLightDir = viewMatrix2 * worldLightDir;
|
|
||||||
|
|
||||||
// Передаем инвертированный вектор (направление НА источник)
|
|
||||||
Vector3f lightToSource = -viewLightDir.normalized();
|
|
||||||
renderer.RenderUniform3fv("uLightDirView", lightToSource.data());
|
|
||||||
|
|
||||||
// В методе Game::drawCubemap
|
|
||||||
//renderer.RenderUniform1f("uTime", SDL_GetTicks() / 1000.0f);
|
|
||||||
// Направление света в мировом пространстве для освещения облаков
|
|
||||||
//Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
|
|
||||||
renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data());
|
|
||||||
|
|
||||||
// 1. Рассчитываем uPlayerLightFactor (как в Game.cpp)
|
|
||||||
Vector3f playerDirWorld = Environment::shipState.position.normalized();
|
|
||||||
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
|
|
||||||
|
|
||||||
// Насколько игрок на свету
|
|
||||||
float playerLightFactor = playerDirWorld.dot(-sunDirWorld);
|
|
||||||
playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); // Мягкий порог
|
|
||||||
|
|
||||||
// 2. ОБЯЗАТЕЛЬНО передаем в шейдер
|
|
||||||
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
|
|
||||||
|
|
||||||
// 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было)
|
|
||||||
renderer.RenderUniform3fv("uSkyColor", color.data());
|
|
||||||
|
|
||||||
//Vector3f playerDirWorld = Environment::shipState.position.normalized();
|
|
||||||
renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());
|
|
||||||
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения
|
|
||||||
|
|
||||||
renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct);
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glDepthMask(GL_TRUE);
|
|
||||||
renderer.PopMatrix();
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
CheckGlError();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlanetObject::drawCamp(Renderer& renderer)
|
|
||||||
{
|
|
||||||
renderer.shaderManager.PushShader(defaultShaderName);
|
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
|
||||||
|
|
||||||
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
|
|
||||||
auto zRange = planetData.calculateZRange(dist);
|
|
||||||
const float currentZNear = zRange.first;
|
|
||||||
const float currentZFar = zRange.second;
|
|
||||||
|
|
||||||
|
|
||||||
// 2. Применяем динамическую матрицу проекции
|
|
||||||
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
|
||||||
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
|
||||||
currentZNear, currentZFar);
|
|
||||||
|
|
||||||
renderer.PushMatrix();
|
|
||||||
renderer.LoadIdentity();
|
|
||||||
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
|
||||||
renderer.RotateMatrix(Environment::inverseShipMatrix);
|
|
||||||
renderer.TranslateMatrix(-Environment::shipState.position);
|
|
||||||
|
|
||||||
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
|
|
||||||
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
|
|
||||||
renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data());
|
|
||||||
//std::cout << "uViewPos" << Environment::shipState.position << std::endl;
|
|
||||||
// PlanetObject.cpp, метод drawStones
|
|
||||||
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
|
|
||||||
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
|
|
||||||
|
|
||||||
Vector3f playerDirWorld = Environment::shipState.position.normalized();
|
|
||||||
float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
|
|
||||||
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
|
|
||||||
|
|
||||||
glEnable(GL_CULL_FACE);
|
|
||||||
glCullFace(GL_BACK);
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
||||||
|
|
||||||
for (int i = 0; i < planetData.campObjects.size(); i++)
|
|
||||||
{
|
|
||||||
|
|
||||||
renderer.PushMatrix();
|
|
||||||
for (int j = 0; j < 5; j++)
|
|
||||||
{
|
|
||||||
renderer.PushMatrix();
|
|
||||||
renderer.TranslateMatrix(planetData.campObjects[i].position);
|
|
||||||
renderer.RotateMatrix(planetData.campObjects[i].rotation);
|
|
||||||
renderer.ScaleMatrix(Vector3f{ 2.0f, 2.0f, 2.0f });
|
|
||||||
renderer.TranslateMatrix(planetData.campObjects[i].platformPos[j]);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, campPlatformTexture->getTexID());
|
|
||||||
renderer.DrawVertexRenderStruct(campPlatform);
|
|
||||||
renderer.PopMatrix();
|
|
||||||
}
|
|
||||||
renderer.PopMatrix();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckGlError();
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glDisable(GL_CULL_FACE);
|
|
||||||
|
|
||||||
renderer.PopMatrix();
|
|
||||||
renderer.PopProjectionMatrix();
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
CheckGlError();
|
|
||||||
|
|
||||||
glClear(GL_DEPTH_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition)
|
|
||||||
{
|
|
||||||
return planetData.distanceToPlanetSurfaceFast(viewerPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace ZL
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "render/Renderer.h"
|
|
||||||
#include "render/TextureManager.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <chrono>
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <array>
|
|
||||||
#include <numeric>
|
|
||||||
#include <random>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include "utils/Perlin.h"
|
|
||||||
#include "PlanetData.h"
|
|
||||||
#include "StoneObject.h"
|
|
||||||
#include "render/FrameBuffer.h"
|
|
||||||
#include <queue>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
class TaskManager;
|
|
||||||
class MainThreadHandler;
|
|
||||||
|
|
||||||
class PlanetObject {
|
|
||||||
public:
|
|
||||||
PlanetData planetData;
|
|
||||||
|
|
||||||
// Данные только для рендеринга (OpenGL specific)
|
|
||||||
VertexRenderStruct planetRenderStruct;
|
|
||||||
VertexRenderStruct planetAtmosphereRenderStruct;
|
|
||||||
StoneGroup planetStones;
|
|
||||||
std::vector<VertexRenderStruct> stonesToRender;
|
|
||||||
VertexRenderStruct stoneToBake;
|
|
||||||
|
|
||||||
std::vector<int> triangleIndicesToDraw;
|
|
||||||
|
|
||||||
std::shared_ptr<Texture> sandTexture;
|
|
||||||
std::shared_ptr<Texture> stoneTexture;
|
|
||||||
|
|
||||||
std::unique_ptr<FrameBuffer> stoneMapFB;
|
|
||||||
|
|
||||||
|
|
||||||
VertexRenderStruct campPlatform;
|
|
||||||
std::shared_ptr<Texture> campPlatformTexture;
|
|
||||||
|
|
||||||
Vector3f lastUpdatePos;
|
|
||||||
|
|
||||||
// External items, set outside
|
|
||||||
Renderer& renderer;
|
|
||||||
TaskManager& taskManager;
|
|
||||||
MainThreadHandler& mainThreadHandler;
|
|
||||||
public:
|
|
||||||
PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler);
|
|
||||||
|
|
||||||
void init();
|
|
||||||
void update(float deltaTimeMs);
|
|
||||||
void bakeStoneTexture(Renderer& renderer);
|
|
||||||
void draw(Renderer& renderer);
|
|
||||||
void drawStones(Renderer& renderer);
|
|
||||||
void drawPlanet(Renderer& renderer);
|
|
||||||
void drawAtmosphere(Renderer& renderer);
|
|
||||||
void drawCamp(Renderer& renderer);
|
|
||||||
|
|
||||||
float distanceToPlanetSurface(const Vector3f& viewerPosition);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ZL
|
|
||||||
@ -1,354 +0,0 @@
|
|||||||
#include "StoneObject.h"
|
|
||||||
|
|
||||||
#include "utils/Utils.h"
|
|
||||||
#include <random>
|
|
||||||
#include <cmath>
|
|
||||||
#include "render/Renderer.h"
|
|
||||||
#include "PlanetData.h"
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
#if defined EMSCRIPTEN || defined __ANDROID__
|
|
||||||
using std::min;
|
|
||||||
using std::max;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const float StoneParams::BASE_SCALE = 10.0f; // Общий размер камня
|
|
||||||
const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси
|
|
||||||
const float StoneParams::MAX_AXIS_SCALE = 1.0f; // Максимальное растяжение/сжатие по оси
|
|
||||||
const float StoneParams::MIN_PERTURBATION = 0.0f; // Минимальное радиальное возмущение вершины
|
|
||||||
const float StoneParams::MAX_PERTURBATION = 0.0f; // Максимальное радиальное возмущение вершины
|
|
||||||
const int StoneParams::STONES_PER_TRIANGLE = 40;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Вспомогательная функция для получения случайного числа в диапазоне [min, max]
|
|
||||||
float getRandomFloat(std::mt19937& gen, float min, float max) {
|
|
||||||
std::uniform_real_distribution<> distrib(min, max);
|
|
||||||
return static_cast<float>(distrib(gen));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вспомогательная функция для генерации случайной точки на треугольнике
|
|
||||||
// Использует барицентрические координаты
|
|
||||||
Vector3f GetRandomPointOnTriangle(const Triangle& t, std::mt19937& engine) {
|
|
||||||
std::uniform_real_distribution<> distrib(0.0f, 1.0f);
|
|
||||||
|
|
||||||
float r1 = getRandomFloat(engine, 0.0f, 1.0f);
|
|
||||||
float r2 = getRandomFloat(engine, 0.0f, 1.0f);
|
|
||||||
|
|
||||||
// Преобразование r1, r2 для получения равномерного распределения
|
|
||||||
float a = 1.0f - std::sqrt(r1);
|
|
||||||
float b = std::sqrt(r1) * r2;
|
|
||||||
float c = 1.0f - a - b; // c = sqrt(r1) * (1 - r2)
|
|
||||||
|
|
||||||
// Барицентрические координаты
|
|
||||||
// P = a*p1 + b*p2 + c*p3
|
|
||||||
Vector3f p1_term = t.data[0] * a;
|
|
||||||
Vector3f p2_term = t.data[1] * b;
|
|
||||||
Vector3f p3_term = t.data[2] * c;
|
|
||||||
|
|
||||||
return p1_term + p2_term + p3_term;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Икосаэдр (на основе золотого сечения phi)
|
|
||||||
// Координаты могут быть вычислены заранее для константного икосаэдра.
|
|
||||||
// Здесь только объявление, чтобы показать идею.
|
|
||||||
|
|
||||||
VertexDataStruct CreateBaseConvexPolyhedron(uint64_t seed) {
|
|
||||||
|
|
||||||
// const size_t SUBDIVISION_LEVEL = 1; // Уровень подразделения (для более круглого камня, пока опустим)
|
|
||||||
|
|
||||||
std::mt19937 engine(static_cast<unsigned int>(seed));
|
|
||||||
|
|
||||||
// Золотое сечение
|
|
||||||
const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
|
|
||||||
|
|
||||||
// 12 вершин икосаэдра
|
|
||||||
std::vector<Vector3f> initialVertices = {
|
|
||||||
{ -1, t, 0 }, { 1, t, 0 }, { -1, -t, 0 }, { 1, -t, 0 },
|
|
||||||
{ 0, -1, t }, { 0, 1, t }, { 0, -1, -t }, { 0, 1, -t },
|
|
||||||
{ t, 0, -1 }, { t, 0, 1 }, { -t, 0, -1 }, { -t, 0, 1 }
|
|
||||||
};
|
|
||||||
|
|
||||||
// 20 треугольных граней (индексы вершин)
|
|
||||||
std::vector<std::array<int, 3>> faces = {
|
|
||||||
// 5 треугольников вокруг вершины 0
|
|
||||||
{0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11},
|
|
||||||
// 5 смежных полос
|
|
||||||
{1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
|
|
||||||
// 5 треугольников вокруг вершины 3
|
|
||||||
{3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
|
|
||||||
// 5 смежных полос
|
|
||||||
{4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1. Нормализация и Возмущение (Perturbation)
|
|
||||||
for (Vector3f& v : initialVertices) {
|
|
||||||
v = v.normalized() * StoneParams::BASE_SCALE; // Нормализация к сфере радиуса BASE_SCALE
|
|
||||||
|
|
||||||
// Радиальное возмущение:
|
|
||||||
float perturbation = getRandomFloat(engine, StoneParams::MIN_PERTURBATION, StoneParams::MAX_PERTURBATION);
|
|
||||||
v = v * (1.0f + perturbation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Трансформация (Масштабирование и Поворот)
|
|
||||||
|
|
||||||
// Случайные масштабы по осям
|
|
||||||
Vector3f scaleFactors = {
|
|
||||||
getRandomFloat(engine, StoneParams::MIN_AXIS_SCALE, StoneParams::MAX_AXIS_SCALE),
|
|
||||||
getRandomFloat(engine, StoneParams::MIN_AXIS_SCALE, StoneParams::MAX_AXIS_SCALE),
|
|
||||||
getRandomFloat(engine, StoneParams::MIN_AXIS_SCALE, StoneParams::MAX_AXIS_SCALE)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Применяем масштабирование
|
|
||||||
for (Vector3f& v : initialVertices) {
|
|
||||||
v(0) *= scaleFactors(0);
|
|
||||||
v(1) *= scaleFactors(1);
|
|
||||||
v(2) *= scaleFactors(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Случайный поворот (например, вокруг трех осей)
|
|
||||||
Vector4f qx = Eigen::Quaternionf(Eigen::AngleAxisf(getRandomFloat(engine, 0.0f, 360.0f), Eigen::Vector3f::UnitX()));//QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f));
|
|
||||||
Vector4f qy = Eigen::Quaternionf(Eigen::AngleAxisf(getRandomFloat(engine, 0.0f, 360.0f), Eigen::Vector3f::UnitY()));//QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f));
|
|
||||||
Vector4f qz = Eigen::Quaternionf(Eigen::AngleAxisf(getRandomFloat(engine, 0.0f, 360.0f), Eigen::Vector3f::UnitZ()));//QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f));
|
|
||||||
Vector4f qFinal = slerp(qx, qy, 0.5f); // Простой пример комбинирования
|
|
||||||
qFinal = slerp(qFinal, qz, 0.5f).normalized();
|
|
||||||
Matrix3f rotationMatrix = QuatToMatrix(qFinal);
|
|
||||||
|
|
||||||
for (Vector3f& v : initialVertices) {
|
|
||||||
v = MultMatrixVector(rotationMatrix, v);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// 3. Сглаженные Нормали и Формирование Mesh
|
|
||||||
VertexDataStruct result;
|
|
||||||
// Карта для накопления нормалей по уникальным позициям вершин
|
|
||||||
// (Требует определенного оператора < для Vector3f в ZLMath.h, который у вас есть)
|
|
||||||
std::map<Vector3f, Vector3f, Vector3fComparator> smoothNormalsMap;
|
|
||||||
|
|
||||||
// Предварительное заполнение карты нормалями
|
|
||||||
for (const auto& face : faces) {
|
|
||||||
Vector3f p1 = initialVertices[face[0]];
|
|
||||||
Vector3f p2 = initialVertices[face[1]];
|
|
||||||
Vector3f p3 = initialVertices[face[2]];
|
|
||||||
|
|
||||||
// Нормаль грани: (p2 - p1) x (p3 - p1)
|
|
||||||
Vector3f faceNormal = (p2 - p1).cross(p3 - p1).normalized();
|
|
||||||
|
|
||||||
smoothNormalsMap[p1] = smoothNormalsMap[p1] + faceNormal;
|
|
||||||
smoothNormalsMap[p2] = smoothNormalsMap[p2] + faceNormal;
|
|
||||||
smoothNormalsMap[p3] = smoothNormalsMap[p3] + faceNormal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Нормализация накопленных нормалей
|
|
||||||
for (auto& pair : smoothNormalsMap) {
|
|
||||||
pair.second = pair.second.normalized();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 4. Финальное заполнение VertexDataStruct и Текстурные Координаты
|
|
||||||
for (const auto& face : faces) {
|
|
||||||
Vector3f p1 = initialVertices[face[0]];
|
|
||||||
Vector3f p2 = initialVertices[face[1]];
|
|
||||||
Vector3f p3 = initialVertices[face[2]];
|
|
||||||
|
|
||||||
// Позиции
|
|
||||||
result.PositionData.push_back(p1);
|
|
||||||
result.PositionData.push_back(p2);
|
|
||||||
result.PositionData.push_back(p3);
|
|
||||||
|
|
||||||
// Сглаженные Нормали (из карты)
|
|
||||||
result.NormalData.push_back(smoothNormalsMap[p1]);
|
|
||||||
result.NormalData.push_back(smoothNormalsMap[p2]);
|
|
||||||
result.NormalData.push_back(smoothNormalsMap[p3]);
|
|
||||||
|
|
||||||
// Текстурные Координаты (Планарная проекция на плоскость грани)
|
|
||||||
// p1 -> (0, 0), p2 -> (L_12, 0), p3 -> (L_13 * cos(angle), L_13 * sin(angle))
|
|
||||||
// Где L_xy - длина вектора, angle - угол между p2-p1 и p3-p1
|
|
||||||
|
|
||||||
Vector3f uAxis = (p2 - p1).normalized();
|
|
||||||
Vector3f vRaw = p3 - p1;
|
|
||||||
|
|
||||||
// Проекция vRaw на uAxis
|
|
||||||
float uProjLen = vRaw.dot(uAxis);
|
|
||||||
|
|
||||||
// Вектор V перпендикулярный U: vRaw - uProj
|
|
||||||
Vector3f vAxisRaw = vRaw - (uAxis * uProjLen);
|
|
||||||
|
|
||||||
// Длина оси V
|
|
||||||
float vLen = vAxisRaw.norm();
|
|
||||||
|
|
||||||
// Нормализованная ось V
|
|
||||||
Vector3f vAxis = vAxisRaw.normalized();
|
|
||||||
|
|
||||||
// Координаты (u, v) для p1, p2, p3 относительно p1
|
|
||||||
Vector2f uv1 = { 0.0f, 0.0f };
|
|
||||||
Vector2f uv2 = { (p2 - p1).norm(), 0.0f }; // p2-p1 вдоль оси U
|
|
||||||
Vector2f uv3 = { uProjLen, vLen }; // p3-p1: u-компонента = uProjLen, v-компонента = vLen
|
|
||||||
|
|
||||||
// Находим максимальный размер, чтобы масштабировать в [0, 1]
|
|
||||||
float maxUV = max(uv2(0), max(uv3(0), uv3(1)));
|
|
||||||
|
|
||||||
if (maxUV > 0.000001f) {
|
|
||||||
// Масштабируем:
|
|
||||||
result.TexCoordData.push_back(uv1);
|
|
||||||
result.TexCoordData.push_back(uv2 * (1.f / maxUV));
|
|
||||||
result.TexCoordData.push_back(uv3 * (1.f / maxUV));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Предотвращение деления на ноль для вырожденных граней
|
|
||||||
result.TexCoordData.push_back({ 0.0f, 0.0f });
|
|
||||||
result.TexCoordData.push_back({ 0.0f, 0.0f });
|
|
||||||
result.TexCoordData.push_back({ 0.0f, 0.0f });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Triangle createLocalTriangle(const Triangle& sampleTri)
|
|
||||||
{
|
|
||||||
// Находим центр в 3D
|
|
||||||
Vector3f center = (sampleTri.data[0] + sampleTri.data[1] + sampleTri.data[2]) * (1.0f / 3.0f);
|
|
||||||
|
|
||||||
// Строим базис самого треугольника
|
|
||||||
// vY направляем на 0-ю вершину (как в вашем Special расчете)
|
|
||||||
Vector3f vY = (sampleTri.data[0] - center).normalized();
|
|
||||||
// Временный X для расчета нормали
|
|
||||||
Vector3f vX_temp = (sampleTri.data[1] - sampleTri.data[2]).normalized();
|
|
||||||
// Чистая нормаль
|
|
||||||
Vector3f vZ = vX_temp.cross(vY).normalized();
|
|
||||||
// Чистый X, перпендикулярный Y и Z
|
|
||||||
Vector3f vX = vY.cross(vZ).normalized();
|
|
||||||
|
|
||||||
// Переводим 3D точки в этот 2D базис (Z зануляется сам собой)
|
|
||||||
auto toLocal = [&](const Vector3f& p) {
|
|
||||||
Vector3f d = p - center;
|
|
||||||
return Vector3f{ d.dot(vX), d.dot(vY), 0.0f };
|
|
||||||
};
|
|
||||||
|
|
||||||
Triangle local;
|
|
||||||
local.data[0] = toLocal(sampleTri.data[0]);
|
|
||||||
local.data[1] = toLocal(sampleTri.data[1]);
|
|
||||||
local.data[2] = toLocal(sampleTri.data[2]);
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
|
|
||||||
StoneGroup CreateStoneGroupData(uint64_t globalSeed, const LodLevel& planetLodLevel) {
|
|
||||||
StoneGroup group;
|
|
||||||
group.allInstances.resize(planetLodLevel.triangles.size());
|
|
||||||
|
|
||||||
for (size_t tIdx = 0; tIdx < planetLodLevel.triangles.size(); ++tIdx) {
|
|
||||||
const Triangle& tri = planetLodLevel.triangles[tIdx];
|
|
||||||
std::mt19937 engine(static_cast<unsigned int>(globalSeed));
|
|
||||||
|
|
||||||
for (int i = 0; i < StoneParams::STONES_PER_TRIANGLE; ++i) {
|
|
||||||
StoneInstance instance;
|
|
||||||
instance.seed = globalSeed;// + tIdx * 1000 + i;
|
|
||||||
instance.position = GetRandomPointOnTriangle(tri, engine);
|
|
||||||
|
|
||||||
float SCALE_MIN = 0.75f;
|
|
||||||
float SCALE_MAX = 2.5f;
|
|
||||||
|
|
||||||
instance.scale = {
|
|
||||||
getRandomFloat(engine, SCALE_MIN, SCALE_MAX),
|
|
||||||
getRandomFloat(engine, SCALE_MIN, SCALE_MAX),
|
|
||||||
getRandomFloat(engine, SCALE_MIN, SCALE_MAX)
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (tIdx == 0)
|
|
||||||
{
|
|
||||||
instance.scale = instance.scale * 0.7f;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Vector4f qx = QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f));
|
|
||||||
Vector4f qy = QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f));
|
|
||||||
Vector4f qz = QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f));
|
|
||||||
instance.rotation = slerp(slerp(qx, qy, 0.5f), qz, 0.5f).normalized();
|
|
||||||
*/
|
|
||||||
instance.rotation = Vector4f(0.f, 0.f, 0.f, 1.f);
|
|
||||||
group.allInstances[tIdx].push_back(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<VertexRenderStruct> StoneGroup::inflate(int count)
|
|
||||||
{
|
|
||||||
std::vector<VertexRenderStruct> result;
|
|
||||||
result.resize(count);
|
|
||||||
|
|
||||||
for (int tIdx = 0; tIdx < count; tIdx++)
|
|
||||||
{
|
|
||||||
result[tIdx] = inflateOne(tIdx, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
VertexRenderStruct StoneGroup::inflateOne(int index, float scaleModifier)
|
|
||||||
{
|
|
||||||
static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337);
|
|
||||||
|
|
||||||
VertexRenderStruct result;
|
|
||||||
|
|
||||||
|
|
||||||
for (const auto& inst : allInstances[index]) {
|
|
||||||
Matrix3f rotMat = inst.rotation.toRotationMatrix();
|
|
||||||
|
|
||||||
for (size_t j = 0; j < baseStone.PositionData.size(); ++j) {
|
|
||||||
Vector3f p = baseStone.PositionData[j];
|
|
||||||
Vector3f n = baseStone.NormalData[j];
|
|
||||||
|
|
||||||
p(0) *= inst.scale(0) * scaleModifier;
|
|
||||||
p(1) *= inst.scale(1) * scaleModifier;
|
|
||||||
p(2) *= inst.scale(2) * scaleModifier;
|
|
||||||
|
|
||||||
result.data.PositionData.push_back(rotMat * p + inst.position);
|
|
||||||
result.data.NormalData.push_back(rotMat * n);
|
|
||||||
result.data.TexCoordData.push_back(baseStone.TexCoordData[j]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
result.RefreshVBO();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
VertexDataStruct StoneGroup::inflateOneDataOnly(int index, float scaleModifier)
|
|
||||||
{
|
|
||||||
static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337);
|
|
||||||
|
|
||||||
VertexDataStruct result;
|
|
||||||
|
|
||||||
|
|
||||||
for (const auto& inst : allInstances[index]) {
|
|
||||||
Matrix3f rotMat = inst.rotation.toRotationMatrix();
|
|
||||||
|
|
||||||
for (size_t j = 0; j < baseStone.PositionData.size(); ++j) {
|
|
||||||
Vector3f p = baseStone.PositionData[j];
|
|
||||||
Vector3f n = baseStone.NormalData[j];
|
|
||||||
|
|
||||||
p(0) *= inst.scale(0) * scaleModifier;
|
|
||||||
p(1) *= inst.scale(1) * scaleModifier;
|
|
||||||
p(2) *= inst.scale(2) * scaleModifier;
|
|
||||||
|
|
||||||
result.PositionData.push_back(rotMat * p + inst.position);
|
|
||||||
result.NormalData.push_back(rotMat * n);
|
|
||||||
result.TexCoordData.push_back(baseStone.TexCoordData[j]);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ZL
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "render/Renderer.h"
|
|
||||||
#include "PlanetData.h"
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
struct StoneParams
|
|
||||||
{
|
|
||||||
static const float BASE_SCALE; // Общий размер камня
|
|
||||||
static const float MIN_AXIS_SCALE; // Минимальное растяжение/сжатие по оси
|
|
||||||
static const float MAX_AXIS_SCALE; // Максимальное растяжение/сжатие по оси
|
|
||||||
static const float MIN_PERTURBATION; // Минимальное радиальное возмущение вершины
|
|
||||||
static const float MAX_PERTURBATION; // Максимальное радиальное возмущение вершины
|
|
||||||
static const int STONES_PER_TRIANGLE;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct StoneInstance {
|
|
||||||
uint64_t seed;
|
|
||||||
Vector3f position;
|
|
||||||
Vector3f scale;
|
|
||||||
Eigen::Quaternionf rotation;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ChunkStatus {
|
|
||||||
Empty, // Данных нет
|
|
||||||
Generating, // Задача в TaskManager (CPU)
|
|
||||||
ReadyToUpload, // Данные в памяти, ждут очереди в главный поток
|
|
||||||
Live // Загружено в GPU и готово к отрисовке
|
|
||||||
};
|
|
||||||
|
|
||||||
struct StoneGroup {
|
|
||||||
// mesh.PositionData и прочие будут заполняться в inflate()
|
|
||||||
VertexDataStruct mesh;
|
|
||||||
|
|
||||||
std::vector<std::vector<StoneInstance>> allInstances;
|
|
||||||
|
|
||||||
// Очищает старую геометрию и генерирует новую для указанных индексов
|
|
||||||
std::vector<VertexRenderStruct> inflate(int count);
|
|
||||||
|
|
||||||
VertexRenderStruct inflateOne(int index, float scaleModifier);
|
|
||||||
VertexDataStruct inflateOneDataOnly(int index, float scaleModifier);
|
|
||||||
|
|
||||||
std::vector<ChunkStatus> statuses;
|
|
||||||
|
|
||||||
// Инициализация статусов при создании группы
|
|
||||||
void initStatuses() {
|
|
||||||
statuses.assign(allInstances.size(), ChunkStatus::Empty);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Теперь возвращает заготовку со всеми параметрами, но без тяжелого меша
|
|
||||||
StoneGroup CreateStoneGroupData(uint64_t globalSeed, const LodLevel& lodLevel);
|
|
||||||
|
|
||||||
Triangle createLocalTriangle(const Triangle& sampleTri);
|
|
||||||
|
|
||||||
} // namespace ZL
|
|
||||||
Loading…
Reference in New Issue
Block a user