diff --git a/Source/Resource/Models/GrannyModelResource.h b/Source/Resource/Models/GrannyModelResource.h new file mode 100644 index 0000000..33c40a2 --- /dev/null +++ b/Source/Resource/Models/GrannyModelResource.h @@ -0,0 +1,164 @@ +#ifndef RAGNAROK_SOURCE_RESOURCE_MODELS_GRANNYMODELRESOURCE_H +#define RAGNAROK_SOURCE_RESOURCE_MODELS_GRANNYMODELRESOURCE_H + +#include "Resource/Models/ModelResource.h" + +#include +#include +#include + +class CTexture; + +struct granny_control; +struct granny_material; +struct granny_mesh_binding; +struct granny_mesh_deformer; +struct granny_model_instance; +struct granny_world_pose; + +struct granny_data_type_definition { + /* 0000 */ std::int32_t type; + /* 0004 */ const char* name; + /* 0008 */ granny_data_type_definition* referenceType; + /* 000C */ std::int32_t arrayWidth; + /* 0010 */ std::int32_t extra[3]; + /* 001C */ std::uint32_t ignored; +}; + +struct granny_pixel_layout { + /* 0000 */ std::int32_t bytesPerPixel; + /* 0004 */ std::int32_t shiftForComponent[4]; + /* 0014 */ std::int32_t bitsForComponent[4]; +}; + +struct granny_texture { + /* 0000 */ const char* fromFileName; + /* 0004 */ std::int32_t textureType; + /* 0008 */ std::int32_t width; + /* 000C */ std::int32_t height; +}; + +struct granny_skeleton { + /* 0000 */ const char* name; + /* 0004 */ std::int32_t boneCount; +}; + +struct granny_material_binding { + /* 0000 */ granny_material* material; +}; + +struct granny_mesh { + /* 0000 */ unsigned char unknown_0000[0x14]; + /* 0014 */ std::int32_t materialBindingCount; + /* 0018 */ granny_material_binding* materialBindings; +}; + +struct granny_model_mesh_binding { + /* 0000 */ granny_mesh* mesh; +}; + +struct granny_model { + /* 0000 */ const char* name; + /* 0004 */ granny_skeleton* skeleton; + /* 0008 */ unsigned char unknown_0008[0x44]; + /* 004C */ std::int32_t meshBindingCount; + /* 0050 */ granny_model_mesh_binding* meshBindings; +}; + +extern "C" { +__declspec(dllimport) extern const granny_pixel_layout GrannyRGBA8888PixelFormat; +__declspec(dllimport) extern granny_data_type_definition GrannyPNT332VertexType[]; + +__declspec(dllimport) void WINAPI GrannySetControlActive(granny_control* control, bool active); +__declspec(dllimport) void WINAPI GrannyFreeControl(granny_control* control); +__declspec(dllimport) bool WINAPI GrannyTextureHasAlpha(const granny_texture* texture); +__declspec(dllimport) void WINAPI GrannyCopyTextureImage( + const granny_texture* texture, std::int32_t imageIndex, std::int32_t mipIndex, const granny_pixel_layout* layout, std::int32_t destWidth, + std::int32_t destHeight, std::int32_t destStride, void* pixels +); +__declspec(dllimport) granny_model_instance* WINAPI GrannyInstantiateModel(const granny_model* model); +__declspec(dllimport) granny_world_pose* WINAPI GrannyNewWorldPose(std::int32_t boneCount); +__declspec(dllimport) granny_skeleton* WINAPI GrannyGetSourceSkeleton(const granny_model_instance* instance); +__declspec(dllimport) granny_mesh_binding* WINAPI GrannyNewMeshBinding(const granny_mesh* mesh, const granny_skeleton* from, const granny_skeleton* to); +__declspec(dllimport) void WINAPI GrannyFreeMeshBinding(granny_mesh_binding* binding); +__declspec(dllimport) std::int32_t WINAPI GrannyGetMeshIndexCount(const granny_mesh* mesh); +__declspec(dllimport) void WINAPI GrannyCopyMeshIndices(const granny_mesh* mesh, std::int32_t bytesPerIndex, void* indices); +__declspec(dllimport) std::int32_t WINAPI GrannyGetMeshVertexCount(const granny_mesh* mesh); +__declspec(dllimport) bool WINAPI GrannyMeshIsRigid(const granny_mesh* mesh); +__declspec(dllimport) granny_data_type_definition* WINAPI GrannyGetMeshVertexType(const granny_mesh* mesh); +__declspec(dllimport) granny_mesh_deformer* WINAPI GrannyNewMeshDeformer( + const granny_data_type_definition* inputLayout, const granny_data_type_definition* outputLayout, std::int32_t deformationType +); +__declspec(dllimport) void WINAPI GrannyCopyMeshVertices(const granny_mesh* mesh, const granny_data_type_definition* vertexType, void* vertices); +__declspec(dllimport) granny_texture* WINAPI GrannyGetMaterialTextureByType(const granny_material* material, std::int32_t type); +} + +struct GrannyTextureEntry { + /* 0000 */ const char* name; + /* 0004 */ CTexture* texture; +}; + +struct GrannyMeshEntry { + /* 0000 */ granny_mesh* mesh; + /* 0004 */ granny_mesh_binding* binding; + /* 0008 */ granny_mesh_deformer* deformer; + /* 000C */ std::uint16_t* indices; + /* 0010 */ unsigned char* vertices; + /* 0014 */ int materialCount; + /* 0018 */ GrannyTextureEntry** textures; +}; + +struct GrannyModelInstance { + /* 0000 */ granny_model_instance* instance; + /* 0004 */ granny_world_pose* worldPose; + /* 0008 */ int meshCount; + /* 000C */ GrannyMeshEntry* meshes; +}; + +class C3dGrannyModelRes : public CRes { +public: + void RebindMeshes(const C3dGrannyModelRes& target); + void InstantiateModel(GrannyModelInstance* target, const granny_model* model); + CTexture* CreateTexture(const granny_texture* texture, int option); + void FreeControl(granny_control* control); + + /* 0110 */ unsigned char unknown_0110[0x150]; + /* 0260 */ void* file; + /* 0264 */ int textureCount; + /* 0268 */ GrannyTextureEntry* textures; + /* 026C */ int modelCount; + /* 0270 */ GrannyModelInstance* instance; + /* 0274 */ int maxBoneCount; + /* 0278 */ void* localPose; + /* 027C */ int deformBufferSize; + /* 0280 */ int unknown_0280; + /* 0284 */ unsigned char unknown_0284[8]; + /* 028C */ char fileName[MAX_PATH]; + /* 0390 */ const granny_model* model; + /* 0394 */ int unknown_0394; + /* 0398 */ int unknown_0398; + /* 039C */ int unknown_039C; + /* 03A0 */ float unknown_03A0; + /* 03A4 */ float unknown_03A4; + /* 03A8 */ void* animations; + /* 03AC */ int job; + /* 03B0 */ int unknown_03B0; + /* 03B4 */ granny_skeleton* skeleton; + +private: + ModelResourceContext* m_context; +}; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(offsetof(granny_texture, width) == 0x08); +static_assert(offsetof(granny_mesh, materialBindings) == 0x18); +static_assert(offsetof(granny_model, meshBindingCount) == 0x4C); +static_assert(sizeof(GrannyMeshEntry) == 0x1C); +static_assert(sizeof(GrannyModelInstance) == 0x10); +static_assert(offsetof(C3dGrannyModelRes, file) == 0x260); +static_assert(offsetof(C3dGrannyModelRes, fileName) == 0x28C); +static_assert(offsetof(C3dGrannyModelRes, model) == 0x390); +static_assert(offsetof(C3dGrannyModelRes, skeleton) == 0x3B4); +#endif + +#endif diff --git a/Source/Resource/Models/ModelResource.cpp b/Source/Resource/Models/ModelResource.cpp index 1199ebf..9df0825 100644 --- a/Source/Resource/Models/ModelResource.cpp +++ b/Source/Resource/Models/ModelResource.cpp @@ -3,6 +3,7 @@ #include "Render/Textures/TextureCache.h" #include "Resource/Bitmap/BitmapResource.h" #include "Resource/Models/AnimatedModelResource.h" +#include "Resource/Models/GrannyModelResource.h" #include "Resource/Models/ModelResource.h" #include "Resource/ResourceByteReader.h" @@ -306,3 +307,128 @@ bool C3dModelResV1::Load(const char* name) { return true; } + +// OFFSET: 0x007577E0 +// FUNCTION: fn_08006 +void C3dGrannyModelRes::RebindMeshes(const C3dGrannyModelRes& target) { + for (int index = 0; index < this->instance->meshCount; ++index) { + GrannyMeshEntry& entry = this->instance->meshes[index]; + GrannyFreeMeshBinding(entry.binding); + entry.binding = GrannyNewMeshBinding(entry.mesh, this->skeleton, target.skeleton); + } +} + +// OFFSET: 0x00758E40 +// FUNCTION: fn_08009 +void C3dGrannyModelRes::InstantiateModel(GrannyModelInstance* target, const granny_model* model) { + const int boneCount = model->skeleton->boneCount; + if (this->maxBoneCount < boneCount) { + this->maxBoneCount = boneCount; + } + + target->instance = GrannyInstantiateModel(model); + target->worldPose = GrannyNewWorldPose(boneCount); + target->meshCount = model->meshBindingCount; + target->meshes = new GrannyMeshEntry[target->meshCount]; + + for (int index = 0; index < target->meshCount; ++index) { + GrannyMeshEntry& entry = target->meshes[index]; + granny_mesh* mesh = model->meshBindings[index].mesh; + entry.mesh = mesh; + granny_skeleton* skeleton = GrannyGetSourceSkeleton(target->instance); + entry.binding = GrannyNewMeshBinding(mesh, skeleton, skeleton); + + const unsigned int indexCount = GrannyGetMeshIndexCount(mesh); + entry.indices = new std::uint16_t[indexCount]; + if (indexCount != 0) { + GrannyCopyMeshIndices(mesh, 2, entry.indices); + } + + const int vertexBytes = GrannyGetMeshVertexCount(mesh) * 32; + entry.vertices = nullptr; + if (!GrannyMeshIsRigid(mesh)) { + if (this->deformBufferSize < vertexBytes) { + this->deformBufferSize = vertexBytes; + } + + entry.deformer = GrannyNewMeshDeformer(GrannyGetMeshVertexType(mesh), GrannyPNT332VertexType, 2); + if (!entry.deformer) { + ShowErrorDialog(this->m_context->renderSystem, "Granny didn't find a matching deformer for the vertex format used by mesh", 0); + } + } else { + entry.vertices = new unsigned char[vertexBytes]; + GrannyCopyMeshVertices(mesh, GrannyPNT332VertexType, entry.vertices); + entry.deformer = nullptr; + } + + entry.materialCount = mesh->materialBindingCount; + entry.textures = new GrannyTextureEntry*[entry.materialCount]; + + for (int material = 0; material < entry.materialCount; ++material) { + GrannyTextureEntry* found = nullptr; + const granny_texture* texture = GrannyGetMaterialTextureByType(mesh->materialBindings[material].material, 2); + if (texture) { + for (int textureIndex = 0; textureIndex < this->textureCount; ++textureIndex) { + if (std::strcmp(this->textures[textureIndex].name, texture->fromFileName) == 0) { + found = &this->textures[textureIndex]; + break; + } + } + } + + entry.textures[material] = found; + } + } +} + +// OFFSET: 0x00759090 +// FUNCTION: fn_08010 +CTexture* C3dGrannyModelRes::CreateTexture(const granny_texture* texture, int) { + const bool hasAlpha = GrannyTextureHasAlpha(texture); + const int width = texture->width; + const int height = texture->height; + unsigned char* source = new unsigned char[height * width * 4]; + unsigned char* pixels = new unsigned char[height * width * 4]; + GrannyCopyTextureImage(texture, 0, 0, &GrannyRGBA8888PixelFormat, width, height, width * 4, source); + + int format; + if (!hasAlpha) { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const int offset = (y * width + x) * 4; + pixels[offset + 0] = source[offset + 2]; + pixels[offset + 1] = source[offset + 1]; + pixels[offset + 2] = source[offset + 0]; + pixels[offset + 3] = 0xFF; + } + } + + format = 0; + } else { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + const int offset = (y * width + x) * 4; + pixels[offset + 0] = source[offset + 2]; + pixels[offset + 1] = source[offset + 1]; + pixels[offset + 2] = source[offset + 0]; + pixels[offset + 3] = source[offset + 3]; + } + } + + format = 1; + } + + CTexture* result = this->m_context->textures.Upload(width, height, reinterpret_cast(pixels), format, 0, false, false, false); + delete[] pixels; + delete[] source; + return result; +} + +// OFFSET: 0x00759330 +// FUNCTION: fn_08011 +void C3dGrannyModelRes::FreeControl(granny_control* control) { + if (control) { + GrannySetControlActive(control, false); + GrannyFreeControl(control); + } +}