Shipwright/ZAPDTR/ZAPD/ZTextureAnimation.cpp

671 lines
22 KiB
C++

/**
* File: ZTextureAnimation.cpp
* ZResources defined: ZTextureAnimation, ZTextureAnimationParams (XML declaration not supported for
* the latter)
* Purpose: extracting texture animating structures from asset files
* Note: data type is exclusive to Majora's Mask
*
* Structure of data:
* A texture animation consists of a main array of data of the form
* `SS 00 00 0T PPPPPPPP`,
* where `S` is the segment that the functionality is sent to (and read by DLists), `T` is the
* "type" (see below), and `P` is a pointer to the type's subsidiary information, which is
* invariably just above this main array. Details of this subsidiary data are given below.
*
* Segment
* ===
* The segment starts at 1 and is incremented in each entry; the final one is negated, which is used
* as the indication to stop reading. The actual segment the game will use to draw is S + 7.
*
* Types
* ===
* There are 7 values that `T` can take:
* `0`: A single texture scroll (Implemented by Gfx_TexScroll): subsidiary params are given as
* an array with one entry, a struct `XX YY WW HH` containing xStep, yStep, width, height
* (all u8).
*
* `1`: A dual texture scroll (Implementated by Gfx_TwoTexScroll): same as type `0`, but with
* two entries in the params array.
*
* `2`: Color changing: Changes the primColor (with a LOD factor) and envColor
* `KKKK LLLL PPPPPPPP QQQQQQQQ RRRRRRRR`
* - `K` (u16) is the total length of the animation (and in this case, the total number of
* colors)
* - `L` (u16) is seemingly always 0 for this type, and not used in the code
* - `P` segmented pointer to array of augmented primColors
* - `Q` segmented pointer to array of envColors, and can be NULL
* - `R` segmented pointer to array of frameData (u8), which is also seemingly always NULL
* for this type.
*
* envColors take the form `RR GG BB AA` as usual, while primColors have an extra LODFrac
* element: RR GG BB AA LL
*
* `3`: Color changing (LERP): similar to type `2`, but uses linear interpolation. The structure
* is similar to `2`, but
* - `K` is now just the animation length, while
* - `L` is the total number of colors, and
* - the frameData is used to determine which colors to interpolate.
*
* `4`: Color changing (Lagrange interpolation): For extraction purposes identical to type 3.
* Uses a nonlinear interpolation formula to change the colours more smoothly.
*
* `5`: Texture cycle: functions like a gif. Subsidiary params:
* `FFFF 0000 PPPPPPPP QQQQQQQQ`
* where
* - `F` (u16) time between changes in frames
* - `P` pointer to array of segmented pointers to textures to cycle through
* - `Q` array of indices, indicating which texture to use next when a change frame is
* reached. The maximum indicates the number of textures in the texture array.
*
* `6`: This is used to indicate an empty/unimplemented params set. It is ignored by the game's
* function provided that the segment is 0. A generic empty one takes the form
* `00 00 00 06 00000000`,
* and has no extra data.
*
* Implementation
* ===
* - ZTextureAnimation requires a declaration in the XML to extract anything. It handles the main
* array. It uses a POD struct for each entry, and declares references to the params structs.
*
* - ZTextureAnimationParams is not (currently) declarable in an XML. It is a parent class for the
* three classes that handle the various cases:
* - TextureScrollingParams for types `0` and `1`
* - TextureColorChangingParams for types `2`,`3`,`4`
* - TextureCyclingParams for type `5`
* Each of these will declare all its subsidiary arrays, using POD structs.
*/
#include "ZTextureAnimation.h"
#include <cassert>
#include <memory>
#include <vector>
#include "Globals.h"
#include "Utils/BitConverter.h"
#include "WarningHandler.h"
#include "ZFile.h"
#include "ZResource.h"
#include "tinyxml2.h"
REGISTER_ZFILENODE(TextureAnimation, ZTextureAnimation);
/* Constructors */
ZTextureAnimationParams::ZTextureAnimationParams(ZFile* parent) : ZResource::ZResource(parent)
{
}
TextureScrollingParams::TextureScrollingParams(ZFile* parent)
: ZTextureAnimationParams::ZTextureAnimationParams(parent)
{
}
TextureColorChangingParams::TextureColorChangingParams(ZFile* parent)
: ZTextureAnimationParams::ZTextureAnimationParams(parent)
{
}
TextureCyclingParams::TextureCyclingParams(ZFile* parent)
: ZTextureAnimationParams::ZTextureAnimationParams(parent)
{
}
/* TextureAnimationParams */
/* This class only implements the functions common to all or most its inheritors */
void ZTextureAnimationParams::ExtractFromBinary(uint32_t nRawDataIndex)
{
rawDataIndex = nRawDataIndex;
ParseRawData();
}
// Implemented by TextureScrollingParams only
void ZTextureAnimationParams::ExtractFromBinary([[maybe_unused]] uint32_t nRawDataIndex,
[[maybe_unused]] int count)
{
}
std::string
ZTextureAnimationParams::GetDefaultName([[maybe_unused]] const std::string& prefix) const
{
return "ShouldNotBeVIsible";
}
ZResourceType ZTextureAnimationParams::GetResourceType() const
{
return ZResourceType::TextureAnimationParams;
}
/* TextureScrollingParams */
void TextureScrollingParams::ParseRawData()
{
const auto& rawData = parent->GetRawData();
for (int i = 0; i < count; i++)
{
rows[i].xStep = BitConverter::ToUInt8BE(rawData, rawDataIndex + 4 * i);
rows[i].yStep = BitConverter::ToUInt8BE(rawData, rawDataIndex + 4 * i + 1);
rows[i].width = BitConverter::ToUInt8BE(rawData, rawDataIndex + 4 * i + 2);
rows[i].height = BitConverter::ToUInt8BE(rawData, rawDataIndex + 4 * i + 3);
}
}
void TextureScrollingParams::ExtractFromBinary(uint32_t nRawDataIndex, int nCount)
{
rawDataIndex = nRawDataIndex;
count = nCount;
ParseRawData();
}
std::string TextureScrollingParams::GetSourceTypeName() const
{
return "AnimatedMatTexScrollParams"; // TODO: Better name
}
std::string TextureScrollingParams::GetDefaultName(const std::string& prefix) const
{
return StringHelper::Sprintf("%sTexScrollParams_%06X", prefix.c_str(), rawDataIndex);
}
size_t TextureScrollingParams::GetRawDataSize() const
{
return 4 * count;
}
/**
* Overrides the parent version to declare an array of the params rather than just one entry.
*/
Declaration* TextureScrollingParams::DeclareVar(const std::string& prefix,
const std::string& bodyStr)
{
std::string auxName = name;
if (name == "")
auxName = GetDefaultName(prefix);
return parent->AddDeclarationArray(rawDataIndex, GetDeclarationAlignment(), GetRawDataSize(),
GetSourceTypeName(), auxName, count, bodyStr);
}
std::string TextureScrollingParams::GetBodySourceCode() const
{
std::string bodyStr;
for (int i = 0; i < count; i++)
{
bodyStr += StringHelper::Sprintf("\t{ %d, %d, 0x%02X, 0x%02X },\n", rows[i].xStep,
rows[i].yStep, rows[i].width, rows[i].height);
}
bodyStr.pop_back();
return bodyStr;
}
/* TextureColorChangingParams */
/**
* Also parses the color and frameData arrays
*/
void TextureColorChangingParams::ParseRawData()
{
const auto& rawData = parent->GetRawData();
animLength = BitConverter::ToUInt16BE(rawData, rawDataIndex);
colorListCount = BitConverter::ToUInt16BE(rawData, rawDataIndex + 2);
// Handle type 2 separately
uint16_t listLength =
((type == TextureAnimationParamsType::ColorChange) ? animLength : colorListCount);
if (listLength == 0)
HANDLE_ERROR_RESOURCE(WarningType::Always, parent, this, rawDataIndex,
"color list length cannot be 0", "");
primColorListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 4);
envColorListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 8);
frameDataListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 0xC);
uint32_t primColorListOffset = Seg2Filespace(primColorListAddress, parent->baseAddress);
uint32_t envColorListOffset = Seg2Filespace(envColorListAddress, parent->baseAddress);
uint32_t frameDataListOffset = Seg2Filespace(frameDataListAddress, parent->baseAddress);
uint32_t currentPtr;
F3DPrimColor currentPrimColor;
for (currentPtr = primColorListOffset; currentPtr < primColorListOffset + 5 * listLength;
currentPtr += 5)
{
currentPrimColor = {BitConverter::ToUInt8BE(rawData, currentPtr),
BitConverter::ToUInt8BE(rawData, currentPtr + 1),
BitConverter::ToUInt8BE(rawData, currentPtr + 2),
BitConverter::ToUInt8BE(rawData, currentPtr + 3),
BitConverter::ToUInt8BE(rawData, currentPtr + 4)};
primColorList.push_back(currentPrimColor);
}
F3DEnvColor currentEnvColor;
for (currentPtr = envColorListOffset; currentPtr < envColorListOffset + 4 * listLength;
currentPtr += 4)
{
currentEnvColor = {BitConverter::ToUInt8BE(rawData, currentPtr),
BitConverter::ToUInt8BE(rawData, currentPtr + 1),
BitConverter::ToUInt8BE(rawData, currentPtr + 2),
BitConverter::ToUInt8BE(rawData, currentPtr + 3)};
envColorList.push_back(currentEnvColor);
}
uint16_t currentFrameData;
for (currentPtr = frameDataListOffset; currentPtr < frameDataListOffset + 2 * listLength;
currentPtr += 2)
{
currentFrameData = BitConverter::ToUInt16BE(rawData, currentPtr);
frameDataList.push_back(currentFrameData);
}
}
std::string TextureColorChangingParams::GetSourceTypeName() const
{
return "AnimatedMatColorParams"; // TODO: Better name
}
std::string TextureColorChangingParams::GetDefaultName(const std::string& prefix) const
{
return StringHelper::Sprintf("%sColorParams_%06X", prefix.c_str(), rawDataIndex);
}
size_t TextureColorChangingParams::GetRawDataSize() const
{
return 0x10;
}
void TextureColorChangingParams::DeclareReferences([[maybe_unused]] const std::string& prefix)
{
if (primColorListAddress != 0) // NULL
{
std::string primColorBodyStr;
for (const auto& color : primColorList)
{
primColorBodyStr += StringHelper::Sprintf("\t{ %d, %d, %d, %d, %d },\n", color.r,
color.g, color.b, color.a, color.lodFrac);
}
primColorBodyStr.pop_back();
parent->AddDeclarationArray(
Seg2Filespace(primColorListAddress, parent->baseAddress), DeclarationAlignment::Align4,
primColorList.size() * 5, "F3DPrimColor",
StringHelper::Sprintf("%sTexColorChangingPrimColors_%06X", parent->GetName().c_str(),
Seg2Filespace(primColorListAddress, parent->baseAddress)),
primColorList.size(), primColorBodyStr);
}
if (envColorListAddress != 0) // NULL
{
std::string envColorBodyStr;
for (const auto& color : envColorList)
{
envColorBodyStr += StringHelper::Sprintf("\t{ %d, %d, %d, %d },\n", color.r, color.g,
color.b, color.a);
}
envColorBodyStr.pop_back();
parent->AddDeclarationArray(
Seg2Filespace(envColorListAddress, parent->baseAddress), DeclarationAlignment::Align4,
envColorList.size() * 4, "F3DEnvColor",
StringHelper::Sprintf("%sTexColorChangingEnvColors_%06X", parent->GetName().c_str(),
Seg2Filespace(envColorListAddress, parent->baseAddress)),
envColorList.size(), envColorBodyStr);
}
if (frameDataListAddress != 0) // NULL
{
std::string frameDataBodyStr = "\t";
for (const auto& frame : frameDataList)
{
frameDataBodyStr += StringHelper::Sprintf("%d, ", frame);
}
frameDataBodyStr.pop_back();
parent->AddDeclarationArray(
Seg2Filespace(frameDataListAddress, parent->baseAddress), DeclarationAlignment::Align4,
frameDataList.size() * 2, "u16",
StringHelper::Sprintf("%sTexColorChangingFrameData_%06X", parent->GetName().c_str(),
Seg2Filespace(frameDataListAddress, parent->baseAddress)),
frameDataList.size(), frameDataBodyStr);
}
}
std::string TextureColorChangingParams::GetBodySourceCode() const
{
std::string primColorListName;
std::string envColorListName;
std::string frameDataListName;
Globals::Instance->GetSegmentedPtrName(primColorListAddress, parent, "", primColorListName,
parent->workerID);
Globals::Instance->GetSegmentedPtrName(envColorListAddress, parent, "", envColorListName,
parent->workerID);
Globals::Instance->GetSegmentedPtrName(frameDataListAddress, parent, "", frameDataListName,
parent->workerID);
std::string bodyStr = StringHelper::Sprintf(
"\n %d, %d, %s, %s, %s,\n", animLength, colorListCount, primColorListName.c_str(),
envColorListName.c_str(), frameDataListName.c_str());
return bodyStr;
}
/* TextureCyclingParams */
void TextureCyclingParams::ParseRawData()
{
const auto& rawData = parent->GetRawData();
cycleLength = BitConverter::ToUInt16BE(rawData, rawDataIndex);
if (cycleLength == 0)
HANDLE_ERROR_RESOURCE(WarningType::Always, parent, this, rawDataIndex,
"cycle length cannot be 0", "");
textureListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 4);
textureIndexListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 8);
uint32_t textureListOffset = Seg2Filespace(textureListAddress, parent->baseAddress);
uint32_t textureIndexListOffset = Seg2Filespace(textureIndexListAddress, parent->baseAddress);
uint32_t currentPtr;
uint8_t currentIndex;
uint8_t maxIndex = 0; // To find the length of the texture list
for (currentPtr = textureIndexListOffset; currentPtr < textureIndexListOffset + cycleLength;
currentPtr++)
{
currentIndex = BitConverter::ToUInt8BE(rawData, currentPtr);
textureIndexList.push_back(currentIndex);
if (currentIndex > maxIndex)
maxIndex = currentIndex;
}
for (currentPtr = textureListOffset; currentPtr <= textureListOffset + 4 * maxIndex;
currentPtr += 4)
{
textureList.push_back(BitConverter::ToUInt32BE(rawData, currentPtr));
}
}
std::string TextureCyclingParams::GetSourceTypeName() const
{
return "AnimatedMatTexCycleParams"; // TODO: Better name
}
std::string TextureCyclingParams::GetDefaultName(const std::string& prefix) const
{
return StringHelper::Sprintf("%sTexCycleParams_%06X", prefix.c_str(), rawDataIndex);
}
size_t TextureCyclingParams::GetRawDataSize() const
{
return 0xC;
}
void TextureCyclingParams::DeclareReferences([[maybe_unused]] const std::string& prefix)
{
if (textureListAddress != 0) // NULL
{
std::string texturesBodyStr;
std::string texName;
std::string comment;
for (const auto& tex : textureList)
{
bool texFound =
Globals::Instance->GetSegmentedPtrName(tex, parent, "", texName, parent->workerID);
// texName is a raw segmented pointer. This occurs if the texture is not declared
// separately since we cannot read the format. In theory we could scan DLists for the
// format on the appropriate segments.
if (!texFound)
{
comment = " // Raw pointer, declare texture in XML to use proper symbol";
auto msgHeader = StringHelper::Sprintf(
"TexCycle texture array declared here points to unknown texture at address %s",
texName.c_str());
HANDLE_WARNING_RESOURCE(
WarningType::HardcodedPointer, parent, this, rawDataIndex, msgHeader,
"Please declare the texture in the XML to use the proper symbol.");
}
texturesBodyStr += StringHelper::Sprintf("\t%s,%s\n", texName.c_str(), comment.c_str());
}
texturesBodyStr.pop_back();
parent->AddDeclarationArray(
Seg2Filespace(textureListAddress, parent->baseAddress), DeclarationAlignment::Align4,
textureList.size() * 4, "TexturePtr",
StringHelper::Sprintf("%sTexCycleTexPtrs_%06X", parent->GetName().c_str(),
Seg2Filespace(textureListAddress, parent->baseAddress)),
textureList.size(), texturesBodyStr);
}
if (textureIndexListAddress != 0) // NULL
{
std::string indicesBodyStr = "\t";
for (uint8_t index : textureIndexList)
{
indicesBodyStr += StringHelper::Sprintf("%d, ", index);
}
indicesBodyStr.pop_back();
parent->AddDeclarationArray(
Seg2Filespace(textureIndexListAddress, parent->baseAddress),
DeclarationAlignment::Align4, textureIndexList.size(), "u8",
StringHelper::Sprintf("%sTexCycleTexIndices_%06X", parent->GetName().c_str(),
Seg2Filespace(textureIndexListAddress, parent->baseAddress)),
textureIndexList.size(), indicesBodyStr);
}
}
std::string TextureCyclingParams::GetBodySourceCode() const
{
std::string textureListName;
std::string textureIndexListName;
Globals::Instance->GetSegmentedPtrName(textureListAddress, parent, "", textureListName,
parent->workerID);
Globals::Instance->GetSegmentedPtrName(textureIndexListAddress, parent, "",
textureIndexListName, parent->workerID);
std::string bodyStr = StringHelper::Sprintf(
"\n %d, %s, %s,\n", cycleLength, textureListName.c_str(), textureIndexListName.c_str());
return bodyStr;
}
/* ZTextureAnimation */
ZTextureAnimation::ZTextureAnimation(ZFile* nParent) : ZResource(nParent)
{
}
/**
* Builds the array of params
*/
void ZTextureAnimation::ParseRawData()
{
ZResource::ParseRawData();
TextureAnimationEntry currentEntry;
auto rawData = parent->GetRawData();
int16_t type;
for (uint32_t currentPtr = rawDataIndex;; currentPtr += 8)
{
type = BitConverter::ToInt16BE(rawData, currentPtr + 2);
currentEntry.segment = BitConverter::ToInt8BE(rawData, currentPtr);
currentEntry.type = (TextureAnimationParamsType)type;
currentEntry.paramsPtr = BitConverter::ToUInt32BE(rawData, currentPtr + 4);
entries.push_back(currentEntry);
if ((type < 0) || (type > 6))
{
HANDLE_ERROR_RESOURCE(
WarningType::Always, parent, this, rawDataIndex,
StringHelper::Sprintf(
"unknown TextureAnimationParams type 0x%02X in TextureAnimation", type),
StringHelper::Sprintf(
"Entry reads { 0x%02X, 0x%02X, 0x%08X } , but type should be "
"between 0x00 and 0x06 inclusive.",
currentEntry.segment, type, currentEntry.paramsPtr));
}
if (currentEntry.segment <= 0)
break;
}
}
#include <optional>
/**
* For each params entry,
*/
void ZTextureAnimation::DeclareReferences(const std::string& prefix)
{
std::string varPrefix = name;
if (varPrefix == "")
varPrefix = prefix;
ZResource::DeclareReferences(varPrefix);
for (const auto& entry : entries)
{
if (entry.paramsPtr != 0 && GETSEGNUM(entry.paramsPtr) == parent->segment)
{
uint32_t paramsOffset = Seg2Filespace(entry.paramsPtr, parent->baseAddress);
if (!parent->HasDeclaration(paramsOffset))
{
ZTextureAnimationParams* params;
int count;
switch (entry.type)
{
case TextureAnimationParamsType::SingleScroll:
if (true)
{
count = 1;
// The else now allows SingleScroll to fall through to params = ... without
// touching the code in the else block
}
else
{
// The contents of this block can only be run by jumping into it with the
// case label
[[fallthrough]];
case TextureAnimationParamsType::DualScroll:
count = 2;
}
params = new TextureScrollingParams(parent);
params->ExtractFromBinary(paramsOffset, count);
break;
case TextureAnimationParamsType::ColorChange:
case TextureAnimationParamsType::ColorChangeLERP:
case TextureAnimationParamsType::ColorChangeLagrange:
params = new TextureColorChangingParams(parent);
params->type = entry.type;
params->ExtractFromBinary(paramsOffset);
break;
case TextureAnimationParamsType::TextureCycle:
params = new TextureCyclingParams(parent);
params->ExtractFromBinary(paramsOffset);
break;
case TextureAnimationParamsType::Empty:
HANDLE_WARNING_RESOURCE(
WarningType::InvalidExtractedData, parent, this, rawDataIndex,
"TextureAnimationParams entry has empty type (6), but params pointer is "
"not NULL",
StringHelper::Sprintf("Params read { 0x%02X, 0x%02X, 0x%08X } .",
entry.segment, (int)entry.type, entry.paramsPtr));
return;
default:
// Because GCC is worried this could happen
assert(entry.type < TextureAnimationParamsType::SingleScroll ||
entry.type > TextureAnimationParamsType::Empty);
return;
}
params->SetName(params->GetDefaultName(varPrefix));
params->DeclareVar(varPrefix, "");
parent->AddResource(params);
}
}
}
}
std::string ZTextureAnimation::GetSourceTypeName() const
{
return "AnimatedMaterial"; // TODO: Better name
}
ZResourceType ZTextureAnimation::GetResourceType() const
{
return ZResourceType::TextureAnimation;
}
/**
* The size of the main array
*/
size_t ZTextureAnimation::GetRawDataSize() const
{
return entries.size() * 8;
}
std::string ZTextureAnimation::GetDefaultName(const std::string& prefix) const
{
return StringHelper::Sprintf("%sTexAnim_%06X", prefix.c_str(), rawDataIndex);
}
Declaration* ZTextureAnimation::DeclareVar(const std::string& prefix, const std::string& bodyStr)
{
std::string auxName = name;
if (name == "")
auxName = GetDefaultName(prefix);
Declaration* decl =
parent->AddDeclarationArray(rawDataIndex, GetDeclarationAlignment(), GetRawDataSize(),
GetSourceTypeName(), auxName, entries.size(), bodyStr);
decl->staticConf = staticConf;
return decl;
}
std::string ZTextureAnimation::GetBodySourceCode() const
{
std::string bodyStr;
for (const auto& entry : entries)
{
std::string paramName;
Globals::Instance->GetSegmentedPtrName(entry.paramsPtr, parent, "", paramName,
parent->workerID);
bodyStr += StringHelper::Sprintf("\t{ %d, %d, %s },\n", entry.segment, entry.type,
paramName.c_str());
}
bodyStr.pop_back();
return bodyStr;
}