/* 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 . * */ /* * 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