Files
scummvm-cursorfix/engines/wintermute/base/gfx/xmesh.cpp
2026-02-02 04:50:13 +01:00

372 lines
11 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* This file is based on WME.
* http://dead-code.org/redir.php?target=wme
* Copyright (c) 2003-2013 Jan Nedoma and contributors
*/
#include "engines/wintermute/base/gfx/3dshadow_volume.h"
#include "engines/wintermute/base/gfx/xmaterial.h"
#include "engines/wintermute/base/gfx/xmesh.h"
#include "engines/wintermute/base/gfx/skin_mesh_helper.h"
#include "engines/wintermute/base/gfx/xframe_node.h"
#include "engines/wintermute/base/gfx/xfile_loader.h"
#include "engines/wintermute/base/gfx/xmodel.h"
#include "engines/wintermute/base/gfx/xbuffer.h"
#include "engines/wintermute/base/gfx/xskinmesh.h"
#include "engines/wintermute/base/gfx/3deffect.h"
#include "engines/wintermute/base/gfx/3dutils.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/utils/path_util.h"
#include "engines/wintermute/dcgf.h"
#include "math/utils.h"
namespace Wintermute {
XMesh::XMesh(Wintermute::BaseGame *inGame) : BaseNamedObject(inGame) {
_skinMesh = nullptr;
_blendedMesh = nullptr;
_staticMesh = nullptr;
_boneMatrices = nullptr;
_adjacency = nullptr;
_BBoxStart = _BBoxEnd = DXVector3(0.0f, 0.0f, 0.0f);
}
XMesh::~XMesh() {
SAFE_DELETE(_skinMesh);
SAFE_DELETE(_blendedMesh);
SAFE_DELETE(_staticMesh);
SAFE_DELETE_ARRAY(_boneMatrices);
SAFE_DELETE_ARRAY(_adjacency);
_materials.removeAll();
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::loadFromXData(const char *filename, XFileData *xobj) {
// get name
if (!XModel::loadName(this, xobj)) {
BaseEngine::LOG(0, "Error loading mesh name");
return false;
}
// load mesh
DXBuffer bufMaterials;
uint32 numMaterials;
DXMesh *mesh;
DXSkinInfo *skinInfo;
auto res = DXLoadSkinMesh(xobj, bufMaterials, numMaterials, &skinInfo, &mesh);
if (!res) {
BaseEngine::LOG(0, "Error loading skin mesh");
return false;
}
_skinMesh = new SkinMeshHelper(mesh, skinInfo);
uint32 numBones = _skinMesh->getNumBones();
// Process skinning data
if (numBones) {
// bones are available
_boneMatrices = new DXMatrix*[numBones];
generateMesh();
} else {
// no bones are found, blend the mesh and use it as a static mesh
_skinMesh->getOriginalMesh(&_staticMesh);
_staticMesh->cloneMesh(&_blendedMesh);
SAFE_DELETE(_skinMesh);
if (_blendedMesh) {
uint32 numFaces = _blendedMesh->getNumFaces();
_adjacency = new uint32[numFaces * 3];
_blendedMesh->generateAdjacency(_adjacency);
}
}
// check for materials
if ((bufMaterials.ptr() == nullptr) || (numMaterials == 0)) {
// no materials are found, create default material
Material *mat = new Material(_game);
mat->_material._diffuse.color._r = 0.5f;
mat->_material._diffuse.color._g = 0.5f;
mat->_material._diffuse.color._b = 0.5f;
mat->_material._specular = mat->_material._diffuse;
mat->_material._ambient = mat->_material._diffuse;
_materials.add(mat);
} else {
// load the materials
DXMaterial *fileMats = (DXMaterial *)bufMaterials.ptr();
for (uint i = 0; i < numMaterials; i++) {
Material *mat = new Material(_game);
mat->_material = fileMats[i];
mat->_material._ambient = mat->_material._diffuse;
if (fileMats[i]._textureFilename[0] != '\0') {
Common::String texturePath = PathUtil::getDirectoryName(filename) + fileMats[i]._textureFilename;
mat->setTexture(texturePath.c_str(), true);
}
_materials.add(mat);
}
}
bufMaterials.free();
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::generateMesh() {
uint32 numFaces = _skinMesh->getNumFaces();
SAFE_DELETE(_blendedMesh);
SAFE_DELETE_ARRAY(_adjacency);
_adjacency = new uint32[numFaces * 3];
// blend the mesh
if (!_skinMesh->generateSkinnedMesh(_adjacency, &_blendedMesh)) {
BaseEngine::LOG(0, "Error converting to blended mesh");
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::findBones(FrameNode *rootFrame) {
// normal meshes don't have bones
if (!_skinMesh)
return true;
// get the buffer with the names of the bones
for (uint32 i = 0; i < _skinMesh->getNumBones(); i++) {
// find a frame with the same name
FrameNode *frame = rootFrame->findFrame(_skinMesh->getBoneName(i));
if (frame) {
// get a *pointer* to its world matrix
_boneMatrices[i] = frame->getCombinedMatrix();
} else {
BaseEngine::LOG(0, "Warning: Cannot find frame '%s'", _skinMesh->getBoneName(i));
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::update(FrameNode *parentFrame) {
if (!_blendedMesh)
return false;
// update skinned mesh
if (_skinMesh) {
int numBones = _skinMesh->getNumBones();
DXMatrix *boneMatrices = new DXMatrix[numBones];
// prepare final matrices
for (int i = 0; i < numBones; i++) {
DXMatrixMultiply(&boneMatrices[i], _skinMesh->getBoneOffsetMatrix(i), _boneMatrices[i]);
}
// generate skinned mesh
_skinMesh->updateSkinnedMesh(boneMatrices, _blendedMesh);
delete[] boneMatrices;
// update mesh bounding box
byte *points = _blendedMesh->getVertexBuffer().ptr();
DXComputeBoundingBox((DXVector3 *)points, _blendedMesh->getNumVertices(), DXGetFVFVertexSize(_blendedMesh->getFVF()), &_BBoxStart, &_BBoxEnd);
// if you want something done right...
if (isnan(_BBoxEnd._x)) {
float minX = FLT_MAX;
float minY = FLT_MAX;
float minZ = FLT_MAX;
float maxX = FLT_MIN;
float maxY = FLT_MIN;
float maxZ = FLT_MIN;
uint32 fvfSize = _blendedMesh->getFVF();
byte *vectBuf = points;
for (uint32 i = 0; i < _blendedMesh->getNumVertices(); i++) {
DXVector3 *vect = (DXVector3 *)vectBuf;
minX = MIN(minX, vect->_x);
minY = MIN(minY, vect->_y);
minZ = MIN(minZ, vect->_z);
maxX = MAX(maxX, vect->_x);
maxY = MAX(maxY, vect->_y);
maxZ = MAX(maxZ, vect->_z);
vectBuf += DXGetFVFVertexSize(fvfSize);
}
_BBoxStart = DXVector3(minX, minY, minZ);
_BBoxEnd = DXVector3(maxX, maxY, maxZ);
}
} else {
// update static mesh
uint32 fvfSize = DXGetFVFVertexSize(_blendedMesh->getFVF());
uint32 numVertices = _blendedMesh->getNumVertices();
// lock static vertex buffer
byte *oldPoints = _staticMesh->getVertexBuffer().ptr();
// lock blended vertex buffer
byte *newPoints = _blendedMesh->getVertexBuffer().ptr();
for (uint32 i = 0; i < numVertices; i++) {
DXVector3 v = *(DXVector3 *)(oldPoints + i * fvfSize);
DXVector4 newVertex;
DXVec3Transform(&newVertex, &v, parentFrame->getCombinedMatrix());
((DXVector3 *)(newPoints + i * fvfSize))->_x = newVertex._x;
((DXVector3 *)(newPoints + i * fvfSize))->_y = newVertex._y;
((DXVector3 *)(newPoints + i * fvfSize))->_z = newVertex._z;
}
// update bounding box
DXComputeBoundingBox((DXVector3 *)newPoints, _blendedMesh->getNumVertices(), DXGetFVFVertexSize(_blendedMesh->getFVF()), &_BBoxStart, &_BBoxEnd);
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::updateShadowVol(ShadowVolume *shadow, DXMatrix *modelMat, DXVector3 *light, float extrusionDepth) {
if (!_blendedMesh)
return false;
return shadow->addMesh(_blendedMesh, _adjacency, modelMat, light, extrusionDepth);
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::pickPoly(DXVector3 *pickRayOrig, DXVector3 *pickRayDir) {
if (!_blendedMesh)
return false;
uint32 fvfSize = DXGetFVFVertexSize(_blendedMesh->getFVF());
uint32 numFaces = _blendedMesh->getNumFaces();
// lock vertex buffer
byte *points = _blendedMesh->getVertexBuffer().ptr();
// lock index buffer
uint32 *indices = (uint32 *)_blendedMesh->getIndexBuffer().ptr();
bool found = false;
DXVector3 intersection;
for (uint32 i = 0; i < numFaces; i++) {
DXVector3 v0 = *(DXVector3 *)(points + indices[3 * i + 0] * fvfSize);
DXVector3 v1 = *(DXVector3 *)(points + indices[3 * i + 1] * fvfSize);
DXVector3 v2 = *(DXVector3 *)(points + indices[3 * i + 2] * fvfSize);
if (isnan(v0._x))
continue;
found = C3DUtils::intersectTriangle(*pickRayOrig, *pickRayDir, v0, v1, v2, &intersection._x, &intersection._y, &intersection._z) != false;
if (found)
break;
}
return found;
}
////////////////////////////////////////////////////////////////////////////
bool XMesh::setMaterialSprite(const char *matName, BaseSprite *sprite) {
for (int32 i = 0; i < _materials.getSize(); i++) {
if (_materials[i]->_name && scumm_stricmp(_materials[i]->_name, matName) == 0) {
_materials[i]->setSprite(sprite);
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::setMaterialTheora(const char *matName, VideoTheoraPlayer *theora) {
for (int32 i = 0; i < _materials.getSize(); i++) {
if (_materials[i]->_name && scumm_stricmp(_materials[i]->_name, matName) == 0) {
_materials[i]->setTheora(theora);
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::setMaterialEffect(const char *matName, Effect3D *effect, Effect3DParams *params) {
for (int32 i = 0; i < _materials.getSize(); i++) {
if (_materials[i]->_name && scumm_stricmp(_materials[i]->_name, matName) == 0) {
_materials[i]->setEffect(effect, params);
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::removeMaterialEffect(const char *matName) {
for (int32 i = 0; i < _materials.getSize(); i++) {
if (_materials[i]->_name && scumm_stricmp(_materials[i]->_name, matName) == 0) {
_materials[i]->setEffect(nullptr, nullptr);
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::invalidateDeviceObjects() {
if (_skinMesh) {
SAFE_DELETE(_blendedMesh);
}
for (int32 i = 0; i < _materials.getSize(); i++) {
_materials[i]->invalidateDeviceObjects();
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::restoreDeviceObjects() {
for (int32 i = 0; i < _materials.getSize(); i++) {
_materials[i]->restoreDeviceObjects();
}
if (_skinMesh) {
return generateMesh();
} else {
return true;
}
}
} // namespace Wintermute