/* 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/file.h"
#include "common/util.h"
#include "common/substream.h"
#include "common/compression/deflate.h"
#include "tetraedge/tetraedge.h"
#include "tetraedge/te/te_light.h"
#include "tetraedge/te/te_model.h"
#include "tetraedge/te/te_model_animation.h"
#include "tetraedge/te/te_renderer.h"
#include "tetraedge/te/te_trs.h"
namespace Tetraedge {
TeModel::TeModel() : _enableLights(false), _skipSkinOffsets(false), _matrixForced(false) {
_modelAnim.setDeleteFn(&TeModelAnimation::deleteLaterStatic);
_modelVertexAnim.setDeleteFn(&TeModelVertexAnimation::deleteLaterStatic);
create();
}
TeModel::~TeModel() {
destroy();
}
void TeModel::blendAnim(TeIntrusivePtr &anim, float seconds, bool repeat) {
if (!_modelAnim) {
setAnim(anim, repeat);
} else {
BonesBlender *blender = new BonesBlender(anim, seconds);
anim->_repeatCount = (repeat ? -1 : 1);
anim->play();
_boneBlenders.push_back(blender);
}
}
void TeModel::blendMesh(const Common::String &s1, const Common::String &s2, float amount) {
_meshBlenders.push_back(new MeshBlender(s1, s2, amount, this));
}
int TeModel::checkFileType(Common::SeekableReadStream &instream) const {
char buf[4];
instream.seek(0);
int sz = instream.read(buf, 4);
instream.seek(0);
if (sz == 4 && strncmp("TEMD", buf, 4) == 0) {
return 1;
} else if (sz == 4 && strncmp("TEAN", buf, 4) == 0) {
return 2;
}
return 0;
}
void TeModel::create() {
// TODO: set field_0x158 to 0
_modelAnim.release();
_modelVertexAnim.release();
_matrixForced = false;
_skipSkinOffsets = false;
}
void TeModel::destroy() {
_weightElements.clear();
_lerpedElements.clear();
_meshes.clear();
_bones.clear();
_boneMatricies.clear();
_skinOffsets.clear();
for (MeshBlender *blender : _meshBlenders)
delete blender;
_meshBlenders.clear();
for (BonesBlender *blender : _boneBlenders)
delete blender;
_boneBlenders.clear();
}
void TeModel::draw() {
TeRenderer *renderer = g_engine->getRenderer();
if (worldVisible()) {
const TeMatrix4x4 transform = transformationMatrix();
renderer->sendModelMatrix(transform);
renderer->pushMatrix();
renderer->multiplyMatrix(transform);
/*
if (name().contains("DEPLIANT")) {
debug("Draw model %p (%s, %d meshes)", this, name().empty() ? "no name" : name().c_str(), _meshes.size());
debug(" renderMatrix %s", renderer->currentMatrix().toString().c_str());
//debug(" position %s", position().dump().c_str());
debug(" worldPos %s", worldPosition().dump().c_str());
//debug(" scale %s", scale().dump().c_str());
debug(" worldScale %s", worldScale().dump().c_str());
//debug(" rotation %s", rotation().dump().c_str());
debug(" worldRot %s", worldRotation().dump().c_str());
}*/
for (auto &mesh : _meshes) {
// TODO: Set some flag (_drawWires?) in mesh to this->field_0x158??
mesh->draw();
}
renderer->popMatrix();
// Note: no corresponding enableAll - they can be enabled inside TeMaterial::apply
renderer->disableAllLights();
}
}
void TeModel::forceMatrix(const TeMatrix4x4 &matrix) {
_matrixForced = true;
_forcedMatrix = matrix;
}
TeTRS TeModel::getBone(TeIntrusivePtr anim, uint num) {
if (anim) {
int boneNo = anim->findBone(_bones[num]._name);
if (boneNo != -1)
return anim->getTRS(boneNo, anim->curFrame2(), false);
}
return _bones[num]._trs;
}
void TeModel::invertNormals() {
for (auto &mesh : _meshes) {
for (uint i = 0; i < mesh->numIndexes() / 3; i += 3) {
// Swap order of verticies in each triangle.
uint idx0 = mesh->index(i * 3);
uint idx2 = mesh->index(i * 3 + 2);
mesh->setIndex(i * 3, idx2);
mesh->setIndex(i * 3 + 2, idx0);
}
for (uint i = 0; i < mesh->numVerticies(); i++) {
mesh->setNormal(i, -mesh->normal(i));
}
}
}
TeMatrix4x4 TeModel::lerpElementsMatrix(uint weightsNum, const Common::Array &matricies) {
TeMatrix4x4 retval;
// Start with a 0 matrix.
for (uint i = 0; i < 4; i++)
retval.setValue(i, i, 0);
const Common::Array &weights = _weightElements[weightsNum];
for (const auto &weight : weights) {
const TeMatrix4x4 offset = matricies[weight._x].meshScale(weight._weight);
retval.meshAdd(offset);
}
return retval;
}
void TeModel::removeAnim() {
for (TeModel::BonesBlender *blender : _boneBlenders) {
delete blender;
}
_boneBlenders.clear();
_modelAnim.release();
}
void TeModel::setColor(const TeColor &col) {
Te3DObject2::setColor(col);
for (auto &mesh : _meshes) {
mesh->setColor(col);
}
}
void TeModel::update() {
//if (name().contains("Kate"))
// debug("TeModel::update model %s", name().c_str());
if (_bones.size()) {
Common::Array matricies(_bones.size());
for (uint i = 0; i < _bones.size(); i++) {
const Bone &b = _bones[i];
const TeMatrix4x4 matrix = TeMatrix4x4::fromTRS(b._trs);
if (b._parentBone == -1 || _bones.size() < 2) {
matricies[0] = matrix;
} else {
matricies[i] = matricies[b._parentBone] * matrix;
}
}
_boneMatricies.resize(_bones.size());
_lerpedElements.resize(_weightElements.size());
TeMatrix4x4 invertx;
invertx.scale(TeVector3f32(-1, 1, 1));
for (uint b = 0; b < _bones.size(); b++) {
TeTRS trs = getBone(_modelAnim, b);
for (uint i = 0; i < _boneBlenders.size(); i++) {
BonesBlender *blender = _boneBlenders[i];
float complete = blender->coef();
TeTRS endTRS = getBone(blender->_anim, b);
if (complete == 1.0f) {
_modelAnim = blender->_anim;
delete blender;
_boneBlenders.remove_at(i);
trs = endTRS;
i--;
} else {
trs = trs.lerp(endTRS, complete);
}
}
TeMatrix4x4 newBoneMatrix;
if (!_matrixForced) {
newBoneMatrix = TeMatrix4x4::fromTRS(trs);
} else {
newBoneMatrix = invertx * _forcedMatrix;
_matrixForced = false;
}
if (_bones.size() < 2 || _bones[b]._parentBone == -1) {
_boneMatricies[b] = newBoneMatrix;
_boneMatricies[b].rotate(_boneRotation);
} else {
_boneMatricies[b] = (invertx * _boneMatricies[_bones[b]._parentBone]) * newBoneMatrix;
}
_boneMatricies[b] = invertx * _boneMatricies[b];
_bonesUpdatedSignal.call(_bones[b]._name, _boneMatricies[b]);
}
if (!_skinOffsets.empty() && !_bones.empty()) {
for (uint b = 0; b < _bones.size(); b++) {
_boneMatricies[b] = _boneMatricies[b] * _skinOffsets[b];
}
}
if (!_skipSkinOffsets && !_weightElements.empty()) {
for (uint i = 0; i < _weightElements.size(); i++) {
_lerpedElements[i] = lerpElementsMatrix(i, _boneMatricies);
}
}
for (uint m = 0; m < _meshes.size(); m++) {
TeMesh &mesh = *_meshes[m];
if (!mesh.visible())
continue;
if (!_skipSkinOffsets && _bones.size() < 2 ) {
if (_modelVertexAnim && mesh.name() == _modelVertexAnim->head()) {
mesh.update(_modelVertexAnim);
// TODO: lines 422 - 427.. set some vals and goto LAB_doMeshBlends;
}
mesh.update(&_boneMatricies, &_lerpedElements);
} else {
mesh.resizeUpdatedTables(mesh.numVerticies());
Common::Array verticies;
if (_modelVertexAnim && mesh.name() == _modelVertexAnim->head())
verticies = _modelVertexAnim->getVertices();
for (uint i = 0; i < mesh.numVerticies(); i++) {
TeVector3f32 vertex;
if (verticies.empty()) {
vertex = mesh.preUpdatedVertex(i);
} else {
if (i < verticies.size())
vertex = verticies[i];
}
TeVector3f32 normal = mesh.preUpdatedNormal(i);
int idx = (int)mesh.matrixIndex(i);
TeVector3f32 updatedvertex;
TeVector3f32 updatednormal;
if (idx < (int)_bones.size()) {
updatedvertex = vertex;
if (verticies.empty())
updatedvertex = _boneMatricies[idx] * updatedvertex;
updatednormal = _boneMatricies[idx].mult3x3(normal);
} else {
idx -= _bones.size();
for (uint w = 0; w < _weightElements[idx].size(); w++) {
const TeMatrix4x4 &wmatrix = _boneMatricies[_weightElements[idx][w]._x];
float weight = _weightElements[idx][w]._weight;
updatedvertex = updatedvertex + ((wmatrix * vertex) * weight);
updatednormal = updatednormal + (wmatrix.mult3x3(normal) * weight);
}
}
mesh.setUpdatedVertex(i, updatedvertex);
mesh.setUpdatedNormal(i, updatednormal);
}
}
for (MeshBlender *mb : _meshBlenders) {
if (mesh.name().contains(mb->_name)) {
error("TODO: Finish meshblend part of model::update");
// TODO: Finish TeModel::update. (disasm 585 ~ 644), LAB_doMeshBlends
//float blendamount = MIN(mb->_timer.getTimeFromStart() / 1000000.0f, 1.0f);
}
}
}
} else {
// No bones..
for (auto &mesh : _meshes) {
if (!_modelVertexAnim) {
mesh->update(nullptr, nullptr);
} else {
if (mesh->name() != _modelVertexAnim->head()) {
mesh->update(nullptr, nullptr);
} else {
mesh->update(_modelVertexAnim);
}
}
}
}
}
TeModel::MeshBlender::MeshBlender(const Common::String &name, const Common::String &meshName, float amount, TeModel *model) :
_name(name), _amount(amount) {
const auto &meshes = model->_meshes;
uint i = 0;
for (; i < meshes.size(); i++) {
if (meshes[i]->name().contains(meshName))
break;
}
_meshNo = i;
_timer.start();
}
/*static*/
void TeModel::loadAlign(Common::SeekableReadStream &stream) {
int64 pos = stream.pos();
if (pos % 4) {
stream.seek(4 - (pos % 4), SEEK_CUR);
}
}
/*static*/
void TeModel::saveAlign(Common::SeekableWriteStream &stream) {
int64 pos = stream.pos();
if (pos % 4) {
stream.seek(4 - (pos % 4), SEEK_CUR);
}
}
bool TeModel::load(Common::SeekableReadStream &stream) {
destroy();
create();
if (!loadAndCheckFourCC(stream, "TEMD")) {
error("[TeModel::load] Unknown format.");
}
uint version = stream.readUint32LE();
if (!((version == 11) || (version == 13))) {
error("[TeModel::load] Unsupported version %d", version);
}
uint32 meshCount = stream.readUint32LE();
if (meshCount > 100000)
error("TeModel::load: Unexpected number of meshes %d", meshCount);
_meshes.resize(meshCount);
for (uint i = 0; i < meshCount; i++)
_meshes[i].reset(TeMesh::makeInstance());
uint32 weightCount = stream.readUint32LE();
if (weightCount > 100000)
error("TeModel::load: Unexpected number of weights %d", weightCount);
_weightElements.resize(weightCount);
uint32 bonecount = stream.readUint32LE();
if (bonecount > 100000)
error("TeModel::load: Unexpected number of bones %d", bonecount);
_bones.resize(bonecount);
_skinOffsets.resize(bonecount);
if (version == 13) {
_skipSkinOffsets = stream.readUint32LE();
}
if (!loadAndCheckFourCC(stream, "SKEL")) {
error("[TeModel::load] Unable to find skeleton.");
}
for (uint i = 0; i < _bones.size(); i++) {
_bones[i]._name = Te3DObject2::deserializeString(stream);
loadAlign(stream);
_bones[i]._parentBone = stream.readUint32LE();
TeTRS::deserialize(stream, _bones[i]._trs);
if (!_skipSkinOffsets || g_engine->gameType() == TetraedgeEngine::kSyberia2) {
_skinOffsets[i].deserialize(stream);
}
}
for (uint m = 0; m < _meshes.size(); m++) {
if (!loadMesh(stream, *_meshes[m])) {
error("[TeModel::load] Error on meshes loading.");
}
}
if (!loadAndCheckFourCC(stream, "WEIG")) {
error("[TeModel::load] Unable to load weight.");
}
for (uint i = 0; i < _weightElements.size(); i++) {
loadWeights(stream, _weightElements[i]);
}
if (_bones.empty())
_bones.resize(1);
return true;
}
bool TeModel::load(const Common::Path &path) {
Common::File modelFile;
if (!modelFile.open(path)) {
warning("[TeModel::load] Can't open file : %s.", path.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
bool retval;
if (loadAndCheckFourCC(modelFile, "TEZ0")) {
Common::SeekableReadStream *zlibStream = tryLoadZlibStream(modelFile);
if (!zlibStream)
return false;
retval = load(*zlibStream);
delete zlibStream;
} else {
modelFile.seek(0);
retval = load(modelFile);
}
return retval;
}
Common::SeekableReadStream *TeModel::tryLoadZlibStream(Common::SeekableReadStream &stream) {
byte version = stream.readByte();
if (version != 1) {
warning("[TeModel::load] invalid version number %d (expect 1)", version);
return nullptr;
}
uint32 compressedSize = stream.readUint32LE();
if (compressedSize > stream.size()) {
warning("[TeModel::load] invalid size %d (file size %d)", compressedSize, (int)stream.size());
return nullptr;
}
uint32 uncompressedSize = stream.readUint32LE();
Common::SeekableSubReadStream *substream = new Common::SeekableSubReadStream(&stream, stream.pos(), stream.size());
return Common::wrapCompressedReadStream(substream, DisposeAfterUse::YES, uncompressedSize);
}
bool TeModel::loadWeights(Common::ReadStream &stream, Common::Array &weights) {
uint32 nweights = stream.readUint32LE();
if (nweights > 100000)
error("Improbable number of weights %d", (int)nweights);
weights.resize(nweights);
for (uint i = 0; i < nweights; i++) {
weights[i]._weight = stream.readFloatLE();
weights[i]._x = stream.readUint16LE();
stream.readUint16LE();
}
return true;
}
bool TeModel::loadMesh(Common::SeekableReadStream &stream, TeMesh &mesh) {
if (!loadAndCheckFourCC(stream, "MESH"))
return false;
uint32 vertcount = stream.readUint32LE();
uint32 matcount = stream.readUint32LE();
uint32 matidxcount = stream.readUint32LE();
uint32 idxcount = stream.readUint32LE();
if (vertcount > 100000 || matcount > 100000 || matidxcount > 100000 || idxcount > 100000)
error("Improbable mesh sizes %d %d %d %d", vertcount, matcount, matidxcount, idxcount);
mesh.setConf(vertcount, idxcount, TeMesh::MeshMode_Triangles, matcount, matidxcount);
uint32 flags = stream.readUint32LE();
if (flags & 1)
mesh.setColor(0, TeColor(0xff, 0xff, 0xff, 0xff));
if (flags & 2)
mesh.setTextureUV(0, TeVector2f32(0.0f, 0.0f));
mesh.setName(Te3DObject2::deserializeString(stream));
loadAlign(stream);
if (!loadAndCheckFourCC(stream, "MTRL"))
return false;
for (uint i = 0; i < mesh.materials().size(); i++) {
TeMaterial mat;
TeMaterial::deserialize(stream, mat, _texturePath);
if (_enableLights)
mat._enableLights = true;
mesh.attachMaterial(i, mat);
}
if (!loadAndCheckFourCC(stream, "VERT"))
return false;
for (uint i = 0; i < mesh.numVerticies(); i++) {
TeVector3f32 v;
TeVector3f32::deserialize(stream, v);
mesh.setVertex(i, v);
}
if (mesh.hasUvs()) {
if (!loadAndCheckFourCC(stream, "TUVS"))
return false;
for (uint i = 0; i < mesh.numVerticies(); i++) {
TeVector2f32 v;
TeVector2f32::deserialize(stream, v);
mesh.setTextureUV(i, v);
}
}
if (!loadAndCheckFourCC(stream, "NORM"))
return false;
for (uint i = 0; i < mesh.numVerticies(); i++) {
TeVector3f32 v;
TeVector3f32::deserialize(stream, v);
mesh.setNormal(i, v);
}
if (mesh.hasColor()) {
if (!loadAndCheckFourCC(stream, "COLS"))
return false;
for (uint i = 0; i < mesh.numVerticies(); i++) {
TeColor c;
c.deserialize(stream);
mesh.setColor(i, c);
}
}
if (!loadAndCheckFourCC(stream, "FCPM"))
return false;
for (uint i = 0; i < mesh.materials().size(); i++) {
mesh.facesPerMaterial(i, stream.readUint16LE());
}
loadAlign(stream);
if (!loadAndCheckFourCC(stream, "MTXI"))
return false;
for (uint i = 0; i < mesh.numVerticies(); i++) {
mesh.matrixIndex(i, stream.readUint16LE());
}
loadAlign(stream);
if (!loadAndCheckFourCC(stream, "IDXS"))
return false;
for (uint i = 0; i < mesh.numIndexes(); i++) {
mesh.setIndex(i, stream.readUint16LE());
}
loadAlign(stream);
return true;
}
void TeModel::setQuad(const TeIntrusivePtr &tex, const Common::Array &verts, const TeColor &col) {
_meshes.clear();
Common::SharedPtr mesh(TeMesh::makeInstance());
mesh->setConf(4, 4, TeMesh::MeshMode_TriangleStrip, 0, 0);
mesh->defaultMaterial(tex);
for (int i = 0; i < 2; i++) {
float f = (i == 0 ? 0.0f : 1.0f);
for (int j = 0; j < 2; j++) {
int index = i * 2 + j;
mesh->setVertex(index, verts[i * 2 + j]);
mesh->setTextureUV(index, TeVector2f32(f, (j == 0 ? 0.0f : 1.0f)));
mesh->setIndex(index, index);
if (col.a() != 0)
mesh->setColor(index, col);
}
}
const TeVector3f32 v1 = verts[1] - verts[0];
const TeVector3f32 v2 = verts[2] - verts[0];
TeVector3f32 v3 = TeVector3f32::crossProduct(v1, v2);
v3.normalize();
for (int i = 0; i < 4; i++) {
mesh->setNormal(i, v3);
}
_meshes.push_back(mesh);
}
void TeModel::setAnim(TeIntrusivePtr &anim, bool repeat) {
for (TeModel::BonesBlender *blender : _boneBlenders) {
delete blender;
}
_boneBlenders.clear();
anim->_repeatCount = (repeat ? -1 : 1);
_modelAnim = anim;
}
void TeModel::setVertexAnim(TeIntrusivePtr &anim, bool repeat) {
anim->_repeatCount = (repeat ? -1 : 1);
_modelVertexAnim = anim;
}
void TeModel::setVisibleByName(const Common::String &name, bool vis) {
for (auto &mesh : _meshes) {
if (mesh->name().contains(name)) {
mesh->setVisible(vis);
}
}
}
TeMatrix4x4 TeModel::skinOffset(uint boneno) const {
if (boneno >= _skinOffsets.size())
return TeMatrix4x4();
return _skinOffsets[boneno];
}
void TeModel::setMeshCount(uint count) {
assert(count < 100000);
while (_meshes.size() < count)
_meshes.push_back(Common::SharedPtr(TeMesh::makeInstance()));
if (_meshes.size() > count)
_meshes.resize(count);
}
TeModel::BonesBlender::BonesBlender(TeIntrusivePtr anim, float seconds) : _anim(anim), _seconds(seconds) {
_anim.setDeleteFn(&TeModelAnimation::deleteLaterStatic);
_timer.stop();
_timer.start();
}
float TeModel::BonesBlender::coef() {
float elapsed = (_timer.getTimeFromStart() / 1000000.0) / _seconds;
return MIN(elapsed, 1.0f);
}
} // end namespace Tetraedge