Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 204 additions & 0 deletions src/animtexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <iostream>
#include <fstream>

AnimTexture::AnimTexture(const std::filesystem::path& path, const std::vector<std::string>& usedNames)
{
Expand All @@ -24,6 +27,195 @@ AnimTexture::AnimTexture(const std::filesystem::path& path, const std::vector<st
if (!ReadAnimation(path)) { ClearAnimation(); }
}


AnimTexture::AnimTexture(const std::string& animName, const std::filesystem::path& tempDir, const std::array<std::vector<PSX::TextureLayout>, 4>& faceFrameLayouts,
const std::array<std::vector<std::string>, 4>& faceFrameMaterials, const std::vector<size_t>& quadIndices, const std::vector<Quadblock>& quadblocks,
const std::unordered_map<LayoutKey, PixelBounds>& textureToPixelBounds, const std::unordered_map<std::string, Texture>& materialToTexture, const PSX::AnimTex& firstAnimData,
const std::vector<AnimTexture>& animTextures)
{
std::filesystem::path animDir = tempDir / animName;
std::filesystem::path tempObjPath = animDir / "frames.obj";
std::ofstream objFile(tempObjPath);
size_t frameCount = firstAnimData.frameCount;

size_t refQuadIdx = quadIndices[0];
const Quadblock& refQuad = quadblocks[refQuadIdx];
bool isTriblock = !refQuad.IsQuadblock();

objFile << "mtllib frames.mtl\n";

// For each frame, create a quadblock with UVs for all 4 faces
for (size_t frameIdx = 0; frameIdx < frameCount; frameIdx++)
{
std::string objName = "Frame_" + std::to_string(frameIdx + 1);
float z = static_cast<float>(frameIdx);

// 9 vertices
objFile << "v 0.0 0.0 " << z << " 0.5 0.5 0.5\n";
objFile << "v 1.0 0.0 " << z << " 0.5 0.5 0.5\n";
objFile << "v 0.0 1.0 " << z << " 0.5 0.5 0.5\n";
objFile << "v 1.0 1.0 " << z << " 0.5 0.5 0.5\n";
objFile << "v 0.5 0.5 " << z << " 0.5 0.5 0.5\n";
objFile << "v 1.0 0.5 " << z << " 0.5 0.5 0.5\n";
objFile << "v 0.5 1.0 " << z << " 0.5 0.5 0.5\n";
objFile << "v 0.0 0.5 " << z << " 0.5 0.5 0.5\n";
objFile << "v 0.5 0.0 " << z << " 0.5 0.5 0.5\n";

// 3 normals
objFile << "vn 0.0 1.0 0.0\n";
objFile << "vn 0.0 1.0 0.0\n";
objFile << "vn 0.0 1.0 0.0\n";

// Write 16 UVs (4 per face)
for (size_t faceIdx = 0; faceIdx < 4; faceIdx++)
{
if (frameIdx >= faceFrameLayouts[faceIdx].size()) continue;

const PSX::TextureLayout& layout = faceFrameLayouts[faceIdx][frameIdx];

// Get the pixel bounds for this texture
LayoutKey key(layout);
bool crop = true;
float u0 = 0, u1 = 0, u2 = 0, u3 = 0 , v0 = 0, v1 = 0, v2 = 0, v3 = 0;
if (crop)
{
const PixelBounds& bounds = textureToPixelBounds.at(key);
float croppedWidth = static_cast<float>(bounds.maxU - bounds.minU);
float croppedHeight = static_cast<float>(bounds.maxV - bounds.minV);
if (croppedWidth == 0) croppedWidth = 1.0f;
if (croppedHeight == 0) croppedHeight = 1.0f;
u0 = (layout.u0 - bounds.minU) / croppedWidth;
v0 = (layout.v0 - bounds.minV) / croppedHeight;
u1 = (layout.u1 - bounds.minU) / croppedWidth;
v1 = (layout.v1 - bounds.minV) / croppedHeight;
u2 = (layout.u2 - bounds.minU) / croppedWidth;
v2 = (layout.v2 - bounds.minV) / croppedHeight;
u3 = (layout.u3 - bounds.minU) / croppedWidth;
v3 = (layout.v3 - bounds.minV) / croppedHeight;
}
else
{
float pW = (float)(64 * ((layout.texPage.texpageColors == 0) ? 4 : (layout.texPage.texpageColors == 1 ? 2 : 1)));
float pH = 256.0f;
u0 = layout.u0 / pW;
v0 = layout.v0 / pH;
u1 = layout.u1 / pW;
v1 = layout.v1 / pH;
u2 = layout.u2 / pW;
v2 = layout.v2 / pH;
u3 = layout.u3 / pW;
v3 = layout.v3 / pH;
}

objFile << "vt " << u0 << " " << (1.0f - v0) << "\n";
objFile << "vt " << u1 << " " << (1.0f - v1) << "\n";
objFile << "vt " << u2 << " " << (1.0f - v2) << "\n";
objFile << "vt " << u3 << " " << (1.0f - v3) << "\n";
}

objFile << "o " << objName << "\n";
if (!faceFrameMaterials[0].empty() && frameIdx < faceFrameMaterials[0].size())
{
objFile << "usemtl " << faceFrameMaterials[0][frameIdx] << "\n";
}

int vOffset = static_cast<int>(frameIdx * 9) + 1;
int vtOffset = static_cast<int>(frameIdx * 16) + 1;
int vnOffset = static_cast<int>(frameIdx * 3) + 1;

if (isTriblock)
{
objFile << "f " << (vOffset + 0) << "/" << (vtOffset + 0) << "/" << (vnOffset + 0) << " "
<< (vOffset + 1) << "/" << (vtOffset + 1) << "/" << (vnOffset + 0) << " "
<< (vOffset + 4) << "/" << (vtOffset + 2) << "/" << (vnOffset + 0) << "\n";
objFile << "f " << (vOffset + 1) << "/" << (vtOffset + 4) << "/" << (vnOffset + 1) << " "
<< (vOffset + 5) << "/" << (vtOffset + 5) << "/" << (vnOffset + 1) << " "
<< (vOffset + 4) << "/" << (vtOffset + 6) << "/" << (vnOffset + 1) << "\n";
objFile << "f " << (vOffset + 5) << "/" << (vtOffset + 8) << "/" << (vnOffset + 2) << " "
<< (vOffset + 3) << "/" << (vtOffset + 9) << "/" << (vnOffset + 2) << " "
<< (vOffset + 4) << "/" << (vtOffset + 10) << "/" << (vnOffset + 2) << "\n";
objFile << "f " << (vOffset + 3) << "/" << (vtOffset + 12) << "/" << (vnOffset + 2) << " "
<< (vOffset + 2) << "/" << (vtOffset + 13) << "/" << (vnOffset + 2) << " "
<< (vOffset + 4) << "/" << (vtOffset + 14) << "/" << (vnOffset + 2) << "\n";
}
else
{
// Face 0: Top-Left Quadrant
// Corners: TL(v+0), T_MID(v+7), CENTER(v+8), L_MID(v+5)
objFile << "f " << (vOffset + 0) << "/" << (vtOffset + 0) << "/" << (vnOffset + 0) << " "
<< (vOffset + 7) << "/" << (vtOffset + 1) << "/" << (vnOffset + 0) << " "
<< (vOffset + 8) << "/" << (vtOffset + 3) << "/" << (vnOffset + 0) << " "
<< (vOffset + 5) << "/" << (vtOffset + 2) << "/" << (vnOffset + 0) << "\n";

// Face 1: Top-Right Quadrant
// Corners: T_MID(v+7), TR(v+1), R_MID(v+4), CENTER(v+8)
objFile << "f " << (vOffset + 7) << "/" << (vtOffset + 4) << "/" << (vnOffset + 1) << " "
<< (vOffset + 1) << "/" << (vtOffset + 5) << "/" << (vnOffset + 1) << " "
<< (vOffset + 4) << "/" << (vtOffset + 7) << "/" << (vnOffset + 1) << " "
<< (vOffset + 8) << "/" << (vtOffset + 6) << "/" << (vnOffset + 1) << "\n";

// Face 2: Bottom-Left Quadrant
// Corners: L_MID(v+5), CENTER(v+8), B_MID(v+6), BL(v+2)
objFile << "f " << (vOffset + 5) << "/" << (vtOffset + 8) << "/" << (vnOffset + 2) << " "
<< (vOffset + 8) << "/" << (vtOffset + 9) << "/" << (vnOffset + 2) << " "
<< (vOffset + 6) << "/" << (vtOffset + 11) << "/" << (vnOffset + 2) << " "
<< (vOffset + 2) << "/" << (vtOffset + 10) << "/" << (vnOffset + 2) << "\n";

// Face 3: Bottom-Right Quadrant
// Corners: CENTER(v+8), R_MID(v+4), BR(v+3), B_MID(v+6)
objFile << "f " << (vOffset + 8) << "/" << (vtOffset + 12) << "/" << (vnOffset + 2) << " "
<< (vOffset + 4) << "/" << (vtOffset + 13) << "/" << (vnOffset + 2) << " "
<< (vOffset + 3) << "/" << (vtOffset + 15) << "/" << (vnOffset + 2) << " "
<< (vOffset + 6) << "/" << (vtOffset + 14) << "/" << (vnOffset + 2) << "\n";
}
}
objFile.close();

// Write MTL
std::filesystem::path tempMtlPath = animDir / "frames.mtl";
std::ofstream mtlFile(tempMtlPath);
std::set<std::string> writtenMaterials;

for (const auto& frameMaterials : faceFrameMaterials)
{
for (const std::string& mat : frameMaterials)
{
if (writtenMaterials.count(mat) || mat == "default") continue;
writtenMaterials.insert(mat);

if (materialToTexture.count(mat))
{
mtlFile << "newmtl " << mat << "\n";
mtlFile << "map_Kd " << materialToTexture.at(mat).GetPath().string() << "\n";
}
}
}
mtlFile.close();

// Create AnimTexture
std::vector<std::string> existingNames;
for (const AnimTexture& at : animTextures)
{
existingNames.push_back(at.GetName());
}

m_path = tempObjPath;
std::string origName = tempObjPath.filename().replace_extension().string();
m_name = origName;
int repetitionCount = 1;
while (true)
{
bool validName = true;
for (const std::string& usedName : existingNames)
{
if (m_name == usedName) { validName = false; break; }
}
if (validName) { break; }
m_name = origName + " (" + std::to_string(repetitionCount++) + ")";
}
if (!ReadAnimation(tempObjPath)) { ClearAnimation(); }
}

bool AnimTexture::IsEmpty() const
{
return m_frames.empty();
Expand Down Expand Up @@ -196,6 +388,18 @@ void AnimTexture::ClearAnimation()
m_lastAppliedMaterialName.clear();
}

void AnimTexture::SetStartFrame(int frame)
{
m_startAtFrame = frame;
m_renderDirty = true;
}

void AnimTexture::SetDuration(int duration)
{
m_duration = duration;
m_renderDirty = true;
}

void AnimTexture::SetDefaultParams()
{
m_startAtFrame = 0;
Expand Down
6 changes: 6 additions & 0 deletions src/animtexture.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class AnimTexture
public:
AnimTexture() {};
AnimTexture(const std::filesystem::path& path, const std::vector<std::string>& usedNames);
AnimTexture(const std::string& animName, const std::filesystem::path& tempDir, const std::array<std::vector<PSX::TextureLayout>, 4>& faceFrameLayouts,
const std::array<std::vector<std::string>, 4>& faceFrameMaterials, const std::vector<size_t>& quadIndices, const std::vector<Quadblock>& quadblocks,
const std::unordered_map<LayoutKey, PixelBounds>& textureToPixelBounds, const std::unordered_map<std::string, Texture>& materialToTexture, const PSX::AnimTex& firstAnimData,
const std::vector<AnimTexture>& animTextures);
bool IsEmpty() const;
bool IsTriblock() const;
const std::vector<AnimTextureFrame>& GetFrames() const;
Expand All @@ -34,6 +38,8 @@ class AnimTexture
void FromJson(const nlohmann::json& json, std::vector<Quadblock>& quadblocks, const std::filesystem::path& parentPath);
void ToJson(nlohmann::json& json, const std::vector<Quadblock>& quadblocks) const;
bool IsEquivalent(const AnimTexture& animTex) const;
void SetStartFrame(int frame);
void SetDuration(int duration);
bool RenderUI(std::vector<std::string>& animTexNames, std::vector<Quadblock>& quadblocks, const std::map<std::string, std::vector<size_t>>& materialMap, const std::string& query, std::vector<AnimTexture>& newTextures);

private:
Expand Down
Loading