1116 lines
47 KiB
C++
1116 lines
47 KiB
C++
#include "Character.h"
|
|
#include "render/TextRenderer.h"
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <random>
|
|
#include <array>
|
|
#include "GameConstants.h"
|
|
#include "Environment.h"
|
|
#include "render/TextureManager.h"
|
|
#include "utils/Utils.h"
|
|
#include "TextModel.h"
|
|
#include "game_server/IGameServer.h"
|
|
#include "navigation/PathFinder.h"
|
|
#include "Location.h"
|
|
|
|
namespace FRG {
|
|
extern float x;
|
|
extern float y;
|
|
|
|
Dialogue::TranslationDatabase Character::nameTranslations;
|
|
|
|
std::shared_ptr<const CharacterState> Character::stateSnapshot() const {
|
|
auto snapshot = gameServer_.snapshot();
|
|
if (!snapshot) return {};
|
|
|
|
auto player = snapshot->players.find(entityId_);
|
|
if (player != snapshot->players.end()) {
|
|
return std::shared_ptr<const CharacterState>(
|
|
std::move(snapshot), &player->second.state);
|
|
}
|
|
|
|
auto location = snapshot->locations.find(locationId_);
|
|
if (location == snapshot->locations.end()) return {};
|
|
auto npc = location->second.npcs.find(entityId_);
|
|
if (npc == location->second.npcs.end()) return {};
|
|
return std::shared_ptr<const CharacterState>(
|
|
std::move(snapshot), &npc->second.state);
|
|
}
|
|
|
|
bool Character::isMoving() const {
|
|
auto state = stateSnapshot();
|
|
return state && state->isMoving();
|
|
}
|
|
|
|
Eigen::Vector3f Character::getRequestedWalkTarget() const {
|
|
auto state = stateSnapshot();
|
|
return state ? state->getRequestedWalkTarget() : Eigen::Vector3f::Zero();
|
|
}
|
|
|
|
Eigen::Vector3f Character::getCurrentNavigationTarget() const {
|
|
auto state = stateSnapshot();
|
|
return state ? state->getCurrentNavigationTarget() : Eigen::Vector3f::Zero();
|
|
}
|
|
|
|
float Character::getHp() const {
|
|
auto state = stateSnapshot();
|
|
return state ? state->getHp() : 0.f;
|
|
}
|
|
|
|
void Character::loadAnimation(AnimationState animState, const std::string& filename, const std::string& zipFile) {
|
|
auto& data = animations[animState];
|
|
data.model.LoadFromFile(filename, zipFile);
|
|
if (!data.model.animations.empty() && !data.model.animations[0].keyFrames.empty()) {
|
|
data.totalFrames = data.model.animations[0].keyFrames.back().frame + 1;
|
|
} else {
|
|
data.totalFrames = 1;
|
|
}
|
|
}
|
|
|
|
void Character::loadBinaryAnimation(AnimationState animState, const std::string& filename, const std::string& zipFile) {
|
|
auto& data = animations[animState];
|
|
data.model.LoadFromBinaryFile(filename, zipFile);
|
|
if (!data.model.animations.empty() && !data.model.animations[0].keyFrames.empty()) {
|
|
data.totalFrames = data.model.animations[0].keyFrames.back().frame + 1;
|
|
} else {
|
|
data.totalFrames = 1;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<Character> Character::createFromState(const CharacterState& st, IGameServer& server,
|
|
const std::string& locationId, const std::string& entityId,
|
|
Renderer& renderer, const char* zipFile) {
|
|
auto c = std::make_unique<Character>(server, locationId, entityId);
|
|
|
|
const std::string zip = zipFile ? zipFile : "";
|
|
|
|
auto loadAnim = [&](AnimationState animState, const std::string& path) {
|
|
if (path.empty()) return;
|
|
try {
|
|
if (path.size() >= 5 && path.substr(path.size() - 5) == ".anim")
|
|
c->loadBinaryAnimation(animState, path, zip);
|
|
else
|
|
c->loadAnimation(animState, path, zip);
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "Character::createFromState: failed to load animation '" << path << "': " << e.what() << std::endl;
|
|
}
|
|
};
|
|
|
|
for (const auto& [animState, path] : st.creationInfo.animationPaths)
|
|
loadAnim(animState, path);
|
|
|
|
auto loadTex = [&](const std::string& path) -> std::shared_ptr<Texture> {
|
|
if (path.empty()) return nullptr;
|
|
try {
|
|
//return std::make_shared<Texture>(CreateTextureDataFromPng(path, zip));
|
|
return renderer.textureManager.LoadFromPng(path, zip);
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "Character::createFromState: failed to load texture '" << path << "': " << e.what() << std::endl;
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
if (!st.creationInfo.singleTexturePath.empty()) {
|
|
auto tex = loadTex(st.creationInfo.singleTexturePath);
|
|
if (tex) c->setTexture(tex);
|
|
}
|
|
for (const auto& [meshName, texPath] : st.creationInfo.meshTexturePaths) {
|
|
auto tex = loadTex(texPath);
|
|
if (tex) c->setTexture(meshName, tex);
|
|
}
|
|
|
|
if (!st.creationInfo.weaponMeshPath.empty()) {
|
|
const std::string& wp = st.creationInfo.weaponMeshPath;
|
|
try {
|
|
if (wp.size() >= 4 && wp.substr(wp.size() - 4) == ".bin")
|
|
c->weaponMesh.data = LoadModelFromBinFile(wp, zip);
|
|
else
|
|
c->weaponMesh.data = LoadFromTextFile02(wp, zip);
|
|
|
|
if (st.creationInfo.weaponMeshMove != Eigen::Vector3f::Zero())
|
|
c->weaponMesh.data.Move(st.creationInfo.weaponMeshMove);
|
|
if (st.creationInfo.weaponMeshScale != 1.0f)
|
|
c->weaponMesh.data.Scale(st.creationInfo.weaponMeshScale);
|
|
|
|
c->weaponMesh.RefreshVBO();
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "Character::createFromState: failed to load weapon mesh '" << wp << "': " << e.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
c->weaponTexture = loadTex(st.creationInfo.weaponTexturePath);
|
|
c->weaponAttachBoneName = st.creationInfo.weaponAttachBoneName;
|
|
c->weaponInitialPosition = st.creationInfo.weaponInitialPosition;
|
|
c->weaponInitialRotation = st.creationInfo.weaponInitialRotation.toRotationMatrix();
|
|
|
|
return c;
|
|
}
|
|
|
|
void Character::setTexture(std::shared_ptr<FRG::Texture> texture) {
|
|
for (auto& animEntry : animations) {
|
|
for (const auto& name : animEntry.second.model.meshNamesOrdered) {
|
|
meshTextures[name] = texture;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Character::setTexture(const std::string& meshName, std::shared_ptr<Texture> tex) {
|
|
meshTextures[meshName] = std::move(tex);
|
|
}
|
|
|
|
AnimationState Character::resolveActiveState(const CharacterState& state) const {
|
|
if (animations.count(state.currentState)) return state.currentState;
|
|
return AnimationState::STAND;
|
|
}
|
|
|
|
void Character::update(int64_t deltaMs, const CharacterUpdateEvents* events) {
|
|
if (hitSparkEmitter.isConfigured()) {
|
|
hitSparkEmitter.update(static_cast<float>(deltaMs));
|
|
}
|
|
|
|
auto stateSnapshot = this->stateSnapshot();
|
|
if (!stateSnapshot) return;
|
|
const CharacterState& state = *stateSnapshot;
|
|
|
|
if (events) {
|
|
if (events->deathAnimationComplete && onDeathAnimComplete)
|
|
onDeathAnimComplete();
|
|
if (events->attackImpact && audioPlayer) {
|
|
audioPlayer->playSoundAsync(state.isPlayer
|
|
? "resources/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg"
|
|
: "resources/audio/13925__adcbicycle__21.ogg");
|
|
}
|
|
}
|
|
|
|
if (!state.enabled) return;
|
|
auto it = animations.find(state.currentState);
|
|
if (it == animations.end()) return;
|
|
|
|
auto& anim = it->second;
|
|
anim.currentFrame = (std::min)(
|
|
state.animationFrame, static_cast<float>((std::max)(0, anim.totalFrames - 1)));
|
|
|
|
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
|
|
anim.gpuSkinningShaderData.ComputeSkinningMatrices(
|
|
anim.model.startBones, anim.model.animations[0].keyFrames,
|
|
static_cast<int>(anim.currentFrame));
|
|
if (!state.useGpuSkinning) {
|
|
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
|
|
}
|
|
anim.lastFrame = static_cast<int>(anim.currentFrame);
|
|
}
|
|
}
|
|
|
|
void Character::draw(Renderer& renderer) {
|
|
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
|
|
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
|
|
}
|
|
|
|
renderer.RenderUniform1f("uAlpha", 1.0f);
|
|
glDisable(GL_BLEND);
|
|
|
|
auto stateSnapshot = this->stateSnapshot();
|
|
if (!stateSnapshot) return;
|
|
const CharacterState& state = *stateSnapshot;
|
|
if (!state.enabled) return;
|
|
|
|
if (state.useGpuSkinning) {
|
|
drawGpuSkinning(renderer, state);
|
|
return;
|
|
}
|
|
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
|
|
renderer.shaderManager.PushShader("fog");
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
const float playerEyePos[3] = { 0.0f, 0.0f, -Environment::zoom };
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePos);
|
|
|
|
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.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
auto& anim = it->second;
|
|
for (const auto& name : anim.model.meshNamesOrdered) {
|
|
auto mit = anim.model.meshes.find(name);
|
|
if (mit == anim.model.meshes.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
|
|
modelMutable.AssignFrom(mit->second.mesh);
|
|
modelMutable.RefreshVBO();
|
|
renderer.DrawVertexRenderStruct(modelMutable);
|
|
}
|
|
|
|
renderer.PopMatrix();
|
|
|
|
drawAttachedWeapon(renderer, state);
|
|
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
}
|
|
|
|
void Character::drawGpuSkinning(Renderer& renderer, const CharacterState& state) {
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
|
|
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;
|
|
anim.gpuSkinningShaderData.ComputeSkinningMatrices(
|
|
anim.model.startBones, anim.model.animations[0].keyFrames,
|
|
static_cast<int>(anim.currentFrame));
|
|
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) return;
|
|
}
|
|
|
|
static const std::string skinningShaderName = "fog_skinning";
|
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
|
|
|
renderer.shaderManager.PushShader(skinningShaderName);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
const float playerEyePosSkin[3] = { 0.0f, 0.0f, -Environment::zoom };
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosSkin);
|
|
|
|
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.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
|
static_cast<int>(anim.gpuSkinningShaderData.skinningMatrices.size()), false,
|
|
anim.gpuSkinningShaderData.skinningMatrices[0].data());
|
|
|
|
for (const auto& name : anim.model.meshNamesOrdered) {
|
|
auto pit = anim.gpuSkinningShaderData.perMesh.find(name);
|
|
if (pit == anim.gpuSkinningShaderData.perMesh.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
|
|
pit->second.RenderVBO(renderer);
|
|
}
|
|
|
|
renderer.PopMatrix();
|
|
|
|
renderer.shaderManager.PushShader("fog");
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosSkin);
|
|
drawAttachedWeapon(renderer, state);
|
|
renderer.shaderManager.PopShader();
|
|
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
}
|
|
|
|
bool Character::prepareGpuSkinning(const CharacterState& state) {
|
|
AnimationState drawState = resolveActiveState(state);
|
|
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;
|
|
}
|
|
|
|
void Character::drawAttachedWeapon(Renderer& renderer, const CharacterState& state) {
|
|
if (!state.showWeapon) return;
|
|
if (weaponMesh.data.PositionData.size() == 0 || !weaponTexture) return;
|
|
|
|
auto it = animations.find(resolveActiveState(state));
|
|
if (it == animations.end()) return;
|
|
|
|
auto& anim = it->second;
|
|
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) return;
|
|
|
|
int boneIdx = anim.model.findBoneIndex(weaponAttachBoneName);
|
|
if (boneIdx < 0) return;
|
|
|
|
const Eigen::Matrix4f& bindBone = anim.model.animations[0].keyFrames[0].bones[boneIdx].boneMatrixWorld;
|
|
Eigen::Matrix4f currentBone = anim.gpuSkinningShaderData.skinningMatrices[boneIdx] * bindBone;
|
|
|
|
renderer.PushMatrix();
|
|
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
renderer.TranslateMatrix(Eigen::Vector3f(currentBone.block<3, 1>(0, 3)));
|
|
renderer.RotateMatrix(Eigen::Matrix3f(currentBone.block<3, 3>(0, 0)));
|
|
renderer.TranslateMatrix(weaponInitialPosition);
|
|
renderer.RotateMatrix(weaponInitialRotation);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, weaponTexture->getTexID());
|
|
renderer.DrawVertexRenderStruct(weaponMesh);
|
|
renderer.PopMatrix();
|
|
}
|
|
|
|
// ==================== Shadow depth pass ====================
|
|
|
|
void Character::drawShadowDepth(Renderer& renderer) {
|
|
auto stateSnapshot = this->stateSnapshot();
|
|
if (!stateSnapshot) return;
|
|
const CharacterState& state = *stateSnapshot;
|
|
if (!state.enabled) return;
|
|
if (state.useGpuSkinning) {
|
|
drawShadowDepthGpuSkinning(renderer, state);
|
|
} else {
|
|
drawShadowDepthCpu(renderer, state);
|
|
}
|
|
}
|
|
|
|
void Character::drawShadowDepthCpu(Renderer& renderer, const CharacterState& state) {
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
|
|
renderer.PushMatrix();
|
|
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
auto& anim = it->second;
|
|
for (const auto& name : anim.model.meshNamesOrdered) {
|
|
auto mit = anim.model.meshes.find(name);
|
|
if (mit == anim.model.meshes.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
modelMutable.AssignFrom(mit->second.mesh);
|
|
modelMutable.RefreshVBO();
|
|
renderer.DrawVertexRenderStruct(modelMutable);
|
|
}
|
|
renderer.PopMatrix();
|
|
|
|
drawAttachedWeapon(renderer, state);
|
|
}
|
|
|
|
void Character::drawShadowDepthGpuSkinning(Renderer& renderer, const CharacterState& state) {
|
|
CheckGlError(__FILE__, __LINE__);
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
if (!prepareGpuSkinning(state)) return;
|
|
CheckGlError(__FILE__, __LINE__);
|
|
|
|
static const std::string shadowSkinningShader = "shadow_depth_skinning";
|
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
|
|
|
renderer.shaderManager.PushShader(shadowSkinningShader);
|
|
|
|
renderer.PushMatrix();
|
|
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
|
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
|
|
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
for (const auto& name : it->second.model.meshNamesOrdered) {
|
|
auto pit = it->second.gpuSkinningShaderData.perMesh.find(name);
|
|
if (pit == it->second.gpuSkinningShaderData.perMesh.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
pit->second.RenderVBO(renderer);
|
|
}
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
renderer.PopMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
|
|
drawAttachedWeapon(renderer, state);
|
|
CheckGlError(__FILE__, __LINE__);
|
|
}
|
|
|
|
// ==================== Main pass with shadows ====================
|
|
|
|
void Character::drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
|
|
auto stateSnapshot = this->stateSnapshot();
|
|
if (!stateSnapshot) return;
|
|
const CharacterState& state = *stateSnapshot;
|
|
if (!state.enabled) return;
|
|
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
|
|
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
|
|
}
|
|
|
|
renderer.RenderUniform1f("uAlpha", 1.0f);
|
|
glDisable(GL_BLEND);
|
|
|
|
if (state.useGpuSkinning) {
|
|
drawGpuSkinningWithShadow(renderer, state, lightFromCamera, shadowMapTex, lightDirCamera);
|
|
} else {
|
|
drawCpuWithShadow(renderer, state, lightFromCamera, shadowMapTex, lightDirCamera);
|
|
}
|
|
}
|
|
|
|
void Character::drawCpuWithShadow(Renderer& renderer, const CharacterState& state, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
|
|
static const std::string shadowShader = "fog_shadow";
|
|
|
|
renderer.shaderManager.PushShader(shadowShader);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform1i("uShadowMap", 1);
|
|
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
|
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
|
|
const float playerEyePosShadow[3] = { 0.0f, 0.0f, -Environment::zoom };
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosShadow);
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(GL_TEXTURE_2D, shadowMapTex);
|
|
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.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
auto& anim = it->second;
|
|
for (const auto& name : anim.model.meshNamesOrdered) {
|
|
auto mit = anim.model.meshes.find(name);
|
|
if (mit == anim.model.meshes.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
|
|
modelMutable.AssignFrom(mit->second.mesh);
|
|
modelMutable.RefreshVBO();
|
|
renderer.DrawVertexRenderStruct(modelMutable);
|
|
}
|
|
|
|
renderer.PopMatrix();
|
|
|
|
renderer.shaderManager.PushShader("fog");
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosShadow);
|
|
drawAttachedWeapon(renderer, state);
|
|
renderer.shaderManager.PopShader();
|
|
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
}
|
|
|
|
void Character::drawGpuSkinningWithShadow(Renderer& renderer, const CharacterState& state, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
if (!prepareGpuSkinning(state)) return;
|
|
CheckGlError(__FILE__, __LINE__);
|
|
|
|
static const std::string skinningShadowShader = "fog_skinning_shadow";
|
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
|
|
|
renderer.shaderManager.PushShader(skinningShadowShader);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform1i("uShadowMap", 1);
|
|
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
|
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
|
|
const float playerEyePosSkinShadow[3] = { 0.0f, 0.0f, -Environment::zoom };
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosSkinShadow);
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(GL_TEXTURE_2D, shadowMapTex);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
|
|
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.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
|
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
|
|
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
|
|
for (const auto& name : it->second.model.meshNamesOrdered) {
|
|
auto pit = it->second.gpuSkinningShaderData.perMesh.find(name);
|
|
if (pit == it->second.gpuSkinningShaderData.perMesh.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
|
|
pit->second.RenderVBO(renderer);
|
|
}
|
|
|
|
renderer.PopMatrix();
|
|
|
|
renderer.shaderManager.PushShader("fog");
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosSkinShadow);
|
|
drawAttachedWeapon(renderer, state);
|
|
renderer.shaderManager.PopShader();
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
}
|
|
|
|
// ==================== Night pass with point lights ====================
|
|
|
|
void Character::drawNight(Renderer& renderer,
|
|
const float* pointLightPosEye, const float* pointLightDirEye,
|
|
const float* pointLightColors, int pointLightCount,
|
|
const float* pointLightWorldXZ, const float* pointLightLimitXZ,
|
|
const Eigen::Matrix4f& cameraViewInverse,
|
|
const float* ambientColor, const float* fogColor)
|
|
{
|
|
auto stateSnapshot = this->stateSnapshot();
|
|
if (!stateSnapshot) return;
|
|
const CharacterState& state = *stateSnapshot;
|
|
if (!state.enabled) return;
|
|
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
|
|
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
|
|
}
|
|
|
|
renderer.RenderUniform1f("uAlpha", 1.0f);
|
|
glDisable(GL_BLEND);
|
|
|
|
if (state.useGpuSkinning) {
|
|
drawNightGpuSkinning(renderer, state, pointLightPosEye, pointLightDirEye, pointLightColors, pointLightCount,
|
|
pointLightWorldXZ, pointLightLimitXZ, cameraViewInverse, ambientColor, fogColor);
|
|
} else {
|
|
drawNightCpu(renderer, state, pointLightPosEye, pointLightDirEye, pointLightColors, pointLightCount,
|
|
pointLightWorldXZ, pointLightLimitXZ, cameraViewInverse, ambientColor, fogColor);
|
|
}
|
|
}
|
|
|
|
void Character::drawNightGpuSkinning(Renderer& renderer,
|
|
const CharacterState& state,
|
|
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
|
|
const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse,
|
|
const float* ambientColor, const float* fogColor)
|
|
{
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
|
|
if (!prepareGpuSkinning(state)) return;
|
|
|
|
static const std::string nightSkinningShader = "night_fog_skinning";
|
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
|
|
|
renderer.shaderManager.PushShader(nightSkinningShader);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform3fv("uAmbientColor", ambientColor);
|
|
renderer.RenderUniform3fv("uFogColor", fogColor);
|
|
const float playerEyePosNight[3] = { 0.0f, 0.0f, -Environment::zoom };
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNight);
|
|
renderer.RenderUniform1i("uPointLightCount", plCount);
|
|
if (plCount > 0) {
|
|
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
|
|
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
|
|
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
|
|
renderer.RenderUniform2fvArray("uPointLightWorldXZ[0]", plCount, plWorldXZ);
|
|
renderer.RenderUniform2fvArray("uPointLightLimitXZ[0]", plCount, plLimitXZ);
|
|
}
|
|
renderer.RenderUniformMatrix4fv("uViewInverse", false, cameraViewInverse.data());
|
|
|
|
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.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
|
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
|
|
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
|
|
|
|
for (const auto& name : it->second.model.meshNamesOrdered) {
|
|
auto pit = it->second.gpuSkinningShaderData.perMesh.find(name);
|
|
if (pit == it->second.gpuSkinningShaderData.perMesh.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
|
|
pit->second.RenderVBO(renderer);
|
|
}
|
|
|
|
renderer.PopMatrix();
|
|
|
|
renderer.shaderManager.PushShader("night_fog");
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform3fv("uAmbientColor", ambientColor);
|
|
renderer.RenderUniform3fv("uFogColor", fogColor);
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNight);
|
|
renderer.RenderUniform1i("uPointLightCount", plCount);
|
|
if (plCount > 0) {
|
|
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
|
|
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
|
|
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
|
|
renderer.RenderUniform2fvArray("uPointLightWorldXZ[0]", plCount, plWorldXZ);
|
|
renderer.RenderUniform2fvArray("uPointLightLimitXZ[0]", plCount, plLimitXZ);
|
|
}
|
|
drawAttachedWeapon(renderer, state);
|
|
renderer.shaderManager.PopShader();
|
|
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
}
|
|
|
|
void Character::drawNightCpu(Renderer& renderer,
|
|
const CharacterState& state,
|
|
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
|
|
const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse,
|
|
const float* ambientColor, const float* fogColor)
|
|
{
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
|
|
static const std::string nightShader = "night_fog";
|
|
|
|
renderer.shaderManager.PushShader(nightShader);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform3fv("uAmbientColor", ambientColor);
|
|
renderer.RenderUniform3fv("uFogColor", fogColor);
|
|
const float playerEyePosNight[3] = { 0.0f, 0.0f, -Environment::zoom };
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNight);
|
|
renderer.RenderUniform1i("uPointLightCount", plCount);
|
|
if (plCount > 0) {
|
|
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
|
|
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
|
|
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
|
|
renderer.RenderUniform2fvArray("uPointLightWorldXZ[0]", plCount, plWorldXZ);
|
|
renderer.RenderUniform2fvArray("uPointLightLimitXZ[0]", plCount, plLimitXZ);
|
|
}
|
|
|
|
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.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
auto& anim = it->second;
|
|
for (const auto& name : anim.model.meshNamesOrdered) {
|
|
auto mit = anim.model.meshes.find(name);
|
|
if (mit == anim.model.meshes.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
|
|
modelMutable.AssignFrom(mit->second.mesh);
|
|
modelMutable.RefreshVBO();
|
|
renderer.DrawVertexRenderStruct(modelMutable);
|
|
}
|
|
|
|
renderer.PopMatrix();
|
|
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNight);
|
|
drawAttachedWeapon(renderer, state);
|
|
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
}
|
|
|
|
// ==================== Night pass with spot-light + shadow ====================
|
|
|
|
void Character::drawNightWithShadow(Renderer& renderer,
|
|
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
|
|
const float* plWorldXZ, const float* plLimitXZ,
|
|
const Eigen::Matrix4f& cameraViewInverse,
|
|
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex,
|
|
const float* ambientColor, const float* fogColor)
|
|
{
|
|
auto stateSnapshot = this->stateSnapshot();
|
|
if (!stateSnapshot) return;
|
|
const CharacterState& state = *stateSnapshot;
|
|
if (!state.enabled) return;
|
|
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
|
|
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
|
|
}
|
|
renderer.RenderUniform1f("uAlpha", 1.0f);
|
|
glDisable(GL_BLEND);
|
|
|
|
if (state.useGpuSkinning) {
|
|
drawNightGpuSkinningWithShadow(renderer, state, plPosEye, plDirEye, plColors, plCount,
|
|
plWorldXZ, plLimitXZ, cameraViewInverse,
|
|
lightFromCamera, shadowMapTex, ambientColor, fogColor);
|
|
} else {
|
|
drawNightCpuWithShadow(renderer, state, plPosEye, plDirEye, plColors, plCount,
|
|
plWorldXZ, plLimitXZ, cameraViewInverse,
|
|
lightFromCamera, shadowMapTex, ambientColor, fogColor);
|
|
}
|
|
}
|
|
|
|
void Character::drawNightGpuSkinningWithShadow(Renderer& renderer,
|
|
const CharacterState& state,
|
|
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
|
|
const float* plWorldXZ, const float* plLimitXZ,
|
|
const Eigen::Matrix4f& cameraViewInverse,
|
|
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex,
|
|
const float* ambientColor, const float* fogColor)
|
|
{
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
if (!prepareGpuSkinning(state)) return;
|
|
|
|
static const std::string nightShadowSkinningShader = "night_fog_skinning_shadow";
|
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
|
|
|
renderer.shaderManager.PushShader(nightShadowSkinningShader);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform1i("uShadowMap", 1);
|
|
renderer.RenderUniform3fv("uAmbientColor", ambientColor);
|
|
renderer.RenderUniform3fv("uFogColor", fogColor);
|
|
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
|
if (plCount > 0) renderer.RenderUniform3fv("uLightDir", plDirEye);
|
|
|
|
const float playerEyePosNightShadow[3] = { 0.0f, 0.0f, -Environment::zoom };
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNightShadow);
|
|
renderer.RenderUniform1i("uPointLightCount", plCount);
|
|
if (plCount > 0) {
|
|
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
|
|
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
|
|
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
|
|
}
|
|
renderer.RenderUniform2fvArray("uPointLightWorldXZ[0]", plCount, plWorldXZ);
|
|
renderer.RenderUniform2fvArray("uPointLightLimitXZ[0]", plCount, plLimitXZ);
|
|
renderer.RenderUniformMatrix4fv("uViewInverse", false, cameraViewInverse.data());
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(GL_TEXTURE_2D, shadowMapTex);
|
|
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.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
|
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
|
|
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
|
|
|
|
for (const auto& name : it->second.model.meshNamesOrdered) {
|
|
auto pit = it->second.gpuSkinningShaderData.perMesh.find(name);
|
|
if (pit == it->second.gpuSkinningShaderData.perMesh.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
|
|
pit->second.RenderVBO(renderer);
|
|
}
|
|
|
|
renderer.PopMatrix();
|
|
|
|
renderer.shaderManager.PushShader("night_fog_shadow");
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform1i("uShadowMap", 1);
|
|
renderer.RenderUniform3fv("uAmbientColor", ambientColor);
|
|
renderer.RenderUniform3fv("uFogColor", fogColor);
|
|
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
|
if (plCount > 0) renderer.RenderUniform3fv("uLightDir", plDirEye);
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNightShadow);
|
|
renderer.RenderUniform1i("uPointLightCount", plCount);
|
|
if (plCount > 0) {
|
|
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
|
|
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
|
|
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
|
|
}
|
|
renderer.RenderUniform2fvArray("uPointLightWorldXZ[0]", plCount, plWorldXZ);
|
|
renderer.RenderUniform2fvArray("uPointLightLimitXZ[0]", plCount, plLimitXZ);
|
|
drawAttachedWeapon(renderer, state);
|
|
renderer.shaderManager.PopShader();
|
|
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
}
|
|
|
|
void Character::drawNightCpuWithShadow(Renderer& renderer,
|
|
const CharacterState& state,
|
|
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
|
|
const float* plWorldXZ, const float* plLimitXZ,
|
|
const Eigen::Matrix4f& cameraViewInverse,
|
|
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex,
|
|
const float* ambientColor, const float* fogColor)
|
|
{
|
|
AnimationState drawState = resolveActiveState(state);
|
|
auto it = animations.find(drawState);
|
|
if (it == animations.end()) return;
|
|
|
|
static const std::string nightShadowShader = "night_fog_shadow";
|
|
|
|
renderer.shaderManager.PushShader(nightShadowShader);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform1i("uShadowMap", 1);
|
|
renderer.RenderUniform3fv("uAmbientColor", ambientColor);
|
|
renderer.RenderUniform3fv("uFogColor", fogColor);
|
|
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
|
if (plCount > 0) renderer.RenderUniform3fv("uLightDir", plDirEye);
|
|
|
|
const float playerEyePosNightShadow[3] = { 0.0f, 0.0f, -Environment::zoom };
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNightShadow);
|
|
renderer.RenderUniform1i("uPointLightCount", plCount);
|
|
if (plCount > 0) {
|
|
renderer.RenderUniform3fvArray("uPointLightPos[0]", plCount, plPosEye);
|
|
renderer.RenderUniform3fvArray("uPointLightDir[0]", plCount, plDirEye);
|
|
renderer.RenderUniform3fvArray("uPointLightColor[0]", plCount, plColors);
|
|
}
|
|
renderer.RenderUniform2fvArray("uPointLightWorldXZ[0]", plCount, plWorldXZ);
|
|
renderer.RenderUniform2fvArray("uPointLightLimitXZ[0]", plCount, plLimitXZ);
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(GL_TEXTURE_2D, shadowMapTex);
|
|
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.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
|
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
renderer.ScaleMatrix(state.modelScale);
|
|
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
|
|
|
|
auto& anim = it->second;
|
|
for (const auto& name : anim.model.meshNamesOrdered) {
|
|
auto mit = anim.model.meshes.find(name);
|
|
if (mit == anim.model.meshes.end()) continue;
|
|
auto tit = meshTextures.find(name);
|
|
if (tit == meshTextures.end() || !tit->second) continue;
|
|
glBindTexture(GL_TEXTURE_2D, tit->second->getTexID());
|
|
modelMutable.AssignFrom(mit->second.mesh);
|
|
modelMutable.RefreshVBO();
|
|
renderer.DrawVertexRenderStruct(modelMutable);
|
|
}
|
|
|
|
renderer.PopMatrix();
|
|
|
|
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePosNightShadow);
|
|
drawAttachedWeapon(renderer, state);
|
|
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
}
|
|
|
|
void Character::setupHitSparks(std::shared_ptr<Texture> sparkTexture) {
|
|
hitSparkEmitter.setTexture(sparkTexture);
|
|
hitSparkEmitter.setShaderProgramName("spark");
|
|
hitSparkEmitter.setMaxParticles(128);
|
|
hitSparkEmitter.setParticleSize(0.02f);
|
|
hitSparkEmitter.setBiasX(0.0f);
|
|
hitSparkEmitter.setEmissionRate(1.0e9f);
|
|
hitSparkEmitter.setSpeedRange(2.0f, 4.0f);
|
|
hitSparkEmitter.setZSpeedRange(0.5f, 2.0f);
|
|
hitSparkEmitter.setScaleRange(0.8f, 1.2f);
|
|
hitSparkEmitter.setLifeTimeRange(300.0f, 600.0f);
|
|
hitSparkEmitter.setIsActive(false);
|
|
hitSparkEmitter.markConfigured();
|
|
}
|
|
|
|
void Character::presentDamage(float hp, float maxHp,
|
|
const Eigen::Vector3f& attackDirection,
|
|
const std::string& attackerId) {
|
|
if (onHpChanged)
|
|
onHpChanged(hp, maxHp, attackerId);
|
|
|
|
if (!hitSparkEmitter.isConfigured()) return;
|
|
auto stateSnapshot = this->stateSnapshot();
|
|
if (!stateSnapshot) return;
|
|
const CharacterState& state = *stateSnapshot;
|
|
|
|
Eigen::Vector3f emitPos = state.position + Eigen::Vector3f(0.f, 1.f, 0.f);
|
|
hitSparkEmitter.resetEmissionPoints({ emitPos });
|
|
hitSparkEmitter.setEmissionDirection(attackDirection);
|
|
|
|
static std::mt19937 gen(std::random_device{}());
|
|
std::uniform_int_distribution<int> countDist(10, 15);
|
|
int count = countDist(gen);
|
|
for (int i = 0; i < count; ++i) {
|
|
hitSparkEmitter.emit();
|
|
}
|
|
}
|
|
|
|
void Character::prepareHitSparksForDraw(const Eigen::Matrix4f& cameraViewMatrix) {
|
|
if (!hitSparkEmitter.isConfigured()) return;
|
|
hitSparkEmitter.prepareForDraw(cameraViewMatrix);
|
|
}
|
|
|
|
static void drawCharacterNameLabel(
|
|
TextRenderer& textRenderer,
|
|
const Eigen::Matrix4f& cameraViewMatrix,
|
|
const Eigen::Matrix4f& projectionMatrix,
|
|
const CharacterState& state,
|
|
const std::string& displayName,
|
|
const std::array<float, 4>& color)
|
|
{
|
|
if (!state.enabled || state.hp <= 0.f || displayName.empty()) return;
|
|
|
|
Eigen::Vector4f worldPos(
|
|
state.position.x(), state.position.y() + 1.83f, state.position.z(), 1.f);
|
|
Eigen::Vector4f clip = projectionMatrix * cameraViewMatrix * worldPos;
|
|
if (clip.w() <= 0.f) return;
|
|
|
|
float ndcX = clip.x() / clip.w();
|
|
float ndcY = clip.y() / clip.w();
|
|
if (ndcX < -1.f || ndcX > 1.f || ndcY < -1.f || ndcY > 1.f) return;
|
|
|
|
float screenX = (ndcX * 0.5f + 0.5f) * Environment::projectionWidth;
|
|
float screenY = (ndcY * 0.5f + 0.5f) * Environment::projectionHeight;
|
|
textRenderer.drawText(displayName, screenX, screenY, 1.0f, true, color);
|
|
}
|
|
|
|
void Character::drawName(TextRenderer& textRenderer,
|
|
const Eigen::Matrix4f& cameraViewMatrix,
|
|
const Eigen::Matrix4f& projectionMatrix)
|
|
{
|
|
auto stateSnapshot = this->stateSnapshot();
|
|
if (!stateSnapshot) return;
|
|
const CharacterState& state = *stateSnapshot;
|
|
if (state.isPlayer) return;
|
|
if (state.npcName.empty()) return;
|
|
|
|
std::array<float, 4> color = state.canAttack
|
|
? std::array<float, 4>{ 1.f, 0.f, 0.f, 1.f }
|
|
: std::array<float, 4>{ 0.f, 1.f, 0.f, 1.f };
|
|
|
|
const std::string& translatedName = Character::nameTranslations.translateRef(state.npcName, g_currentLanguage);
|
|
|
|
drawCharacterNameLabel(
|
|
textRenderer, cameraViewMatrix, projectionMatrix,
|
|
state, translatedName, color);
|
|
}
|
|
|
|
void Character::drawPlayerName(
|
|
TextRenderer& textRenderer,
|
|
const Eigen::Matrix4f& cameraViewMatrix,
|
|
const Eigen::Matrix4f& projectionMatrix,
|
|
const std::string& displayName)
|
|
{
|
|
auto snapshot = stateSnapshot();
|
|
if (!snapshot) return;
|
|
static const std::array<float, 4> color = {0.f, 1.f, 0.f, 1.f};
|
|
drawCharacterNameLabel(
|
|
textRenderer, cameraViewMatrix, projectionMatrix,
|
|
*snapshot, displayName, color);
|
|
}
|
|
|
|
void Character::drawHealthBar(Renderer& renderer,
|
|
const Eigen::Matrix4f& cameraViewMatrix,
|
|
const Eigen::Matrix4f& projectionMatrix)
|
|
{
|
|
auto stateSnapshot = this->stateSnapshot();
|
|
if (!stateSnapshot) return;
|
|
const CharacterState& state = *stateSnapshot;
|
|
if (state.isPlayer || !state.canAttack) return;
|
|
if (!state.enabled) return;
|
|
if (state.hp <= 0.f) return;
|
|
if (state.initialHp <= 0.f) return;
|
|
|
|
Eigen::Vector4f worldPos(state.position.x(), state.position.y() + 1.75f, state.position.z(), 1.f);
|
|
Eigen::Vector4f clip = projectionMatrix * cameraViewMatrix * worldPos;
|
|
if (clip.w() <= 0.f) return;
|
|
float ndcX = clip.x() / clip.w();
|
|
float ndcY = clip.y() / clip.w();
|
|
if (ndcX < -1.2f || ndcX > 1.2f || ndcY < -1.2f || ndcY > 1.2f) return;
|
|
|
|
const float sx = (ndcX * 0.5f + 0.5f) * Environment::projectionWidth;
|
|
const float sy = (ndcY * 0.5f + 0.5f) * Environment::projectionHeight;
|
|
|
|
const float barW = 60.f;
|
|
const float barH = 6.f;
|
|
|
|
const float left = sx - barW * 0.5f;
|
|
const float right = sx + barW * 0.5f;
|
|
const float bottom = sy - barH * 0.5f;
|
|
const float top = sy + barH * 0.5f;
|
|
|
|
float frac = state.hp / state.initialHp;
|
|
if (frac < 0.f) frac = 0.f;
|
|
if (frac > 1.f) frac = 1.f;
|
|
const float split = left + barW * frac;
|
|
|
|
VertexDataStruct data;
|
|
const Eigen::Vector3f green(0.f, 1.f, 0.f);
|
|
const Eigen::Vector3f red (1.f, 0.f, 0.f);
|
|
|
|
auto pushQuad = [&](float x0, float x1, const Eigen::Vector3f& col) {
|
|
data.PositionData.push_back({ x0, bottom, 0.f });
|
|
data.PositionData.push_back({ x1, bottom, 0.f });
|
|
data.PositionData.push_back({ x1, top, 0.f });
|
|
data.PositionData.push_back({ x0, bottom, 0.f });
|
|
data.PositionData.push_back({ x1, top, 0.f });
|
|
data.PositionData.push_back({ x0, top, 0.f });
|
|
for (int i = 0; i < 6; ++i) data.ColorData.push_back(col);
|
|
};
|
|
|
|
if (frac > 0.f) pushQuad(left, split, green);
|
|
if (frac < 1.f) pushQuad(split, right, red);
|
|
|
|
healthBarMesh.AssignFrom(data);
|
|
healthBarMesh.RefreshVBO();
|
|
|
|
renderer.shaderManager.PushShader("defaultColor");
|
|
renderer.PushProjectionMatrix(0.f, Environment::projectionWidth,
|
|
0.f, Environment::projectionHeight,
|
|
-1.f, 1.f);
|
|
renderer.PushMatrix();
|
|
renderer.LoadIdentity();
|
|
|
|
renderer.DrawVertexRenderStruct(healthBarMesh);
|
|
|
|
renderer.PopMatrix();
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
}
|
|
|
|
} // namespace FRG
|