space-game001/src/Character.cpp
2026-04-22 22:38:20 +03:00

695 lines
24 KiB
C++

#include "Character.h"
#include <cmath>
#include <iostream>
#include "GameConstants.h"
#include "Environment.h"
namespace ZL {
const float ATTACK_COOLDOWN_TIME = 3.0f;
extern float x;
extern float y;
void Character::loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile) {
auto& data = animations[state];
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 state, const std::string& filename, const std::string& zipFile) {
auto& data = animations[state];
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;
}
}
void Character::setTarget(const Eigen::Vector3f& target,
std::function<void()> onArrived) {
Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z());
const bool sameRequestedTarget =
(normalizedTarget - requestedWalkTarget).norm() <= TARGET_REPLAN_THRESHOLD;
const bool alreadyMovingToTarget =
!pathWaypoints.empty() || (walkTarget - normalizedTarget).norm() <= TARGET_REPLAN_THRESHOLD;
const bool stoppedAfterFailedPath =
pathWaypoints.empty() && (walkTarget - position).norm() <= TARGET_REPLAN_THRESHOLD;
if (!onArrived && sameRequestedTarget && (alreadyMovingToTarget || stoppedAfterFailedPath)) {
return;
}
requestedWalkTarget = normalizedTarget;
onArrivedCallback = std::move(onArrived);
if (pathPlanner) {
pathWaypoints = pathPlanner(position, normalizedTarget);
currentWaypointIndex = 0;
if (!pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : pathWaypoints) {
waypoint.y() = 0.f;
}
walkTarget = pathWaypoints.back();
return;
}
walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z());
onArrivedCallback = nullptr;
return;
}
pathWaypoints.clear();
currentWaypointIndex = 0;
walkTarget = normalizedTarget;
}
void Character::setPathPlanner(PathPlanner planner) {
pathPlanner = std::move(planner);
}
void Character::setTexture(std::shared_ptr<ZL::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);
}
/*
void Character::setTexture(std::shared_ptr<Texture> tex) {
for (auto& animEntry : animations) {
for (const auto& name : animEntry.second.model.meshNamesOrdered) {
meshTextures[name] = tex;
}
}
}
*/
AnimationState Character::resolveActiveState() const {
if (animations.count(currentState)) return currentState;
return AnimationState::STAND;
}
void Character::update(int64_t deltaMs) {
//weaponInitialRotation = Eigen::AngleAxisf(x/180.0, Eigen::Vector3f::UnitZ()).toRotationMatrix();
//weaponInitialRotation = Eigen::AngleAxisf(-M_PI*0.5, Eigen::Vector3f::UnitZ()).toRotationMatrix();
//weaponInitialPosition = Eigen::Vector3f(0, 0.09, 0.016);
/*if (deltaMs > 200)
{
deltaMs = 200;
}*/
Eigen::Vector3f activeTarget;
Eigen::Vector3f lookTarget;
if (attackTarget == nullptr)
{
battle_state = 0;
attack = 0;
}
if (attackTarget != nullptr)
{
float distToGhost = (attackTarget->position - position).norm();
if (distToGhost >= 10.f)
{
if (isPlayer)
{
setTarget(attackTarget->position);
}
battle_state = 0;
}
else if (distToGhost < 10.0f && distToGhost >= 1.f) {
setTarget(attackTarget->position);
battle_state = 0;
}
else
{
battle_state = 1;
}
}
if (!pathWaypoints.empty() && currentWaypointIndex < pathWaypoints.size()) {
activeTarget = pathWaypoints[currentWaypointIndex];
}
else
{
activeTarget = walkTarget;
}
if (battle_state == 0)
{
lookTarget = activeTarget;
}
else
{
lookTarget = attackTarget->position;
}
targetFacingAngle = facingAngle;
if (currentState == AnimationState::WALK || currentState == AnimationState::STAND)
{
Eigen::Vector3f toTarget = activeTarget - position;
toTarget.y() = 0.f;
float dist = toTarget.norm();
if (dist > WALK_THRESHOLD) {
Eigen::Vector3f dir = toTarget / dist;
float moveAmount = walkSpeed * static_cast<float>(deltaMs) / 1000.f;
if (moveAmount >= dist) {
position = activeTarget;
position.y() = 0.f;
}
else {
position += dir * moveAmount;
}
targetFacingAngle = atan2(dir.x(), -dir.z());
currentState = AnimationState::WALK;
}
else {
currentState = AnimationState::STAND;
const bool hasNextWaypoint = !pathWaypoints.empty() && currentWaypointIndex + 1 < pathWaypoints.size();
if (hasNextWaypoint) {
++currentWaypointIndex;
}
else {
pathWaypoints.clear();
currentWaypointIndex = 0;
}
if (!hasNextWaypoint && onArrivedCallback) {
auto cb = std::move(onArrivedCallback);
onArrivedCallback = nullptr;
cb();
}
targetFacingAngle = facingAngle;
}
}
if (battle_state == 1)
{
targetFacingAngle = atan2(lookTarget.x() - position.x(), -(lookTarget.z() - position.z()));
if (currentState == AnimationState::STAND || currentState == AnimationState::WALK) {
currentState = AnimationState::STAND_TO_ACTION;
resetAnim = true;
}
if (canAttack && attack == 0 && attack_cooldown < 0.f && currentState == AnimationState::ACTION_IDLE)
{
attack = 1;
attack_cooldown = ATTACK_COOLDOWN_TIME;
}
if (canAttack && attack_cooldown >= 0.f)
{
attack_cooldown = attack_cooldown - deltaMs / 1000.f;
}
if (attack == 1 && currentState == AnimationState::ACTION_IDLE)
{
currentState = AnimationState::ACTION_ATTACK;
resetAnim = true;
}
}
else
{
if (currentState == AnimationState::STAND_TO_ACTION
|| currentState == AnimationState::ACTION_IDLE
|| currentState == AnimationState::ACTION_ATTACK
) {
currentState = AnimationState::ACTION_TO_STAND;
resetAnim = true;
}
}
// Rotate toward target facing angle at constant angular speed
float angleDiff = targetFacingAngle - facingAngle;
while (angleDiff > static_cast<float>(M_PI)) angleDiff -= 2.f * static_cast<float>(M_PI);
while (angleDiff < -static_cast<float>(M_PI)) angleDiff += 2.f * static_cast<float>(M_PI);
float rotStep = rotationSpeed * static_cast<float>(deltaMs) / 1000.f;
if (std::fabs(angleDiff) <= rotStep) {
facingAngle = targetFacingAngle;
} else {
facingAngle += (angleDiff > 0.f ? rotStep : -rotStep);
}
auto it = animations.find(currentState);
if (it == animations.end()) return;
auto& anim = it->second;
if (resetAnim)
{
resetAnim = false;
anim.currentFrame = 0;
}
anim.currentFrame += static_cast<float>(deltaMs) / 24.f;
//std::cout << "Current animation frame: " << anim.currentFrame << " / " << anim.totalFrames << " -- " << anim.lastFrame << std::endl;
if (static_cast<int>(anim.currentFrame) >= 20 && currentState == AnimationState::STAND_TO_ACTION)
{
showWeapon = true;
}
else if (static_cast<int>(anim.currentFrame) <= 32 && currentState == AnimationState::ACTION_TO_STAND)
{
showWeapon = true;
}
else
{
showWeapon = false;
}
int frms = anim.model.animations[0].keyFrames[anim.model.animations[0].keyFrames.size() - 1].frame;
if (static_cast<int>(anim.currentFrame) >= frms) {
anim.currentFrame = anim.model.startingFrame;
if (currentState == AnimationState::STAND_TO_ACTION)
{
currentState = AnimationState::ACTION_IDLE;
resetAnim = true;
}
if (currentState == AnimationState::ACTION_TO_STAND)
{
currentState = AnimationState::STAND;
resetAnim = true;
}
if (currentState == AnimationState::ACTION_ATTACK)
{
currentState = AnimationState::ACTION_IDLE;
resetAnim = true;
attack = 0;
}
}
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
anim.gpuSkinningShaderData.ComputeSkinningMatrices(anim.model.startBones, anim.model.animations[0].keyFrames, static_cast<int>(anim.currentFrame));
if (!useGpuSkinning) {
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
}
anim.lastFrame = static_cast<int>(anim.currentFrame);
}
}
void Character::draw(Renderer& renderer) {
if (useGpuSkinning) {
drawGpuSkinning(renderer);
return;
}
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end()) return;
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.TranslateMatrix({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(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);
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
void Character::drawGpuSkinning(Renderer& renderer) {
AnimationState drawState = resolveActiveState();
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 = "skinning";
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
renderer.shaderManager.PushShader(skinningShaderName);
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.TranslateMatrix({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
// Upload bone skinning matrices
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(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
drawAttachedWeapon(renderer);
renderer.shaderManager.PopShader();
renderer.PopProjectionMatrix();
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;
}
void Character::drawAttachedWeapon(Renderer& renderer)
{
if (!showWeapon) return;
if (weaponMesh.data.PositionData.size() == 0 || !weaponTexture) return;
auto it = animations.find(resolveActiveState());
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({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(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) {
if (useGpuSkinning) {
drawShadowDepthGpuSkinning(renderer);
} else {
drawShadowDepthCpu(renderer);
}
}
void Character::drawShadowDepthCpu(Renderer& renderer) {
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end()) return;
// The caller has already pushed the shadow_depth shader and the
// light's projection + view onto the renderer stacks.
renderer.PushMatrix();
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(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);
}
void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
CheckGlError(__FILE__, __LINE__);
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end()) return;
CheckGlError(__FILE__, __LINE__);
if (!prepareGpuSkinning()) return;
CheckGlError(__FILE__, __LINE__);
static const std::string shadowSkinningShader = "shadow_depth_skinning";
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
// Switch to the skinning depth shader (caller has the static depth shader active).
renderer.shaderManager.PushShader(shadowSkinningShader);
renderer.PushMatrix();
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(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);
CheckGlError(__FILE__, __LINE__);
}
// ==================== Main pass with shadows ====================
void Character::drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
if (useGpuSkinning) {
drawGpuSkinningWithShadow(renderer, lightFromCamera, shadowMapTex, lightDirCamera);
} else {
drawCpuWithShadow(renderer, lightFromCamera, shadowMapTex, lightDirCamera);
}
}
void Character::drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end()) return;
static const std::string shadowShader = "default_shadow";
renderer.shaderManager.PushShader(shadowShader);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1i("uShadowMap", 1);
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
renderer.RenderUniform3fv("uLightDir", lightDirCamera.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({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(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(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
drawAttachedWeapon(renderer);
renderer.shaderManager.PopShader();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end()) return;
CheckGlError(__FILE__, __LINE__);
if (!prepareGpuSkinning()) return;
CheckGlError(__FILE__, __LINE__);
static const std::string skinningShadowShader = "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());
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({ position.x(), position.y(), position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(modelScale);
renderer.RotateMatrix(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(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
drawAttachedWeapon(renderer);
renderer.shaderManager.PopShader();
CheckGlError(__FILE__, __LINE__);
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
CheckGlError(__FILE__, __LINE__);
}
} // namespace ZL