#include "Character.h"
#include <cmath>
#include <iostream>
#include "GameConstants.h"
#include "Environment.h"

namespace ZL {

	const float ATTACK_COOLDOWN_TIME = 3.0f; 

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::setTexture(std::shared_ptr<Texture> tex) {
    texture = tex;
}*/

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);
}

AnimationState Character::resolveActiveState() const {
    
    if (animations.count(currentState)) return currentState;
    return AnimationState::STAND;
}

void Character::update(int64_t deltaMs) {
	//std::cout << "update called with deltaMs: " << deltaMs << std::endl;
    // Move toward walk target on the XZ plane
    Eigen::Vector3f activeTarget = walkTarget;
    if (!pathWaypoints.empty() && currentWaypointIndex < pathWaypoints.size()) {
        activeTarget = pathWaypoints[currentWaypointIndex];
    }

    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());
        if (battle_state == 0 && currentState == AnimationState::STAND)
        {
            currentState = AnimationState::WALK;
        }
    } else {
        if (battle_state == 0 && 
            currentState == AnimationState::WALK
            )
        {
            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();
        }
    }

    if (battle_state == 1 && attack == 1 && attackTarget != nullptr)
    {
		targetFacingAngle = atan2(attackTarget->position.x() - position.x(), -(attackTarget->position.z() - position.z()));
    }

    // 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);
    }

    if (canAttack && attackTarget != nullptr)
    {
        float distToGhost = (attackTarget->position - position).norm();
        if (distToGhost >= 10.f)
        {
            if (isPlayer)
            {
				setTarget(attackTarget->position);
            }
            if (battle_state != 0)
            {
                battle_state = 0;
            }
        }
        else if (distToGhost < 10.0f && distToGhost >= 1.f) {
            setTarget(attackTarget->position);
            if (battle_state != 0)
            {
                battle_state = 0;
            }
        }
        else
        {
            setTarget(position);
            if (battle_state != 1)
            {
                battle_state = 1;
            }
        }
    }

    if (attackTarget == nullptr)
    {
        if (battle_state != 0)
        {
            battle_state = 0;
            attack = 0;
        }
    }

    if (battle_state == 1)
    {
        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;
        }
    }

    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;
    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) {
        if (useGpuSkinning) {
            anim.model.ComputeSkinningMatrices(static_cast<int>(anim.currentFrame), anim.skinningMatrices);
        } else {
            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() || !texture) 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;
    anim.modelMutable.AssignFrom(anim.model.mesh);
    anim.modelMutable.RefreshVBO();
    glBindTexture(GL_TEXTURE_2D, texture->getTexID());
    renderer.DrawVertexRenderStruct(anim.modelMutable);

    renderer.PopMatrix();
    renderer.PopProjectionMatrix();
    renderer.shaderManager.PopShader();
}

void Character::prepareGpuSkinningVBOs(AnimationData& anim) {
    if (anim.gpuSkinningPrepared)
    {

        return;
    }

    anim.model.PrepareGpuSkinningData();

    // 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) {
    AnimationState drawState = resolveActiveState();
    auto it = animations.find(drawState);
    if (it == animations.end() || !texture)
    {
        return;
    }

    auto& anim = it->second;
    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 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.skinningMatrices.size()), false,
        anim.skinningMatrices[0].data());

    glBindTexture(GL_TEXTURE_2D, texture->getTexID());

    // Bind VAO (desktop only)
#ifndef EMSCRIPTEN
#ifndef __ANDROID__
    if (anim.bindPoseMutable.vao) {
        glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
        renderer.shaderManager.EnableVertexAttribArrays();
    }
#endif
#endif

    // Bind position and texcoord VBOs
    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.PopProjectionMatrix();
    renderer.shaderManager.PopShader();
}

// ==================== 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;
    anim.modelMutable.AssignFrom(anim.model.mesh);
    anim.modelMutable.RefreshVBO();
    renderer.DrawVertexRenderStruct(anim.modelMutable);

    renderer.PopMatrix();
}

void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
    AnimationState drawState = resolveActiveState();
    auto it = animations.find(drawState);
    if (it == animations.end()) return;

    auto& anim = it->second;
    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 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());

    renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
        static_cast<int>(anim.skinningMatrices.size()), false,
        anim.skinningMatrices[0].data());

#ifndef EMSCRIPTEN
#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.shaderManager.PopShader();
}

// ==================== 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() || !texture) 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;
    anim.modelMutable.AssignFrom(anim.model.mesh);
    anim.modelMutable.RefreshVBO();
    glBindTexture(GL_TEXTURE_2D, texture->getTexID());
    renderer.DrawVertexRenderStruct(anim.modelMutable);

    renderer.PopMatrix();
    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() || !texture) return;

    auto& anim = it->second;
    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 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());

    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());

    renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
        static_cast<int>(anim.skinningMatrices.size()), false,
        anim.skinningMatrices[0].data());

    glBindTexture(GL_TEXTURE_2D, texture->getTexID());

#ifndef EMSCRIPTEN
#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.PopProjectionMatrix();
    renderer.shaderManager.PopShader();
}

} // namespace ZL
