initial commit
This commit is contained in:
parent
f2088d1762
commit
593cc0730b
84
.gitignore
vendored
Normal file
84
.gitignore
vendored
Normal 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
19
BoostTestHelpers.cmake
Normal 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
85
CMakeLists.txt
Normal 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
16
CMakeSettings.json
Normal 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": ""
|
||||
}
|
||||
]
|
||||
}
|
24
README.md
24
README.md
|
@ -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
120
TestApp/Main.cpp
Normal 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
329
TestApp/ModelWindow.cpp
Normal 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
48
TestApp/ModelWindow.h
Normal 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
69
snowlib/AnimData.cpp
Normal 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
61
snowlib/AnimData.h
Normal 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
154
snowlib/AnimDecoder.cpp
Normal 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
9
snowlib/AnimDecoder.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
class AnimData;
|
||||
|
||||
class AnimDecoder
|
||||
{
|
||||
public:
|
||||
AnimData* decode(const unsigned char* data, int length);
|
||||
};
|
102
snowlib/AnimationProcessor.cpp
Normal file
102
snowlib/AnimationProcessor.cpp
Normal 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;
|
||||
}
|
11
snowlib/AnimationProcessor.h
Normal file
11
snowlib/AnimationProcessor.h
Normal 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
11
snowlib/DataAccess.h
Normal 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
20
snowlib/DataUtil.h
Normal 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
99
snowlib/FntDecoder.cpp
Normal 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
35
snowlib/FntDecoder.h
Normal 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
29
snowlib/GIFTag.cpp
Normal 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
17
snowlib/GIFTag.h
Normal 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
10
snowlib/GameType.h
Normal 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
49
snowlib/GobFile.cpp
Normal 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
38
snowlib/GobFile.h
Normal 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
15
snowlib/Helpers.cpp
Normal 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
6
snowlib/Helpers.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
namespace Helpers
|
||||
{
|
||||
const char* strjoin(const char* s1, const char* s2);
|
||||
}
|
71
snowlib/LmpFile.cpp
Normal file
71
snowlib/LmpFile.cpp
Normal 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
47
snowlib/LmpFile.h
Normal 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
33
snowlib/LmpRepository.cpp
Normal 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
38
snowlib/LmpRepository.h
Normal 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
3
snowlib/Logger.cpp
Normal file
|
@ -0,0 +1,3 @@
|
|||
#include "Logger.h"
|
||||
|
||||
Logger* Logger::logger;
|
43
snowlib/Logger.h
Normal file
43
snowlib/Logger.h
Normal 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
15
snowlib/Mesh.cpp
Normal 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
20
snowlib/Mesh.h
Normal 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
19
snowlib/Model.h
Normal 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
62
snowlib/Palette.h
Normal 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
17
snowlib/Scene.h
Normal 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
182
snowlib/TexDecoder.cpp
Normal 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
19
snowlib/TexDecoder.h
Normal 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
29
snowlib/Texture.h
Normal 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
53
snowlib/VertexDefs.h
Normal 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
15
snowlib/VertexWeight.h
Normal 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
476
snowlib/VifDecoder.cpp
Normal 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
11
snowlib/VifDecoder.h
Normal 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
33
snowlib/World.cpp
Normal 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
63
snowlib/World.h
Normal 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
130
snowlib/WorldReader.cpp
Normal 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
16
snowlib/WorldReader.h
Normal 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);
|
||||
};
|
61
snowlib_test/LmpFileTest.cpp
Normal file
61
snowlib_test/LmpFileTest.cpp
Normal 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;
|
||||
}
|
Reference in a new issue