1125 lines
29 KiB
C++
1125 lines
29 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This file is based on WME.
|
|
* http://dead-code.org/redir.php?target=wme
|
|
* Copyright (c) 2003-2013 Jan Nedoma and contributors
|
|
*/
|
|
|
|
#include "common/compression/deflate.h"
|
|
|
|
#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"
|
|
#include "engines/wintermute/base/gfx/xactive_animation.h"
|
|
#include "engines/wintermute/base/gfx/xanimation_channel.h"
|
|
#include "engines/wintermute/base/gfx/xanimation_set.h"
|
|
#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/3deffect.h"
|
|
#include "engines/wintermute/base/gfx/xfile.h"
|
|
#include "engines/wintermute/base/gfx/xfile_loader.h"
|
|
#include "engines/wintermute/dcgf.h"
|
|
#include "engines/wintermute/platform_osystem.h"
|
|
#include "engines/wintermute/utils/path_util.h"
|
|
#include "engines/wintermute/utils/utils.h"
|
|
#include "engines/wintermute/wintermute.h"
|
|
#include "engines/wintermute/dcgf.h"
|
|
|
|
namespace Wintermute {
|
|
|
|
IMPLEMENT_PERSISTENT(XModel, false)
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
XModel::XModel(BaseGame *inGame, BaseObject *owner) : BaseObject(inGame) {
|
|
_owner = owner;
|
|
|
|
_rootFrame = nullptr;
|
|
|
|
memset(&_drawingViewport, 0, sizeof(DXViewport));
|
|
DXMatrixIdentity(&_lastWorldMat);
|
|
DXMatrixIdentity(&_lastViewMat);
|
|
DXMatrixIdentity(&_lastProjMat);
|
|
_lastOffsetX = _lastOffsetY = 0;
|
|
|
|
_BBoxStart = _BBoxEnd = DXVector3(0.0f, 0.0f, 0.0f);
|
|
BasePlatform::setRectEmpty(&_boundingRect);
|
|
|
|
for (int i = 0; i < X_NUM_ANIMATION_CHANNELS; i++) {
|
|
_channels[i] = nullptr;
|
|
}
|
|
|
|
_ticksPerSecond = kDefaultTicksPerSecond;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
XModel::~XModel() {
|
|
cleanup();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void XModel::cleanup(bool complete) {
|
|
// empty animation channels
|
|
for (int i = 0; i < X_NUM_ANIMATION_CHANNELS; i++) {
|
|
SAFE_DELETE(_channels[i]);
|
|
}
|
|
|
|
// remove animation sets
|
|
for (int32 i = 0; i < _animationSets.getSize(); i++) {
|
|
delete _animationSets[i];
|
|
}
|
|
_animationSets.removeAll();
|
|
|
|
if (complete) {
|
|
for (int32 i = 0; i < _mergedModels.getSize(); ++i) {
|
|
SAFE_DELETE_ARRAY(_mergedModels[i]);
|
|
}
|
|
_mergedModels.removeAll();
|
|
}
|
|
|
|
for (int32 i = 0; i < _matSprites.getSize(); i++) {
|
|
SAFE_DELETE(_matSprites[i]);
|
|
}
|
|
_matSprites.removeAll();
|
|
|
|
// remove root frame
|
|
SAFE_DELETE(_rootFrame);
|
|
|
|
_parentModel = nullptr;
|
|
|
|
_ticksPerSecond = kDefaultTicksPerSecond;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::loadFromFile(const char *filename, XModel *parentModel) {
|
|
cleanup(false);
|
|
|
|
XFile *xfile = new XFile(_game);
|
|
if (!xfile)
|
|
return false;
|
|
|
|
XFileData xobj;
|
|
bool resLoop = false;
|
|
|
|
_parentModel = parentModel;
|
|
|
|
bool res = xfile->openFile(filename);
|
|
if (!res) {
|
|
delete xfile;
|
|
BaseEngine::LOG(0, "Error loading X file: %s", filename);
|
|
return false;
|
|
}
|
|
|
|
// get top level objects
|
|
_rootFrame = new FrameNode(_game);
|
|
|
|
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->loadFromXData(filename, this, &xobj);
|
|
if (!res) {
|
|
BaseEngine::LOG(0, "Error loading top level object from '%s'", filename);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!_rootFrame->hasChildren()) {
|
|
BaseEngine::LOG(0, "Error getting any top level objects in '%s'", filename);
|
|
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(_game, this);
|
|
}
|
|
|
|
if (_filename != filename)
|
|
BaseUtils::setString(&_filename, filename);
|
|
|
|
delete xfile;
|
|
|
|
return res;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::mergeFromFile(const char *filename) {
|
|
if (!_rootFrame) {
|
|
BaseEngine::LOG(0, "Error: XModel::mergeFromFile called on an empty model");
|
|
return false;
|
|
}
|
|
|
|
XFile *xfile = new XFile(_game);
|
|
if (!xfile)
|
|
return false;
|
|
|
|
bool res = xfile->openFile(filename);
|
|
if (!res) {
|
|
delete xfile;
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (res) {
|
|
res = findBones(true);
|
|
}
|
|
|
|
// remember path for save/load purposes
|
|
bool found = false;
|
|
for (int32 i = 0; i < _mergedModels.getSize(); ++i) {
|
|
if (scumm_stricmp(_mergedModels[i], filename) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
size_t filenameSize = strlen(filename) + 1;
|
|
char *path = new char[filenameSize];
|
|
Common::strcpy_s(path, filenameSize, filename);
|
|
_mergedModels.add(path);
|
|
}
|
|
|
|
delete xfile;
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::loadAnimationSet(const char *filename, XFileData *xobj) {
|
|
bool res = true;
|
|
|
|
// create the animation set object
|
|
AnimationSet *animSet = new AnimationSet(_game, this);
|
|
res = loadName(animSet, xobj);
|
|
if (!res) {
|
|
SAFE_DELETE(animSet);
|
|
return res;
|
|
}
|
|
|
|
// use the filename for unnamed animation sets
|
|
if (animSet->_name[0] == '\0') {
|
|
animSet->setName(PathUtil::getFileName(filename).c_str());
|
|
}
|
|
|
|
// query through the child objects to load the animations
|
|
XFileData xchildData;
|
|
XClassType objectType;
|
|
|
|
uint32 numChildren = 0;
|
|
xobj->getChildren(numChildren);
|
|
|
|
for (uint32 i = 0; i < numChildren; i++) {
|
|
_game->miniUpdate();
|
|
|
|
res = xobj->getChild(i, xchildData);
|
|
if (res) {
|
|
res = xchildData.getType(objectType);
|
|
if (!res) {
|
|
SAFE_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) {
|
|
SAFE_DELETE(animSet);
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_animationSets.add(animSet);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::loadAnimation(const char *filename, XFileData *xobj, AnimationSet *parentAnimSet) {
|
|
// if no parent anim set is specified, create one
|
|
bool newAnimSet = false;
|
|
if (parentAnimSet == nullptr) {
|
|
parentAnimSet = new AnimationSet(_game, this);
|
|
|
|
parentAnimSet->setName(PathUtil::getFileName(filename).c_str());
|
|
|
|
newAnimSet = true;
|
|
}
|
|
|
|
// create the new object
|
|
Animation *anim = new Animation(_game);
|
|
|
|
// load the animation
|
|
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) {
|
|
SAFE_DELETE(anim);
|
|
if (newAnimSet)
|
|
SAFE_DELETE(parentAnimSet);
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
parentAnimSet->addAnimation(anim);
|
|
|
|
if (newAnimSet)
|
|
_animationSets.add(parentAnimSet);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::findBones(bool animOnly, XModel *parentModel) {
|
|
FrameNode *rootFrame;
|
|
if (parentModel == nullptr)
|
|
rootFrame = _rootFrame;
|
|
else
|
|
rootFrame = parentModel->getRootFrame();
|
|
|
|
if (rootFrame && !animOnly) {
|
|
_rootFrame->findBones(rootFrame);
|
|
}
|
|
|
|
for (int32 i = 0; i < _animationSets.getSize(); i++) {
|
|
_animationSets[i]->findBones(rootFrame);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
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
|
|
reset();
|
|
|
|
// update all animation channels
|
|
for (int i = 0; i < X_NUM_ANIMATION_CHANNELS; i++) {
|
|
_channels[i]->update(i == 1);
|
|
}
|
|
|
|
// update matrices
|
|
if (_rootFrame) {
|
|
DXMatrix tempMat;
|
|
DXMatrixIdentity(&tempMat);
|
|
_rootFrame->updateMatrices(&tempMat);
|
|
|
|
return _rootFrame->updateMeshes();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::playAnim(int channel, const char *name, uint32 transitionTime, bool forceReset, uint32 stopTransitionTime) {
|
|
if (channel < 0 || channel >= X_NUM_ANIMATION_CHANNELS) {
|
|
return false;
|
|
}
|
|
|
|
// are we already playing this animation?
|
|
if (!forceReset) {
|
|
if (_channels[channel]->getName() && scumm_stricmp(name, _channels[channel]->getName()) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// find animation set by name
|
|
AnimationSet *anim = getAnimationSetByName(name);
|
|
if (anim) {
|
|
char *currentAnim = _channels[channel]->getName();
|
|
if (_owner && currentAnim && name) {
|
|
transitionTime = _owner->getAnimTransitionTime(currentAnim, name);
|
|
}
|
|
|
|
return _channels[channel]->playAnim(anim, transitionTime, stopTransitionTime);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::stopAnim(int channel, uint32 transitionTime) {
|
|
if (channel < 0 || channel >= X_NUM_ANIMATION_CHANNELS) {
|
|
return false;
|
|
}
|
|
|
|
return _channels[channel]->stopAnim(transitionTime);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::stopAnim(uint32 transitionTime) {
|
|
const int NUM_SKEL_ANI_CHANNELS = 10;
|
|
|
|
for (int channel = 0; channel < NUM_SKEL_ANI_CHANNELS; channel++) {
|
|
stopAnim(channel, transitionTime);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::reset() {
|
|
if (_rootFrame) {
|
|
_rootFrame->resetMatrices();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::isAnimPending(int channel, const char *animName) {
|
|
if (!animName) {
|
|
if (_channels[channel]->isPlaying()) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (_channels[channel]->isPlaying() && _channels[channel]->getName() && scumm_stricmp(animName, _channels[channel]->getName()) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::isAnimPending(char *animName) {
|
|
for (int channel = 0; channel < X_NUM_ANIMATION_CHANNELS; channel++) {
|
|
if (isAnimPending(channel, animName)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::updateShadowVol(ShadowVolume *shadow, DXMatrix *modelMat, DXVector3 *light, float extrusionDepth) {
|
|
if (_rootFrame) {
|
|
return _rootFrame->updateShadowVol(shadow, modelMat, light, extrusionDepth);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::render() {
|
|
BaseRenderer3D *renderer = _game->_renderer3D;
|
|
|
|
if (_rootFrame) {
|
|
// set culling
|
|
if (_owner && !_owner->_drawBackfaces) {
|
|
renderer->enableCulling();
|
|
} else {
|
|
renderer->disableCulling();
|
|
}
|
|
|
|
// render everything
|
|
bool res = _rootFrame->render(this);
|
|
|
|
// remember matrices for object picking purposes
|
|
renderer->getWorldTransform(&_lastWorldMat);
|
|
renderer->getViewTransform(&_lastViewMat);
|
|
renderer->getProjectionTransform(&_lastProjMat);
|
|
|
|
// remember scene offset
|
|
Common::Rect32 rc;
|
|
_game->getCurrentViewportRect(&rc);
|
|
float width = (float)rc.right - (float)rc.left;
|
|
float height = (float)rc.bottom - (float)rc.top;
|
|
|
|
// margins
|
|
int mleft = rc.left;
|
|
int mright = renderer->getWidth() - width - rc.left;
|
|
int mtop = rc.top;
|
|
int mbottom = renderer->getHeight() - height - rc.top;
|
|
|
|
_lastOffsetX = _game->_offsetX + (mleft - mright) / 2;
|
|
_lastOffsetY = _game->_offsetY + (mtop - mbottom) / 2;
|
|
|
|
// update bounding box and 2D bounding rectangle
|
|
updateBoundingRect();
|
|
|
|
return res;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool XModel::renderFlatShadowModel(uint32 shadowColor) {
|
|
if (_rootFrame) {
|
|
if(_owner && !_owner->_drawBackfaces) {
|
|
_game->_renderer3D->enableCulling();
|
|
} else {
|
|
_game->_renderer3D->disableCulling();
|
|
}
|
|
|
|
return _rootFrame->renderFlatShadowModel(shadowColor);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
DXMatrix *XModel::getBoneMatrix(const char *boneName) {
|
|
FrameNode *bone = _rootFrame->findFrame(boneName);
|
|
|
|
if (bone) {
|
|
return bone->getCombinedMatrix();
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
FrameNode *XModel::getRootFrame() {
|
|
return _rootFrame;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::isTransparentAt(int x, int y) {
|
|
if (!_rootFrame) {
|
|
return false;
|
|
}
|
|
|
|
x += _lastOffsetX;
|
|
y += _lastOffsetY;
|
|
|
|
BaseRenderer3D *renderer = _game->_renderer3D;
|
|
if (!_game->_renderer3D->_camera)
|
|
return true;
|
|
|
|
float resWidth, resHeight;
|
|
float layerWidth, layerHeight;
|
|
float modWidth, modHeight;
|
|
bool customViewport;
|
|
_game->_renderer3D->getProjectionParams(&resWidth, &resHeight, &layerWidth, &layerHeight, &modWidth, &modHeight, &customViewport);
|
|
|
|
x -= _drawingViewport._x + modWidth;
|
|
y -= _drawingViewport._y + modHeight;
|
|
|
|
if (customViewport) {
|
|
x += renderer->_drawOffsetX;
|
|
y += renderer->_drawOffsetY;
|
|
}
|
|
|
|
DXVector3 pickRayDir;
|
|
DXVector3 pickRayOrig;
|
|
|
|
// Compute the vector of the pick ray in screen space
|
|
DXVector3 vec;
|
|
vec._x = (((2.0f * x) / (_drawingViewport._width)) - 1) / _lastProjMat.matrix._11;
|
|
vec._y = -(((2.0f * y) / (_drawingViewport._height)) - 1) / _lastProjMat.matrix._22;
|
|
vec._z = 1.0f;
|
|
|
|
// Get the inverse view matrix
|
|
DXMatrix m;
|
|
DXMatrixInverse(&m, nullptr, &_lastViewMat);
|
|
|
|
// Transform the screen space pick ray into 3D space
|
|
pickRayDir._x = vec._x * m.matrix._11 + vec._y * m.matrix._21 + vec._z * m.matrix._31;
|
|
pickRayDir._y = vec._x * m.matrix._12 + vec._y * m.matrix._22 + vec._z * m.matrix._32;
|
|
pickRayDir._z = vec._x * m.matrix._13 + vec._y * m.matrix._23 + vec._z * m.matrix._33;
|
|
pickRayOrig._x = m.matrix._41;
|
|
pickRayOrig._y = m.matrix._42;
|
|
pickRayOrig._z = m.matrix._43;
|
|
|
|
// transform to model space
|
|
DXVector3 end = pickRayOrig + pickRayDir;
|
|
DXMatrixInverse(&m, nullptr, &_lastWorldMat);
|
|
DXVec3TransformCoord(&pickRayOrig, &pickRayOrig, &m);
|
|
DXVec3TransformCoord(&end, &end, &m);
|
|
pickRayDir = end - pickRayOrig;
|
|
|
|
return !_rootFrame->pickPoly(&pickRayOrig, &pickRayDir);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void XModel::updateBoundingRect() {
|
|
_BBoxStart = _BBoxEnd = DXVector3(0, 0, 0);
|
|
|
|
if (_rootFrame) {
|
|
_rootFrame->getBoundingBox(&_BBoxStart, &_BBoxEnd);
|
|
}
|
|
|
|
_boundingRect.left = _boundingRect.top = INT_MAX_VALUE;
|
|
_boundingRect.right = _boundingRect.bottom = INT_MIN_VALUE;
|
|
|
|
BaseRenderer3D *renderer = _game->_renderer3D;
|
|
|
|
DXMatrix viewMat, projMat, worldMat;
|
|
DXVector3 vec2d(0, 0, 0);
|
|
renderer->getViewTransform(&viewMat);
|
|
renderer->getProjectionTransform(&projMat);
|
|
renderer->getWorldTransform(&worldMat);
|
|
|
|
_drawingViewport = renderer->getViewPort();
|
|
|
|
float x1 = _BBoxStart._x;
|
|
float x2 = _BBoxEnd._x;
|
|
float y1 = _BBoxStart._y;
|
|
float y2 = _BBoxEnd._y;
|
|
float z1 = _BBoxStart._z;
|
|
float z2 = _BBoxEnd._z;
|
|
|
|
DXVector3 v111(x1 ,y1, z1);
|
|
DXVec3Project(&vec2d, &v111, &_drawingViewport, &projMat, &viewMat, &worldMat);
|
|
updateRect(&_boundingRect, &vec2d);
|
|
DXVector3 v211(x2, y1, z1);
|
|
DXVec3Project(&vec2d, &v211, &_drawingViewport, &projMat, &viewMat, &worldMat);
|
|
updateRect(&_boundingRect, &vec2d);
|
|
DXVector3 v112(x1, y1, z2);
|
|
DXVec3Project(&vec2d, &v112, &_drawingViewport, &projMat, &viewMat, &worldMat);
|
|
updateRect(&_boundingRect, &vec2d);
|
|
DXVector3 v212(x2, y1, z2);
|
|
DXVec3Project(&vec2d, &v212, &_drawingViewport, &projMat, &viewMat, &worldMat);
|
|
updateRect(&_boundingRect, &vec2d);
|
|
|
|
DXVector3 v121(x1, y2, z1);
|
|
DXVec3Project(&vec2d, &v121, &_drawingViewport, &projMat, &viewMat, &worldMat);
|
|
updateRect(&_boundingRect, &vec2d);
|
|
DXVector3 v221(x2, y2, z1);
|
|
DXVec3Project(&vec2d, &v221, &_drawingViewport, &projMat, &viewMat, &worldMat);
|
|
updateRect(&_boundingRect, &vec2d);
|
|
DXVector3 v122(x1, y2, z2);
|
|
DXVec3Project(&vec2d, &v122, &_drawingViewport, &projMat, &viewMat, &worldMat);
|
|
updateRect(&_boundingRect, &vec2d);
|
|
DXVector3 v222(x2, y2, z2);
|
|
DXVec3Project(&vec2d, &v222, &_drawingViewport, &projMat, &viewMat, &worldMat);
|
|
updateRect(&_boundingRect, &vec2d);
|
|
|
|
_boundingRect.left -= renderer->_drawOffsetX;
|
|
_boundingRect.right -= renderer->_drawOffsetX;
|
|
_boundingRect.bottom -= renderer->_drawOffsetY;
|
|
_boundingRect.top -= renderer->_drawOffsetY;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void XModel::updateRect(Common::Rect32 *rc, DXVector3 *vec) {
|
|
rc->left = MIN(rc->left, (int32)vec->_x);
|
|
rc->right = MAX(rc->right, (int32)vec->_x);
|
|
rc->top = MIN(rc->top, (int32)vec->_y);
|
|
rc->bottom = MAX(rc->bottom, (int32)vec->_y);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
AnimationSet *XModel::getAnimationSetByName(const char *name) {
|
|
for (int32 i = 0; i < _animationSets.getSize(); i++) {
|
|
if (scumm_stricmp(name, _animationSets[i]->_name) == 0) {
|
|
return _animationSets[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
TOKEN_DEF_START
|
|
TOKEN_DEF(NAME)
|
|
TOKEN_DEF(LOOPING)
|
|
TOKEN_DEF(EVENT)
|
|
TOKEN_DEF(FRAME)
|
|
TOKEN_DEF_END
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::parseAnim(char *buffer) {
|
|
TOKEN_TABLE_START(commands)
|
|
TOKEN_TABLE(NAME)
|
|
TOKEN_TABLE(LOOPING)
|
|
TOKEN_TABLE(EVENT)
|
|
TOKEN_TABLE_END
|
|
|
|
char *params;
|
|
int cmd;
|
|
BaseParser parser(_game);
|
|
|
|
char *name = nullptr;
|
|
bool looping = false;
|
|
bool loopingSet = false;
|
|
|
|
while ((cmd = parser.getCommand(&buffer, commands, ¶ms)) > 0) {
|
|
switch (cmd) {
|
|
case TOKEN_NAME: {
|
|
BaseUtils::setString(&name, params);
|
|
|
|
AnimationSet *anim = getAnimationSetByName(name);
|
|
if (!anim) {
|
|
_game->LOG(0, "Error: Animation '%s' cannot be found in the model.", name);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TOKEN_LOOPING:
|
|
parser.scanStr(params, "%b", &looping);
|
|
loopingSet = true;
|
|
break;
|
|
|
|
case TOKEN_EVENT:
|
|
if (!name) {
|
|
_game->LOG(0, "Error: NAME filed must precede any EVENT fields in actor definition files.");
|
|
} else {
|
|
AnimationSet *anim = getAnimationSetByName(name);
|
|
if (anim)
|
|
parseEvent(anim, params);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cmd != PARSERR_EOF) {
|
|
return false;
|
|
}
|
|
|
|
bool ret = true;
|
|
|
|
if (name) {
|
|
AnimationSet *anim = getAnimationSetByName(name);
|
|
if (anim) {
|
|
if (loopingSet)
|
|
anim->_looping = looping;
|
|
}
|
|
}
|
|
|
|
delete[] name;
|
|
|
|
return ret;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::parseEvent(AnimationSet *anim, char *buffer) {
|
|
TOKEN_TABLE_START(commands)
|
|
TOKEN_TABLE(NAME)
|
|
TOKEN_TABLE(FRAME)
|
|
TOKEN_TABLE_END
|
|
|
|
char *params;
|
|
int cmd;
|
|
BaseParser parser(_game);
|
|
|
|
AnimationSet::AnimationEvent *event = new AnimationSet::AnimationEvent();
|
|
if (!event) {
|
|
return false;
|
|
}
|
|
|
|
while ((cmd = parser.getCommand(&buffer, commands, ¶ms)) > 0) {
|
|
switch (cmd) {
|
|
case TOKEN_NAME:
|
|
BaseUtils::setString(&event->_eventName, params);
|
|
break;
|
|
|
|
case TOKEN_FRAME:
|
|
parser.scanStr(params, "%d", &event->_frame);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cmd != PARSERR_EOF) {
|
|
SAFE_DELETE(event);
|
|
return false;
|
|
}
|
|
|
|
if (event->_eventName) {
|
|
anim->addEvent(event);
|
|
} else {
|
|
SAFE_DELETE(event);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::setMaterialSprite(const char *materialName, const char *spriteFilename) {
|
|
if (!materialName || !spriteFilename) {
|
|
return false;
|
|
}
|
|
|
|
if (!_rootFrame) {
|
|
return false;
|
|
}
|
|
|
|
BaseSprite *sprite = new BaseSprite(_game);
|
|
if (!sprite || !sprite->loadFile(spriteFilename)) {
|
|
SAFE_DELETE(sprite);
|
|
return false;
|
|
}
|
|
|
|
XModelMatSprite *matSprite = nullptr;
|
|
for (int32 i = 0; i < _matSprites.getSize(); i++) {
|
|
if (scumm_stricmp(_matSprites[i]->_matName, materialName) == 0) {
|
|
matSprite = _matSprites[i];
|
|
break;
|
|
}
|
|
}
|
|
if (matSprite) {
|
|
matSprite->setSprite(sprite);
|
|
} else {
|
|
matSprite = new XModelMatSprite(materialName, sprite);
|
|
_matSprites.add(matSprite);
|
|
}
|
|
|
|
_rootFrame->setMaterialSprite(matSprite->_matName, matSprite->_sprite);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::setMaterialTheora(const char *materialName, const char *theoraFilename) {
|
|
if (!materialName || !theoraFilename) {
|
|
return false;
|
|
}
|
|
|
|
if (!_rootFrame) {
|
|
return false;
|
|
}
|
|
|
|
VideoTheoraPlayer *theora = new VideoTheoraPlayer(_game);
|
|
if (!theora || theora->initialize(theoraFilename)) {
|
|
SAFE_DELETE(theora);
|
|
return false;
|
|
}
|
|
|
|
theora->play(VID_PLAY_POS, 0, 0, false, false, true);
|
|
|
|
XModelMatSprite *matSprite = nullptr;
|
|
for (int32 i = 0; i < _matSprites.getSize(); i++) {
|
|
if (scumm_stricmp(_matSprites[i]->_matName, materialName) == 0) {
|
|
matSprite = _matSprites[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (matSprite) {
|
|
matSprite->setTheora(theora);
|
|
} else {
|
|
matSprite = new XModelMatSprite(materialName, theora);
|
|
_matSprites.add(matSprite);
|
|
}
|
|
_rootFrame->setMaterialTheora(matSprite->_matName, matSprite->_theora);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::setMaterialEffect(const char *materialName, const char *effectFilename) {
|
|
if (!materialName || !effectFilename)
|
|
return false;
|
|
if (!_rootFrame)
|
|
return false;
|
|
|
|
|
|
Effect3D *effect = new Effect3D(_game);
|
|
if (!effect->createFromFile(effectFilename)) {
|
|
SAFE_DELETE(effect);
|
|
return false;
|
|
}
|
|
|
|
XModelMatSprite *matSprite = nullptr;
|
|
for (int32 i = 0 ; i < _matSprites.getSize(); i++) {
|
|
if (scumm_stricmp(_matSprites[i]->_matName, materialName) == 0) {
|
|
matSprite = _matSprites[i];
|
|
break;
|
|
}
|
|
}
|
|
if (matSprite) {
|
|
matSprite->setEffect(effect);
|
|
} else {
|
|
matSprite = new XModelMatSprite(materialName, effect);
|
|
_matSprites.add(matSprite);
|
|
}
|
|
_rootFrame->setMaterialEffect(matSprite->_matName, matSprite->_effect, matSprite->_effectParams);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::removeMaterialEffect(const char *materialName) {
|
|
if (!materialName)
|
|
return false;
|
|
if (!_rootFrame)
|
|
return false;
|
|
|
|
for (int32 i = 0; i < _matSprites.getSize(); i++) {
|
|
if (scumm_stricmp(_matSprites[i]->_matName, materialName) == 0) {
|
|
SAFE_DELETE(_matSprites[i]);
|
|
_matSprites.removeAt(i);
|
|
_rootFrame->removeMaterialEffect(materialName);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::setMaterialEffectParam(const char *materialName, const char *paramName, ScValue *val) {
|
|
if (!materialName)
|
|
return false;
|
|
if (!_rootFrame)
|
|
return false;
|
|
|
|
|
|
for (int32 i = 0 ; i < _matSprites.getSize(); i++) {
|
|
if (scumm_stricmp(_matSprites[i]->_matName, materialName) == 0) {
|
|
if (_matSprites[i]->_effectParams) {
|
|
_matSprites[i]->_effectParams->setParam(paramName, val);
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::setMaterialEffectParam(const char *materialName, const char *paramName, DXVector4 val) {
|
|
if (!materialName)
|
|
return false;
|
|
if (!_rootFrame)
|
|
return false;
|
|
|
|
|
|
for (int32 i = 0; i < _matSprites.getSize(); i++) {
|
|
if (scumm_stricmp(_matSprites[i]->_matName, materialName) == 0) {
|
|
if (_matSprites[i]->_effectParams) {
|
|
_matSprites[i]->_effectParams->setParam(paramName, val);
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::initializeSimple() {
|
|
if (!_rootFrame) {
|
|
return false;
|
|
}
|
|
|
|
// init after load
|
|
for (int32 i = 0; i < _matSprites.getSize(); i++) {
|
|
if (_matSprites[i]->_theora) {
|
|
_rootFrame->setMaterialTheora(_matSprites[i]->_matName, _matSprites[i]->_theora);
|
|
} else if (_matSprites[i]->_sprite) {
|
|
_rootFrame->setMaterialSprite(_matSprites[i]->_matName, _matSprites[i]->_sprite);
|
|
}
|
|
|
|
if (_matSprites[i]->_effectFile) {
|
|
Effect3D *effect = new Effect3D(_game);
|
|
if (effect->createFromFile(_matSprites[i]->_effectFile)) {
|
|
_matSprites[i]->_effect = effect;
|
|
_rootFrame->setMaterialEffect(_matSprites[i]->_matName, _matSprites[i]->_effect, _matSprites[i]->_effectParams);
|
|
} else {
|
|
SAFE_DELETE(effect);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_parentModel) {
|
|
findBones(false, _parentModel);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::persist(BasePersistenceManager *persistMgr) {
|
|
BaseObject::persist(persistMgr);
|
|
|
|
persistMgr->transferVector3d(TMEMBER(_BBoxStart));
|
|
persistMgr->transferVector3d(TMEMBER(_BBoxEnd));
|
|
persistMgr->transferRect32(TMEMBER(_boundingRect));
|
|
|
|
if (!persistMgr->getIsSaving()) {
|
|
memset(&_drawingViewport, 0, sizeof(DXViewport));
|
|
}
|
|
|
|
persistMgr->transferSint32(TMEMBER(_lastOffsetX));
|
|
persistMgr->transferSint32(TMEMBER(_lastOffsetY));
|
|
|
|
persistMgr->transferMatrix4(TMEMBER(_lastProjMat));
|
|
persistMgr->transferMatrix4(TMEMBER(_lastViewMat));
|
|
persistMgr->transferMatrix4(TMEMBER(_lastWorldMat));
|
|
|
|
persistMgr->transferPtr(TMEMBER(_owner));
|
|
_mergedModels.persist(persistMgr);
|
|
|
|
// load model
|
|
if (!persistMgr->getIsSaving()) {
|
|
for (int i = 0; i < X_NUM_ANIMATION_CHANNELS; i++) {
|
|
_channels[i] = nullptr;
|
|
}
|
|
_rootFrame = nullptr;
|
|
|
|
if (_filename && _filename[0]) {
|
|
loadFromFile(_filename);
|
|
}
|
|
|
|
for (int32 i = 0; i < _mergedModels.getSize(); ++i) {
|
|
mergeFromFile(_mergedModels[i]);
|
|
}
|
|
}
|
|
|
|
persistMgr->transferPtr(TMEMBER(_parentModel));
|
|
|
|
// animation properties
|
|
int32 numAnims;
|
|
if (persistMgr->getIsSaving()) {
|
|
numAnims = _animationSets.getSize();
|
|
}
|
|
|
|
persistMgr->transferSint32(TMEMBER(numAnims));
|
|
|
|
if (persistMgr->getIsSaving()) {
|
|
for (int32 i = 0; i < _animationSets.getSize(); i++) {
|
|
persistMgr->transferCharPtr(TMEMBER(_animationSets[i]->_name));
|
|
_animationSets[i]->persist(persistMgr);
|
|
}
|
|
} else {
|
|
for (int i = 0; i < numAnims; i++) {
|
|
bool needsDelete = false;
|
|
char *animName;
|
|
persistMgr->transferCharPtr(TMEMBER(animName));
|
|
AnimationSet *animSet = getAnimationSetByName(animName);
|
|
if (!animSet) {
|
|
animSet = new AnimationSet(_game, this);
|
|
needsDelete = true;
|
|
}
|
|
|
|
animSet->persist(persistMgr);
|
|
if (needsDelete) {
|
|
delete animSet;
|
|
}
|
|
|
|
delete[] animName;
|
|
}
|
|
}
|
|
|
|
// persist channels
|
|
for (int i = 0; i < X_NUM_ANIMATION_CHANNELS; i++) {
|
|
_channels[i]->persist(persistMgr);
|
|
}
|
|
|
|
// persist material sprites
|
|
int32 numMatSprites;
|
|
if (persistMgr->getIsSaving()) {
|
|
numMatSprites = _matSprites.getSize();
|
|
}
|
|
|
|
persistMgr->transferSint32(TMEMBER(numMatSprites));
|
|
|
|
for (int i = 0; i < numMatSprites; i++) {
|
|
if (persistMgr->getIsSaving()) {
|
|
_matSprites[i]->persist(persistMgr);
|
|
} else {
|
|
XModelMatSprite *MatSprite = new XModelMatSprite();
|
|
MatSprite->persist(persistMgr);
|
|
_matSprites.add(MatSprite);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::invalidateDeviceObjects() {
|
|
if (_rootFrame) {
|
|
return _rootFrame->invalidateDeviceObjects();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::restoreDeviceObjects() {
|
|
if (_rootFrame) {
|
|
return _rootFrame->restoreDeviceObjects();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XModel::unloadAnimation(const char *animName) {
|
|
bool found = false;
|
|
for (int32 i = 0; i < _animationSets.getSize(); i++) {
|
|
if (scumm_stricmp(animName, _animationSets[i]->_name) == 0) {
|
|
for (int32 j = 0; j < X_NUM_ANIMATION_CHANNELS; j++) {
|
|
if (_channels[j])
|
|
_channels[j]->unloadAnim(_animationSets[i]);
|
|
}
|
|
|
|
found = true;
|
|
SAFE_DELETE(_animationSets[i]);
|
|
_animationSets.removeAt(i);
|
|
i++;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
} // namespace Wintermute
|