WINTERMUTE: Implement X file loader and restore original engine code

This commit is contained in:
Paweł Kołodziejski 2022-09-11 23:27:00 +02:00
parent ed543e3747
commit 0c4d570bcf
No known key found for this signature in database
GPG Key ID: 0BDADC9E74440FF7
21 changed files with 2849 additions and 1324 deletions

View File

@ -49,8 +49,8 @@ XMeshOpenGLShader::~XMeshOpenGLShader() {
glDeleteBuffers(1, &_indexBuffer);
}
bool XMeshOpenGLShader::loadFromX(const Common::String &filename, XFileLexer &lexer, Common::Array<MaterialReference> &materialReferences) {
if (XMesh::loadFromX(filename, lexer, materialReferences)) {
bool XMeshOpenGLShader::loadFromXData(const Common::String &filename, XFileData *xobj, Common::Array<MaterialReference> &materialReferences) {
if (XMesh::loadFromXData(filename, xobj, materialReferences)) {
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, 4 * kVertexComponentCount * _vertexCount, _vertexData, GL_DYNAMIC_DRAW);

View File

@ -41,7 +41,7 @@ public:
XMeshOpenGLShader(BaseGame *inGame, OpenGL::Shader *shader, OpenGL::Shader *flatShadowShader);
~XMeshOpenGLShader() override;
bool loadFromX(const Common::String &filename, XFileLexer &lexer, Common::Array<MaterialReference> &materialReferences) override;
bool loadFromXData(const Common::String &filename, XFileData *xobj, Common::Array<MaterialReference> &materialReferences) override;
bool render(XModel *model) override;
bool renderFlatShadowModel() override;
bool update(FrameNode *parentFrame) override;

View File

@ -26,11 +26,12 @@
*/
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/base/gfx/xanimation.h"
#include "engines/wintermute/base/gfx/xanimation_set.h"
#include "engines/wintermute/base/gfx/xframe_node.h"
#include "engines/wintermute/base/gfx/xmodel.h"
#include "engines/wintermute/base/gfx/xloader.h"
#include "engines/wintermute/base/gfx/xfile_loader.h"
#include "engines/wintermute/dcgf.h"
namespace Wintermute {
@ -67,72 +68,197 @@ bool Animation::findBone(FrameNode *rootFrame) {
}
//////////////////////////////////////////////////////////////////////////
bool Animation::loadFromX(XFileLexer &lexer, AnimationSet *parentAnimSet) {
if (lexer.tokenIsIdentifier()) {
lexer.advanceToNextToken(); // skip name
lexer.advanceOnOpenBraces();
} else {
lexer.advanceOnOpenBraces();
}
bool Animation::load(XFileData *xobj, AnimationSet *parentAnimSet) {
bool result;
XClassType objectType;
bool ret = true;
if (xobj->isReference()) {
// The original data is found
result = xobj->getType(objectType);
if (!result)
return result;
while (!lexer.eof()) {
if (lexer.tokenIsIdentifier("AnimationKey")) {
lexer.advanceToNextToken();
lexer.advanceToNextToken(); // skip name
lexer.advanceOnOpenBraces();
int keyType = lexer.readInt();
int keyCount = lexer.readInt();
switch (keyType) {
case 0:
loadRotationKeyData(lexer, keyCount);
break;
case 1:
loadScaleKeyData(lexer, keyCount);
break;
case 2:
loadPositionKeyData(lexer, keyCount);
break;
case 3:
case 4:
loadMatrixKeyData(lexer, keyCount);
break;
default:
warning("Animation::loadFromX unknown key type encountered");
// The object must be a frame
if (objectType == kXClassFrame) {
// The frame is found, get its name
// The name will be used later by the findBone function to get
// a pointer to the target frame
if (_targetFrame) {
BaseEngine::LOG(0, "Animation frame name reference duplicated");
return false;
}
lexer.advanceToNextToken(); // skip closed braces
// get name
result = XModel::loadName(_targetName, xobj);
if (!result) {
BaseEngine::LOG(0, "Error retrieving frame name while loading animation");
return false;
}
}
} else {
// a data object is found, get its type
result = xobj->getType(objectType);
if (!result)
return false;
} else if (lexer.tokenIsIdentifier("AnimationOptions")) {
lexer.advanceToNextToken();
lexer.advanceToNextToken();
lexer.advanceOnOpenBraces();
// I think we can ignore these for the moment
lexer.readInt(); // whether animation is open or closed
lexer.readInt(); // position quality
lexer.advanceToNextToken(); // skip closed braces
} else if (lexer.tokenIsOfType(OPEN_BRACES)) {
// this is a reference to a frame/bone, given as an identifier
lexer.advanceToNextToken();
_targetName = lexer.readString();
} else if (lexer.tokenIsIdentifier("Animation")) {
// pass it up
break;
} else if (lexer.reachedClosedBraces()) {
lexer.advanceToNextToken(); // skip closed braces
break;
} else {
warning("Animation::loadFromX unexpected token encounterd");
ret = false;
break;
if (objectType == kXClassAnimationKey) {
// an animation key is found, load the data
XAnimationKeyObject *animationKey = xobj->getXAnimationKeyObject();
if (!animationKey)
return false;
result = loadAnimationKeyData(animationKey);
if (!result)
return false;
} else if (objectType == kXClassAnimationOptions) {
XAnimationOptionsObject *animationOptions = xobj->getXAnimationOptionsObject();
if (!animationOptions)
return false;
result = loadAnimationOptionData(animationOptions, parentAnimSet);
if (!result)
return false;
}
}
return ret;
return true;
}
//////////////////////////////////////////////////////////////////////////
bool Animation::loadAnimationOptionData(XAnimationOptionsObject *animationOptionData, AnimationSet *parentAnimSet) {
if (animationOptionData->_openclosed && parentAnimSet)
parentAnimSet->_looping = true;
return true;
}
//////////////////////////////////////////////////////////////////////////
bool Animation::loadAnimationKeyData(XAnimationKeyObject *animationKey) {
// get the type and count of the key
uint32 keyType = animationKey->_keyType;
uint32 numKeys = animationKey->_numKeys;
if (keyType == 0) { // rotation key
if (_rotKeys.size() != 0) {
BaseEngine::LOG(0, "Rotation key duplicated");
return false;
}
for (uint32 key = 0; key < numKeys; key++) {
const XTimedFloatKeys *fileRotKey = &animationKey->_keys[key];
assert(fileRotKey->_numTfkeys == 4);
BoneRotationKey *rotKey = new BoneRotationKey;
rotKey->_time = fileRotKey->_time;
// NOTE x files are w x y z and QUATERNIONS are x y z w
rotKey->_rotation.w() = fileRotKey->_tfkeys[0];
rotKey->_rotation.x() = fileRotKey->_tfkeys[1];
rotKey->_rotation.y() = fileRotKey->_tfkeys[2];
// mirror z component
rotKey->_rotation.z() = -fileRotKey->_tfkeys[3];
_rotKeys.push_back(rotKey);
}
} else if (keyType == 1) { // scale key
if (_scaleKeys.size() != 0) {
BaseEngine::LOG(0, "Scale key duplicated");
return false;
}
for (uint32 key = 0; key < numKeys; key++) {
const XTimedFloatKeys *fileScaleKey = &animationKey->_keys[key];
assert(fileScaleKey->_numTfkeys == 3);
BoneScaleKey *scaleKey = new BoneScaleKey;
scaleKey->_time = fileScaleKey->_time;
for (uint i = 0; i < fileScaleKey->_numTfkeys; ++i) {
scaleKey->_scale.getData()[i] = fileScaleKey->_tfkeys[i];
}
_scaleKeys.push_back(scaleKey);
}
} else if (keyType == 2) { // position key
if (_posKeys.size() != 0) {
BaseEngine::LOG(0, "Position key duplicated");
return false;
}
for (uint32 key = 0; key < numKeys; key++) {
const XTimedFloatKeys *filePosKey = &animationKey->_keys[key];
assert(filePosKey->_numTfkeys == 3);
BonePositionKey *posKey = new BonePositionKey;
posKey->_time = filePosKey->_time;
for (uint i = 0; i < filePosKey->_numTfkeys; ++i) {
posKey->_pos.getData()[i] = filePosKey->_tfkeys[i];
}
// mirror Z
posKey->_pos.getData()[2] *= -1.0f;
_posKeys.push_back(posKey);
}
} else if (keyType == 4) { // matrix key
if (_rotKeys.size() != 0 || _scaleKeys.size() != 0 || _posKeys.size() != 0) {
BaseEngine::LOG(0, "Matrix key duplicated");
return false;
}
for (uint32 key = 0; key < numKeys; key++) {
const XTimedFloatKeys *fileMatrixKey = &animationKey->_keys[key];
uint32 time = fileMatrixKey->_time;
assert(fileMatrixKey->_numTfkeys == 16);
Math::Matrix4 keyData;
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) {
keyData(c, r) = fileMatrixKey->_tfkeys[r * 4 + c];
}
}
// mirror at orign
keyData(2, 3) *= -1.0f;
// mirror base vectors
keyData(2, 0) *= -1.0f;
keyData(2, 1) *= -1.0f;
// change handedness
keyData(0, 2) *= -1.0f;
keyData(1, 2) *= -1.0f;
Math::Vector3d translation = keyData.getPosition();
Math::Vector3d scale;
scale.x() = keyData(0, 0) * keyData(0, 0) + keyData(1, 0) * keyData(1, 0) + keyData(2, 0) * keyData(2, 0);
scale.x() = sqrtf(scale.x());
scale.y() = keyData(0, 1) * keyData(0, 1) + keyData(1, 1) * keyData(1, 1) + keyData(2, 1) * keyData(2, 1);
scale.y() = sqrtf(scale.y());
scale.z() = keyData(0, 2) * keyData(0, 2) + keyData(1, 2) * keyData(1, 2) + keyData(2, 2) * keyData(2, 2);
scale.z() = sqrtf(scale.z());
Math::Quaternion rotation;
rotation.fromMatrix(keyData.getRotation());
BonePositionKey *positionKey = new BonePositionKey;
BoneScaleKey *scaleKey = new BoneScaleKey;
BoneRotationKey *rotationKey = new BoneRotationKey;
positionKey->_time = time;
scaleKey->_time = time;
rotationKey->_time = time;
positionKey->_pos = translation;
scaleKey->_scale = scale;
rotationKey->_rotation = rotation;
_posKeys.push_back(positionKey);
_scaleKeys.push_back(scaleKey);
_rotKeys.push_back(rotationKey);
}
} else {
// the type is unknown, report the error
BaseEngine::LOG(0, "Unexpected animation key type (%d)", keyType);
}
return true;
}
//////////////////////////////////////////////////////////////////////////
@ -307,146 +433,4 @@ uint32 Animation::getTotalTime() {
return totalTime;
}
// try to put these functions into a template?
bool Animation::loadRotationKeyData(XFileLexer &lexer, int count) {
for (int keyIndex = 0; keyIndex < count; ++keyIndex) {
BoneRotationKey *key = new BoneRotationKey;
key->_time = lexer.readInt();
int floatCount = lexer.readInt();
assert(floatCount == 4);
// .X file format puts the w coordinate first
key->_rotation.w() = lexer.readFloat();
key->_rotation.x() = lexer.readFloat();
key->_rotation.y() = lexer.readFloat();
// mirror z component
key->_rotation.z() = -lexer.readFloat();
lexer.skipTerminator(); // skip semicolon
if (lexer.tokenIsOfType(SEMICOLON) || lexer.tokenIsOfType(COMMA)) {
lexer.advanceToNextToken(); // skip closed braces
}
_rotKeys.push_back(key);
}
return true;
}
bool Animation::loadScaleKeyData(XFileLexer &lexer, int count) {
for (int keyIndex = 0; keyIndex < count; ++keyIndex) {
BoneScaleKey *key = new BoneScaleKey;
key->_time = lexer.readInt();
int floatCount = lexer.readInt();
assert(floatCount == 3);
for (int i = 0; i < floatCount; ++i) {
key->_scale.getData()[i] = lexer.readFloat();
}
lexer.skipTerminator(); // skip semicolon
if (lexer.tokenIsOfType(SEMICOLON) || lexer.tokenIsOfType(COMMA)) {
lexer.advanceToNextToken(); // skip closed braces
}
_scaleKeys.push_back(key);
}
return true;
}
bool Animation::loadPositionKeyData(XFileLexer &lexer, int count) {
for (int keyIndex = 0; keyIndex < count; ++keyIndex) {
BonePositionKey *key = new BonePositionKey;
key->_time = lexer.readInt();
int floatCount = lexer.readInt();
assert(floatCount == 3);
for (int i = 0; i < floatCount; ++i) {
key->_pos.getData()[i] = lexer.readFloat();
}
key->_pos.getData()[2] *= -1.0f;
lexer.skipTerminator(); // skip semicolon
if (lexer.tokenIsOfType(SEMICOLON) || lexer.tokenIsOfType(COMMA)) {
lexer.advanceToNextToken(); // skip closed braces
}
_posKeys.push_back(key);
}
return true;
}
bool Animation::loadMatrixKeyData(XFileLexer &lexer, int count) {
for (int keyIndex = 0; keyIndex < count; ++keyIndex) {
uint32 time = lexer.readInt();
int floatCount = lexer.readInt();
assert(floatCount == 16);
Math::Matrix4 keyData;
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) {
keyData(c, r) = lexer.readFloat();
}
}
// mirror at orign
keyData(2, 3) *= -1.0f;
// mirror base vectors
keyData(2, 0) *= -1.0f;
keyData(2, 1) *= -1.0f;
// change handedness
keyData(0, 2) *= -1.0f;
keyData(1, 2) *= -1.0f;
Math::Vector3d translation = keyData.getPosition();
Math::Vector3d scale;
scale.x() = keyData(0, 0) * keyData(0, 0) + keyData(1, 0) * keyData(1, 0) + keyData(2, 0) * keyData(2, 0);
scale.x() = sqrtf(scale.x());
scale.y() = keyData(0, 1) * keyData(0, 1) + keyData(1, 1) * keyData(1, 1) + keyData(2, 1) * keyData(2, 1);
scale.y() = sqrtf(scale.y());
scale.z() = keyData(0, 2) * keyData(0, 2) + keyData(1, 2) * keyData(1, 2) + keyData(2, 2) * keyData(2, 2);
scale.z() = sqrtf(scale.z());
Math::Quaternion rotation;
rotation.fromMatrix(keyData.getRotation());
BonePositionKey *positionKey = new BonePositionKey;
BoneScaleKey *scaleKey = new BoneScaleKey;
BoneRotationKey *rotationKey = new BoneRotationKey;
positionKey->_time = time;
scaleKey->_time = time;
rotationKey->_time = time;
positionKey->_pos = translation;
scaleKey->_scale = scale;
rotationKey->_rotation = rotation;
_posKeys.push_back(positionKey);
_scaleKeys.push_back(scaleKey);
_rotKeys.push_back(rotationKey);
lexer.skipTerminator(); // skip semicolon
if (lexer.tokenIsOfType(SEMICOLON) || lexer.tokenIsOfType(COMMA)) {
lexer.advanceToNextToken(); // skip closed braces
}
}
return true;
}
} // namespace Wintermute

View File

@ -38,14 +38,16 @@ namespace Wintermute {
class FrameNode;
class AnimationSet;
class XFileLexer;
class XFileData;
struct XAnimationKeyObject;
struct XAnimationOptionsObject;
class Animation : public BaseClass {
public:
Animation(BaseGame *inGame);
virtual ~Animation();
bool loadFromX(XFileLexer &lexer, AnimationSet *parentAnimationSet);
bool load(XFileData *xobj, AnimationSet *parentAnimSet);
bool findBone(FrameNode *rootFrame);
bool update(int slot, uint32 localTime, float animLerpValue);
@ -79,10 +81,8 @@ protected:
BaseArray<BoneScaleKey *> _scaleKeys;
private:
bool loadRotationKeyData(XFileLexer &lexer, int count);
bool loadScaleKeyData(XFileLexer &lexer, int count);
bool loadPositionKeyData(XFileLexer &lexer, int count);
bool loadMatrixKeyData(XFileLexer &lexer, int count);
bool loadAnimationKeyData(XAnimationKeyObject *animationKey);
bool loadAnimationOptionData(XAnimationOptionsObject *animationSet, AnimationSet *parentAnimSet);
};
} // namespace Wintermute

View File

@ -28,7 +28,6 @@
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/gfx/xanimation_set.h"
#include "engines/wintermute/base/gfx/xmodel.h"
#include "engines/wintermute/base/gfx/xloader.h"
#include "engines/wintermute/dcgf.h"
namespace Wintermute {
@ -56,39 +55,6 @@ AnimationSet::~AnimationSet() {
_events.clear();
}
bool AnimationSet::loadFromX(XFileLexer &lexer, const Common::String &filename) {
if (lexer.tokenIsIdentifier()) {
setName(lexer.tokenToString().c_str());
lexer.advanceToNextToken();
} else {
Common::String name = filename + "_animation";
setName(name.c_str());
}
lexer.advanceToNextToken();
bool ret = true;
while (!lexer.eof()) {
if (lexer.tokenIsIdentifier("Animation")) {
lexer.advanceToNextToken();
Animation *animation = new Animation(_gameRef);
animation->loadFromX(lexer, this);
_animations.add(animation);
} else if (lexer.reachedClosedBraces()) {
lexer.advanceToNextToken(); // skip closed braces
break;
} else {
warning("AnimationSet::loadFromX unexpected token");
ret = false;
break;
}
}
return ret;
}
//////////////////////////////////////////////////////////////////////////
bool AnimationSet::findBones(FrameNode *rootFrame) {
for (uint32 i = 0; i < _animations.size(); i++) {

View File

@ -38,7 +38,6 @@
namespace Wintermute {
class XModel;
class XFileLexer;
class AnimationSet : public BaseNamedObject {
public:
@ -75,7 +74,6 @@ public:
AnimationSet(BaseGame *inGame, XModel *model);
virtual ~AnimationSet();
bool loadFromX(XFileLexer &lexer, const Common::String &filename);
bool findBones(FrameNode *rootFrame);
bool addAnimation(Animation *anim);
bool addEvent(AnimationEvent *event);

View File

@ -0,0 +1,89 @@
/* 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/base_file_manager.h"
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/base/gfx/xfile.h"
#include "engines/wintermute/dcgf.h"
namespace Wintermute {
//////////////////////////////////////////////////////////////////////////
XFile::XFile(BaseGame *inGame) : BaseClass(inGame) {
_xfile = nullptr;
}
//////////////////////////////////////////////////////////////////////////
XFile::~XFile() {
closeFile();
}
//////////////////////////////////////////////////////////////////////////
bool XFile::closeFile() {
delete _xfile;
_xfile = nullptr;
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XFile::openFile(const Common::String &filename) {
closeFile();
// load file
uint32 size;
byte *buffer = BaseFileManager::getEngineInstance()->readWholeFile(filename, &size);
if (!buffer) {
closeFile();
return false;
}
_xfile = new XFileLoader();
if (!_xfile) {
delete[] buffer;
return false;
}
bool res = _xfile->load(buffer, size);
delete[] buffer;
if (!res) {
BaseEngine::LOG(0, "Error loading X file '%s'", filename.c_str());
return false;
}
// create enum object
if (!res || !_xfile->createEnumObject(_xenum)) {
BaseEngine::LOG(res, "Error creating XFile enum object for '%s'", filename.c_str());
closeFile();
return false;
}
return true;
}
} // namespace Wintermute

View File

@ -0,0 +1,56 @@
/* 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
*/
#ifndef WINTERMUTE_XFILE_H
#define WINTERMUTE_XFILE_H
#include "engines/wintermute/base/base.h"
#include "engines/wintermute/base/gfx/xfile_loader.h"
namespace Wintermute {
class XFile : public BaseClass {
public:
XFile(BaseGame *inGame);
virtual ~XFile();
bool openFile(const Common::String &filename);
bool closeFile();
XFileEnumObject getEnum() {
return _xenum;
}
private:
XFileLoader *_xfile;
XFileEnumObject _xenum;
};
} // namespace Wintermute
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,566 @@
/* 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/>.
*
*/
/*
* Partially based on XFile parser code from Wine sources.
* Copyright 2008 Christian Costa
*/
#ifndef WINTERMUTE_XFILE_LOADER_H
#define WINTERMUTE_XFILE_LOADER_H
#include "common/str.h"
#include "common/stack.h"
namespace Wintermute {
enum XTokenType : uint16 {
XTOKEN_ERROR = 0xffff,
XTOKEN_NONE = 0,
XTOKEN_NAME = 1,
XTOKEN_STRING = 2,
XTOKEN_INTEGER = 3,
XTOKEN_GUID = 5,
XTOKEN_INTEGER_LIST = 6,
XTOKEN_FLOAT_LIST = 7,
XTOKEN_OBRACE = 10,
XTOKEN_CBRACE = 11,
XTOKEN_OPAREN = 12,
XTOKEN_CPAREN = 13,
XTOKEN_OBRACKET = 14,
XTOKEN_CBRACKET = 15,
XTOKEN_OANGLE = 16,
XTOKEN_CANGLE = 17,
XTOKEN_DOT = 18,
XTOKEN_COMMA = 19,
XTOKEN_SEMICOLON = 20,
XTOKEN_TEMPLATE = 31,
XTOKEN_WORD = 40,
XTOKEN_DWORD = 41,
XTOKEN_FLOAT = 42,
XTOKEN_DOUBLE = 43,
XTOKEN_CHAR = 44,
XTOKEN_UCHAR = 45,
XTOKEN_SWORD = 46,
XTOKEN_SDWORD = 47,
XTOKEN_VOID = 48,
XTOKEN_LPSTR = 49,
XTOKEN_UNICODE = 50,
XTOKEN_CSTRING = 51,
XTOKEN_ARRAY = 52
};
#define XMAX_NAME_LEN 120
#define XMAX_STRING_LEN 500
struct XToken {
XTokenType _type;
char _textVal[XMAX_STRING_LEN];
uint32 _integerVal;
float _floatVal;
};
struct XVector {
float _x;
float _y;
float _z;
};
struct XCoords2d {
float _u;
float _v;
};
struct XMeshFace {
uint32 _numFaceVertexIndices;
uint32 _faceVertexIndices[4];
};
struct XTimedFloatKeys {
float _time;
uint32 _numTfkeys;
float _tfkeys[16];
};
struct XIndexedColor {
uint32 _index;
float _indexColorR;
float _indexColorG;
float _indexColorB;
float _indexColorA;
};
struct XVertexElement {
uint32 _type;
uint32 _method;
uint32 _usage;
uint32 _usageIndex;
};
struct XMeshMaterialListObject {
uint32 _nMaterials;
uint32 _numFaceIndexes;
uint32 *_faceIndexes{};
~XMeshMaterialListObject() {
delete[] _faceIndexes;
}
};
struct XVertexDuplicationIndicesObject {
uint32 _nOriginalVertices;
uint32 _numIndices;
uint32 *_indices{};
~XVertexDuplicationIndicesObject() {
delete[] _indices;
}
};
struct XSkinMeshHeaderObject{
uint32 _nMaxSkinWeightsPerVertex;
uint32 _nMaxSkinWeightsPerFace;
uint32 _nBones;
};
struct XSkinWeightsObject {
char _transformNodeName[XMAX_NAME_LEN];
uint32 _numVertexIndices;
uint32 *_vertexIndices{};
uint32 _numWeights;
float *_weights{};
float _matrixOffset[16];
~XSkinWeightsObject() {
delete[] _vertexIndices;
delete[] _weights;
}
};
struct XMeshObject {
uint32 _numVertices;
XVector *_vertices{};
uint32 _numFaces;
XMeshFace *_faces{};
~XMeshObject() {
delete[] _vertices;
delete[] _faces;
}
};
struct XMeshNormalsObject {
uint32 _numNormals;
XVector *_normals{};
uint32 _numFaceNormals;
XMeshFace *_faceNormals{};
~XMeshNormalsObject() {
delete[] _normals;
delete[] _faceNormals;
}
};
struct XMeshVertexColorsObject {
uint32 _numVertexColors;
XIndexedColor *_vertexColors{};
~XMeshVertexColorsObject() {
delete[] _vertexColors;
}
};
struct XMeshTextureCoordsObject {
uint32 _numTextureCoords;
XCoords2d *_textureCoords{};
~XMeshTextureCoordsObject() {
delete[] _textureCoords;
}
};
struct XMaterialObject {
float _colorR;
float _colorG;
float _colorB;
float _colorA;
float _power;
float _specularR;
float _specularG;
float _specularB;
float _emissiveR;
float _emissiveG;
float _emissiveB;
};
struct XTextureFilenameObject {
char _filename[XMAX_NAME_LEN];
};
struct XAnimTicksPerSecondObject {
uint32 _animTicksPerSecond;
};
struct XAnimationSetObject{
};
struct XAnimationObject{
};
struct XAnimationKeyObject {
uint32 _keyType;
uint32 _numKeys;
XTimedFloatKeys *_keys{};
~XAnimationKeyObject() {
delete[] _keys;
}
};
struct XAnimationOptionsObject {
uint32 _openclosed;
uint32 _positionquality;
};
struct XFrameObject {
};
struct XFrameTransformMatrixObject {
float _frameMatrix[16];
};
struct XDeclDataObject {
uint32 _numElements;
XVertexElement *_elements{};
uint32 _numData;
uint32 *_data{};
~XDeclDataObject() {
delete[] _elements;
delete[] _data;
}
};
struct XFVFDataObject {
uint32 _dwFVF;
uint32 _numData;
uint32 *_data{};
~XFVFDataObject() {
delete[] _data;
}
};
enum XClassType {
kXClassUnknown = 0,
kXClassAnimTicksPerSecond,
kXClassFrameTransformMatrix,
kXClassFrame,
kXClassMesh,
kXClassMeshNormals,
kXClassMeshVertexColors,
kXClassMeshTextureCoords,
kXClassMeshMaterialList,
kXClassVertexDuplicationIndices,
kXClassMaterial,
kXClassTextureFilename,
kXClassSkinMeshHeader,
kXClassSkinWeights,
kXClassAnimationSet,
kXClassAnimation,
kXClassAnimationKey,
kXClassAnimationOptions,
kXClassDeclData,
kXClassFVFData,
};
class XFileEnumObject;
class XObject {
friend class XFileLoader;
friend class XFileData;
friend class XFileEnumObject;
private:
Common::String _name;
XClassType _classType{};
void *_object{};
XObject *_targetObject{};
Common::Stack<XObject *> _children;
public:
void deinit() {
switch (_classType) {
case kXClassAnimTicksPerSecond:
delete (XAnimTicksPerSecondObject *)_object;
break;
case kXClassAnimationKey:
delete (XAnimationKeyObject *)_object;
break;
case kXClassAnimation:
delete (XAnimationObject *)_object;
break;
case kXClassAnimationOptions:
delete (XAnimationOptionsObject *)_object;
break;
case kXClassAnimationSet:
delete (XAnimationSetObject *)_object;
break;
case kXClassDeclData:
delete (XDeclDataObject *)_object;
break;
case kXClassFrame:
delete (XFrameObject *)_object;
break;
case kXClassFrameTransformMatrix:
delete (XFrameTransformMatrixObject *)_object;
break;
case kXClassFVFData:
delete (XFVFDataObject *)_object;
break;
case kXClassMaterial:
delete (XMaterialObject *)_object;
break;
case kXClassMesh:
delete (XMeshObject *)_object;
break;
case kXClassMeshMaterialList:
delete (XMeshMaterialListObject *)_object;
break;
case kXClassMeshNormals:
delete (XMeshNormalsObject *)_object;
break;
case kXClassMeshVertexColors:
delete (XMeshVertexColorsObject *)_object;
break;
case kXClassMeshTextureCoords:
delete (XMeshTextureCoordsObject *)_object;
break;
case kXClassSkinMeshHeader:
delete (XSkinMeshHeaderObject *)_object;
break;
case kXClassSkinWeights:
delete (XSkinWeightsObject *)_object;
break;
case kXClassVertexDuplicationIndices:
delete (XVertexDuplicationIndicesObject *)_object;
break;
case kXClassTextureFilename:
delete (XTextureFilenameObject *)_object;
break;
case kXClassUnknown:
break;
}
}
};
class XFileLoader {
friend class XFileEnumObject;
private:
const int kCabBlockSize = 0x8000;
const int kCabInputmax = kCabBlockSize + 12;
bool _initialised{};
XToken _currentToken;
byte *_decompBuffer{};
byte *_buffer;
uint32 _bufferLeft;
bool _isText;
uint32 _listNbElements;
bool _listTypeFloat;
bool _listSeparator;
bool _tokenPresent;
Common::Stack<XObject *> _xobjects;
public:
XFileLoader();
~XFileLoader();
bool load(byte *buffer, uint32 bufferSize);
bool createEnumObject(XFileEnumObject &xobj);
private:
void init();
void deinit();
FORCEINLINE bool readChar(char &c);
FORCEINLINE void rewindBytes(uint32 size);
bool readBytes(void *data, uint32 size);
bool readLE16(uint16 *data);
bool readLE32(uint32 *data);
bool readBE32(uint32 *data);
FORCEINLINE bool getInteger(uint32 &value);
FORCEINLINE bool getFloat(float &value);
FORCEINLINE bool getString(char *str, uint maxLen);
FORCEINLINE bool skipSemicolonComma();
FORCEINLINE bool isSpace(char c);
FORCEINLINE bool isOperator(char c);
FORCEINLINE bool isSeparator(char c);
FORCEINLINE bool isPrimitiveType(XTokenType token);
FORCEINLINE bool isGuid();
FORCEINLINE bool isName();
FORCEINLINE bool isFloat();
FORCEINLINE bool isInteger();
FORCEINLINE bool isString();
FORCEINLINE bool isKeyword(const char *keyword, uint len);
FORCEINLINE XTokenType getKeywordToken();
FORCEINLINE XTokenType checkToken();
XTokenType getToken();
void parseToken();
bool decompressMsZipData();
bool parseHeader();
bool parseTemplate();
bool parseTemplateParts();
bool parseTemplateOptionInfo();
bool parseTemplateMembersList();
XObject *resolveChildObject(XObject *object, const Common::String &referenceName);
bool resolveObject(XObject *referenceObject, const Common::String &referenceName);
bool parseObject(XObject *object);
bool parseChildObjects(XObject *object);
bool parseObjectParts(XObject *object);
};
class XFileData {
friend class XFileEnumObject;
private:
XObject *_xobject{};
bool _reference{};
public:
bool getChild(uint id, XFileData &child) {
if (_xobject) {
if (id < _xobject->_children.size()) {
child._xobject = _xobject->_children[id];
if (child._xobject->_targetObject) {
child._xobject = child._xobject->_targetObject;
child._reference = true;
}
return true;
}
}
return false;
}
bool getChildren(uint &num) {
if (_xobject) {
num = _xobject->_children.size();
return true;
}
return false;
}
bool getName(Common::String &name) {
if (_xobject) {
name = _xobject->_name;
return true;
}
return false;
}
bool getType(XClassType &classType) {
if (_xobject) {
classType = _xobject->_classType;
return true;
}
return false;
}
bool isReference() {
if (_xobject) {
return _reference;
}
return false;
}
#define GET_OBJECT_FUNC(objectName) \
objectName *get ## objectName() { \
if (_xobject) \
return static_cast<objectName *>(_xobject->_object); \
else \
return nullptr; \
}
GET_OBJECT_FUNC(XAnimTicksPerSecondObject)
GET_OBJECT_FUNC(XAnimationKeyObject)
GET_OBJECT_FUNC(XAnimationObject)
GET_OBJECT_FUNC(XAnimationOptionsObject)
GET_OBJECT_FUNC(XAnimationSetObject)
GET_OBJECT_FUNC(XDeclDataObject)
GET_OBJECT_FUNC(XFrameObject)
GET_OBJECT_FUNC(XFrameTransformMatrixObject)
GET_OBJECT_FUNC(XFVFDataObject)
GET_OBJECT_FUNC(XMaterialObject)
GET_OBJECT_FUNC(XMeshObject)
GET_OBJECT_FUNC(XMeshMaterialListObject)
GET_OBJECT_FUNC(XMeshNormalsObject)
GET_OBJECT_FUNC(XMeshVertexColorsObject)
GET_OBJECT_FUNC(XMeshTextureCoordsObject)
GET_OBJECT_FUNC(XSkinMeshHeaderObject)
GET_OBJECT_FUNC(XSkinWeightsObject)
GET_OBJECT_FUNC(XVertexDuplicationIndicesObject)
GET_OBJECT_FUNC(XTextureFilenameObject)
};
class XFileEnumObject {
friend class XFileLoader;
private:
XFileLoader *_file{};
public:
bool getChild(uint id, XFileData &child) {
if (_file) {
if (id < _file->_xobjects.size()) {
child._xobject = _file->_xobjects[id];
return true;
}
}
return false;
}
bool getChildren(uint &num) {
if (_file) {
num = _file->_xobjects.size();
return true;
}
return false;
}
};
} // namespace Wintermute
#endif

View File

@ -26,11 +26,12 @@
*/
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/base/gfx/base_renderer3d.h"
#include "engines/wintermute/base/gfx/xmaterial.h"
#include "engines/wintermute/base/gfx/xframe_node.h"
#include "engines/wintermute/base/gfx/xmodel.h"
#include "engines/wintermute/base/gfx/xloader.h"
#include "engines/wintermute/base/gfx/xfile_loader.h"
#include "engines/wintermute/dcgf.h"
namespace Wintermute {
@ -97,42 +98,35 @@ void FrameNode::setTransformation(int slot, Math::Vector3d pos, Math::Vector3d s
}
//////////////////////////////////////////////////////////////////////////
bool FrameNode::loadFromX(const Common::String &filename, XFileLexer &lexer, XModel *model, Common::Array<MaterialReference> &materialReferences) {
bool FrameNode::loadFromXData(const Common::String &filename, XModel *model, XFileData *xobj, Common::Array<MaterialReference> &materialReferences) {
_gameRef->miniUpdate();
bool ret = true;
bool res = true;
setName(lexer.tokenToString().c_str());
lexer.advanceToNextToken();
lexer.advanceOnOpenBraces();
while (!lexer.eof()) {
if (lexer.tokenIsIdentifier("Frame")) {
lexer.advanceToNextToken();
FrameNode *child = new FrameNode(_gameRef);
if (child->loadFromX(filename, lexer, model, materialReferences)) {
_frames.add(child);
} else {
delete child;
}
} else if (lexer.tokenIsIdentifier("Mesh")) {
lexer.advanceToNextToken();
XMesh *mesh = _gameRef->_renderer3D->createXMesh();
if (mesh->loadFromX(filename, lexer, materialReferences)) {
_meshes.add(mesh);
} else {
delete mesh;
}
} else if (lexer.tokenIsIdentifier("FrameTransformMatrix")) {
lexer.advanceToNextToken();
lexer.advanceToNextToken(); // skip optional name
lexer.advanceOnOpenBraces();
// get the type of the object
XClassType objectType;
res = xobj->getType(objectType);
if (objectType == kXClassMesh) { // load a child mesh
XMesh *mesh = _gameRef->_renderer3D->createXMesh();
res = mesh->loadFromXData(filename, xobj, materialReferences);
if (res) {
_meshes.add(mesh);
return true;
} else {
delete mesh;
return false;
}
} else if (objectType == kXClassFrameTransformMatrix) { // load the transformation matrix
XFrameTransformMatrixObject *frameTransformMatrix = xobj->getXFrameTransformMatrixObject();
if (!frameTransformMatrix) {
BaseEngine::LOG(0, "Error loading transformation matrix");
return false;
} else {
// TODO: check if this is the right format
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) {
_transformationMatrix(c, r) = lexer.readFloat();
_transformationMatrix(c, r) = frameTransformMatrix->_frameMatrix[r * 4 + c];
}
}
@ -148,84 +142,85 @@ bool FrameNode::loadFromX(const Common::String &filename, XFileLexer &lexer, XMo
_transformationMatrix(1, 2) *= -1.0f;
_originalMatrix = _transformationMatrix;
lexer.skipTerminator();
lexer.advanceToNextToken();
} else if (lexer.reachedClosedBraces()) {
lexer.advanceToNextToken();
break;
} else {
warning("FrameNode::loadFromX unexpected %i token excountered", lexer.getTypeOfToken());
ret = false;
break;
return true;
}
} else if (objectType == kXClassAnimationSet) { // load animation set
return model->loadAnimationSet(filename, xobj);
} else if (objectType == kXClassAnimation) { // load a single animation (shouldn't happen here)
return model->loadAnimation(filename, xobj);
} else if (objectType == kXClassFrame) { // create a new child frame
FrameNode *childFrame = new FrameNode(_gameRef);
// get the name of the child frame
res = XModel::loadName(childFrame, xobj);
if (!res) {
BaseEngine::LOG(0, "Error loading frame name");
delete childFrame;
return res;
}
// Enumerate child objects.
res = false;
uint32 numChildren = 0;
xobj->getChildren(numChildren);
for (uint32 i = 0; i < numChildren; i++) {
XFileData xchildData;
res = xobj->getChild(i, xchildData);
if (res)
res = childFrame->loadFromXData(filename, model, &xchildData, materialReferences);
}
if (res)
_frames.add(childFrame);
else
delete childFrame;
return res;
} else if (objectType == kXClassAnimTicksPerSecond) {
if (!xobj->getXAnimTicksPerSecondObject()) {
BaseEngine::LOG(0, "Error loading ticks per seconds info");
return res;
} else {
model->_ticksPerSecond = xobj->getXAnimTicksPerSecondObject()->_animTicksPerSecond;
return true;
}
} else if (objectType == kXClassMaterial) {
MaterialReference materialReference;
xobj->getName(materialReference._name);
materialReference._material = new Material(_gameRef);
materialReference._material->loadFromX(xobj, filename);
materialReferences.push_back(materialReference);
return true;
}
return ret;
return true;
}
bool FrameNode::loadFromXAsRoot(const Common::String &filename, XFileLexer &lexer, XModel *model, Common::Array<MaterialReference> &materialReferences) {
// technically, there is no root node in a .X file
// so we just start parsing it here
lexer.advanceToNextToken();
bool FrameNode::mergeFromXData(const Common::String &filename, XModel *model, XFileData *xobj) {
bool res = true;
while (!lexer.eof()) {
if (lexer.tokenIsIdentifier("Frame")) {
lexer.advanceToNextToken();
FrameNode *child = new FrameNode(_gameRef);
if (child->loadFromX(filename, lexer, model, materialReferences)) {
_frames.add(child);
} else {
delete child;
}
} else if (lexer.tokenIsIdentifier("Mesh")) {
lexer.advanceToNextToken();
XMesh *mesh = _gameRef->_renderer3D->createXMesh();
// get the type of the object
XClassType objectType;
res = xobj->getType(objectType);
if (!res) {
BaseEngine::LOG(0, "Error getting object type");
return res;
} else if (objectType == kXClassAnimationSet) { // load animation set
return model->loadAnimationSet(filename, xobj);
} else if (objectType == kXClassAnimation) { // load a single animation (shouldn't happen here)
return model->loadAnimation(filename, xobj);
} else if (objectType == kXClassFrame) { // scan child frames
// Enumerate child objects.
res = false;
if (mesh->loadFromX(filename, lexer, materialReferences)) {
_meshes.add(mesh);
} else {
delete mesh;
}
} else if (lexer.tokenIsIdentifier("AnimTicksPerSecond")) {
lexer.advanceToNextToken();
lexer.advanceOnOpenBraces();
model->_ticksPerSecond = lexer.readInt();
lexer.advanceToNextToken(); // skip closed braces
} else if (lexer.tokenIsIdentifier("AnimationSet")) {
lexer.advanceToNextToken();
model->loadAnimationSet(lexer, filename);
} else if (lexer.tokenIsIdentifier("template")) {
// we can ignore templates
while (!lexer.eof()) {
if (lexer.reachedClosedBraces()) {
break;
}
lexer.advanceToNextToken();
}
lexer.advanceToNextToken();
} else if(lexer.tokenIsIdentifier("Material")) {
lexer.advanceToNextToken();
MaterialReference materialReference;
materialReference._name = lexer.tokenToString();
materialReference._material = new Material(_gameRef);
materialReference._material->loadFromX(lexer, filename);
materialReferences.push_back(materialReference);
} else if (lexer.tokenIsOfType(NULL_CHAR)) {
// prevents some unnecessary warnings
lexer.advanceToNextToken();
} else {
warning("FrameNode::loadFromXAsRoot unknown token %i encountered", lexer.getTypeOfToken());
lexer.advanceToNextToken(); // just ignore it for the moment
uint32 numChildren = 0;
xobj->getChildren(numChildren);
for (uint32 i = 0; i < numChildren; i++) {
XFileData xchildData;
res = xobj->getChild(i, xchildData);
if (res)
res = mergeFromXData(filename, model, &xchildData);
}
return res;
}
return true;
}

View File

@ -40,8 +40,8 @@
namespace Wintermute {
class XModel;
class XFileData;
class BaseSprite;
class XFileLexer;
class FrameNode : public BaseNamedObject {
public:
@ -55,8 +55,8 @@ public:
bool renderFlatShadowModel();
bool updateShadowVol(ShadowVolume *shadow, Math::Matrix4 &modelMat, const Math::Vector3d &light, float extrusionDepth);
bool loadFromX(const Common::String &filename, XFileLexer &lexer, XModel *model, Common::Array<MaterialReference> &materialReferences);
bool loadFromXAsRoot(const Common::String &filename, XFileLexer &lexer, XModel *model, Common::Array<MaterialReference> &materialReferences);
bool loadFromXData(const Common::String &filename, XModel *model, XFileData *xobj, Common::Array<MaterialReference> &materialReferences);
bool mergeFromXData(const Common::String &filename, XModel *model, XFileData *xobj);
bool findBones(FrameNode *rootFrame);
FrameNode *findFrame(const char *frameName);
Math::Matrix4 *getCombinedMatrix();

View File

@ -1,483 +0,0 @@
/* 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/>.
*
*/
#include "common/algorithm.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "engines/wintermute/base/gfx/xloader.h"
namespace Wintermute {
void nextTokenText(Common::MemoryReadStream &buffer, int &lineCount, Token &tok) {
char current = buffer.readSByte();
while (true) {
if (Common::isSpace(current)) {
if (current == '\n') {
++lineCount;
}
current = buffer.readSByte();
} else if (current == '/' || current == '#') {
// single slashes do not seem to be used in .X files,
// so checking for one should be enough
while (current != '\n') {
current = buffer.readSByte();
}
current = buffer.readSByte();
++lineCount;
} else {
break;
}
}
if (Common::isAlpha(current) || current == '_') {
tok.pushChar(current);
current = buffer.readSByte();
while (Common::isAlnum(current) || current == '_' || current == '-') {
tok.pushChar(current);
current = buffer.readSByte();
}
buffer.seek(-1, SEEK_CUR);
tok._type = IDENTIFIER;
return;
} else if (Common::isDigit(current) || current == '-') {
tok.pushChar(current);
current = buffer.readSByte();
while (Common::isDigit(current)) {
tok.pushChar(current);
current = buffer.readSByte();
}
if (current == '.') {
tok.pushChar(current);
current = buffer.readSByte();
while (Common::isDigit(current)) {
tok.pushChar(current);
current = buffer.readSByte();
}
buffer.seek(-1, SEEK_CUR);
tok._type = FLOAT;
return;
}
buffer.seek(-1, SEEK_CUR);
tok._type = INT;
return;
} else if (current == '<') {
// a uuid consists of 36 characters, 32 alphanumeric characters
// and four "-". We add space for a null character at the
// end of the tempoary buffer
const int uuidSize = 37;
char uuid[uuidSize];
buffer.read(uuid, 36);
uuid[uuidSize - 1] = 0;
current = buffer.readSByte();
if (current != '>') {
warning("Wrong UUID format at line %i", lineCount);
} else {
tok._textVal = uuid;
tok._type = UUID;
return;
}
} else if (current == '"') {
current = buffer.readSByte();
while (current != '"') {
tok.pushChar(current);
current = buffer.readSByte();
}
tok._type = STRING;
return;
}
switch (current) {
case '(':
tok._type = OPEN_PAREN;
break;
case ')':
tok._type = CLOSE_PAREN;
break;
case '{':
tok._type = OPEN_BRACES;
break;
case '}':
tok._type = CLOSE_BRACES;
break;
case ']':
tok._type = OPEN_BRACKET;
break;
case '[':
tok._type = CLOSE_BRACKET;
break;
case ',':
tok._type = COMMA;
break;
case ';':
tok._type = SEMICOLON;
break;
case '.':
tok._type = DOT;
break;
case '\0':
tok._type = NULL_CHAR;
break;
default:
tok._type = UNKNOWN_TOKEN;
warning("Unknown token %c at line %i", current, lineCount);
}
}
// based on MSDN .X file format documentation
const uint16 XBIN_TOKEN_NAME = 1;
const uint16 XBIN_TOKEN_STRING = 2;
const uint16 XBIN_TOKEN_INTEGER = 3;
const uint16 XBIN_TOKEN_GUID = 5;
const uint16 XBIN_TOKEN_INTEGER_LIST = 6;
const uint16 XBIN_TOKEN_FLOAT_LIST = 7;
const uint16 XBIN_TOKEN_OBRACE = 10;
const uint16 XBIN_TOKEN_CBRACE = 11;
const uint16 XBIN_TOKEN_OPAREN = 12;
const uint16 XBIN_TOKEN_CPAREN = 13;
const uint16 XBIN_TOKEN_OBRACKET = 14;
const uint16 XBIN_TOKEN_CBRACKET = 15;
const uint16 XBIN_TOKEN_OANGLE = 16;
const uint16 XBIN_TOKEN_CANGLE = 17;
const uint16 XBIN_TOKEN_DOT = 18;
const uint16 XBIN_TOKEN_COMMA = 19;
const uint16 XBIN_TOKEN_SEMICOLON = 20;
const uint16 XBIN_TOKEN_TEMPLATE = 31;
const uint16 XBIN_TOKEN_WORD = 40;
const uint16 XBIN_TOKEN_DWORD = 41;
const uint16 XBIN_TOKEN_FLOAT = 42;
const uint16 XBIN_TOKEN_DOUBLE = 43;
const uint16 XBIN_TOKEN_CHAR = 44;
const uint16 XBIN_TOKEN_UCHAR = 45;
const uint16 XBIN_TOKEN_SWORD = 46;
const uint16 XBIN_TOKEN_SDWORD = 47;
const uint16 XBIN_TOKEN_VOID = 48;
const uint16 XBIN_TOKEN_LPSTR = 49;
const uint16 XBIN_TOKEN_UNICODE = 50;
const uint16 XBIN_TOKEN_CSTRING = 51;
const uint16 XBIN_TOKEN_ARRAY = 52;
void XFileLexer::nextTokenBinary() {
uint16 current = _buffer.readUint16LE();
uint32 length = -1;
switch (current) {
case XBIN_TOKEN_NAME:
length = _buffer.readUint32LE();
for (uint32 i = 0; i < length; ++i) {
_tok.pushChar(_buffer.readByte());
}
_tok._type = IDENTIFIER;
break;
case XBIN_TOKEN_STRING:
length = _buffer.readUint32LE();
for (uint32 i = 0; i < length; ++i) {
_tok.pushChar(_buffer.readByte());
}
_tok._type = STRING;
break;
case XBIN_TOKEN_INTEGER:
_tok._integerVal = _buffer.readUint32LE();
_tok._type = INT;
break;
case XBIN_TOKEN_GUID:
// ignore the UUID value
_buffer.readUint32LE();
_buffer.readUint16LE();
_buffer.readUint16LE();
for (int i = 0; i < 8; ++i) {
_buffer.readByte();
}
_tok._type = UUID;
break;
case XBIN_TOKEN_INTEGER_LIST:
_integersToRead = _buffer.readUint32LE();
_tok._type = INT;
_expectsTerminator = false;
break;
case XBIN_TOKEN_FLOAT_LIST:
_floatsToRead = _buffer.readUint32LE();
_tok._type = FLOAT;
_expectsTerminator = false;
break;
case XBIN_TOKEN_OBRACE:
_tok._type = OPEN_BRACES;
break;
case XBIN_TOKEN_CBRACE:
_tok._type = CLOSE_BRACES;
break;
case XBIN_TOKEN_OPAREN:
_tok._type = OPEN_PAREN;
break;
case XBIN_TOKEN_CPAREN:
_tok._type = CLOSE_PAREN;
break;
case XBIN_TOKEN_OBRACKET:
_tok._type = OPEN_BRACKET;
break;
case XBIN_TOKEN_CBRACKET:
_tok._type = CLOSE_BRACKET;
break;
case XBIN_TOKEN_OANGLE:
_tok._type = OPEN_ANGLE;
break;
case XBIN_TOKEN_CANGLE:
_tok._type = CLOSE_ANGLE;
break;
case XBIN_TOKEN_DOT:
_tok._type = DOT;
break;
case XBIN_TOKEN_COMMA:
_tok._type = COMMA;
break;
case XBIN_TOKEN_SEMICOLON:
_tok._type = SEMICOLON;
break;
case XBIN_TOKEN_TEMPLATE:
_tok._textVal = "template";
_tok._type = IDENTIFIER;
break;
case XBIN_TOKEN_WORD:
break;
case XBIN_TOKEN_DWORD:
break;
case XBIN_TOKEN_FLOAT:
break;
case XBIN_TOKEN_DOUBLE:
break;
case XBIN_TOKEN_CHAR:
break;
case XBIN_TOKEN_UCHAR:
break;
case XBIN_TOKEN_SWORD:
break;
case XBIN_TOKEN_SDWORD:
break;
case XBIN_TOKEN_VOID:
break;
case XBIN_TOKEN_LPSTR:
break;
case XBIN_TOKEN_UNICODE:
break;
case XBIN_TOKEN_CSTRING:
break;
case XBIN_TOKEN_ARRAY:
break;
case 0:
_tok._type = NULL_CHAR;
break;
default:
_tok._type = UNKNOWN_TOKEN;
warning("XFileLexer::nextBinaryToken: Unknown token encountered");
}
}
XFileLexer::XFileLexer(byte *buffer, uint32 fileSize, bool isText)
: _buffer(buffer, fileSize), _lineCount(1), _isText(isText), _integersToRead(0), _floatsToRead(0), _expectsTerminator(true) {
}
void XFileLexer::advanceToNextToken() {
_tok._textVal.clear();
if (_isText) {
nextTokenText(_buffer, _lineCount, _tok);
} else {
nextTokenBinary();
}
}
void XFileLexer::skipTerminator() {
if (_expectsTerminator) {
advanceToNextToken();
}
_expectsTerminator = (_floatsToRead == 0) && (_integersToRead == 0);
}
bool XFileLexer::eof() {
return _buffer.pos() == _buffer.size();
}
bool XFileLexer::tokenIsIdentifier() {
return _tok._type == IDENTIFIER;
}
bool XFileLexer::tokenIsIdentifier(const char *val) {
return _tok._type == IDENTIFIER && _tok._textVal == val;
}
void XFileLexer::advanceOnOpenBraces() {
if (_tok._type == OPEN_BRACES) {
advanceToNextToken();
}
}
bool XFileLexer::reachedClosedBraces() {
return _tok._type == CLOSE_BRACES;
}
TokenType XFileLexer::getTypeOfToken() {
return _tok._type;
}
bool XFileLexer::tokenIsOfType(TokenType type) {
return _tok._type == type;
}
int XFileLexer::tokenToInt() {
return atoi(_tok._textVal.c_str());
}
double XFileLexer::tokenToFloat() {
return atof(_tok._textVal.c_str());
}
Common::String XFileLexer::tokenToString() {
return _tok._textVal;
}
uint32 XFileLexer::tokenToUint32() {
// All integer values in a .X file are unsigned and at most 32 bit
// so parsing to unsigned long and then converting down should be fine
return strtoul(_tok._textVal.c_str(), nullptr, 10);
}
void XFileLexer::skipObject() {
advanceToNextToken(); // optional name
advanceToNextToken();
advanceOnOpenBraces();
// we have one open braces right now, once the counter reaches zero, we're done
int closedBracesCount = 1;
while (closedBracesCount > 0) {
while (_integersToRead > 0 || _floatsToRead > 0) {
if (_integersToRead > 0) {
readInt();
}
if (_floatsToRead > 0) {
readFloat();
}
}
if (_tok._type == OPEN_BRACES) {
++closedBracesCount;
}
if (_tok._type == CLOSE_BRACES) {
--closedBracesCount;
}
advanceToNextToken();
}
}
void Token::pushChar(char c) {
_textVal.insertChar(c, _textVal.size());
}
float XFileLexer::readFloat() {
if (_floatsToRead > 0) {
--_floatsToRead;
float tmp = _buffer.readFloatLE();
if (_floatsToRead == 0) {
advanceToNextToken();
}
return tmp;
}
float tmp = tokenToFloat();
advanceToNextToken();
advanceToNextToken(); // skip comma or semicolon
return tmp;
}
int XFileLexer::readInt() {
if (_integersToRead > 0) {
--_integersToRead;
int tmp = _buffer.readUint32LE();
if (_integersToRead == 0) {
advanceToNextToken();
}
return tmp;
}
int tmp = tokenToInt();
advanceToNextToken();
advanceToNextToken(); // skip comma or semicolon
return tmp;
}
Common::String XFileLexer::readString() {
Common::String tmp = tokenToString();
advanceToNextToken();
advanceToNextToken(); // skip comma or semicolon
return tmp;
}
uint32 XFileLexer::readUint32() {
if (_integersToRead > 0) {
--_integersToRead;
uint32 tmp = _buffer.readUint32LE();
if (_integersToRead == 0) {
advanceToNextToken();
}
return tmp;
}
uint32 tmp = tokenToUint32();
advanceToNextToken();
advanceToNextToken(); // skip comma or semicolon
return tmp;
}
} // namespace Wintermute

View File

@ -1,100 +0,0 @@
/* 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/>.
*
*/
#ifndef WINTERMUTE_X_FILE_LEXER_H
#define WINTERMUTE_X_FILE_LEXER_H
#include "common/memstream.h"
#include "common/scummsys.h"
#include "common/str.h"
namespace Wintermute {
enum TokenType {
IDENTIFIER = 0,
STRING,
UUID,
INT,
FLOAT,
OPEN_BRACES,
CLOSE_BRACES,
OPEN_PAREN,
CLOSE_PAREN,
OPEN_ANGLE,
CLOSE_ANGLE,
OPEN_BRACKET,
CLOSE_BRACKET,
SEMICOLON,
COMMA,
DOT,
NULL_CHAR,
UNKNOWN_TOKEN
};
struct Token {
TokenType _type;
Common::String _textVal;
int _integerVal;
float _floatVal;
void pushChar(char c);
};
class XFileLexer {
public:
XFileLexer(byte *buffer, uint32 fileSize, bool isText);
void advanceToNextToken();
void skipTerminator();
bool eof();
bool tokenIsIdentifier();
bool tokenIsIdentifier(const char *val);
void advanceOnOpenBraces();
bool reachedClosedBraces();
TokenType getTypeOfToken();
bool tokenIsOfType(TokenType type);
int tokenToInt();
double tokenToFloat();
Common::String tokenToString();
uint32 tokenToUint32();
void skipObject();
float readFloat();
int readInt();
Common::String readString();
uint32 readUint32();
private:
void nextTokenBinary();
Token _tok;
Common::MemoryReadStream _buffer;
int _lineCount;
bool _isText;
int _integersToRead;
int _floatsToRead;
bool _expectsTerminator;
};
} // namespace Wintermute
#endif

View File

@ -30,7 +30,7 @@
#include "engines/wintermute/base/base_surface_storage.h"
#include "engines/wintermute/base/gfx/base_surface.h"
#include "engines/wintermute/base/gfx/xmaterial.h"
#include "engines/wintermute/base/gfx/xloader.h"
#include "engines/wintermute/base/gfx/xfile_loader.h"
#include "engines/wintermute/dcgf.h"
#include "engines/wintermute/utils/path_util.h"
#include "engines/wintermute/video/video_theora_player.h"
@ -141,51 +141,44 @@ BaseSurface *Material::getSurface() {
}
}
bool Material::loadFromX(XFileLexer &lexer, const Common::String &filename) {
lexer.advanceToNextToken(); // skip optional name
lexer.advanceOnOpenBraces();
bool Material::loadFromX(XFileData *xobj, const Common::String &filename) {
XMaterialObject *material = xobj->getXMaterialObject();
if (!material)
return false;
_diffuse.r() = lexer.readFloat();
_diffuse.g() = lexer.readFloat();
_diffuse.b() = lexer.readFloat();
_diffuse.a() = lexer.readFloat();
lexer.skipTerminator(); // skip semicolon
_diffuse.r() = material->_colorR;
_diffuse.g() = material->_colorG;
_diffuse.b() = material->_colorB;
_diffuse.a() = material->_colorA;
_shininess = lexer.readFloat();
_shininess = material->_power;
_specular.r() = lexer.readFloat();
_specular.g() = lexer.readFloat();
_specular.b() = lexer.readFloat();
_specular.r() = material->_specularR;
_specular.g() = material->_specularG;
_specular.b() = material->_specularB;
_specular.a() = 1.0f;
lexer.skipTerminator(); // skip semicolon
_emissive.r() = lexer.readFloat();
_emissive.g() = lexer.readFloat();
_emissive.b() = lexer.readFloat();
_emissive.r() = material->_emissiveR;
_emissive.g() = material->_emissiveG;
_emissive.b() = material->_emissiveB;
_emissive.a() = 1.0f;
lexer.skipTerminator();
while (!lexer.eof()) {
// according to assimp sources, we got both possibilities
// wine also seems to support this
// MSDN only names the first option
if (lexer.tokenIsIdentifier("TextureFilename") || lexer.tokenIsIdentifier("TextureFileName")) {
lexer.advanceToNextToken(); // skip optional name
lexer.advanceOnOpenBraces();
uint32 numChildren = 0;
xobj->getChildren(numChildren);
Common::String textureFilename = lexer.readString();
PathUtil::getDirectoryName(filename);
setTexture(PathUtil::getDirectoryName(filename) + textureFilename);
lexer.advanceToNextToken(); // skip semicolon
} else if (lexer.tokenIsIdentifier()) {
warning("Material::loadFromX unexpected token %i", lexer.getTypeOfToken());
return false;
} else {
break;
for (uint32 i = 0; i < numChildren; i++) {
XFileData xchildData;
XClassType objectType;
bool res = xobj->getChild(i, xchildData);
if (res) {
res = xchildData.getType(objectType);
if (res && objectType == kXClassTextureFilename) {
Common::String textureFilename = xchildData.getXTextureFilenameObject()->_filename;
setTexture(PathUtil::getDirectoryName(filename) + textureFilename);
}
}
}
lexer.advanceToNextToken(); // skip semicolon
return true;
}

View File

@ -35,7 +35,7 @@ namespace Wintermute {
class BaseSprite;
class BaseSurface;
class VideoTheoraPlayer;
class XFileLexer;
class XFileData;
struct ColorValue {
float &r() {
@ -89,7 +89,7 @@ public:
bool setTheora(VideoTheoraPlayer *theora, bool adoptName = false);
BaseSurface *getSurface();
bool loadFromX(XFileLexer &lexer, const Common::String &filename);
bool loadFromX(XFileData *xobj, const Common::String &filename);
bool invalidateDeviceObjects();
bool restoreDeviceObjects();

View File

@ -29,8 +29,9 @@
#include "engines/wintermute/base/gfx/xmaterial.h"
#include "engines/wintermute/base/gfx/xmesh.h"
#include "engines/wintermute/base/gfx/xframe_node.h"
#include "engines/wintermute/base/gfx/xloader.h"
#include "engines/wintermute/base/gfx/xfile_loader.h"
#include "engines/wintermute/base/gfx/xmodel.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/math/math_util.h"
namespace Wintermute {
@ -60,10 +61,20 @@ XMesh::~XMesh() {
}
//////////////////////////////////////////////////////////////////////////
bool XMesh::loadFromX(const Common::String &filename, XFileLexer &lexer, Common::Array<MaterialReference> &materialReferences) {
lexer.advanceToNextToken(); // skip the name
lexer.advanceOnOpenBraces();
_vertexCount = lexer.readInt();
bool XMesh::loadFromXData(const Common::String &filename, XFileData *xobj, Common::Array<MaterialReference> &materialReferences) {
// get name
if (!XModel::loadName(this, xobj)) {
BaseEngine::LOG(0, "Error loading mesh name");
return false;
}
XMeshObject *mesh = xobj->getXMeshObject();
if (!mesh) {
BaseEngine::LOG(0, "Error loading skin mesh");
return false;
}
_vertexCount = mesh->_numVertices;
// vertex format for .X meshes will be position + normals + textures
_vertexData = new float[kVertexComponentCount * _vertexCount]();
@ -72,76 +83,47 @@ bool XMesh::loadFromX(const Common::String &filename, XFileLexer &lexer, Common:
// TODO: might have to generate normals if file does not contain any
_vertexNormalData = new float[3 * _vertexCount]();
parsePositionCoords(lexer);
parsePositionCoords(mesh);
int faceCount = lexer.readInt();
int faceCount = mesh->_numFaces;
Common::Array<int> indexCountPerFace;
parseFaces(lexer, faceCount, indexCountPerFace);
parseFaces(mesh, faceCount, indexCountPerFace);
while (!lexer.eof()) {
if (lexer.tokenIsIdentifier("MeshTextureCoords")) {
lexer.advanceToNextToken();
lexer.advanceToNextToken();
lexer.advanceOnOpenBraces();
uint32 numChildren = 0;
xobj->getChildren(numChildren);
parseTextureCoords(lexer);
} else if (lexer.tokenIsIdentifier("MeshNormals")) {
lexer.advanceToNextToken();
lexer.advanceToNextToken();
lexer.advanceOnOpenBraces();
for (uint32 i = 0; i < numChildren; i++) {
XFileData xchildData;
XClassType objectType;
if (xobj->getChild(i, xchildData)) {
if (xchildData.getType(objectType)) {
if (objectType == kXClassMeshTextureCoords) {
parseTextureCoords(&xchildData);
} else if (objectType == kXClassMeshNormals) {
parseNormalCoords(&xchildData);
} else if (objectType == kXClassMeshMaterialList) {
parseMaterials(&xchildData, faceCount, filename, materialReferences, indexCountPerFace);
} else if (objectType == kXClassMaterial) {
Material *mat = new Material(_gameRef);
mat->loadFromX(&xchildData, filename);
_materials.add(mat);
parseNormalCoords(lexer);
} else if (lexer.tokenIsIdentifier("MeshMaterialList")) {
lexer.advanceToNextToken();
lexer.advanceToNextToken();
lexer.advanceOnOpenBraces();
parseMaterials(lexer, faceCount, filename, materialReferences, indexCountPerFace);
} else if (lexer.tokenIsIdentifier("Material")) {
lexer.advanceToNextToken();
Material *mat = new Material(_gameRef);
mat->loadFromX(lexer, filename);
_materials.add(mat);
// one material = one index range
_numAttrs = 1;
_indexRanges.push_back(0);
_indexRanges.push_back(_indexData.size());
} else if (lexer.tokenIsIdentifier("XSkinMeshHeader")) {
lexer.advanceToNextToken();
lexer.advanceOnOpenBraces();
// if any of this is zero, we should have an unskinned mesh
lexer.readInt(); // max skin weights per vertex
lexer.readInt(); // max skin weights per face
int boneCount = lexer.readInt();
_skinnedMesh = boneCount > 0;
lexer.advanceToNextToken(); // skip semicolon
} else if (lexer.tokenIsIdentifier("SkinWeights")) {
// but now we certainly should have a skinned mesh
_skinnedMesh = true;
lexer.advanceToNextToken();
lexer.advanceOnOpenBraces();
parseSkinWeights(lexer);
} else if (lexer.tokenIsIdentifier("DeclData")) {
lexer.advanceToNextToken();
lexer.advanceToNextToken();
lexer.advanceOnOpenBraces();
parseVertexDeclaration(lexer);
} else if (lexer.tokenIsIdentifier()) {
lexer.skipObject();
} else if (lexer.reachedClosedBraces()) {
lexer.advanceToNextToken(); // skip closed braces
break;
} else {
warning("XMesh::loadFromX unknown token %i encountered", lexer.getTypeOfToken());
lexer.advanceToNextToken();
// one material = one index range
_numAttrs = 1;
_indexRanges.push_back(0);
_indexRanges.push_back(_indexData.size());
} else if (objectType == kXClassSkinMeshHeader) {
int boneCount = xchildData.getXSkinMeshHeaderObject()->_nBones;
_skinnedMesh = boneCount > 0;
} else if (objectType == kXClassSkinWeights) {
_skinnedMesh = true;
parseSkinWeights(&xchildData);
} else if (objectType == kXClassDeclData) {
parseVertexDeclaration(&xchildData);
}
}
}
}
@ -468,43 +450,41 @@ bool XMesh::restoreDeviceObjects() {
}
}
bool XMesh::parsePositionCoords(XFileLexer &lexer) {
bool XMesh::parsePositionCoords(XMeshObject *mesh) {
for (uint i = 0; i < _vertexCount; ++i) {
_vertexPositionData[i * 3 + 0] = mesh->_vertices[i]._x;
_vertexPositionData[i * 3 + 1] = mesh->_vertices[i]._y;
_vertexPositionData[i * 3 + 2] = mesh->_vertices[i]._z;
for (int j = 0; j < 3; ++j) {
_vertexPositionData[i * 3 + j] = lexer.readFloat();
_vertexData[i * kVertexComponentCount + kPositionOffset + j] = _vertexPositionData[i * 3 + j];
}
_vertexPositionData[i * 3 + 2] *= -1.0f;
_vertexData[i * kVertexComponentCount + kPositionOffset + 2] *= -1.0f;
lexer.skipTerminator(); // skip semicolon
}
return true;
}
bool XMesh::parseFaces(XFileLexer &lexer, int faceCount, Common::Array<int>& indexCountPerFace) {
bool XMesh::parseFaces(XMeshObject *mesh, int faceCount, Common::Array<int> &indexCountPerFace) {
for (int i = 0; i < faceCount; ++i) {
int indexCount = lexer.readInt();
XMeshFace *face = &mesh->_faces[i];
int indexCount = face->_numFaceVertexIndices;
if (indexCount == 3) {
uint16 index1 = lexer.readInt();
uint16 index2 = lexer.readInt();
uint16 index3 = lexer.readInt();
uint16 index1 = face->_faceVertexIndices[0];
uint16 index2 = face->_faceVertexIndices[1];
uint16 index3 = face->_faceVertexIndices[2];
_indexData.push_back(index3);
_indexData.push_back(index2);
_indexData.push_back(index1);
lexer.skipTerminator(); // skip semicolon
indexCountPerFace.push_back(3);
} else if (indexCount == 4) {
uint16 index1 = lexer.readInt();
uint16 index2 = lexer.readInt();
uint16 index3 = lexer.readInt();
uint16 index4 = lexer.readInt();
uint16 index1 = face->_faceVertexIndices[0];
uint16 index2 = face->_faceVertexIndices[1];
uint16 index3 = face->_faceVertexIndices[2];
uint16 index4 = face->_faceVertexIndices[3];
_indexData.push_back(index3);
_indexData.push_back(index2);
@ -514,8 +494,6 @@ bool XMesh::parseFaces(XFileLexer &lexer, int faceCount, Common::Array<int>& ind
_indexData.push_back(index3);
_indexData.push_back(index1);
lexer.skipTerminator(); // skip semicolon
indexCountPerFace.push_back(6);
} else {
warning("XMeshOpenGL::loadFromX faces with more than four vertices are not supported");
@ -526,62 +504,59 @@ bool XMesh::parseFaces(XFileLexer &lexer, int faceCount, Common::Array<int>& ind
return true;
}
bool XMesh::parseTextureCoords(XFileLexer &lexer) {
bool XMesh::parseTextureCoords(XFileData *xobj) {
XMeshTextureCoordsObject *texCoords = xobj->getXMeshTextureCoordsObject();
if (!texCoords)
return false;
// should be the same as _vertexCount
int textureCoordCount = lexer.readInt();
int textureCoordCount = texCoords->_numTextureCoords;
for (int i = 0; i < textureCoordCount; ++i) {
_vertexData[i * kVertexComponentCount + kTextureCoordOffset + 0] = lexer.readFloat();
_vertexData[i * kVertexComponentCount + kTextureCoordOffset + 1] = lexer.readFloat();
lexer.skipTerminator(); // skip semicolon
}
if (lexer.reachedClosedBraces()) {
lexer.advanceToNextToken();
} else {
warning("Missing } in mesh object");
_vertexData[i * kVertexComponentCount + kTextureCoordOffset + 0] = texCoords->_textureCoords[i]._u;
_vertexData[i * kVertexComponentCount + kTextureCoordOffset + 1] = texCoords->_textureCoords[i]._v;
}
return true;
}
bool XMesh::parseNormalCoords(XFileLexer &lexer) {
bool XMesh::parseNormalCoords(XFileData *xobj) {
XMeshNormalsObject *normals = xobj->getXMeshNormalsObject();
if (!normals)
return false;
// should be the same as _vertex count
uint vertexNormalCount = lexer.readInt();
// assert(vertexNormalCount == _vertexCount);
uint vertexNormalCount = normals->_numNormals;
//assert(vertexNormalCount == _vertexCount);
Common::Array<float> vertexNormalData;
vertexNormalData.resize(3 * vertexNormalCount);
for (uint i = 0; i < vertexNormalCount; ++i) {
vertexNormalData[i * 3 + 0] = lexer.readFloat();
vertexNormalData[i * 3 + 1] = lexer.readFloat();
vertexNormalData[i * 3 + 0] = normals->_normals[i]._x;
vertexNormalData[i * 3 + 1] = normals->_normals[i]._y;
// mirror z coordinate to change to OpenGL coordinate system
vertexNormalData[i * 3 + 2] = -lexer.readFloat();
lexer.skipTerminator(); // skip semicolon
vertexNormalData[i * 3 + 2] = -normals->_normals[i]._z;
}
uint faceNormalCount = lexer.readInt();
uint faceNormalCount = normals->_numFaceNormals;
Common::Array<int> faceNormals;
for (uint i = 0; i < faceNormalCount; ++i) {
int indexCount = lexer.readInt();
XMeshFace *normalFace = &normals->_faceNormals[i];
int indexCount = normalFace->_numFaceVertexIndices;
if (indexCount == 3) {
uint16 index1 = lexer.readInt();
uint16 index2 = lexer.readInt();
uint16 index3 = lexer.readInt();
uint16 index1 = normalFace->_faceVertexIndices[0];
uint16 index2 = normalFace->_faceVertexIndices[1];
uint16 index3 = normalFace->_faceVertexIndices[2];
faceNormals.push_back(index3);
faceNormals.push_back(index2);
faceNormals.push_back(index1);
lexer.skipTerminator(); // skip semicolon
} else if (indexCount == 4) {
uint16 index1 = lexer.readInt();
uint16 index2 = lexer.readInt();
uint16 index3 = lexer.readInt();
uint16 index4 = lexer.readInt();
uint16 index1 = normalFace->_faceVertexIndices[0];
uint16 index2 = normalFace->_faceVertexIndices[1];
uint16 index3 = normalFace->_faceVertexIndices[2];
uint16 index4 = normalFace->_faceVertexIndices[3];
faceNormals.push_back(index3);
faceNormals.push_back(index2);
@ -590,8 +565,6 @@ bool XMesh::parseNormalCoords(XFileLexer &lexer) {
faceNormals.push_back(index4);
faceNormals.push_back(index3);
faceNormals.push_back(index1);
lexer.skipTerminator(); // skip semicolon
} else {
warning("XMeshOpenGL::loadFromX faces with more than four vertices are not supported");
return false;
@ -610,27 +583,28 @@ bool XMesh::parseNormalCoords(XFileLexer &lexer) {
}
}
lexer.advanceToNextToken(); // skip closed braces
return true;
}
bool XMesh::parseMaterials(XFileLexer &lexer, int faceCount, const Common::String &filename, Common::Array<MaterialReference> &materialReferences, const Common::Array<int> &indexCountPerFace) {
bool XMesh::parseMaterials(XFileData *xobj, int faceCount, const Common::String &filename, Common::Array<MaterialReference> &materialReferences, const Common::Array<int> &indexCountPerFace) {
XMeshMaterialListObject *materialList = xobj->getXMeshMaterialListObject();
if (!materialList)
return false;
// there can be unused materials inside a .X file
// so this piece of information is probably useless
lexer.readInt(); // material count
// should be the same as faceCount
int faceMaterialCount = lexer.readInt();
int faceMaterialCount = materialList->_numFaceIndexes;
assert(faceMaterialCount == faceCount);
_indexRanges.push_back(0);
int currentMaterialIndex = lexer.readInt();
int currentMaterialIndex = materialList->_faceIndexes[0];
_materialIndices.push_back(currentMaterialIndex);
int currentIndex = indexCountPerFace[0];
for (int i = 1; i < faceMaterialCount; ++i) {
int currentMaterialIndexTmp = lexer.readInt();
int currentMaterialIndexTmp = materialList->_faceIndexes[i];
if (currentMaterialIndex != currentMaterialIndexTmp) {
currentMaterialIndex = currentMaterialIndexTmp;
@ -644,82 +618,65 @@ bool XMesh::parseMaterials(XFileLexer &lexer, int faceCount, const Common::Strin
_indexRanges.push_back(currentIndex);
_numAttrs = _indexRanges.size() - 1;
// Strange Change has an extra semicolon here
if (lexer.tokenIsOfType(SEMICOLON)) {
lexer.advanceToNextToken();
}
uint32 numChildren = 0;
xobj->getChildren(numChildren);
while (!lexer.eof()) {
if (lexer.tokenIsIdentifier("Material")) {
lexer.advanceToNextToken();
Material *mat = new Material(_gameRef);
mat->loadFromX(lexer, filename);
_materials.add(mat);
MaterialReference materialReference;
materialReference._material = mat;
materialReferences.push_back(materialReference);
} else if (lexer.tokenIsIdentifier()) {
while (!lexer.reachedClosedBraces()) {
lexer.advanceToNextToken();
}
lexer.advanceToNextToken(); // skip closed braces
} else if (lexer.reachedClosedBraces()) {
break;
} else if (lexer.tokenIsOfType(OPEN_BRACES)) {
lexer.advanceToNextToken();
Common::String materialReference = lexer.tokenToString();
for (uint i = 0; i < materialReferences.size(); ++i) {
if (materialReferences[i]._name == materialReference) {
_materials.add(materialReferences[i]._material);
break;
for (uint32 i = 0; i < numChildren; i++) {
XFileData xchildData;
XClassType objectType;
bool res = xobj->getChild(i, xchildData);
if (res) {
res = xchildData.getType(objectType);
if (res) {
if (xchildData.isReference()) {
Common::String materialReference;
xchildData.getName(materialReference);
for (uint32 j = 0; j < materialReferences.size(); j++) {
if (materialReferences[j]._name == materialReference) {
_materials.add(materialReferences[j]._material);
break;
}
}
} else if (objectType == kXClassMaterial) {
Material *mat = new Material(_gameRef);
mat->loadFromX(&xchildData, filename);
_materials.add(mat);
MaterialReference materialReference;
materialReference._material = mat;
materialReferences.push_back(materialReference);
}
}
lexer.advanceToNextToken();
lexer.advanceToNextToken();
} else {
warning("XMeshOpenGL::loadFromX unknown token %i encountered while loading materials", lexer.getTypeOfToken());
break;
}
}
lexer.advanceToNextToken(); // skip closed braces
return true;
}
bool XMesh::parseSkinWeights(XFileLexer &lexer) {
bool XMesh::parseSkinWeights(XFileData *xobj) {
XSkinWeightsObject *skinWeights = xobj->getXSkinWeightsObject();
if (!skinWeights)
return false;
skinWeightsList.resize(skinWeightsList.size() + 1);
SkinWeights &currSkinWeights = skinWeightsList.back();
currSkinWeights._boneName = lexer.readString();
currSkinWeights._boneName = skinWeights->_transformNodeName;
int weightCount = lexer.readInt();
int weightCount = skinWeights->_numWeights;
currSkinWeights._vertexIndices.resize(weightCount);
currSkinWeights._vertexWeights.resize(weightCount);
for (int i = 0; i < weightCount; ++i) {
currSkinWeights._vertexIndices[i] = lexer.readInt();
}
if (weightCount == 0 && lexer.tokenIsOfType(SEMICOLON)) {
lexer.advanceToNextToken();
currSkinWeights._vertexIndices[i] = skinWeights->_vertexIndices[i];
}
for (int i = 0; i < weightCount; ++i) {
currSkinWeights._vertexWeights[i] = lexer.readFloat();
}
if (weightCount == 0 && lexer.tokenIsOfType(SEMICOLON)) {
lexer.advanceToNextToken();
currSkinWeights._vertexWeights[i] = skinWeights->_weights[i];
}
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) {
currSkinWeights._offsetMatrix(c, r) = lexer.readFloat();
currSkinWeights._offsetMatrix(c, r) = skinWeights->_matrixOffset[r * 4 + c];
}
}
@ -734,14 +691,15 @@ bool XMesh::parseSkinWeights(XFileLexer &lexer) {
currSkinWeights._offsetMatrix(0, 2) *= -1.0f;
currSkinWeights._offsetMatrix(1, 2) *= -1.0f;
lexer.skipTerminator(); // semicolon of matrix
lexer.advanceToNextToken(); // closed braces of skin weights object
return true;
}
bool XMesh::parseVertexDeclaration(XFileLexer &lexer) {
int vertexElementCount = lexer.readInt();
bool XMesh::parseVertexDeclaration(XFileData *xobj) {
XDeclDataObject *declData = xobj->getXDeclDataObject();
if (!declData)
return false;
int vertexElementCount = declData->_numElements;
// size of a vertex measured in four byte blocks
int vertexSize = 0;
@ -749,11 +707,10 @@ bool XMesh::parseVertexDeclaration(XFileLexer &lexer) {
int textureOffset = -1;
for (int i = 0; i < vertexElementCount; ++i) {
int type = lexer.readInt();
int method = lexer.readInt();
int usage = lexer.readInt();
int usageIndex = lexer.readInt();
lexer.skipTerminator();
int type = declData->_elements[i]._type;
int method = declData->_elements[i]._method;
int usage = declData->_elements[i]._usage;
int usageIndex = declData->_elements[i]._usageIndex;
debug("Vertex Element: Type: %i, Method: %i, Usage: %i, Usage index: %i", type, method, usage, usageIndex);
@ -842,24 +799,14 @@ bool XMesh::parseVertexDeclaration(XFileLexer &lexer) {
}
}
if (lexer.tokenIsOfType(SEMICOLON)) {
lexer.advanceToNextToken();
}
int dataSize = lexer.readInt();
int dataSize = declData->_numData;
Common::Array<uint32> data;
data.reserve(dataSize);
for (int i = 0; i < dataSize; ++i) {
data.push_back(lexer.readUint32());
data.push_back(declData->_data[i]);
}
if (lexer.tokenIsOfType(SEMICOLON)) {
lexer.advanceToNextToken();
}
lexer.advanceToNextToken(); // skip closed braces
assert(dataSize % vertexSize == 0);
assert(dataSize / vertexSize == static_cast<int>(_vertexCount));

View File

@ -43,7 +43,7 @@ class Material;
class XModel;
class ShadowVolume;
class VideoTheoraPlayer;
class XFileLexer;
struct XMeshObject;
struct SkinWeights {
Common::String _boneName;
@ -57,7 +57,7 @@ public:
XMesh(BaseGame *inGame);
virtual ~XMesh();
virtual bool loadFromX(const Common::String &filename, XFileLexer &lexer, Common::Array<MaterialReference> &materialReferences);
virtual bool loadFromXData(const Common::String &filename, XFileData *xobj, Common::Array<MaterialReference> &materialReferences);
bool findBones(FrameNode *rootFrame);
virtual bool update(FrameNode *parentFrame);
virtual bool render(XModel *model) = 0;
@ -84,13 +84,13 @@ protected:
// anything which does not fit into 16 bits would we fine
static const uint32 kNullIndex = 0xFFFFFFFF;
bool parsePositionCoords(XFileLexer &lexer);
bool parseFaces(XFileLexer &lexer, int faceCount, Common::Array<int> &indexCountPerFace);
bool parseTextureCoords(XFileLexer &lexer);
bool parseNormalCoords(XFileLexer &lexer);
bool parseMaterials(XFileLexer &lexer, int faceCount, const Common::String &filename, Common::Array<MaterialReference> &materialReferences, const Common::Array<int> &indexCountPerFace);
bool parseSkinWeights(XFileLexer &lexer);
bool parseVertexDeclaration(XFileLexer &lexer);
bool parsePositionCoords(XMeshObject *mesh);
bool parseFaces(XMeshObject *mesh, int faceCount, Common::Array<int> &indexCountPerFace);
bool parseTextureCoords(XFileData *xobj);
bool parseNormalCoords(XFileData *xobj);
bool parseMaterials(XFileData *xobj, int faceCount, const Common::String &filename, Common::Array<MaterialReference> &materialReferences, const Common::Array<int> &indexCountPerFace);
bool parseSkinWeights(XFileData *xobj);
bool parseVertexDeclaration(XFileData *xobj);
void updateBoundingBox();

View File

@ -29,6 +29,7 @@
#include "engines/wintermute/base/base_file_manager.h"
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/base/base_parser.h"
#include "engines/wintermute/base/gfx/base_renderer.h"
#include "engines/wintermute/base/gfx/opengl/base_render_opengl3d.h"
@ -38,7 +39,8 @@
#include "engines/wintermute/base/gfx/xframe_node.h"
#include "engines/wintermute/base/gfx/xmaterial.h"
#include "engines/wintermute/base/gfx/xmodel.h"
#include "engines/wintermute/base/gfx/xloader.h"
#include "engines/wintermute/base/gfx/xfile.h"
#include "engines/wintermute/base/gfx/xfile_loader.h"
#include "engines/wintermute/dcgf.h"
#include "engines/wintermute/utils/path_util.h"
#include "engines/wintermute/utils/utils.h"
@ -46,106 +48,6 @@
namespace Wintermute {
static const int kCabBlockSize = 0x8000;
static const int kCabInputmax = kCabBlockSize + 12;
static byte *DecompressMsZipData(byte *buffer, uint32 inputSize, uint32 &decompressedSize) {
#ifdef USE_ZLIB
bool error = false;
Common::MemoryReadStream data(buffer, inputSize);
byte *compressedBlock = new byte[kCabInputmax];
byte *decompressedBlock = new byte[kCabBlockSize];
// Read decompressed size of compressed data and minus 16 bytes of xof header
decompressedSize = data.readUint32LE() - 16;
uint32 remainingData = inputSize;
uint32 decompressedPos = 0;
byte *decompressedData = new byte[decompressedSize];
if (!decompressedData)
error = true;
while (!error && remainingData) {
uint16 uncompressedLen = data.readUint16LE();
uint16 compressedLen = data.readUint16LE();
remainingData -= 4;
if (data.err()) {
error = true;
break;
}
if (remainingData == 0) {
break;
}
if (compressedLen > kCabInputmax || uncompressedLen > kCabBlockSize) {
error = true;
break;
}
// Read the compressed block
if (data.read(compressedBlock, compressedLen) != compressedLen) {
error = true;
break;
}
remainingData -= compressedLen;
// Check the CK header
if (compressedBlock[0] != 'C' || compressedBlock[1] != 'K') {
error = true;
break;
}
// Decompress the block. If it isn't the first, provide the previous block as dictonary
byte *dict = decompressedPos ? decompressedBlock : nullptr;
bool decRes = Common::inflateZlibHeaderless(decompressedBlock, uncompressedLen, compressedBlock + 2, compressedLen - 2, dict, kCabBlockSize);
if (!decRes) {
error = true;
break;
}
// Copy the decompressed data
memcpy(decompressedData + decompressedPos, decompressedBlock, uncompressedLen);
decompressedPos += uncompressedLen;
}
if (decompressedSize != decompressedPos)
error = true;
delete[] compressedBlock;
delete[] decompressedBlock;
if (!error)
return decompressedData;
#endif
warning("DecompressMsZipData: Error decompressing data!");
decompressedSize = 0;
return nullptr;
}
static XFileLexer createXFileLexer(byte *&buffer, uint32 fileSize) {
// xof header of an .X file consists of 16 bytes
// bytes 9 to 12 contain a string which can be 'txt ', 'bin ', 'bzip, 'tzip', depending on the format
byte dataFormatBlock[5];
Common::copy(buffer + 8, buffer + 12, dataFormatBlock);
dataFormatBlock[4] = '\0';
bool textMode = (strcmp((char *)dataFormatBlock, "txt ") == 0 || strcmp((char *)dataFormatBlock, "tzip") == 0);
if (strcmp((char *)dataFormatBlock, "bzip") == 0 || strcmp((char *)dataFormatBlock, "tzip") == 0) {
uint32 decompressedSize;
// we skip the 16 bytes xof header of the file
byte *buf = DecompressMsZipData(buffer + 16, fileSize - 16, decompressedSize);
delete[] buffer;
buffer = buf;
return XFileLexer(buffer, decompressedSize, textMode);
} else {
// we skip the 16 bytes xof header of the file
return XFileLexer(buffer + 16, fileSize - 16, textMode);
}
}
IMPLEMENT_PERSISTENT(XModel, false)
//////////////////////////////////////////////////////////////////////////
@ -221,98 +123,197 @@ void XModel::cleanup(bool complete) {
bool XModel::loadFromFile(const Common::String &filename, XModel *parentModel) {
cleanup(false);
XFile *xfile = new XFile(_gameRef);
if (!xfile)
return false;
XFileData xobj;
bool resLoop = false;
_parentModel = parentModel;
uint32 fileSize = 0;
byte *buffer = BaseFileManager::getEngineInstance()->getEngineInstance()->readWholeFile(filename, &fileSize);
XFileLexer lexer = createXFileLexer(buffer, fileSize);
bool res = xfile->openFile(filename);
if (!res) {
delete xfile;
//return false;
error("XModel: Error loading X file: %s", filename.c_str());
}
_rootFrame = new FrameNode(_gameRef);
bool res = _rootFrame->loadFromXAsRoot(filename, lexer, this, _materialReferences);
if (res) {
findBones(false, parentModel);
uint32 numChildren = 0;
xfile->getEnum().getChildren(numChildren);
for (uint i = 0; i < numChildren; i++) {
resLoop = xfile->getEnum().getChild(i, xobj);
if (!resLoop)
break;
bool res = _rootFrame->loadFromXData(filename, this, &xobj, _materialReferences);
if (!res) {
BaseEngine::LOG(0, "Error loading top level object from '%s'", filename.c_str());
break;
}
}
if (!_rootFrame->hasChildren()) {
BaseEngine::LOG(0, "Error getting any top level objects in '%s'", filename.c_str());
res = false;
}
if (res) {
res = findBones(false, parentModel);
}
// setup animation channels
for (int i = 0; i < X_NUM_ANIMATION_CHANNELS; ++i) {
_channels[i] = new AnimationChannel(_gameRef, this);
}
setFilename(filename.c_str());
delete[] buffer;
delete xfile;
return res;
}
//////////////////////////////////////////////////////////////////////////
bool XModel::mergeFromFile(const Common::String &filename) {
if (!_rootFrame) {
BaseEngine::LOG(0, "Error: XModel::mergeFromFile called on an empty model");
return false;
}
uint32 fileSize = 0;
byte *buffer = BaseFileManager::getEngineInstance()->getEngineInstance()->readWholeFile(filename, &fileSize);
XFileLexer lexer = createXFileLexer(buffer, fileSize);
XFile *xfile = new XFile(_gameRef);
if (!xfile)
return false;
lexer.advanceToNextToken();
parseFrameDuringMerge(lexer, filename);
bool res = xfile->openFile(filename);
if (!res) {
delete xfile;
return false;
}
findBones(false, nullptr);
XFileData xobj;
bool resLoop = false;
uint32 numChildren = 0;
xfile->getEnum().getChildren(numChildren);
for (uint i = 0; i < numChildren; i++) {
resLoop = xfile->getEnum().getChild(i, xobj);
if (!resLoop)
break;
res = _rootFrame->mergeFromXData(filename, this, &xobj);
if (!res) {
BaseEngine::LOG(0, "Error loading top level object from '%s'", filename.c_str());
break;
}
}
if (res) {
res = findBones(true);
}
bool found = false;
for (uint i = 0; i < _mergedModels.size(); ++i) {
if (scumm_stricmp(_mergedModels[i], filename.c_str()) == 0) {
found = true;
break;
}
}
if (!found) {
char *path = new char[filename.size() + 1];
strcpy(path, filename.c_str());
_mergedModels.add(path);
}
delete[] buffer;
delete xfile;
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XModel::loadAnimationSet(XFileLexer &lexer, const Common::String &filename) {
bool XModel::loadAnimationSet(const Common::String &filename, XFileData *xobj) {
bool res = true;
AnimationSet *animSet = new AnimationSet(_gameRef, this);
if (animSet->loadFromX(lexer, filename)) {
_animationSets.add(animSet);
} else {
res = loadName(animSet, xobj);
if (!res) {
delete animSet;
res = false;
return res;
}
return res;
// use the filename for unnamed animation sets
if (animSet->_name[0] == '\0') {
animSet->setName(PathUtil::getFileName(filename).c_str());
}
XFileData xchildData;
XClassType objectType;
uint32 numChildren = 0;
xobj->getChildren(numChildren);
for (uint32 i = 0; i < numChildren; i++) {
_gameRef->miniUpdate();
res = xobj->getChild(i, xchildData);
if (res) {
res = xchildData.getType(objectType);
if (!res) {
delete animSet;
BaseEngine::LOG(0, "Error getting object type while loading animation set");
return res;
}
if (objectType == kXClassAnimation) {
res = loadAnimation(filename, &xchildData, animSet);
if (!res) {
delete animSet;
return res;
}
}
}
}
_animationSets.add(animSet);
return true;
}
//////////////////////////////////////////////////////////////////////////
bool XModel::loadAnimation(const Common::String &filename, AnimationSet *parentAnimSet) {
// not sure if we need this here (not completely implemented anyways and also not called)
// are there animation objects in .X outside of an animation set?
bool XModel::loadAnimation(const Common::String &filename, XFileData *xobj, AnimationSet *parentAnimSet) {
// if no parent anim set is specified, create one
bool newAnimSet = false;
if (parentAnimSet == nullptr) {
parentAnimSet = new AnimationSet(_gameRef, this);
parentAnimSet->setName(PathUtil::getFileName(filename).c_str());
newAnimSet = true;
}
// create the new object
Animation *Anim = new Animation(_gameRef);
Animation *anim = new Animation(_gameRef);
parentAnimSet->addAnimation(Anim);
uint32 numChildren = 0;
xobj->getChildren(numChildren);
for (uint32 i = 0; i < numChildren; i++) {
XFileData xchildData;
bool res = xobj->getChild(i, xchildData);
if (res) {
res = anim->load(&xchildData, parentAnimSet);
if (!res) {
delete anim;
if (newAnimSet) {
delete parentAnimSet;
}
return res;
}
}
}
parentAnimSet->addAnimation(anim);
if (newAnimSet) {
_animationSets.add(parentAnimSet);
@ -340,22 +341,23 @@ bool XModel::findBones(bool animOnly, XModel *parentModel) {
return true;
}
void XModel::parseFrameDuringMerge(XFileLexer &lexer, const Common::String &filename) {
while (!lexer.eof()) {
if (lexer.tokenIsIdentifier("Frame")) {
lexer.advanceToNextToken();
parseFrameDuringMerge(lexer, filename);
} else if (lexer.tokenIsIdentifier("AnimationSet")) {
lexer.advanceToNextToken();
loadAnimationSet(lexer, filename);
} else if (lexer.tokenIsOfType(IDENTIFIER)) {
lexer.skipObject();
} else {
lexer.advanceToNextToken(); // we ignore anything else here
}
//////////////////////////////////////////////////////////////////////////
bool XModel::loadName(BaseNamedObject *obj, XFileData *data) {
Common::String name;
if (data->getName(name)) {
obj->_name = new char[name.size() + 1];
Common::strlcpy(obj->_name, name.c_str(), name.size() + 1);
return true;
} else {
return false;
}
}
//////////////////////////////////////////////////////////////////////////
bool XModel::loadName(Common::String &targetStr, XFileData *data) {
return data->getName(targetStr);
}
//////////////////////////////////////////////////////////////////////////
bool XModel::update() {
// reset all bones to default position

View File

@ -46,7 +46,7 @@ class AnimationSet;
class FrameNode;
class Material;
class ShadowVolume;
class XFileLexer;
class XFileData;
struct MaterialReference {
Common::String _name;
@ -152,8 +152,11 @@ public:
bool isTransparentAt(int x, int y);
bool loadAnimationSet(XFileLexer &lexer, const Common::String &filename);
bool loadAnimation(const Common::String &filename, AnimationSet *parentAnimSet);
static bool loadName(BaseNamedObject *obj, XFileData *data);
static bool loadName(Common::String &targetStr, XFileData *data);
bool loadAnimationSet(const Common::String &filename, XFileData *xobj);
bool loadAnimation(const Common::String &filename, XFileData *xobj, AnimationSet *parentAnimSet = nullptr);
Math::Matrix4 _lastWorldMat;
Rect32 _boundingRect;
@ -186,8 +189,6 @@ private:
void cleanup(bool complete = true);
bool findBones(bool animOnly = false, XModel *parentModel = nullptr);
void parseFrameDuringMerge(XFileLexer &lexer, const Common::String &filename);
void updateBoundingRect();
void static inline updateRect(Rect32 *rc, int32 x, int32 y);
Rect32 _drawingViewport;

View File

@ -168,11 +168,12 @@ MODULE_OBJS += \
base/gfx/xanimation.o \
base/gfx/xanimation_channel.o \
base/gfx/xanimation_set.o \
base/gfx/xfile.o \
base/gfx/xfile_loader.o \
base/gfx/xframe_node.o \
base/gfx/xmaterial.o \
base/gfx/xmesh.o \
base/gfx/xmodel.o \
base/gfx/xloader.o \
base/gfx/opengl/base_surface_opengl3d.o \
base/gfx/opengl/base_render_opengl3d.o \
base/gfx/opengl/base_render_opengl3d_shader.o \