initial commit

This commit is contained in:
Ian Brown 2020-02-06 00:23:46 +00:00
parent f2088d1762
commit 593cc0730b
48 changed files with 2914 additions and 2 deletions

84
.gitignore vendored Normal file
View file

@ -0,0 +1,84 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
roms
Network Trash Folder
Temporary Items
.apdisk
# Windows
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
*.db
Debug/
Release/
/.vs
build

19
BoostTestHelpers.cmake Normal file
View file

@ -0,0 +1,19 @@
function(add_boost_test SOURCE_FILE_NAME DEPENDENCY_LIB1 DEPENDENCY_LIB2)
get_filename_component(TEST_EXECUTABLE_NAME ${SOURCE_FILE_NAME} NAME_WE)
add_executable(${TEST_EXECUTABLE_NAME} ${SOURCE_FILE_NAME})
target_link_libraries(${TEST_EXECUTABLE_NAME}
${DEPENDENCY_LIB1} ${DEPENDENCY_LIB2} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
file(READ "${SOURCE_FILE_NAME}" SOURCE_FILE_CONTENTS)
string(REGEX MATCHALL "BOOST_AUTO_TEST_CASE\\( *([A-Za-z_0-9]+) *\\)"
FOUND_TESTS ${SOURCE_FILE_CONTENTS})
foreach(HIT ${FOUND_TESTS})
string(REGEX REPLACE ".*\\( *([A-Za-z_0-9]+) *\\).*" "\\1" TEST_NAME ${HIT})
add_test(NAME "${TEST_EXECUTABLE_NAME}.${TEST_NAME}"
COMMAND ${TEST_EXECUTABLE_NAME}
--run_test=${TEST_NAME} --catch_system_error=yes)
endforeach()
endfunction()

85
CMakeLists.txt Normal file
View file

@ -0,0 +1,85 @@
# travis has cmake 3.9
cmake_minimum_required(VERSION 3.9)
project(FROSTBITE)
enable_testing()
set(PROJECT_NAME "Frostbite")
set(PROJECT_Version 0.1)
set(PROJECT_LIBS)
set(COVERAGE OFF CACHE BOOL "Coverage")
set(Boost_USE_STATIC_LIBS ON)
find_package(Boost REQUIRED COMPONENTS program_options filesystem)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
endif()
find_package(GLM REQUIRED)
include_directories(${GLM_INCLUDE_DIR})
find_package(SDL2 REQUIRED)
find_package(GLEW REQUIRED)
include_directories(${GLEW_INCLUDE_DIR})
# flags
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /MP")
endif()
if (COVERAGE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
endif()
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING
"Choose the type of build, options are: None Debug Release"
FORCE)
endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
if(WIN32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
add_definitions(-D_UNICODE -DUNICODE)
endif()
set(SNOWLIB_SRC_FILES
snowlib/AnimationProcessor.cpp
snowlib/AnimData.cpp
snowlib/AnimDecoder.cpp
snowlib/FntDecoder.cpp
snowlib/GameType.h
snowlib/GIFTag.cpp
snowlib/GobFile.cpp
snowlib/Helpers.cpp
snowlib/LmpFile.cpp
snowlib/LmpRepository.cpp
snowlib/Logger.cpp
snowlib/Mesh.cpp
snowlib/TexDecoder.cpp
snowlib/VifDecoder.cpp
snowlib/World.cpp
snowlib/WorldReader.cpp
)
include_directories("${FROSTBITE_SOURCE_DIR}/snowlib")
set(TESTAPP_SRC_FILES
TestApp/Main.cpp
TestApp/ModelWindow.cpp
)
add_library(snowlib STATIC ${SNOWLIB_SRC_FILES})
add_executable(testApp ${TESTAPP_SRC_FILES})
target_link_libraries(testApp snowlib ${Boost_LIBRARIES} ${GLEW_LIBRARIES} SDL2::SDL2 SDL2::SDL2main opengl32.lib)
#include(BoostTestHelpers.cmake)

16
CMakeSettings.json Normal file
View file

@ -0,0 +1,16 @@
{
// See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file.
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "-v",
"ctestCommandArgs": ""
}
]
}

View file

@ -1,2 +1,22 @@
# frostbite
experimental implementation of the snowblind engine
Building
========
For windows, I use vcpkg to install packages. See https://github.com/Microsoft/vcpkg
you will want to install the following packages as follows:
```
vcpkg install sdl2:x64-windows
vcpkg install glm:x64-windows
vcpkg install glew:x64-windows
vcpkg install boost:x64-windows
```
Then you can open the directory in visual studio and it will be recognised as a cmake project.
Whether you use the open folder functionality or call cmake on the command line you will need to
set the toolchain file to the vcpkg one. This lives in the vcpkg directory under `scripts\buildsystems\vcpkg.cmake`
https://github.com/microsoft/vcpkg/blob/master/docs/examples/installing-and-using-packages.md#cmake-toolchain-file
note that if you do `vcpkg integrate install` then you don't need to set the toolchain when using visual studio 2019

120
TestApp/Main.cpp Normal file
View file

@ -0,0 +1,120 @@
#pragma once
#include "SDL.h"
#include "ModelWindow.h"
#include "windows.h"
#include "LmpRepository.h"
#include "GobFile.h"
#include "VifDecoder.h"
#include "TexDecoder.h"
#include "AnimDecoder.h"
#include "Model.h"
#include "Texture.h"
#include "AnimData.h"
#include "WorldReader.h"
#include "World.h"
LmpRepository* lmpRepository;
TexturedMesh* readTexturedMesh(string dataPath, const char* lmpName, const char* modelName)
{
LmpFile* lmpFile = lmpRepository->getLmp(lmpName);
LmpEntry* vifEntry = lmpFile->findEntry(modelName, ".vif");
LmpEntry* texEntry = lmpFile->findEntry(modelName, ".tex");
TexturedMesh* texturedMesh = new TexturedMesh();
VifDecoder vifDecoder;
TexDecoder texDecoder;
texturedMesh->meshList = vifDecoder.decode(vifEntry->data, vifEntry->length);
texturedMesh->texture = texDecoder.decode(texEntry->data, 0);
for (auto& mesh : *texturedMesh->meshList){
mesh->adjustUVs(texturedMesh->texture->widthPixels, texturedMesh->texture->heightPixels);
}
return texturedMesh;
}
Model* readModel(string dataPath, const char* lmpName, vector<string> meshNames, const char *anmName)
{
Model* model = new Model();
for (string meshName : meshNames){
TexturedMesh* texturedMesh = readTexturedMesh(dataPath, lmpName, meshName.c_str());
model->texturedMeshList.push_back(texturedMesh);
}
LmpFile* lmpFile = lmpRepository->getLmp(lmpName);
LmpEntry* anmEntry = anmName == nullptr ? nullptr : lmpFile->findEntry(anmName, ".anm");
if (anmEntry != nullptr){
AnimDecoder animDecoder;
model->animData = animDecoder.decode(anmEntry->data, anmEntry->length);
} else {
model->animData = nullptr;
}
return model;
}
int main(int argc, char **argv) {
string dataPath = "D:\\emu\\bgda\\BG\\DATA\\";
if (argc == 2){
dataPath = argv[1];
}
lmpRepository = new LmpRepositoryImpl(dataPath, GameType::DARK_ALLIANCE);
GobFile cuttownGob = GobFile(dataPath + "CUTTOWN.GOB", GameType::DARK_ALLIANCE);
World* world = WorldReader().readWorld(&cuttownGob, "cuttown");
const char* lmpName = "DWARF.LMP";
vector<string> meshNames;
meshNames.push_back("dwarf");
meshNames.push_back("hair");
// Model* model = readModel(dataPath, "CHEST.LMP", "chest_large", "chest_large_open");
Model* model = readModel(dataPath, lmpName, meshNames, "l_idlea");
vector<string> animNames = lmpRepository->getLmp(lmpName)->findFilenamesByExtension(".anm");
ModelWindow modelWindow;
modelWindow.init();
int maxFrame = model->animData->NumFrames;
float targetFrameRate = 60.0f / 1000.0f; // frames per millisecond
unsigned int animStartClock = SDL_GetTicks();
bool done=false;
while (!done){
unsigned int tick = SDL_GetTicks();
SDL_Event event;
while (SDL_PollEvent(&event)){
if (event.type == SDL_QUIT){
done = true;
} else if (event.type == SDL_KEYDOWN){
if (event.key.keysym.sym == SDLK_ESCAPE){
done = true;
}
}
}
unsigned int tock = SDL_GetTicks();
int animMillis = tock - animStartClock;
int frame = (int)(animMillis * targetFrameRate) % maxFrame;
modelWindow.drawFrame(model, frame);
unsigned int deltaclock = SDL_GetTicks() - tick;
if ( deltaclock != 0 ){
unsigned int currentFPS = 1000 / deltaclock;
char fpsString[64];
sprintf(fpsString, "FPS: %d\n", currentFPS);
OutputDebugStringA(fpsString);
}
}
modelWindow.exit();
return 0;
}

329
TestApp/ModelWindow.cpp Normal file
View file

@ -0,0 +1,329 @@
#include "ModelWindow.h"
#include "SDL.h"
#include "Mesh.h"
#include "Model.h"
#include "Texture.h"
#include "Logger.h"
#include "glm/glm.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include <assert.h>
#include "AnimationProcessor.h"
#define check() {\
GLenum error = glGetError(); \
if (error != 0){ \
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Gl Error: %d\n", error); \
assert(false); \
} \
}
float cubeVertices[] = {
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f
};
float cubeColors[] = {
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 0.5f, 0.0f, 1.0f,
1.0f, 0.5f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f, 1.0f
};
GLuint cubeIndices[] = {
0, 4, 5, 0, 5, 1,
1, 5, 6, 1, 6, 2,
2, 6, 7, 2, 7, 3,
3, 7, 4, 3, 4, 0,
4, 7, 6, 4, 6, 5,
3, 0, 1, 3, 1, 2
};
float cubeRotation = 0.0f;
void ModelWindow::drawCube()
{
// Now render to the main frame buffer
glBindFramebuffer(GL_FRAMEBUFFER,0);
// Clear the background (not really necessary I suppose)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
check();
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -10.0f);
glRotatef(cubeRotation, 1.0f, 1.0f, 1.0f);
check();
glFrontFace(GL_CW);
check();
glVertexPointer(3, GL_FLOAT, 0, cubeVertices);
glColorPointer(4, GL_FLOAT, 0, cubeColors);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, cubeIndices);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glLoadIdentity();
glBindBuffer(GL_ARRAY_BUFFER, 0);
check();
glFlush();
glFinish();
check();
SDL_GL_SwapWindow(window);
cubeRotation -= 0.15f;
}
const char* fragmentShaderCode =
"#version 120\n"
"varying vec2 UV;\n"
"uniform sampler2D myTextureSampler;\n"
"void main(){\n"
" gl_FragColor = texture2D( myTextureSampler, UV );\n"
"// gl_FragColor = vec4 (0.0, 1.0, 0.0, 1.0);\n"
"}\n";
const char* vertexShaderCode =
"#version 120\n"
"attribute vec3 vertexPosition_modelspace;\n"
"attribute vec2 vertexUV;\n"
"varying vec2 UV;\n"
"uniform mat4 MVP;\n"
"void main(){\n"
" gl_Position = MVP * vec4(vertexPosition_modelspace,1);\n"
" UV = vertexUV;\n"
"}";
GLuint LoadShaders()
{
GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
GLint Result = GL_FALSE;
int InfoLogLength;
glShaderSource(VertexShaderID, 1, &vertexShaderCode , NULL);
glCompileShader(VertexShaderID);
check();
glShaderSource(FragmentShaderID, 1, &fragmentShaderCode , NULL);
glCompileShader(FragmentShaderID);
glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> ProgramErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", &ProgramErrorMessage[0]);
Logger::getLogger()->log(&ProgramErrorMessage[0]);
}
check();
GLuint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VertexShaderID);
glAttachShader(ProgramID, FragmentShaderID);
check();
glLinkProgram(ProgramID);
// Check the program
glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> ProgramErrorMessage(InfoLogLength+1);
glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", &ProgramErrorMessage[0]);
Logger::getLogger()->log(&ProgramErrorMessage[0]);
}
check();
glDeleteShader(VertexShaderID);
glDeleteShader(FragmentShaderID);
check();
return ProgramID;
}
void ModelWindow::drawMesh(Model* model, int frame)
{
AnimationProcessor animationProcessor;
glColor3f(1.0f,1.0f,1.0f);
// Now render to the main frame buffer
glBindFramebuffer(GL_FRAMEBUFFER,0);
// Clear the background (not really necessary I suppose)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
check();
//glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
check();
// Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 1000 units
glm::mat4 Projection = glm::perspective(45.0f, (float)screen_width / screen_height, 0.1f, 1000.0f);
// Camera matrix
glm::mat4 View = glm::lookAt(
glm::vec3(0,-100,50), // Camera is at (0,-100,50), in World Space
glm::vec3(0,0,30), // and looks at
glm::vec3(0,0,1) // Head is up
);
// Model matrix : an identity matrix (model will be at the origin)
glm::mat4 Model = glm::mat4(1.0f);
// Our ModelViewProjection : multiplication of our 3 matrices
glm::mat4 MVP = Projection * View * Model; // Remember, matrix multiplication is the other way around
GLuint MatrixID = glGetUniformLocation(shaderProgramID, "MVP");
check();
GLuint vertexPosition_modelspaceID = glGetAttribLocation(shaderProgramID, "vertexPosition_modelspace");
GLuint vertexUVID = glGetAttribLocation(shaderProgramID, "vertexUV");
GLuint textureSamplerID = glGetUniformLocation(shaderProgramID, "myTextureSampler");
check();
for (TexturedMesh* texturedMesh : model->texturedMeshList){
Texture* texture = texturedMesh->texture;
// Create one OpenGL texture
GLuint textureID;
glGenTextures(1, &textureID);
glActiveTexture(GL_TEXTURE0);
// "Bind" the newly created texture : all future texture functions will modify this texture
glBindTexture(GL_TEXTURE_2D, textureID);
// Give the image to OpenGL
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, texture->widthPixels, texture->heightPixels, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture->data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
check();
glUseProgram(shaderProgramID);
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
glUniform1i(textureSamplerID, 0);
for (auto& mesh : *texturedMesh->meshList)
{
FloatVector* animatedPositions = animationProcessor.softwareSkin(mesh, model->animData, frame);
GLuint vertexbuffer;
glGenBuffers(1, &vertexbuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, mesh->numVertices * 12, animatedPositions != nullptr ? animatedPositions : mesh->positions, GL_STATIC_DRAW);
GLuint uvbuffer;
glGenBuffers(1, &uvbuffer);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
glBufferData(GL_ARRAY_BUFFER, mesh->numVertices * 8, mesh->uvCoords, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
vertexPosition_modelspaceID, // The attribute we want to configure
3, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
// 2nd attribute buffer : UVs
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
glVertexAttribPointer(
vertexUVID, // The attribute we want to configure
2, // size : U+V => 2
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
glVertexPointer(3, GL_FLOAT, 0, mesh->positions);
glDrawElements(GL_TRIANGLES, mesh->triangleIndices.size(), GL_UNSIGNED_INT, mesh->triangleIndices.data());
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDeleteBuffers(1, &vertexbuffer);
glDeleteBuffers(1, &uvbuffer);
delete[] animatedPositions;
}
glDeleteTextures(1, &textureID);
}
glLoadIdentity();
glFlush();
glFinish();
check();
SDL_GL_SwapWindow(window);
cubeRotation -= 0.015f;
}
bool ModelWindow::init()
{
bool success = true;
/* Enable standard application logging */
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
/* Initialize SDL for video output */
if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to initialize SDL: %s\n", SDL_GetError());
success = false;
}
if (success){
/* Create a 640x480 OpenGL screen */
window = SDL_CreateWindow( "Model Viewer", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL );
if ( !window ) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create OpenGL window: %s\n", SDL_GetError());
success = false;
}
}
if (success && !SDL_GL_CreateContext(window)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create OpenGL context: %s\n", SDL_GetError());
success = false;
}
if (success){
GLenum err = glewInit();
if (GLEW_OK != err){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GLEW init failed\n");
success = false;
}
}
if (success){
glClearColor(0.15f, 0.25f, 0.35f, 1.0f);
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode(GL_PROJECTION);
shaderProgramID = LoadShaders();
glEnable(GL_DEPTH_TEST);
// Accept fragment if it closer to the camera than the former one
glDepthFunc(GL_LESS);
}
return success;
}
void ModelWindow::exit()
{
glDeleteProgram(shaderProgramID);
SDL_Quit();
}
void ModelWindow::drawFrame(Model* model, int frame)
{
if (nullptr == model){
drawCube();
} else {
drawMesh(model, frame);
}
}

48
TestApp/ModelWindow.h Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include "GL/glew.h"
#include "SDL_opengl.h"
#include "SDL.h"
class Mesh;
class Model;
class ModelWindow
{
uint32_t screen_width;
uint32_t screen_height;
SDL_Window *window;
GLuint verbose;
GLuint vshader;
GLuint fshader;
GLuint mshader;
GLuint program;
GLuint program2;
GLuint tex_fb;
GLuint tex;
GLuint buf;
// julia attribs
GLuint unif_color, attr_vertex, unif_scale, unif_offset, unif_tex, unif_centre;
// mandelbrot attribs
GLuint attr_vertex2, unif_scale2, unif_offset2, unif_centre2;
GLuint shaderProgramID;
public:
ModelWindow()
{
screen_width=640; screen_height=480;
}
bool init();
void exit();
void drawFrame(Model* model, int frame);
private:
void drawCube();
void drawMesh(Model* model, int frame);
};

69
snowlib/AnimData.cpp Normal file
View file

@ -0,0 +1,69 @@
#include "AnimData.h"
#include "glm/gtc/matrix_transform.hpp"
void AnimData::BuildPerFrameFKPoses()
{
perFrameFKPoses = new AnimPose[NumFrames * NumBones];
glm::vec3* parentPoints = new glm::vec3[64];
glm::quat* parentRotations = new glm::quat[64];
for (int frame = 0; frame < NumFrames; ++frame) {
for (int jointNum = 0; jointNum < NumBones; ++jointNum) {
int parentIndex = skeletonDef[jointNum];
const glm::vec3& parentPos = parentPoints[parentIndex];
const glm::quat& parentRot = parentRotations[parentIndex];
// The world position of the child joint is the local position of the child joint rotated by the
// world rotation of the parent and then offset by the world position of the parent.
const AnimPose& pose = perFramePoses[frame * NumBones + jointNum];
glm::mat3 m = glm::mat3_cast(parentRot);
glm::vec3 thisPos = m * pose.Position;
thisPos += parentPos;
// The world rotation of the child joint is the world rotation of the parent rotated by the local rotation of the child.
glm::quat thisRot = glm::normalize(parentRot * glm::normalize(pose.Rotation));
AnimPose fkPose;
fkPose.Position = thisPos;
fkPose.Rotation = thisRot;
perFrameFKPoses[frame * NumBones + jointNum] = fkPose;
parentPoints[parentIndex + 1] = fkPose.Position;
parentRotations[parentIndex + 1] = fkPose.Rotation;
}
}
}
void AnimData::BuildPerFramePoses()
{
perFramePoses = new AnimPose[NumFrames * NumBones];
for (auto& pose : MeshPoses)
{
perFramePoses[pose.FrameNum * NumBones + pose.BoneNum] = pose;
}
for (int bone = 0; bone < NumBones; ++bone)
{
for (int frame = 1; frame < NumFrames; ++frame)
{
const AnimPose& prevPose = perFramePoses[(frame-1) * NumBones + bone];
if (perFramePoses[frame * NumBones + bone].BoneNum < 0)
{
int frameDiff = frame - prevPose.FrameNum;
float avCoeff = frameDiff / 131072.0f;
glm::quat rotDelta = prevPose.AngularVelocity * avCoeff;
float velCoeff = frameDiff / 512.0f;
glm::vec3 posDelta = prevPose.Velocity * velCoeff;
AnimPose& pose = perFramePoses[frame * NumBones + bone];
pose.BoneNum = bone;
pose.FrameNum = frame;
pose.Position = prevPose.Position + posDelta;
pose.Rotation = glm::normalize(prevPose.Rotation + rotDelta);
pose.AngularVelocity = prevPose.AngularVelocity;
pose.Velocity = prevPose.Velocity;
}
}
}
}

61
snowlib/AnimData.h Normal file
View file

@ -0,0 +1,61 @@
#pragma once
#include <string>
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
struct AnimPose
{
AnimPose()
{
// Signal uninitialised
BoneNum = -1;
FrameNum = -1;
}
glm::vec3 Position;
glm::quat Rotation;
glm::quat AngularVelocity;
glm::vec3 Velocity;
int BoneNum;
int FrameNum;
};
class AnimData
{
public:
int NumBones;
int NumFrames;
int Offset4Val;
int Offset14Val;
int Offset18Val; // These are 4 bytes which are all ored together
int* skeletonDef;
glm::vec3* bindingPose;
std::string Other;
std::vector<AnimPose> MeshPoses;
// With forward kinematics applied, Array numFrames * numBones
AnimPose* perFrameFKPoses;
AnimPose& getFKPose(int frame, int bone)
{
return perFrameFKPoses[frame * NumBones + bone];
}
void BuildPerFrameFKPoses();
void BuildPerFramePoses();
private:
// Array numFrames * numBones
AnimPose* perFramePoses;
};

154
snowlib/AnimDecoder.cpp Normal file
View file

@ -0,0 +1,154 @@
#include "AnimDecoder.h"
#include "AnimData.h"
#include "DataUtil.h"
AnimData* AnimDecoder::decode(const unsigned char* data, int length)
{
// Decode BGDA
int endIndex = length;
AnimData* animData = new AnimData();
animData->NumBones = DataUtil::getLEInt(data, 0);
animData->Offset4Val = DataUtil::getLEInt(data, 4);
animData->Offset14Val = DataUtil::getLEInt(data, 0x14);
animData->Offset18Val = DataUtil::getLEInt(data, 0x18);
int offset8Val = DataUtil::getLEInt(data, 8);
int bindingPoseOffset = DataUtil::getLEInt(data, 0x0C);
animData->bindingPose = new glm::vec3[animData->NumBones];
for (int i = 0; i < animData->NumBones; ++i)
{
animData->bindingPose[i].x = -DataUtil::getLEShort(data, bindingPoseOffset + i * 8 + 0) / 64.0f;
animData->bindingPose[i].y = -DataUtil::getLEShort(data, bindingPoseOffset + i * 8 + 2) / 64.0f;
animData->bindingPose[i].z = -DataUtil::getLEShort(data, bindingPoseOffset + i * 8 + 4) / 64.0f;
}
// Skeleton structure
int offset10Val = DataUtil::getLEInt(data, 0x10);
animData->skeletonDef = new int[animData->NumBones];
for (int i = 0; i < animData->NumBones; ++i)
{
animData->skeletonDef[i] = data[offset10Val + i];
}
AnimPose* curPose = new AnimPose[animData->NumBones];
for (int boneNum = 0; boneNum < animData->NumBones; ++boneNum)
{
AnimPose& pose = curPose[boneNum];
pose.BoneNum = boneNum;
pose.FrameNum = 0;
int frameOff = offset8Val + boneNum * 0x0e;
pose.Position.x = DataUtil::getLEShort(data, frameOff) / 64.0f;
pose.Position.y = DataUtil::getLEShort(data, frameOff + 2) / 64.0f;
pose.Position.z = DataUtil::getLEShort(data, frameOff + 4) / 64.0f;
pose.Rotation.w = DataUtil::getLEShort(data, frameOff + 6) / 4096.0f;
pose.Rotation.x = DataUtil::getLEShort(data, frameOff + 8) / 4096.0f;
pose.Rotation.y = DataUtil::getLEShort(data, frameOff + 0x0A) / 4096.0f;
pose.Rotation.z = DataUtil::getLEShort(data, frameOff + 0x0C) / 4096.0f;
pose.Velocity.x = pose.Velocity.y = pose.Velocity.z = 0.0f;
pose.AngularVelocity.w = pose.AngularVelocity.x = pose.AngularVelocity.y = pose.AngularVelocity.z = 0.0f;
// This may give us duplicate frame zero poses, but that's ok.
animData->MeshPoses.push_back(pose);
}
int* curAngVelFrame = (int*)calloc(animData->NumBones, sizeof(int));
int* curVelFrame = (int*)calloc(animData->NumBones, sizeof(int));
animData->NumFrames = 1;
int totalFrame = 0;
int otherOff = offset8Val + animData->NumBones * 0x0e;
AnimPose* pose = nullptr;
while (otherOff < endIndex) {
int count = data[otherOff++];
unsigned char byte2 = data[otherOff++];
int boneNum = byte2 & 0x3f;
if (boneNum == 0x3f) break;
totalFrame += count;
if (pose == nullptr || pose->FrameNum != totalFrame || pose->BoneNum != boneNum)
{
if (pose != nullptr)
{
animData->MeshPoses.push_back(*pose);
}
delete pose;
pose = new AnimPose();
pose->FrameNum = totalFrame;
pose->BoneNum = boneNum;
pose->Position = curPose[boneNum].Position;
pose->Rotation = curPose[boneNum].Rotation;
pose->AngularVelocity = curPose[boneNum].AngularVelocity;
pose->Velocity = curPose[boneNum].Velocity;
}
// bit 7 specifies whether to read 4 (set) or 3 elements following
// bit 6 specifies whether they are shorts or bytes (set).
if ((byte2 & 0x80) == 0x80) {
int w, x, y, z;
if ((byte2 & 0x40) == 0x40) {
w = (char)data[otherOff++];
x = (char)data[otherOff++];
y = (char)data[otherOff++];
z = (char)data[otherOff++];
} else {
w = DataUtil::getLEShort(data, otherOff);
x = DataUtil::getLEShort(data, otherOff+2);
y = DataUtil::getLEShort(data, otherOff+4);
z = DataUtil::getLEShort(data, otherOff+6);
otherOff += 8;
}
glm::quat angVel(w, x, y, z);
glm::quat prevAngVel = pose->AngularVelocity;
float coeff = (totalFrame - curAngVelFrame[boneNum]) / 131072.0f;
glm::quat angDelta = prevAngVel * coeff;
pose->Rotation = pose->Rotation + angDelta;
pose->FrameNum = totalFrame;
pose->AngularVelocity = angVel;
curPose[boneNum].Rotation = pose->Rotation;
curPose[boneNum].AngularVelocity = pose->AngularVelocity;
curAngVelFrame[boneNum] = totalFrame;
}
else
{
int x, y, z;
if ((byte2 & 0x40) == 0x40) {
x = (char)data[otherOff++];
y = (char)data[otherOff++];
z = (char)data[otherOff++];
} else {
x = DataUtil::getLEShort(data, otherOff);
y = DataUtil::getLEShort(data, otherOff + 2);
z = DataUtil::getLEShort(data, otherOff + 4);
otherOff += 6;
}
glm::vec3 vel(x, y, z);
glm::vec3 prevVel = pose->Velocity;
float coeff = (totalFrame - curVelFrame[boneNum]) / 512.0f;
glm::vec3 posDelta = prevVel * coeff;
pose->Position = pose->Position + posDelta;
pose->FrameNum = totalFrame;
pose->Velocity = vel;
curPose[boneNum].Position = pose->Position;
curPose[boneNum].Velocity = pose->Velocity;
curVelFrame[boneNum] = totalFrame;
}
}
animData->MeshPoses.push_back(*pose);
delete pose;
free(curAngVelFrame);
free(curVelFrame);
animData->NumFrames = totalFrame+1;
animData->BuildPerFramePoses();
animData->BuildPerFrameFKPoses();
return animData;
}

9
snowlib/AnimDecoder.h Normal file
View file

@ -0,0 +1,9 @@
#pragma once
class AnimData;
class AnimDecoder
{
public:
AnimData* decode(const unsigned char* data, int length);
};

View file

@ -0,0 +1,102 @@
#include "AnimationProcessor.h"
#include "Mesh.h"
#include "AnimData.h"
#include <glm/gtc/matrix_transform.hpp>
FloatVector* AnimationProcessor::softwareSkin(Mesh* mesh, AnimData* animData, int frame)
{
FloatVector* animatedPositions;
if (animData == nullptr || frame < 0 || mesh->vertexWeights.empty()){
animatedPositions = nullptr;
} else {
auto vwIterator = mesh->vertexWeights.begin();
VertexWeight* vw = &*vwIterator++;
animatedPositions = new FloatVector[mesh->numVertices];
for (int vnum=0; vnum<mesh->numVertices; ++vnum){
if (vw->endVertex < vnum)
{
vw = &*vwIterator++;
}
glm::vec4 vpos = glm::vec4(mesh->positions[vnum].x, mesh->positions[vnum].y, mesh->positions[vnum].z, 1.0f);
int bone1No = vw->bone1;
glm::vec3 bindingPos1 = animData->bindingPose[bone1No];
const AnimPose& bone1Pose = animData->getFKPose(frame, bone1No);
glm::vec3 joint1Pos = bone1Pose.Position;
glm::vec3 inverseBindingPos1 = -bindingPos1;
glm::mat4 m1 = glm::translate(glm::mat4(1.0f), inverseBindingPos1);
m1 = glm::mat4_cast(bone1Pose.Rotation) * m1;
glm::vec4 vpos1 = m1 * vpos;
m1 = glm::translate(glm::mat4(1.0f), bone1Pose.Position);
vpos1 = m1 * vpos1;
if (vw->bone2 == 0xFF) {
vpos = vpos1;
} else {
// multi-bone
int bone2No = vw->bone2;
glm::vec3 bindingPos2 = animData->bindingPose[bone2No];
const AnimPose& bone2Pose = animData->getFKPose(frame, bone2No);
glm::vec3 joint2Pos = bone2Pose.Position;
glm::vec3 inverseBindingPos2 = -bindingPos2;
glm::mat4 m2 = glm::translate(glm::mat4(1.0f), inverseBindingPos2);
m2 = glm::mat4_cast(bone2Pose.Rotation) * m2;
glm::vec4 vpos2 = m2 * vpos;
m2 = glm::translate(glm::mat4(1.0f), bone2Pose.Position);
vpos2 = m2 * vpos2;
if (vw->bone3 == 0xFF) {
float boneSum = (float)(vw->boneWeight1 + vw->boneWeight2);
float bone1Coeff = vw->boneWeight1 / boneSum;
float bone2Coeff = vw->boneWeight2 / boneSum;
vpos = vpos1 * bone1Coeff + vpos2 * bone2Coeff;
} else {
int bone3No = vw->bone3;
glm::vec3 bindingPos3 = animData->bindingPose[bone3No];
const AnimPose& bone3Pose = animData->getFKPose(frame, bone3No);
glm::vec3 joint3Pos = bone3Pose.Position;
glm::vec3 inverseBindingPos3 = -bindingPos3;
glm::mat4 m3 = glm::translate(glm::mat4(1.0f), inverseBindingPos3);
m3 = glm::mat4_cast(bone3Pose.Rotation) * m3;
glm::vec4 vpos3 = m3 * vpos;
m3 = glm::translate(glm::mat4(1.0f), bone3Pose.Position);
vpos3 = m3 * vpos3;
if (vw->bone4 == 0xFF) {
float boneSum = (float)(vw->boneWeight1 + vw->boneWeight2 + vw->boneWeight3);
float bone1Coeff = vw->boneWeight1 / boneSum;
float bone2Coeff = vw->boneWeight2 / boneSum;
float bone3Coeff = vw->boneWeight3 / boneSum;
vpos = vpos1 * bone1Coeff + vpos2 * bone2Coeff + vpos3 * bone3Coeff;
} else {
int bone4No = vw->bone4;
glm::vec3 bindingPos4 = animData->bindingPose[bone4No];
const AnimPose& bone4Pose = animData->getFKPose(frame, bone4No);
glm::vec3 joint4Pos = bone4Pose.Position;
glm::vec3 inverseBindingPos4 = -bindingPos4;
glm::mat4 m4 = glm::translate(glm::mat4(1.0f), inverseBindingPos4);
m4 = glm::mat4_cast(bone4Pose.Rotation) * m4;
glm::vec4 vpos4 = m4 * vpos;
m4 = glm::translate(glm::mat4(1.0f), bone4Pose.Position);
vpos4 = m4 * vpos4;
float boneSum = (float)(vw->boneWeight1 + vw->boneWeight2 + vw->boneWeight3 + vw->boneWeight4);
float bone1Coeff = vw->boneWeight1 / boneSum;
float bone2Coeff = vw->boneWeight2 / boneSum;
float bone3Coeff = vw->boneWeight3 / boneSum;
float bone4Coeff = vw->boneWeight4 / boneSum;
vpos = vpos1 * bone1Coeff + vpos2 * bone2Coeff + vpos3 * bone3Coeff + vpos4 * bone4Coeff;
}
}
}
animatedPositions[vnum].x = vpos.x;
animatedPositions[vnum].y = vpos.y;
animatedPositions[vnum].z = vpos.z;
}
}
return animatedPositions;
}

View file

@ -0,0 +1,11 @@
#pragma once
struct FloatVector;
class Mesh;
class AnimData;
class AnimationProcessor
{
public:
FloatVector* softwareSkin(Mesh* mesh, AnimData* animData, int frame);
};

11
snowlib/DataAccess.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef DATAACCESS_H_
#define DATAACCESS_H_
// functions for reading data from a buffer.
int getLEInt32(const unsigned char* data)
{
return *reinterpret_cast<const int*>(data);
}
#endif // DATAACCESS_H_

20
snowlib/DataUtil.h Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <stdint.h>
class DataUtil
{
public:
static int getLEInt(const unsigned char* data, int offset){
return *(int32_t *)(data + offset);
}
static short getLEShort(const unsigned char* data, int offset){
return *(int16_t *)(data + offset);
}
static unsigned short getLEUShort(const unsigned char* data, int offset){
return *(uint16_t *)(data + offset);
}
};

99
snowlib/FntDecoder.cpp Normal file
View file

@ -0,0 +1,99 @@
#include "FntDecoder.h"
#include "DataUtil.h"
#define GLYPH_MASK 0xf800
// Enable debugging in release mode
#pragma optimize( "", off )
void FntDecoder::charsToGlyphsInplace(const unsigned char* font, unsigned short* charsOrGlyphs, int len, int ps2FontAddress)
{
if (len == 0 || charsOrGlyphs[0] == 0 || (charsOrGlyphs[0] & GLYPH_MASK) == GLYPH_MASK) {
// Already glyphs, no conversion necessary
return;
}
for (int i = 0; i < len; ++i) {
int charCode = charsOrGlyphs[i];
int glyphId = charCodeToGlyphId(font, charCode, ps2FontAddress);
charsOrGlyphs[i] = glyphId;
}
}
int FntDecoder::charCodeToGlyphId(const unsigned char* font, int charCode, int ps2FontAddress)
{
int glyphId = 0;
int numGlyphs = DataUtil::getLEShort(font, 0);
int glyphTableOffset = DataUtil::getLEInt(font, 8) - ps2FontAddress;
int glyphOffset = glyphTableOffset;
for (int glyphNum = 0; glyphNum < numGlyphs; ++glyphNum) {
int charId = DataUtil::getLEShort(font, glyphOffset);
if (charId == charCode) {
return glyphNum;
}
glyphOffset += 0x10;
}
return glyphId;
}
GlyphInfo& FntDecoder::lookupGlyph(const unsigned char* font, int glyphNum, int ps2FontAddress)
{
int numGlyphs = DataUtil::getLEShort(font, 0);
glyphNum &= ~GLYPH_MASK;
if (glyphNum >= numGlyphs) {
glyphNum = 0;
}
int glyphTableOffset = DataUtil::getLEInt(font, 8) - ps2FontAddress;
return *(GlyphInfo*)(font + glyphTableOffset + 0x10 * glyphNum);
}
int FntDecoder::getCharAdvance(const unsigned char* font, uint16_t glyphNum, int ps2FontAddress)
{
int numGlyphs = DataUtil::getLEShort(font, 0);
int glyphTableOffset = DataUtil::getLEInt(font, 8) - ps2FontAddress;
glyphNum &= ~GLYPH_MASK;
if (glyphNum >= numGlyphs) {
glyphNum = 0;
}
GlyphInfo* glyph = (GlyphInfo*)(font + glyphTableOffset + 0x10 * glyphNum);
int advance = glyph->width;
return advance;
}
int FntDecoder::getKernPairAdjust(const unsigned char* font, uint16_t previousGlyph, uint16_t glyphNum, int ps2FontAddress)
{
int numGlyphs = DataUtil::getLEShort(font, 0);
int glyphTableOffset = DataUtil::getLEInt(font, 8) - ps2FontAddress;
int kernTableOffset = DataUtil::getLEInt(font, 0x0C) - ps2FontAddress;
glyphNum &= ~GLYPH_MASK;
previousGlyph &= ~GLYPH_MASK;
if (glyphNum >= numGlyphs) {
glyphNum = 0;
}
GlyphInfo* glyph = (GlyphInfo*)(font + glyphTableOffset + 0x10 * glyphNum);
int advance = 0;
int kernId = glyph->kernId;
if (previousGlyph > 0) {
while (kernId != 0xFFFF) {
GlyphKernPair* kernPairEntry = (GlyphKernPair*)(font + kernTableOffset + 6 * kernId);
++kernId;
if (kernPairEntry->glyph1 == 0xFFFF || kernPairEntry->glyph2 != glyphNum) {
kernId = 0xFFFF;
}
else if (kernPairEntry->glyph1 == previousGlyph) {
advance += kernPairEntry->kern;
kernId = 0xFFFF;
}
}
}
return advance;
}

35
snowlib/FntDecoder.h Normal file
View file

@ -0,0 +1,35 @@
#pragma once
#include "stdint.h"
class Texture;
struct GlyphInfo
{
uint16_t charId;
uint16_t x0;
uint16_t x1;
uint16_t y0;
uint16_t y1;
uint16_t yOffset;
uint16_t width;
uint16_t kernId;
};
struct GlyphKernPair
{
uint16_t glyph1;
uint16_t glyph2;
int16_t kern;
};
class FntDecoder
{
public:
void charsToGlyphsInplace(const unsigned char* font, unsigned short* charsOrGlyphs, int len, int ps2FontAddress);
int charCodeToGlyphId(const unsigned char* font, int charCode, int ps2FontAddress);
GlyphInfo& lookupGlyph(const unsigned char* font, int glyphNum, int ps2FontAddress);
int getCharAdvance(const unsigned char* font, uint16_t glyph, int ps2FontAddress);
int getKernPairAdjust(const unsigned char* font, uint16_t previousGlyph, uint16_t glyph, int ps2FontAddress);
};

29
snowlib/GIFTag.cpp Normal file
View file

@ -0,0 +1,29 @@
#include "GIFTag.h"
#include "DataUtil.h"
void GIFTag::parse(const unsigned char* data, int offset)
{
int low32 = DataUtil::getLEInt(data, offset);
nloop = low32 & 0x7FFF;
eop = (low32 & 0x8000) == 0x8000;
int next32 = DataUtil::getLEInt(data, offset + 4);
// bit 32 is bit 0 of next 32
pre = ((next32 >> (46 - 32)) & 1) == 1;
// prim 11 bits 47 - 57
prim = ((next32 >> (47 - 32)) & 0x3FF);
flg = ((next32 >> (58 - 32)) & 0x3);
nreg = ((next32 >> (60 - 32)) & 0xf);
if (0 == nreg){
nreg = 16;
}
int regs64 = DataUtil::getLEInt(data, offset + 8);
int regs96 = DataUtil::getLEInt(data, offset + 12);
for (int reg=0; reg < nreg; ++reg){
int rgs = reg > 7 ? regs96 : regs64;
regs[reg] = (rgs >> ((reg & 7) * 4)) & 0x0f;
}
}

17
snowlib/GIFTag.h Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <vector>
class GIFTag
{
public:
int nloop;
bool eop;
bool pre;
int prim;
int flg;
int nreg;
int regs[16];
void parse(const unsigned char* data, int offset);
};

10
snowlib/GameType.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef GAMETYPE_H_
#define GAMETYPE_H_
enum GameType
{
DARK_ALLIANCE,
CHAMPIONS_RTA
};
#endif // GAMETYPE_H_

49
snowlib/GobFile.cpp Normal file
View file

@ -0,0 +1,49 @@
#include "GobFile.h"
#include "Logger.h"
#include "DataUtil.h"
#include <fstream>
void GobFile::buildLmpFileMap()
{
lmpFileMap.clear();
int offset = 0;
while (gobData[offset] != 0){
std::string lmpName((char*)gobData + offset);
int lmpDataOffset = DataUtil::getLEInt(gobData, offset + 0x20);
int lmpDataLength = DataUtil::getLEInt(gobData, offset + 0x24);
LmpFile* lmpFile = new LmpFile(gameType, gobData, lmpDataOffset, lmpDataLength);
lmpFileMap[lmpName] = lmpFile;
offset += 0x28;
}
}
void GobFile::readGobFile()
{
FILE* file = fopen(gobPath.c_str(), "r");
if (file != nullptr){
fseek(file, 0, SEEK_END);
long size = ftell(file);
rewind(file);
gobDataLength = (int)size;
delete[] gobData;
gobData = new unsigned char[gobDataLength];
long bytesRead = fread(gobData, 1, size, file);
fclose(file);
}
else {
Logger::getLogger()->logFormat("failed to read file: '%s'\n", gobPath.c_str());
}
}
LmpFile* GobFile::getLmp(string lmpName)
{
if (gobDataLength == 0){
readGobFile();
buildLmpFileMap();
}
return lmpFileMap[lmpName];
}

38
snowlib/GobFile.h Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include "LmpRepository.h"
/// A Gob file is a collection of Lmp files.
class GobFile : public LmpRepository
{
string gobPath;
GameType gameType;
unsigned char* gobData;
int gobDataLength;
unordered_map<string, LmpFile*> lmpFileMap;
void readGobFile();
void buildLmpFileMap();
public:
GobFile(string gobPath, GameType gameType)
{
this->gobPath = gobPath;
this->gameType = gameType;
gobData = nullptr;
gobDataLength = 0;
}
~GobFile()
{
for (auto& x : lmpFileMap){
delete x.second;
}
lmpFileMap.clear();
delete[] gobData;
}
LmpFile* getLmp(string lmpName);
};

15
snowlib/Helpers.cpp Normal file
View file

@ -0,0 +1,15 @@
#include "Helpers.h"
#include <cstring>
namespace Helpers
{
const char* strjoin(const char* s1, const char* s2)
{
int len = strlen(s1) + strlen(s2);
char* buf = new char[len+1];
strcpy(buf, s1);
strcat(buf, s2);
return buf;
}
}

6
snowlib/Helpers.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
namespace Helpers
{
const char* strjoin(const char* s1, const char* s2);
}

71
snowlib/LmpFile.cpp Normal file
View file

@ -0,0 +1,71 @@
#include "LmpFile.h"
#include "DataAccess.h"
#include "Helpers.h"
#include "Logger.h"
#include "DataUtil.h"
LmpFile::LmpFile(GameType gameType, const unsigned char* data, int startOffset, int dataLength)
{
_gameType = gameType;
_data = data;
_startOffset = startOffset;
_dataLength = dataLength;
}
#define LMP_ENTRY_LEN 64
LmpEntry* LmpFile::findEntry(const char* name)
{
LmpEntry* entry = NULL;
int numEntries = getLEInt32(_data+_startOffset);
for (int entryNum=0; entryNum<numEntries && entry == NULL; ++entryNum)
{
const unsigned char* pEntry = _data + _startOffset + 4 + LMP_ENTRY_LEN * entryNum;
if (0 == strcmp(name, (const char*)pEntry)){
const unsigned char* entryDataStart = _data+_startOffset + DataUtil::getLEInt(pEntry, 56);
entry = new LmpEntry(entryDataStart, DataUtil::getLEInt(pEntry, 60));
}
}
if (entry == nullptr){
Logger::getLogger()->logFormat("Failed to find LMP entry '%s'\n", name);
}
return entry;
}
LmpEntry* LmpFile::findEntry(const char* name, const char* extension)
{
return findEntry(Helpers::strjoin(name, extension));
}
/* returns 1 iff str ends with suffix */
int str_ends_with(const char * str, const char * suffix) {
if( str == nullptr || suffix == nullptr )
return 0;
size_t str_len = strlen(str);
size_t suffix_len = strlen(suffix);
if(suffix_len > str_len)
return 0;
return 0 == strncmp( str + str_len - suffix_len, suffix, suffix_len );
}
std::vector<std::string> LmpFile::findFilenamesByExtension(const char* extension)
{
std::vector<std::string> foundFilenames;
int numEntries = getLEInt32(_data+_startOffset);
int extensionLength = strlen(extension);
for (int entryNum=0; entryNum<numEntries; ++entryNum)
{
const unsigned char* pEntry = _data + _startOffset + 4 + LMP_ENTRY_LEN * entryNum;
const char* pName = (const char*)pEntry;
int entryNameLen = strlen(pName);
if (entryNameLen > extensionLength && 0 == strncmp( pName + entryNameLen - extensionLength, extension, extensionLength )){
foundFilenames.push_back(std::string(pName));
}
}
return foundFilenames;
}

47
snowlib/LmpFile.h Normal file
View file

@ -0,0 +1,47 @@
#ifndef LMPFILE_H_
#define LMPFILE_H_
#include <string>
#include <vector>
#include "GameType.h"
/// An object that describes an entry in a LmpFile.
class LmpEntry
{
public:
LmpEntry(const unsigned char* dataIn, int lenIn) : data(dataIn), length(lenIn){}
/// Pointer to the start of the file data.
const unsigned char* data;
/// The length of the file data.
int length;
};
class LmpFile
{
public:
/// Constructor.
LmpFile(GameType gameType, const unsigned char* data, int startOffset, int dataLength);
/// Find an entry in the Lump.
/// Returns the entry descriptor. Caller is responsible for deleting the returned object.
/// If the file is not found, returns null.
LmpEntry* findEntry(const char* name);
/// Find an entry in the Lump.
/// Returns the entry descriptor. Caller is responsible for deleting the returned object.
/// If the file is not found, returns null.
LmpEntry* findEntry(const char* name, const char* extension);
std::vector<std::string> findFilenamesByExtension(const char* extension);
private:
GameType _gameType;
const unsigned char* _data;
int _startOffset;
int _dataLength;
};
#endif // LMPFILE_H_

33
snowlib/LmpRepository.cpp Normal file
View file

@ -0,0 +1,33 @@
#include "LmpRepository.h"
#include "Logger.h"
LmpFile* LmpRepositoryImpl::readLmpFile(string lmpName)
{
LmpFile* lmpFile = nullptr;
string fullpath = dataPath + lmpName;
FILE* file = fopen(fullpath.c_str(), "r");
if (file != nullptr){
fseek (file , 0 , SEEK_END);
long size = ftell (file);
rewind (file);
char* buffer = (char*) malloc (size);
long bytesRead = fread (buffer,1,size,file);
fclose(file);
lmpFile = new LmpFile(gameType, (unsigned char *)buffer, 0, size);
} else {
Logger::getLogger()->logFormat("failed to read file: '%s'\n", fullpath.c_str());
}
return lmpFile;
}
LmpRepositoryImpl::~LmpRepositoryImpl()
{
for (auto& x : lmpFileMap){
delete x.second;
}
lmpFileMap.clear();
}

38
snowlib/LmpRepository.h Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include <unordered_map>
#include "LmpFile.h"
using namespace std;
class LmpRepository
{
public:
virtual LmpFile* getLmp(string lmpName) = 0;
};
/// Stores Lmp Files
class LmpRepositoryImpl : public LmpRepository
{
unordered_map<string, LmpFile*> lmpFileMap;
string dataPath;
GameType gameType;
LmpFile* readLmpFile(string lmpName);
public:
LmpRepositoryImpl(string dataPath, GameType gameType)
{
this->dataPath = dataPath;
this->gameType = gameType;
}
~LmpRepositoryImpl();
LmpFile* getLmp(string lmpName)
{
if (lmpFileMap.count(lmpName) == 0){
lmpFileMap[lmpName] = readLmpFile(lmpName);
}
return lmpFileMap[lmpName];
}
};

3
snowlib/Logger.cpp Normal file
View file

@ -0,0 +1,3 @@
#include "Logger.h"
Logger* Logger::logger;

43
snowlib/Logger.h Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#ifdef WIN32
#include <Windows.h>
#endif
#include <iostream>
#include <stdarg.h>
class Logger
{
private:
static Logger* logger;
Logger(){}
public:
static Logger* getLogger()
{
if (logger == nullptr){
logger = new Logger();
}
return logger;
}
void log(const char* message)
{
std::cerr << message;
#ifdef WIN32
OutputDebugStringA(message);
#endif
}
void logFormat(const char* format, ...)
{
char buf[256];
va_list args;
va_start (args, format);
vsnprintf(buf, 256, format, args);
va_end(args);
log(buf);
}
};

15
snowlib/Mesh.cpp Normal file
View file

@ -0,0 +1,15 @@
#include "Mesh.h"
#include "assert.h"
void Mesh::adjustUVs(int textureWidth, int textureHeight)
{
for (int i=0; i<numVertices; ++i)
{
FloatPoint& uv = uvCoords[i];
uv.x /= textureWidth;
uv.y /= textureHeight;
//uv.y = 1.0f - uv.y;
}
}

20
snowlib/Mesh.h Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include "VertexDefs.h"
#include "VertexWeight.h"
#include <vector>
class Mesh
{
public:
std::vector<int> triangleIndices;
FloatVector* positions;
FloatVector* normals;
FloatPoint* uvCoords;
int numVertices;
std::vector<VertexWeight> vertexWeights;
void adjustUVs(int textureWidth, int textureHeight);
};

19
snowlib/Model.h Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <vector>
class Texture;
class AnimData;
class TexturedMesh
{
public:
std::vector<Mesh*>* meshList;
Texture* texture;
};
class Model
{
public:
std::vector<TexturedMesh*> texturedMeshList;
AnimData* animData;
};

62
snowlib/Palette.h Normal file
View file

@ -0,0 +1,62 @@
#pragma once
#include <string.h>
#include <stdint.h>
class Palette
{
public:
Palette()
{
rgbaData = nullptr;
numEntries = 0;
}
~Palette()
{
delete[] rgbaData;
}
// in ps2 0x80 is fully transparent and 0 is opaque.
unsigned char* rgbaData;
int numEntries;
int32_t getValue(unsigned int idx)
{
return ((int32_t*)rgbaData)[idx];
}
void read(const unsigned char* data, int palw, int palh)
{
numEntries = palw * palh;
rgbaData = new unsigned char[numEntries*4];
memcpy(rgbaData, data, numEntries*4);
}
void unswizzle()
{
if (numEntries == 256) {
unsigned char* unswizzled = new unsigned char[numEntries*4];
int j = 0;
for (int i = 0; i < 256; i += 32, j += 32) {
copy(unswizzled, i, rgbaData, j, 8);
copy(unswizzled, i + 16, rgbaData, j + 8, 8);
copy(unswizzled, i + 8, rgbaData, j + 16, 8);
copy(unswizzled, i + 24, rgbaData, j + 24, 8);
}
delete[] rgbaData;
rgbaData = unswizzled;
}
}
void copy(unsigned char* unswizzled, int i, unsigned char* swizzled, int j, int num)
{
int32_t* unswizzled32 = (int32_t *)unswizzled;
int32_t* swizzled32 = (int32_t *)swizzled;
for (int x = 0; x < num; ++x) {
unswizzled32[i + x] = swizzled32[j + x];
}
}
};

17
snowlib/Scene.h Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <glm/glm.hpp>
class Model;
class SceneElement
{
public:
Model* model;
glm::vec3 position;
};
class Scene
{
};

182
snowlib/TexDecoder.cpp Normal file
View file

@ -0,0 +1,182 @@
#include "Texture.h"
#include "TexDecoder.h"
#include "DataUtil.h"
#include "GIFTag.h"
#include "Palette.h"
#pragma optimize( "", off )
Texture* TexDecoder::decode(const unsigned char* data, int ps2Addr)
{
int finalw = DataUtil::getLEShort(data, 0);
int finalh = DataUtil::getLEShort(data, 02);
int curIdx = DataUtil::getLEInt(data, 0x10);
return decode(finalw, finalh, data, curIdx - ps2Addr);
}
Texture* TexDecoder::decode(int finalw, int finalh, const unsigned char* data, int curIdx)
{
int sourcew = finalw;
int sourceh = finalh;
pixels = nullptr;
pixelsLength = 0;
GIFTag gifTag;
gifTag.parse(data, curIdx);
// This is basically heuristics. Writing a full GIF parser is complex and as the texture files are written by a tool,
// we can safely make some assumptions about their structure.
if (gifTag.nloop == 4) {
int palw = DataUtil::getLEShort(data, curIdx + 0x30);
int palh = DataUtil::getLEShort(data, curIdx + 0x34);
curIdx += 0x50;
GIFTag gifTag2;
gifTag2.parse(data, curIdx);
// 8 bit palletised
Palette* palette = new Palette();
palette->read(data + curIdx + 0x10, palw, palh);
palette->unswizzle();
int palLen = palw * palh * 4;
curIdx += (palLen + 0x10);
GIFTag* gifTag50 = new GIFTag();
gifTag50->parse(data, curIdx);
curIdx += 0x20;
int dbw = (sourcew / 2 + 0x07) & ~0x07;
int dbh = (sourceh / 2 + 0x07) & ~0x07;
int totalRrw = 0;
bool eop = false;
// Need to find a better way than this.
while (!eop || totalRrw < dbw) {
GIFTag* gifTag3 = new GIFTag();
gifTag3->parse(data, curIdx);
int dimOffset = 0x10;
int thisRrw = DataUtil::getLEShort(data, curIdx + dimOffset);
int thisRrh = DataUtil::getLEShort(data, curIdx + dimOffset + 4);
int startx = DataUtil::getLEShort(data, curIdx + dimOffset + 20);
int starty = DataUtil::getLEShort(data, curIdx + dimOffset + 22);
curIdx += gifTag.nloop * 0x10 + 0x10;
readPixels32(data, palette, curIdx, startx, starty, thisRrw, thisRrh, dbw, dbh);
curIdx += thisRrw * thisRrh * 4;
totalRrw += thisRrw;
eop = gifTag3->eop;
}
if (palLen != 64) {
unswizzle8bpp(dbw * 2, dbh * 2);
sourcew = dbw * 2;
sourceh = dbh * 2;
} else {
sourcew = dbw;
sourceh = dbh;
}
delete palette;
} else if (gifTag.nloop == 3) {
GIFTag* gifTag2 = new GIFTag();
gifTag2->parse(data, 0xC0);
if (gifTag2->flg == 2) {
// image mode
readPixels32(data + 0xD0, finalw, finalh);
}
}
Texture* texture = new Texture(finalw, finalh, sourcew, sourceh, pixels, sourcew * sourceh * 4);
return texture;
}
void TexDecoder::unswizzle8bpp(int w, int h)
{
unsigned char* unswizzled = new unsigned char[pixelsLength];
int32_t* unswizzled32 = (int32_t*)unswizzled;
int32_t* pixels32 = (int32_t*)pixels;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int block_location = (y & (~0xf)) * w + (x & (~0xf)) * 2;
int swap_selector = (((y + 2) >> 2) & 0x1) * 4;
int posY = (((y & (~3)) >> 1) + (y & 1)) & 0x7;
int column_location = posY * w * 2 + ((x + swap_selector) & 0x7) * 4;
int byte_num = ((y >> 1) & 1) + ((x >> 2) & 2); // 0,1,2,3
int idx = block_location + column_location + byte_num;
if (idx < pixelsLength) {
unswizzled32[(y * w) + x] = pixels32[idx];
}
}
}
delete[] pixels;
pixels = unswizzled;
}
void TexDecoder::readPixels32(const unsigned char* data, Palette* palette, int startOffset, int startx, int starty, int rrw, int rrh, int dbw, int dbh)
{
if (palette->numEntries == 256) {
int numDestPixels = dbh * dbw * 4;
int widthPixels = dbw * 4;
if (pixels == nullptr) {
pixelsLength = numDestPixels*4;
pixels = new unsigned char[pixelsLength];
}
int32_t* pixels32 = (int32_t*)pixels;
int idx = startOffset;
for (int y = 0; y < rrh && (y+starty) < dbh; ++y) {
for (int x = 0; x < rrw; ++x) {
int destIdx = (y+starty) * widthPixels + (x + startx) * 4;
pixels32[destIdx++] = palette->getValue(data[idx++]);
pixels32[destIdx++] = palette->getValue(data[idx++]);
pixels32[destIdx++] = palette->getValue(data[idx++]);
pixels32[destIdx] = palette->getValue(data[idx++]);
}
}
} else {
int numDestPixels = rrh * dbw;
if (pixels == nullptr) {
pixelsLength = numDestPixels*4;
pixels = new unsigned char[pixelsLength];
}
int32_t* pixels32 = (int32_t*)pixels;
int idx = startOffset;
bool lowbit = false;
for (int y = 0; y < rrh; ++y) {
for (int x = 0; x < rrw; ++x) {
int destIdx = (y + starty) * dbw + x + startx;
if (!lowbit) {
pixels[destIdx] = palette->getValue(data[idx] >> 4 & 0x0F);
} else {
pixels[destIdx] = palette->getValue(data[idx++] & 0x0F);
}
lowbit = !lowbit;
}
}
}
}
void TexDecoder::readPixels32(const unsigned char* data, int w, int h)
{
int numDestPixels = w * h;
if (pixels == nullptr) {
pixelsLength = numDestPixels*4;
pixels = new unsigned char[pixelsLength];
}
memcpy(pixels, data, numDestPixels*4);
}

19
snowlib/TexDecoder.h Normal file
View file

@ -0,0 +1,19 @@
#pragma once
class Texture;
class Palette;
class TexDecoder
{
public:
Texture* decode(const unsigned char* data, int ps2Addr);
Texture* decode(int finalw, int finalh, const unsigned char* data, int curIdx);
private:
void readPixels32(const unsigned char* data, Palette* palette, int startOffset, int startx, int starty, int rrw, int rrh, int dbw, int dbh);
void readPixels32(const unsigned char* data, int w, int h);
void unswizzle8bpp(int w, int h);
unsigned char* pixels;
int pixelsLength;
};

29
snowlib/Texture.h Normal file
View file

@ -0,0 +1,29 @@
#pragma once
class Texture
{
public:
Texture(int logw, int logh, int w, int h, unsigned char* dataIn, int dataLengthIn)
{
logicalWidth = logw;
logicalHeight = logh;
widthPixels = w;
heightPixels = h;
dataLength = dataLengthIn;
data = dataIn;
}
~Texture()
{
delete data;
}
int logicalWidth;
int logicalHeight;
int widthPixels;
int heightPixels;
int dataLength;
unsigned char* data;
};

53
snowlib/VertexDefs.h Normal file
View file

@ -0,0 +1,53 @@
#pragma once
struct FloatVector
{
FloatVector(){}
FloatVector(float xx, float yy, float zz) : x(xx), y(yy), z(zz){}
float x;
float y;
float z;
};
struct ShortVector
{
ShortVector(short xx, short yy, short zz) : x(xx), y(yy), z(zz){}
short x;
short y;
short z;
};
struct SByteVector
{
SByteVector(char xx, char yy, char zz) : x(xx), y(yy), z(zz){}
char x;
char y;
char z;
};
struct Point
{
Point(){}
Point(int xx, int yy) : x(xx), y(yy){}
int x;
int y;
};
struct FloatPoint
{
FloatPoint() : x(-10000.0), y(-10000.0) {}
FloatPoint(float xx, float yy) : x(xx), y(yy){}
bool isUninitialised() {return x == -10000.0 && y == -10000.0;}
bool isInitialised() {return !isUninitialised();}
bool operator!=(FloatPoint& in){return x!=in.x || y!=in.y;}
float x;
float y;
};

15
snowlib/VertexWeight.h Normal file
View file

@ -0,0 +1,15 @@
#pragma once
struct VertexWeight
{
int startVertex;
int endVertex;
int bone1;
int bone2;
int bone3;
int bone4;
int boneWeight1;
int boneWeight2;
int boneWeight3;
int boneWeight4;
};

476
snowlib/VifDecoder.cpp Normal file
View file

@ -0,0 +1,476 @@
#include "VifDecoder.h"
#include "DataUtil.h"
#include "GIFTag.h"
#include "Logger.h"
#include "VertexDefs.h"
using namespace std;
struct VLoc
{
VLoc(int va, int vb, int vc) : v1(va), v2(vb), v3(vc){}
int v1;
int v2;
int v3;
};
struct UV
{
UV(short uu, short vv) : u(uu), v(vv){}
short u;
short v;
};
struct Chunk
{
int mscalID;
GIFTag* gifTag0;
GIFTag* gifTag1;
vector<ShortVector> vertices;
vector<SByteVector> normals;
vector<VLoc> vlocs;
vector<UV> uvs;
vector<VertexWeight> vertexWeights;
unsigned short* extraVlocs;
int numExtraVlocs;
vector<GIFTag> DIRECTGifTags;
};
#define NOP_CMD 0x00
#define STCYCL_CMD 0x01
#define ITOP_CMD 0x04
#define STMOD_CMD 0x05
#define FLUSH_CMD 0x11
#define MSCAL_CMD 0x14
#define STMASK_CMD 0x20
#define DIRECT_CMD 0x50
vector<Chunk*>* readVerts(const unsigned char* data, int offset, int endOffset)
{
auto chunks = new vector<Chunk*>();
Chunk* currentChunk = new Chunk();
Chunk* previousChunk = nullptr;
while (offset < endOffset) {
int vifCommand = data[offset + 3] & 0x7f;
int numCommand = data[offset + 2];
int immCommand = DataUtil::getLEShort(data, offset);
switch (vifCommand) {
case NOP_CMD:
case STCYCL_CMD:
case ITOP_CMD:
case STMOD_CMD:
case FLUSH_CMD:
offset += 4;
break;
case MSCAL_CMD:
if (immCommand != 66 && immCommand != 68 && immCommand != 70)
{
Logger::getLogger()->logFormat("**** Microcode %d not supported", immCommand);
}
currentChunk->mscalID = immCommand;
chunks->push_back(currentChunk);
previousChunk = currentChunk;
currentChunk = new Chunk();
offset += 4;
break;
case STMASK_CMD:
offset += 8;
break;
case DIRECT_CMD:
for (int i = 0; i < immCommand; i++)
{
GIFTag gifTag;
gifTag.parse(data, offset + 4 + i*16);
currentChunk->DIRECTGifTags.push_back(gifTag);
}
offset += 4;
offset += immCommand * 16;
break;
default:
if ((vifCommand & 0x60) == 0x60) {
// unpack command
bool mask = ((vifCommand & 0x10) == 0x10);
int vn = (vifCommand >> 2) & 3;
int vl = vifCommand & 3;
int addr = immCommand & 0x1ff;
bool flag = (immCommand & 0x8000) == 0x8000;
bool usn = (immCommand & 0x4000) == 0x4000;
offset += 4;
if (vn == 1 && vl == 1) {
// v2-16
// The UVs come after the MSCAL instruction.
if (previousChunk != nullptr) {
for (int uvnum = 0; uvnum < numCommand; ++uvnum) {
short u = DataUtil::getLEShort(data, offset);
short v = DataUtil::getLEShort(data, offset + 2);
previousChunk->uvs.push_back(UV(u, v));
offset += 4;
}
} else {
int numBytes = numCommand * 4;
offset += numBytes;
}
} else if (vn == 2 && vl == 1) {
// v3-16
// each vertex is 128 bits, so num is the number of vertices
for (int vnum = 0; vnum < numCommand; ++vnum) {
if (!usn) {
short x = DataUtil::getLEShort(data, offset);
short y = DataUtil::getLEShort(data, offset + 2);
short z = DataUtil::getLEShort(data, offset + 4);
offset += 6;
currentChunk->vertices.push_back(ShortVector(x, y, z));
} else {
int x = DataUtil::getLEUShort(data, offset);
int y = DataUtil::getLEUShort(data, offset + 2);
int z = DataUtil::getLEUShort(data, offset + 4);
offset += 6;
currentChunk->vlocs.push_back(VLoc(x,y,z));
}
}
offset = (offset + 3) & ~3;
} else if (vn == 2 && vl == 2) {
// v3-8
int idx = offset;
for (int vnum = 0; vnum < numCommand; ++vnum) {
char x = (char)data[idx++];
char y = (char)data[idx++];
char z = (char)data[idx++];
currentChunk->normals.push_back(SByteVector(x, y, z));
}
int numBytes = ((numCommand * 3) + 3) & ~3;
offset += numBytes;
} else if (vn == 3 && vl == 0) {
// v4-32
if (1 == numCommand) {
currentChunk->gifTag0 = new GIFTag();
currentChunk->gifTag0->parse(data, offset);
} else if (2 == numCommand) {
currentChunk->gifTag0 = new GIFTag();
currentChunk->gifTag0->parse(data, offset);
currentChunk->gifTag1 = new GIFTag();
currentChunk->gifTag1->parse(data, offset + 16);
} else {
Logger::getLogger()->log("unknown number of gif commands.\n");
}
int numBytes = numCommand * 16;
offset += numBytes;
} else if (vn == 3 && vl == 1) {
// v4-16
int numShorts = numCommand * 4;
if (usn) {
currentChunk->extraVlocs = new unsigned short[numShorts];
currentChunk->numExtraVlocs = numShorts;
for (int i = 0; i < numCommand; ++i) {
currentChunk->extraVlocs[i*4] = DataUtil::getLEUShort(data, offset + i * 8);
currentChunk->extraVlocs[i * 4 + 1] = DataUtil::getLEUShort(data, offset + i * 8 + 2);
currentChunk->extraVlocs[i * 4 + 2] = DataUtil::getLEUShort(data, offset + i * 8 + 4);
currentChunk->extraVlocs[i * 4 + 3] = DataUtil::getLEUShort(data, offset + i * 8 + 6);
}
} else {
Logger::getLogger()->log("Unsupported tag\n");
}
offset += numShorts * 2;
} else if (vn == 3 && vl == 2) {
// v4-8
int numBytes = numCommand * 4;
int curVertex=0;
for (int i = 0; i < numCommand; ++i) {
VertexWeight vw;
vw.startVertex = curVertex;
vw.bone1 = data[offset++] / 4;
vw.boneWeight1 = data[offset++];
vw.bone2 = data[offset++];
if (vw.bone2 == 0xFF) {
// Single bone
vw.boneWeight2 = 0;
int count = data[offset++];
curVertex += count;
} else {
vw.bone2 /= 4;
vw.boneWeight2 = data[offset++];
++curVertex;
if (vw.boneWeight1 + vw.boneWeight2 < 0xFF)
{
++i;
vw.bone3 = data[offset++] / 4;
vw.boneWeight3 = data[offset++];
vw.bone4 = data[offset++];
int bw4 = data[offset++];
if (vw.bone4 != 0xFF)
{
vw.bone4 /= 4;
vw.boneWeight4 = bw4;
}
} else {
vw.bone3 = 0xFF;
vw.boneWeight3 = 0;
vw.bone4 = 0xFF;
vw.boneWeight4 = 0;
}
}
vw.endVertex = curVertex - 1;
currentChunk->vertexWeights.push_back(vw);
}
} else {
offset = endOffset;
}
} else {
Logger::getLogger()->logFormat("Unknown command: %d", vifCommand);
offset = endOffset;
}
break;
}
}
return chunks;
}
VertexWeight emptyVertexWeight;
VertexWeight& FindVertexWeight(vector<VertexWeight>& weights, int vertexNum)
{
for(auto& weight : weights) {
if (vertexNum >= weight.startVertex && vertexNum <= weight.endVertex) {
return weight;
}
}
if (!weights.empty()) {
Logger::getLogger()->log("Failed to find vertex weight\n");
}
return emptyVertexWeight;
}
Mesh* chunksToMesh(vector<Chunk*>* chunks)
{
Mesh* mesh = new Mesh();
int numVertices = 0;
for (auto& chunk : *chunks) {
numVertices += chunk->gifTag0->nloop;
}
// Make the assumption that this will be enough to deal with multiple uvs on the same vertex.
mesh->positions = new FloatVector[numVertices*2];
mesh->normals = new FloatVector[numVertices*2];
mesh->uvCoords = new FloatPoint[numVertices*2];
int vstart = 0;
for (auto& chunk : *chunks) {
if ((chunk->gifTag0->prim & 0x07) != 4) {
Logger::getLogger()->log("Can only deal with tri strips");
}
int vnum=vstart;
for (auto& vertex : chunk->vertices) {
FloatVector& fv = mesh->positions[vnum++];
fv.x = vertex.x / 16.0f;
fv.y = vertex.y / 16.0f;
fv.z = vertex.z / 16.0f;
}
vnum=vstart;
for (auto& normal : chunk->normals) {
FloatVector& meshNormal = mesh->normals[vnum++];
meshNormal.x = normal.x / 127.0f;
meshNormal.y = normal.y / 127.0f;
meshNormal.z = normal.z / 127.0f;
}
for (auto& vw : chunk->vertexWeights) {
VertexWeight vwAdjusted = vw;
vwAdjusted.startVertex += vstart;
const int numChunkVertices = chunk->vertices.size();
if (vwAdjusted.endVertex >= numChunkVertices) {
vwAdjusted.endVertex = numChunkVertices - 1;
}
vwAdjusted.endVertex += vstart;
if (vw.startVertex <= (numChunkVertices-1)) {
mesh->vertexWeights.push_back(vwAdjusted);
}
}
int vstripLen = chunk->gifTag0->nloop;
int* vstrip = new int[vstripLen];
int regsPerVertex = chunk->gifTag0->nreg;
int numVlocs = chunk->vlocs.size();
int numVertsInChunk = chunk->vertices.size();
for (int vlocIndx = 2; vlocIndx < numVlocs; ++vlocIndx) {
int v = vlocIndx - 2;
int stripIdx2 = (chunk->vlocs[vlocIndx].v2 & 0x1FF) / regsPerVertex;
int stripIdx3 = (chunk->vlocs[vlocIndx].v3 & 0x1FF) / regsPerVertex;
if (stripIdx3 < vstripLen && stripIdx2 < vstripLen) {
vstrip[stripIdx3] = vstrip[stripIdx2] & 0x1FF;
bool skip2 = (chunk->vlocs[vlocIndx].v3 & 0x8000) == 0x8000;
if (skip2) {
vstrip[stripIdx3] |= 0x8000;
}
}
int stripIdx = (chunk->vlocs[vlocIndx].v1 & 0x1FF) / regsPerVertex;
bool skip = (chunk->vlocs[vlocIndx].v1 & 0x8000) == 0x8000;
if (v < numVertsInChunk && stripIdx < vstripLen) {
vstrip[stripIdx] = skip ? (v | 0x8000) : v;
}
}
int numExtraVlocs = chunk->extraVlocs[0];
for (int extraVloc = 0; extraVloc < numExtraVlocs; ++extraVloc) {
int idx = extraVloc * 4 + 4;
int stripIndxSrc = (chunk->extraVlocs[idx] & 0x1FF) / regsPerVertex;
int stripIndxDest = (chunk->extraVlocs[idx + 1] & 0x1FF) / regsPerVertex; ;
vstrip[stripIndxDest] = (chunk->extraVlocs[idx + 1] & 0x8000) | (vstrip[stripIndxSrc] & 0x1FF);
stripIndxSrc = (chunk->extraVlocs[idx + 2] & 0x1FF) / regsPerVertex;
stripIndxDest = (chunk->extraVlocs[idx + 3] & 0x1FF) / regsPerVertex; ;
vstrip[stripIndxDest] = (chunk->extraVlocs[idx + 3] & 0x8000) | (vstrip[stripIndxSrc] & 0x1FF);
}
int triIdx = 0;
for (int i = 2; i < vstripLen; ++i) {
int vidx1 = vstart + (vstrip[i - 2] & 0xFF);
int vidx2 = vstart + (vstrip[i - 1] & 0xFF);
int vidx3 = vstart + (vstrip[i] & 0xFF);
int uv1 = i - 2;
int uv2 = i - 1;
int uv3 = i;
// Flip the faces (indices 1 and 2) to keep the winding rule consistent.
if ((triIdx & 1) == 1) {
int temp = uv1;
uv1 = uv2;
uv2 = temp;
temp = vidx1;
vidx1 = vidx2;
vidx2 = temp;
}
if ((vstrip[i] & 0x8000) == 0) {
float udiv = 16.0f;
float vdiv = 16.0f;
FloatPoint p1(chunk->uvs[uv1].u / udiv, chunk->uvs[uv1].v / vdiv);
FloatPoint p2(chunk->uvs[uv2].u / udiv, chunk->uvs[uv2].v / vdiv);
FloatPoint p3(chunk->uvs[uv3].u / udiv, chunk->uvs[uv3].v / vdiv);
if (mesh->uvCoords[vidx1].isInitialised() && p1 != mesh->uvCoords[vidx1])
{
// There is more than 1 uv assigment to this vertex, so we need to duplicate it.
int originalVIdx = vidx1;
vidx1 = vstart + numVertsInChunk;
numVertsInChunk++;
mesh->positions[vidx1] = mesh->positions[originalVIdx];
mesh->normals[vidx1] = mesh->normals[originalVIdx];
VertexWeight& weight = FindVertexWeight(chunk->vertexWeights, originalVIdx - vstart);
if (weight.boneWeight1 > 0)
{
VertexWeight vw = weight;
vw.startVertex = vidx1;
vw.endVertex = vidx1;
mesh->vertexWeights.push_back(vw);
}
}
if (mesh->uvCoords[vidx2].isInitialised() && p2 != mesh->uvCoords[vidx2])
{
// There is more than 1 uv assigment to this vertex, so we need to duplicate it.
int originalVIdx = vidx2;
vidx2 = vstart + numVertsInChunk;
numVertsInChunk++;
mesh->positions[vidx2] = mesh->positions[originalVIdx];
mesh->normals[vidx2] = mesh->normals[originalVIdx];
VertexWeight& weight = FindVertexWeight(chunk->vertexWeights, originalVIdx - vstart);
if (weight.boneWeight1 > 0)
{
VertexWeight vw = weight;
vw.startVertex = vidx2;
vw.endVertex = vidx2;
mesh->vertexWeights.push_back(vw);
}
}
if (mesh->uvCoords[vidx3].isInitialised() && p3 != mesh->uvCoords[vidx3])
{
// There is more than 1 uv assigment to this vertex, so we need to duplicate it.
int originalVIdx = vidx3;
vidx3 = vstart + numVertsInChunk;
numVertsInChunk++;
mesh->positions[vidx3] = mesh->positions[originalVIdx];
mesh->normals[vidx3] = mesh->normals[originalVIdx];
VertexWeight& weight = FindVertexWeight(chunk->vertexWeights, originalVIdx - vstart);
if (weight.boneWeight1 > 0)
{
VertexWeight vw = weight;
vw.startVertex = vidx3;
vw.endVertex = vidx3;
mesh->vertexWeights.push_back(vw);
}
}
mesh->uvCoords[vidx1] = p1;
mesh->uvCoords[vidx2] = p2;
mesh->uvCoords[vidx3] = p3;
// Double sided hack. Should fix this with normals really
mesh->triangleIndices.push_back(vidx1);
mesh->triangleIndices.push_back(vidx2);
mesh->triangleIndices.push_back(vidx3);
mesh->triangleIndices.push_back(vidx2);
mesh->triangleIndices.push_back(vidx1);
mesh->triangleIndices.push_back(vidx3);
}
++triIdx;
}
vstart += numVertsInChunk;
}
FloatVector* positions = new FloatVector[vstart];
memcpy(positions, mesh->positions, vstart * sizeof(FloatVector));
delete[] mesh->positions;
mesh->positions = positions;
FloatVector* normals = new FloatVector[vstart];
memcpy(normals, mesh->normals, vstart * sizeof(FloatVector));
delete[] mesh->normals;
mesh->normals = normals;
FloatPoint* uvCoords = new FloatPoint[vstart];
memcpy(uvCoords, mesh->uvCoords, vstart * sizeof(FloatPoint));
delete[] mesh->uvCoords;
mesh->uvCoords = uvCoords;
mesh->numVertices = vstart;
return mesh;
}
vector<Mesh*>* VifDecoder::decode(const unsigned char* data, int base_addr)
{
vector<Mesh*>* meshes = new vector<Mesh*>();
int numMeshes = data[0x12];
int offset1 = DataUtil::getLEInt(data, 0x24);
for (int meshNum = 0; meshNum < numMeshes; ++meshNum) {
int offsetVerts = DataUtil::getLEInt(data, 0x28 + meshNum * 4) - base_addr;
int offsetEndVerts = DataUtil::getLEInt(data, 0x2C + meshNum * 4)- base_addr;
auto chunks = readVerts(data, offsetVerts, offsetEndVerts);
Mesh* mesh = chunksToMesh(chunks);
meshes->push_back(mesh);
}
return meshes;
}

11
snowlib/VifDecoder.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include "Mesh.h"
#include <vector>
class VifDecoder
{
public:
std::vector<Mesh*>* decode(const unsigned char* data, int base_addr);
};

33
snowlib/World.cpp Normal file
View file

@ -0,0 +1,33 @@
#include "World.h"
World::World()
{
topoStartCol = 0;
topoStartRow = 0;
numTopoCols = 0;
numTopoRows = 0;
}
World::~World()
{
for (auto& element : topoElements)
{
delete element;
}
for (auto& patch : topoPatches)
{
delete patch;
}
}
TopoPatch::TopoPatch(int width, int height)
{
w = width;
h = height;
heights = new int[w * h];
}
TopoPatch::~TopoPatch()
{
delete[] heights;
}

63
snowlib/World.h Normal file
View file

@ -0,0 +1,63 @@
#pragma once
#include <vector>
class TopoPatch
{
public:
TopoPatch(int w, int h);
~TopoPatch();
int x0, y0, w, h;
int minHeight, maxHeight;
int* heights;
};
class TopoElement
{
public:
int llx, lly, urx, ury;
int int8;
TopoPatch* patch;
int flags;
int x0, y0;
int baseHeight;
double cos_alpha, sin_alpha;
};
class World
{
public:
World();
~World();
// Topography
//
// The topography is a compressed height map for the world.
// The world is broken down into a coarse grid of cells.
// The topography is specified by a collection of elements.
// A topography element positions and rotates a reusable topography patch.
// A patch is a simple rectangular array of heights which can be used by
// multiple topography elements.
// In order to speed up the processing to derive a height of a given coordinate,
// a list of all the topographical elements that intersect each cell is stored.
/// The column that the 0th column in topoElementsPerCell represents
int topoStartCol;
/// The row that the 0th row in topoElementsPerCell represents
int topoStartRow;
/// The number of columns in the topoElementsPerCell vector
int numTopoCols;
/// The number of rows in the topoElementsPerCell vector
int numTopoRows;
/// The topo elements that intersect a coarse grid.
std::vector<std::vector<TopoElement*>> topoElementsPerCell;
// All of the topo elements. This collection owns the elements.
std::vector<TopoElement*> topoElements;
// All of the topo patches. This collection owns the patches.
std::vector<TopoPatch*> topoPatches;
};

130
snowlib/WorldReader.cpp Normal file
View file

@ -0,0 +1,130 @@
#include "WorldReader.h"
#include "World.h"
#include "LmpRepository.h"
#include "DataUtil.h"
World* WorldReader::readWorld(LmpRepository* lmpRepository, const char* name)
{
World* world = new World();
std::string worldLmpName = std::string(name) + ".lmp";
LmpFile* world_lmp = lmpRepository->getLmp(worldLmpName);
LmpEntry* world_entry = world_lmp->findEntry(world_lmp->findFilenamesByExtension("world")[0].c_str());
decodeWorldFile(world, world_entry->data, world_entry->length);
return world;
}
void WorldReader::decodeWorldFile(World* world, const unsigned char* data, int dataLength)
{
int numElements = DataUtil::getLEInt(data, 0);
int offset4 = DataUtil::getLEInt(data, 4);
decodeTopography(world, data, dataLength);
int elementBase = DataUtil::getLEInt(data, 0x24);
int i28 = DataUtil::getLEInt(data, 0x28);
int i2C = DataUtil::getLEInt(data, 0x2C);
int cols_38 = DataUtil::getLEInt(data, 0x30);
int rows_38 = DataUtil::getLEInt(data, 0x34);
int offset_38 = DataUtil::getLEInt(data, 0x38);
int i3C = DataUtil::getLEInt(data, 0x3C);
int i40 = DataUtil::getLEInt(data, 0x40);
int i44 = DataUtil::getLEInt(data, 0x44);
int i48 = DataUtil::getLEInt(data, 0x48);
int offset4C = DataUtil::getLEInt(data, 0x4C);
int len50 = DataUtil::getLEInt(data, 0x50);
int offset54 = DataUtil::getLEInt(data, 0x54);
int texMinxy = DataUtil::getLEInt(data, 0x58);
int texMaxxy = DataUtil::getLEInt(data, 0x5C);
int texMinx = texMinxy % 100;
int texMiny = texMinxy / 100;
int texMaxx = texMaxxy % 100;
int texMaxy = texMaxxy / 100;
int offset60 = DataUtil::getLEInt(data, 0x60);
int textureArrayOffset = DataUtil::getLEInt(data, 0x64);
int offset68 = DataUtil::getLEInt(data, 0x68);
int offsetTex6C = DataUtil::getLEInt(data, 0x6C);
}
void WorldReader::decodeTopography(World* world, const unsigned char* data, int dataLength)
{
world->topoStartCol = DataUtil::getLEInt(data, 8);
world->topoStartRow = DataUtil::getLEInt(data, 0x0C);
world->numTopoCols = DataUtil::getLEInt(data, 0x10);
world->numTopoRows = DataUtil::getLEInt(data, 0x14);
int topoElementsPerCellOffset = DataUtil::getLEInt(data, 0x18);
int numTopoElements = DataUtil::getLEInt(data, 0x1C);
int topoArrayOffset = DataUtil::getLEInt(data, 0x20);
// Allows us to quickly look up patches from the offsets stored in the file.
std::unordered_map<int, TopoPatch*> patchAddressMap;
world->topoElements.resize(numTopoElements);
for (int el = 0; el < numTopoElements; ++el){
int topoElementOffset = topoArrayOffset + el * 0x1C;
TopoElement* element = new TopoElement();
element->llx = DataUtil::getLEShort(data, topoElementOffset);
element->lly = DataUtil::getLEShort(data, topoElementOffset + 2);
element->urx = DataUtil::getLEShort(data, topoElementOffset + 4);
element->ury = DataUtil::getLEShort(data, topoElementOffset + 6);
element->int8 = DataUtil::getLEInt(data, topoElementOffset + 8);
int patchOffset = DataUtil::getLEInt(data, topoElementOffset + 0xc);
if (patchAddressMap.find(patchOffset) == patchAddressMap.end()){
patchAddressMap[patchOffset] = readTopoPatch(data, patchOffset);
}
element->patch = patchAddressMap[patchOffset];
element->flags = DataUtil::getLEShort(data, topoElementOffset + 0x10);
element->x0 = DataUtil::getLEShort(data, topoElementOffset + 0x12);
element->y0 = DataUtil::getLEShort(data, topoElementOffset + 0x14);
element->baseHeight = DataUtil::getLEShort(data, topoElementOffset + 0x16);
element->cos_alpha = DataUtil::getLEShort(data, topoElementOffset + 0x18) / 32767.0;
element->sin_alpha = DataUtil::getLEShort(data, topoElementOffset + 0x1A) / 32767.0;
world->topoElements.push_back(element);
}
world->topoElementsPerCell.resize(world->numTopoCols * world->numTopoRows);
int index = 0;
for (int r = 0; r < world->numTopoRows; ++r){
for (int c = 0; c < world->numTopoCols; ++c){
int elementsInCellListOffset = DataUtil::getLEInt(data, topoElementsPerCellOffset + index*4);
auto& elementList = world->topoElementsPerCell[index];
int elementId = DataUtil::getLEShort(data, elementsInCellListOffset);
while (elementId >= 0){
elementsInCellListOffset += 2;
elementList.push_back(world->topoElements[elementId]);
elementId = DataUtil::getLEShort(data, elementsInCellListOffset);
}
index++;
}
}
}
TopoPatch* WorldReader::readTopoPatch(const unsigned char* data, int offset)
{
int w = DataUtil::getLEInt(data, offset + 8);
int h = DataUtil::getLEInt(data, offset + 0x0c);
TopoPatch* patch = new TopoPatch(w, h);
patch->x0 = DataUtil::getLEInt(data, offset);
patch->y0 = DataUtil::getLEInt(data, offset + 4);
patch->minHeight = DataUtil::getLEShort(data, offset + 0x10);
patch->maxHeight = DataUtil::getLEShort(data, offset + 0x12);
int j = 0;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int i = y * w + x;
patch->heights[j++] = DataUtil::getLEShort(data, offset + 0x14 + i * 2);
}
}
return patch;
}

16
snowlib/WorldReader.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
class World;
class LmpRepository;
class TopoPatch;
class WorldReader
{
public:
World* readWorld(LmpRepository* lmpRepository, const char* name);
private:
void decodeWorldFile(World* world, const unsigned char* data, int dataLength);
void decodeTopography(World* world, const unsigned char* data, int dataLength);
TopoPatch* readTopoPatch(const unsigned char* data, int offset);
};

View file

@ -0,0 +1,61 @@
#include "LmpFile.h"
#define BOOST_TEST_MODULE LmpFileTest
#include "boost/test/included/unit_test.hpp"
const unsigned char lmpData[] = {
// Some initial data that should be skipped, 12 bytes
0xba, 0xdf, 0x00, 0x0d,
0xba, 0xdf, 0x00, 0x0d,
0xba, 0xdf, 0x00, 0x0d,
0x02, 0x00, 0x00,0x00, // Number of entries
// Entry 1
// Name of 56 bytes
'f', 'i', 'l', 'e', '1', '.', 'a', 'b',
'c', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// offset to entry data from start
64*2+4, 0, 0, 0,
// length of entry data
16, 0, 0, 0,
// Entry 2
// Name of 56 bytes
'f', 'i', 'l', 'e', '2', '.', 'a', 'b',
'd', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// offset to entry data from start
64*2+4+16, 0, 0, 0,
// length of entry data
16, 0, 0, 0,
// Entry 1 data, 16 bytes
1, 2, 3, 4, 5, 6, 7, 8,
9, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
// Entry 2 data, 16 bytes
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20
};
BOOST_AUTO_TEST_CASE( lmp_test )
{
LmpFile* lmpFile = new LmpFile(DARK_ALLIANCE, lmpData, 12, sizeof(lmpData));
LmpEntry* entry = lmpFile->findEntry("file2.abd");
BOOST_CHECK(entry != NULL);
delete entry;
delete lmpFile;
}