diff --git a/engines/wintermute/base/gfx/opengl/meshx_opengl_shader.cpp b/engines/wintermute/base/gfx/opengl/meshx_opengl_shader.cpp index 2ba1e5a52e7..b2c14985c8c 100644 --- a/engines/wintermute/base/gfx/opengl/meshx_opengl_shader.cpp +++ b/engines/wintermute/base/gfx/opengl/meshx_opengl_shader.cpp @@ -49,8 +49,8 @@ XMeshOpenGLShader::~XMeshOpenGLShader() { glDeleteBuffers(1, &_indexBuffer); } -bool XMeshOpenGLShader::loadFromX(const Common::String &filename, XFileLexer &lexer, Common::Array &materialReferences) { - if (XMesh::loadFromX(filename, lexer, materialReferences)) { +bool XMeshOpenGLShader::loadFromXData(const Common::String &filename, XFileData *xobj, Common::Array &materialReferences) { + if (XMesh::loadFromXData(filename, xobj, materialReferences)) { glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); glBufferData(GL_ARRAY_BUFFER, 4 * kVertexComponentCount * _vertexCount, _vertexData, GL_DYNAMIC_DRAW); diff --git a/engines/wintermute/base/gfx/opengl/meshx_opengl_shader.h b/engines/wintermute/base/gfx/opengl/meshx_opengl_shader.h index 9e35545392f..4ef48abff0b 100644 --- a/engines/wintermute/base/gfx/opengl/meshx_opengl_shader.h +++ b/engines/wintermute/base/gfx/opengl/meshx_opengl_shader.h @@ -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 &materialReferences) override; + bool loadFromXData(const Common::String &filename, XFileData *xobj, Common::Array &materialReferences) override; bool render(XModel *model) override; bool renderFlatShadowModel() override; bool update(FrameNode *parentFrame) override; diff --git a/engines/wintermute/base/gfx/xanimation.cpp b/engines/wintermute/base/gfx/xanimation.cpp index 2ad7b76858c..e8ce6c2205d 100644 --- a/engines/wintermute/base/gfx/xanimation.cpp +++ b/engines/wintermute/base/gfx/xanimation.cpp @@ -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 diff --git a/engines/wintermute/base/gfx/xanimation.h b/engines/wintermute/base/gfx/xanimation.h index 659d9f5eee4..650a85b34c8 100644 --- a/engines/wintermute/base/gfx/xanimation.h +++ b/engines/wintermute/base/gfx/xanimation.h @@ -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 _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 diff --git a/engines/wintermute/base/gfx/xanimation_set.cpp b/engines/wintermute/base/gfx/xanimation_set.cpp index 02e24da98eb..b022d2749f2 100644 --- a/engines/wintermute/base/gfx/xanimation_set.cpp +++ b/engines/wintermute/base/gfx/xanimation_set.cpp @@ -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++) { diff --git a/engines/wintermute/base/gfx/xanimation_set.h b/engines/wintermute/base/gfx/xanimation_set.h index 54e5b5db314..57eac886d82 100644 --- a/engines/wintermute/base/gfx/xanimation_set.h +++ b/engines/wintermute/base/gfx/xanimation_set.h @@ -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); diff --git a/engines/wintermute/base/gfx/xfile.cpp b/engines/wintermute/base/gfx/xfile.cpp new file mode 100644 index 00000000000..81e65bc591d --- /dev/null +++ b/engines/wintermute/base/gfx/xfile.cpp @@ -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 . + * + */ + +/* + * 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 diff --git a/engines/wintermute/base/gfx/xfile.h b/engines/wintermute/base/gfx/xfile.h new file mode 100644 index 00000000000..6c349dc797d --- /dev/null +++ b/engines/wintermute/base/gfx/xfile.h @@ -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 . + * + */ + +/* + * 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 diff --git a/engines/wintermute/base/gfx/xfile_loader.cpp b/engines/wintermute/base/gfx/xfile_loader.cpp new file mode 100644 index 00000000000..3ec4537be9d --- /dev/null +++ b/engines/wintermute/base/gfx/xfile_loader.cpp @@ -0,0 +1,1510 @@ +/* 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 . + * + */ + +/* + * Partially based on XFile parser code from Wine sources. + * Copyright 2008 Christian Costa + */ + +#include "common/endian.h" +#include "common/str.h" +#include "common/util.h" +#include "common/zlib.h" + +#include "wintermute/base/gfx/xfile_loader.h" + +namespace Wintermute { + +typedef struct { + const char *className; + const XClassType type; +} XClassEntries; + +// strings must be in lower case +static const XClassEntries gXClasses[] = { + { "skinweights", kXClassSkinWeights }, + { "frame", kXClassFrame }, + { "frametransformmatrix", kXClassFrameTransformMatrix }, + { "animationkey", kXClassAnimationKey }, + { "animationoptions", kXClassAnimationOptions }, + { "mesh", kXClassMesh }, + { "meshnormals", kXClassMeshNormals }, + { "animation", kXClassAnimation }, + { "animationset", kXClassAnimationSet }, + { "meshvertexcolors", kXClassMeshVertexColors }, + { "meshtexturecoords", kXClassMeshTextureCoords }, + { "meshmateriallist", kXClassMeshMaterialList }, + { "vertexduplicationindices", kXClassVertexDuplicationIndices }, + { "material", kXClassMaterial }, + { "texturefilename", kXClassTextureFilename }, + { "xskinmeshheader", kXClassSkinMeshHeader }, + { "animtickspersecond", kXClassAnimTicksPerSecond }, + { "decldata", kXClassDeclData }, + { "fvfdata", kXClassFVFData }, +}; + +// first string expected in lower case +FORCEINLINE static int XFileLoader_strncmp(const char *s1, const char *s2, uint n) { + byte l1, l2; + do { + if (n-- == 0) + return 0; + + l1 = (byte)*s1++; + l2 = (byte)*s2++; + l2 = tolower(l2); + } while (l1 == l2 && l1 != 0); + return l1 - l2; +} + +// first string expected in lower case +FORCEINLINE static int XFileLoader_strcmp(const char *s1, const char *s2) { + byte l1, l2; + do { + l1 = (byte)*s1++; + l2 = (byte)*s2++; + l2 = tolower(l2); + } while (l1 == l2 && l1 != 0); + return l1 - l2; +} + +XFileLoader::XFileLoader() { + init(); +} + +XFileLoader::~XFileLoader() { + deinit(); +} + +void XFileLoader::init() { + deinit(); + + _decompBuffer = nullptr; + _tokenPresent = false; + _isText = false; + _listSeparator = false; + _listTypeFloat = false; + _listNbElements = 0; + + _initialised = true; +} + +void XFileLoader::deinit() { + delete _decompBuffer; + for (uint i = 0; i < _xobjects.size(); i++) { + if (_xobjects[i]->_object && !_xobjects[i]->_targetObject) { + _xobjects[i]->deinit(); + } + _xobjects[i]->_object = nullptr; + delete _xobjects[i]; + } + _xobjects.clear(); +} + +bool XFileLoader::createEnumObject(XFileEnumObject &xobj) { + if (_initialised) { + xobj._file = this; + return true; + } + return false; +} + +bool XFileLoader::isSpace(char c) { + switch (c) { + case ' ': + case '\t': + case 0x0D: + case 0x0A: + case 0x00: + return true; + default: + return false; + } + return false; +} + +bool XFileLoader::isOperator(char c) { + switch (c) { + case ',': + case ';': + case '{': + case '}': + case '[': + case ']': + case '(': + case ')': + case '<': + case '>': + return true; + default: + return false; + } + return false; +} + +bool XFileLoader::isSeparator(char c) { + return isSpace(c) || isOperator(c); +} + +bool XFileLoader::isPrimitiveType(XTokenType token) { + switch (token) { + case XTOKEN_DWORD: + case XTOKEN_FLOAT: + case XTOKEN_WORD: + case XTOKEN_CSTRING: + case XTOKEN_DOUBLE: + case XTOKEN_CHAR: + case XTOKEN_UCHAR: + case XTOKEN_SWORD: + case XTOKEN_SDWORD: + case XTOKEN_VOID: + case XTOKEN_LPSTR: + case XTOKEN_UNICODE: + return true; + default: + return false; + } +} + +bool XFileLoader::readChar(char &c) { + if (_bufferLeft == 0) + return false; + c = *_buffer; + _buffer += 1; + _bufferLeft -= 1; + return true; +} + +bool XFileLoader::readBytes(void *data, uint32 size) { + if (_bufferLeft < size) + return false; + memcpy(data, _buffer, size); + _buffer += size; + _bufferLeft -= size; + return true; +} + +bool XFileLoader::readLE16(uint16 *data) { + if (_bufferLeft < 2) + return false; + *data = READ_LE_UINT16(_buffer); + _buffer += 2; + _bufferLeft -= 2; + return true; +} + +bool XFileLoader::readLE32(uint32 *data) { + if (_bufferLeft < 4) + return false; + *data = READ_LE_UINT32(_buffer); + _buffer += 4; + _bufferLeft -= 4; + return true; +} + +bool XFileLoader::readBE32(uint32 *data) { + if (_bufferLeft < 4) + return false; + *data = READ_BE_UINT32(_buffer); + _buffer += 4; + _bufferLeft -= 4; + return true; +} + +void XFileLoader::rewindBytes(uint32 size) { + _buffer -= size; + _bufferLeft += size; +} + +static struct keywords { + const char *keyword; + const uint len; + const XTokenType token; +} XKeywords[] = { + { "dword", sizeof("dword") - 1, XTOKEN_DWORD }, + { "float", sizeof("float") - 1, XTOKEN_FLOAT }, + { "array", sizeof("array") - 1, XTOKEN_ARRAY }, + { "template", sizeof("template") - 1, XTOKEN_TEMPLATE }, + { "word", sizeof("word") - 1, XTOKEN_WORD }, + { "cstring", sizeof("cstring") - 1, XTOKEN_CSTRING }, + { "char", sizeof("char") - 1, XTOKEN_CHAR }, + { "uchar", sizeof("uchar") - 1, XTOKEN_UCHAR }, + { "sword", sizeof("sword") - 1, XTOKEN_SWORD }, + { "sdword", sizeof("sdword") - 1, XTOKEN_SDWORD }, + { "void", sizeof("void") - 1, XTOKEN_VOID }, + { "string", sizeof("string") - 1, XTOKEN_LPSTR }, +}; + +// string expected in lower case +bool XFileLoader::isKeyword(const char *keyword, uint len) { + if (XFileLoader_strncmp(keyword, (char *)_buffer, len)) { + return false; + } + _buffer += len; + _bufferLeft -= len; + + char tmp; + if (!readChar(tmp)) + return true; + if (isSeparator(tmp)) { + rewindBytes(1); + return true; + } + + rewindBytes(len + 1); + return false; +} + +XTokenType XFileLoader::getKeywordToken() { + for (int i = 0; i < ARRAYSIZE(XKeywords); i++) { + if (isKeyword(XKeywords[i].keyword, XKeywords[i].len)) + return XKeywords[i].token; + } + return XTOKEN_NONE; +} + +bool XFileLoader::isGuid() { + if (_bufferLeft < 38 || *_buffer != '<') + return false; + + char tmp[50]; + uint32 pos = 1; + tmp[0] = '<'; + while (pos < sizeof(tmp) - 2 && *(_buffer + pos) != '>') { + tmp[pos] = *(_buffer + pos); + pos++; + } + tmp[pos++] = '>'; + tmp[pos] = 0; + if (pos != 38) { + warning("XFileLoader: Wrong guid %s (%d)", tmp, pos); + return false; + } + _buffer += pos; + _bufferLeft -= pos; + return true; +} + +bool XFileLoader::isName() { + char tmp[XMAX_STRING_LEN]; + uint32 pos = 0; + char c; + bool error = false; + + while (pos < _bufferLeft && !isSeparator(c = *(_buffer + pos))) { + if (!(((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || ((c >= '0') && (c <= '9')) || (c == '_') || (c == '-'))) + error = true; + if (pos < sizeof(tmp)) + tmp[pos] = c; + pos++; + } + tmp[MIN(pos, (uint32)sizeof(tmp) - 1)] = 0; + + if (error) { + warning("XFileLoader: Wrong name %s", tmp); + return false; + } + + _buffer += pos; + _bufferLeft -= pos; + + Common::strlcpy(_currentToken._textVal, tmp, XMAX_STRING_LEN); + + return true; +} + +bool XFileLoader::isFloat() { + char tmp[XMAX_STRING_LEN]; + uint32 pos = 0; + char c; + bool dot = false; + + while (pos < _bufferLeft && !isSeparator(c = *(_buffer + pos))) { + if (!((!pos && (c == '-')) || ((c >= '0') && (c <= '9')) || (!dot && (c == '.')))) + return false; + if (c == '.') + dot = true; + if (pos < sizeof(tmp)) + tmp[pos] = c; + pos++; + } + tmp[MIN(pos, (uint32)sizeof(tmp) - 1)] = 0; + + _buffer += pos; + _bufferLeft -= pos; + + _currentToken._floatVal = atof(tmp); + + return true; +} + +bool XFileLoader::isInteger() { + char tmp[XMAX_STRING_LEN]; + uint32 pos = 0; + char c; + + while (pos < _bufferLeft && !isSeparator(c = *(_buffer + pos))) { + if (!((c >= '0') && (c <= '9'))) + return false; + if (pos < sizeof(tmp)) + tmp[pos] = c; + pos++; + } + tmp[MIN(pos, (uint32)sizeof(tmp) - 1)] = 0; + + _buffer += pos; + _bufferLeft -= pos; + + _currentToken._integerVal = atoi(tmp); + + return true; +} + +bool XFileLoader::isString() { + char tmp[XMAX_STRING_LEN]; + uint32 pos = 0; + char c; + bool ok = false; + + if (*_buffer != '"') + return false; + + while ((pos + 1) < _bufferLeft) { + c = *(_buffer + pos + 1); + if (c == '"') { + ok = true; + break; + } + if (pos < sizeof(tmp)) + tmp[pos] = c; + pos++; + } + tmp[MIN((int32)pos, (int32)sizeof(tmp) - 1)] = 0; + + if (!ok) { + warning("XFileLoader: Wrong string %s", tmp); + return false; + } + + _buffer += pos + 2; + _bufferLeft -= pos + 2; + + Common::strlcpy(_currentToken._textVal, tmp, XMAX_STRING_LEN); + + return true; +} + +void XFileLoader::parseToken() { + if (_isText) { + char current; + + while (true) { + if (!readChar(current)) { + _currentToken._type = XTOKEN_NONE; + return; + } + if (isSpace(current)) { + continue; + } else if (current == '/' || current == '#') { + if (current == '/') { + if (!readChar(current)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + if (current != '/') { + _currentToken._type = XTOKEN_ERROR; + warning("XFileLoader: Unknown token %c", current); + return; + } + } + while (current != 0xA) { + if (!readChar(current)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + } + continue; + } + break; + } + + switch (current) { + case ';': + _currentToken._type = XTOKEN_SEMICOLON; + return; + case ',': + _currentToken._type = XTOKEN_COMMA; + return; + case '{': + _currentToken._type = XTOKEN_OBRACE; + return; + case '}': + _currentToken._type = XTOKEN_CBRACE; + return; + case '(': + _currentToken._type = XTOKEN_OPAREN; + return; + case ')': + _currentToken._type = XTOKEN_CPAREN; + return; + case '[': + _currentToken._type = XTOKEN_OBRACKET; + return; + case ']': + _currentToken._type = XTOKEN_CBRACKET; + return; + case '>': + _currentToken._type = XTOKEN_CANGLE; + return; + case '.': + _currentToken._type = XTOKEN_DOT; + return; + default: + rewindBytes(1); + _currentToken._type = getKeywordToken(); + if (_currentToken._type != XTOKEN_NONE) { + return; + } else if (isGuid()) { + _currentToken._type = XTOKEN_GUID; + } else if (isInteger()) { + _currentToken._type = XTOKEN_INTEGER; + } else if (isFloat()) { + _currentToken._type = XTOKEN_FLOAT; + } else if (isString()) { + _currentToken._type = XTOKEN_STRING; + } else if (isName()) { + _currentToken._type = XTOKEN_NAME; + } else { + _currentToken._type = XTOKEN_ERROR; + warning("XFileLoader: Unknown token %c", current); + } + } + } else { + if (!_listNbElements) { + uint16 type; + if (!readLE16(&type)) { + _currentToken._type = XTOKEN_NONE; + return; + } + _currentToken._type = (XTokenType)type; + + if (_currentToken._type == XTOKEN_INTEGER_LIST) { + if (!readLE32(&_listNbElements)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + _currentToken._type = XTOKEN_INTEGER; + _listTypeFloat = false; + } else if (_currentToken._type == XTOKEN_FLOAT_LIST) { + if (!readLE32(&_listNbElements)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + _currentToken._type = XTOKEN_FLOAT; + _listTypeFloat = true; + } + } + + if (_listNbElements) { + if (_listSeparator) { + _listNbElements--; + _listSeparator = false; + _currentToken._type = XTOKEN_COMMA; + } else { + uint32 value; + if (!readLE32(&value)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + _listSeparator = true; + if (_listTypeFloat) { + _currentToken._type = XTOKEN_FLOAT; + _currentToken._floatVal = *(float *)&value; + } else { + _currentToken._type = XTOKEN_INTEGER; + _currentToken._integerVal = value; + } + } + return; + } + + switch (_currentToken._type) { + case XTOKEN_NAME: { + uint32 count; + if (!readLE32(&count)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + char name[XMAX_NAME_LEN]; + if (!readBytes(name, count)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + name[count] = 0; + assert(count < XMAX_NAME_LEN); + Common::strlcpy(_currentToken._textVal, name, XMAX_NAME_LEN); + } + break; + case XTOKEN_INTEGER: { + uint32 integer; + if (!readLE32(&integer)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + _currentToken._integerVal = integer; + } + break; + case XTOKEN_GUID: { + byte classId[16]; + if (!readBytes(&classId, 16)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + } + break; + case XTOKEN_STRING: { + uint32 count; + if (!readLE32(&count)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + char string[XMAX_NAME_LEN]; + if (!readBytes(string, count)) { + _currentToken._type = XTOKEN_ERROR; + return; + } + string[count] = 0; + assert(count < XMAX_NAME_LEN); + Common::strlcpy(_currentToken._textVal, string, XMAX_NAME_LEN); + } + break; + case XTOKEN_COMMA: + break; + case XTOKEN_SEMICOLON: + break; + case XTOKEN_OBRACE: + break; + case XTOKEN_CBRACE: + break; + case XTOKEN_DWORD: + break; + case XTOKEN_FLOAT: + break; + case XTOKEN_OBRACKET: + break; + case XTOKEN_CBRACKET: + break; + case XTOKEN_ARRAY: + break; + case XTOKEN_WORD: + break; + case XTOKEN_CSTRING: + break; + case XTOKEN_OPAREN: + break; + case XTOKEN_CPAREN: + break; + case XTOKEN_OANGLE: + break; + case XTOKEN_CANGLE: + break; + case XTOKEN_DOT: + break; + case XTOKEN_TEMPLATE: + break; + case XTOKEN_DOUBLE: + break; + case XTOKEN_CHAR: + break; + case XTOKEN_UCHAR: + break; + case XTOKEN_SWORD: + break; + case XTOKEN_SDWORD: + break; + case XTOKEN_VOID: + break; + case XTOKEN_LPSTR: + break; + case XTOKEN_UNICODE: + break; + default: + _currentToken._type = XTOKEN_ERROR; + warning("XFileLoader::nextToken: Unknown token encountered"); + return; + } + } +} + +XTokenType XFileLoader::getToken() { + if (_tokenPresent) { + _tokenPresent = false; + return _currentToken._type; + } + + parseToken(); + + return _currentToken._type; +} + +XTokenType XFileLoader::checkToken() { + if (_tokenPresent) + return _currentToken._type; + + parseToken(); + _tokenPresent = true; + + return _currentToken._type; +} + +bool XFileLoader::skipSemicolonComma() { + if (checkToken() != XTOKEN_COMMA && checkToken() != XTOKEN_SEMICOLON) { + return false; + } + while (checkToken() == XTOKEN_SEMICOLON) + getToken(); + if (checkToken() == XTOKEN_COMMA) + getToken(); + return true; +} + +bool XFileLoader::getInteger(uint32 &value) { + if (getToken() != XTOKEN_INTEGER) { + return false; + } + value = _currentToken._integerVal; + return skipSemicolonComma(); +} + +bool XFileLoader::getFloat(float &value) { + if (getToken() != XTOKEN_FLOAT) { + return false; + } + value = _currentToken._floatVal; + return skipSemicolonComma(); +} + +bool XFileLoader::getString(char *str, uint maxLen) { + if (getToken() != XTOKEN_STRING) { + return false; + } + uint len = strlen(_currentToken._textVal); + assert(maxLen > len); + Common::strlcpy(str, _currentToken._textVal, maxLen); + return skipSemicolonComma(); +} + +bool XFileLoader::decompressMsZipData() { +#ifdef USE_ZLIB + bool error = false; + + byte *compressedBlock = new byte[kCabInputmax]; + byte *decompressedBlock = new byte[kCabBlockSize]; + + uint32 decompressedSize = 0; + if (!readLE32(&decompressedSize)) { + error = true; + } else { + decompressedSize -= 16; + } + + uint32 decompressedPos = 0; + byte *decompressedData = new byte[decompressedSize]; + if (!decompressedData) + error = true; + + while (!error && _bufferLeft) { + uint16 uncompressedLen, compressedLen; + if (!readLE16(&uncompressedLen) || !readLE16(&compressedLen)) { + error = true; + break; + } + + if (_bufferLeft == 0) { + break; + } + + if (compressedLen > kCabInputmax || uncompressedLen > kCabBlockSize) { + error = true; + break; + } + + if (!readBytes(compressedBlock, compressedLen)) { + error = true; + break; + } + + if (compressedBlock[0] != 'C' || compressedBlock[1] != 'K') { + error = true; + break; + } + + const byte *dict = decompressedPos ? decompressedBlock : nullptr; + bool decRes = Common::inflateZlibHeaderless(decompressedBlock, uncompressedLen, compressedBlock + 2, compressedLen - 2, dict, kCabBlockSize); + if (!decRes) { + error = true; + break; + } + + memcpy(decompressedData + decompressedPos, decompressedBlock, uncompressedLen); + decompressedPos += uncompressedLen; + } + if (decompressedSize != decompressedPos) + error = true; + + delete[] compressedBlock; + delete[] decompressedBlock; + + if (!error) { + _decompBuffer = _buffer = decompressedData; + _bufferLeft = decompressedSize; + return true; + } + + delete[] decompressedData; +#endif + + warning("XFileLoader: decompressMsZipData: Error decompressing data!"); + return false; +} + +bool XFileLoader::parseHeader() { + uint32 header[4]; + + for (int i = 0; i < 4; i++) { + if (!readBE32(&header[i])) { + warning("XFileLoader: bad file"); + return false; + } + } + + if (header[0] != MKTAG('x','o','f',' ')) { + warning("XFileLoader: bad file"); + return false; + } + + if (header[1] != MKTAG('0','3','0','2') && + header[1] != MKTAG('0','3','0','3')) { + warning("XFileLoader: bad version"); + return false; + } + + if (header[2] != MKTAG('b','i','n',' ') && + header[2] != MKTAG('t','x','t',' ') && + header[2] != MKTAG('b','z','i','p') && + header[2] != MKTAG('t','z','i','p')) { + warning("XFileLoader: file type unknown"); + return false; + } + + if (header[3] != MKTAG('0','0','3','2') && + header[3] != MKTAG('0','0','6','4')) { + warning("XFileLoader: bad float size"); + return false; + } + + if (header[3] == MKTAG('0','0','6','4')) { + warning("XFileLoader: double float size is not supported"); + return false; + } + + _isText = header[2] == MKTAG('t','x','t',' ') || + header[2] == MKTAG('t','z','i','p'); + + if (header[2] == MKTAG('b','z','i','p') || + header[2] == MKTAG('t','z','i','p')) { + if (!decompressMsZipData()) { + return false; + } + } + + return true; +} + +bool XFileLoader::parseTemplateOptionInfo() { + if (checkToken() == XTOKEN_DOT) { + getToken(); + if (getToken() != XTOKEN_DOT) + return false; + if (getToken() != XTOKEN_DOT) + return false; + } else { + while (1) { + if (getToken() != XTOKEN_NAME) + return false; + if (checkToken() == XTOKEN_GUID) + getToken(); + if (checkToken() != XTOKEN_COMMA) + break; + getToken(); + } + } + return true; +} + +bool XFileLoader::parseTemplateMembersList() { + while (true) { + bool array = false; + if (checkToken() == XTOKEN_ARRAY) { + getToken(); + array = true; + } + + if (checkToken() == XTOKEN_NAME) { + getToken(); + } else if (isPrimitiveType(checkToken())) { + getToken(); + } else + break; + + if (getToken() != XTOKEN_NAME) + return false; + + if (array) { + while (checkToken() == XTOKEN_OBRACKET) { + getToken(); + if (checkToken() == XTOKEN_INTEGER) { + getToken(); + } else { + if (getToken() != XTOKEN_NAME) + return false; + } + if (getToken() != XTOKEN_CBRACKET) + return false; + } + } + if (getToken() != XTOKEN_SEMICOLON) + return false; + } + return true; +} + +bool XFileLoader::parseTemplateParts() { + if (!parseTemplateMembersList()) + return false; + if (checkToken() == XTOKEN_OBRACKET) { + getToken(); + if (!parseTemplateOptionInfo()) + return false; + if (getToken() != XTOKEN_CBRACKET) + return false; + } + return true; +} + +bool XFileLoader::parseTemplate() { + if (getToken() != XTOKEN_TEMPLATE) + return false; + if (getToken() != XTOKEN_NAME) + return false; + if (getToken() != XTOKEN_OBRACE) + return false; + if (getToken() != XTOKEN_GUID) + return false; + if (!parseTemplateParts()) + return false; + if (getToken() != XTOKEN_CBRACE) + return false; + return true; +} + +bool XFileLoader::parseObjectParts(XObject *object) { + switch (object->_classType) { + case kXClassAnimTicksPerSecond: { + auto objClass = new XAnimTicksPerSecondObject; + if (!getInteger(objClass->_animTicksPerSecond)) { + delete objClass; + return false; + } + object->_object = objClass; + } + break; + + case kXClassFrame: { + auto objClass = new XFrameObject; + if (!parseChildObjects(object)) { + delete objClass; + return false; + } + object->_object = objClass; + } + break; + + case kXClassFrameTransformMatrix: { + auto objClass = new XFrameTransformMatrixObject; + for (int m = 0; m < 16; m++) { + if (!getFloat(objClass->_frameMatrix[m])) { + delete objClass; + return false; + } + } + object->_object = objClass; + } + break; + + case kXClassMesh: { + auto objClass = new XMeshObject; + if (!getInteger(objClass->_numVertices)) { + delete objClass; + return false; + } + + objClass->_vertices = new XVector[objClass->_numVertices]; + for (uint n = 0; n < objClass->_numVertices; n++) { + if (!getFloat(objClass->_vertices[n]._x) || + !getFloat(objClass->_vertices[n]._y) || + !getFloat(objClass->_vertices[n]._z)) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + if (!getInteger(objClass->_numFaces)) { + delete objClass; + return false; + } + objClass->_faces = new XMeshFace[objClass->_numFaces]; + for (uint n = 0; n < objClass->_numFaces; n++) { + if (!getInteger(objClass->_faces[n]._numFaceVertexIndices)) { + delete objClass; + return false; + } + assert(objClass->_faces[n]._numFaceVertexIndices == 3 || + objClass->_faces[n]._numFaceVertexIndices == 4); + + for (uint f = 0; f < objClass->_faces[n]._numFaceVertexIndices; f++) { + if (!getInteger(objClass->_faces[n]._faceVertexIndices[f])) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + } + skipSemicolonComma(); + + if (!parseChildObjects(object)) { + delete objClass; + return false; + } + + object->_object = objClass; + break; + } + + case kXClassMeshNormals: { + auto objClass = new XMeshNormalsObject; + if (!getInteger(objClass->_numNormals)) { + delete objClass; + return false; + } + + objClass->_normals = new XVector[objClass->_numNormals]; + for (uint n = 0; n < objClass->_numNormals; n++) { + if (!getFloat(objClass->_normals[n]._x) || + !getFloat(objClass->_normals[n]._y) || + !getFloat(objClass->_normals[n]._z)) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + if (!getInteger(objClass->_numFaceNormals)) { + delete objClass; + return false; + } + objClass->_faceNormals = new XMeshFace[objClass->_numFaceNormals]; + + for (uint n = 0; n < objClass->_numFaceNormals; n++) { + if (!getInteger(objClass->_faceNormals[n]._numFaceVertexIndices)) { + delete objClass; + return false; + } + assert(objClass->_faceNormals[n]._numFaceVertexIndices == 3 || + objClass->_faceNormals[n]._numFaceVertexIndices == 4); + + for (uint f = 0; f < objClass->_faceNormals[n]._numFaceVertexIndices; f++) { + if (!getInteger(objClass->_faceNormals[n]._faceVertexIndices[f])) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + } + skipSemicolonComma(); + + object->_object = objClass; + break; + } + + case kXClassMeshVertexColors: { + auto objClass = new XMeshVertexColorsObject; + if (!getInteger(objClass->_numVertexColors)) { + delete objClass; + return false; + } + + objClass->_vertexColors = new XIndexedColor[objClass->_numVertexColors]; + for (uint n = 0; n < objClass->_numVertexColors; n++) { + if (!getInteger(objClass->_vertexColors[n]._index) || + !getFloat(objClass->_vertexColors[n]._indexColorR) || + !getFloat(objClass->_vertexColors[n]._indexColorG) || + !getFloat(objClass->_vertexColors[n]._indexColorB) || + !getFloat(objClass->_vertexColors[n]._indexColorA)) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + object->_object = objClass; + break; + } + + case kXClassMeshTextureCoords: { + auto objClass = new XMeshTextureCoordsObject; + if (!getInteger(objClass->_numTextureCoords)) { + delete objClass; + return false; + } + + objClass->_textureCoords = new XCoords2d[objClass->_numTextureCoords]; + for (uint n = 0; n < objClass->_numTextureCoords; n++) { + if (!getFloat(objClass->_textureCoords[n]._u)) { + delete objClass; + return false; + } + if (!getFloat(objClass->_textureCoords[n]._v)) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + object->_object = objClass; + break; + } + + case kXClassVertexDuplicationIndices: { + auto objClass = new XVertexDuplicationIndicesObject; + if (!getInteger(objClass->_numIndices) || + !getInteger(objClass->_nOriginalVertices)) { + delete objClass; + return false; + } + + objClass->_indices = new uint32[objClass->_numIndices]; + for (uint n = 0; n < objClass->_numIndices; n++) { + if (!getInteger(objClass->_indices[n])) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + object->_object = objClass; + break; + } + + case kXClassMeshMaterialList: { + auto objClass = new XMeshMaterialListObject; + if (!getInteger(objClass->_nMaterials) || + !getInteger(objClass->_numFaceIndexes)) { + delete objClass; + return false; + } + + objClass->_faceIndexes = new uint32[objClass->_numFaceIndexes]; + for (uint n = 0; n < objClass->_numFaceIndexes; n++) { + if (!getInteger(objClass->_faceIndexes[n])) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + if (!parseChildObjects(object)) { + delete objClass; + return false; + } + + object->_object = objClass; + break; + } + + case kXClassMaterial: { + auto objClass = new XMaterialObject; + if (!getFloat(objClass->_colorR) || + !getFloat(objClass->_colorG) || + !getFloat(objClass->_colorB) || + !getFloat(objClass->_colorA) || + !getFloat(objClass->_power) || + !getFloat(objClass->_specularR) || + !getFloat(objClass->_specularG) || + !getFloat(objClass->_specularB) || + !getFloat(objClass->_emissiveR) || + !getFloat(objClass->_emissiveG) || + !getFloat(objClass->_emissiveB)) { + delete objClass; + return false; + } + + if (!parseChildObjects(object)) { + delete objClass; + return false; + } + object->_object = objClass; + break; + } + + case kXClassTextureFilename: { + auto objClass = new XTextureFilenameObject; + if (!getString((char *)objClass->_filename, XMAX_NAME_LEN)) { + delete objClass; + return false; + } + object->_object = objClass; + break; + } + + case kXClassSkinMeshHeader: { + auto objClass = new XSkinMeshHeaderObject; + if (!getInteger(objClass->_nMaxSkinWeightsPerVertex) || + !getInteger(objClass->_nMaxSkinWeightsPerFace) || + !getInteger(objClass->_nBones)) { + delete objClass; + return false; + } + object->_object = objClass; + break; + } + + case kXClassSkinWeights: { + auto objClass = new XSkinWeightsObject; + if (!getString(objClass->_transformNodeName, XMAX_NAME_LEN) || + !getInteger(objClass->_numWeights)) { + delete objClass; + return false; + } + + objClass->_vertexIndices = new uint32[objClass->_numWeights]; + for (uint n = 0; n < objClass->_numWeights; n++) { + if (!getInteger(objClass->_vertexIndices[n])) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + objClass->_weights = new float[objClass->_numWeights]; + for (uint n = 0; n < objClass->_numWeights; n++) { + if (!getFloat(objClass->_weights[n])) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + for (int m = 0; m < 16; m++) { + if (!getFloat(objClass->_matrixOffset[m])) { + delete objClass; + return false; + } + } + + object->_object = objClass; + break; + } + + case kXClassAnimationSet: { + auto objClass = new XAnimationSetObject; + if (!parseChildObjects(object)) { + delete objClass; + return false; + } + object->_object = objClass; + } + break; + + case kXClassAnimation: { + auto objClass = new XAnimationObject; + if (!parseChildObjects(object)) { + delete objClass; + return false; + } + object->_object = objClass; + } + break; + + case kXClassAnimationKey: { + auto objClass = new XAnimationKeyObject; + if (!getInteger(objClass->_keyType) || + !getInteger(objClass->_numKeys)) { + delete objClass; + return false; + } + if (objClass->_keyType > 4) { + warning("XFileLoader: AnimationKey key type invalid"); + delete objClass; + return false; + } + + objClass->_keys = new XTimedFloatKeys[objClass->_numKeys]; + for (uint n = 0; n < objClass->_numKeys; n++) { + if (checkToken() == XTOKEN_INTEGER) { + uint32 timeVal; + if (!getInteger(timeVal)) { + delete objClass; + return false; + } + objClass->_keys[n]._time = timeVal; + } else if (checkToken() == XTOKEN_FLOAT) { + if (!getFloat(objClass->_keys[n]._time)) { + delete objClass; + return false; + } + } + + if (!getInteger(objClass->_keys[n]._numTfkeys)) { + delete objClass; + return false; + } + for (uint f = 0; f < objClass->_keys[n]._numTfkeys; f++) { + if (!getFloat(objClass->_keys[n]._tfkeys[f])) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + } + skipSemicolonComma(); + + object->_object = objClass; + break; + } + + case kXClassAnimationOptions: { + auto objClass = new XAnimationOptionsObject; + if (!getInteger(objClass->_openclosed) || + !getInteger(objClass->_positionquality)) { + delete objClass; + return false; + } + + object->_object = objClass; + break; + } + + case kXClassDeclData: { + auto objClass = new XDeclDataObject; + if (!getInteger(objClass->_numElements)) { + delete objClass; + return false; + } + + objClass->_elements = new XVertexElement[objClass->_numElements]; + for (uint n = 0; n < objClass->_numElements; n++) { + if (!getInteger(objClass->_elements[n]._type) || + !getInteger(objClass->_elements[n]._method) || + !getInteger(objClass->_elements[n]._usage) || + !getInteger(objClass->_elements[n]._usageIndex)) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + if (!getInteger(objClass->_numData)) { + delete objClass; + return false; + } + objClass->_data = new uint32[objClass->_numData]; + for (uint n = 0; n < objClass->_numData; n++) { + if (!getInteger(objClass->_data[n])) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + object->_object = objClass; + break; + } + + case kXClassFVFData: { + auto objClass = new XFVFDataObject; + if (!getInteger(objClass->_dwFVF) || + !getInteger(objClass->_numData)) { + delete objClass; + return false; + } + + objClass->_data = new uint32[objClass->_numData]; + for (uint n = 0; n < objClass->_numData; n++) { + if (!getInteger(objClass->_data[n])) { + delete objClass; + return false; + } + } + skipSemicolonComma(); + + object->_object = objClass; + break; + } + + default: + error("XFileLoader: Not implemented class %d", object->_classType); + } + + return true; +} + +XObject *XFileLoader::resolveChildObject(XObject *object, const Common::String &referenceName) { + if (object->_name == referenceName) { + return object; + } + for (uint i = 0; i < object->_children.size(); i++) { + XObject *targetObject = resolveChildObject(object->_children[i], referenceName); + if (targetObject) + return targetObject; + } + return nullptr; +} + +bool XFileLoader::resolveObject(XObject *referenceObject, const Common::String &referenceName) { + bool found = false; + for (uint i = 0; i < _xobjects.size(); i++) { + XObject *targetObject = resolveChildObject(_xobjects[i], referenceName); + if (targetObject) { + referenceObject->_targetObject = targetObject; + found = true; + break; + } + } + return found; +} + +bool XFileLoader::parseChildObjects(XObject *object) { + if (checkToken() != XTOKEN_NAME && checkToken() != XTOKEN_OBRACE) { + return true; + } + + while (true) { + if (checkToken() == XTOKEN_OBRACE) { + getToken(); + if (getToken() != XTOKEN_NAME) + return false; + XObject *child = new XObject(); + object->_children.push(child); + if (!resolveObject(child, _currentToken._textVal)) { + warning("XFileLoader: Referenced object doesn't exists \"%s\"", _currentToken._textVal); + } + if (getToken() != XTOKEN_CBRACE) + return false; + } else if (checkToken() == XTOKEN_NAME) { + XObject *child = new XObject(); + object->_children.push(child); + if (!parseObject(child)) + return false; + } else if (checkToken() != XTOKEN_CBRACE) { + return false; + } else + break; + } + + return true; +} + +bool XFileLoader::parseObject(XObject *object) { + if (getToken() != XTOKEN_NAME) + return false; + + for (uint i = 0; i < ARRAYSIZE(gXClasses); i++) { + if (!XFileLoader_strcmp(gXClasses[i].className, _currentToken._textVal)) { + object->_classType = gXClasses[i].type; + break; + } + } + if (object->_classType == kXClassUnknown) { + error("XFileLoader: Unknown class \"%s\"", _currentToken._textVal); + return false; + } + + if (checkToken() == XTOKEN_NAME) { + getToken(); + object->_name = _currentToken._textVal; + } + + if (getToken() != XTOKEN_OBRACE) + return false; + + if (checkToken() == XTOKEN_GUID) { + getToken(); + } + + if (!parseObjectParts(object)) + return false; + if (getToken() != XTOKEN_CBRACE) + return false; + + checkToken(); + + return true; +} + +bool XFileLoader::load(byte *buffer, uint32 bufferSize) { + if (!_initialised) + return false; + + _buffer = buffer; + _bufferLeft = bufferSize; + + if (!parseHeader()) + return false; + + while (_bufferLeft) { + XTokenType token = checkToken(); + switch (token) { + case XTOKEN_TEMPLATE: + if (!parseTemplate()) { + warning("XFileLoader: Template is not correct"); + return false; + } + break; + case XTOKEN_NAME: { + XObject *xobject = new XObject(); + _xobjects.push(xobject); + if (!parseObject(xobject)) { + warning("XFileLoader: Object is not correct"); + return false; + } + } + break; + default: + warning("XFileLoader: Unexpected token"); + return false; + } + } + return true; +} + +} // namespace Wintermute diff --git a/engines/wintermute/base/gfx/xfile_loader.h b/engines/wintermute/base/gfx/xfile_loader.h new file mode 100644 index 00000000000..aa19a9fae46 --- /dev/null +++ b/engines/wintermute/base/gfx/xfile_loader.h @@ -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 . + * + */ + +/* + * 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 _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 _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(_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 diff --git a/engines/wintermute/base/gfx/xframe_node.cpp b/engines/wintermute/base/gfx/xframe_node.cpp index b6aeff3dcba..0f29e52ac36 100644 --- a/engines/wintermute/base/gfx/xframe_node.cpp +++ b/engines/wintermute/base/gfx/xframe_node.cpp @@ -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 &materialReferences) { +bool FrameNode::loadFromXData(const Common::String &filename, XModel *model, XFileData *xobj, Common::Array &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 &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; } diff --git a/engines/wintermute/base/gfx/xframe_node.h b/engines/wintermute/base/gfx/xframe_node.h index 933f62bfe17..0932c093fc9 100644 --- a/engines/wintermute/base/gfx/xframe_node.h +++ b/engines/wintermute/base/gfx/xframe_node.h @@ -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 &materialReferences); - bool loadFromXAsRoot(const Common::String &filename, XFileLexer &lexer, XModel *model, Common::Array &materialReferences); + bool loadFromXData(const Common::String &filename, XModel *model, XFileData *xobj, Common::Array &materialReferences); + bool mergeFromXData(const Common::String &filename, XModel *model, XFileData *xobj); bool findBones(FrameNode *rootFrame); FrameNode *findFrame(const char *frameName); Math::Matrix4 *getCombinedMatrix(); diff --git a/engines/wintermute/base/gfx/xloader.cpp b/engines/wintermute/base/gfx/xloader.cpp deleted file mode 100644 index 312dbdce549..00000000000 --- a/engines/wintermute/base/gfx/xloader.cpp +++ /dev/null @@ -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 . - * - */ - -#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 diff --git a/engines/wintermute/base/gfx/xloader.h b/engines/wintermute/base/gfx/xloader.h deleted file mode 100644 index 65acc0a247d..00000000000 --- a/engines/wintermute/base/gfx/xloader.h +++ /dev/null @@ -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 . - * - */ - -#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 diff --git a/engines/wintermute/base/gfx/xmaterial.cpp b/engines/wintermute/base/gfx/xmaterial.cpp index f7e3ef25e48..144afccf52d 100644 --- a/engines/wintermute/base/gfx/xmaterial.cpp +++ b/engines/wintermute/base/gfx/xmaterial.cpp @@ -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; } diff --git a/engines/wintermute/base/gfx/xmaterial.h b/engines/wintermute/base/gfx/xmaterial.h index 566cff160e3..540399291ec 100644 --- a/engines/wintermute/base/gfx/xmaterial.h +++ b/engines/wintermute/base/gfx/xmaterial.h @@ -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(); diff --git a/engines/wintermute/base/gfx/xmesh.cpp b/engines/wintermute/base/gfx/xmesh.cpp index 4a5574e0a60..d0e7387c09c 100644 --- a/engines/wintermute/base/gfx/xmesh.cpp +++ b/engines/wintermute/base/gfx/xmesh.cpp @@ -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 &materialReferences) { - lexer.advanceToNextToken(); // skip the name - lexer.advanceOnOpenBraces(); - _vertexCount = lexer.readInt(); +bool XMesh::loadFromXData(const Common::String &filename, XFileData *xobj, Common::Array &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 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& indexCountPerFace) { +bool XMesh::parseFaces(XMeshObject *mesh, int faceCount, Common::Array &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& 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& 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 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 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 &materialReferences, const Common::Array &indexCountPerFace) { +bool XMesh::parseMaterials(XFileData *xobj, int faceCount, const Common::String &filename, Common::Array &materialReferences, const Common::Array &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 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(_vertexCount)); diff --git a/engines/wintermute/base/gfx/xmesh.h b/engines/wintermute/base/gfx/xmesh.h index 3de79ed2cac..17542d61142 100644 --- a/engines/wintermute/base/gfx/xmesh.h +++ b/engines/wintermute/base/gfx/xmesh.h @@ -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 &materialReferences); + virtual bool loadFromXData(const Common::String &filename, XFileData *xobj, Common::Array &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 &indexCountPerFace); - bool parseTextureCoords(XFileLexer &lexer); - bool parseNormalCoords(XFileLexer &lexer); - bool parseMaterials(XFileLexer &lexer, int faceCount, const Common::String &filename, Common::Array &materialReferences, const Common::Array &indexCountPerFace); - bool parseSkinWeights(XFileLexer &lexer); - bool parseVertexDeclaration(XFileLexer &lexer); + bool parsePositionCoords(XMeshObject *mesh); + bool parseFaces(XMeshObject *mesh, int faceCount, Common::Array &indexCountPerFace); + bool parseTextureCoords(XFileData *xobj); + bool parseNormalCoords(XFileData *xobj); + bool parseMaterials(XFileData *xobj, int faceCount, const Common::String &filename, Common::Array &materialReferences, const Common::Array &indexCountPerFace); + bool parseSkinWeights(XFileData *xobj); + bool parseVertexDeclaration(XFileData *xobj); void updateBoundingBox(); diff --git a/engines/wintermute/base/gfx/xmodel.cpp b/engines/wintermute/base/gfx/xmodel.cpp index 407d7068835..e3f444a79ba 100644 --- a/engines/wintermute/base/gfx/xmodel.cpp +++ b/engines/wintermute/base/gfx/xmodel.cpp @@ -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 diff --git a/engines/wintermute/base/gfx/xmodel.h b/engines/wintermute/base/gfx/xmodel.h index a4798ac600c..71186f654b7 100644 --- a/engines/wintermute/base/gfx/xmodel.h +++ b/engines/wintermute/base/gfx/xmodel.h @@ -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; diff --git a/engines/wintermute/module.mk b/engines/wintermute/module.mk index 8a930d19866..e5020797de0 100644 --- a/engines/wintermute/module.mk +++ b/engines/wintermute/module.mk @@ -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 \