chore: initial commit

This commit is contained in:
fallenoak 2023-01-02 13:17:18 -06:00
commit 70b00c5c38
No known key found for this signature in database
GPG key ID: 7628F8E61AEA070D
965 changed files with 264882 additions and 0 deletions

94
src/model/CM2Cache.cpp Normal file
View file

@ -0,0 +1,94 @@
#include "model/CM2Cache.hpp"
#include "gx/Gx.hpp"
#include "model/CM2Shared.hpp"
#include "model/Model2.hpp"
#include "util/Filesystem.hpp"
#include "util/SFile.hpp"
#include <cstring>
#include <new>
#include <storm/Memory.hpp>
#include <storm/String.hpp>
#include <tempest/Box.hpp>
CM2Cache CM2Cache::s_cache;
void CM2Cache::BeginThread(void (*callback)(void*), void* arg) {
// TODO
}
CM2Shared* CM2Cache::CreateShared(const char* path, uint32_t flags) {
char convertedPath[STORM_MAX_PATH];
if (!M2ConvertModelFileName(path, convertedPath, STORM_MAX_PATH, flags)) {
return nullptr;
}
char* ext = OsPathFindExtensionWithDot(convertedPath);
CAaBox v28;
ModelBlobQuery(convertedPath, v28.b, v28.t);
if (ext) {
*ext = '.';
}
// TODO
SFile* fileptr;
if (SFile::OpenEx(nullptr, convertedPath, (flags >> 2) & 1, &fileptr)) {
void* m = SMemAlloc(sizeof(CM2Shared), __FILE__, __LINE__, 0x0);
CM2Shared* shared = new (m) CM2Shared(this);
if (shared->Load(fileptr, flags & 0x4, &v28)) {
strcpy(shared->m_filePath, convertedPath);
shared->ext = strrchr(shared->m_filePath, '.');;
if (shared->ext > shared->m_filePath) {
// TODO
}
// TODO
return shared;
}
SFile::Close(fileptr);
delete shared;
}
return nullptr;
}
void CM2Cache::GarbageCollect(int32_t a2) {
// TODO
}
int32_t CM2Cache::Initialize(uint32_t flags) {
if (this->m_initialized) {
// TODO
return 1;
}
// TODO
if (flags & 0x8) {
if (GxCaps()->m_vertexShaderTarget > GxShVS_none && GxCaps()->m_pixelShaderTarget > GxShPS_none) {
this->m_flags |= 0x8;
}
}
// TODO
this->m_initialized = 1;
return 1;
}
void CM2Cache::UpdateShared() {
// TODO
}
void CM2Cache::WaitThread() {
// TODO
}

26
src/model/CM2Cache.hpp Normal file
View file

@ -0,0 +1,26 @@
#ifndef MODEL_C_M2_CACHE_HPP
#define MODEL_C_M2_CACHE_HPP
#include <cstdint>
class CM2Shared;
class CM2Cache {
public:
// Static variables
static CM2Cache s_cache;
// Member variables
uint32_t m_initialized = 0;
uint32_t m_flags = 0;
// Member functions
void BeginThread(void (*callback)(void*), void* arg);
CM2Shared* CreateShared(const char*, uint32_t);
void GarbageCollect(int32_t a2);
int32_t Initialize(uint32_t flags);
void UpdateShared(void);
void WaitThread(void);
};
#endif

78
src/model/CM2Light.cpp Normal file
View file

@ -0,0 +1,78 @@
#include "model/CM2Light.hpp"
#include "model/CM2Scene.hpp"
void CM2Light::Initialize(CM2Scene* scene) {
this->m_scene = scene;
// TODO
}
void CM2Light::Link() {
if (!this->m_visible || !this->m_scene) {
return;
}
if (this->m_type == M2LIGHT_1) {
// TODO
} else {
if (!(this->m_scene->m_flags & 0x1)) {
this->m_lightPrev = &this->m_scene->m_lightList;
this->m_lightNext = this->m_scene->m_lightList;
this->m_scene->m_lightList = this;
if (this->m_lightNext) {
this->m_lightNext->m_lightPrev = &this->m_lightNext;
}
}
}
}
void CM2Light::SetDirection(const C3Vector& dir) {
this->m_dir = dir;
if (this->m_dir.SquaredMag() > 0.00000023841858) {
this->m_dir.Normalize();
}
}
void CM2Light::SetLightType(M2LIGHTTYPE lightType) {
if (this->m_type == lightType) {
return;
}
this->m_type = lightType;
if (this->m_visible && this->m_scene) {
this->Unlink();
this->Link();
}
}
void CM2Light::SetVisible(int32_t visible) {
if (this->m_visible == visible) {
return;
}
this->m_visible = visible;
if (this->m_scene) {
if (this->m_visible) {
this->Link();
} else {
this->Unlink();
}
}
}
void CM2Light::Unlink() {
if (this->m_lightPrev) {
*this->m_lightPrev = this->m_lightNext;
}
if (this->m_lightNext) {
this->m_lightNext->m_lightPrev = this->m_lightPrev;
}
this->m_lightPrev = nullptr;
this->m_lightNext = nullptr;
}

35
src/model/CM2Light.hpp Normal file
View file

@ -0,0 +1,35 @@
#ifndef MODEL_C_M2_LIGHT_HPP
#define MODEL_C_M2_LIGHT_HPP
#include "model/M2Types.hpp"
#include <tempest/Vector.hpp>
class CM2Scene;
class CM2Light {
public:
// Member variables
CM2Scene* m_scene = nullptr;
int32_t m_type = 1;
C3Vector m_pos;
C3Vector m_dir;
C3Vector m_ambColor;
C3Vector m_dirColor;
C3Vector m_specColor;
float m_constantAttenuation = 0.0f;
float m_linearAttenuation = 0.69999999f;
float m_quadraticAttenuation = 0.029999999f;
int32_t m_visible = 0;
CM2Light** m_lightPrev = nullptr;
CM2Light* m_lightNext = nullptr;
// Member functions
void Initialize(CM2Scene* scene);
void Link();
void SetDirection(const C3Vector& dir);
void SetLightType(M2LIGHTTYPE lightType);
void SetVisible(int32_t visible);
void Unlink();
};
#endif

130
src/model/CM2Lighting.cpp Normal file
View file

@ -0,0 +1,130 @@
#include "model/CM2Lighting.hpp"
#include "model/CM2Light.hpp"
#include "model/CM2Scene.hpp"
#include <cstring>
void CM2Lighting::AddAmbient(const C3Vector& ambColor) {
this->m_sunAmbient = this->m_sunAmbient + ambColor;
}
void CM2Lighting::AddDiffuse(const C3Vector& dirColor, const C3Vector& dir) {
C3Vector viewDir = dir;
if (this->m_scene) {
viewDir = {
this->m_scene->m_view.a0 * dir.x + this->m_scene->m_view.b0 * dir.y + this->m_scene->m_view.c0 * dir.z,
this->m_scene->m_view.a1 * dir.x + this->m_scene->m_view.b1 * dir.y + this->m_scene->m_view.c1 * dir.z,
this->m_scene->m_view.a2 * dir.x + this->m_scene->m_view.b2 * dir.y + this->m_scene->m_view.c2 * dir.z
};
}
this->vector18.x = viewDir.x * dirColor.x + this->vector18.x;
this->vector18.y = viewDir.y * dirColor.x + this->vector18.y;
this->vector18.z = viewDir.z * dirColor.x + this->vector18.z;
this->vector24.x = viewDir.x * dirColor.y + this->vector24.x;
this->vector24.y = viewDir.y * dirColor.y + this->vector24.y;
this->vector24.z = viewDir.z * dirColor.y + this->vector24.z;
this->vector30.x = viewDir.x * dirColor.z + this->vector30.x;
this->vector30.y = viewDir.y * dirColor.z + this->vector30.y;
this->vector30.z = viewDir.z * dirColor.z + this->vector30.z;
float v7 = dirColor.y * 0.71516001f + dirColor.x * 0.212671f + dirColor.z * 0.072168998f;
this->vector3C.x = viewDir.x * v7 + this->vector3C.x;
this->vector3C.y = viewDir.y * v7 + this->vector3C.y;
this->vector3C.z = viewDir.z * v7 + this->vector3C.z;
this->vector48.x = dirColor.x + this->vector48.x;
this->vector48.y = dirColor.y + this->vector48.y;
this->vector48.z = dirColor.z + this->vector48.z;
this->m_sunDir = dir;
this->m_sunDiffuse = dirColor;
}
void CM2Lighting::AddLight(CM2Light* light) {
if (!light->m_visible) {
return;
}
if (light->m_type == 1) {
// TODO
} else {
this->AddAmbient(light->m_ambColor);
this->AddDiffuse(light->m_dirColor, light->m_dir);
this->AddSpecular(light->m_specColor);
}
}
void CM2Lighting::AddSpecular(const C3Vector& specColor) {
this->m_sunSpecular = this->m_sunSpecular + specColor;
}
void CM2Lighting::CameraSpace() {
// TODO
}
void CM2Lighting::Initialize(CM2Scene* scene, const CAaSphere& a3) {
memset(this, 0, sizeof(CM2Lighting));
this->m_scene = scene;
this->m_flags |= 0x20u;
this->sphere4 = a3;
}
void CM2Lighting::SetFog(const C3Vector& fogColor, float fogStart, float fogEnd) {
this->m_fogStart = fogStart;
this->m_fogEnd = fogEnd;
this->m_fogScale = 1.0f / (fogEnd - fogStart);
this->m_fogDensity = 1.0f;
this->m_fogColor = fogColor;
}
void CM2Lighting::SetFog(const C3Vector& fogColor, float fogStart, float fogEnd, float fogDensity) {
this->m_fogStart = fogStart;
this->m_fogEnd = fogEnd;
this->m_fogScale = 1.0f / (fogEnd - fogStart);
this->m_fogDensity = fogDensity;
this->m_fogColor = fogColor;
}
void CM2Lighting::SetupGxLights(const C3Vector* a2) {
// TODO
}
void CM2Lighting::SetupSunlight() {
if (this->m_flags & 0x2) {
return;
}
this->m_sunDir.x = this->vector3C.x;
this->m_sunDir.y = this->vector3C.y;
this->m_sunDir.z = this->vector3C.z;
if (this->m_sunDir.SquaredMag() <= 0.0000099999997) {
this->m_sunDir = { 0.0f, 0.0f, -1.0f };
} else {
this->m_sunDir.Normalize();
}
float v6 = this->m_sunDir.z * this->vector18.z + this->m_sunDir.y * this->vector18.y + this->m_sunDir.x * this->vector18.x;
float v7 = this->m_sunDir.z * this->vector24.z + this->m_sunDir.y * this->vector24.y + this->m_sunDir.x * this->vector24.x;
float v8 = this->m_sunDir.z * this->vector30.z + this->m_sunDir.y * this->vector30.y + this->m_sunDir.x * this->vector30.x;
this->m_sunDiffuse = {
v6 * 1.25f - this->vector48.x * 0.25f,
v7 * 1.25f - this->vector48.y * 0.25f,
v8 * 1.25f - this->vector48.z * 0.25f
};
this->m_sunAmbient = {
(this->vector48.x - v6) * 0.25f + this->m_sunAmbient.x,
(this->vector48.y - v7) * 0.25f + this->m_sunAmbient.y,
(this->vector48.z - v8) * 0.25f + this->m_sunAmbient.z
};
this->m_flags |= 0x2;
}

48
src/model/CM2Lighting.hpp Normal file
View file

@ -0,0 +1,48 @@
#ifndef MODEL_C_M2_LIGHTING_HPP
#define MODEL_C_M2_LIGHTING_HPP
#include <tempest/Plane.hpp>
#include <tempest/Sphere.hpp>
#include <tempest/Vector.hpp>
class CM2Light;
class CM2Scene;
class CM2Lighting {
public:
// Member variables
CM2Scene* m_scene;
CAaSphere sphere4;
uint32_t m_flags;
C3Vector vector18;
C3Vector vector24;
C3Vector vector30;
C3Vector vector3C;
C3Vector vector48;
C3Vector m_sunAmbient;
C3Vector m_sunDiffuse;
C3Vector m_sunSpecular;
C3Vector m_sunDir;
CM2Light* m_lights[4];
uint32_t m_lightCount;
float m_fogStart;
float m_fogEnd;
float m_fogScale;
float m_fogDensity;
C3Vector m_fogColor;
C4Plane m_liquidPlane;
// Member functions
void AddAmbient(const C3Vector& ambColor);
void AddDiffuse(const C3Vector& dirColor, const C3Vector& dir);
void AddLight(CM2Light* light);
void AddSpecular(const C3Vector& specColor);
void CameraSpace(void);
void Initialize(CM2Scene* scene, const CAaSphere& a3);
void SetFog(const C3Vector& fogColor, float fogStart, float fogEnd);
void SetFog(const C3Vector& fogColor, float fogStart, float fogEnd, float fogDensity);
void SetupGxLights(const C3Vector* a2);
void SetupSunlight(void);
};
#endif

1505
src/model/CM2Model.cpp Normal file

File diff suppressed because it is too large Load diff

180
src/model/CM2Model.hpp Normal file
View file

@ -0,0 +1,180 @@
#ifndef MODEL_C_M2_MODEL_HPP
#define MODEL_C_M2_MODEL_HPP
#include "gx/Camera.hpp"
#include "gx/Texture.hpp"
#include "model/CM2Lighting.hpp"
#include <cstdint>
#include <tempest/Matrix.hpp>
#include <tempest/Vector.hpp>
class CAaBox;
class CM2Scene;
class CM2Shared;
class M2Batch;
class M2Data;
class M2ModelBone;
class M2ModelBoneSeq;
class M2ModelCamera;
class M2ModelColor;
class M2ModelLight;
class M2ModelTextureWeight;
class M2SequenceFallback;
class M2TrackBase;
struct CM2ModelCall {
uint32_t type = -1;
CM2ModelCall* modelCallNext;
uint32_t time;
uint32_t args[8];
};
class CM2Model {
public:
// Static variables
static uint32_t s_loadingSequence;
static uint8_t* s_sequenceBase;
static uint32_t s_sequenceBaseSize;
static uint32_t s_skinProfileBoneCountMax[];
// Static functions
static CM2Model* AllocModel(uint32_t* heapId);
static bool Sub825E00(M2Data* data, uint32_t a2);
static uint16_t Sub8260C0(M2Data* data, uint32_t sequenceId, int32_t a3);
// Member variables
uint32_t m_refCount = 1;
uint32_t m_flags = 0;
CM2Model** m_scenePrev = nullptr;
CM2Model* m_sceneNext = nullptr;
uint32_t m_loaded : 1;
uint32_t m_flag2 : 1;
uint32_t m_flag4 : 1;
uint32_t m_flag8 : 1;
uint32_t m_flag10 : 1;
uint32_t m_flag20 : 1;
uint32_t m_flag40 : 1;
uint32_t m_flag80 : 1;
uint32_t m_flag100 : 1;
uint32_t m_flag200 : 1;
uint32_t m_flag400 : 1;
uint32_t m_flag800 : 1;
uint32_t m_flag1000 : 1;
uint32_t m_flag2000 : 1;
uint32_t m_flag4000 : 1;
uint32_t m_flag8000 : 1;
uint32_t m_flag10000 : 1;
uint32_t m_flag20000 : 1;
uint32_t m_flag40000 : 1;
uint32_t m_flag80000 : 1;
uint32_t m_flag100000 : 1;
uint32_t m_flag200000 : 1;
uint32_t m_flag400000 : 1;
CM2Model** m_callbackPrev = nullptr;
CM2Model* m_callbackNext = nullptr;
void (*m_loadedCallback)(CM2Model*, void*) = nullptr;
void* m_loadedArg = nullptr;
CM2Scene* m_scene = nullptr;
CM2Shared* m_shared = nullptr;
CM2Model* model30 = nullptr;
CM2ModelCall* m_modelCallList = nullptr;
CM2ModelCall** m_modelCallTail = nullptr;
CM2Model** m_animatePrev = nullptr;
CM2Model* m_animateNext = nullptr;
CM2Model* m_attachParent = nullptr;
uint32_t m_time = 0;
CM2Model** m_drawPrev = nullptr;
CM2Model* m_drawNext = nullptr;
uint32_t* m_loops = nullptr;
uint32_t uint74 = 0;
float float88 = 0.0f;
uint32_t uint90 = 0;
M2ModelBone* m_bones = nullptr;
C44Matrix* m_boneMatrices = nullptr;
M2ModelColor* m_colors = nullptr;
HTEXTURE* m_textures = nullptr;
M2ModelTextureWeight* m_textureWeights = nullptr;
C44Matrix* m_textureMatrices = nullptr;
C44Matrix matrixB4;
C44Matrix matrixF4;
float float198 = 1.0f;
float alpha19C = 1.0f;
C3Vector m_currentDiffuse = { 1.0f, 1.0f, 1.0f };
C3Vector m_currentEmissive = { 0.0f, 0.0f, 0.0f };
M2ModelLight* m_lights;
CM2Lighting m_lighting;
CM2Lighting* m_currentLighting = nullptr;
void (*m_lightingCallback)(CM2Model*, CM2Lighting*, void*) = nullptr;
void* m_lightingArg = nullptr;
M2ModelCamera* m_cameras = nullptr;
void* ptr2D0 = nullptr;
// Member functions
CM2Model()
: m_loaded(0)
, m_flag2(0)
, m_flag4(0)
, m_flag8(0)
, m_flag10(0)
, m_flag20(0)
, m_flag40(1)
, m_flag80(0)
, m_flag100(1)
, m_flag200(1)
, m_flag400(0)
, m_flag800(0)
, m_flag1000(0)
, m_flag2000(0)
, m_flag4000(0)
, m_flag8000(0)
, m_flag10000(0)
, m_flag20000(0)
, m_flag40000(0)
, m_flag80000(0)
, m_flag100000(0)
, m_flag200000(0)
, m_flag400000(0)
{};
void Animate(void);
void AnimateCamerasST(void);
void AnimateMT(const C44Matrix* view, const C3Vector& a3, const C3Vector& a4, float a5, float a6);
void AnimateMTSimple(const C44Matrix* view, const C3Vector& a3, const C3Vector& a4, float a5, float a6);
void AnimateST(void);
void AttachToScene(CM2Scene* scene);
void CancelDeferredSequences(uint32_t boneIndex, bool a3);
void DetachFromScene(void);
void FindKey(M2ModelBoneSeq* sequence, const M2TrackBase& track, uint32_t& currentKey, uint32_t& nextKey, float& ratio);
CAaBox& GetBoundingBox(CAaBox& bounds);
HCAMERA GetCameraByIndex(uint32_t index);
C3Vector GetPosition(void);
int32_t Initialize(CM2Scene* scene, CM2Shared* shared, CM2Model* a4, uint32_t flags);
int32_t InitializeLoaded(void);
int32_t IsBatchDoodadCompatible(M2Batch* batch);
int32_t IsDrawable(int32_t a2, int32_t a3);
int32_t IsLoaded(int32_t a2, int32_t attachments);
void LinkToCallbackListTail(void);
int32_t ProcessCallbacks();
void ProcessCallbacksRecursive(void);
void Release(void);
void SetAnimating(int32_t animating);
void SetBoneSequence(uint32_t boneId, uint32_t sequenceId, uint32_t a4, uint32_t time, float a6, int32_t a7, int32_t a8);
void SetBoneSequenceDeferred(uint16_t a2, M2Data* data, uint16_t boneIndex, uint32_t time, float a6, M2SequenceFallback fallback, int32_t a8, int32_t a9, int32_t a10);
void SetIndices(void);
void SetLightingCallback(void (*lightingCallback)(CM2Model*, CM2Lighting*, void*), void* lightingArg);
void SetLoadedCallback(void (*loadedCallback)(CM2Model*, void*), void* loadedArg);
void SetPrimaryBoneSequence(uint16_t sequenceIndex, uint16_t boneIndex, M2SequenceFallback fallback, uint32_t time, float a6, int32_t a7);
void SetSecondaryBoneSequence(uint16_t a2, uint16_t boneIndex, M2SequenceFallback fallback, uint32_t time, float a6);
void SetupBoneSequence(uint16_t sequenceIndex, M2SequenceFallback fallback, uint32_t a4, float a5, M2ModelBoneSeq* boneSequence);
void SetupLighting(void);
void SetVisible(int32_t visible);
void SetWorldTransform(const C3Vector& position, float orientation, float scale);
void Sub826350(M2SequenceFallback& fallback, uint32_t sequenceId);
int32_t Sub8269C0(uint32_t boneId, uint16_t boneIndex);
void Sub826E60(uint32_t* a2, uint32_t* a3);
void UnlinkFromCallbackList(void);
void UnsetBoneSequence(uint32_t boneId, int32_t a3, int32_t a4);
void UpdateLoaded(void);
void WaitForLoad(const char* a2);
};
#endif

718
src/model/CM2Scene.cpp Normal file
View file

@ -0,0 +1,718 @@
#include "model/CM2Scene.hpp"
#include "gx/Shader.hpp"
#include "gx/Transform.hpp"
#include "model/CM2Cache.hpp"
#include "model/CM2Light.hpp"
#include "model/CM2Model.hpp"
#include "model/CM2SceneRender.hpp"
#include "model/CM2Shared.hpp"
#include "model/M2Internal.hpp"
#include "model/M2Sort.hpp"
#include <algorithm>
#include <cassert>
#include <tempest/Math.hpp>
uint32_t CM2Scene::s_optFlags = 0xFFFFFFFF;
void CM2Scene::AnimateThread(void* arg) {
// TODO
}
void CM2Scene::ComputeElementShaders(M2Element* element) {
auto model = element->model;
auto batch = element->batch;
auto material = &model->m_shared->m_data->materials[batch->materialIndex];
auto lighting = model->m_currentLighting;
int32_t shaded;
int32_t lightCount;
if (material->flags & 0x1 || CM2SceneRender::s_shadedList[material->blendMode] == 0) {
shaded = 0;
lightCount = material->flags & 0x1 ? 0 : lighting->m_lightCount;
} else {
shaded = 1;
lightCount = lighting->m_lightCount;
}
int32_t boneInfluences = element->skinSection->boneInfluences;
int32_t v18;
if (material->blendMode == M2BLEND_OPAQUE) {
v18 = 0;
} else if (material->blendMode == M2BLEND_ALPHA_KEY) {
v18 = CMath::fuint(element->alpha * 224.0f);
} else {
v18 = 1;
}
int32_t v8 = 0;
if (!(material->flags & 0x1) && !(material->flags & 0x100) && lighting->m_flags & 0x10) {
// TODO
// v8 = Sub873FF0();
if (v8) {
if (lighting->m_flags & 0x8) {
v8 = 1;
}
if (element->type == 1) {
v8 = 0;
}
}
}
int32_t v9 = v18 && (/* TODO !GxCaps()->dword130 ||*/ v8);
int32_t v10 = std::min(boneInfluences, 2);
int32_t v11 = std::min(v8, 2);
element->vertexPermute = shaded + 2 * (v11 + v10 + 2 * v11 + lightCount + 4 * (v11 + v10 + 2 * v11));
element->pixelPermute = v8 + 4 * (CShaderEffect::s_usePcfFiltering + 2 * v9);
// TODO
// element->dword3C = v8;
}
int32_t CM2Scene::SortOpaque(uint32_t a, uint32_t b, const void* userArg) {
auto elements = static_cast<const CM2Scene*>(userArg)->m_elements.Ptr();
auto elementA = const_cast<M2Element*>(&elements[a]);
auto elementB = const_cast<M2Element*>(&elements[b]);
if (elementA->type < elementB->type) {
return -1;
}
if (elementA->type > elementB->type) {
return 1;
}
switch (elementA->type) {
case 0:
case 1:
return CM2Scene::SortOpaqueGeoBatches(elementA, elementB);
case 3:
return CM2Scene::SortOpaqueRibbons(elementA, elementB);
case 4:
return CM2Scene::SortOpaqueParticles(elementA, elementB);
default:
return 0;
}
}
int32_t CM2Scene::SortOpaqueGeoBatches(M2Element* elementA, M2Element* elementB) {
auto modelA = elementA->model;
auto dataA = modelA->m_shared->m_data;
auto batchA = elementA->batch;
auto modelB = elementB->model;
auto dataB = modelB->m_shared->m_data;
auto batchB = elementB->batch;
if (elementA->type == 0) {
if (batchA->materialLayer < batchB->materialLayer) {
return -1;
}
if (batchA->materialLayer > batchB->materialLayer) {
return 1;
}
if (elementA->effect && elementB->effect) {
auto effectA = elementA->effect;
auto effectB = elementB->effect;
auto vertexShaderA = effectA->m_vertexShaders[elementA->vertexPermute];
auto pixelShaderA = effectA->m_pixelShaders[elementA->pixelPermute];
auto vertexShaderB = effectB->m_vertexShaders[elementB->vertexPermute];
auto pixelShaderB = effectB->m_pixelShaders[elementB->pixelPermute];
if (vertexShaderA < vertexShaderB) {
return -1;
}
if (vertexShaderA > vertexShaderB) {
return 1;
}
if (pixelShaderA < pixelShaderB) {
return -1;
}
if (pixelShaderA > pixelShaderB) {
return 1;
}
}
if (modelA->m_shared < modelB->m_shared) {
return -1;
}
if (modelA->m_shared > modelB->m_shared) {
return 1;
}
if ((elementA->flags & 0x4) < (elementB->flags & 0x4)) {
return -1;
}
if ((elementA->flags & 0x4) > (elementB->flags & 0x4)) {
return 1;
}
if (modelA < modelB) {
return -1;
}
if (modelA > modelB) {
return 1;
}
if (elementA->skinSection->boneComboIndex < elementB->skinSection->boneComboIndex) {
return -1;
}
if (elementA->skinSection->boneComboIndex > elementB->skinSection->boneComboIndex) {
return 1;
}
}
auto materialA = &dataA->materials[batchA->materialIndex];
auto materialB = &dataB->materials[batchB->materialIndex];
if (materialA->blendMode < materialB->blendMode) {
return -1;
}
if (materialA->blendMode > materialB->blendMode) {
return 1;
}
if ((materialA->flags & 0x1F) < (materialB->flags & 0x1F)) {
return -1;
}
if ((materialA->flags & 0x1F) > (materialB->flags & 0x1F)) {
return 1;
}
if (batchA->textureCount > 0 && batchB->textureCount > 0) {
for (int32_t i = 0; i < std::min(batchA->textureCount, batchB->textureCount); i++) {
auto textureIndexA = dataA->textureCombos[batchA->textureComboIndex];
auto textureA = textureIndexA >= dataA->textures.Count() ? 0 : reinterpret_cast<intptr_t>(modelA->m_textures[textureIndexA]);
auto textureIndexB = dataB->textureCombos[batchB->textureComboIndex];
auto textureB = textureIndexB >= dataB->textures.Count() ? 0 : reinterpret_cast<intptr_t>(modelB->m_textures[textureIndexB]);
if ((textureA - textureB) / sizeof(void*) < 0) {
return -1;
}
if ((textureA - textureB) / sizeof(void*) > 0) {
return 1;
}
}
}
if (batchA->textureCount < batchB->textureCount) {
return -1;
}
if (batchA->textureCount > batchB->textureCount) {
return 1;
}
if (batchA < batchB) {
return -1;
}
return batchA > batchB;
}
int32_t CM2Scene::SortOpaqueParticles(M2Element* elementA, M2Element* elementB) {
// TODO
return 0;
}
int32_t CM2Scene::SortOpaqueRibbons(M2Element* elementA, M2Element* elementB) {
// TODO
return 0;
}
int32_t CM2Scene::SortTransparent(uint32_t a, uint32_t b, const void* userArg) {
auto elements = static_cast<const CM2Scene*>(userArg)->m_elements.Ptr();
auto elementA = const_cast<M2Element*>(&elements[a]);
auto elementB = const_cast<M2Element*>(&elements[b]);
if (elementA->float10 > elementB->float10) {
return -1;
}
if (elementA->float10 < elementB->float10) {
return 1;
}
if ((elementA->flags & 0x1) > (elementB->flags & 0x1)) {
return -1;
}
if ((elementA->flags & 0x1) < (elementB->flags & 0x1)) {
return 1;
}
if (elementA->priorityPlane < elementB->priorityPlane) {
return -1;
}
if (elementA->priorityPlane > elementB->priorityPlane) {
return 1;
}
if (elementA->float14 > elementB->float14) {
return -1;
}
if (elementA->float14 < elementB->float14) {
return 1;
}
if ((CM2Scene::s_optFlags & 0x4000)
&& (elementA->type != elementB->type || elementA->model != elementB->model)
&& elementA->effect
&& elementB->effect
) {
auto effectA = elementA->effect;
auto effectB = elementB->effect;
auto vertexShaderA = effectA->m_vertexShaders[elementA->vertexPermute];
auto pixelShaderA = effectA->m_pixelShaders[elementA->pixelPermute];
auto vertexShaderB = effectB->m_vertexShaders[elementB->vertexPermute];
auto pixelShaderB = effectB->m_pixelShaders[elementB->pixelPermute];
if (vertexShaderA < vertexShaderB) {
return -1;
}
if (vertexShaderA > vertexShaderB) {
return 1;
}
if (pixelShaderA < pixelShaderB) {
return -1;
}
if (pixelShaderA > pixelShaderB) {
return 1;
}
}
if (elementA->model < elementB->model) {
return -1;
}
if (elementA->model > elementB->model) {
return 1;
}
if (elementA->type < elementB->type) {
return -1;
}
if (elementA->type > elementB->type) {
return 1;
}
if (elementA->type <= 2) {
if (elementA->batch->materialLayer < elementB->batch->materialLayer) {
return -1;
}
if (elementA->batch->materialLayer > elementB->batch->materialLayer) {
return 1;
}
}
if (!(CM2Scene::s_optFlags & 0x4000) || !elementA->effect || !elementB->effect) {
return CM2Scene::SortOpaque(a, b, userArg);
}
auto effectA = elementA->effect;
auto effectB = elementB->effect;
auto vertexShaderA = effectA->m_vertexShaders[elementA->vertexPermute];
auto pixelShaderA = effectA->m_pixelShaders[elementA->pixelPermute];
auto vertexShaderB = effectB->m_vertexShaders[elementB->vertexPermute];
auto pixelShaderB = effectB->m_pixelShaders[elementB->pixelPermute];
if (vertexShaderA < vertexShaderB) {
return -1;
}
if (vertexShaderA > vertexShaderB) {
return 1;
}
if (pixelShaderA < pixelShaderB) {
return -1;
}
if (pixelShaderA > pixelShaderB) {
return 1;
}
return CM2Scene::SortOpaque(a, b, userArg);
}
void CM2Scene::AdvanceTime(uint32_t a2) {
this->m_time += a2;
this->m_cache->UpdateShared();
this->m_cache->GarbageCollect(0);
this->m_flags |= 0x4;
this->uint10 = a2;
if (a2) {
for (auto model = this->m_animateList; model; model = model->m_animateNext) {
model->ProcessCallbacksRecursive();
}
}
this->m_flags &= ~0x4;
}
void CM2Scene::Animate(const C3Vector& cameraPos) {
this->uint14++;
uint32_t optFlags = this->m_cache->m_flags & 0xE000;
if (CM2Scene::s_optFlags != optFlags) {
CM2Scene::s_optFlags = optFlags;
}
GxXformView(this->m_view);
C3Vector invCameraPos = { -cameraPos.x, -cameraPos.y, -cameraPos.z };
this->m_view.Translate(invCameraPos);
this->m_viewInv = this->m_view.Inverse(this->m_view.Determinant());
if (this->m_cache->m_flags & 0x4) {
// In multithreaded mode, iteration over the animate list is interleaved:
// - the current thread animates entries 0, 2, 4, ...
// - the newly created thread animates entries 1, 3, 5, ...
this->m_cache->BeginThread(CM2Scene::AnimateThread, this);
CM2Model* nextModel;
for (auto model = this->m_animateList; model; model = nextModel->m_animateNext) {
if (!model->m_attachParent) {
if (model->m_flag1000) {
C3Vector v222 = { 0.0f, 0.0f, 0.0f };
C3Vector v218 = { 1.0f, 1.0f, 1.0f };
model->AnimateMTSimple(&this->m_view, v218, v222, 1.0f, 1.0f);
} else {
C3Vector v220 = { 0.0f, 0.0f, 0.0f };
C3Vector v221 = { 1.0f, 1.0f, 1.0f };
model->AnimateMT(&this->m_view, v221, v220, 1.0f, 1.0f);
}
}
nextModel = model->m_animateNext;
if (!nextModel) {
break;
}
}
this->m_cache->WaitThread();
} else {
for (auto model = this->m_animateList; model; model = model->m_animateNext) {
if (!model->m_attachParent) {
if (model->m_flag1000 != 0) {
C3Vector v222 = { 0.0f, 0.0f, 0.0f };
C3Vector v218 = { 1.0f, 1.0f, 1.0f };
model->AnimateMTSimple(&this->m_view, v218, v222, 1.0f, 1.0f);
} else {
C3Vector v220 = { 0.0f, 0.0f, 0.0f };
C3Vector v221 = { 1.0f, 1.0f, 1.0f };
model->AnimateMT(&this->m_view, v221, v220, 1.0f, 1.0f);
}
}
}
}
for (auto model = this->m_animateList; model; model = model->m_animateNext) {
if (!model->m_attachParent) {
model->AnimateST();
}
}
while (this->m_animateList) {
// TODO
// - this is clearing out the animate list; why? something must reattach things to it...
auto model = this->m_animateList;
this->m_animateList = model->m_animateNext;
model->m_animatePrev = nullptr;
model->m_animateNext = nullptr;
model->SetupLighting();
}
this->array44.SetCount(0);
for (int32_t i = 0; i < M2PASS_COUNT; i++) {
this->array54[i].SetCount(0);
}
this->m_elements.SetCount(0);
int32_t elementIndex = 0;
while (this->m_drawList) {
auto model = this->m_drawList;
this->m_drawList = model->m_drawNext;
model->m_flag8 = 0;
model->m_flag10000 = 0;
model->m_drawPrev = nullptr;
model->m_drawNext = nullptr;
if (!model->IsDrawable(0, 0) || model->m_flag4000) {
continue;
}
auto v19 = model->m_currentLighting;
auto data = model->m_shared->m_data;
auto v21 = v19->m_flags & 0x20;
auto v22 = v19->m_flags & 0x40;
if (v21 && v22) {
// TODO
// - liquid plane stuff
}
auto skinProfile = model->m_shared->skinProfile;
auto v17 = (this->m_cache->m_flags & 0x1) == 0;
int32_t v229;
if (v17 || (model->m_flags & 0x1) != 0 || (v17 = (model->m_flag40) == 0, v229 = 1, v17)) {
v229 = 0;
}
uint32_t batchCount;
if (model->ptr2D0) {
// TODO
// batchCount = (model->ptr2D0 + 4);
assert(false);
} else {
batchCount = skinProfile->batches.Count();
}
for (int32_t batchIndex = 0; batchIndex < batchCount; batchIndex++) {
M2Batch* batch;
M2SkinSection* skinSection;
CShaderEffect* effect;
int32_t v221;
int32_t v222;
if (model->ptr2D0) {
// TODO
// batch = &model->m_optGeo->batches[batchIndex];
// skinSection = model->m_optGeo->skinSections[batch->skinSectionIndex];
assert(false);
} else {
batch = &skinProfile->batches[batchIndex];
skinSection = &model->m_shared->m_skinSections[batch->skinSectionIndex];
if (!skinSection) {
continue;
}
}
if (batch->shader == 0x8000) {
continue;
}
float alpha = model->alpha19C;
if (batch->colorIndex < data->colors.Count()) {
auto& color = model->m_colors[batch->colorIndex];
alpha *= color.alphaTrack.currentValue;
}
if (batch->textureCount) {
auto& textureWeight = model->m_textureWeights[data->textureWeightCombos[batch->textureWeightComboIndex]];
alpha *= textureWeight.weightTrack.currentValue;
}
if (alpha < 0.000099999997f) {
continue;
}
M2Material* material = &data->materials[batch->materialIndex];
auto v17 = (batch->flags & 0x4) == 0;
if (v17 || (v17 = this->uint104 == 0, v222 = 1, v17)) {
v222 = 0;
}
M2Material* layerMaterial = batch->materialLayer
? &data->materials[batch->materialIndex - batch->materialLayer]
: &data->materials[batch->materialIndex];
if (layerMaterial->blendMode > 1 || (v221 = 0, alpha < 0.99998999f)) {
v221 = 1;
}
if (model->ptr2D0) {
// TODO
// effect = model->m_optGeo->effects[batchIndex];
assert(false);
} else {
effect = model->m_shared->m_batchShaders[batchIndex];
}
if (!effect) {
continue;
}
auto element = this->m_elements.New();
if (v222) {
element->type = 1;
} else if (!model->IsBatchDoodadCompatible(batch) || v221) {
element->type = 0;
} else {
element->type = 2;
}
element->model = model;
element->flags = 0x0;
if (v221 == 1 && v21 && v22 && !v222) {
element->flags |= 0x2;
}
if (model->ptr2D0) {
element->flags |= 0x4;
}
element->alpha = alpha;
element->index = batchIndex;
element->priorityPlane = batch->priorityPlane;
element->batch = batch;
element->skinSection = skinSection;
element->effect = effect;
CM2Scene::ComputeElementShaders(element);
float v58;
if (v221 < 1) {
element->float14 = model->float88;
v58 = model->float88;
} else if (data->flags & 0x10) {
element->float14 = (skinSection->sortCenterPosition * model->m_boneMatrices[skinSection->centerBoneIndex]).SquaredMag();
v58 = model->float88;
} else {
// TODO other sort position logic
v58 = model->float88;
}
element->float10 = v58;
if (element->type == 2) {
// TODO
} else if (v221 == 1) {
if (v222) {
if (v22) {
*this->array54[2].New() = elementIndex;
} else {
*this->array54[1].New() = elementIndex;
}
} else {
if (v21) {
*this->array54[1].New() = elementIndex;
}
if (v22) {
*this->array54[2].New() = elementIndex;
}
}
} else {
*this->array54[v221].New() = elementIndex;
}
elementIndex++;
if (v229 && !v222 && v221 >= 1 && !(material->flags & 0x10)) {
// TODO
}
}
// TODO
// - ribbons
// TODO
// - draw callbacks
}
M2HeapSort(CM2Scene::SortOpaque, this->array54[0].Ptr(), this->array54[0].Count(), this);
M2HeapSort(CM2Scene::SortTransparent, this->array54[1].Ptr(), this->array54[1].Count(), this);
M2HeapSort(CM2Scene::SortTransparent, this->array54[2].Ptr(), this->array54[2].Count(), this);
// TODO sort additive particles
}
CM2Model* CM2Scene::CreateModel(const char* file, uint32_t a3) {
if (!file) {
return nullptr;
}
CM2Shared* shared = this->m_cache->CreateShared(file, a3);
if (!shared) {
shared = this->m_cache->CreateShared("Spells\\ErrorCube.mdx", 0);
}
CM2Model* model = nullptr;
if (shared) {
model = CM2Model::AllocModel(g_modelPool);
if (model) {
if (!model->Initialize(this, shared, nullptr, a3)) {
// TODO
}
}
shared->Release();
}
return model;
}
int32_t CM2Scene::Draw(M2PASS pass) {
// TODO
// - conditional check on this->dword144
if (CM2Scene::s_optFlags != (this->m_cache->m_flags & 0xE000)) {
CM2Scene::s_optFlags = this->m_cache->m_flags & 0xE000;
}
CM2SceneRender render(this);
render.Draw(pass, this->m_elements.m_data, this->array54[pass].m_data, this->array54[pass].Count());
if (pass == M2PASS_0) {
render.Draw(pass, this->m_elements.m_data, this->array44.m_data, this->array44.Count());
}
return 1;
}
void CM2Scene::SelectLights(CM2Lighting* lighting) {
for (auto light = this->m_lightList; light; light = light->m_lightNext) {
lighting->AddLight(light);
}
// TODO
}

57
src/model/CM2Scene.hpp Normal file
View file

@ -0,0 +1,57 @@
#ifndef MODEL_C_M2_SCENE_HPP
#define MODEL_C_M2_SCENE_HPP
#include "model/M2Model.hpp"
#include "model/M2Types.hpp"
#include <cstdint>
#include <storm/Array.hpp>
#include <tempest/Matrix.hpp>
class CM2Cache;
class CM2Light;
class CM2Lighting;
class CM2Model;
class CM2Scene {
public:
// Static variables
static uint32_t s_optFlags;
// Static functions
static void AnimateThread(void* arg);
static void ComputeElementShaders(M2Element* element);
static int32_t SortOpaque(uint32_t a, uint32_t b, const void* userArg);
static int32_t SortOpaqueGeoBatches(M2Element* elementA, M2Element* elementB);
static int32_t SortOpaqueParticles(M2Element* elementA, M2Element* elementB);
static int32_t SortOpaqueRibbons(M2Element* elementA, M2Element* elementB);
static int32_t SortTransparent(uint32_t a, uint32_t b, const void* userArg);
// Member variables
CM2Cache* m_cache;
CM2Model* m_modelList = nullptr;
uint32_t m_time = 0;
uint32_t uint10;
uint32_t uint14 = 0;
uint32_t m_flags = 0;
CM2Light* m_lightList = nullptr;
CM2Model* m_animateList = nullptr;
CM2Model* m_drawList = nullptr;
TSGrowableArray<M2Element> m_elements;
TSGrowableArray<uint32_t> array44;
TSGrowableArray<uint32_t> array54[3];
C44Matrix m_view;
C44Matrix m_viewInv;
uint32_t uint104 = 0;
// Member functions
CM2Scene(CM2Cache* cache)
: m_cache(cache)
{};
void AdvanceTime(uint32_t a2);
void Animate(const C3Vector& cameraPos);
CM2Model* CreateModel(const char*, uint32_t);
int32_t Draw(M2PASS pass);
void SelectLights(CM2Lighting* lighting);
};
#endif

View file

@ -0,0 +1,541 @@
#include "model/CM2SceneRender.hpp"
#include "gx/Draw.hpp"
#include "gx/RenderState.hpp"
#include "gx/Shader.hpp"
#include "gx/Texture.hpp"
#include "gx/Transform.hpp"
#include "model/CM2Cache.hpp"
#include "model/CM2Model.hpp"
#include "model/CM2Shared.hpp"
#include "model/M2Types.hpp"
#include <tempest/Math.hpp>
C44Matrix CM2SceneRender::s_identity;
int32_t CM2SceneRender::s_fogModeList[M2BLEND_COUNT] = {
1, // M2BLEND_OPAQUE
1, // M2BLEND_ALPHA_KEY
1, // M2BLEND_ALPHA
2, // M2BLEND_NO_ALPHA_ADD
2, // M2BLEND_ADD
3, // M2BLEND_MOD
4 // M2BLEND_MOD_2X
};
EGxBlend CM2SceneRender::s_gxBlend[M2PASS_COUNT][M2BLEND_COUNT] = {
// M2PASS_0
{
GxBlend_Opaque, // M2BLEND_OPAQUE
GxBlend_AlphaKey, // M2BLEND_ALPHA_KEY
GxBlend_Alpha, // M2BLEND_ALPHA
GxBlend_NoAlphaAdd, // M2BLEND_NO_ALPHA_ADD
GxBlend_Add, // M2BLEND_ADD
GxBlend_Mod, // M2BLEND_MOD
GxBlend_Mod2x // M2BLEND_MOD_2X
},
// M2PASS_1
{
GxBlend_Alpha, // M2BLEND_OPAQUE
GxBlend_Alpha, // M2BLEND_ALPHA_KEY
GxBlend_Alpha, // M2BLEND_ALPHA
GxBlend_NoAlphaAdd, // M2BLEND_NO_ALPHA_ADD
GxBlend_Add, // M2BLEND_ADD
GxBlend_Mod, // M2BLEND_MOD
GxBlend_Mod2x // M2BLEND_MOD_2X
},
// M2PASS_2
{
GxBlend_Alpha, // M2BLEND_OPAQUE
GxBlend_Alpha, // M2BLEND_ALPHA_KEY
GxBlend_Alpha, // M2BLEND_ALPHA
GxBlend_NoAlphaAdd, // M2BLEND_NO_ALPHA_ADD
GxBlend_Add, // M2BLEND_ADD
GxBlend_Mod, // M2BLEND_MOD
GxBlend_Mod2x // M2BLEND_MOD_2X
}
};
int32_t CM2SceneRender::s_shadedList[M2BLEND_COUNT] = {
1, // M2BLEND_OPAQUE
1, // M2BLEND_ALPHA_KEY
1, // M2BLEND_ALPHA
1, // M2BLEND_NO_ALPHA_ADD
1, // M2BLEND_ADD
0, // M2BLEND_MOD
0 // M2BLEND_MOD_2X
};
void CM2SceneRender::Draw(M2PASS pass, M2Element* elements, uint32_t* a4, uint32_t a5) {
if (!a5) {
return;
}
GxRsPush();
C44Matrix savedView;
GxXformView(savedView);
for (int32_t xf = GxXform_Tex0; xf <= GxXform_World; xf++) {
GxXformPush(static_cast<EGxXform>(xf));
}
GxXformSetView(CM2SceneRender::s_identity);
GxXformSet(GxXform_World, CM2SceneRender::s_identity);
GxRsSet(GxRs_DepthFunc, 0);
GxRsSet(GxRs_PolygonOffset, 0);
GxRsSet(GxRs_Lighting, 0);
GxRsSet(GxRs_Fog, 0);
GxRsSet(GxRs_MatSpecularExp, 0.0f);
if (CShaderEffect::s_enableShaders) {
C44Matrix projNative;
GxXformProjNative(projNative);
this->matrix0 = projNative.Inverse(projNative.Determinant()).Transpose();
CShaderEffect::UpdateProjMatrix();
// TODO
// CShadowCache::SetShadowMapGenericGlobal();
}
this->m_curPass = pass;
for (int32_t i = 0; i < a5; i++) {
uint32_t index = a4[i];
auto element = &elements[index];
if (element->type == 2 || element->type == 4 || !element->model->m_flag2000) {
this->m_curElement = element;
this->m_curType = element->type;
this->m_curModel = element->model;
this->m_curShared = element->model->m_shared;
this->m_curShaded = 1;
this->m_curFogMode = 1;
this->m_curBatch = nullptr;
this->m_curSkinSection = nullptr;
this->m_curLighting = element->model->m_currentLighting;
// TODO
// this->m_curMaterial = this->dwordB8;
this->m_data = element->model->m_shared->m_data;
// TODO
// this->m_cache->LinkToSharedUpdateList(this->m_curShared);
switch (this->m_curElement->type) {
case 0: {
this->DrawBatch();
break;
}
case 1: {
this->DrawBatchProj();
break;
}
case 2: {
this->DrawBatchDoodad(elements, &a4[i]);
// TODO
// i += this->m_curElement->dword1C - 1;
break;
}
case 3: {
this->DrawRibbon();
break;
}
case 4: {
i += this->DrawParticle(i, elements, a4, a5);
break;
}
case 5: {
this->DrawCallback();
break;
}
default:
continue;
}
this->m_prevElement = this->m_curElement;
this->m_prevType = this->m_curType;
this->m_prevModel = this->m_curModel;
this->m_prevShared = this->m_curShared;
this->m_curLighting = this->m_prevLighting; // TODO investigate (maybe bug?)
this->m_prevShaded = this->m_curShaded;
this->m_prevFogMode = this->m_curFogMode;
this->m_prevBatch = this->m_curBatch;
this->m_prevSkinSection = this->m_curSkinSection;
this->m_prevMaterial = this->m_curMaterial;
}
}
for (int32_t xf = GxXform_Tex0; xf <= GxXform_World; xf++) {
GxXformPop(static_cast<EGxXform>(xf));
}
GxXformSetView(savedView);
GxRsPop();
GxRsSet(GxRs_Fog, 0);
}
void CM2SceneRender::DrawBatch() {
auto element = this->m_curElement;
this->m_curBatch = element->batch;
this->m_curSkinSection = element->skinSection;
this->m_curMaterial = &this->m_data->materials[element->batch->materialIndex];
element->effect->SetCurrent();
this->SetupLighting();
this->SetupMaterial();
this->SetupTextures();
if (
CShaderEffect::s_enableShaders
&& (
this->m_curType != this->m_prevType
|| this->m_curModel != this->m_prevModel
|| this->m_curSkinSection->boneComboIndex != this->m_prevSkinSection->boneComboIndex
)
) {
C4Vector* constants = reinterpret_cast<C4Vector*>(GxShaderConstantsLock(GxSh_Vertex));
for (int32_t i = 0; i < this->m_curSkinSection->boneCount; i++) {
auto& boneMatrix = this->m_curModel->m_boneMatrices[this->m_data->boneCombos[this->m_curSkinSection->boneComboIndex + i]];
constants[31 + (i * 3) + 0] = { boneMatrix.a0, boneMatrix.b0, boneMatrix.c0, boneMatrix.d0 };
constants[31 + (i * 3) + 1] = { boneMatrix.a1, boneMatrix.b1, boneMatrix.c1, boneMatrix.d1 };
constants[31 + (i * 3) + 2] = { boneMatrix.a2, boneMatrix.b2, boneMatrix.c2, boneMatrix.d2 };
}
GxShaderConstantsUnlock(GxSh_Vertex, 31, this->m_curSkinSection->boneCount * 3);
}
if (this->m_curElement->flags & 0x4) {
if (
this->m_curType != this->m_prevType
|| this->m_curModel != this->m_prevModel
) {
this->m_curModel->SetIndices();
}
} else if (
this->m_curType != this->m_prevType
|| this->m_curShared != this->m_prevShared
|| this->m_prevElement->flags & 0x4
) {
this->m_curShared->SetIndices();
}
int32_t v9 = this->m_curModel->m_shared->m_data->bones.count == 1 && this->m_cache->m_flags & 0x40;
this->SetBatchVertices(v9);
CShaderEffect::SetShaders(this->m_curElement->vertexPermute, this->m_curElement->pixelPermute);
if (CShaderEffect::s_enableShaders) {
auto skinSection = this->m_curSkinSection;
CGxBatch batch;
batch.m_primType = GxPrim_Triangles;
batch.m_start = skinSection->indexStart;
batch.m_count = skinSection->indexCount;
batch.m_minIndex = skinSection->vertexStart;
batch.m_maxIndex = skinSection->vertexStart + skinSection->vertexCount - 1;
GxDraw(&batch, 1);
} else if (v9) {
// TODO
} else {
// TODO
}
}
void CM2SceneRender::DrawBatchDoodad(M2Element* elements, uint32_t* a3) {
// TODO
}
void CM2SceneRender::DrawBatchProj() {
// TODO
}
void CM2SceneRender::DrawCallback() {
// TODO
}
int32_t CM2SceneRender::DrawParticle(uint32_t a2, M2Element* elements, uint32_t* a4, uint32_t a5) {
// TODO
return 0;
}
void CM2SceneRender::DrawRibbon() {
// TODO
}
void CM2SceneRender::SetBatchVertices(int32_t a2) {
if (CShaderEffect::s_enableShaders) {
if (this->m_curType != this->m_prevType || this->m_curShared != this->m_prevShared) {
this->m_curShared->SetVertices(0);
}
} else {
// TODO
// - non-shader code path
}
}
void CM2SceneRender::SetupLighting() {
if (this->m_curMaterial->flags & 0x1) {
this->m_curShaded = 0;
} else {
this->m_curShaded = CM2SceneRender::s_shadedList[this->m_curMaterial->blendMode];
}
CShaderEffect::SetLocalLighting(this->m_curLighting, this->m_curShaded, 0);
// TODO
// dwordD43010 = this->m_curElement->dword3C;
if ((this->m_curMaterial->flags & 0x2) || this->m_curLighting->m_fogScale <= 0.0f) {
this->m_curFogMode = 0;
} else {
this->m_curFogMode = CM2SceneRender::s_fogModeList[this->m_curMaterial->blendMode];
}
if (this->m_curFogMode == 0) {
CShaderEffect::SetFogEnabled(0);
} else {
CImVector fogColor;
switch (this->m_curFogMode) {
case 1: {
float x = this->m_curLighting->m_fogColor.x;
float y = this->m_curLighting->m_fogColor.y;
float z = this->m_curLighting->m_fogColor.z;
fogColor.b = z <= 0.0f ? 0x00 : z >= 1.0f ? 0xFF : CMath::fuint_n(z * 255.0f);
fogColor.g = y <= 0.0f ? 0x00 : y >= 1.0f ? 0xFF : CMath::fuint_n(y * 255.0f);
fogColor.r = x <= 0.0f ? 0x00 : x >= 1.0f ? 0xFF : CMath::fuint_n(x * 255.0f);
fogColor.a = 0xFF;
break;
}
case 2: {
fogColor = { 0x00, 0x00, 0x00, 0x00 };
break;
}
case 3: {
fogColor = { 0xFF, 0xFF, 0xFF, 0x00 };
break;
}
case 4: {
fogColor = { 0x80, 0x80, 0x80, 0x00 };
break;
}
}
CShaderEffect::SetFogParams(
this->m_curLighting->m_fogStart,
this->m_curLighting->m_fogEnd,
this->m_curLighting->m_fogDensity,
fogColor
);
CShaderEffect::SetFogEnabled(1);
}
if (
this->m_curType != this->m_prevType
|| this->m_curModel != this->m_prevModel
|| this->m_curLighting != this->m_prevLighting
|| ((this->m_curElement->flags & 0x2) != (this->m_prevElement->flags & 0x2))
) {
if (this->m_curElement->flags & 0x2) {
// TODO
// - enable clip plane mask for liquid plane
} else {
GxRsSet(GxRs_ClipPlaneMask, 0);
}
}
}
void CM2SceneRender::SetupMaterial() {
if (
this->m_curType != this->m_prevType
|| this->m_curMaterial != this->m_prevMaterial
|| (this->m_curElement->flags & 0x1) != (this->m_prevElement->flags & 0x1)
|| this->m_curElement->alpha != this->m_prevElement->alpha
) {
EGxBlend blendingMode = this->m_curElement->flags & 0x1
? GxBlend_AlphaKey
: CM2SceneRender::s_gxBlend[this->m_curPass][this->m_curMaterial->blendMode];
int32_t colorWrite = (this->m_curElement->flags & 0x1)
? 0
: 15;
GxRsSet(GxRs_ColorWrite, colorWrite);
GxRsSet(GxRs_BlendingMode, blendingMode);
float alphaRef;
if (this->m_curMaterial->blendMode == 0) {
alphaRef = 0.0f;
} else if (this->m_curMaterial->blendMode == 1) {
alphaRef = this->m_curElement->alpha * 0.87843138f;
} else {
alphaRef = 0.0039215689f;
}
CShaderEffect::SetAlphaRef(alphaRef);
}
if (
this->m_curType != this->m_prevType
|| this->m_curMaterial != this->m_prevMaterial
) {
int32_t culling = (this->m_curMaterial->flags & 0x4) == 0;
GxRsSet(GxRs_Culling, culling);
int32_t depthTest = (this->m_curMaterial->flags & 0x8) == 0;
GxRsSet(GxRs_DepthTest, depthTest);
int32_t depthWrite = (this->m_curMaterial->flags & 0x10) == 0;
GxRsSet(GxRs_DepthWrite, depthWrite);
}
if (!CShaderEffect::s_enableShaders) {
// TODO
}
if (this->m_curElement->type > 2) {
if (
this->m_curType != this->m_prevType
|| this->m_curElement->alpha != this->m_prevElement->alpha
) {
C4Vector diffuse = { 1.0f, 1.0f, 1.0f, this->m_curElement->alpha };
CShaderEffect::SetDiffuse(diffuse);
C4Vector emissive = { 0.0f, 0.0f, 0.0f, 0.0f };
CShaderEffect::SetEmissive(emissive);
}
} else if (
this->m_curType != this->m_prevType
|| this->m_curShared != this->m_prevShared
|| this->m_curShaded != this->m_prevShaded
|| this->m_curMaterial->blendMode != this->m_prevMaterial->blendMode
|| this->m_prevBatch == nullptr
|| this->m_curBatch->colorIndex != this->m_prevBatch->colorIndex
|| this->m_prevElement->alpha != this->m_curElement->alpha
|| this->m_curModel->m_currentDiffuse != this->m_prevModel->m_currentDiffuse
|| this->m_curModel->m_currentEmissive != this->m_prevModel->m_currentEmissive
) {
if (this->m_curMaterial->blendMode == M2BLEND_MOD) {
C4Vector diffuse = { 0.0f, 0.0f, 0.0f, this->m_curElement->alpha };
CShaderEffect::SetDiffuse(diffuse);
C4Vector emissive = { 1.0f, 1.0f, 1.0f, 0.0f };
CShaderEffect::SetEmissive(emissive);
} else if (this->m_curMaterial->blendMode == M2BLEND_MOD_2X) {
C4Vector diffuse = { 0.0f, 0.0f, 0.0f, this->m_curElement->alpha };
CShaderEffect::SetDiffuse(diffuse);
C4Vector emissive = { 0.5f, 0.5f, 0.5f, 0.0f };
CShaderEffect::SetEmissive(emissive);
} else {
auto modelDiffuse = this->m_curModel->m_currentDiffuse;
auto modelEmissive = this->m_curModel->m_currentEmissive;
if (this->m_curBatch->colorIndex < this->m_data->colors.Count()) {
auto& modelColor = this->m_curModel->m_colors[this->m_curBatch->colorIndex];
modelDiffuse.x *= modelColor.colorTrack.currentValue.x;
modelDiffuse.y *= modelColor.colorTrack.currentValue.y;
modelDiffuse.z *= modelColor.colorTrack.currentValue.z;
}
if (!this->m_curShaded) {
modelEmissive.x += modelDiffuse.x;
modelEmissive.y += modelDiffuse.y;
modelEmissive.z += modelDiffuse.z;
modelDiffuse.x = 0.0f;
modelDiffuse.y = 0.0f;
modelDiffuse.z = 0.0f;
}
C4Vector diffuse = { modelDiffuse.x, modelDiffuse.y, modelDiffuse.z, this->m_curElement->alpha };
CShaderEffect::SetDiffuse(diffuse);
C4Vector emissive = { modelEmissive.x, modelEmissive.y, modelEmissive.z, 0.0f };
CShaderEffect::SetEmissive(emissive);
}
}
}
void CM2SceneRender::SetupTextures() {
if (this->m_curType > 2) {
for (int32_t i = 0; i < 2; i++) {
GxRsSet(static_cast<EGxRenderState>(GxRs_Texture0 + i), 0);
CShaderEffect::SetTexMtx_Identity(i);
}
return;
}
uint32_t textureCount = this->m_curBatch->textureCount;
int32_t v19 = 1;
// TODO
// - override texture count in certain cases
if (!(this->m_curBatch->shader & 0x4000) || textureCount != 1) {
v19 = 0;
}
for (int32_t i = 0; i < 2; i++) {
if (i >= textureCount) {
GxRsSet(static_cast<EGxRenderState>(GxRs_Texture0 + i), static_cast<CGxTex*>(nullptr));
continue;
}
auto textureIndex = this->m_data->textureCombos[this->m_curBatch->textureComboIndex + i];
auto textureHandle = textureIndex < this->m_data->textures.Count()
? this->m_curModel->m_textures[textureIndex]
: nullptr;
auto texture = textureHandle
? TextureGetGxTex(textureHandle, 1, nullptr)
: nullptr;
if (texture) {
uint16_t textureFlags = this->m_data->textures[textureIndex].flags;
EGxTexWrapMode wrapU = static_cast<EGxTexWrapMode>(textureFlags & 0x1);
EGxTexWrapMode wrapV = static_cast<EGxTexWrapMode>(textureFlags & 0x2);
GxTexSetWrap(texture, wrapU, wrapV);
}
GxRsSet(static_cast<EGxRenderState>(GxRs_Texture0 + i), texture);
auto textureTransformIndex = this->m_data->textureTransformCombos[this->m_curBatch->textureTransformComboIndex + i];
auto stageShift = M2COMBINER_STAGE_SHIFT * (2 - (i + 1));
uint32_t v21 = v19 == 0 ? i : 1;
if (this->m_curBatch->shader & 0x8000 || !((this->m_curBatch->shader >> stageShift) & M2COMBINER_ENVMAP)) {
if (textureTransformIndex >= this->m_data->textureTransforms.Count()) {
CShaderEffect::SetTexMtx_Identity(v21);
} else {
CShaderEffect::SetTexMtx(this->m_curModel->m_textureMatrices[textureTransformIndex], v21);
}
} else {
CShaderEffect::SetTexMtx_SphereMap(v21);
}
}
}

View file

@ -0,0 +1,73 @@
#ifndef MODEL_C_M2_SCENE_RENDER_HPP
#define MODEL_C_M2_SCENE_RENDER_HPP
#include "gx/Types.hpp"
#include "model/CM2Scene.hpp"
#include "model/M2Types.hpp"
#include <cstdint>
class CM2Cache;
class CM2Lighting;
class CM2Model;
class CM2Shared;
class M2Batch;
class M2Data;
class M2Element;
class M2Material;
class M2SkinSection;
class CM2SceneRender {
public:
// Static variables
static C44Matrix s_identity;
static int32_t s_fogModeList[M2BLEND_COUNT];
static EGxBlend s_gxBlend[M2PASS_COUNT][M2BLEND_COUNT];
static int32_t s_shadedList[M2BLEND_COUNT];
// Member variables
C44Matrix matrix0;
CM2Scene* m_scene;
CM2Cache* m_cache;
M2Data* m_data = nullptr;
M2PASS m_curPass;
M2Element* m_curElement = nullptr;
M2Element* m_prevElement = nullptr;
uint32_t m_curType = -1u;
uint32_t m_prevType = -1u;
CM2Model* m_curModel = nullptr;
CM2Model* m_prevModel = nullptr;
CM2Shared* m_curShared = nullptr;
CM2Shared* m_prevShared = nullptr;
CM2Lighting* m_curLighting = nullptr;
CM2Lighting* m_prevLighting = nullptr;
uint32_t m_curShaded = 0;
uint32_t m_prevShaded = 0;
uint32_t m_curFogMode = -1u;
uint32_t m_prevFogMode = -1u;
M2Batch* m_curBatch = nullptr;
M2Batch* m_prevBatch = nullptr;
M2SkinSection* m_curSkinSection = nullptr;
M2SkinSection* m_prevSkinSection = nullptr;
M2Material* m_curMaterial = nullptr;
M2Material* m_prevMaterial = nullptr;
// Member functions
CM2SceneRender(CM2Scene* scene)
: m_scene(scene)
, m_cache(scene->m_cache)
{};
void Draw(M2PASS pass, M2Element* elements, uint32_t* a4, uint32_t a5);
void DrawBatch(void);
void DrawBatchDoodad(M2Element* elements, uint32_t* a3);
void DrawBatchProj(void);
void DrawCallback(void);
int32_t DrawParticle(uint32_t a2, M2Element* elements, uint32_t* a4, uint32_t a5);
void DrawRibbon(void);
void SetBatchVertices(int32_t a2);
void SetupBatchVertices(void);
void SetupLighting(void);
void SetupMaterial(void);
void SetupTextures(void);
};
#endif

806
src/model/CM2Shared.cpp Normal file
View file

@ -0,0 +1,806 @@
#include "model/CM2Shared.hpp"
#include "async/AsyncFile.hpp"
#include "gx/Buffer.hpp"
#include "gx/Shader.hpp"
#include "gx/Texture.hpp"
#include "model/CM2Cache.hpp"
#include "model/CM2Model.hpp"
#include "model/M2Data.hpp"
#include "model/M2Init.hpp"
#include "model/M2Types.hpp"
#include "util/CStatus.hpp"
#include "util/SFile.hpp"
#include <cstring>
void CM2Shared::LoadFailedCallback(void* arg) {
CM2Shared* shared = static_cast<CM2Shared*>(arg);
AsyncFileReadDestroyObject(shared->asyncObject);
shared->asyncObject = nullptr;
}
void CM2Shared::LoadSucceededCallback(void* arg) {
CM2Shared* shared = static_cast<CM2Shared*>(arg);
AsyncFileReadDestroyObject(shared->asyncObject);
shared->asyncObject = nullptr;
uint8_t* base = reinterpret_cast<uint8_t*>(shared->m_data);
uint32_t size = shared->m_dataSize;
M2Data& data = *shared->m_data;
if (!M2Init(base, size, data)) {
return;
}
if (!shared->Initialize()) {
return;
}
// TODO
// - allocate space for low priority sequence pointers
shared->m_m2DataLoaded = 1;
}
void CM2Shared::SkinProfileLoadedCallback(void* arg) {
CM2Shared* shared = static_cast<CM2Shared*>(arg);
shared->FinishLoadingSkinProfile(shared->asyncObject->size);
AsyncFileReadDestroyObject(shared->asyncObject);
shared->asyncObject = nullptr;
}
void CM2Shared::AddRef() {
// TODO
}
int32_t CM2Shared::CallbackWhenLoaded(CM2Model* model) {
// TODO
// if (model->dword4 & 0x20) {
// return 1;
// }
if (this->m_m2DataLoaded && this->m_skinProfileLoaded) {
model->InitializeLoaded();
return 1;
}
model->LinkToCallbackListTail();
return 1;
}
CShaderEffect* CM2Shared::CreateSimpleEffect(uint32_t textureCount, uint16_t shader, uint16_t textureCoordComboIndex) {
uint32_t combiner[2];
uint32_t envmap[2];
combiner[0] = (shader >> M2COMBINER_STAGE_SHIFT) & M2COMBINER_OP_MASK; // T1 Combiner
combiner[1] = (shader >> 0) & M2COMBINER_OP_MASK; // T2 Combiner
envmap[0] = (shader >> M2COMBINER_STAGE_SHIFT) & M2COMBINER_ENVMAP; // T1 Env Mapped
envmap[1] = (shader >> 0) & M2COMBINER_ENVMAP; // T2 Env Mapped
const char* vsName;
const char* psName;
// 1 texture
if (textureCount == 1) {
if (envmap[0]) {
vsName = "Diffuse_Env";
} else if (this->m_data->textureCoordCombos[textureCoordComboIndex] == 0) {
vsName = "Diffuse_T1";
} else {
vsName = "Diffuse_T2";
}
switch (combiner[0]) {
case M2COMBINER_OPAQUE:
psName = "Combiners_Opaque";
break;
case M2COMBINER_MOD:
psName = "Combiners_Mod";
break;
case M2COMBINER_DECAL:
psName = "Combiners_Decal";
break;
case M2COMBINER_ADD:
psName = "Combiners_Add";
break;
case M2COMBINER_MOD2X:
psName = "Combiners_Mod2x";
break;
case M2COMBINER_FADE:
psName = "Combiners_Fade";
break;
default:
psName = "Combiners_Mod";
break;
}
// 2 textures
} else {
if (envmap[0] && envmap[1]) {
vsName = "Diffuse_Env_Env";
} else if (envmap[0]) {
vsName = "Diffuse_Env_T2";
} else if (envmap[1]) {
vsName = "Diffuse_T1_Env";
} else {
vsName = "Diffuse_T1_T2";
}
switch (combiner[0]) {
case M2COMBINER_OPAQUE:
switch (combiner[1]) {
case M2COMBINER_OPAQUE:
psName = "Combiners_Opaque_Opaque";
break;
case M2COMBINER_ADD:
psName = "Combiners_Opaque_Add";
break;
case M2COMBINER_MOD2X:
psName = "Combiners_Opaque_Mod2x";
break;
case M2COMBINER_MOD2X_NA:
psName = "Combiners_Opaque_Mod2xNA";
break;
case M2COMBINER_ADD_NA:
psName = "Combiners_Opaque_AddNA";
break;
default:
psName = "Combiners_Opaque_Mod";
break;
}
break;
case M2COMBINER_MOD:
switch (combiner[1]) {
case M2COMBINER_OPAQUE:
psName = "Combiners_Mod_Opaque";
break;
case M2COMBINER_MOD:
psName = "Combiners_Mod_Mod";
break;
case M2COMBINER_ADD:
psName = "Combiners_Mod_Add";
break;
case M2COMBINER_MOD2X:
psName = "Combiners_Mod_Mod2x";
break;
case M2COMBINER_MOD2X_NA:
psName = "Combiners_Mod_Mod2xNA";
break;
case M2COMBINER_ADD_NA:
psName = "Combiners_Mod_AddNA";
break;
default:
psName = "Combiners_Mod_Mod";
break;
}
break;
case M2COMBINER_ADD:
switch (combiner[1]) {
case M2COMBINER_MOD:
psName = "Combiners_Add_Mod";
break;
default:
return nullptr;
}
break;
case M2COMBINER_MOD2X:
switch (combiner[1]) {
case M2COMBINER_MOD2X:
psName = "Combiners_Mod2x_Mod2x";
break;
default:
return nullptr;
}
break;
}
}
char effectName[256];
// Create effect name for hashing
strcpy(effectName, vsName);
strcat(effectName, psName);
CShaderEffect* effect = CShaderEffectManager::GetEffect(effectName);
if (!effect) {
effect = CShaderEffectManager::CreateEffect(effectName);
effect->InitEffect(vsName, psName);
// M2GetCombinerOps (inlined)
for (int32_t textureIndex = 0; textureIndex < textureCount; textureIndex++) {
// TODO
// colorOps[textureIndex] = colorOpTable[combiner[textureIndex]];
// alphaOps[textureIndex] = alphaOpTable[combiner[textureIndex]];
}
// TODO
// effect->InitFixedFuncPass(colorOps, alphaOps, textureCount);
}
return effect;
}
int32_t CM2Shared::FinishLoadingSkinProfile(uint32_t size) {
if (this->m_skinProfileLoaded) {
return 1;
}
uint8_t* base = reinterpret_cast<uint8_t*>(this->skinProfile);
M2Data& data = *this->m_data;
M2SkinProfile& skinProfile = *this->skinProfile;
if (!M2Init(base, size, data, skinProfile)) {
return 0;
}
if (!this->InitializeSkinProfile()) {
return 0;
}
this->m_skinProfileLoaded = 1;
while (this->m_callbackList) {
CM2Model* model = this->m_callbackList;
model->UnlinkFromCallbackList();
model->InitializeLoaded();
}
return 1;
}
CShaderEffect* CM2Shared::GetEffect(M2Batch* batch) {
CShaderEffect* effect;
// Simple effect
if (!(batch->shader & 0x8000)) {
effect = this->CreateSimpleEffect(batch->textureCount, batch->shader, batch->textureCoordComboIndex);
// Fallback
// 0000000000010001
// 1 texture: Combiners_Mod
// 2 texture: Combiners_Mod_Mod
if (!effect) {
effect = this->CreateSimpleEffect(batch->textureCount, 0x11, batch->textureCoordComboIndex);
}
return effect;
}
// Specialized effect
const char* vsName = nullptr;
const char* psName = nullptr;
uint32_t colorOp = 0;
uint32_t alphaOp = 0;
switch (batch->shader & 0x7FFF) {
case 0:
return nullptr;
case 1:
vsName = "Diffuse_T1_Env";
psName = "Combiners_Opaque_Mod2xNA_Alpha";
colorOp = 0;
alphaOp = 3;
break;
case 2:
vsName = "Diffuse_T1_Env";
psName = "Combiners_Opaque_AddAlpha";
colorOp = 0;
alphaOp = 3;
break;
case 3:
vsName = "Diffuse_T1_Env";
psName = "Combiners_Opaque_AddAlpha_Alpha";
colorOp = 0;
alphaOp = 3;
break;
default:
break;
}
char effectName[256];
// Create effect name for hashing
strcpy(effectName, vsName);
strcat(effectName, psName);
effect = CShaderEffectManager::GetEffect(effectName);
if (!effect) {
effect = CShaderEffectManager::CreateEffect(effectName);
effect->InitEffect(vsName, psName);
// TODO
// effect->InitFixedFuncPass(effect, &colorOp, &alphaOp, 1);
}
return effect;
}
int32_t CM2Shared::Initialize() {
this->skinProfile = nullptr;
// TODO
// implement logic to select skin profile
uint32_t profile = this->m_data->numSkinProfiles - 1;
if (!this->LoadSkinProfile(profile)) {
return 0;
}
void* textures = SMemAlloc(sizeof(HTEXTURE) * this->m_data->textures.count, __FILE__, __LINE__, 0x8);
this->textures = static_cast<HTEXTURE*>(textures);
if (!textures) {
// TODO
// CM2Model::ErrorSetFileLine(__FILE__, __LINE__);
// CM2Model::ErrorSet("Failed to allocate texture array: %s", this + 60);
return 0;
}
for (int32_t i = 0; i < this->m_data->materials.count; i++) {
M2Material& material = this->m_data->materials[i];
if (material.blendMode >= M2BLEND_MOD) {
material.flags |= 0x1;
}
}
for (int32_t i = 0; i < this->m_data->textures.count; i++) {
M2Texture& texture = this->m_data->textures[i];
if (texture.filename.count > 1) {
CGxTexFlags texFlags = CGxTexFlags(GxTex_Linear, 0, 0, 0, 0, 0, 1);
int32_t createFlags = 0;
if (this->m_flag4) {
createFlags |= 0x2;
}
if (this->m_flag40) {
createFlags |= 0x30;
}
CStatus* status = &GetGlobalStatusObj();
this->textures[i] = TextureCreate(&texture.filename[0], texFlags, status, createFlags);
} else {
static CImVector FRIENDLY_WHITE = { 0xFF, 0xFF, 0xFF, 0xFF };
this->textures[i] = TextureCreateSolid(FRIENDLY_WHITE);
}
}
for (int32_t i = 0; i < this->m_data->bones.count; i++) {
M2CompBone& bone = this->m_data->bones[i];
if (bone.flags & (0x200 | 0x80 | 0x40 | 0x20 | 0x10 | 0x8)) {
// TODO
}
}
return 1;
}
int32_t CM2Shared::InitializeSkinProfile() {
this->uint194 = this->skinProfile->indices.Count()
? 65536 / this->skinProfile->indices.Count()
: 1;
for (int32_t i = 0; i < this->skinProfile->skinSections.Count(); i++) {
uint32_t v6 = this->skinProfile->boneCountMax / this->skinProfile->skinSections[i].boneCount;
if (this->uint194 > v6) {
this->uint194 = v6;
}
}
if (!this->uint194) {
this->uint194 = 1;
}
this->uint190 = 1;
uint32_t dataSize = sizeof(M2SkinSection) * this->skinProfile->skinSections.Count();
if (this->skinProfile->indices.Count()) {
dataSize += sizeof(CShaderEffect*) * this->skinProfile->batches.Count();
}
char* data = static_cast<char*>(SMemAlloc(dataSize, __FILE__, __LINE__, 0x0));
if (!data) {
return 0;
}
this->m_skinSections = reinterpret_cast<M2SkinSection*>(data);
if (this->skinProfile->skinSections.Count()) {
data += sizeof(M2SkinSection) * this->skinProfile->skinSections.Count();
memcpy(this->m_skinSections, this->skinProfile->skinSections.Data(), this->skinProfile->skinSections.Count() * sizeof(M2SkinSection));
}
if (this->skinProfile->indices.Count()) {
this->m_batchShaders = reinterpret_cast<CShaderEffect**>(data);
memset(this->m_batchShaders, 0, sizeof(CShaderEffect*) * this->skinProfile->batches.Count());
}
this->SubstituteSimpleShaders();
this->SubstituteSpecializedShaders();
if (this->skinProfile->indices.Count()) {
for (int32_t i = 0; i < this->skinProfile->batches.Count(); i++) {
this->m_batchShaders[i] = this->GetEffect(&this->skinProfile->batches[i]);
}
}
if (!(this->m_cache->m_flags & 0x8)) {
// TODO
// - non-shader-path vertex logic
}
if (!(this->m_cache->m_flags & 0x8)) {
for (int32_t i = 0; i < this->skinProfile->batches.Count(); i++) {
auto& batch = this->skinProfile->batches[i];
if (batch.textureCount > 1) {
this->skinProfile->batches[i - batch.materialLayer].flags |= 0x40;
}
}
for (int32_t i = 0; i < this->skinProfile->batches.Count(); i++) {
auto& batch = this->skinProfile->batches[i];
if (batch.materialLayer) {
if (this->skinProfile->batches[i - batch.materialLayer].flags & 0x40) {
batch.flags |= 0x40;
}
}
}
}
return 1;
}
int32_t CM2Shared::Load(SFile* file, int32_t a3, CAaBox* a4) {
// TODO
// this->dword8 ^= (this->dword8 ^ (4 * (a3 != 0))) & 4;
this->m_dataSize = SFile::GetFileSize(file, 0);
// TODO use proper allocation function here
this->m_data = static_cast<M2Data*>(SMemAlloc(this->m_dataSize, __FILE__, __LINE__, 0));
if (!this->m_data) {
return 0;
}
if (a4) {
this->aaBox154 = *a4;
}
this->asyncObject = AsyncFileReadAllocObject();
if (!this->asyncObject) {
return 0;
}
this->asyncObject->file = file;
this->asyncObject->buffer = this->m_data;
this->asyncObject->size = this->m_dataSize;
this->asyncObject->userArg = this;
this->asyncObject->userPostloadCallback = &CM2Shared::LoadSucceededCallback;
this->asyncObject->userFailedCallback = &CM2Shared::LoadFailedCallback;
this->asyncObject->isRead = 0;
this->asyncObject->isProcessed = 0;
AsyncFileReadObject(this->asyncObject, 0);
return 1;
}
int32_t CM2Shared::LoadSkinProfile(uint32_t profile) {
// TODO
// the file path logic is in its own function
char skinFilePath[STORM_MAX_PATH];
strcpy(skinFilePath, this->m_filePath);
char* v4 = strrchr(skinFilePath, '.');
if (v4) {
*v4 = 0;
}
sprintf(&skinFilePath[strlen(skinFilePath)], "%02d.skin", profile);
SFile* fileptr;
if (!SFile::OpenEx(nullptr, skinFilePath, this->m_flag4, &fileptr)) {
// TODO
// error handling
return 0;
}
uint32_t size = SFile::GetFileSize(fileptr, nullptr);
// TODO use proper allocation function here
this->skinProfile = static_cast<M2SkinProfile*>(SMemAlloc(size, __FILE__, __LINE__, 0));
if (!this->skinProfile) {
SFile::Close(fileptr);
return 0;
}
this->asyncObject = AsyncFileReadAllocObject();
if (!this->asyncObject) {
SFile::Close(fileptr);
delete this->skinProfile;
return 0;
}
this->asyncObject->file = fileptr;
this->asyncObject->buffer = this->skinProfile;
this->asyncObject->size = size,
this->asyncObject->userArg = this;
this->asyncObject->userPostloadCallback = &CM2Shared::SkinProfileLoadedCallback;
this->asyncObject->userFailedCallback = &CM2Shared::LoadFailedCallback;
this->asyncObject->isRead = 0;
this->asyncObject->isProcessed = 0;
this->asyncObject->priority = 125;
AsyncFileReadObject(this->asyncObject, 1);
return 1;
}
void CM2Shared::Release() {
// TODO
}
int32_t CM2Shared::SetIndices() {
if (!this->m_indexPool) {
this->m_indexPool = GxPoolCreate(
GxPoolTarget_Index,
GxPoolUsage_Dynamic,
2 * this->uint190 * this->skinProfile->indices.Count(),
GxPoolHintBit_Unk1,
this->ext
);
this->m_indexBuf = GxBufCreate(
this->m_indexPool,
2,
this->uint190 * this->skinProfile->indices.Count(),
0
);
if (!this->m_indexPool || !this->m_indexBuf) {
return 0;
}
}
if (!this->m_indexBuf->unk1C || !this->m_indexBuf->unk1D) {
char* indexData = GxBufLock(this->m_indexBuf);
uint16_t* indexBuf = reinterpret_cast<uint16_t*>(indexData);
if (!indexData) {
return 0;
}
int32_t v10 = (this->m_cache->m_flags & 0x8) != 0
|| (this->m_data->bones.Count() == 1 && (this->m_cache->m_flags & 0x40) != 0);
uint32_t v21 = 0;
for (int32_t i = 0; i < this->skinProfile->skinSections.Count(); i++) {
auto& skinSection = this->m_skinSections[i];
auto indexStart = this->skinProfile->skinSections[i].indexStart;
auto v25 = v10 ? 0 : -skinSection.vertexStart;
for (int32_t j = 0; j < this->uint190; j++) {
for (int32_t k = 0; k < skinSection.indexCount; k++) {
indexBuf[k] = this->skinProfile->indices[indexStart + k + v25];
}
indexBuf += skinSection.indexCount;
v25 += v10 ? this->skinProfile->vertices.Count() : skinSection.vertexCount;
}
skinSection.indexStart = v21;
v21 += this->uint190 * skinSection.indexCount;
}
GxBufUnlock(this->m_indexBuf, 0);
}
GxPrimIndexPtr(this->m_indexBuf);
return 1;
}
int32_t CM2Shared::SetVertices(uint32_t a2) {
if (!this->m_vertexPool) {
this->m_vertexPool = GxPoolCreate(
GxPoolTarget_Vertex,
GxPoolUsage_Static,
sizeof(CGxVertexPBNT2) * this->uint190 * this->skinProfile->vertices.Count(),
GxPoolHintBit_Unk1,
this->ext
);
this->m_vertexBuf = GxBufCreate(
this->m_vertexPool,
sizeof(CGxVertexPBNT2),
this->uint190 * this->skinProfile->vertices.Count(),
0
);
if (!this->m_vertexPool || !this->m_vertexBuf) {
return 0;
}
}
if (CShaderEffect::s_enableShaders) {
if (!this->m_vertexBuf->unk1C || !this->m_vertexBuf->unk1D) {
char* vertexData = GxBufLock(this->m_vertexBuf);
CGxVertexPBNT2* vertexBuf = reinterpret_cast<CGxVertexPBNT2*>(vertexData);
if (!vertexData) {
return 0;
}
auto v27 = 0;
for (int32_t i = 0; i < this->uint190; i++) {
for (int32_t j = 0; j < this->skinProfile->skinSections.Count(); j++) {
auto& skinSection = this->skinProfile->skinSections[j];
auto vertexStart = skinSection.vertexStart;
auto vertexEnd = vertexStart + skinSection.vertexCount;
uint32_t v25 = 0x1010101 * i * skinSection.boneCount;
if (vertexStart < vertexEnd) {
for (int32_t k = vertexStart; k < vertexEnd; k++) {
auto vertex = &this->m_data->vertices[this->skinProfile->vertices[k]];
memcpy(&vertexBuf[k], vertex, sizeof(CGxVertexPBNT2));
vertexBuf[k].bi.u = v25 + this->skinProfile->bones[k].u;
}
}
}
vertexBuf += this->skinProfile->vertices.Count();
}
GxBufUnlock(this->m_vertexBuf, 0);
}
GxPrimVertexPtr(this->m_vertexBuf, GxVBF_PBNT2);
return 1;
} else {
// TODO
// non-shader code path
return 1;
}
}
void CM2Shared::SubstituteSimpleShaders() {
for (int32_t batchIndex = 0; batchIndex < this->skinProfile->batches.Count(); batchIndex++) {
auto& batch = this->skinProfile->batches[batchIndex];
if (batch.shader & 0x8000) {
continue;
}
auto& material = this->m_data->materials[batch.materialIndex];
uint16_t textureCoordComboIndex = batch.textureCoordComboIndex;
// M2Data flag 0x8: use combiner combos
if (this->m_data->flags & 0x8) {
uint16_t textureCombinerComboIndex = batch.shader;
batch.shader = 0;
uint16_t shader[2] = { 0, 0 };
for (int32_t textureIndex = 0; textureIndex < batch.textureCount; textureIndex++) {
bool isFirstTexture = textureIndex == 0;
bool isLastTexture = textureIndex == batch.textureCount - 1;
// If this is the first texture and the batch material's blending mode is opaque,
// override the combiner mode to opaque; otherwise, use the combiner mode from the
// combiner combos
uint16_t textureCombiner = isFirstTexture && material.blendMode == M2BLEND_OPAQUE
? M2COMBINER_OPAQUE
: this->m_data->textureCombinerCombos[textureCombinerComboIndex + textureIndex];
shader[textureIndex] |= textureCombiner;
uint16_t textureCoord = this->m_data->textureCoordCombos[textureCoordComboIndex + textureIndex];
// If the texture coord is env, set env bit for texture
if (textureCoord > 2) {
shader[textureIndex] |= M2COMBINER_ENVMAP;
}
// If this is the last texture and the texture coord is T2, enable bit 15
if (isLastTexture && textureCoord == 1) {
batch.shader |= 0x4000;
}
}
batch.shader |= (shader[0] << M2COMBINER_STAGE_SHIFT) | shader[1];
} else {
uint16_t shader = 0;
// If the material blend mode is opaque, force the combiner to opaque; otherwise,
// default combiner to mod
uint16_t textureCombiner = material.blendMode == M2BLEND_OPAQUE
? M2COMBINER_OPAQUE
: M2COMBINER_MOD;
shader |= textureCombiner;
uint16_t textureCoord = this->m_data->textureCoordCombos[textureCoordComboIndex];
// If the texture coord is env, set env bit for texture
if (textureCoord > 2) {
shader |= M2COMBINER_ENVMAP;
}
shader <<= M2COMBINER_STAGE_SHIFT;
// If the texture coord is T2, enable bit 15
if (textureCoord == 1) {
shader |= 0x4000;
}
batch.shader = shader;
}
}
}
void CM2Shared::SubstituteSpecializedShaders() {
// TODO
}

83
src/model/CM2Shared.hpp Normal file
View file

@ -0,0 +1,83 @@
#ifndef MODEL_C_M2_SHARED_HPP
#define MODEL_C_M2_SHARED_HPP
#include "gx/Texture.hpp"
#include <cstdint>
#include <storm/String.hpp>
#include <tempest/Box.hpp>
class CAsyncObject;
class CGxBuf;
class CGxPool;
class CM2Cache;
class CM2Model;
class CShaderEffect;
class M2Batch;
class M2Data;
class M2SkinProfile;
class M2SkinSection;
class SFile;
class CM2Shared {
public:
// Static functions
static void LoadFailedCallback(void* param);
static void LoadSucceededCallback(void* param);
static void SkinProfileLoadedCallback(void* param);
// Member variables
CM2Cache* m_cache;
uint32_t m_m2DataLoaded : 1;
uint32_t m_skinProfileLoaded : 1;
uint32_t m_flag4 : 1;
uint32_t m_flag8 : 1;
uint32_t m_flag10 : 1;
uint32_t m_flag20 : 1;
uint32_t m_flag40 : 1;
CAsyncObject* asyncObject = nullptr;
CM2Model* m_callbackList = nullptr;
CM2Model** m_callbackListTail = &this->m_callbackList;
char m_filePath[STORM_MAX_PATH];
char* ext = nullptr;
M2Data* m_data = nullptr;
CAaBox aaBox154;
uint32_t m_dataSize = 0;
M2SkinProfile* skinProfile = nullptr;
HTEXTURE* textures = nullptr;
CGxPool* m_indexPool = nullptr;
CGxBuf* m_indexBuf = nullptr;
CGxPool* m_vertexPool = nullptr;
CGxBuf* m_vertexBuf = nullptr;
CShaderEffect** m_batchShaders = nullptr;
M2SkinSection* m_skinSections = nullptr;
uint32_t uint190 = 0;
uint32_t uint194 = 0;
// Member functions
CM2Shared(CM2Cache* cache)
: m_cache(cache)
, m_m2DataLoaded(0)
, m_skinProfileLoaded(0)
, m_flag4(0)
, m_flag8(0)
, m_flag10(0)
, m_flag20(0)
, m_flag40(0)
{};
void AddRef(void);
int32_t CallbackWhenLoaded(CM2Model* model);
CShaderEffect* CreateSimpleEffect(uint32_t textureCount, uint16_t shader, uint16_t textureCoordComboIndex);
CShaderEffect* GetEffect(M2Batch* batch);
int32_t FinishLoadingSkinProfile(uint32_t size);
int32_t Initialize(void);
int32_t InitializeSkinProfile(void);
int32_t Load(SFile*, int32_t, CAaBox*);
int32_t LoadSkinProfile(uint32_t profile);
void Release(void);
int32_t SetIndices(void);
int32_t SetVertices(uint32_t a2);
void SubstituteSimpleShaders(void);
void SubstituteSpecializedShaders(void);
};
#endif

22
src/model/CMakeLists.txt Normal file
View file

@ -0,0 +1,22 @@
file(GLOB PRIVATE_SOURCES "*.cpp")
add_library(model STATIC
${PRIVATE_SOURCES}
)
target_include_directories(model
PRIVATE
${CMAKE_SOURCE_DIR}/src
)
target_link_libraries(model
PRIVATE
async
gx
math
util
PUBLIC
common
storm
tempest
)

150
src/model/M2Animate.hpp Normal file
View file

@ -0,0 +1,150 @@
#ifndef MODEL_M2_ANIMATE_HPP
#define MODEL_M2_ANIMATE_HPP
#include "model/CM2Model.hpp"
#include "model/M2Data.hpp"
#include "model/M2Model.hpp"
struct M2SequenceFallback {
uint16_t uint0;
uint16_t uint2;
};
template<class T1, class T2>
void M2SetValue(const T1& sourceValue, T2& destValue) {
destValue = sourceValue;
}
template<>
void M2SetValue(const M2CompQuat& sourceValue, C4Quaternion& destValue) {
destValue.x = (sourceValue.auCompQ[0] & 0xFFFF) * 0.000030518044f - 1.0f;
destValue.y = (sourceValue.auCompQ[0] >> 16) * 0.000030518044f - 1.0f;
destValue.z = (sourceValue.auCompQ[1] & 0xFFFF) * 0.000030518044f - 1.0f;
destValue.w = (sourceValue.auCompQ[1] >> 16) * 0.000030518044f - 1.0f;
}
template<>
void M2SetValue(const fixed16& sourceValue, float& destValue) {
destValue = static_cast<float>(sourceValue);
}
void M2InterpolateLinear(const C3Vector& startValue, const C3Vector& endValue, float ratio, C3Vector& value) {
value.x = startValue.x + (ratio * (endValue.x - startValue.x));
value.y = startValue.y + (ratio * (endValue.y - startValue.y));
value.z = startValue.z + (ratio * (endValue.z - startValue.z));
}
void M2InterpolateLinear(float startValue, float endValue, float ratio, float& value) {
value = startValue + (ratio * (endValue - startValue));
}
void M2InterpolateLinear(fixed16 startValue, fixed16 endValue, float ratio, float& value) {
value = static_cast<float>(startValue) + (ratio * (static_cast<float>(endValue) - static_cast<float>(startValue)));
}
void M2InterpolateLinear(uint8_t startValue, uint8_t endValue, float ratio, uint8_t& value) {
value = startValue + (ratio * (endValue - startValue));
}
void M2InterpolateLinear(const M2CompQuat& startValue, const M2CompQuat& endValue, float ratio, C4Quaternion& value) {
C4Quaternion quat1;
C4Quaternion quat2;
M2SetValue(startValue, quat1);
M2SetValue(endValue, quat2);
value = C4Quaternion::Nlerp(ratio, quat1, quat2);
}
void M2InterpolateCubicBezier(const M2SplineKey<C3Vector>& startKey, const M2SplineKey<C3Vector>& endKey, float ratio, C3Vector& value) {
// TODO
}
void M2InterpolateCubicBezier(const M2SplineKey<float>& startKey, const M2SplineKey<float>& endKey, float ratio, float& value) {
// TODO
}
void M2InterpolateCubicHermite(const M2SplineKey<C3Vector>& startKey, const M2SplineKey<C3Vector>& endKey, float ratio, C3Vector& value) {
// TODO
}
void M2InterpolateCubicHermite(const M2SplineKey<float>& startKey, const M2SplineKey<float>& endKey, float ratio, float& value) {
// TODO
}
template<class T1, class T2>
void M2AnimateSplineTrack(CM2Model* model, M2ModelBone* modelBone, const M2Track<T1>& track, M2ModelTrack<T2>& modelTrack, const T2& defaultValue) {
auto seqIndex = modelBone->sequence.uint4 < track.sequenceKeys.Count() ? modelBone->sequence.uint4 : 0;
auto& seqKeys = track.sequenceKeys[seqIndex];
if (seqKeys.keys.Count()) {
uint32_t nextKey;
float ratio;
model->FindKey(&modelBone->sequence, track, modelTrack.currentKey, nextKey, ratio);
if (track.trackType == 0) {
modelTrack.currentValue = seqKeys.keys[modelTrack.currentKey].value;
return;
}
auto& startKey = seqKeys.keys[modelTrack.currentKey];
auto& endKey = seqKeys.keys[nextKey];
switch (track.trackType) {
case 1:
M2InterpolateLinear(startKey.value, endKey.value, ratio, modelTrack.currentValue);
break;
case 2:
M2InterpolateCubicBezier(startKey, endKey, ratio, modelTrack.currentValue);
break;
case 3:
M2InterpolateCubicHermite(startKey, endKey, ratio, modelTrack.currentValue);
break;
}
} else {
modelTrack.currentValue = defaultValue;
if (track.trackType == 0) {
return;
}
}
// TODO
// - blend with secondary active sequence
}
template<class T1, class T2>
void M2AnimateTrack(CM2Model* model, M2ModelBone* modelBone, const M2Track<T1>& track, M2ModelTrack<T2>& modelTrack, const T2& defaultValue) {
auto seqIndex = modelBone->sequence.uint4 < track.sequenceKeys.Count() ? modelBone->sequence.uint4 : 0;
auto& seqKeys = track.sequenceKeys[seqIndex];
if (seqKeys.keys.Count()) {
uint32_t nextKey;
float ratio;
model->FindKey(&modelBone->sequence, track, modelTrack.currentKey, nextKey, ratio);
if (track.trackType == 0) {
M2SetValue<T1, T2>(seqKeys.keys[modelTrack.currentKey], modelTrack.currentValue);
return;
}
auto& startValue = seqKeys.keys[modelTrack.currentKey];
auto& endValue = seqKeys.keys[nextKey];
M2InterpolateLinear(startValue, endValue, ratio, modelTrack.currentValue);
} else {
modelTrack.currentValue = defaultValue;
if (track.trackType == 0) {
return;
}
}
// TODO
// - blend with secondary active sequence
}
#endif

409
src/model/M2Data.hpp Normal file
View file

@ -0,0 +1,409 @@
#ifndef MODEL_M2_DATA_HPP
#define MODEL_M2_DATA_HPP
#include "gx/Buffer.hpp"
#include "math/Types.hpp"
#include <cstdint>
#include <tempest/Box.hpp>
#include <tempest/Vector.hpp>
/*
M2Array has been modified from the implementation present in 12340. The
implementation present in 12340 looks like this:
template<class T>
struct M2Array {
uint32_t count;
union {
T* data;
uint32_t offset;
}
};
On a 32-bit system, sizeof(M2Array) == 8 bytes in memory: 4 bytes for the
count, and 4 bytes for the union. This lines up with M2Array in the .m2
files: each M2Array is 8 bytes.
In 12340 (and until 64-bit support was introduced), the M2Init functions
simply adjust the M2Array when loading:
m2Data->someM2Array.offset =
(uint32_t)m2Data + m2Data->someM2Array.offset;
This ensures T* data points to the appropriate (absolute) location in
memory.
Unfortunately, this approach fails on 64-bit systems. On a 64-bit system,
M2Array would occupy 12 bytes in memory: 4 bytes for the count, and 8 bytes
for the union. This would make the approach outlined above fail.
As a result, on 64-bit systems, a different approach is used: M2Arrays are
assumed (reasonably so) to only exist within the same structure that their
on-disk offsets reference. Thus, M2Init adjusts the M2Array when loading:
uintptr_t absoluteOffset =
(uintptr_t)m2Data + m2Data->someM2Array.offset;
uintptr_t relativeOffset =
absoluteOffset - (uintptr_t)&m2Data->someM2Array;
m2Data->someM2Array.offset =
(uint32_t)relativeOffset;
By storing the relative offset, access to the data is possible by adding
the relative offset to the address of the M2Array:
uintptr_t absoluteOffset =
(uintptr_t)m2Data->someM2Array + m2Data->someM2Array.offset;
T* data = (T*)absoluteOffset;
*/
template<class T>
struct M2Array {
uint32_t count;
uint32_t offset;
T& operator[](uint32_t i);
T& operator[](uint32_t i) const;
uint32_t Count(void);
uint32_t Count(void) const;
T* Data(void);
};
template<class T>
T& M2Array<T>::operator[](uint32_t i) {
T* data = reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(this) + this->offset);
return data[i];
}
template<class T>
T& M2Array<T>::operator[](uint32_t i) const {
T* data = reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(this) + this->offset);
return data[i];
}
template<class T>
uint32_t M2Array<T>::Count() {
return this->count;
}
template<class T>
uint32_t M2Array<T>::Count() const {
return this->count;
}
template<class T>
T* M2Array<T>::Data() {
T* data = reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(this) + this->offset);
return data;
}
template<class T>
struct M2SequenceKeys {
M2Array<T> keys;
};
struct M2SequenceTimes {
M2Array<uint32_t> times;
};
struct M2TrackBase {
uint16_t trackType;
uint16_t loopIndex;
M2Array<M2SequenceTimes> sequenceTimes;
};
template<class T>
class M2Track : public M2TrackBase {
public:
M2Array<M2SequenceKeys<T>> sequenceKeys;
};
struct M2Attachment {
uint32_t attachmentId;
uint16_t boneIndex;
C3Vector position;
M2Track<uint8_t> visibilityTrack;
};
struct M2Batch {
uint8_t flags;
int8_t priorityPlane;
uint16_t shader;
uint16_t skinSectionIndex;
uint16_t geosetIndex;
uint16_t colorIndex;
uint16_t materialIndex;
uint16_t materialLayer;
uint16_t textureCount;
uint16_t textureComboIndex;
uint16_t textureCoordComboIndex;
uint16_t textureWeightComboIndex;
uint16_t textureTransformComboIndex;
};
struct M2Bounds {
CAaBox extent;
float radius;
};
template<class T>
struct M2SplineKey {
T value;
T inTan;
T outTan;
};
struct M2Camera {
uint32_t cameraId;
float fieldOfView;
float farClip;
float nearClip;
M2Track<M2SplineKey<C3Vector>> positionTrack;
C3Vector positionPivot;
M2Track<M2SplineKey<C3Vector>> targetTrack;
C3Vector targetPivot;
M2Track<M2SplineKey<float>> rollTrack;
};
struct M2Color {
M2Track<C3Vector> colorTrack;
M2Track<fixed16> alphaTrack;
};
struct M2CompQuat {
uint32_t auCompQ[2];
};
struct M2CompBone {
uint32_t boneId;
uint32_t flags;
uint16_t parentIndex;
uint16_t uDistToParent;
union {
struct {
uint16_t uDistToFurthDesc;
uint16_t uZRatioOfChain;
} CompressData;
uint32_t boneNameCRC;
};
M2Track<C3Vector> translationTrack;
M2Track<M2CompQuat> rotationTrack;
M2Track<C3Vector> scaleTrack;
C3Vector pivot;
};
struct M2Event {
uint32_t eventId;
uint32_t data;
uint16_t boneIndex;
C3Vector position;
M2TrackBase eventTrack;
};
struct M2Light {
uint16_t lightType;
uint16_t boneIndex;
C3Vector position;
M2Track<C3Vector> ambientColorTrack;
M2Track<float> ambientIntensityTrack;
M2Track<C3Vector> diffuseColorTrack;
M2Track<float> diffuseIntensityTrack;
M2Track<float> attenuationStartTrack;
M2Track<float> attenuationEndTrack;
M2Track<uint8_t> visibilityTrack;
};
struct M2Loop {
uint32_t length;
};
struct M2Material {
uint16_t flags;
uint16_t blendMode;
};
template<class T>
struct M2PartTrack {
M2Array<fixed16> times;
M2Array<T> values;
};
struct M2Particle {
uint32_t particleId;
uint32_t flags;
C3Vector position;
uint16_t boneIndex;
uint16_t textureIndex;
M2Array<char> geometryMdl;
M2Array<char> recursionMdl;
uint8_t blendMode;
uint8_t emitterType;
uint16_t colorIndex;
uint16_t pad;
int16_t priorityPlane;
uint16_t rows;
uint16_t cols;
M2Track<float> speedTrack;
M2Track<float> variationTrack;
M2Track<float> latitudeTrack;
M2Track<float> longitudeTrack;
M2Track<float> gravityTrack;
M2Track<float> lifeTrack;
float lifeVariation;
M2Track<float> emissionRateTrack;
float emissionRateVariation;
M2Track<float> widthTrack;
M2Track<float> lengthTrack;
M2Track<float> zsourceTrack;
M2PartTrack<C3Vector> colorTrack;
M2PartTrack<fixed16> alphaTrack;
M2PartTrack<C2Vector> scaleTrack;
C2Vector scaleVariation;
M2PartTrack<uint16_t> headCellTrack;
M2PartTrack<uint16_t> tailCellTrack;
float tailLength;
float twinkleFPS;
float twinkleOnOff;
CRange twinkleScale;
float ivelScale;
float drag;
float initialSpin;
float initialSpinVariation;
float spin;
float spinVariation;
CAaBox tumble;
C3Vector windVector;
float windTime;
float followSpeed1;
float followScale1;
float followSpeed2;
float followScale2;
M2Array<C3Vector> spline;
M2Track<uint8_t> visibilityTrack;
};
struct M2Ribbon {
uint32_t ribbonId;
uint16_t boneIndex;
C3Vector position;
M2Array<uint16_t> textureIndices;
M2Array<uint16_t> materialIndices;
M2Track<C3Vector> colorTrack;
M2Track<fixed16> alphaTrack;
M2Track<float> heightAboveTrack;
M2Track<float> heightBelowTrack;
float edgesPerSecond;
float edgeLifetime;
float gravity;
uint16_t textureRows;
uint16_t textureCols;
M2Track<uint16_t> textureSlotTrack;
M2Track<uint8_t> visibilityTrack;
int16_t priorityPlane;
uint16_t pad;
};
struct M2Sequence {
uint16_t id;
uint16_t variationIndex;
uint32_t duration;
float movespeed;
uint32_t flags;
uint32_t frequency;
CiRange replay;
uint32_t blendtime;
M2Bounds bounds;
uint16_t variationNext;
uint16_t aliasNext;
};
struct M2SkinSection {
uint32_t skinSectionId;
uint16_t vertexStart;
uint16_t vertexCount;
uint16_t indexStart;
uint16_t indexCount;
uint16_t boneCount;
uint16_t boneComboIndex;
uint16_t boneInfluences;
uint16_t centerBoneIndex;
C3Vector centerPosition;
C3Vector sortCenterPosition;
float sortRadius;
};
struct M2Texture {
uint32_t textureId;
uint16_t flags;
M2Array<char> filename;
};
struct M2TextureTransform {
M2Track<C3Vector> translationTrack;
M2Track<M2CompQuat> rotationTrack;
M2Track<C3Vector> scaleTrack;
};
struct M2TextureWeight {
M2Track<fixed16> weightTrack;
};
struct M2Vertex {
C3Vector position;
ubyte4 weights;
ubyte4 indices;
C3Vector normal;
C2Vector texcoord[2];
};
// .m2 files
struct M2Data {
uint32_t MD20;
uint32_t version;
M2Array<char> name;
uint32_t flags;
M2Array<M2Loop> loops;
M2Array<M2Sequence> sequences;
M2Array<uint16_t> sequenceIdxHashById;
M2Array<M2CompBone> bones;
M2Array<uint16_t> boneIndicesById;
M2Array<M2Vertex> vertices;
uint32_t numSkinProfiles;
M2Array<M2Color> colors;
M2Array<M2Texture> textures;
M2Array<M2TextureWeight> textureWeights;
M2Array<M2TextureTransform> textureTransforms;
M2Array<uint16_t> textureIndicesById;
M2Array<M2Material> materials;
M2Array<uint16_t> boneCombos;
M2Array<uint16_t> textureCombos;
M2Array<uint16_t> textureCoordCombos;
M2Array<uint16_t> textureWeightCombos;
M2Array<uint16_t> textureTransformCombos;
M2Bounds bounds;
M2Bounds collisionBounds;
M2Array<uint16_t> collisionIndices;
M2Array<C3Vector> collisionPositions;
M2Array<C3Vector> collisionFaceNormals;
M2Array<M2Attachment> attachments;
M2Array<uint16_t> attachmentIndicesById;
M2Array<M2Event> events;
M2Array<M2Light> lights;
M2Array<M2Camera> cameras;
M2Array<uint16_t> cameraIndicesById;
M2Array<M2Ribbon> ribbons;
M2Array<M2Particle> particles;
M2Array<uint16_t> textureCombinerCombos;
};
// .skin files
struct M2SkinProfile {
uint32_t magic;
M2Array<uint16_t> vertices;
M2Array<uint16_t> indices;
M2Array<ubyte4> bones;
M2Array<M2SkinSection> skinSections;
M2Array<M2Batch> batches;
uint32_t boneCountMax;
};
#endif

538
src/model/M2Init.cpp Normal file
View file

@ -0,0 +1,538 @@
#include "model/M2Init.hpp"
int32_t M2Init(uint8_t* base, uint32_t size, M2Data& data) {
if (!M2Init<char>(base, size, data, data.name)) {
return 0;
}
if (!M2Init<M2Loop>(base, size, data, data.loops)) {
return 0;
}
if (!M2Init<M2Sequence>(base, size, data, data.sequences)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.sequenceIdxHashById)) {
return 0;
}
if (!M2Init<M2CompBone>(base, size, data, data.bones)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.boneIndicesById)) {
return 0;
}
if (!M2Init<M2Vertex>(base, size, data, data.vertices)) {
return 0;
}
if (!M2Init<M2Color>(base, size, data, data.colors)) {
return 0;
}
if (!M2Init<M2Texture>(base, size, data, data.textures)) {
return 0;
}
if (!M2Init<M2TextureWeight>(base, size, data, data.textureWeights)) {
return 0;
}
if (!M2Init<M2TextureTransform>(base, size, data, data.textureTransforms)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.textureIndicesById)) {
return 0;
}
if (!M2Init<M2Material>(base, size, data, data.materials)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.boneCombos)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.textureCombos)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.textureCoordCombos)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.textureWeightCombos)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.textureTransformCombos)) {
return 0;
}
if (!M2Init(base, size, data, data.bounds)) {
return 0;
}
if (!M2Init(base, size, data, data.collisionBounds)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.collisionIndices)) {
return 0;
}
if (!M2Init<C3Vector>(base, size, data, data.collisionPositions)) {
return 0;
}
if (!M2Init<C3Vector>(base, size, data, data.collisionFaceNormals)) {
return 0;
}
if (!M2Init<M2Attachment>(base, size, data, data.attachments)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.attachmentIndicesById)) {
return 0;
}
if (!M2Init<M2Event>(base, size, data, data.events)) {
return 0;
}
if (!M2Init<M2Light>(base, size, data, data.lights)) {
return 0;
}
if (!M2Init<M2Camera>(base, size, data, data.cameras)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, data.cameraIndicesById)) {
return 0;
}
if (!M2Init<M2Ribbon>(base, size, data, data.ribbons)) {
return 0;
}
if (!M2Init<M2Particle>(base, size, data, data.particles)) {
return 0;
}
if (data.flags & 0x8) {
if (!M2Init<uint16_t>(base, size, data, data.textureCombinerCombos)) {
return 0;
}
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, char& value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, fixed16& value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, float& value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint8_t value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, ubyte4 value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint16_t value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint32_t value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, C2Vector& value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, C3Vector& value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Attachment& attachment) {
if (!M2Init<uint8_t>(base, size, data, attachment.visibilityTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Batch& batch) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Bounds& bounds) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Camera& camera) {
if (!M2Init<M2SplineKey<C3Vector>>(base, size, data, camera.positionTrack)) {
return 0;
}
if (!M2Init<M2SplineKey<C3Vector>>(base, size, data, camera.targetTrack)) {
return 0;
}
if (!M2Init<M2SplineKey<float>>(base, size, data, camera.rollTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Color& color) {
if (!M2Init<C3Vector>(base, size, data, color.colorTrack)) {
return 0;
}
if (!M2Init<fixed16>(base, size, data, color.alphaTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2CompBone& bone) {
if (!M2Init<C3Vector>(base, size, data, bone.translationTrack)) {
return 0;
}
if (!M2Init<M2CompQuat>(base, size, data, bone.rotationTrack)) {
return 0;
}
if (!M2Init<C3Vector>(base, size, data, bone.scaleTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2CompQuat& value) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Event& event) {
if (!M2Init(base, size, data, event.eventTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Light& light) {
if (!M2Init<C3Vector>(base, size, data, light.ambientColorTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, light.ambientIntensityTrack)) {
return 0;
}
if (!M2Init<C3Vector>(base, size, data, light.diffuseColorTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, light.diffuseIntensityTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, light.attenuationStartTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, light.attenuationEndTrack)) {
return 0;
}
if (!M2Init<uint8_t>(base, size, data, light.visibilityTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Loop& loop) {
if (!M2Init(base, size, data, loop.length)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Material& material) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Particle& particle) {
if (!M2Init<char>(base, size, data, particle.geometryMdl)) {
return 0;
}
if (!M2Init<char>(base, size, data, particle.recursionMdl)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.speedTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.variationTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.latitudeTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.longitudeTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.gravityTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.lifeTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.emissionRateTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.widthTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.lengthTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, particle.zsourceTrack)) {
return 0;
}
if (!M2Init<C3Vector>(base, size, data, particle.colorTrack)) {
return 0;
}
if (!M2Init<fixed16>(base, size, data, particle.alphaTrack)) {
return 0;
}
if (!M2Init<C2Vector>(base, size, data, particle.scaleTrack)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, particle.headCellTrack)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, particle.tailCellTrack)) {
return 0;
}
if (!M2Init<uint8_t>(base, size, data, particle.visibilityTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Ribbon& ribbon) {
if (!M2Init<uint16_t>(base, size, data, ribbon.textureIndices)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, ribbon.materialIndices)) {
return 0;
}
if (!M2Init<C3Vector>(base, size, data, ribbon.colorTrack)) {
return 0;
}
if (!M2Init<fixed16>(base, size, data, ribbon.alphaTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, ribbon.heightAboveTrack)) {
return 0;
}
if (!M2Init<float>(base, size, data, ribbon.heightBelowTrack)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, ribbon.textureSlotTrack)) {
return 0;
}
if (!M2Init<uint8_t>(base, size, data, ribbon.visibilityTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Sequence& sequence) {
if (sequence.flags & 0x1) {
sequence.flags |= 0x80;
}
switch (sequence.id) {
case 0:
case 4:
case 5:
case 13:
case 41:
case 42:
case 43:
case 44:
case 45:
case 69:
case 119:
case 120:
case 143:
case 223:
sequence.flags &= ~0x1;
break;
default:
break;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SequenceTimes& track) {
if (!M2Init<uint32_t>(base, size, data, track.times)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SkinProfile& skinProfile) {
if (!M2Init<uint16_t>(base, size, data, skinProfile.vertices)) {
return 0;
}
if (!M2Init<uint16_t>(base, size, data, skinProfile.indices)) {
return 0;
}
if (!M2Init<ubyte4>(base, size, data, skinProfile.bones)) {
return 0;
}
if (!M2Init<M2SkinSection>(base, size, data, skinProfile.skinSections)) {
return 0;
}
if (!M2Init<M2Batch>(base, size, data, skinProfile.batches)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SkinSection& skinSection) {
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Texture& texture) {
if (!M2Init<char>(base, size, data, texture.filename)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TextureTransform& transform) {
if (!M2Init<C3Vector>(base, size, data, transform.translationTrack)) {
return 0;
}
if (!M2Init<M2CompQuat>(base, size, data, transform.rotationTrack)) {
return 0;
}
if (!M2Init<C3Vector>(base, size, data, transform.scaleTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TextureWeight& weight) {
if (!M2Init<fixed16>(base, size, data, weight.weightTrack)) {
return 0;
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TrackBase& track) {
if (CM2Model::s_loadingSequence == 0xFFFFFFFF) {
if (track.loopIndex == 0xFFFF) {
if (track.sequenceTimes.count) {
uintptr_t absoluteOffset = reinterpret_cast<uintptr_t>(base) + track.sequenceTimes.offset;
uintptr_t relativeOffset = absoluteOffset - reinterpret_cast<uintptr_t>(&track.sequenceTimes);
track.sequenceTimes.offset = relativeOffset;
} else {
track.sequenceTimes.offset = 0;
}
for (int32_t i = 0; i < track.sequenceTimes.count; i++) {
auto& keys = track.sequenceTimes[i];
if (!M2InitSequenceKeyFrames<uint32_t>(base, size, data, i, keys.times)) {
return 0;
}
}
} else {
if (!M2Init<M2SequenceTimes>(base, size, data, track.sequenceTimes)) {
return 0;
}
}
} else {
if (CM2Model::s_loadingSequence < track.sequenceTimes.count) {
auto& keys = track.sequenceTimes[CM2Model::s_loadingSequence];
if (!M2InitKeyFrameData<uint32_t>(CM2Model::s_sequenceBase, CM2Model::s_sequenceBaseSize, data, CM2Model::s_loadingSequence, keys.times)) {
return 0;
}
}
}
return 1;
}
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Vertex& vertex) {
return 1;
}

219
src/model/M2Init.hpp Normal file
View file

@ -0,0 +1,219 @@
#ifndef MODEL_M2_INIT_HPP
#define MODEL_M2_INIT_HPP
#include "model/M2Data.hpp"
#include "model/CM2Model.hpp"
int32_t M2Init(uint8_t* base, uint32_t size, M2Data& data);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, char& value);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, fixed16& value);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, float& value);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint8_t value);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, ubyte4 value);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint16_t value);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint32_t value);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, C2Vector& value);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, C3Vector& value);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Attachment& attachment);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Batch& batch);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Bounds& bounds);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Camera& camera);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Color& color);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2CompBone& bone);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2CompQuat& quat);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Event& event);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Light& light);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Loop& loop);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Material& material);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Particle& particle);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Ribbon& ribbon);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Sequence& sequence);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SequenceTimes& track);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SkinProfile& skinProfile);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SkinSection& skinSection);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Texture& texture);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TextureTransform& transform);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TextureWeight& weight);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TrackBase& track);
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Vertex& vertex);
template<class T>
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Array<T>& array) {
if (CM2Model::s_loadingSequence == 0xFFFFFFFF) {
if (array.offset > size) {
return 0;
}
if (array.offset + (array.count * sizeof(T)) > size) {
return 0;
}
if (array.count) {
uintptr_t absoluteOffset = reinterpret_cast<uintptr_t>(base) + array.offset;
uintptr_t relativeOffset = absoluteOffset - reinterpret_cast<uintptr_t>(&array);
array.offset = relativeOffset;
} else {
array.offset = 0;
}
}
for (int32_t i = 0; i < array.count; i++) {
auto& element = array[i];
if (!M2Init(base, size, data, element)) {
return 0;
}
}
return 1;
}
template<class T>
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2PartTrack<T>& track) {
if (!M2Init<fixed16>(base, size, data, track.times)) {
return 0;
}
if (!M2Init<T>(base, size, data, track.values)) {
return 0;
}
return 1;
}
template<class T>
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SequenceKeys<T>& track) {
if (!M2Init<T>(base, size, data, track.keys)) {
return 0;
}
return 1;
}
template<class T>
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SplineKey<T>& key) {
return 1;
}
template<class T>
int32_t M2InitKeyFrameData(uint8_t* base, uint32_t size, const M2Data& data, uint32_t sequence, M2Array<T>& keys) {
if (keys.offset > size) {
return 0;
}
if (keys.offset + (keys.count * sizeof(T)) > size) {
return 0;
}
if (keys.count) {
uintptr_t absoluteOffset = reinterpret_cast<uintptr_t>(base) + keys.offset;
uintptr_t relativeOffset = absoluteOffset - reinterpret_cast<uintptr_t>(&keys);
keys.offset = relativeOffset;
} else {
keys.offset = 0;
}
uint32_t sequenceFlags = data.sequences[sequence].flags;
if (sequenceFlags & 0x40) {
return 1;
}
for (int32_t i = 0; i < keys.count; i++) {
auto& element = keys[i];
if (!M2Init(base, size, data, element)) {
return 0;
}
}
return 1;
}
template<class T>
int32_t M2InitSequenceKeyFrames(uint8_t* base, uint32_t size, const M2Data& data, uint32_t sequence, M2Array<T>& keys) {
uint32_t sequenceFlags = data.sequences[sequence].flags;
if (sequenceFlags & 0x20) {
if (!M2InitKeyFrameData<T>(base, size, data, sequence, keys)) {
return 0;
}
}
return 1;
}
template<class T>
int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Track<T>& track) {
if (!M2Init(base, size, data, static_cast<M2TrackBase&>(track))) {
return 0;
}
if (CM2Model::s_loadingSequence == 0xFFFFFFFF) {
if (track.loopIndex == 0xFFFF) {
if (track.sequenceKeys.count) {
uintptr_t absoluteOffset = reinterpret_cast<uintptr_t>(base) + track.sequenceKeys.offset;
uintptr_t relativeOffset = absoluteOffset - reinterpret_cast<uintptr_t>(&track.sequenceKeys);
track.sequenceKeys.offset = relativeOffset;
} else {
track.sequenceKeys.offset = 0;
}
for (int32_t i = 0; i < track.sequenceTimes.count; i++) {
auto& keys = track.sequenceKeys[i];
if (!M2InitSequenceKeyFrames<T>(base, size, data, i, keys.keys)) {
return 0;
}
}
} else {
if (!M2Init<M2SequenceKeys<T>>(base, size, data, track.sequenceKeys)) {
return 0;
}
}
} else if (CM2Model::s_loadingSequence < track.sequenceKeys.count) {
auto& keys = track.sequenceKeys[CM2Model::s_loadingSequence];
if (!M2InitKeyFrameData<T>(CM2Model::s_sequenceBase, CM2Model::s_sequenceBaseSize, data, CM2Model::s_loadingSequence, keys.keys)) {
return 0;
}
}
return 1;
}
#endif

3
src/model/M2Internal.cpp Normal file
View file

@ -0,0 +1,3 @@
#include "model/M2Internal.hpp"
uint32_t* g_modelPool;

8
src/model/M2Internal.hpp Normal file
View file

@ -0,0 +1,8 @@
#ifndef MODEL_M2_INTERNAL_HPP
#define MODEL_M2_INTERNAL_HPP
#include <cstdint>
extern uint32_t* g_modelPool;
#endif

82
src/model/M2Model.hpp Normal file
View file

@ -0,0 +1,82 @@
#ifndef MODEL_M2_MODEL_HPP
#define MODEL_M2_MODEL_HPP
#include "gx/Camera.hpp"
#include "model/CM2Light.hpp"
#include <cstdint>
#include <tempest/Quaternion.hpp>
#include <tempest/Vector.hpp>
template<class T>
class M2Track;
template<class T>
struct M2ModelTrack {
uint32_t currentKey = 0;
M2Track<T>* sourceTrack = nullptr;
T currentValue;
};
struct M2ModelAttachment {
};
struct M2ModelBoneSeq {
uint32_t uint0 = 0;
uint16_t uint4 = -1;
uint16_t uint6 = -1;
uint16_t uint8 = -1;
uint8_t uintA = 1;
uint8_t uintB = 1;
uint32_t uintC = 0;
uint32_t uint10 = 0;
float float14 = 0.0;
float float18 = 0.0;
uint32_t uint1C = 0;
uint32_t uint20 = 0;
};
struct M2ModelBone {
M2ModelTrack<C3Vector> translationTrack;
M2ModelTrack<C4Quaternion> rotationTrack;
M2ModelTrack<C3Vector> scaleTrack;
M2ModelBoneSeq sequence;
M2ModelBoneSeq secondarySequence;
uint32_t flags = 0;
uint32_t uint90 = -1;
uint16_t uint94 = 0;
uint32_t uint9C = 0;
float floatA0 = 0.0f;
float floatA4 = 1.0f;
float floatA8 = 0.0f;
};
struct M2ModelCamera {
M2ModelTrack<C3Vector> positionTrack;
M2ModelTrack<C3Vector> targetTrack;
M2ModelTrack<float> rollTrack;
HCAMERA m_camera = nullptr;
};
struct M2ModelColor {
M2ModelTrack<C3Vector> colorTrack;
M2ModelTrack<float> alphaTrack;
};
struct M2ModelLight {
M2ModelTrack<C3Vector> ambientColorTrack;
M2ModelTrack<float> ambientIntensityTrack;
M2ModelTrack<C3Vector> diffuseColorTrack;
M2ModelTrack<float> diffuseIntensityTrack;
M2ModelTrack<uint8_t> visibilityTrack;
uint32_t uint64 = 1;
CM2Light light;
};
struct M2ModelTextureTransform {
};
struct M2ModelTextureWeight {
M2ModelTrack<float> weightTrack;
};
#endif

83
src/model/M2Sort.cpp Normal file
View file

@ -0,0 +1,83 @@
#include "model/M2Sort.hpp"
void M2HeapSort(int32_t (*sortFunc)(uint32_t, uint32_t, const void*), uint32_t* indices, uint32_t count, const void* userArg) {
if (count <= 1) {
return;
}
auto v5 = count / 2;
if (v5) {
auto v6 = 2 * v5 + 1;
do {
auto v7 = indices[--v5];
v6 -= 2;
auto v15 = v5;
auto v14 = v6;
auto v22 = v5;
auto v18 = v7;
if (v6 < count) {
do {
auto v20 = indices[v6];
if (v6 + 1 < count) {
auto v17 = indices[v6 + 1];
if (sortFunc(v17, indices[v6], userArg) > 0) {
v6++;
v20 = v17;
}
}
if (sortFunc(v20, v18, userArg) <= 0) {
break;
}
auto v8 = v22;
auto v9 = indices[v6];
v22 = v6;
v6 = 2 * v6 + 1;
indices[v8] = v9;
} while (v6 < count);
v5 = v15;
v6 = v14;
}
indices[v22] = v18;
} while (v5);
}
uint32_t j, v21, v12, v13;
for (auto i = count - 1; i; indices[j] = v21) {
auto v11 = 1;
v21 = indices[i];
indices[i] = *indices;
for (j = 0; v11 < i; indices[v12] = v13) {
auto v19 = indices[v11];
if ( v11 + 1 < i ) {
auto v16 = indices[v11 + 1];
if (sortFunc(v16, indices[v11], userArg) > 0) {
v11++;
v19 = v16;
}
}
if (sortFunc(v19, v21, userArg) <= 0) {
break;
}
v12 = j;
v13 = indices[v11];
j = v11;
v11 = 2 * v11 + 1;
}
i--;
}
}

8
src/model/M2Sort.hpp Normal file
View file

@ -0,0 +1,8 @@
#ifndef MODEL_M2_SORT_HPP
#define MODEL_M2_SORT_HPP
#include <cstdint>
void M2HeapSort(int32_t (*sortFunc)(uint32_t, uint32_t, const void*), uint32_t* indices, uint32_t count, const void* userArg);
#endif

62
src/model/M2Types.hpp Normal file
View file

@ -0,0 +1,62 @@
#ifndef MODEL_M2_TYPES_HPP
#define MODEL_M2_TYPES_HPP
#include "M2Data.hpp"
class CM2Model;
class CShaderEffect;
enum M2BLEND {
M2BLEND_OPAQUE = 0x0,
M2BLEND_ALPHA_KEY = 0x1,
M2BLEND_ALPHA = 0x2,
M2BLEND_NO_ALPHA_ADD = 0x3,
M2BLEND_ADD = 0x4,
M2BLEND_MOD = 0x5,
M2BLEND_MOD_2X = 0x6,
M2BLEND_COUNT = 0x7,
};
enum M2COMBINER {
M2COMBINER_OPAQUE = 0x0,
M2COMBINER_MOD = 0x1,
M2COMBINER_DECAL = 0x2,
M2COMBINER_ADD = 0x3,
M2COMBINER_MOD2X = 0x4,
M2COMBINER_FADE = 0x5,
M2COMBINER_MOD2X_NA = 0x6,
M2COMBINER_ADD_NA = 0x7,
M2COMBINER_OP_MASK = 0x7,
M2COMBINER_ENVMAP = 0x8,
M2COMBINER_STAGE_SHIFT = 0x4,
};
enum M2LIGHTTYPE {
M2LIGHT_0 = 0,
M2LIGHT_1 = 1
};
enum M2PASS {
M2PASS_0 = 0,
M2PASS_1 = 1,
M2PASS_2 = 2,
M2PASS_COUNT = 3
};
struct M2Element {
int32_t type;
CM2Model* model;
uint32_t flags;
float alpha;
float float10;
float float14;
int32_t index;
int32_t priorityPlane;
M2Batch* batch;
M2SkinSection* skinSection;
CShaderEffect* effect;
uint32_t vertexPermute;
uint32_t pixelPermute;
};
#endif

276
src/model/Model2.cpp Normal file
View file

@ -0,0 +1,276 @@
#include "model/Model2.hpp"
#include "model/CM2Cache.hpp"
#include "model/M2Internal.hpp"
#include "util/CVar.hpp"
#include "util/Filesystem.hpp"
#include <cstring>
#include <new>
#include <common/ObjectAlloc.hpp>
#include <storm/Memory.hpp>
#include <storm/String.hpp>
static CVar* s_M2UseZFillVar;
static CVar* s_M2UseClipPlanesVar;
static CVar* s_M2UseThreadsVar;
static CVar* s_M2BatchDoodadsVar;
static CVar* s_M2BatchParticlesVar;
static CVar* s_M2ForceAdditiveParticleSortVar;
static CVar* s_M2FasterVar;
static CVar* s_M2FasterDebugVar;
uint32_t M2ConvertFasterFlags(int32_t faster, int32_t debugFaster) {
uint32_t flags = 0x0;
switch (faster) {
case 0: {
if (debugFaster) {
// TODO
} else {
flags = 0x0;
}
break;
}
case 1: {
flags = 0x2000 | 0x4000 | 0x8000;
break;
}
case 2:
case 3: {
flags = 0x2000;
break;
}
default: {
flags = 0x0;
}
}
return flags;
}
bool BatchDoodadsCallback(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) {
// TODO
return true;
}
bool BatchParticlesCallback(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) {
// TODO
return true;
}
bool ForceAdditiveParticleSortCallback(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) {
// TODO
return true;
}
bool M2FasterChanged(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) {
uint16_t flags = s_M2FasterDebugVar
? M2ConvertFasterFlags(SStrToInt(newValue), s_M2FasterDebugVar->GetInt())
: M2ConvertFasterFlags(SStrToInt(newValue), 0);
M2SetGlobalOptFlags(flags);
return true;
}
bool M2DebugFasterChanged(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) {
uint16_t flags = s_M2FasterVar
? M2ConvertFasterFlags(s_M2FasterVar->GetInt(), SStrToInt(newValue))
: M2ConvertFasterFlags(0, SStrToInt(newValue));
M2SetGlobalOptFlags(flags);
return true;
}
int32_t M2ConvertModelFileName(const char* source, char* dest, uint32_t a3, uint32_t a4) {
SStrCopy(dest, source, a3);
SStrLower(dest);
char* ext = OsPathFindExtensionWithDot(dest);
if ((a4 & 0x1000) != 0) {
return 1;
}
int32_t invalidExt =
!*ext || (strcmp(ext, ".mdl") && strcmp(ext, ".mdx") && strcmp(ext, ".m2"));
if (invalidExt) {
// TODO
// OsOutputDebugString("Model2: Invalid file extension: %s\n", dest);
return 0;
}
if (!strcmp(ext, ".m2")) {
return 1;
}
strcpy(ext, ".m2");
return 1;
}
CM2Scene* M2CreateScene() {
void* m = SMemAlloc(sizeof(CM2Scene), __FILE__, __LINE__, 0x0);
return new (m) CM2Scene(&CM2Cache::s_cache);
}
uint32_t M2GetCacheFlags() {
return CM2Cache::s_cache.m_flags;
}
void M2Initialize(uint16_t flags, uint32_t a2) {
CM2Cache::s_cache.Initialize(flags);
if (!a2) {
a2 = 2048;
}
uint32_t* heapId = static_cast<uint32_t*>(SMemAlloc(sizeof(uint32_t), __FILE__, __LINE__, 0));
*heapId = ObjectAllocAddHeap(sizeof(CM2Model), a2, "CM2Model", 1);
g_modelPool = heapId;
}
void M2SetGlobalOptFlags(uint16_t flags) {
flags &= (0x2000 | 0x4000 | 0x8000);
CM2Cache::s_cache.m_flags |= flags;
}
uint32_t M2RegisterCVars() {
s_M2UseZFillVar = CVar::Register(
"M2UseZFill",
"z-fill transparent objects",
0,
"1",
nullptr,
1,
false,
nullptr,
false
);
s_M2UseClipPlanesVar = CVar::Register(
"M2UseClipPlanes",
"use clip planes for sorting transparent objects",
0,
"1",
nullptr,
1,
false,
nullptr,
false
);
s_M2UseThreadsVar = CVar::Register(
"M2UseThreads",
"multithread model animations",
0,
"1",
nullptr,
1,
false,
nullptr,
false
);
s_M2BatchDoodadsVar = CVar::Register(
"M2BatchDoodads",
"combine doodads to reduce batch count",
0,
"1",
BatchDoodadsCallback,
1,
false,
nullptr,
false
);
s_M2BatchParticlesVar = CVar::Register(
"M2BatchParticles",
"combine particle emitters to reduce batch count",
0,
"1",
BatchParticlesCallback,
1,
false,
nullptr,
false
);
s_M2ForceAdditiveParticleSortVar = CVar::Register(
"M2ForceAdditiveParticleSort",
"force all particles to sort as though they were additive",
0,
"0",
ForceAdditiveParticleSortCallback,
1,
false,
nullptr,
false
);
s_M2FasterVar = CVar::Register(
"M2Faster",
"end user control of scene optimization mode - (0-3)",
0,
"1",
M2FasterChanged,
1,
false,
nullptr,
false
);
s_M2FasterDebugVar = CVar::Register(
"M2FasterDebug",
"programmer control of scene optimization mode",
0,
"0",
M2DebugFasterChanged,
1,
false,
nullptr,
false
);
uint32_t flags = 0;
if (s_M2UseZFillVar->GetInt()) {
flags |= 0x1;
}
if (s_M2UseClipPlanesVar->GetInt()) {
flags |= 0x2;
}
if (s_M2UseThreadsVar->GetInt()) {
flags |= 0x4;
}
if (s_M2BatchDoodadsVar->GetInt()) {
flags |= 0x20;
}
if (s_M2BatchParticlesVar->GetInt()) {
flags |= 0x80;
}
if (s_M2ForceAdditiveParticleSortVar->GetInt()) {
flags |= 0x100;
}
flags |= 0x8;
return flags;
}
int32_t ModelBlobQuery(const char* a1, C3Vector& a2, C3Vector& a3) {
// TODO
return 0;
}

24
src/model/Model2.hpp Normal file
View file

@ -0,0 +1,24 @@
#ifndef MODEL_MODEL2_HPP
#define MODEL_MODEL2_HPP
#include "model/CM2Light.hpp"
#include "model/CM2Model.hpp"
#include "model/CM2Scene.hpp"
class C3Vector;
int32_t M2ConvertModelFileName(const char*, char*, uint32_t, uint32_t);
CM2Scene* M2CreateScene(void);
uint32_t M2GetCacheFlags(void);
void M2Initialize(uint16_t flags, uint32_t a2);
uint32_t M2RegisterCVars();
void M2SetGlobalOptFlags(uint16_t flags);
int32_t ModelBlobQuery(const char*, C3Vector&, C3Vector&);
#endif