Files
2026-02-02 04:50:13 +01:00

1208 lines
33 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Spriter - 3D Renderer
*/
#include "tinsel/noir/spriter.h"
#include "tinsel/handle.h"
#include "tinsel/tinsel.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "common/rect.h"
#include "common/str.h"
#include "common/file.h"
#include "math/quat.h"
#if defined(USE_TINYGL)
#include "graphics/tinygl/tinygl.h"
#endif
namespace Tinsel {
#define PALETTE_COUNT 22
#define TEXTURE_COUNT 4
#define MERGE_VERTICES_OFFSET 0.1f // TODO: find the proper ratio, this one is perhaps too big?
static float ConvertAngle(uint32 angle) {
return ((float(angle & 0xfff) / 4095.0f) * 360.0f);
}
Spriter::Spriter() {
_textureGenerated = false;
_sequencesCount = 0;
_animId = 0;
_animSpeed = 0;
}
Spriter::~Spriter() {
#if defined(USE_TINYGL)
if (_textureGenerated) {
tglDeleteTextures(TEXTURE_COUNT, _texture);
}
#endif
}
const Math::Matrix4& Spriter::MatrixCurrent() const {
return _currentMatrix->top();
}
void Spriter::MatrixReset() {
_currentMatrix->clear();
Math::Matrix4 m;
_currentMatrix->push(m);
}
void Spriter::MatrixPop() {
_currentMatrix->pop();
}
void Spriter::MatrixPush() {
_currentMatrix->push(_currentMatrix->top());
}
void Spriter::MatrixTranslate(float x, float y, float z) {
Math::Vector3d v {x,y,z};
_currentMatrix->top().translate(v);
}
void Spriter::MatrixScale(float x, float y, float z) {
Math::Matrix4 m;
m.setValue(0, 0, x);
m.setValue(1, 1, y);
m.setValue(2, 2, z);
_currentMatrix->top() = _currentMatrix->top() * m;
}
void Spriter::MatrixRotateX(float angle) {
Math::Matrix4 &m = _currentMatrix->top();
m = m * Math::Quaternion::xAxis(angle).toMatrix();
}
void Spriter::MatrixRotateY(float angle) {
Math::Matrix4 &m = _currentMatrix->top();
m = m * Math::Quaternion::yAxis(angle).toMatrix();
}
void Spriter::MatrixRotateZ(float angle) {
Math::Matrix4 &m = _currentMatrix->top();
m = m * Math::Quaternion::zAxis(angle).toMatrix();
}
void Spriter::SetViewport(int ap) {
_view.viewport.ap = ap;
int ratio = ((_view.screenRect.right - _view.screenRect.left) << 12) / ap;
_view.viewport.width = ratio;
_view.viewport.height = ratio;
_view.viewport.ap = (ap + 1800) / 3600;
_view.viewport.rect = _view.viewRect;
}
void Spriter::Init(int width, int height) {
_currentMatrix = &_modelMatrix;
_meshShadow.resize(50);
_view.screenRect.left = 0;
_view.screenRect.top = 0;
_view.screenRect.right = width;
_view.screenRect.bottom = height;
_view.centerX = width / 2;
_view.centerY = height / 2;
_view.viewRect.left = -_view.centerX;
_view.viewRect.top = -_view.centerY;
_view.viewRect.right = _view.screenRect.right - _view.screenRect.left - _view.centerX - 1;
_view.viewRect.bottom = _view.screenRect.bottom - _view.screenRect.top - _view.centerY;
SetViewport(306030);
}
void Spriter::SetCamera(int rotX, int rotY, int rotZ, int posX, int posY, int posZ, int cameraAp) {
_view.position.set(posX * 0.01f, posY * 0.01f, posZ * 0.01f);
_view.rotation.set(ConvertAngle(rotX), ConvertAngle(rotY), ConvertAngle(rotZ));
SetViewport(cameraAp);
_modelIdle = true;
}
void Spriter::TransformSceneXYZ(int x, int y, int z, int& xOut, int& yOut) {
MatrixReset();
MatrixRotateX(_view.rotation.x());
MatrixRotateY(_view.rotation.y());
MatrixRotateZ(_view.rotation.z());
MatrixTranslate(-_view.position.x(), -_view.position.y(), -_view.position.z());
MatrixTranslate((float)x / 100.0f, (float)y / 100.0f, (float)z / 100.0f);
Math::Vector3d v(0,0,0);
MatrixCurrent().transform(&v, true);
// Apply the viewport
xOut = _view.centerX + v.x();
yOut = _view.centerY + v.y();
}
void Spriter::LoadH(const Common::String& modelName) {
Common::String filename = modelName + ".h";
Common::File f;
f.open(Common::Path(filename));
while(!f.eos()) {
Common::String line = f.readLine();
if (!line.hasPrefix("#define")) {
continue;
}
line.erase(0, 8); // remove "#define "
size_t underscorePos = line.findFirstOf('_');
AnimationInfo *anim = nullptr;
Common::String name = line.substr(0, underscorePos);
line.erase(0, name.size() + 1); // remove the underscore too
if (name.hasPrefix("SHADOW")) {
anim = &_animShadow;
} else {
for (Common::Array<AnimationInfo>::iterator it = _animMain.begin(); it != _animMain.end(); ++it) {
if (it->name.equals(name)) {
anim = it;
break;
}
}
if (anim == nullptr) {
AnimationInfo tempAnim {};
tempAnim.name = name;
_animMain.push_back(tempAnim);
anim = &_animMain.back();
}
}
size_t spacePos = line.findFirstOf(' ');
Common::String sub = line.substr(0, spacePos);
auto val = atoi(line.substr(spacePos).c_str());
if (sub.equals("MESH_NUM")) {
anim->meshNum = val;
} else if (sub.equals("SCALE_NUM")) {
anim->scaleNum = val;
} else if (sub.equals("TRANSLATE_NUM")) {
anim->translateNum = val;
} else if (sub.equals("ROTATE_NUM")) {
anim->rotateNum = val;
}
}
}
void Spriter::LoadGBL(const Common::String& modelName) {
Common::String filename = modelName + ".gbl";
Common::File f;
f.open(Common::Path(filename));
while(!f.eos()) {
Common::String line = f.readLine();
if (!line.hasPrefix("#define")) {
continue;
}
line.erase(0, 8); // remove "#define "
size_t underscorePos = line.findFirstOf('_');
AnimationInfo *anim = nullptr;
MeshInfo *mesh = nullptr;
Common::String name = line.substr(0, underscorePos);
line.erase(0, name.size() + 1); // remove the underscore too
uint meshId = 0;
if (name.hasPrefix("SHADOW")) {
anim = &_animShadow;
meshId = atoi(name.substr(6).c_str());
assert(meshId < _meshShadow.size());
mesh = &_meshShadow[meshId];
} else {
for (Common::Array<AnimationInfo>::iterator it = _animMain.begin(); it != _animMain.end(); ++it) {
if (it->name.equals(name)) {
anim = it;
break;
}
}
mesh = &_meshMain;
}
assert(anim != nullptr);
assert(mesh != nullptr);
size_t spacePos = line.findFirstOf(' ');
Common::String sub = line.substr(0, spacePos);
auto val = atoi(line.substr(spacePos).c_str());
if (sub.equals("MESH_TABLES")) {
mesh->meshTables = val;
} else if (sub.equals("MESH_TABLES_hunk")) {
mesh->meshTablesHunk = val;
} else if (sub.equals("RENDER_PROGRAM")) {
mesh->program = val;
} else if (sub.equals("RENDER_PROGRAM_hunk")) {
mesh->programHunk = val;
} else if (sub.equals("TRANSLATE_TABLES")) {
anim->translateTables = val;
} else if (sub.equals("TRANSLATE_TABLES_hunk")) {
anim->translateTablesHunk = val;
} else if (sub.equals("ROTATE_TABLES")) {
anim->rotateTables = val;
} else if (sub.equals("ROTATE_TABLES_hunk")) {
anim->rotateTablesHunk = val;
} else if (sub.equals("SCALE_TABLES")) {
anim->scaleTables = val;
} else if (sub.equals("SCALE_TABLES_hunk")) {
anim->scaleTablesHunk = val;
}
}
}
#define kPIFF 0x46464950
#define kRBHF 0x46484252
#define kRBHH 0x48484252
#define kBODY 0x59444f42
#define kRELC 0x434c4552
void Spriter::LoadRBH(const Common::String& modelName, Hunks& hunks) {
Common::String filename = modelName + ".rbh";
Common::File f;
f.open(Common::Path(filename));
uint tag = f.readUint32LE();
assert(tag == kPIFF);
uint fileSize = f.readUint32LE();
tag = f.readUint32LE();
assert(tag == kRBHF);
tag = f.readUint32LE();
assert(tag == kRBHH);
uint headerSize = f.readUint32LE();
uint entriesCount = headerSize / 12;
hunks.resize(entriesCount);
for (Hunks::iterator it = hunks.begin(); it != hunks.end(); ++it) {
f.skip(4); // pointer to data
it->size = f.readUint32LE();
it->flags = f.readUint16LE();
f.skip(2); // padding
it->data.resize(it->size);
}
uint entryIdx = 0;
while (f.pos() < fileSize) {
tag = f.readUint32LE();
uint size = f.readUint32LE();
if (tag == kBODY) {
f.read(hunks[entryIdx].data.data(), size);
++entryIdx;
} else if (tag == kRELC) {
uint srcIdx = f.readUint32LE();
uint dstIdx = f.readUint32LE();
f.skip(size - 8); // ScummVM's implementation does not need to read the offsets for relocation
hunks[srcIdx].mappingIdx.push_back(dstIdx);
} else {
assert(false);
}
}
}
void Spriter::LoadVMC(const Common::String& textureName) {
Common::String filename = textureName + ".vmc";
Common::File f;
f.open(Common::Path(filename));
int16 buffer[4];
_textureData.resize(4 * 256 * 256);
while (true) {
buffer[0] = f.readSint16LE();
buffer[1] = f.readSint16LE();
buffer[2] = f.readSint16LE();
buffer[3] = f.readSint16LE();
if ((buffer[3] | buffer[0] | buffer[2] | buffer[1]) == 0) break;
int a = buffer[1];
int size1 = buffer[2];
int b = buffer[0];
int size2 = buffer[3];
int size = size2 * size1 * 2;
uint texId = (((a >> 8) & 0xfffU) * 16 + ((b >> 7) & 0xffffU)) & 0xffff;
if (texId > 3) {
return;
}
uint aAdj = a & 0xff;
if (a < 0) {
aAdj = -(-a & 0xffU);
}
uint bAdj = b & 0x7f;
if (b < 0) {
bAdj = -(-b & 0x7fU);
}
uint pos = ((((aAdj & 0x1ff) * 128 + (bAdj & 0xffff)) & 0xffff) * 2) + (texId * 65536);
f.read(_textureData.data() + pos, size);
}
UpdateTextures();
}
void Spriter::UpdateTextures() {
#if defined(USE_TINYGL)
if (_textureGenerated) {
tglDeleteTextures(TEXTURE_COUNT, _texture);
}
tglGenTextures(TEXTURE_COUNT, _texture);
Common::Array<uint8> tex(256*256*3);
bool hasPalette = _palette.size() > 0;
for (uint i = 0; i < TEXTURE_COUNT; ++i) {
for (uint j = 0; j < 256 * 256; ++j) {
uint32 index = _textureData[(i * 65536) + j];
if (hasPalette) {
tex[(j * 3) + 0] = _palette[(index * 3) + 0];
tex[(j * 3) + 1] = _palette[(index * 3) + 1];
tex[(j * 3) + 2] = _palette[(index * 3) + 2];
} else {
tex[(j * 3) + 0] = index;
tex[(j * 3) + 1] = index;
tex[(j * 3) + 2] = index;
}
}
tglBindTexture(TGL_TEXTURE_2D, _texture[i]);
tglTexImage2D(TGL_TEXTURE_2D, 0, TGL_RGB, 256, 256, 0, TGL_RGB, TGL_UNSIGNED_BYTE, tex.data());
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_S, TGL_REPEAT);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_T, TGL_REPEAT);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MAG_FILTER, TGL_NEAREST);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MIN_FILTER, TGL_NEAREST);
tglBindTexture(TGL_TEXTURE_2D, 0);
}
#endif
_textureGenerated = true;
}
void Spriter::SetPalette(SCNHANDLE hPalette) {
const
uint32 paletteHeaderSize = 2 + 2;
uint32 paletteBodySize = PALETTE_COUNT * 256 * sizeof(uint16);
/* Select only one palette, as they are sorted by light intensity */
uint32 paletteId = PALETTE_COUNT - 1;
Graphics::PixelFormat paletteFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
Common::MemoryReadStream s(_vm->_handle->LockMem(hPalette), paletteHeaderSize + paletteBodySize);
uint zero = s.readUint16LE();
uint size = s.readUint16LE();
if (zero == 0 && size == paletteBodySize) {
_palette.resize(256 * 3);
s.skip(paletteId * sizeof(uint16) * 256);
for (uint i = 0; i < 256; ++i) {
uint16 color = s.readUint16LE();
uint8 r, g, b;
paletteFormat.colorToRGB(color, r, g, b);
_palette[(i * 3) + 0] = r;
_palette[(i * 3) + 1] = g;
_palette[(i * 3) + 2] = b;
}
UpdateTextures();
} else {
warning("unknown palette data");
}
}
void Spriter::SetSequence(uint animId, uint delay) {
assert(animId < _animMain.size());
_sequencesCount = _sequencesCount + 1;
_animId = animId;
if ((!_modelIdle) && (delay != 0)) {
_modelMain.startFrame = -1;
_modelMain.time = 0;
_animDelay = delay;
_animDelayMax = delay;
if (!SetEndFrame(_modelMain, _animMain[animId], 0)) {
error("Spr_SetSeq: Could not set end frame");
}
} else {
if (!SetStartFrame(_modelMain, _animMain[animId], 0)) {
error("Spr_SetSeq: Could not set start frame");
}
if (!SetEndFrame(_modelMain, _animMain[animId], _animMain[animId].maxFrame != 0)) {
error("Spr_SetSeq: Could not set end frame");
}
}
}
Common::Rect Spriter::Draw(int direction, int x, int y, int z, int tDelta) {
if (_modelIdle) {
_modelIdle = false;
_direction = direction;
}
_modelMain.position.set((float)x * 0.01f, (float)y * 0.01f, (float)z * 0.01f);
_modelMain.scale.set(1.0f, 1.0f, 1.0f);
if ((_animMain[_animId].maxFrame < 2) && (_modelMain.startFrame != -1)) {
_modelMain.time = 0;
if (!SetStartFrame(_modelMain, _animMain[_animId], _modelMain.endFrame)) {
error("Spr_Step: Could not set start frame");
}
} else {
if (_animDelay == 0) {
_modelMain.time += tDelta;
} else {
_animDelay--;
_modelMain.time += (uint)tDelta / _animDelayMax;
}
while (_modelMain.time > 0xFFFF) {
_modelMain.time -= 0x10000;
if (!SetStartFrame(_modelMain, _animMain[_animId], _modelMain.endFrame)) {
error("Spr_Step: Could not set start frame");
}
_modelMain.endFrame++;
if ((int)_animMain[_animId].maxFrame < _modelMain.endFrame) {
_sequencesCount++;
_modelMain.endFrame = 0;
}
}
}
// TODO: Do a gradual direction change when the model is idle - Noir is using 5 steps.
_modelMain.rotation.set(0, ConvertAngle(direction), 0);
RenderModel(_modelMain);
// TODO: Add the shadow rendering after the lights are implemented
// int shadowId = 3;
// if (_animId == 2) {
// shadowId = ((_modelMain.endFrame + 17) % 18) + 1;
// }
// _modelShadow.program = _modelShadow.hunks[_meshShadow[shadowId].programHunk].data.data() + _meshShadow[shadowId].program;
// _modelShadow.tables.meshes = LoadMeshes(_modelShadow.hunks, _meshShadow[shadowId].meshTablesHunk, _meshShadow[shadowId].meshTables, 1);
// float dx = _modelMain.position.x() - _modelMain.lightPosition[0].x;
// float dz = _modelMain.position.z() - _modelMain.lightPosition[0].z;
// float dy = _modelMain.position.y() - _modelMain.lightPosition[0].y;
// float sheerx = dz * dz + dx * dx;
// float sheerz = 0.0;
// if (dy * dy < sheerx) {
// dy = 1.0f / sqrt(sheerx);
// sheerx = dx * dy;
// sheerz = dy * dz;
// } else {
// sheerx = 0.0f;
// sheerz = 0.0f;
// if (0.0f != dy) {
// sheerx = dx * (1.0f / dy);
// sheerz = (1.0f / dy) * dz;
// }
// }
// _modelShadow.scale.set(
// abs(sheerx) + 1.0f,
// _modelMain.scale.y(),
// abs(sheerz) + 1.0f
// );
// _modelShadow.position.set(
// _modelMain.position.x() + sheerx * 10.0f,
// 0,
// _modelMain.position.z() + sheerz * 10.0f
// );
// _modelShadow.rotation.set(
// _modelMain.rotation.x(),
// _modelMain.rotation.y() + 180.0f,
// _modelMain.rotation.z()
// );
// RenderModel(_modelShadow);
return Common::Rect {0, 0};
}
Meshes Spriter::LoadMeshes(const Hunks &hunks, uint hunk, uint offset, int frame) {
assert(hunk < hunks.size());
Common::MemoryReadStream framesStream(hunks[hunk].data.data(), hunks[hunk].data.size());
framesStream.skip(offset);
uint numFrames = framesStream.readUint16LE();
uint numEntries = framesStream.readUint16LE();
assert(frame < (int)numFrames);
framesStream.skip(frame * 4);
uint meshListOffset = framesStream.readUint32LE();
uint meshListHunk = hunks[hunk].mappingIdx[0];
Common::MemoryReadStream meshListStream(hunks[meshListHunk].data.data(), hunks[meshListHunk].data.size());
meshListStream.skip(meshListOffset);
Meshes result;
result.vertexCount = meshListStream.readUint32LE();
result.normalCount = meshListStream.readUint32LE();
result.meshes.resize(numEntries);
// Read all meshes
for (auto& mesh : result.meshes) {
uint meshOffset = meshListStream.readUint32LE();
uint meshHunk = hunks[meshListHunk].mappingIdx[0];
Common::MemoryReadStream meshStream(hunks[meshHunk].data.data(), hunks[meshHunk].data.size());
meshStream.skip(meshOffset);
// Read vertices
uint verticesOffset = meshStream.readUint32LE();
uint verticesHunk = hunks[meshHunk].mappingIdx[1];
mesh.vertices.resize(meshStream.readUint32LE());
Common::MemoryReadStream verticesStream(hunks[verticesHunk].data.data(), hunks[verticesHunk].data.size());
verticesStream.skip(verticesOffset);
for (auto& v : mesh.vertices) {
v.readFromStream(&verticesStream);
}
// Read normals
uint normalsOffset = meshStream.readUint32LE();
uint normalsHunk = hunks[meshHunk].mappingIdx[1];
mesh.normals.resize(meshStream.readUint32LE());
Common::MemoryReadStream normalsStream(hunks[normalsHunk].data.data(), hunks[normalsHunk].data.size());
normalsStream.skip(normalsOffset);
for (auto& n : mesh.normals) {
n.readFromStream(&normalsStream);
}
// Read primitives
uint primitivesOffset = meshStream.readUint32LE();
uint primitivesHunk = hunks[meshHunk].mappingIdx[0];
Common::MemoryReadStream primitivesStream(hunks[primitivesHunk].data.data(), hunks[primitivesHunk].data.size());
primitivesStream.skip(primitivesOffset);
while (true) {
uint primitiveCount = primitivesStream.readUint16LE();
uint primitiveType = primitivesStream.readUint16LE();
uint dataSize = primitivesStream.readUint32LE();
if (primitiveCount == 0) {
break;
}
MeshPart part;
part.numVertices = (primitiveType & 1) ? 4 : 3;
part.type = static_cast<MeshPartType>((primitiveType & 0x7f) >> 1);
part.cull = primitiveType & 0x80;
part.primitives.resize(primitiveCount);
int64 start = primitivesStream.pos();
for (auto& prim : part.primitives) {
for (uint i = 0; i < 8; ++i) {
prim.indices[i] = primitivesStream.readUint16LE();
}
switch (part.type) {
case MESH_PART_TYPE_COLOR:
prim.color = primitivesStream.readUint32LE();
break;
case MESH_PART_TYPE_SOLID:
assert(false); // TODO: not used? maybe in overlay model?
break;
case MESH_PART_TYPE_TEXTURE:
// Has texture
for (uint i = 0; i < part.numVertices; ++i) {
prim.uv[i].readFromStream(&primitivesStream);
}
prim.texture = primitivesStream.readUint16LE();
primitivesStream.skip(2); //padding
break;
}
}
int64 end = primitivesStream.pos();
assert(dataSize == end - start);
mesh.parts.push_back(part);
}
}
return result;
}
template<bool convert>
AnimationData Spriter::LoadAnimationData(const Hunks& hunks, uint hunk, uint offset) {
assert(hunk < hunks.size());
Common::MemoryReadStream framesStream(hunks[hunk].data.data(), hunks[hunk].data.size());
framesStream.skip(offset);
uint numFrames = framesStream.readUint16LE();
uint numEntries = framesStream.readUint16LE();
AnimationData result;
result.resize(numFrames);
uint vectorsHunk = hunks[hunk].mappingIdx[0];
assert(vectorsHunk < hunks.size());
for (uint frame = 0; frame < numFrames; frame++) {
auto& vectors = result[frame];
uint vectorsOffset = framesStream.readUint32LE();
Common::MemoryReadStream vectorsStream(hunks[vectorsHunk].data.data(), hunks[vectorsHunk].data.size());
vectorsStream.skip(vectorsOffset);
vectors.resize(numEntries);
for (auto& v : vectors) {
if (convert) {
uint32 x = vectorsStream.readUint32LE();
uint32 y = vectorsStream.readUint32LE();
uint32 z = vectorsStream.readUint32LE();
v.set(ConvertAngle(x), ConvertAngle(y), ConvertAngle(z));
} else {
v.readFromStream(&vectorsStream);
}
}
}
return result;
}
void Spriter::InitModel(Model &model, MeshInfo &meshInfo, Common::Array<AnimationInfo> &animInfos, uint flags) {
model.flags = flags;
model.program = model.hunks[meshInfo.programHunk].data.data() + meshInfo.program;
AnimationInfo &animInfo = animInfos[0];
model.tables.meshes = LoadMeshes(model.hunks, meshInfo.meshTablesHunk, meshInfo.meshTables, 0);
model.tables.translations = LoadAnimationData<false>(model.hunks, animInfo.translateTablesHunk, animInfo.translateTables)[0];
model.tables.rotations = LoadAnimationData<true>(model.hunks, animInfo.rotateTablesHunk, animInfo.rotateTables)[0];
model.tables.scales = LoadAnimationData<false>(model.hunks, animInfo.scaleTablesHunk, animInfo.scaleTables)[0];
model.position.set(0.0f, 0.0f, 0.0f);
model.rotation.set(0.0f, 0.0f, 0.0f);
model.scale.set(1.0f, 1.0f, 1.0f);
_currentMatrix = &_modelMatrix;
MatrixReset();
// Preprocess model - merge vertices that are close to each other
RunRenderProgram(model, true);
bool valid = true;
if (model.flags & MODEL_HAS_TRANSLATION_TABLE) {
for (const auto &anim : animInfos) {
if (anim.translateNum != animInfo.translateNum) {
valid = false;
}
}
model.tables.translations.clear();
model.tables.translations.resize(animInfo.translateNum);
}
assert(valid); // Warn if animation tables are incorrect
for (uint i = 0; i < model.animationCount; ++i) {
// TODO: check if animation data has same number of frames for all transformation types
// SetStartFrame(model, animInfos[i], 0);
}
}
void Spriter::RunRenderProgram(Model &model, bool preprocess) {
uint8* program = model.program;
uint ip = 0;
Vectors vertices;
vertices.reserve(model.tables.meshes.vertexCount);
Vectors normals;
normals.resize(model.tables.meshes.normalCount);
Common::Array<uint16> sameVertices;
sameVertices.resize(model.tables.meshes.vertexCount);
bool stop = false;
do {
RenderProgramOp opCode = (RenderProgramOp)READ_LE_UINT16(&program[ip]);
ip += 2;
switch(opCode) {
case MATRIX_DUPLICATE: {
MatrixPush();
break;
}
case MATRIX_REMOVE: {
MatrixPop();
break;
}
case UNUSED: {
// TODO: Check where is this used. In some model overlay?
// uint16 entry = READ_LE_UINT16(&program[ip]);
ip += 2;
break;
}
case TRANSFORM: {
uint16 entry = READ_LE_UINT16(&program[ip]);
Mesh& mesh = model.tables.meshes.meshes[entry];
ip += 2;
if (preprocess) {
FindSimilarVertices(mesh, vertices, sameVertices);
MergeVertices(mesh, sameVertices);
} else {
TransformMesh(mesh, vertices);
CalculateNormals(mesh, vertices, normals);
RenderMesh(mesh, vertices, normals);
}
break;
}
case TRANSLATE_X: {
uint16 entry = READ_LE_UINT16(&program[ip]);
ip += 2;
Math::Vector3d& v = model.tables.translations[entry];
MatrixTranslate(v.x(), 0, 0);
break;
}
case TRANSLATE_Y: {
uint16 entry = READ_LE_UINT16(&program[ip]);
ip += 2;
Math::Vector3d& v = model.tables.translations[entry];
MatrixTranslate(0, v.y(), 0);
break;
}
case TRANSLATE_Z: {
uint16 entry = READ_LE_UINT16(&program[ip]);
ip += 2;
Math::Vector3d& v = model.tables.translations[entry];
MatrixTranslate(0, 0, v.z());
break;
}
case TRANSLATE_XYZ: {
uint16 entry = READ_LE_UINT16(&program[ip]);
ip += 2;
Math::Vector3d& v = model.tables.translations[entry];
MatrixTranslate(v.x(), v.y(), v.z());
break;
}
case ROTATE_X: {
uint16 entry = READ_LE_UINT16(&program[ip]);
ip += 2;
Math::Vector3d& v = model.tables.rotations[entry];
MatrixRotateX(v.x());
break;
}
case ROTATE_Y: {
uint16 entry = READ_LE_UINT16(&program[ip]);
ip += 2;
Math::Vector3d& v = model.tables.rotations[entry];
MatrixRotateY(v.y());
break;
}
case ROTATE_Z: {
uint16 entry = READ_LE_UINT16(&program[ip]);
ip += 2;
Math::Vector3d& v = model.tables.rotations[entry];
MatrixRotateZ(v.z());
break;
}
case STOP: {
stop = true;
break;
}
case ROTATE_XYZ: {
uint entry = READ_LE_UINT16(&program[ip]);
ip += 2;
Math::Vector3d& v = model.tables.rotations[entry];
MatrixRotateX(v.x());
MatrixRotateY(v.y());
MatrixRotateZ(v.z());
break;
}
default:
{
error("UNKNOWN RENDER OP %i\n", opCode);
}
}
} while (!stop);
}
void Spriter::FindSimilarVertices(Mesh& mesh, Vectors& vertices, Common::Array<uint16>& sameVertices) const {
const Math::Matrix4 &m = MatrixCurrent();
uint i_start = vertices.size();
for (uint i = 0; i < mesh.vertices.size(); ++i) {
Math::Vector3d& vIn = mesh.vertices[i];
Math::Vector3d vOut = vIn;
m.transform(&vOut, true);
vertices.push_back(vOut);
for (uint j = 0; j < vertices.size() - 1; ++j) {
float d = vOut.getDistanceTo(vertices[j]);
if (d < MERGE_VERTICES_OFFSET) {
sameVertices[i_start + i] = j + 1; // 0 is reserved for "not found"
break;
}
}
}
}
void Spriter::MergeVertices(Mesh &mesh, Common::Array<uint16>& sameVertices) {
for (auto& part : mesh.parts) {
for (auto& prim : part.primitives) {
for (uint i = 0; i < part.numVertices; ++i) {
if (sameVertices[prim.indices[i]] != 0) {
prim.indices[i] = sameVertices[prim.indices[i]] - 1;
}
}
}
}
}
void Spriter::TransformMesh(Mesh& mesh, Vectors& vertices) {
// Transformed vertices from previous meshes might be used by the current mesh.
const Math::Matrix4 &m = MatrixCurrent();
for (auto& vIn : mesh.vertices) {
Math::Vector3d vOut = vIn;
m.transform(&vOut, true);
vertices.push_back(vOut);
}
}
void Spriter::CalculateNormals(Mesh& mesh, Vectors& vertices, Vectors &normals) {
for (auto& part : mesh.parts) {
for (auto& prim : part.primitives) {
Math::Vector3d v0 = vertices[prim.indices[0]];
Math::Vector3d v1 = vertices[prim.indices[1]];
Math::Vector3d v2 = vertices[prim.indices[2]];
Math::Vector3d norm = Math::Vector3d::crossProduct(v2 - v0, v1 - v0).getNormalized();
for (uint i = 0; i < part.numVertices; ++i) {
normals[prim.indices[i]] += norm;
}
}
}
}
void Spriter::RenderMesh(Mesh& mesh, Vectors& vertices, Vectors &normals) {
for(auto& part : mesh.parts) {
switch(part.type) {
case MESH_PART_TYPE_COLOR:
RenderMeshPartColor(part, vertices, normals);
break;
case MESH_PART_TYPE_SOLID:
// TODO: Check where is this used. In some model overlay?
// This just uses white color
// RenderMeshPartColor(part, vertices, normals);
break;
case MESH_PART_TYPE_TEXTURE:
RenderMeshPartTexture(part, vertices, normals);
break;
}
}
return;
}
void Spriter::RenderMeshPartColor(MeshPart& part, Vectors& vertices, Vectors &normals) {
#if defined(USE_TINYGL)
if(!part.cull) {
tglEnable(TGL_CULL_FACE);
}
for (auto& prim : part.primitives) {
TGLubyte r = (prim.color >> 0) & 0xff;
TGLubyte g = (prim.color >> 8) & 0xff;
TGLubyte b = (prim.color >> 16) & 0xff;
tglColor4ub(r, g, b, 255);
if (part.numVertices == 4) {
tglBegin(TGL_QUADS);
} else {
tglBegin(TGL_TRIANGLES);
}
for (uint32 i = 0; i < part.numVertices; ++i) {
uint32 index = prim.indices[i];
Math::Vector3d n = normals[index].getNormalized();
tglNormal3f(n.x(), n.y(), n.z());
Math::Vector3d& v = vertices[index];
tglVertex3f(v.x(), v.y(), v.z());
}
tglEnd();
}
tglDisable(TGL_CULL_FACE);
#endif
}
void Spriter::RenderMeshPartTexture(MeshPart& part, Vectors& vertices, Vectors &normals) {
#if defined(USE_TINYGL)
if (!part.cull) {
tglEnable(TGL_CULL_FACE);
}
tglEnable(TGL_TEXTURE_2D);
tglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
for (auto& prim : part.primitives) {
tglBindTexture(TGL_TEXTURE_2D, _texture[prim.texture]);
if (part.numVertices == 4) {
tglBegin(TGL_QUADS);
} else {
tglBegin(TGL_TRIANGLES);
}
for (uint32 i = 0; i < part.numVertices; ++i) {
uint index = prim.indices[i];
tglTexCoord2f(prim.uv[i].getX() / 256.0f, prim.uv[i].getY() / 256.0f);
Math::Vector3d n = normals[index].getNormalized();
tglNormal3f(n.x(), n.y(), n.z());
Math::Vector3d& v = vertices[index];
tglVertex3f(v.x(), v.y(), v.z());
}
tglEnd();
}
tglDisable(TGL_TEXTURE_2D);
tglDisable(TGL_CULL_FACE);
#endif
}
void Spriter::Load(const Common::String &modelName, const Common::String &textureName) {
LoadH(modelName);
LoadGBL(modelName);
LoadRBH(modelName, _modelMain.hunks);
LoadVMC(modelName);
InitModel(_modelMain, _meshMain, _animMain, MODEL_HAS_TRANSLATION_TABLE | MODEL_HAS_ROTATION_TABLE);
// TODO: Check where is this used. In some model overlay?
// for (uint i = 0; i < _animMain.size(); ++i) { }
_modelIdle = true;
_modelMain.time = 0;
}
void lerp3(Vectors &dst, const Vectors &src1, const Vectors &src2, uint t) {
assert(dst.size() == src1.size() && src1.size() == src2.size());
float interpolator = static_cast<float>(t) / 65536.0f;
float interpolatorInv = 1.0f - interpolator;
for (uint i = 0; i < dst.size(); ++i) {
dst[i] = src1[i] * interpolator + src2[i] * interpolatorInv;
}
}
void Spriter::RenderModel(Model &model) {
if (model.flags & MODEL_HAS_TRANSLATION_TABLE) {
if (model.startFrame == -1) {
lerp3(model.tables.translations, model.tables.translations, model.endTranslateTables[model.endFrame], model.time);
} else {
lerp3(model.tables.translations, model.startTranslateTables[model.startFrame], model.endTranslateTables[model.endFrame], model.time);
}
}
if (model.flags & MODEL_HAS_ROTATION_TABLE) {
if (model.startFrame == -1) {
lerp3(model.tables.rotations, model.tables.rotations, model.endRotateTables[model.endFrame], model.time);
} else {
lerp3(model.tables.rotations, model.startRotateTables[model.startFrame], model.endRotateTables[model.endFrame], model.time);
}
}
if (model.flags & MODEL_HAS_SCALE_TABLE) {
if (model.startFrame == -1) {
lerp3(model.tables.scales, model.tables.scales, model.endScaleTables[model.endFrame], model.time);
} else {
lerp3(model.tables.scales, model.startScaleTables[model.startFrame], model.endScaleTables[model.endFrame], model.time);
}
}
MatrixReset();
MatrixTranslate(model.position.x(), model.position.y(), model.position.z());
MatrixScale(model.scale.x(), model.scale.y(), model.scale.z());
MatrixRotateX(model.rotation.x());
MatrixRotateY(model.rotation.y());
MatrixRotateZ(model.rotation.z());
MatrixRotateX(_view.rotation.x());
MatrixRotateY(_view.rotation.y());
MatrixRotateZ(_view.rotation.z());
MatrixTranslate(-_view.position.x(), -_view.position.y(), -_view.position.z());
#if defined(USE_TINYGL)
tglViewport(0, 0, _vm->screen().w, _vm->screen().h);
tglMatrixMode(TGL_PROJECTION);
tglLoadIdentity();
tglFrustum(-1.0f, 1.0f, -3.0f / 4.0f, 3.0f / 4.0f, 1.0f, 1000.0f);
// GL uses bottom left system
tglScalef(1.0f, -1.0f, 1.0f);
// Z is inverted in GL, we need to invert the face orientation too.
tglScalef(1.0f, 1.0f, -1.0f);
tglFrontFace(TGL_CW);
tglMatrixMode(TGL_MODELVIEW);
tglLoadIdentity();
// TODO: Use GL's view matrix instead of the game one.
// tglRotatef(_view.rotation.x(), 1.0f, 0.0f, 0.0f);
// tglRotatef(_view.rotation.y(), 0.0f, 1.0f, 0.0f);
// tglRotatef(_view.rotation.z(), 0.0f, 0.0f, 1.0f);
// tglTranslatef(-_view.position.x(), -_view.position.y(), -_view.position.z());
tglEnable(TGL_DEPTH_TEST);
tglDepthFunc(TGL_LESS);
tglDepthMask(TGL_TRUE);
tglClearDepth(1.0f);
tglShadeModel(TGL_SMOOTH);
tglClear(TGL_DEPTH_BUFFER_BIT);
#if 0
// TODO: Remove. Used only for debugging of the normals calculation and rendering.
tglEnable(TGL_LIGHTING);
TGLfloat light_position[] = { 0.0, 0.0, 0.0, 1.0 };
tglLightfv(TGL_LIGHT0, TGL_POSITION, light_position);
tglLightf(TGL_LIGHT0, TGL_CONSTANT_ATTENUATION, 1000.0f);
tglEnable(TGL_LIGHT0);
#endif
#endif
RunRenderProgram(model, false);
#if defined(USE_TINYGL)
TinyGL::presentBuffer();
#endif
}
bool Spriter::SetStartFrame(Model &model, const AnimationInfo &anim, int frame) {
const Hunks &hunks = model.hunks;
uint numFrames = 0;
if ((model.flags & MODEL_HAS_TRANSLATION_TABLE) != 0) {
model.startTranslateTables = LoadAnimationData<false>(hunks, anim.translateTablesHunk, anim.translateTables);
numFrames = model.startTranslateTables.size();
}
if ((model.flags & MODEL_HAS_ROTATION_TABLE) != 0) {
model.startRotateTables = LoadAnimationData<true>(hunks, anim.rotateTablesHunk, anim.rotateTables);
numFrames = model.startRotateTables.size();
}
if ((model.flags & MODEL_HAS_SCALE_TABLE) != 0) {
model.startScaleTables = LoadAnimationData<false>(hunks, anim.scaleTablesHunk, anim.scaleTables);
numFrames = model.startScaleTables.size();
}
if (frame < 0 || frame >= (int)numFrames) {
return false;
}
model.startFrame = frame;
return true;
}
bool Spriter::SetEndFrame(Model &model, const AnimationInfo &anim, int frame) {
const Hunks &hunks = model.hunks;
uint numFrames = 0;
if ((model.flags & MODEL_HAS_TRANSLATION_TABLE) != 0) {
model.endTranslateTables = LoadAnimationData<false>(hunks, anim.translateTablesHunk, anim.translateTables);
numFrames = model.endTranslateTables.size();
}
if ((model.flags & MODEL_HAS_ROTATION_TABLE) != 0) {
model.endRotateTables = LoadAnimationData<true>(hunks, anim.rotateTablesHunk, anim.rotateTables);
numFrames = model.endRotateTables.size();
}
if ((model.flags & MODEL_HAS_SCALE_TABLE) != 0) {
model.endScaleTables = LoadAnimationData<false>(hunks, anim.scaleTablesHunk, anim.scaleTables);
numFrames = model.endScaleTables.size();
}
if (frame < 0 || frame >= (int)numFrames) {
return false;
}
model.endFrame = frame;
return true;
}
} // End of namespace Tinsel