1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00
luanti/irr/src/SkinnedMesh.cpp

672 lines
17 KiB
C++
Raw Normal View History

2024-03-21 20:13:15 +01:00
// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "SkinnedMesh.h"
#include "IBoneSceneNode.h"
2024-03-21 20:13:15 +01:00
#include "CBoneSceneNode.h"
#include "IAnimatedMeshSceneNode.h"
#include "SSkinMeshBuffer.h"
#include "Transform.h"
#include "aabbox3d.h"
#include "irrMath.h"
#include "matrix4.h"
2024-03-21 20:13:15 +01:00
#include "os.h"
#include "vector3d.h"
#include <cassert>
#include <cstddef>
#include <variant>
2024-12-12 15:33:08 +01:00
#include <vector>
#include <cassert>
2024-03-21 20:13:15 +01:00
namespace irr
{
namespace scene
{
//! destructor
SkinnedMesh::~SkinnedMesh()
2024-03-21 20:13:15 +01:00
{
2024-12-12 15:33:08 +01:00
for (auto *joint : AllJoints)
delete joint;
2024-03-21 20:13:15 +01:00
2024-12-12 15:33:08 +01:00
for (auto *buffer : LocalBuffers) {
if (buffer)
buffer->drop();
2024-03-21 20:13:15 +01:00
}
}
f32 SkinnedMesh::getMaxFrameNumber() const
2024-03-21 20:13:15 +01:00
{
return EndFrame;
2024-03-21 20:13:15 +01:00
}
//! Gets the default animation speed of the animated mesh.
/** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */
f32 SkinnedMesh::getAnimationSpeed() const
2024-03-21 20:13:15 +01:00
{
return FramesPerSecond;
}
//! Gets the frame count of the animated mesh.
/** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated.
The actual speed is set in the scene node the mesh is instantiated in.*/
void SkinnedMesh::setAnimationSpeed(f32 fps)
2024-03-21 20:13:15 +01:00
{
FramesPerSecond = fps;
}
// Keyframe Animation
2024-03-21 20:13:15 +01:00
using VariantTransform = SkinnedMesh::SJoint::VariantTransform;
std::vector<VariantTransform> SkinnedMesh::animateMesh(f32 frame)
2024-03-21 20:13:15 +01:00
{
assert(HasAnimation);
std::vector<VariantTransform> result;
result.reserve(AllJoints.size());
for (auto *joint : AllJoints)
result.push_back(joint->animate(frame));
return result;
}
core::aabbox3df SkinnedMesh::calculateBoundingBox(
const std::vector<core::matrix4> &global_transforms)
{
assert(global_transforms.size() == AllJoints.size());
core::aabbox3df result = StaticPartsBox;
// skeletal animation
for (u16 i = 0; i < AllJoints.size(); ++i) {
auto box = AllJoints[i]->LocalBoundingBox;
global_transforms[i].transformBoxEx(box);
result.addInternalBox(box);
}
// rigid animation
for (u16 i = 0; i < AllJoints.size(); ++i) {
for (u32 j : AllJoints[i]->AttachedMeshes) {
auto box = (*SkinningBuffers)[j]->BoundingBox;
global_transforms[i].transformBoxEx(box);
result.addInternalBox(box);
2024-03-21 20:13:15 +01:00
}
}
return result;
2024-03-21 20:13:15 +01:00
}
// Software Skinning
2024-03-21 20:13:15 +01:00
void SkinnedMesh::skinMesh(const std::vector<core::matrix4> &global_matrices)
2024-03-21 20:13:15 +01:00
{
if (!HasAnimation)
2024-03-21 20:13:15 +01:00
return;
// rigid animation
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
Buffer->Transformation = global_matrices[i];
2024-03-21 20:13:15 +01:00
}
}
2024-03-21 20:13:15 +01:00
// clear skinning helper array
for (std::vector<char> &buf : Vertices_Moved)
std::fill(buf.begin(), buf.end(), false);
2024-03-21 20:13:15 +01:00
// skin starting with the root joints
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
if (joint->Weights.empty())
continue;
2024-03-21 20:13:15 +01:00
// Find this joints pull on vertices
// Note: It is assumed that the global inversed matrix has been calculated at this point.
core::matrix4 jointVertexPull = global_matrices[i] * joint->GlobalInversedMatrix.value();
2024-03-21 20:13:15 +01:00
core::vector3df thisVertexMove, thisNormalMove;
2024-12-12 15:33:08 +01:00
auto &buffersUsed = *SkinningBuffers;
2024-03-21 20:13:15 +01:00
// Skin Vertices, Positions and Normals
2024-12-12 15:33:08 +01:00
for (const auto &weight : joint->Weights) {
2024-03-21 20:13:15 +01:00
// Pull this vertex...
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
if (AnimateNormals) {
thisNormalMove = jointVertexPull.rotateAndScaleVect(weight.StaticNormal);
thisNormalMove.normalize(); // must renormalize after potentially scaling
}
2024-03-21 20:13:15 +01:00
if (!(*(weight.Moved))) {
*(weight.Moved) = true;
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength;
if (AnimateNormals)
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength;
//*(weight._Pos) = thisVertexMove * weight.strength;
} else {
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength;
if (AnimateNormals)
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength;
//*(weight._Pos) += thisVertexMove * weight.strength;
}
}
}
for (auto *buffer : *SkinningBuffers)
buffer->setDirty(EBT_VERTEX);
2024-03-21 20:13:15 +01:00
}
//! Gets joint count.
u32 SkinnedMesh::getJointCount() const
2024-03-21 20:13:15 +01:00
{
return AllJoints.size();
}
//! Gets the name of a joint.
const std::optional<std::string> &SkinnedMesh::getJointName(u32 number) const
2024-03-21 20:13:15 +01:00
{
if (number >= getJointCount()) {
static const std::optional<std::string> nullopt;
return nullopt;
}
return AllJoints[number]->Name;
}
//! Gets a joint number from its name
std::optional<u32> SkinnedMesh::getJointNumber(const std::string &name) const
2024-03-21 20:13:15 +01:00
{
for (u32 i = 0; i < AllJoints.size(); ++i) {
if (AllJoints[i]->Name == name)
return i;
}
return std::nullopt;
}
//! returns amount of mesh buffers.
u32 SkinnedMesh::getMeshBufferCount() const
2024-03-21 20:13:15 +01:00
{
return LocalBuffers.size();
}
//! returns pointer to a mesh buffer
IMeshBuffer *SkinnedMesh::getMeshBuffer(u32 nr) const
2024-03-21 20:13:15 +01:00
{
if (nr < LocalBuffers.size())
return LocalBuffers[nr];
else
return 0;
}
//! Returns pointer to a mesh buffer which fits a material
IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
2024-03-21 20:13:15 +01:00
{
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
if (LocalBuffers[i]->getMaterial() == material)
return LocalBuffers[i];
}
return nullptr;
2024-03-21 20:13:15 +01:00
}
u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
{
return TextureSlots.at(meshbufNr);
}
void SkinnedMesh::setTextureSlot(u32 meshbufNr, u32 textureSlot) {
TextureSlots.at(meshbufNr) = textureSlot;
}
2024-03-21 20:13:15 +01:00
//! set the hardware mapping hint, for driver
void SkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint,
2024-03-21 20:13:15 +01:00
E_BUFFER_TYPE buffer)
{
for (u32 i = 0; i < LocalBuffers.size(); ++i)
LocalBuffers[i]->setHardwareMappingHint(newMappingHint, buffer);
}
//! flags the meshbuffer as changed, reloads hardware buffers
void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
2024-03-21 20:13:15 +01:00
{
for (u32 i = 0; i < LocalBuffers.size(); ++i)
LocalBuffers[i]->setDirty(buffer);
}
void SkinnedMesh::refreshJointCache()
2024-03-21 20:13:15 +01:00
{
// copy cache from the mesh...
2024-12-12 15:33:08 +01:00
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
2024-03-21 20:13:15 +01:00
}
}
}
void SkinnedMesh::resetAnimation()
2024-03-21 20:13:15 +01:00
{
// copy from the cache to the mesh...
2024-12-12 15:33:08 +01:00
for (auto *joint : AllJoints) {
for (const auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = weight.StaticPos;
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal;
2024-03-21 20:13:15 +01:00
}
}
}
//! Turns the given array of local matrices into an array of global matrices
//! by multiplying with respective parent matrices.
void SkinnedMesh::calculateGlobalMatrices(std::vector<core::matrix4> &matrices) const
2024-03-21 20:13:15 +01:00
{
// Note that the joints are topologically sorted.
for (u16 i = 0; i < AllJoints.size(); ++i) {
if (auto parent_id = AllJoints[i]->ParentJointID) {
matrices[i] = matrices[*parent_id] * matrices[i];
}
2024-03-21 20:13:15 +01:00
}
}
bool SkinnedMesh::checkForAnimation() const
2024-03-21 20:13:15 +01:00
{
2024-12-12 15:33:08 +01:00
for (auto *joint : AllJoints) {
if (!joint->keys.empty()) {
return true;
2024-03-21 20:13:15 +01:00
}
}
// meshes with weights are animatable
for (auto *joint : AllJoints) {
if (!joint->Weights.empty()) {
return true;
2024-03-21 20:13:15 +01:00
}
}
return false;
}
void SkinnedMesh::prepareForSkinning()
{
HasAnimation = checkForAnimation();
if (!HasAnimation || PreparedForSkinning)
return;
PreparedForSkinning = true;
EndFrame = 0.0f;
for (const auto *joint : AllJoints) {
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
2024-03-21 20:13:15 +01:00
}
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
// check for invalid ids
if (buffer_id >= LocalBuffers.size()) {
os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
weight.buffer_id = weight.vertex_id = 0;
} else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) {
os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
weight.buffer_id = weight.vertex_id = 0;
2024-03-21 20:13:15 +01:00
}
}
}
2024-03-21 20:13:15 +01:00
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
Vertices_Moved[i][j] = false;
2024-03-21 20:13:15 +01:00
// For skinning: cache weight values for speed
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
2024-03-21 20:13:15 +01:00
weight.Moved = &Vertices_Moved[buffer_id][vertex_id];
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
}
}
normalizeWeights();
2024-03-21 20:13:15 +01:00
for (auto *joint : AllJoints) {
joint->keys.cleanup();
}
}
2024-03-21 20:13:15 +01:00
void SkinnedMesh::calculateStaticBoundingBox()
{
std::vector<std::vector<bool>> animated(getMeshBufferCount());
for (u32 mb = 0; mb < getMeshBufferCount(); mb++)
animated[mb] = std::vector<bool>(getMeshBuffer(mb)->getVertexCount());
2024-03-21 20:13:15 +01:00
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
animated[buffer_id][vertex_id] = true;
}
}
bool first = true;
for (u16 mb = 0; mb < getMeshBufferCount(); mb++) {
for (u32 v = 0; v < getMeshBuffer(mb)->getVertexCount(); v++) {
if (!animated[mb][v]) {
auto pos = getMeshBuffer(mb)->getVertexBuffer()->getPosition(v);
if (!first) {
StaticPartsBox.addInternalPoint(pos);
} else {
StaticPartsBox.reset(pos);
first = false;
}
2024-03-21 20:13:15 +01:00
}
}
}
}
2024-03-21 20:13:15 +01:00
void SkinnedMesh::calculateJointBoundingBoxes()
{
for (auto *joint : AllJoints) {
bool first = true;
for (auto &weight : joint->Weights) {
if (weight.strength < 1e-6)
continue;
auto pos = weight.StaticPos;
joint->GlobalInversedMatrix.value().transformVect(pos);
if (!first) {
joint->LocalBoundingBox.addInternalPoint(pos);
} else {
joint->LocalBoundingBox.reset(pos);
first = false;
}
}
2024-03-21 20:13:15 +01:00
}
}
void SkinnedMesh::calculateBufferBoundingBoxes()
2024-03-21 20:13:15 +01:00
{
for (u32 j = 0; j < LocalBuffers.size(); ++j) {
// If we use skeletal animation, this will just be a bounding box of the static pose;
// if we use rigid animation, this will correctly transform the points first.
LocalBuffers[j]->recalculateBoundingBox();
}
}
2024-03-21 20:13:15 +01:00
void SkinnedMesh::recalculateBaseBoundingBoxes() {
calculateStaticBoundingBox();
calculateJointBoundingBoxes();
calculateBufferBoundingBoxes();
}
2024-03-21 20:13:15 +01:00
void SkinnedMesh::topoSortJoints()
{
size_t n = AllJoints.size();
std::vector<u16> new_to_old_id;
std::vector<std::vector<u16>> children(n);
for (u16 i = 0; i < n; ++i) {
if (auto parentId = AllJoints[i]->ParentJointID)
children[*parentId].push_back(i);
else
new_to_old_id.push_back(i);
2024-03-21 20:13:15 +01:00
}
// Levelorder
for (u16 i = 0; i < n; ++i) {
new_to_old_id.insert(new_to_old_id.end(),
children[new_to_old_id[i]].begin(),
children[new_to_old_id[i]].end());
}
2024-03-21 20:13:15 +01:00
std::vector<u16> old_to_new_id(n);
for (u16 i = 0; i < n; ++i)
old_to_new_id[new_to_old_id[i]] = i;
2024-03-21 20:13:15 +01:00
std::vector<SJoint *> joints(n);
for (u16 i = 0; i < n; ++i) {
joints[i] = AllJoints[new_to_old_id[i]];
joints[i]->JointID = i;
if (auto parentId = joints[i]->ParentJointID)
joints[i]->ParentJointID = old_to_new_id[*parentId];
}
AllJoints = std::move(joints);
2024-03-21 20:13:15 +01:00
for (u16 i = 0; i < n; ++i) {
if (auto pjid = AllJoints[i]->ParentJointID)
assert(*pjid < i);
2024-03-21 20:13:15 +01:00
}
}
2024-03-21 20:13:15 +01:00
//! called by loader after populating with mesh and bone data
SkinnedMesh *SkinnedMeshBuilder::finalize()
{
os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
2024-03-21 20:13:15 +01:00
topoSortJoints();
// Set array sizes
2024-12-12 15:33:08 +01:00
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
2024-03-21 20:13:15 +01:00
}
prepareForSkinning();
2024-03-21 20:13:15 +01:00
std::vector<core::matrix4> matrices;
matrices.reserve(AllJoints.size());
for (auto *joint : AllJoints) {
if (const auto *matrix = std::get_if<core::matrix4>(&joint->transform))
matrices.push_back(*matrix);
else
matrices.push_back(std::get<core::Transform>(joint->transform).buildMatrix());
2024-03-21 20:13:15 +01:00
}
calculateGlobalMatrices(matrices);
2024-03-21 20:13:15 +01:00
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
if (!joint->GlobalInversedMatrix) {
joint->GlobalInversedMatrix = matrices[i];
joint->GlobalInversedMatrix->makeInverse();
}
// rigid animation for non animated meshes
2024-12-12 15:33:08 +01:00
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
Buffer->Transformation = matrices[i];
2024-03-21 20:13:15 +01:00
}
}
recalculateBaseBoundingBoxes();
StaticPoseBox = calculateBoundingBox(matrices);
2024-12-12 15:33:08 +01:00
return this;
2024-03-21 20:13:15 +01:00
}
2024-12-12 15:33:08 +01:00
scene::SSkinMeshBuffer *SkinnedMeshBuilder::addMeshBuffer()
2024-03-21 20:13:15 +01:00
{
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
TextureSlots.push_back(LocalBuffers.size());
2024-03-21 20:13:15 +01:00
LocalBuffers.push_back(buffer);
return buffer;
}
2024-12-12 15:33:08 +01:00
void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
{
TextureSlots.push_back(LocalBuffers.size());
LocalBuffers.push_back(meshbuf);
}
2024-12-12 15:33:08 +01:00
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
2024-03-21 20:13:15 +01:00
{
SJoint *joint = new SJoint;
joint->setParent(parent);
2024-03-21 20:13:15 +01:00
joint->JointID = AllJoints.size();
2024-03-21 20:13:15 +01:00
AllJoints.push_back(joint);
return joint;
}
2024-12-12 15:33:08 +01:00
void SkinnedMeshBuilder::addPositionKey(SJoint *joint, f32 frame, core::vector3df pos)
2024-03-21 20:13:15 +01:00
{
assert(joint);
2024-12-12 15:33:08 +01:00
joint->keys.position.pushBack(frame, pos);
2024-03-21 20:13:15 +01:00
}
2024-12-12 15:33:08 +01:00
void SkinnedMeshBuilder::addScaleKey(SJoint *joint, f32 frame, core::vector3df scale)
2024-03-21 20:13:15 +01:00
{
assert(joint);
2024-12-12 15:33:08 +01:00
joint->keys.scale.pushBack(frame, scale);
2024-03-21 20:13:15 +01:00
}
2024-12-12 15:33:08 +01:00
void SkinnedMeshBuilder::addRotationKey(SJoint *joint, f32 frame, core::quaternion rot)
2024-03-21 20:13:15 +01:00
{
assert(joint);
2024-12-12 15:33:08 +01:00
joint->keys.rotation.pushBack(frame, rot);
2024-03-21 20:13:15 +01:00
}
2024-12-12 15:33:08 +01:00
SkinnedMesh::SWeight *SkinnedMeshBuilder::addWeight(SJoint *joint)
2024-03-21 20:13:15 +01:00
{
if (!joint)
2024-12-12 15:33:08 +01:00
return nullptr;
2024-03-21 20:13:15 +01:00
2024-12-12 15:33:08 +01:00
joint->Weights.emplace_back();
return &joint->Weights.back();
2024-03-21 20:13:15 +01:00
}
void SkinnedMesh::normalizeWeights()
2024-03-21 20:13:15 +01:00
{
// note: unsure if weights ids are going to be used.
// Normalise the weights on bones....
2024-12-12 15:33:08 +01:00
std::vector<std::vector<f32>> verticesTotalWeight;
2024-03-21 20:13:15 +01:00
2024-12-12 15:33:08 +01:00
verticesTotalWeight.reserve(LocalBuffers.size());
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
verticesTotalWeight.emplace_back(LocalBuffers[i]->getVertexCount());
2024-03-21 20:13:15 +01:00
}
2024-12-12 15:33:08 +01:00
for (u32 i = 0; i < verticesTotalWeight.size(); ++i)
for (u32 j = 0; j < verticesTotalWeight[i].size(); ++j)
2024-03-21 20:13:15 +01:00
verticesTotalWeight[i][j] = 0;
2024-12-12 15:33:08 +01:00
for (auto *joint : AllJoints) {
auto &weights = joint->Weights;
weights.erase(std::remove_if(weights.begin(), weights.end(), [](const auto &weight) {
return weight.strength <= 0;
}), weights.end());
for (const auto &weight : weights) {
verticesTotalWeight[weight.buffer_id][weight.vertex_id] += weight.strength;
2024-03-21 20:13:15 +01:00
}
}
2024-12-12 15:33:08 +01:00
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const f32 total = verticesTotalWeight[weight.buffer_id][weight.vertex_id];
2024-03-21 20:13:15 +01:00
if (total != 0 && total != 1)
2024-12-12 15:33:08 +01:00
weight.strength /= total;
2024-03-21 20:13:15 +01:00
}
}
}
void SkinnedMesh::convertMeshToTangents()
2024-03-21 20:13:15 +01:00
{
// now calculate tangents
for (u32 b = 0; b < LocalBuffers.size(); ++b) {
if (LocalBuffers[b]) {
LocalBuffers[b]->convertToTangents();
const s32 idxCnt = LocalBuffers[b]->getIndexCount();
u16 *idx = LocalBuffers[b]->getIndices();
video::S3DVertexTangents *v =
(video::S3DVertexTangents *)LocalBuffers[b]->getVertices();
for (s32 i = 0; i < idxCnt; i += 3) {
calculateTangents(
v[idx[i + 0]].Normal,
v[idx[i + 0]].Tangent,
v[idx[i + 0]].Binormal,
v[idx[i + 0]].Pos,
v[idx[i + 1]].Pos,
v[idx[i + 2]].Pos,
v[idx[i + 0]].TCoords,
v[idx[i + 1]].TCoords,
v[idx[i + 2]].TCoords);
calculateTangents(
v[idx[i + 1]].Normal,
v[idx[i + 1]].Tangent,
v[idx[i + 1]].Binormal,
v[idx[i + 1]].Pos,
v[idx[i + 2]].Pos,
v[idx[i + 0]].Pos,
v[idx[i + 1]].TCoords,
v[idx[i + 2]].TCoords,
v[idx[i + 0]].TCoords);
calculateTangents(
v[idx[i + 2]].Normal,
v[idx[i + 2]].Tangent,
v[idx[i + 2]].Binormal,
v[idx[i + 2]].Pos,
v[idx[i + 0]].Pos,
v[idx[i + 1]].Pos,
v[idx[i + 2]].TCoords,
v[idx[i + 0]].TCoords,
v[idx[i + 1]].TCoords);
}
}
}
}
void SkinnedMesh::calculateTangents(
2024-03-21 20:13:15 +01:00
core::vector3df &normal,
core::vector3df &tangent,
core::vector3df &binormal,
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3, // vertices
const core::vector2df &tc1, const core::vector2df &tc2, const core::vector2df &tc3) // texture coords
{
core::vector3df v1 = vt1 - vt2;
core::vector3df v2 = vt3 - vt1;
normal = v2.crossProduct(v1);
normal.normalize();
// binormal
f32 deltaX1 = tc1.X - tc2.X;
f32 deltaX2 = tc3.X - tc1.X;
binormal = (v1 * deltaX2) - (v2 * deltaX1);
binormal.normalize();
// tangent
f32 deltaY1 = tc1.Y - tc2.Y;
f32 deltaY2 = tc3.Y - tc1.Y;
tangent = (v1 * deltaY2) - (v2 * deltaY1);
tangent.normalize();
// adjust
core::vector3df txb = tangent.crossProduct(binormal);
if (txb.dotProduct(normal) < 0.0f) {
tangent *= -1.0f;
binormal *= -1.0f;
}
}
} // end namespace scene
} // end namespace irr