diff --git a/Makefile b/Makefile index b9067e97..113aee26 100644 --- a/Makefile +++ b/Makefile @@ -271,7 +271,7 @@ $$($(1)_DIR)/%.o: %.s $$($(1)_DIR)/%.o: %.c @mkdir -p $$(dir $$@) - $$(CPP) -E -lang-c -nostdinc -Iinclude $$< -o $$($(1)_DIR)/$$(*F).i && \ + $$(CPP) -E -lang-c -nostdinc -Iinclude $$(if $$(wildcard include/effect/params/$(1)),-Iinclude/effect/params/$(1)) $$< -o $$($(1)_DIR)/$$(*F).i && \ $$(if $$(filter $$<,$$(PSYQ43_SRCS)), \ $$(PSYQ43_CC1) -quiet $$(CC_FLAGS) $$($(1)_DIR)/$$(*F).i -o $$($(1)_DIR)/$$(*F).s && \ cat $$($(1)_DIR)/$$(*F).s | $$(MASPSX) $$(PSYQ43_MASPSXFLAGS) $$(call expand_div,$(1),$$<) --run-assembler $$(ASFLAGS) -o $$@, \ diff --git a/config/ff8.yaml b/config/ff8.yaml index 36c9e38c..ae67bb81 100644 --- a/config/ff8.yaml +++ b/config/ff8.yaml @@ -676,13 +676,22 @@ binaries: options: target_path: original/effect/effect_001.bin src_path: src/effect + section_order: [.text, .rodata, .data, .bss] segments: - name: effect_001 type: code start: 0x0 vram: 0x801A0000 subsegments: - - [0x0, c, effect_001] + - [0x0, c, effect_001_entry] + - [0x1F0, c, lib/common] + - [0x960, c, lib/aim] + - [0xAD8, c, lib/entity] + - [0x1648, c, lib/sprite] + - [0x16E4, c, lib/step] + - [0x172C, c, effect_001_anim] + - [0x181C, c, lib/render] + - [0x2E48, c, effect_001] - [0x4614, .rodata, effect_001] # The overlay's data, cut at every address the code names. The TIM parses # to exactly 0x2532C, where the five prim source tables begin. @@ -1039,14 +1048,33 @@ binaries: options: target_path: original/effect/effect_025.bin src_path: src/effect + section_order: [.text, .rodata, .data, .bss] segments: - name: effect_025 type: code start: 0x0 vram: 0x801A0000 subsegments: - - [0x0, c, effect_025] - - [0x8304, .rodata, effect_025] + - [0x0, c, effect_025_entry] + - [0x14C, c, lib/common] + - [0x8BC, c, lib/aim] + - [0xA34, c, lib/heading] + - [0xA64, c, lib/scatter] + - [0xB6C, c, lib/render] + - [0x2198, c, lib/entity] + - [0x2D08, c, lib/emit_mode] + - [0x4BFC, c, effect_025_scroll] + - [0x4F44, c, lib/sprite] + - [0x4FE0, c, effect_025_anim] + - [0x5204, c, lib/step] + - [0x524C, c, effect_025_motes] + - [0x5CEC, c, lib/fill] + - [0x5D10, c, lib/rot] + - [0x5F14, c, lib/pose] + - [0x6910, c, lib/interpolate] + - [0x6A00, c, effect_025] + - [0x8304, .rodata, effect_025_motes] + - [0x8344, .rodata, effect_025] - [0x83D0, bin, effect_025] - [0x33EA4] - name: effect_026 @@ -1084,14 +1112,38 @@ binaries: options: target_path: original/effect/effect_028.bin src_path: src/effect + section_order: [.text, .rodata, .data, .bss] segments: - name: effect_028 type: code start: 0x0 vram: 0x801A0000 subsegments: - - [0x0, c, effect_028] - - [0x9EFC, .rodata, effect_028] + - [0x0, c, effect_028_entry] + - [0x208, c, lib/common] + - [0x978, c, lib/rotz] + - [0xB04, c, lib/heading] + - [0xB34, c, lib/entity] + - [0x16A4, c, lib/tint] + - [0x1980, c, lib/vector] + - [0x20EC, c, lib/emit] + - [0x3F20, c, lib/packet] + - [0x4128, c, lib/particlestep] + - [0x541C, c, lib/particlealloc] + - [0x5538, c, lib/draw] + - [0x6194, c, lib/emitteralloc] + - [0x62B4, c, lib/spawn] + - [0x8068, c, lib/list] + - [0x8684, c, lib/trail] + - [0x87F8, c, lib/strands] + - [0x8CE4, c, lib/tables] + - [0x8FF8, c, lib/bankclear] + - [0x9050, c, lib/drawscript] + - [0x94B4, c, effect_028] + - [0x9EFC, .rodata, lib/particlestep] + - [0x9F10, pad] + - [0x9F14, .rodata, lib/list] + - [0x9F40, .rodata, effect_028] - [0x9FA0, bin, effect_028] - [0x3CCB0] - name: effect_029 diff --git a/config/symbols.effect_001.txt b/config/symbols.effect_001.txt new file mode 100644 index 00000000..43ce60a6 --- /dev/null +++ b/config/symbols.effect_001.txt @@ -0,0 +1,49 @@ +effectTextInit = 0x801A01F0; // type:func +effectTextHex = 0x801A0340; // type:func +effectTextNewline = 0x801A03EC; // type:func +effectNop = 0x801A040C; // type:func +effectDrawQuad128 = 0x801A0414; // type:func +effectDrawQuad256 = 0x801A0510; // type:func +effectMatrixIdentity = 0x801A060C; // type:func +effectMatrixRotX = 0x801A0644; // type:func +effectMatrixRotY = 0x801A07D4; // type:func +effectMatrixAim = 0x801A0960; // type:func +effectMatrixLookAt = 0x801A0A6C; // type:func +effectReleaseWait = 0x801A0AD8; // type:func +effectSpawnTask = 0x801A0B00; // type:func +effectCacheSlotAnchors = 0x801A0C8C; // type:func +effectSlotAnchor0 = 0x801A0DC0; // type:func +effectSlotAnchor1 = 0x801A0DF0; // type:func +effectSlotAnchorMid = 0x801A0E20; // type:func +effectCacheAnimSlotAnchors = 0x801A0E50; // type:func +effectAnimSlotAnchor0 = 0x801A0F8C; // type:func +effectAnimSlotAnchor1 = 0x801A0FBC; // type:func +effectAnimSlotAnchorMid = 0x801A0FEC; // type:func +effectUpdateModelBounds = 0x801A101C; // type:func +effectGetModelBounds = 0x801A1268; // type:func +effectModelRadius = 0x801A12B8; // type:func +effectModelHalfHeight = 0x801A135C; // type:func +effectModelHalfWidth = 0x801A138C; // type:func +effectModelHeight = 0x801A13E4; // type:func +effectModelTop = 0x801A1408; // type:func +effectModelBottom = 0x801A1450; // type:func +effectModelRandomY = 0x801A1498; // type:func +effectModelCentreY = 0x801A1550; // type:func +effectModelCentre = 0x801A15A4; // type:func +effectZeroWords = 0x801A1610; // type:func +effectDrawSprite = 0x801A1648; // type:func +effectStepCounter = 0x801A16E4; // type:func +effectRenderSetPose = 0x801A181C; // type:func +effectRenderPoseJoints = 0x801A1960; // type:func +effectRebasePointers = 0x801A1A0C; // type:func +effectRenderFaceCamera = 0x801A1A4C; // type:func +effectRenderReset = 0x801A1D38; // type:func +effectRenderSetTexture = 0x801A1DF4; // type:func +effectRenderMesh = 0x801A1EBC; // type:func +effectRenderModel = 0x801A2D0C; // type:func +g_effectPrimCursor = 0x801C58F4; +g_effectHexGlyphs = 0x801D5D00; +g_effectTextX = 0x801D5D14; +g_effectTextY = 0x801D5D18; +g_effectTextColour = 0x801D5D1C; +g_effectJointOffsets = 0x801D5D20; diff --git a/config/symbols.effect_025.txt b/config/symbols.effect_025.txt new file mode 100644 index 00000000..8decca0e --- /dev/null +++ b/config/symbols.effect_025.txt @@ -0,0 +1,72 @@ +effectTextInit = 0x801A014C; // type:func +effectTextHex = 0x801A029C; // type:func +effectTextNewline = 0x801A0348; // type:func +effectNop = 0x801A0368; // type:func +effectDrawQuad128 = 0x801A0370; // type:func +effectDrawQuad256 = 0x801A046C; // type:func +effectMatrixIdentity = 0x801A0568; // type:func +effectMatrixRotX = 0x801A05A0; // type:func +effectMatrixRotY = 0x801A0730; // type:func +effectMatrixAim = 0x801A08BC; // type:func +effectMatrixLookAt = 0x801A09C8; // type:func +effectMatrixHeading = 0x801A0A34; // type:func +effectScatter = 0x801A0A64; // type:func +effectRenderSetPose = 0x801A0B6C; // type:func +effectRenderPoseJoints = 0x801A0CB0; // type:func +effectRebasePointers = 0x801A0D5C; // type:func +effectRenderFaceCamera = 0x801A0D9C; // type:func +effectRenderReset = 0x801A1088; // type:func +effectRenderSetTexture = 0x801A1144; // type:func +effectRenderMesh = 0x801A120C; // type:func +effectRenderModel = 0x801A205C; // type:func +effectReleaseWait = 0x801A2198; // type:func +effectSpawnTask = 0x801A21C0; // type:func +effectCacheSlotAnchors = 0x801A234C; // type:func +effectSlotAnchor0 = 0x801A2480; // type:func +effectSlotAnchor1 = 0x801A24B0; // type:func +effectSlotAnchorMid = 0x801A24E0; // type:func +effectCacheAnimSlotAnchors = 0x801A2510; // type:func +effectAnimSlotAnchor0 = 0x801A264C; // type:func +effectAnimSlotAnchor1 = 0x801A267C; // type:func +effectAnimSlotAnchorMid = 0x801A26AC; // type:func +effectUpdateModelBounds = 0x801A26DC; // type:func +effectGetModelBounds = 0x801A2928; // type:func +effectModelRadius = 0x801A2978; // type:func +effectModelHalfHeight = 0x801A2A1C; // type:func +effectModelHalfWidth = 0x801A2A4C; // type:func +effectModelHeight = 0x801A2AA4; // type:func +effectModelTop = 0x801A2AC8; // type:func +effectModelBottom = 0x801A2B10; // type:func +effectModelRandomY = 0x801A2B58; // type:func +effectModelCentreY = 0x801A2C10; // type:func +effectModelCentre = 0x801A2C64; // type:func +effectZeroWords = 0x801A2CD0; // type:func +effectEmitTris = 0x801A2D08; // type:func +effectEmitQuads = 0x801A2FE8; // type:func +effectEmitTexTris = 0x801A3320; // type:func +effectEmitTexQuads = 0x801A36B4; // type:func +effectEmitGouraudTris = 0x801A3AAC; // type:func +effectEmitGouraudQuads = 0x801A3DD4; // type:func +effectEmitGouraudTexTris = 0x801A4174; // type:func +effectEmitGouraudTexQuads = 0x801A4548; // type:func +effectEmitMesh = 0x801A49A8; // type:func +effectDrawSprite = 0x801A4F44; // type:func +effectStepCounter = 0x801A5204; // type:func +effectFillWords = 0x801A5CEC; // type:func +effectMatrixSetRotX = 0x801A5D10; // type:func +effectMatrixSetRotY = 0x801A5D8C; // type:func +effectMatrixSetRotZ = 0x801A5E08; // type:func +effectMatrixSetRotZXY = 0x801A5E84; // type:func +effectPoseInit = 0x801A5F14; // type:func +effectPoseFindShorts = 0x801A6094; // type:func +effectPoseFindWords = 0x801A60FC; // type:func +effectPoseStepChannel = 0x801A6164; // type:func +effectPoseWalk = 0x801A6354; // type:func +effectInterpolateFrames = 0x801A6910; // type:func +g_effectPrimCursor = 0x801D3EB8; +g_effectHexGlyphs = 0x801ED404; +g_effectTextX = 0x801ED418; +g_effectTextY = 0x801ED41C; +g_effectTextColour = 0x801ED420; +g_effectJointOffsets = 0x801ED424; +g_effectDrawMode = 0x801EEA0C; diff --git a/config/symbols.effect_028.txt b/config/symbols.effect_028.txt new file mode 100644 index 00000000..190c50fe --- /dev/null +++ b/config/symbols.effect_028.txt @@ -0,0 +1,113 @@ +effectTextInit = 0x801A0208; // type:func +effectTextHex = 0x801A0358; // type:func +effectTextNewline = 0x801A0404; // type:func +effectNop = 0x801A0424; // type:func +effectDrawQuad128 = 0x801A042C; // type:func +effectDrawQuad256 = 0x801A0528; // type:func +effectMatrixIdentity = 0x801A0624; // type:func +effectMatrixRotX = 0x801A065C; // type:func +effectMatrixRotY = 0x801A07EC; // type:func +effectMatrixRotZ = 0x801A0978; // type:func +effectMatrixHeading = 0x801A0B04; // type:func +effectReleaseWait = 0x801A0B34; // type:func +effectSpawnTask = 0x801A0B5C; // type:func +effectCacheSlotAnchors = 0x801A0CE8; // type:func +effectSlotAnchor0 = 0x801A0E1C; // type:func +effectSlotAnchor1 = 0x801A0E4C; // type:func +effectSlotAnchorMid = 0x801A0E7C; // type:func +effectCacheAnimSlotAnchors = 0x801A0EAC; // type:func +effectAnimSlotAnchor0 = 0x801A0FE8; // type:func +effectAnimSlotAnchor1 = 0x801A1018; // type:func +effectAnimSlotAnchorMid = 0x801A1048; // type:func +effectUpdateModelBounds = 0x801A1078; // type:func +effectGetModelBounds = 0x801A12C4; // type:func +effectModelRadius = 0x801A1314; // type:func +effectModelHalfHeight = 0x801A13B8; // type:func +effectModelHalfWidth = 0x801A13E8; // type:func +effectModelHeight = 0x801A1440; // type:func +effectModelTop = 0x801A1464; // type:func +effectModelBottom = 0x801A14AC; // type:func +effectModelRandomY = 0x801A14F4; // type:func +effectModelCentreY = 0x801A15AC; // type:func +effectModelCentre = 0x801A1600; // type:func +effectZeroWords = 0x801A166C; // type:func +effectTintPlay = 0x801A16A4; // type:func +effectRandBetween = 0x801A1980; // type:func +effectRandProduct = 0x801A1A04; // type:func +effectRandVector = 0x801A1AA8; // type:func +effectMatrixFromAngles = 0x801A1B20; // type:func +effectJitterAngle = 0x801A1BEC; // type:func +effectJitterVector = 0x801A1C80; // type:func +effectJitterAngleS = 0x801A1CF8; // type:func +effectJitterSVector = 0x801A1D88; // type:func +effectCopyVector = 0x801A1E00; // type:func +effectFadeValue = 0x801A1E30; // type:func +effectFadeVector = 0x801A1E8C; // type:func +effectBlendPointSets = 0x801A1F58; // type:func +effectEmitTris = 0x801A20EC; // type:func +effectEmitQuads = 0x801A23B8; // type:func +effectEmitTexTris = 0x801A26DC; // type:func +effectEmitTexQuads = 0x801A2A64; // type:func +effectEmitGouraudTris = 0x801A2E48; // type:func +effectEmitGouraudQuads = 0x801A3150; // type:func +effectEmitGouraudTexTris = 0x801A34D8; // type:func +effectEmitGouraudTexQuads = 0x801A3894; // type:func +effectEmitStream = 0x801A3CDC; // type:func +effectBuildMeshPacket = 0x801A3F20; // type:func +effectParticleLateStep = 0x801A4128; // type:func +effectParticleAdvance = 0x801A41E0; // type:func +effectParticleAgeHold = 0x801A49E8; // type:func +effectParticlePose = 0x801A4ADC; // type:func +effectParticleAlloc = 0x801A541C; // type:func +effectStepRunKind = 0x801A5538; // type:func +effectStepFire = 0x801A5570; // type:func +effectParticleStep = 0x801A561C; // type:func +effectEmitterCountStep = 0x801A5708; // type:func +effectSpriteEmit = 0x801A5720; // type:func +effectSpritePacket = 0x801A5D4C; // type:func +effectParticleDrawPacket = 0x801A5FB4; // type:func +effectDrawParticles = 0x801A6074; // type:func +effectEmitterAlloc = 0x801A6194; // type:func +effectEmitterAge = 0x801A62B4; // type:func +effectEmitterSpawn = 0x801A631C; // type:func +effectEmitterSpawnShare = 0x801A6DB8; // type:func +effectEmitterStep = 0x801A6EB8; // type:func +effectEmitterOnStrand = 0x801A6F58; // type:func +effectEmitterOnTrail = 0x801A71E0; // type:func +effectEmitterOnOwner = 0x801A73E8; // type:func +effectEmitterSlide = 0x801A75F8; // type:func +effectListAppend = 0x801A8068; // type:func +effectListRemove = 0x801A80A4; // type:func +effectListClear = 0x801A80E4; // type:func +effectListRun = 0x801A80FC; // type:func +effectEmitterSetup = 0x801A8184; // type:func +effectStepFireSlots = 0x801A83B8; // type:func +effectEmittersOnCue = 0x801A846C; // type:func +effectTrailPush = 0x801A85A8; // type:func +effectTrailAim = 0x801A8684; // type:func +effectStrandsSeed = 0x801A87F8; // type:func +effectStrandsPush = 0x801A8904; // type:func +effectStrandsAim = 0x801A8A1C; // type:func +effectPoseTablesRelocate = 0x801A8CE4; // type:func +effectStartChildScript = 0x801A8F88; // type:func +effectBanksClear = 0x801A8FF8; // type:func +effectDrawScriptStart = 0x801A9050; // type:func +effectDrawScriptRun = 0x801A92F8; // type:func +effectDrawListSpan = 0x801A9420; // type:func +g_effectStackBase = 0x801D3CD4; +g_effectStackTop = 0x801D3CD8; +g_effectEmitterBank = 0x801D3F7C; +g_effectParticleBank = 0x801D3F80; +g_effectDrawList = 0x801D3F84; +g_effectEmitterCursor = 0x801D3F88; +g_effectParticleCursor = 0x801D3F8A; +g_effectParticleTotal = 0x801D3F8C; +g_effectLiveTotal = 0x801D3F8E; +g_effectBankUnk90 = 0x801D3F90; +g_effectBankUnk92 = 0x801D3F92; +g_effectChildPool = 0x801D478C; +g_effectPrimCursor = 0x801D479C; +g_effectHexGlyphs = 0x801D4ADC; +g_effectTextX = 0x801D4AF0; +g_effectTextY = 0x801D4AF4; +g_effectTextColour = 0x801D4AF8; diff --git a/include/effect/effect_001.h b/include/effect/effect_001.h index 75e60e1d..cce9881f 100644 --- a/include/effect/effect_001.h +++ b/include/effect/effect_001.h @@ -3,17 +3,6 @@ #include "effect.h" -/** - * @brief Entry point of the effect_001 overlay. - * - * battle.bin calls this at the overlay's load address once the image is in - * place; it builds the effect's root entity and its task pools. - * - * @param animSet Animation table the effect draws its models from. - * @return The task pool the effect's scripts run out of. - */ -void *func_801A0000(EffectAnimSet *animSet); - /** * @name Overlay data * @@ -83,7 +72,6 @@ extern MATRIX *D_801C58EC; extern u8 *D_801C58F0; /** @brief Cursors this frame's prims and frames are written through. */ -extern void *D_801C58F4; extern void *D_801C58F8; /** @brief The two bank pairs, one per parity of the frame counter. */ @@ -114,17 +102,9 @@ extern EffectRender D_801D3800; extern s32 D_801D3900; extern s32 D_801D5CF0; -/** @brief The sixteen hex digit glyphs, cached from the main string table. */ -extern u8 D_801D5D00[]; - -/** @brief Debug text cursor: X, Y, and the colour the glyphs are drawn in. */ -extern s32 D_801D5D14; -extern s32 D_801D5D18; -extern s32 D_801D5D1C; - -/** @brief Per-joint world matrices, one per joint of the posed skeleton. */ -extern MATRIX D_801D5D20[]; - /** @} */ +/** @brief Advance one effect script by a single frame. */ +s32 func_801A4434(EffectEntity *entity); + #endif /* EFFECT_EFFECT_001_H */ diff --git a/include/effect/effect_001_anim.h b/include/effect/effect_001_anim.h new file mode 100644 index 00000000..b1bb22b8 --- /dev/null +++ b/include/effect/effect_001_anim.h @@ -0,0 +1,18 @@ +#ifndef EFFECT_EFFECT_001_ANIM_H +#define EFFECT_EFFECT_001_ANIM_H + +#include "effect.h" + +/** + * @file effect_001_anim.h + * @brief Cure: draw an animation frame at the entity. + */ + +/** + * @brief Draw @p anim at the entity's position in @p colour and link it into + * the OT. + */ +void func_801A172C(EffectEntity *entity, BattleSpriteAnim *anim, + CVECTOR *colour); + +#endif /* EFFECT_EFFECT_001_ANIM_H */ diff --git a/include/effect/effect_001_entry.h b/include/effect/effect_001_entry.h new file mode 100644 index 00000000..c4b8ab57 --- /dev/null +++ b/include/effect/effect_001_entry.h @@ -0,0 +1,22 @@ +#ifndef EFFECT_EFFECT_001_ENTRY_H +#define EFFECT_EFFECT_001_ENTRY_H + +#include "effect.h" + +/** + * @file effect_001_entry.h + * @brief Cure: the overlay entry point. + */ + +/** + * @brief Entry point of the effect_001 overlay. + * + * battle.bin calls this at the overlay's load address once the image is in + * place; it builds the effect's root entity and its task pools. + * + * @param animSet Animation table the effect draws its models from. + * @return The task pool the effect's scripts run out of. + */ +void *func_801A0000(EffectAnimSet *animSet); + +#endif /* EFFECT_EFFECT_001_ENTRY_H */ diff --git a/include/effect/effect_025.h b/include/effect/effect_025.h index 5d64ab4d..70864f0a 100644 --- a/include/effect/effect_025.h +++ b/include/effect/effect_025.h @@ -2,36 +2,7 @@ #define EFFECT_EFFECT_025_H #include "effect.h" - -/** - * @brief Entry point of the effect_025 overlay. - * - * battle.bin calls this at the overlay's load address once the image is in - * place; it builds the effect's root entity and its task pools. - * - * @param animSet Animation table the effect draws its models from. - * @return The task pool the effect's scripts run out of. - */ -void *func_801A0000(EffectAnimSet *animSet); - -/** @brief One animated part of a posed model; its flags size its pose slot. */ -typedef struct { - /* 0x00 */ s32 unk000; - /* 0x04 */ s32 flags; -} EffectPosePart; - -/** @brief The part list a posed model carries, each part at a byte offset. */ -typedef struct { - /* 0x00 */ s32 count; - /* 0x04 */ s32 unk004; - /* 0x08 */ s32 offsets[1]; -} EffectPoseParts; - -/** @brief A posed model: a header naming the part list behind it. */ -typedef struct { - /* 0x00 */ s32 unk000; - /* 0x04 */ s32 partsOffset; -} EffectPoseModel; +#include "effect/lib/pose.h" /** * @name Overlay data @@ -88,12 +59,6 @@ extern s32 D_801D7324; /** @brief Texture the posed model is drawn with. */ extern s32 D_801D3EA4; -/** @brief Cursor this frame's prims are written through. */ -extern void *D_801D3EB8; - -/** @brief Draw-mode command word every emitted prim carries. */ -extern u32 D_801EEA0C; - /** @brief Script entity pool: 4 entries of 0x58. */ extern s32 D_801D4104; @@ -122,17 +87,12 @@ extern s32 D_801E74F4; extern EffectRenderPart D_801E7504; extern s32 D_801E7534; -/** @brief The sixteen hex digit glyphs, cached from the main string table. */ -extern u8 D_801ED404[]; - -/** @brief Debug text cursor: X, Y, and the colour the glyphs are drawn in. */ -extern s32 D_801ED418; -extern s32 D_801ED41C; -extern s32 D_801ED420; - -/** @brief Per-joint world matrices, one per joint of the posed skeleton. */ -extern MATRIX D_801ED424[]; - /** @} */ +/** + * @brief Root script dispatcher: swap the prim bank, run one step, then run + * every child pool. + */ +s32 func_801A81D0(EffectEntity *entity); + #endif /* EFFECT_EFFECT_025_H */ diff --git a/include/effect/effect_025_anim.h b/include/effect/effect_025_anim.h new file mode 100644 index 00000000..d858952f --- /dev/null +++ b/include/effect/effect_025_anim.h @@ -0,0 +1,21 @@ +#ifndef EFFECT_EFFECT_025_ANIM_H +#define EFFECT_EFFECT_025_ANIM_H + +#include "effect.h" + +/** + * @file effect_025_anim.h + * @brief Cura: draw an animation frame at the entity, upright or rolled. + */ + +/** + * @brief Draw @p anim upright at the entity's position, tinted with @p colour. + */ +void func_801A4FE0(EffectEntity *entity, BattleSpriteAnim *anim, + CVECTOR *colour); + +/** @brief As @ref func_801A4FE0, rolled by @p angle and scaled by @p scale. */ +void func_801A50D4(EffectEntity *entity, BattleSpriteAnim *anim, + CVECTOR *colour, s16 angle, s16 scale); + +#endif /* EFFECT_EFFECT_025_ANIM_H */ diff --git a/include/effect/effect_025_entry.h b/include/effect/effect_025_entry.h new file mode 100644 index 00000000..15098b0b --- /dev/null +++ b/include/effect/effect_025_entry.h @@ -0,0 +1,22 @@ +#ifndef EFFECT_EFFECT_025_ENTRY_H +#define EFFECT_EFFECT_025_ENTRY_H + +#include "effect.h" + +/** + * @file effect_025_entry.h + * @brief Cura: the overlay entry point. + */ + +/** + * @brief Entry point of the effect_025 overlay. + * + * battle.bin calls this at the overlay's load address once the image is in + * place; it builds the effect's root entity and its task pools. + * + * @param animSet Animation table the effect draws its models from. + * @return The task pool the effect's scripts run out of. + */ +void *func_801A0000(EffectAnimSet *animSet); + +#endif /* EFFECT_EFFECT_025_ENTRY_H */ diff --git a/include/effect/effect_025_motes.h b/include/effect/effect_025_motes.h new file mode 100644 index 00000000..cc6b29d6 --- /dev/null +++ b/include/effect/effect_025_motes.h @@ -0,0 +1,22 @@ +#ifndef EFFECT_EFFECT_025_MOTES_H +#define EFFECT_EFFECT_025_MOTES_H + +#include "effect.h" + +/** + * @file effect_025_motes.h + * @brief Cura: the rising, falling and arc mote scripts. + */ + +/** + * @brief Script dispatcher: run this frame's step, draw, and retire the entity. + */ +s32 func_801A533C(EffectEntity *entity); + +/** @brief Script dispatcher: the falling-mote script, drawn spun and tinted. */ +s32 func_801A54DC(EffectEntity *entity); + +/** @brief Script dispatcher: the rising-mote script, drawn tinted. */ +s32 func_801A5C24(EffectEntity *entity); + +#endif /* EFFECT_EFFECT_025_MOTES_H */ diff --git a/include/effect/effect_025_scroll.h b/include/effect/effect_025_scroll.h new file mode 100644 index 00000000..e6047a71 --- /dev/null +++ b/include/effect/effect_025_scroll.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_EFFECT_025_SCROLL_H +#define EFFECT_EFFECT_025_SCROLL_H + +#include "effect.h" + +/** + * @file effect_025_scroll.h + * @brief Cura: scroll the textured quad list's V coordinates. + */ + +/** @brief Scroll the textured quad list's V coordinates by @p delta. */ +void func_801A4D9C(s32 *header, s32 delta); + +#endif /* EFFECT_EFFECT_025_SCROLL_H */ diff --git a/include/effect/effect_028.h b/include/effect/effect_028.h index d2a91a15..7a8309ea 100644 --- a/include/effect/effect_028.h +++ b/include/effect/effect_028.h @@ -2,310 +2,23 @@ #define EFFECT_EFFECT_028_H #include "effect.h" +#include "effect/lib/particle.h" +#include "effect/lib/tint.h" /** * @file * @brief Curaga: the overlay's own storage. */ -/** - * @brief Start the effect's script and hand back its task pool. - * - * @param animSet Animation set the effect plays. - * @return The pool the root entity lives in. - */ -void *func_801A0000(EffectAnimSet *animSet); - -/** @brief A packet's far colour: three bytes and the code byte beside them. */ -typedef struct { - /* 0x00 */ u8 r; - /* 0x01 */ u8 g; - /* 0x02 */ u8 b; - /* 0x03 */ u8 code; -} EffectPrimColor; /* 0x04 */ - -/** @brief One step of the pose script the emitters read. */ -typedef struct { - /* 0x00 */ s32 offsetX; /**< Offset added to the point, 16.16. */ - /* 0x04 */ s32 offsetY; - /* 0x08 */ s32 offsetZ; - /* 0x0C */ u8 pad00C[0x11 - 0xC]; - /* 0x11 */ u8 unk011; - /* 0x12 */ u8 unk012; /**< Frames a mote taken from this source lives. */ - /* 0x13 */ u8 unk013; /**< Frame of the list's life the source fires on. */ - /* 0x14 */ u8 pad014[0x16 - 0x14]; - /* 0x16 */ u8 mode; /**< 4 and 5 take the late step. */ - /* 0x17 */ u8 unk017; - /* 0x18 */ u8 unk018; - /* 0x19 */ u8 unk019; - /* 0x1A */ u8 unk01A; - /* 0x1B */ u8 unk01B; /**< Set, the pose's Y is flattened every frame. */ - /* 0x1C */ u8 unk01C; - /* 0x1D */ u8 unk01D; /**< Which order the pose's three turns are applied in. */ - /* 0x1E */ u8 kind; /**< Selects which of the four steps runs. */ - /* 0x1F */ u8 unk01F; - /* 0x20 */ u8 unk020; - /* 0x21 */ u8 unk021; /**< Where a spawned node's own spin comes from. */ - /* 0x22 */ u8 unk022; /**< Where the posed offsets are written back to. */ - /* 0x23 */ u8 pad023[0x25 - 0x23]; - /* 0x25 */ u8 anchor; /**< Which of the trail's points the motes start at. */ - /* 0x26 */ u8 unk026; /**< Set, the pose is turned before it is applied. */ - /* 0x27 */ u8 unk027; /**< Which way the pose's offsets are built. */ - /* 0x28 */ u8 unk028; - /* 0x29 */ u8 unk029; - /* 0x2A */ u8 unk02A; - /* 0x2B */ u8 unk02B; - /* 0x2C */ u8 unk02C; - /* 0x2D */ u8 unk02D; - /* 0x2E */ u8 unk02E; - /* 0x2F */ u8 unk02F; - /* 0x30 */ u8 unk030; - /* 0x31 */ u8 unk031; - /* 0x32 */ u8 pad032; - /* 0x33 */ u8 unk033; /**< Set, the spawn speeds are taken as they come. */ - /* 0x34 */ u8 unk034; /**< Which way a spawned node is aimed. */ - /* 0x35 */ u8 midpoint; /**< Set, motes start midway along the node. */ - /* 0x36 */ u8 unk036; /**< Splits a frame's work into chunks this big. */ - /* 0x37 */ u8 unk037; /**< Set, the source fires once per strand point. */ - /* 0x038 */ s16 unk038; /**< Added to the node's Z once it is placed. */ - /* 0x03A */ s16 unk03A; - /* 0x03C */ SVECTOR unk03C; - /* 0x044 */ SVECTOR unk044; - /* 0x04C */ s32 unk04C; - /* 0x050 */ s32 unk050; - /* 0x054 */ s32 unk054; - /* 0x058 */ s32 unk058; - /* 0x05C */ s32 unk05C; - /* 0x060 */ s32 unk060; - /* 0x064 */ u16 unk064; /**< Drag a spawned node keeps its motion with. */ - /* 0x066 */ u8 pad066[0x68 - 0x66]; - /* 0x068 */ VECTOR unk068; - /* 0x078 */ VECTOR unk078; - /* 0x088 */ VECTOR unk088; - /* 0x098 */ VECTOR unk098; - /* 0x0A8 */ VECTOR unk0A8; - /* 0x0B8 */ VECTOR unk0B8; - /* - * 0x0C8 to 0x144 is one table per animated property, each indexed by the - * frame the node is showing. They are stored as offsets from the pose - * tables' own start; @ref func_801A8CE4 turns all thirty-two into - * addresses the first time a script uses them. - */ - /* 0x0C8 */ u8 *unk0C8; /**< How many motes the source takes that frame. */ - /* 0x0CC */ u16 *unk0CC; - /* 0x0D0 */ u16 *unk0D0; - /* 0x0D4 */ u16 *unk0D4; - /* 0x0D8 */ u16 *unk0D8; - /* 0x0DC */ u16 *unk0DC; - /* 0x0E0 */ u16 *unk0E0; - /* 0x0E4 */ u16 *unk0E4; - /* 0x0E8 */ u16 *unk0E8; - /* 0x0EC */ u16 *unk0EC; - /* 0x0F0 */ u8 *unk0F0; - /* 0x0F4 */ u8 *unk0F4; - /* 0x0F8 */ u8 *unk0F8; - /* 0x0FC */ u16 *unk0FC; - /* 0x100 */ u8 *unk100; - /* 0x104 */ u8 *unk104; - /* 0x108 */ u8 *unk108; - /* 0x10C */ u8 *unk10C; - /* 0x110 */ u16 *unk110; - /* 0x114 */ u16 *unk114; - /* 0x118 */ u16 *unk118; - /* 0x11C */ u16 *unk11C; - /* 0x120 */ u16 *unk120; - /* 0x124 */ u16 *unk124; - /* 0x128 */ s16 *unk128; - /* 0x12C */ u8 *targets; /**< One target point set per frame. */ - /* 0x130 */ u8 *framesA; /**< First source point set per frame. */ - /* 0x134 */ u8 *framesB; /**< Second source point set per frame. */ - /* 0x138 */ s16 *weights; /**< Weight of the second source, per frame. */ - /* 0x13C */ s16 *unk13C; - /* 0x140 */ s16 *unk140; - /* 0x144 */ s16 *unk144; - /* 0x148 */ SVECTOR unk148; - /* 0x150 */ SVECTOR unk150; -} EffectPoseStep; /* 0x158 */ - -/** - * @brief The tighter of the two record kinds the draw list carries. - * - * Its bank packs these 0x6C apart, so it shares only the head of - * @ref EffectDrawNode and is linked through the same two pointers. - */ -typedef struct EffectMote { - /* 0x00 */ struct EffectDrawNode *prev; - /* 0x04 */ struct EffectDrawNode *next; - /* 0x08 */ u16 unk008; - /* 0x0A */ u8 pad00A[0x2C - 0xA]; - /* 0x2C */ MATRIX mtx; /**< Turns the step's offset into world space. */ - /* 0x4C */ VECTOR pos; /**< Where the mote sits, 16.16. */ - /* 0x5C */ struct EffectDrawNode *owner; - /* 0x60 */ s16 unk060; /**< Frames this spark has been drawn for. */ - /* 0x62 */ s16 unk062; /**< Zero until it has been set up once. */ - /* 0x64 */ s16 life; /**< Counts down; the spark unlinks at zero. */ - /* 0x66 */ u8 pad066[0x68 - 0x66]; - /* 0x68 */ u8 kind; /**< Selects the per-frame step below. */ - /* 0x69 */ u8 taken; /**< Non-zero while this slot is in use. */ - /* 0x6A */ u8 unk06A; /**< Which of the list's sources drives it. */ - /* 0x6B */ u8 unk06B; -} EffectMote; /* 0x6C */ - -/** @brief One entry on the effect's draw list, linked both ways. */ -typedef struct EffectDrawNode { - /* 0x000 */ struct EffectDrawNode *prev; - /* 0x004 */ struct EffectDrawNode *next; - /* 0x008 */ s16 unk008; - /* 0x00A */ u8 pad00A[0xC - 0xA]; - /* 0x00C */ MATRIX poses[2]; /**< One per copy; only two fit before the header. */ - /* 0x04C */ u8 pad04C[0x60 - 0x4C]; - /* 0x060 */ u16 unk060; /**< Frames this node has been drawn for. */ - /* 0x062 */ s16 unk062; /**< Zero until the node has been set up once. */ - /* 0x064 */ s16 life; /**< Counts down; the node unlinks at zero. */ - /* 0x066 */ u8 pad066[0x68 - 0x66]; - /* 0x068 */ u8 kind; /**< Selects the per-frame step below. */ - /* 0x069 */ u8 unk069; - /* 0x06A */ u8 unk06A; /**< Which of the list's sources drives the emitter. */ - /* 0x06B */ u8 pad06B[0x8C - 0x6B]; - /* 0x08C */ MATRIX orient; /**< Turn the motes hung off this node inherit. */ - /* 0x0AC */ MATRIX mtx; /**< Pose the node's model is drawn with. */ - /* 0x0CC */ SVECTOR angle; /**< This frame's turn, wrapped to a whole circle. */ - /* 0x0D4 */ s16 scaleX; /**< This frame's scale, before it is applied. */ - /* 0x0D6 */ s16 scaleY; - /* 0x0D8 */ s16 scaleZ; - /* 0x0DA */ u8 pad0DA[0xDC - 0xDA]; - /* 0x0DC */ VECTOR pos[4]; /**< Where each copy sits. */ - /* 0x11C */ VECTOR lastPos; /**< Where the first copy sat last frame. */ - /* 0x12C */ VECTOR offset[4]; /**< The turned offset each copy is drawn at. */ - /* 0x16C */ EffectPrimColor color; - /* 0x170 */ void *stream; /**< What the node draws -- a mesh stream or a - sprite animation, by kind -- or NULL. */ - /* 0x174 */ s32 spin[4]; /**< How far each copy has spun about its pose. */ - /* 0x184 */ s32 spinRate[4]; - /* 0x194 */ SVECTOR angleBase; /**< The turn this frame's angle starts from. */ - /* 0x19C */ SVECTOR offsets[1]; /**< Where each copy past the first is placed. */ - /* 0x1A4 */ u8 pad1A4[0x1BC - 0x1A4]; - /* 0x1BC */ struct EffectMote *owner; - /* 0x1C0 */ u8 pad1C0[0x1C8 - 0x1C0]; - /* 0x1C8 */ s16 unk1C8; /**< Counts down; a step fires when it goes negative. */ - /* 0x1CA */ s16 unk1CA; /**< Which frame of the pose the node is showing. */ - /* 0x1CC */ s16 unk1CC; /**< Which phase of its life the node is in. */ - /* 0x1CE */ s16 unk1CE; /**< Depth cue: zero leaves the node unshaded. */ - /* 0x1D0 */ u8 unk1D0; /**< Frames the current step has held for. */ - /* 0x1D1 */ u8 unk1D1; /**< How long it holds before advancing. */ - /* 0x1D2 */ u8 unk1D2; - /* 0x1D3 */ u8 unk1D3; - /* 0x1D4 */ u8 unk1D4; - /* 0x1D5 */ u8 unk1D5; /**< Repeats left before the hold gives up. */ - /* 0x1D6 */ u8 unk1D6; /**< Which of the list's sources this node came from. */ - /* 0x1D7 */ u8 unk1D7; - /* 0x1D8 */ u8 copies; /**< How many times the node draws its model. */ - /* 0x1D9 */ u8 unk1D9; - /* 0x1DA */ s16 unk1DA; /**< Drag: how much of each frame's motion is kept. */ - /* 0x1DC */ s32 unk1DC; /**< Added to every copy's Y velocity each frame. */ - /* 0x1E0 */ VECTOR vel[4]; - /* 0x220 */ VECTOR accel[4]; - /* 0x260 */ VECTOR jerk[4]; -} EffectDrawNode; /* 0x2A0 */ - -/** @brief A set of posed points: a two-word header, then @c count of them. */ -typedef struct EffectPointSet { - /* 0x00 */ s32 unk000; - /* 0x04 */ s32 count; - /* 0x08 */ SVECTOR points[1]; -} EffectPointSet; - -/** - * @brief The tables a script is handed: sixteen sources and their point sets. - * - * @ref func_801A8CE4 fills the pointers in once and sets @c ready so a restart - * does not do it again. - */ -typedef struct { - /* 0x000 */ s32 ready; - /* 0x004 */ EffectPoseStep *sources[16]; - /* 0x044 */ void *unk044[16]; - /* 0x084 */ EffectPointSet *targets[16]; - /* 0x0C4 */ EffectPointSet *frames[16]; -} EffectPoseTables; /* 0x104 */ - -/** @brief The draw list the emitters walk, with its head and tail. */ -typedef struct { - /* 0x000 */ s32 spread; /**< Distance between the two slots the effect spans. */ - /* 0x004 */ s32 parts[4]; /**< The model part each strand is built from. */ - /* 0x014 */ u16 count; - /* 0x016 */ u16 live; /**< Nodes still on the list. */ - /* 0x018 */ s16 age; /**< Frames this list has been running. */ - /* 0x01A */ s16 span; /**< How long it runs before it may finish. */ - /* 0x01C */ s16 strandLen; /**< How many points of each strand are used. */ - /* 0x01E */ s16 slot; /**< Battle slot the effect is aimed at. */ - /* 0x020 */ s16 phase; /**< Which stage of its life the list is in. */ - /* 0x022 */ u16 held; /**< One bit per source: set, the source is held off. */ - /* 0x024 */ s16 unk024; - /* 0x026 */ u8 pad026[0x2C - 0x26]; - /* 0x02C */ EffectDrawNode *head; - /* 0x030 */ EffectDrawNode *tail; - /* 0x034 */ u8 pad034[0x54 - 0x34]; - /* 0x054 */ VECTOR strands[5][4]; /**< The five strands the effect draws. */ - /* 0x194 */ VECTOR strandSeed; /**< The point every strand starts from. */ - /* 0x1A4 */ VECTOR strandHead; /**< The newest point, pushed on each frame. */ - /* 0x1B4 */ VECTOR trail[6]; /**< Where the head has been, newest first. */ - /* 0x214 */ s32 unk214; - /* 0x218 */ s32 unk218; - /* 0x21C */ s32 unk21C; - /* 0x220 */ u8 pad220[0x224 - 0x220]; - /* 0x224 */ EffectPoseStep **sources; - /* 0x228 */ void **unk228; - /* 0x22C */ struct EffectPointSet **targets; /**< Point sets the poses write. */ - /* 0x230 */ struct EffectPointSet **frames; /**< Point sets the poses read. */ -} EffectDrawList; /* 0x234 */ - -/** @brief The one draw list this effect builds into. */ -extern EffectDrawList *D_801D3F84; - -/** @brief One step of the tint script: a colour and what to do with it. */ -typedef struct { - /* 0x00 */ u8 r; - /* 0x01 */ u8 g; - /* 0x02 */ u8 b; /**< A hold step keeps the frame it waits for here. */ - /* 0x03 */ u8 op; /**< 0xFF ends the script, 0xFE holds, anything else steps. */ -} EffectTintStep; /* 0x04 */ - -/** @brief Cursors this frame's prims and frames are written through. */ -extern void *D_801D479C; - extern s32 D_801D49FC; -/** @brief The sixteen hex digit glyphs, cached from the main string table. */ -extern u8 D_801D4ADC[]; - -/** @brief Debug text cursor: X, Y, and the colour the glyphs are drawn in. */ -extern s32 D_801D4AF0; - -extern s32 D_801D4AF4; - -extern s32 D_801D4AF8; - -/** @brief The two prim banks the emitters write through. */ -extern EffectMote *D_801D3F7C; -extern EffectDrawNode *D_801D3F80; - -/** @brief Frame counters the script resets when it restarts. */ -extern s16 D_801D3F88; -extern s16 D_801D3F8A; -extern s16 D_801D3F90; -extern s16 D_801D3F92; - /** @brief Pose script the model-taking handler runs. */ extern EffectPoseTables D_801D4AFC; /** @brief Pool the trailing sparks are allocated from. */ extern s32 D_801D3F6C; -/** @brief Pool the pose-script children are allocated from. */ -extern s32 D_801D478C; - -/** @brief Cursor the prim banks are carved off, moving up as they are taken. */ +/** @brief Cursor the record banks are carved off, moving up as they are taken. */ extern u8 *D_801D3CDC; /** @@ -339,12 +52,6 @@ extern s32 D_801D3CEC; extern s32 D_801D3CCC; extern s32 D_801D3CD0; -/** @brief Top of the scratchpad stack the draw packets are built on. */ -extern u8 *D_801D3CD8; - -/** @brief Where that stack started, kept so the frame can be unwound. */ -extern MATRIX *D_801D3CD4; - /** * @name Prim buffers * @@ -369,8 +76,9 @@ extern EffectTintStep D_801A9FA0[]; /** @brief Pool the frame-20 child is allocated from. */ extern s32 D_801D4ACC; -/** @brief Running totals the draw list adds into every frame. */ -extern u16 D_801D3F8C; -extern u16 D_801D3F8E; +/** + * @brief Run one frame of the effect: its opcode, then every task pool it owns. + */ +s32 func_801A9CF4(EffectEntity *entity); #endif /* EFFECT_EFFECT_028_H */ diff --git a/include/effect/effect_028_entry.h b/include/effect/effect_028_entry.h new file mode 100644 index 00000000..ae44b070 --- /dev/null +++ b/include/effect/effect_028_entry.h @@ -0,0 +1,19 @@ +#ifndef EFFECT_EFFECT_028_ENTRY_H +#define EFFECT_EFFECT_028_ENTRY_H + +#include "effect.h" + +/** + * @file effect_028_entry.h + * @brief Curaga: the overlay entry point. + */ + +/** + * @brief Start the effect's script and hand back its task pool. + * + * @param animSet Animation set the effect plays. + * @return The pool the root entity lives in. + */ +void *func_801A0000(EffectAnimSet *animSet); + +#endif /* EFFECT_EFFECT_028_ENTRY_H */ diff --git a/include/effect/lib/aim.h b/include/effect/lib/aim.h new file mode 100644 index 00000000..e60ea6df --- /dev/null +++ b/include/effect/lib/aim.h @@ -0,0 +1,19 @@ +#ifndef EFFECT_LIB_AIM_H +#define EFFECT_LIB_AIM_H + +#include "effect.h" + +/** + * @file aim.h + * @brief Matrices that aim along a direction. + */ + +/** + * @brief Build a matrix in @p m that aims along @p dir with @p up as the roll. + */ +void effectMatrixAim(MATRIX *m, SVECTOR *dir, SVECTOR *up); + +/** @brief Aim @p m along the vector from @p from to @p to, Y up. */ +void effectMatrixLookAt(MATRIX *m, SVECTOR *from, SVECTOR *to); + +#endif /* EFFECT_LIB_AIM_H */ diff --git a/include/effect/lib/bankclear.h b/include/effect/lib/bankclear.h new file mode 100644 index 00000000..954ba42f --- /dev/null +++ b/include/effect/lib/bankclear.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_BANKCLEAR_H +#define EFFECT_LIB_BANKCLEAR_H + +#include "effect/lib/particle.h" + +/** + * @file bankclear.h + * @brief Clear both record banks and the ring cursors. + */ + +/** @brief Clear both record banks and the script's frame counters. */ +void effectBanksClear(void); + +#endif /* EFFECT_LIB_BANKCLEAR_H */ diff --git a/include/effect/lib/common.h b/include/effect/lib/common.h new file mode 100644 index 00000000..ff3cfc5d --- /dev/null +++ b/include/effect/lib/common.h @@ -0,0 +1,53 @@ +#ifndef EFFECT_LIB_COMMON_H +#define EFFECT_LIB_COMMON_H + +#include "effect.h" + +/** + * @file common.h + * @brief Debug text, screen quads and the basic matrix builders. + */ + +/** @brief Cursor this frame's prims are written through. */ +extern void *g_effectPrimCursor; +/** @brief The sixteen hex digit glyphs, cached from the main string table. */ +extern u8 g_effectHexGlyphs[]; +/** @brief Debug text cursor: X, Y, and the colour the glyphs are drawn in. */ +extern s32 g_effectTextX; +extern s32 g_effectTextY; +extern s32 g_effectTextColour; + +/** + * @brief Cache the sixteen hex digit glyphs and reset the debug text cursor. + */ +void effectTextInit(void); + +/** + * @brief Draw @p value as eight hex glyphs at the current debug text cursor. + */ +void effectTextHex(u32 value); + +/** @brief Move the debug text cursor to the start of the next line. */ +void effectTextNewline(void); + +/** @brief Opcode handler: no-op. */ +void effectNop(EffectEntity *entity); + +/** @brief Emit a 128x128 textured quad at the head of the prim buffer. */ +void effectDrawQuad128(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, + s32 abr); + +/** @brief Emit a 256x256 textured quad at the head of the prim buffer. */ +void effectDrawQuad256(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, + s32 abr); + +/** @brief Load the identity matrix. */ +void effectMatrixIdentity(MATRIX *m); + +/** @brief Post-multiply @p m by a rotation of @p angle about X. */ +void effectMatrixRotX(MATRIX *m, s32 angle); + +/** @brief Compose a rotation about the Y axis into @p m. */ +void effectMatrixRotY(MATRIX *m, s32 angle); + +#endif /* EFFECT_LIB_COMMON_H */ diff --git a/include/effect/lib/draw.h b/include/effect/lib/draw.h new file mode 100644 index 00000000..9ccef2d8 --- /dev/null +++ b/include/effect/lib/draw.h @@ -0,0 +1,22 @@ +#ifndef EFFECT_LIB_DRAW_H +#define EFFECT_LIB_DRAW_H + +#include "effect/lib/particle.h" + +/** + * @file draw.h + * @brief Run the steps of the pose list and draw the particles they leave. + */ + +/** + * @brief Step one particle of the pose list, or retire it once its phase is over. + */ +void effectParticleStep(EffectParticle *particle); + +/** @brief One less than @p particle's count, as a signed step. */ +s16 effectEmitterCountStep(EffectEmitter *emitter); + +/** @brief Draw every particle on the list that has a model and is still posing. */ +void effectDrawParticles(void); + +#endif /* EFFECT_LIB_DRAW_H */ diff --git a/include/effect/lib/drawscript.h b/include/effect/lib/drawscript.h new file mode 100644 index 00000000..e5ae26c6 --- /dev/null +++ b/include/effect/lib/drawscript.h @@ -0,0 +1,28 @@ +#ifndef EFFECT_LIB_DRAWSCRIPT_H +#define EFFECT_LIB_DRAWSCRIPT_H + +#include "effect/lib/particle.h" + +/** + * @file drawscript.h + * @brief Run the draw list a script carries. + */ + +/** @brief Running totals the draw list adds into every frame. */ +extern u16 g_effectParticleTotal; +extern u16 g_effectLiveTotal; + +/** + * @brief Point the draw list at this script's tables and aim it at its slot. + */ +void effectDrawScriptStart(EffectDrawScript *script); + +/** @brief Run one frame of the draw list @p script carries. */ +s32 effectDrawScriptRun(EffectDrawScript *script); + +/** + * @brief Set the draw list's span from three times the slot's height, clamped. + */ +void effectDrawListSpan(s16 slot); + +#endif /* EFFECT_LIB_DRAWSCRIPT_H */ diff --git a/include/effect/lib/emit.h b/include/effect/lib/emit.h new file mode 100644 index 00000000..002f542e --- /dev/null +++ b/include/effect/lib/emit.h @@ -0,0 +1,78 @@ +#ifndef EFFECT_LIB_EMIT_H +#define EFFECT_LIB_EMIT_H + +#include "effect/lib/particle.h" + +/** + * @file emit.h + * @brief The mesh prim emitters that write four-word prims. + */ + +/** + * @name Prim emitter flags -- @ref EffectPrimBuild::flags. + * @{ + */ +#define EFFECT_EMIT_SEMITRANS 0x1 +#define EFFECT_EMIT_OPAQUE 0x4 +#define EFFECT_EMIT_TWO_SIDED 0x10 +#define EFFECT_EMIT_DEPTH_CUE 0x40 + +/* The gouraud emitters read their own copy of each of the four bits above. */ +#define EFFECT_EMIT_G_SEMITRANS 0x2 +#define EFFECT_EMIT_G_OPAQUE 0x8 +#define EFFECT_EMIT_G_TWO_SIDED 0x20 +#define EFFECT_EMIT_G_DEPTH_CUE 0x80 +#define EFFECT_EMIT_TPAGE_SET 0x100 +#define EFFECT_EMIT_CLUT_SET 0x200 +#define EFFECT_EMIT_TPAGE_ADD 0x400 +#define EFFECT_EMIT_CLUT_ADD 0x800 +/** @brief Set, the vertex buffer the stream names is already resolved. */ +#define EFFECT_EMIT_VERTS_SET 0x2000 +/** @brief Set, the emitters keep the depth the packet came in with. */ +#define EFFECT_EMIT_KEEP_DEPTH 0x1000 +/** @} */ + +/** @brief The flat triangle an emitter writes; six words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u16 x1; + /* 0x0E */ u16 y1; + /* 0x10 */ u16 x2; + /* 0x12 */ u16 y2; +} EffectTri; /* 0x14 */ + +/** + * @brief What @ref effectEmitStream walks: the mesh stream and how to shade it. + * + * Built on the scratchpad stack @ref g_effectStackTop walks. The overlay reserves + * battle's 0x58 bytes for it even though it fills far less. + */ +typedef struct { + /* 0x00 */ s32 *stream; /**< The mesh the packet draws. */ + /* 0x04 */ u32 *verts; /**< Where the projected points are kept. */ + /* 0x08 */ EffectPrimColor color; /**< Far colour the depth cue fades toward. */ + /* 0x0C */ s32 depth; /**< Depth-cue weight handed to DPCS. */ + /* 0x10 */ u16 tpage; /**< Texture page the textured prims take. */ + /* 0x12 */ u8 pad012[0x14 - 0x12]; + /* 0x14 */ u16 clut; /**< CLUT id the textured prims take. */ + /* 0x16 */ u8 pad016[0x18 - 0x16]; + /* 0x18 */ s32 unk018; + /* 0x1C */ u32 flags; + /* 0x20 */ s32 *cursor; /**< Walks the stream, one entry per prim kind. */ + /* 0x24 */ s32 nclip; + /* 0x28 */ u8 pad028[0x2C - 0x28]; + /* 0x2C */ s32 otz; + /* 0x30 */ u32 gteFlag; + /* 0x34 */ u8 pad034[0x58 - 0x34]; +} EffectPrimBuild; /* 0x58 */ + +/** + * @brief Walk @p prim's mesh stream, letting each emitter draw its own prims. + */ +EffectTri *effectEmitStream(EffectPrimBuild *prim, u32 *ot, s32 otShift, + EffectTri *head); + +#endif /* EFFECT_LIB_EMIT_H */ diff --git a/include/effect/lib/emit_mode.h b/include/effect/lib/emit_mode.h new file mode 100644 index 00000000..f8dd2e3c --- /dev/null +++ b/include/effect/lib/emit_mode.h @@ -0,0 +1,95 @@ +#ifndef EFFECT_LIB_EMIT_MODE_H +#define EFFECT_LIB_EMIT_MODE_H + +#include "effect.h" + +/** + * @file emit_mode.h + * @brief The mesh prim emitters whose prims carry a draw-mode word. + */ + +/** @brief Draw-mode command word every emitted prim carries. */ +extern u32 g_effectDrawMode; + +/** + * @name Prim emitter flags -- @ref EffectPrimBuild::flags. + * @{ + */ +#define EFFECT_EMIT_SEMITRANS 0x1 +#define EFFECT_EMIT_OPAQUE 0x4 +#define EFFECT_EMIT_TWO_SIDED 0x10 +#define EFFECT_EMIT_DEPTH_CUE 0x40 + +/* The gouraud emitters read their own copy of each of the four bits above. */ +#define EFFECT_EMIT_G_SEMITRANS 0x2 +#define EFFECT_EMIT_G_OPAQUE 0x8 +#define EFFECT_EMIT_G_TWO_SIDED 0x20 +#define EFFECT_EMIT_G_DEPTH_CUE 0x80 +#define EFFECT_EMIT_TPAGE_SET 0x100 +#define EFFECT_EMIT_CLUT_SET 0x200 +#define EFFECT_EMIT_TPAGE_ADD 0x400 +#define EFFECT_EMIT_CLUT_ADD 0x800 +#define EFFECT_EMIT_UNK1000 0x1000 +#define EFFECT_EMIT_UNK2000 0x2000 +/** @} */ + +/** @brief What the prim emitters walk: the mesh stream and the GTE results. */ +typedef struct { + /* 0x00 */ s32 *stream; + /* 0x04 */ u32 *verts; + /* 0x08 */ u8 r; /**< Far colour the depth cue fades toward. */ + /* 0x09 */ u8 g; + /* 0x0A */ u8 b; + /* 0x0B */ u8 pad00B[0xC - 0xB]; + /* 0x0C */ s32 depth; /**< Depth-cue weight handed to DPCS. */ + /* 0x10 */ u16 tpage; /**< Texture page the textured prims take. */ + /* 0x12 */ u8 pad012[0x14 - 0x12]; + /* 0x14 */ u16 clut; /**< CLUT id the textured prims take. */ + /* 0x16 */ u8 pad016[0x18 - 0x16]; + /* 0x18 */ s32 unk018; + /* 0x1C */ u32 flags; + /* 0x20 */ s32 *cursor; /**< Walks the stream, one entry per prim kind. */ + /* 0x24 */ s32 nclip; + /* 0x28 */ u8 pad028[0x2C - 0x28]; + /* 0x2C */ s32 otz; + /* 0x30 */ u32 gteFlag; +} EffectPrimBuild; /* 0x34 */ + +/** @brief The flat triangle an emitter writes; six words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u16 x1; + /* 0x0E */ u16 y1; + /* 0x10 */ u16 x2; + /* 0x12 */ u16 y2; + /* 0x14 */ u32 unk014; +} EffectTri; /* 0x18 */ + +/** + * @brief One gouraud textured quad in the mesh stream. + * + * The last two UV pairs share a word, so the build's offset is doubled into + * both halves and the sum split again on the way out. + */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; + /* 0x14 */ u32 uv23; + /* 0x18 */ u32 colour1; + /* 0x1C */ u32 colour2; + /* 0x20 */ u32 colour3; +} EffectEmitGouraudTexQuad; /* 0x24 */ + +/** @brief Run every prim kind of one mesh stream into the ordering table. */ +EffectTri *effectEmitMesh(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim); + +#endif /* EFFECT_LIB_EMIT_MODE_H */ diff --git a/include/effect/lib/emitteralloc.h b/include/effect/lib/emitteralloc.h new file mode 100644 index 00000000..2e888d95 --- /dev/null +++ b/include/effect/lib/emitteralloc.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_EMITTERALLOC_H +#define EFFECT_LIB_EMITTERALLOC_H + +#include "effect/lib/particle.h" + +/** + * @file emitteralloc.h + * @brief Allocate an emitter from its fixed pool of slots. + */ + +/** @brief Allocate an emitter from its pool and put it on the draw list. */ +EffectEmitter *effectEmitterAlloc(void *owner, s16 source, s16 index); + +#endif /* EFFECT_LIB_EMITTERALLOC_H */ diff --git a/include/effect/lib/entity.h b/include/effect/lib/entity.h new file mode 100644 index 00000000..454479eb --- /dev/null +++ b/include/effect/lib/entity.h @@ -0,0 +1,80 @@ +#ifndef EFFECT_LIB_ENTITY_H +#define EFFECT_LIB_ENTITY_H + +#include "effect.h" + +/** + * @file entity.h + * @brief Effect entities: task spawning, battle-slot anchors and model bounds. + */ + +/** @brief Opcode handler: release one frame of the linked script's wait. */ +void effectReleaseWait(EffectEntity *entity); + +/** @brief Allocate an effect entity from @p pool and hang it off @p owner. */ +void *effectSpawnTask(void *pool, void *task, s32 stride, EffectEntity *owner); + +/** @brief Cache the battle slot's two anchor points and their midpoint. */ +void effectCacheSlotAnchors(EffectEntity *entity); + +/** @brief Copy @c unk030 of the linked model out to @p out. */ +void effectSlotAnchor0(EffectEntity *entity, SVECTOR *out); + +/** @brief Copy @c unk038 of the linked model out to @p out. */ +void effectSlotAnchor1(EffectEntity *entity, SVECTOR *out); + +/** @brief Copy @c unk040 of the linked model out to @p out. */ +void effectSlotAnchorMid(EffectEntity *entity, SVECTOR *out); + +/** + * @brief As @ref effectCacheSlotAnchors, but for the slot the animation set + * names. + */ +void effectCacheAnimSlotAnchors(EffectEntity *entity); + +/** @brief Copy @c unk030 of the linked model out to @p out. */ +void effectAnimSlotAnchor0(EffectEntity *entity, SVECTOR *out); + +/** @brief Copy @c unk038 of the linked model out to @p out. */ +void effectAnimSlotAnchor1(EffectEntity *entity, SVECTOR *out); + +/** @brief Copy @c unk040 of the linked model out to @p out. */ +void effectAnimSlotAnchorMid(EffectEntity *entity, SVECTOR *out); + +/** @brief Recompute the model's bounding box from its posed skeleton. */ +void effectUpdateModelBounds(EffectEntity *entity); + +/** @brief Copy the linked model's bounding box out to @p min and @p max. */ +void effectGetModelBounds(EffectEntity *entity, SVECTOR *min, SVECTOR *max); + +/** @brief Half the longest side of the model's bounding box. */ +s32 effectModelRadius(EffectEntity *entity); + +/** @brief Half the height of the linked model's bounding box. */ +s16 effectModelHalfHeight(EffectEntity *entity); + +/** @brief Half the linked model's larger horizontal extent. */ +s16 effectModelHalfWidth(EffectEntity *entity); + +/** @brief Height of the linked model's bounding box. */ +s16 effectModelHeight(EffectEntity *entity); + +/** @brief Y of the top of the linked model, in battle-entity space. */ +s16 effectModelTop(EffectEntity *entity); + +/** @brief Y of the bottom of the linked model, in battle-entity space. */ +s16 effectModelBottom(EffectEntity *entity); + +/** @brief A random point up the linked model, in battle-entity space. */ +s16 effectModelRandomY(EffectEntity *entity); + +/** @brief Y of the centre of the linked model, in battle-entity space. */ +s16 effectModelCentreY(EffectEntity *entity); + +/** @brief Centre of the linked model's bounding box. */ +void effectModelCentre(EffectEntity *entity, SVECTOR *out); + +/** @brief Zero @p size bytes' worth of words starting at @p dst. */ +void effectZeroWords(void *dst, s32 size); + +#endif /* EFFECT_LIB_ENTITY_H */ diff --git a/include/effect/lib/fill.h b/include/effect/lib/fill.h new file mode 100644 index 00000000..cb73b942 --- /dev/null +++ b/include/effect/lib/fill.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_FILL_H +#define EFFECT_LIB_FILL_H + +#include "effect.h" + +/** + * @file fill.h + * @brief Fill words. + */ + +/** @brief Fill @p count words at @p dst with @p value. */ +void effectFillWords(s32 *dst, s32 value, s32 count); + +#endif /* EFFECT_LIB_FILL_H */ diff --git a/include/effect/lib/heading.h b/include/effect/lib/heading.h new file mode 100644 index 00000000..f7d35980 --- /dev/null +++ b/include/effect/lib/heading.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_HEADING_H +#define EFFECT_LIB_HEADING_H + +#include "effect.h" + +/** + * @file heading.h + * @brief The heading a matrix faces. + */ + +/** @brief The heading @p m faces, a quarter turn ahead of its Z axis. */ +s32 effectMatrixHeading(MATRIX *m); + +#endif /* EFFECT_LIB_HEADING_H */ diff --git a/include/effect/lib/interpolate.h b/include/effect/lib/interpolate.h new file mode 100644 index 00000000..d7a43934 --- /dev/null +++ b/include/effect/lib/interpolate.h @@ -0,0 +1,23 @@ +#ifndef EFFECT_LIB_INTERPOLATE_H +#define EFFECT_LIB_INTERPOLATE_H + +#include "effect.h" + +/** + * @file interpolate.h + * @brief Interpolate a vertex animation between two of its frames. + */ + +/** @brief A vertex animation: a header, then @c count posed points per frame. */ +typedef struct { + /* 0x00 */ s32 unk000; + /* 0x04 */ s32 count; + /* 0x08 */ s32 unk008; + /* 0x0C */ SVECTOR frames[1]; +} EffectVertexAnim; + +/** @brief Interpolate every point of @p anim between two of its frames. */ +void effectInterpolateFrames(EffectVertexAnim *anim, s32 frameA, s32 frameB, + s32 t, SVECTOR *out); + +#endif /* EFFECT_LIB_INTERPOLATE_H */ diff --git a/include/effect/lib/list.h b/include/effect/lib/list.h new file mode 100644 index 00000000..c3975cd3 --- /dev/null +++ b/include/effect/lib/list.h @@ -0,0 +1,40 @@ +#ifndef EFFECT_LIB_LIST_H +#define EFFECT_LIB_LIST_H + +#include "effect/lib/particle.h" + +/** + * @file list.h + * @brief The draw list itself, and the per-frame setup of the emitters on it. + */ + +/** + * @brief Give @p particle the count @p value and put it on the tail of the draw + * list. + */ +void effectListAppend(EffectParticle *particle, s16 value); + +/** @brief Take @p particle off the draw list, mending both links. */ +void effectListRemove(EffectParticle *particle); + +/** @brief Empty the draw list. */ +void effectListClear(void); + +/** @brief Run every record on the draw list through the step its kind wants. */ +void effectListRun(void); + +/** + * @brief Set @p emitter up for this frame: turn it, place it, and reset its life. + */ +void effectEmitterSetup(EffectEmitter *emitter); + +/** @brief Fire each of @p step's four slots that is armed. */ +void effectStepFireSlots(EffectParticle *particle, EffectPoseStep *step); + +/** @brief Allocate an emitter for every source whose cue falls on this frame. */ +void effectEmittersOnCue(void); + +/** @brief Push the script's current position onto the head of the trail. */ +void effectTrailPush(EffectDrawScript *script); + +#endif /* EFFECT_LIB_LIST_H */ diff --git a/include/effect/lib/packet.h b/include/effect/lib/packet.h new file mode 100644 index 00000000..172e6cb7 --- /dev/null +++ b/include/effect/lib/packet.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_PACKET_H +#define EFFECT_LIB_PACKET_H + +#include "effect/lib/particle.h" + +/** + * @file packet.h + * @brief Build a mesh packet for a particle and link its copies into the OT. + */ + +/** @brief Build one mesh packet for @p particle and link every copy into the OT. */ +void effectBuildMeshPacket(EffectParticle *particle, EffectPoseStep *step); + +#endif /* EFFECT_LIB_PACKET_H */ diff --git a/include/effect/lib/particle.h b/include/effect/lib/particle.h new file mode 100644 index 00000000..1f556036 --- /dev/null +++ b/include/effect/lib/particle.h @@ -0,0 +1,322 @@ +#ifndef EFFECT_LIB_PARTICLE_H +#define EFFECT_LIB_PARTICLE_H + +#include "effect.h" +#include "effect_params.h" + +/** + * @file particle.h + * @brief The mote engine's records and state. + */ + +/** @brief A packet's far colour: three bytes and the code byte beside them. */ +typedef struct { + /* 0x00 */ u8 r; + /* 0x01 */ u8 g; + /* 0x02 */ u8 b; + /* 0x03 */ u8 code; +} EffectPrimColor; /* 0x04 */ + +/** @brief One step of the pose script the emitters read. */ +typedef struct { + /* 0x00 */ s32 offsetX; /**< Offset added to the point, 16.16. */ + /* 0x04 */ s32 offsetY; + /* 0x08 */ s32 offsetZ; + /* 0x0C */ u8 pad00C[0x11 - 0xC]; + /* 0x11 */ u8 unk011; + /* 0x12 */ u8 unk012; /**< Frames an emitter allocated from this source lives. */ + /* 0x13 */ u8 unk013; /**< Frame of the list's life the source fires on. */ + /* 0x14 */ u8 pad014[0x16 - 0x14]; + /* 0x16 */ u8 mode; /**< 4 and 5 take the late step. */ + /* 0x17 */ u8 unk017; + /* 0x18 */ u8 unk018; + /* 0x19 */ u8 unk019; + /* 0x1A */ u8 unk01A; + /* 0x1B */ u8 unk01B; /**< Set, the pose's Y is flattened every frame. */ + /* 0x1C */ u8 unk01C; + /* 0x1D */ u8 unk01D; /**< Which order the pose's three turns are applied in. */ + /* 0x1E */ u8 kind; /**< Selects which of the four steps runs. */ + /* 0x1F */ u8 unk01F; + /* 0x20 */ u8 unk020; + /* 0x21 */ u8 unk021; /**< Where a spawned node's own spin comes from. */ + /* 0x22 */ u8 unk022; /**< Where the posed offsets are written back to. */ + /* 0x23 */ u8 pad023[0x25 - 0x23]; + /* 0x25 */ u8 anchor; /**< Which of the trail's points the motes start at. */ + /* 0x26 */ u8 unk026; /**< Set, the pose is turned before it is applied. */ + /* 0x27 */ u8 unk027; /**< Which way the pose's offsets are built. */ + /* 0x28 */ u8 unk028; + /* 0x29 */ u8 unk029; + /* 0x2A */ u8 unk02A; + /* 0x2B */ u8 unk02B; + /* 0x2C */ u8 unk02C; + /* 0x2D */ u8 unk02D; + /* 0x2E */ u8 unk02E; + /* 0x2F */ u8 unk02F; + /* 0x30 */ u8 unk030; + /* 0x31 */ u8 unk031; + /* 0x32 */ u8 pad032; + /* 0x33 */ u8 unk033; /**< Set, the spawn speeds are taken as they come. */ + /* 0x34 */ u8 unk034; /**< Which way a spawned node is aimed. */ + /* 0x35 */ u8 midpoint; /**< Set, motes start midway along the node. */ + /* 0x36 */ u8 unk036; /**< Splits a frame's work into chunks this big. */ + /* 0x37 */ u8 unk037; /**< Set, the source fires once per strand point. */ + /* 0x038 */ s16 unk038; /**< Added to the node's Z once it is placed. */ + /* 0x03A */ s16 unk03A; + /* 0x03C */ SVECTOR unk03C; + /* 0x044 */ SVECTOR unk044; + /* 0x04C */ s32 unk04C; + /* 0x050 */ s32 unk050; + /* 0x054 */ s32 unk054; + /* 0x058 */ s32 unk058; + /* 0x05C */ s32 unk05C; + /* 0x060 */ s32 unk060; + /* 0x064 */ u16 unk064; /**< Drag a spawned node keeps its motion with. */ + /* 0x066 */ u8 pad066[0x68 - 0x66]; + /* 0x068 */ VECTOR unk068; + /* 0x078 */ VECTOR unk078; + /* 0x088 */ VECTOR unk088; + /* 0x098 */ VECTOR unk098; + /* 0x0A8 */ VECTOR unk0A8; + /* 0x0B8 */ VECTOR unk0B8; + /* + * 0x0C8 to 0x144 is one table per animated property, each indexed by the + * frame the node is showing. They are stored as offsets from the pose + * tables' own start; @ref func_801A8CE4 turns all thirty-two into + * addresses the first time a script uses them. + */ + /* 0x0C8 */ u8 *unk0C8; /**< How many motes the source takes that frame. */ + /* 0x0CC */ u16 *unk0CC; + /* 0x0D0 */ u16 *unk0D0; + /* 0x0D4 */ u16 *unk0D4; + /* 0x0D8 */ u16 *unk0D8; + /* 0x0DC */ u16 *unk0DC; + /* 0x0E0 */ u16 *unk0E0; + /* 0x0E4 */ u16 *unk0E4; + /* 0x0E8 */ u16 *unk0E8; + /* 0x0EC */ u16 *unk0EC; + /* 0x0F0 */ u8 *unk0F0; + /* 0x0F4 */ u8 *unk0F4; + /* 0x0F8 */ u8 *unk0F8; + /* 0x0FC */ u16 *unk0FC; + /* 0x100 */ u8 *unk100; + /* 0x104 */ u8 *unk104; + /* 0x108 */ u8 *unk108; + /* 0x10C */ u8 *unk10C; + /* 0x110 */ u16 *unk110; + /* 0x114 */ u16 *unk114; + /* 0x118 */ u16 *unk118; + /* 0x11C */ u16 *unk11C; + /* 0x120 */ u16 *unk120; + /* 0x124 */ u16 *unk124; + /* 0x128 */ s16 *unk128; + /* 0x12C */ u8 *targets; /**< One target point set per frame. */ + /* 0x130 */ u8 *framesA; /**< First source point set per frame. */ + /* 0x134 */ u8 *framesB; /**< Second source point set per frame. */ + /* 0x138 */ s16 *weights; /**< Weight of the second source, per frame. */ + /* 0x13C */ s16 *unk13C; + /* 0x140 */ s16 *unk140; + /* 0x144 */ s16 *unk144; + /* 0x148 */ SVECTOR unk148; + /* 0x150 */ SVECTOR unk150; +} EffectPoseStep; /* 0x158 */ + +/** + * @brief The lighter of the two record kinds the draw list carries. + * + * Its bank packs these 0x6C apart, so it shares only the head of + * @ref EffectParticle and is linked through the same two pointers. + */ +typedef struct EffectEmitter { + /* 0x00 */ struct EffectParticle *prev; + /* 0x04 */ struct EffectParticle *next; + /* 0x08 */ u16 unk008; + /* 0x0A */ u8 pad00A[0x2C - 0xA]; + /* 0x2C */ MATRIX mtx; /**< Turns the step's offset into world space. */ + /* 0x4C */ VECTOR pos; /**< Where the mote sits, 16.16. */ + /* 0x5C */ struct EffectParticle *owner; + /* 0x60 */ s16 unk060; /**< Frames this spark has been drawn for. */ + /* 0x62 */ s16 unk062; /**< Zero until it has been set up once. */ + /* 0x64 */ s16 life; /**< Counts down; the spark unlinks at zero. */ + /* 0x66 */ u8 pad066[0x68 - 0x66]; + /* 0x68 */ u8 kind; /**< Selects the per-frame step below. */ + /* 0x69 */ u8 inUse; /**< Cleared when the life runs out, freeing the slot. */ + /* 0x6A */ u8 unk06A; /**< Which of the list's sources drives it. */ + /* 0x6B */ u8 unk06B; +} EffectEmitter; /* 0x6C */ + +/** @brief One drawn particle on the effect's list, linked both ways. */ +typedef struct EffectParticle { + /* 0x000 */ struct EffectParticle *prev; + /* 0x004 */ struct EffectParticle *next; + /* 0x008 */ s16 unk008; + /* 0x00A */ u8 pad00A[0xC - 0xA]; + /* 0x00C */ MATRIX poses[2]; /**< One per copy; only two fit before the header. */ + /* 0x04C */ u8 pad04C[0x60 - 0x4C]; + /* 0x060 */ u16 unk060; /**< Frames this node has been drawn for. */ + /* 0x062 */ s16 unk062; /**< Zero until the node has been set up once. */ + /* 0x064 */ s16 life; /**< Counts down; the node unlinks at zero. */ + /* 0x066 */ u8 pad066[0x68 - 0x66]; + /* 0x068 */ u8 kind; /**< Selects the per-frame step below. */ + /* 0x069 */ u8 unk069; + /* 0x06A */ u8 unk06A; /**< Which of the list's sources drives the emitter. */ + /* 0x06B */ u8 pad06B[0x8C - 0x6B]; + /* 0x08C */ MATRIX orient; /**< Turn the particles hung off this emitter inherit. */ + /* 0x0AC */ MATRIX mtx; /**< Pose the node's model is drawn with. */ + /* 0x0CC */ SVECTOR angle; /**< This frame's turn, wrapped to a whole circle. */ + /* 0x0D4 */ s16 scaleX; /**< This frame's scale, before it is applied. */ + /* 0x0D6 */ s16 scaleY; + /* 0x0D8 */ s16 scaleZ; + /* 0x0DA */ u8 pad0DA[0xDC - 0xDA]; + /* 0x0DC */ VECTOR pos[4]; /**< Where each copy sits. */ + /* 0x11C */ VECTOR lastPos; /**< Where the first copy sat last frame. */ + /* 0x12C */ VECTOR offset[4]; /**< The turned offset each copy is drawn at. */ + /* 0x16C */ EffectPrimColor color; + /* 0x170 */ void *stream; /**< What the node draws -- a mesh stream or a + sprite animation, by kind -- or NULL. */ + /* 0x174 */ s32 spin[4]; /**< How far each copy has spun about its pose. */ + /* 0x184 */ s32 spinRate[4]; + /* 0x194 */ SVECTOR angleBase; /**< The turn this frame's angle starts from. */ + /* 0x19C */ SVECTOR offsets[1]; /**< Where each copy past the first is placed. */ + /* 0x1A4 */ u8 pad1A4[0x1BC - 0x1A4]; + /* 0x1BC */ struct EffectEmitter *owner; + /* 0x1C0 */ u8 pad1C0[0x1C8 - 0x1C0]; + /* 0x1C8 */ s16 unk1C8; /**< Counts down; a step fires when it goes negative. */ + /* 0x1CA */ s16 unk1CA; /**< Which frame of the pose the node is showing. */ + /* 0x1CC */ s16 unk1CC; /**< Which phase of its life the node is in. */ + /* 0x1CE */ s16 unk1CE; /**< Depth cue: zero leaves the node unshaded. */ + /* 0x1D0 */ u8 unk1D0; /**< Frames the current step has held for. */ + /* 0x1D1 */ u8 unk1D1; /**< How long it holds before advancing. */ + /* 0x1D2 */ u8 unk1D2; + /* 0x1D3 */ u8 unk1D3; + /* 0x1D4 */ u8 unk1D4; + /* 0x1D5 */ u8 unk1D5; /**< Repeats left before the hold gives up. */ + /* 0x1D6 */ u8 unk1D6; /**< Which of the list's sources this node came from. */ + /* 0x1D7 */ u8 inUse; /**< Cleared when the phase ends, freeing the slot. */ + /* 0x1D8 */ u8 copies; /**< How many times the node draws its model. */ + /* 0x1D9 */ u8 unk1D9; + /* 0x1DA */ s16 unk1DA; /**< Drag: how much of each frame's motion is kept. */ + /* 0x1DC */ s32 unk1DC; /**< Added to every copy's Y velocity each frame. */ + /* 0x1E0 */ VECTOR vel[4]; + /* 0x220 */ VECTOR accel[4]; + /* 0x260 */ VECTOR jerk[4]; +} EffectParticle; /* 0x2A0 */ + +/** @brief A set of posed points: a two-word header, then @c count of them. */ +typedef struct EffectPointSet { + /* 0x00 */ s32 unk000; + /* 0x04 */ s32 count; + /* 0x08 */ SVECTOR points[1]; +} EffectPointSet; + +/** + * @brief The tables a script is handed: sixteen sources and their point sets. + * + * @ref func_801A8CE4 fills the pointers in once and sets @c ready so a restart + * does not do it again. + */ +typedef struct { + /* 0x000 */ s32 ready; + /* 0x004 */ EffectPoseStep *sources[16]; + /* 0x044 */ void *unk044[16]; + /* 0x084 */ EffectPointSet *targets[16]; + /* 0x0C4 */ EffectPointSet *frames[16]; +} EffectPoseTables; /* 0x104 */ + +/** @brief The draw list the emitters walk, with its head and tail. */ +typedef struct { + /* 0x000 */ s32 spread; /**< Distance between the two slots the effect spans. */ + /* 0x004 */ s32 parts[4]; /**< The model part each strand is built from. */ + /* 0x014 */ u16 count; + /* 0x016 */ u16 live; /**< Nodes still on the list. */ + /* 0x018 */ s16 age; /**< Frames this list has been running. */ + /* 0x01A */ s16 span; /**< How long it runs before it may finish. */ + /* 0x01C */ s16 strandLen; /**< How many points of each strand are used. */ + /* 0x01E */ s16 slot; /**< Battle slot the effect is aimed at. */ + /* 0x020 */ s16 phase; /**< Which stage of its life the list is in. */ + /* 0x022 */ u16 held; /**< One bit per source: set, the source is held off. */ + /* 0x024 */ s16 unk024; + /* 0x026 */ u8 pad026[0x2C - 0x26]; + /* 0x02C */ EffectParticle *head; + /* 0x030 */ EffectParticle *tail; + /* 0x034 */ u8 pad034[0x54 - 0x34]; + /* 0x054 */ VECTOR strands[5][4]; /**< The five strands the effect draws. */ + /* 0x194 */ VECTOR strandSeed; /**< The point every strand starts from. */ + /* 0x1A4 */ VECTOR strandHead; /**< The newest point, pushed on each frame. */ + /* 0x1B4 */ VECTOR trail[6]; /**< Where the head has been, newest first. */ + /* 0x214 */ s32 unk214; + /* 0x218 */ s32 unk218; + /* 0x21C */ s32 unk21C; + /* 0x220 */ u8 pad220[0x224 - 0x220]; + /* 0x224 */ EffectPoseStep **sources; + /* 0x228 */ void **unk228; + /* 0x22C */ struct EffectPointSet **targets; /**< Point sets the poses write. */ + /* 0x230 */ struct EffectPointSet **frames; /**< Point sets the poses read. */ +} EffectDrawList; /* 0x234 */ + +/** + * @name Script records + * + * @ref effectSpawnTask hands back as many bytes as the script asks for, with an + * @ref EffectEntity at the front of them, so a script owns everything the + * engine leaves alone. These two lay their own state over the pose fields. + * @{ + */ + +/** @brief The entity of the script that builds and draws the list. */ +typedef struct { + /* 0x000 */ u8 pad000[0xC]; /**< The @ref EffectEntity the engine steps. */ + /* 0x00C */ EffectAnimSet *animSet; + /* 0x010 */ EffectModel *model; + /* 0x014 */ u8 pad014[0x2A - 0x14]; + /* 0x02A */ u8 anim; /**< Which of the set's animations is playing. */ + /* 0x02B */ u8 pad02B; + /* 0x02C */ u8 slot; /**< Battle slot the effect is aimed at. */ + /* 0x02D */ u8 pad02D[0x30 - 0x2D]; + /* 0x030 */ EffectPoseTables *tables; /**< The tables the script was started on. */ + /* 0x034 */ EffectDrawList list; + /* 0x268 */ u8 pad268[0x288 - 0x268]; + /* 0x288 */ s16 unk288; /**< Where the strands are seeded from. */ + /* 0x28A */ s16 unk28A; + /* 0x28C */ s16 unk28C; + /* 0x28E */ u8 pad28E[0x290 - 0x28E]; + /* 0x290 */ s16 unk290; /**< Where the trail's newest point goes. */ + /* 0x292 */ s16 unk292; + /* 0x294 */ s16 unk294; + /* 0x296 */ u8 pad296[0x298 - 0x296]; + /* 0x298 */ s16 stopFrame; /**< Frame the script starts drawing on. */ + /* 0x29A */ u16 unk29A; + /* 0x29C */ s16 unk29C; + /* 0x29E */ s16 unk29E; + /* 0x2A0 */ u8 pad2A0[0x2A4 - 0x2A0]; +} EffectDrawScript; /* 0x2A4 */ + +/** @} */ + +/** + * @name Prim bank sizes + * + * A bank holds one record more than its ring's highest index, and the script + * carves both out of the same arena behind the texture. + * @{ + */ +#define EFFECT_EMITTER_BANK_BYTES ((EFFECT_EMITTER_COUNT + 1) * sizeof(EffectEmitter)) +#define EFFECT_PARTICLE_BANK_BYTES ((EFFECT_PARTICLE_COUNT + 1) * sizeof(EffectParticle)) +/** @} */ + +/** @brief The one draw list this effect builds into. */ +extern EffectDrawList *g_effectDrawList; +/** @brief Top of the scratchpad stack the draw packets are built on. */ +extern u8 *g_effectStackTop; +/** @brief Where that stack started, kept so the frame can be unwound. */ +extern MATRIX *g_effectStackBase; +/** @brief The two record banks the emitters write through. */ +extern EffectEmitter *g_effectEmitterBank; +extern EffectParticle *g_effectParticleBank; +/** @brief Where the next take starts its search of each ring. */ +extern s16 g_effectEmitterCursor; +extern s16 g_effectParticleCursor; +/** @brief Cleared with the banks; nothing reads them yet. */ +extern s16 g_effectBankUnk90; +extern s16 g_effectBankUnk92; + +#endif /* EFFECT_LIB_PARTICLE_H */ diff --git a/include/effect/lib/particlealloc.h b/include/effect/lib/particlealloc.h new file mode 100644 index 00000000..6b17c38c --- /dev/null +++ b/include/effect/lib/particlealloc.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_PARTICLEALLOC_H +#define EFFECT_LIB_PARTICLEALLOC_H + +#include "effect/lib/particle.h" + +/** + * @file particlealloc.h + * @brief Allocate a particle from its fixed pool of slots. + */ + +/** @brief Allocate a particle from its pool and put it on the draw list. */ +EffectParticle *effectParticleAlloc(EffectEmitter *owner, u8 source); + +#endif /* EFFECT_LIB_PARTICLEALLOC_H */ diff --git a/include/effect/lib/particlestep.h b/include/effect/lib/particlestep.h new file mode 100644 index 00000000..0586dc20 --- /dev/null +++ b/include/effect/lib/particlestep.h @@ -0,0 +1,27 @@ +#ifndef EFFECT_LIB_PARTICLESTEP_H +#define EFFECT_LIB_PARTICLESTEP_H + +#include "effect/lib/particle.h" + +/** + * @file particlestep.h + * @brief Advance, age and pose the copies of a particle. + */ + +/** @brief Run the late step on every particle whose source is in the right mode. */ +void effectParticleLateStep(void); + +/** + * @brief Advance every copy of @p particle: spin it, drift it and turn its pose. + */ +void effectParticleAdvance(EffectParticle *particle, EffectPoseStep *step); + +/** + * @brief Age @p particle's hold on its current step and advance when it expires. + */ +void effectParticleAgeHold(EffectParticle *particle, EffectPoseStep *step); + +/** @brief Build @p particle's pose: orient it, colour it, scale it and place it. */ +void effectParticlePose(EffectParticle *particle, EffectPoseStep *step); + +#endif /* EFFECT_LIB_PARTICLESTEP_H */ diff --git a/include/effect/lib/pose.h b/include/effect/lib/pose.h new file mode 100644 index 00000000..9326de12 --- /dev/null +++ b/include/effect/lib/pose.h @@ -0,0 +1,62 @@ +#ifndef EFFECT_LIB_POSE_H +#define EFFECT_LIB_POSE_H + +#include "effect.h" + +/** + * @file pose.h + * @brief Walk a pose script and hand each posed part to a callback. + */ + +/** @brief A posed model: a header naming the part list behind it. */ +typedef struct { + /* 0x00 */ s32 unk000; + /* 0x04 */ s32 partsOffset; +} EffectPoseModel; + +/** + * @brief One posed part, rebuilt from the pose script for every draw. + * + * @ref effectPoseWalk zeroes this, walks the script into it and hands it to the + * per-part callback; the fields past @c colour are the animation cursor. + */ +typedef struct { + /* 0x00 */ s16 index; /**< The part's own slot in the pose script. */ + /* 0x02 */ s16 mesh; /**< Which of the model's meshes it draws. */ + /* 0x04 */ u32 unk004; /**< The part entry's header word; @c mesh is its low byte. */ + /* 0x08 */ s16 pos[3]; + /* 0x0E */ u8 pad00E[0x10 - 0xE]; + /* 0x10 */ s16 rot[3]; + /* 0x16 */ u8 pad016[0x18 - 0x16]; + /* 0x18 */ s16 scale[3]; + /* 0x1E */ u8 pad01E[0x20 - 0x1E]; + /* 0x20 */ CVECTOR colour; + /* 0x24 */ s16 depth; + /* 0x26 */ s16 weight; /**< Blend between @c frameA and @c frameB. */ + /* 0x28 */ s16 frameA; + /* 0x2A */ s16 frameB; +} EffectPartPose; /* 0x2C */ + +/** @brief What one pose walk draws: the matrix, its colours and its texture. */ +typedef struct { + /* 0x00 */ MATRIX *m; + /* 0x04 */ u32 *colours; + /* 0x08 */ void *unk08; + /* 0x0C */ s32 unk00C; + /* 0x10 */ s16 unk010; + /* 0x12 */ u8 pad012[0x14 - 0x12]; +} EffectDrawRequest; /* 0x14 */ + +/** @brief What @ref effectPoseWalk hands each part it has posed to. */ +typedef void (*EffectPartFn)(s32 *pose, EffectPartPose *part, + EffectDrawRequest *req); + +/** + * @brief Clear @p size bytes of pose state at @p out and seed it from @p model. + */ +void effectPoseInit(EffectPoseModel *model, s32 *out, s32 size); + +/** @brief Walk one pose script and hand every part it poses to @p fn. */ +s32 effectPoseWalk(s32 *pose, EffectPartFn fn, EffectDrawRequest *req); + +#endif /* EFFECT_LIB_POSE_H */ diff --git a/include/effect/lib/render.h b/include/effect/lib/render.h new file mode 100644 index 00000000..0b831945 --- /dev/null +++ b/include/effect/lib/render.h @@ -0,0 +1,37 @@ +#ifndef EFFECT_LIB_RENDER_H +#define EFFECT_LIB_RENDER_H + +#include "effect.h" + +/** + * @file render.h + * @brief The effect renderer: pose a model and draw its meshes. + */ + +/** @brief Per-joint world matrices, one per joint of the posed skeleton. */ +extern MATRIX g_effectJointOffsets[]; + +/** + * @brief Compose each joint's matrix through the pose and the per-joint offset. + */ +void effectRenderPoseJoints(EffectSkeletonRef *ref, EffectPose *pose); + +/** + * @brief Rebase a table of absolute pointers into offsets from its own head. + */ +void effectRebasePointers(s32 *table); + +/** @brief Reset a render request to its default pose, white, and unit scale. */ +void effectRenderReset(EffectRender *render, void *pose, EffectRenderPart *part, + void *texture, BattleEffectSlot *source, s32 frame); + +/** + * @brief Point a render request at a texture page, CLUT and source rectangle. + */ +void effectRenderSetTexture(EffectRender *render, SVECTOR *pos, s16 tx, s16 ty, + s16 clutX, s16 clutY, s16 w, s16 h); + +/** @brief Pose the effect's model and link its skeletons into the battle OT. */ +void effectRenderModel(EffectRender *render); + +#endif /* EFFECT_LIB_RENDER_H */ diff --git a/include/effect/lib/rot.h b/include/effect/lib/rot.h new file mode 100644 index 00000000..003c8b3c --- /dev/null +++ b/include/effect/lib/rot.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_ROT_H +#define EFFECT_LIB_ROT_H + +#include "effect.h" + +/** + * @file rot.h + * @brief Rotation matrices built from an angle. + */ + +/** @brief Build the Z, X then Y rotation of @p angles in @p out. */ +void effectMatrixSetRotZXY(SVECTOR *angles, MATRIX *out); + +#endif /* EFFECT_LIB_ROT_H */ diff --git a/include/effect/lib/rotz.h b/include/effect/lib/rotz.h new file mode 100644 index 00000000..df076a73 --- /dev/null +++ b/include/effect/lib/rotz.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_ROTZ_H +#define EFFECT_LIB_ROTZ_H + +#include "effect.h" + +/** + * @file rotz.h + * @brief Rotation about the Z axis. + */ + +/** @brief Turn @p m about Z by @p angle. */ +void effectMatrixRotZ(MATRIX *m, s32 angle); + +#endif /* EFFECT_LIB_ROTZ_H */ diff --git a/include/effect/lib/scatter.h b/include/effect/lib/scatter.h new file mode 100644 index 00000000..3310fa07 --- /dev/null +++ b/include/effect/lib/scatter.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_SCATTER_H +#define EFFECT_LIB_SCATTER_H + +#include "effect.h" + +/** + * @file scatter.h + * @brief Push an entity to a random point. + */ + +/** @brief Push @p entity to a random point within @p radius of where it is. */ +void effectScatter(EffectEntity *entity, s16 radius); + +#endif /* EFFECT_LIB_SCATTER_H */ diff --git a/include/effect/lib/spawn.h b/include/effect/lib/spawn.h new file mode 100644 index 00000000..1f96a0af --- /dev/null +++ b/include/effect/lib/spawn.h @@ -0,0 +1,35 @@ +#ifndef EFFECT_LIB_SPAWN_H +#define EFFECT_LIB_SPAWN_H + +#include "effect/lib/particle.h" + +/** + * @file spawn.h + * @brief Spawn particles off emitters and place emitters on the points their sources name. + */ + +/** @brief Step one emitter: set it up once, run its kind, then age it. */ +void effectEmitterStep(EffectEmitter *emitter); + +/** + * @brief Put @p emitter on the strand point its source names, plus that source's + * offset. + */ +void effectEmitterOnStrand(EffectEmitter *emitter); + +/** + * @brief Put @p emitter on the trail point its source names, plus that source's + * offset. + */ +void effectEmitterOnTrail(EffectEmitter *emitter); + +/** + * @brief Put @p emitter on the node that owns it, plus that source's turned + * offset. + */ +void effectEmitterOnOwner(EffectEmitter *emitter); + +/** @brief Slide @p emitter between the two points its source names. */ +void effectEmitterSlide(EffectEmitter *emitter); + +#endif /* EFFECT_LIB_SPAWN_H */ diff --git a/include/effect/lib/sprite.h b/include/effect/lib/sprite.h new file mode 100644 index 00000000..b09f75bf --- /dev/null +++ b/include/effect/lib/sprite.h @@ -0,0 +1,17 @@ +#ifndef EFFECT_LIB_SPRITE_H +#define EFFECT_LIB_SPRITE_H + +#include "effect.h" + +/** + * @file sprite.h + * @brief One sprite through the battle sprite packet. + */ + +/** + * @brief Build this effect's primitive and link it into the battle display + * list. + */ +void effectDrawSprite(EffectEntity *entity); + +#endif /* EFFECT_LIB_SPRITE_H */ diff --git a/include/effect/lib/step.h b/include/effect/lib/step.h new file mode 100644 index 00000000..7e4eb55c --- /dev/null +++ b/include/effect/lib/step.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_STEP_H +#define EFFECT_LIB_STEP_H + +#include "effect.h" + +/** + * @file step.h + * @brief Script counter stepping. + */ + +/** @brief Advance the script's counter, clamping at its limit. */ +s32 effectStepCounter(EffectEntity *entity); + +#endif /* EFFECT_LIB_STEP_H */ diff --git a/include/effect/lib/strands.h b/include/effect/lib/strands.h new file mode 100644 index 00000000..c31666d2 --- /dev/null +++ b/include/effect/lib/strands.h @@ -0,0 +1,23 @@ +#ifndef EFFECT_LIB_STRANDS_H +#define EFFECT_LIB_STRANDS_H + +#include "effect/lib/particle.h" + +/** + * @file strands.h + * @brief Seed, push and aim the five strands. + */ + +/** @brief Seed every point of all five strands from the list's seed point. */ +void effectStrandsSeed(void); + +/** @brief Push @p node's position onto the head of every strand. */ +void effectStrandsPush(EffectDrawScript *script); + +/** + * @brief Aim every strand at its own model part, then seed them from the + * middle. + */ +void effectStrandsAim(EffectDrawScript *script); + +#endif /* EFFECT_LIB_STRANDS_H */ diff --git a/include/effect/lib/tables.h b/include/effect/lib/tables.h new file mode 100644 index 00000000..426f5218 --- /dev/null +++ b/include/effect/lib/tables.h @@ -0,0 +1,21 @@ +#ifndef EFFECT_LIB_TABLES_H +#define EFFECT_LIB_TABLES_H + +#include "effect/lib/particle.h" + +/** + * @file tables.h + * @brief Relocate a pose-script table set and start a child script on it. + */ + +/** @brief Pool the pose-script children are allocated from. */ +extern s32 g_effectChildPool; + +/** @brief Turn every offset in @p tables into an address. */ +void effectPoseTablesRelocate(EffectPoseTables *tables); + +/** @brief Start @p task as a child of @p owner and seed its script fields. */ +void effectStartChildScript(EffectEntity *owner, void *task, void *arg, + s16 stopFrame, s16 a, s16 b); + +#endif /* EFFECT_LIB_TABLES_H */ diff --git a/include/effect/lib/tint.h b/include/effect/lib/tint.h new file mode 100644 index 00000000..5c670491 --- /dev/null +++ b/include/effect/lib/tint.h @@ -0,0 +1,48 @@ +#ifndef EFFECT_LIB_TINT_H +#define EFFECT_LIB_TINT_H + +#include "effect/lib/particle.h" + +/** + * @file tint.h + * @brief Play one step of a screen tint as four quads over the frame. + */ + +/** @brief One step of the tint script: a colour and what to do with it. */ +typedef struct { + /* 0x00 */ u8 r; + /* 0x01 */ u8 g; + /* 0x02 */ u8 b; /**< A hold step keeps the frame it waits for here. */ + /* 0x03 */ u8 op; /**< 0xFF ends the script, 0xFE holds, anything else steps. */ +} EffectTintStep; /* 0x04 */ + +/** + * @name Script records + * + * @ref effectSpawnTask hands back as many bytes as the script asks for, with an + * @ref EffectEntity at the front of them, so a script owns everything the + * engine leaves alone. These two lay their own state over the pose fields. + * @{ + */ + +/** @brief The entity of the script that tints the screen. */ +typedef struct { + /* 0x00 */ u8 pad000[0x24]; /**< The @ref EffectEntity the engine steps. */ + /* 0x24 */ s16 frame; /**< Frames the script has run. */ + /* 0x26 */ u16 flags; /**< See @c EFFECT_FLAG_*. */ + /* 0x28 */ u8 wait; /**< Live children. */ + /* 0x29 */ u8 pad029[0x30 - 0x29]; + /* 0x30 */ EffectTintStep *steps; + /* 0x34 */ EffectTintStep tint; /**< The colour this frame's quads take. */ + /* 0x38 */ s16 step; /**< How far into @c steps the play has got. */ + /* 0x3A */ u8 pad03A[0x3C - 0x3A]; +} EffectTintScript; /* 0x3C */ + +/** @} */ + +/** + * @brief Play one step of the screen tint and lay four quads over the frame. + */ +s32 effectTintPlay(EffectTintScript *script); + +#endif /* EFFECT_LIB_TINT_H */ diff --git a/include/effect/lib/trail.h b/include/effect/lib/trail.h new file mode 100644 index 00000000..dc5cc7d2 --- /dev/null +++ b/include/effect/lib/trail.h @@ -0,0 +1,14 @@ +#ifndef EFFECT_LIB_TRAIL_H +#define EFFECT_LIB_TRAIL_H + +#include "effect/lib/particle.h" + +/** + * @file trail.h + * @brief Aim the trail at the battle slot. + */ + +/** @brief Aim the trail at the battle slot: its two points and its height. */ +void effectTrailAim(void); + +#endif /* EFFECT_LIB_TRAIL_H */ diff --git a/include/effect/lib/vector.h b/include/effect/lib/vector.h new file mode 100644 index 00000000..f2115288 --- /dev/null +++ b/include/effect/lib/vector.h @@ -0,0 +1,53 @@ +#ifndef EFFECT_LIB_VECTOR_H +#define EFFECT_LIB_VECTOR_H + +#include "effect/lib/particle.h" + +/** + * @file vector.h + * @brief Random draws, angle jitter and vector helpers for the particle engine. + */ + +/** @brief A random value between @p lo and @p hi. */ +s32 effectRandBetween(s32 lo, s32 hi); + +/** + * @brief A random value between @p lo and @p hi, from a product of two draws. + */ +s32 effectRandProduct(s32 lo, s32 hi); + +/** + * @brief Combine @p a and @p b component-wise through @ref effectRandProduct. + */ +void effectRandVector(VECTOR *out, VECTOR *a, VECTOR *b); + +/** + * @brief Build @p m from @p angles, applying only the turns that are non-zero. + */ +void effectMatrixFromAngles(VECTOR *angles, MATRIX *m); + +/** + * @brief Combine @p a and @p b component-wise through @ref effectJitterAngle. + */ +void effectJitterVector(VECTOR *out, VECTOR *a, VECTOR *b); + +/** + * @brief Combine @p a and @p b component-wise through @ref effectJitterAngleS. + */ +void effectJitterSVector(SVECTOR *out, SVECTOR *a, SVECTOR *b); + +/** @brief Copy the three words at @p src to @p dst. */ +void effectCopyVector(s32 *src, s32 *dst); + +/** @brief Fade @p value towards zero by @p t, a 16.16 fraction of it. */ +void effectFadeValue(s32 t, s32 *value); + +/** @brief Fade all three components of @p v towards zero by @p t. */ +void effectFadeVector(s32 t, VECTOR *v); + +/** + * @brief Blend this frame's two source point sets into the particle's target set. + */ +void effectBlendPointSets(EffectParticle *particle, EffectPoseStep *step); + +#endif /* EFFECT_LIB_VECTOR_H */ diff --git a/include/effect/params/effect_028/effect_params.h b/include/effect/params/effect_028/effect_params.h new file mode 100644 index 00000000..8ce61050 --- /dev/null +++ b/include/effect/params/effect_028/effect_params.h @@ -0,0 +1,13 @@ +#ifndef EFFECT_PARAMS_H +#define EFFECT_PARAMS_H + +/** + * @file effect_params.h + * @brief Curaga: the ring sizes its mote engine is built with. + */ + +/** @brief Highest index each ring reaches before it wraps. */ +#define EFFECT_EMITTER_COUNT 0x59 +#define EFFECT_PARTICLE_COUNT 0x8B + +#endif /* EFFECT_PARAMS_H */ diff --git a/src/effect/effect_001.c b/src/effect/effect_001.c index b1d5aac6..b2a1aae9 100644 --- a/src/effect/effect_001.c +++ b/src/effect/effect_001.c @@ -9,12 +9,14 @@ #include "psxsdk/libgpu.h" #include "psxsdk/inline_c.h" #include "effect/effect_001.h" +#include "effect/effect_001_anim.h" +#include "effect/lib/common.h" +#include "effect/lib/entity.h" +#include "effect/lib/sprite.h" +#include "effect/lib/step.h" +#include "effect/lib/render.h" #include "btl_entity.h" -static void func_801A0644(MATRIX *m, s32 angle); -static void func_801A0960(MATRIX *m, SVECTOR *dir, SVECTOR *up); -static EffectEntity *func_801A0B00(void *pool, void *task, s32 stride, - EffectEntity *owner); static s32 func_801A2E48(EffectSpark *spark); static void func_801A351C(EffectEntity *entity); static void func_801A3640(EffectEntity *entity); @@ -22,1112 +24,6 @@ static s32 func_801A3924(EffectEntity *entity); static s32 func_801A3B6C(EffectEntity *entity); static s32 func_801A3E64(EffectEntity *entity); static s32 func_801A41C8(EffectEntity *entity); -static s32 func_801A4434(EffectEntity *entity); - -/** @brief Stride between the prim and frame banks carved out of the TIM. */ -#define EFFECT_BANK_SIZE 0x10000 - - -/** - * @brief Start an effect script and hand back its task pool. - * - * Builds the four task pools, allocates the root entity, seeds it from the - * animation set, and publishes the three frame banks the renderer reads from. - * - * @param animSet Animation set the effect plays. - * @return The pool the root entity lives in. - */ -void *func_801A0000(EffectAnimSet *animSet) { - u8 *bank0; - EffectEntity *entity; - u8 *bank1; - u8 *frames; - - D_801C58E0 = 0; - D_801C58E4 = 0; - func_800B2A00(&D_801C59E0, &D_801C5910, 0x64, 2); - entity = func_801A0B00(&D_801C59E0, func_801A4434, 0x64, NULL); - entity->animSet = animSet; - entity->unk02C = animSet->anims[entity->unk02A].unk000; - entity->unk02D = - entity->animSet->anims[entity->unk02A].parts[entity->unk02B].unk000; - entity->pc = 0; - entity->unk05A = animSet->anims->unk010; - entity->unk058 = animSet->anims->unk011; - entity->unk02F = entity->unk05A - 1; - if (entity->unk02F < entity->unk058) { - entity->unk02F = entity->unk058; - } - if (!(animSet->flags & EFFECT_ANIMSET_FLAG_LOADED)) { - func_800C3BE0(&D_801A475C); - func_800BB084(&D_801A4F0C); - } - D_801C58F0 = &D_801A4F0C; - bank0 = &D_801A4F0C; - D_801C58FC = bank0; - bank1 = bank0 + EFFECT_BANK_SIZE; - D_801C5904 = bank0 + EFFECT_BANK_SIZE; - D_801C58F0 = bank0 + EFFECT_BANK_SIZE; - frames = bank1; - D_801C5900 = frames; - frames += EFFECT_BANK_SIZE; - D_801C58F0 = frames; - D_801C5908 = frames; - func_800B2A00(&D_801C5B50, &D_801C59F0, 0x58, 4); - func_800B2A00(&D_801CD9F0, &D_801C5B60, 0x6C, 0x12C); - func_800B2A00(&D_801D5CF0, &D_801D3900, 0x5C, 0x64); - return &D_801C59E0; -} - -/** - * @brief Cache the sixteen hex digit glyphs and reset the debug text cursor. - * - * @note The lookup runs once per glyph -- the target holds 16 separate calls, - * so a loop or a cached pointer does not match. - */ -static void func_801A01F0(void) { - D_801D5D00[0] = getMenuString(11)[1]; - D_801D5D00[1] = getMenuString(11)[2]; - D_801D5D00[2] = getMenuString(11)[3]; - D_801D5D00[3] = getMenuString(11)[4]; - D_801D5D00[4] = getMenuString(11)[5]; - D_801D5D00[5] = getMenuString(11)[6]; - D_801D5D00[6] = getMenuString(11)[7]; - D_801D5D00[7] = getMenuString(11)[8]; - D_801D5D00[8] = getMenuString(11)[9]; - D_801D5D00[9] = getMenuString(11)[10]; - D_801D5D00[10] = getMenuString(11)[11]; - D_801D5D00[11] = getMenuString(11)[12]; - D_801D5D00[12] = getMenuString(11)[13]; - D_801D5D00[13] = getMenuString(11)[14]; - D_801D5D00[14] = getMenuString(11)[15]; - D_801D5D00[15] = getMenuString(11)[16]; - D_801D5D00[16] = 0; - D_801D5D14 = 10; - D_801D5D18 = 20; - D_801D5D1C = 3; -} - -/** @brief Draw @p value as eight hex glyphs at the current debug text cursor. */ -static void func_801A0340(u32 value) { - u32 *ot = D_800FA5E8->frontOT; - u8 text[9]; - s32 i; - - for (i = 7; i >= 0; i--) { - text[i] = D_801D5D00[value & 0xF]; - value >>= 4; - } - text[8] = 0; - D_801C58F4 = func_8002C56C(ot, D_801C58F4, D_801D5D14, D_801D5D18, - text, D_801D5D1C); - D_801D5D14 += 0x48; -} - -/** @brief Opcode handler: arm the frame delay and advance the running total. */ -static void func_801A03EC(void) { - D_801D5D14 = 10; - D_801D5D18 += 10; -} - -/** @brief Opcode handler: no-op. */ -static void func_801A040C(EffectEntity *entity) { -} - -/** - * @brief Emit a 128x128 textured quad at the head of the prim buffer. - * - * @param x Left edge in screen space. - * @param y Top edge in screen space. - * @param tx Texture page X. - * @param ty Texture page Y. - * @param clutX Palette X. - * @param clutY Palette Y. - * @param abr Semi-transparency rate for the page. - */ -static void func_801A0414(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, - s32 abr) { - POLY_FT4 *poly = D_801C58F4; - - setPolyFT4(poly); - setSemiTrans(poly, 1); - setShadeTex(poly, 1); - poly->tpage = getTPage(1, abr, tx, ty); - poly->clut = getClut(clutX, clutY); - poly->u0 = poly->u2 = 0; - poly->u1 = poly->u3 = 0xFF; - poly->v0 = poly->v1 = 0; - poly->v2 = poly->v3 = 0xFF; - poly->x0 = poly->x2 = x; - poly->x1 = poly->x3 = x + 0x7F; - poly->y0 = poly->y1 = y; - poly->y2 = poly->y3 = y + 0x7F; - AddPrim(D_800FA5E8, poly); - poly++; - D_801C58F4 = poly; -} - -/** - * @brief Emit a 256x256 textured quad at the head of the prim buffer. - * - * @param x Left edge in screen space. - * @param y Top edge in screen space. - * @param tx Texture page X. - * @param ty Texture page Y. - * @param clutX Palette X. - * @param clutY Palette Y. - * @param abr Semi-transparency rate for the page. - */ -static void func_801A0510(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, - s32 abr) { - POLY_FT4 *poly = D_801C58F4; - - setPolyFT4(poly); - setSemiTrans(poly, 1); - setShadeTex(poly, 1); - poly->tpage = getTPage(1, abr, tx, ty); - poly->clut = getClut(clutX, clutY); - poly->u0 = poly->u2 = 0; - poly->u1 = poly->u3 = 0xFF; - poly->v0 = poly->v1 = 0; - poly->v2 = poly->v3 = 0xFF; - poly->x0 = poly->x2 = x; - poly->x1 = poly->x3 = x + 0xFF; - poly->y0 = poly->y1 = y; - poly->y2 = poly->y3 = y + 0xFF; - AddPrim(D_800FA5E8, poly); - poly++; - D_801C58F4 = poly; -} - -/** @brief Load the identity matrix. */ -static void func_801A060C(MATRIX *m) { - m->m[0][0] = ONE; - m->m[1][0] = 0; - m->m[2][0] = 0; - m->m[0][1] = 0; - m->m[1][1] = ONE; - m->m[2][1] = 0; - m->m[0][2] = 0; - m->m[1][2] = 0; - m->m[2][2] = ONE; - m->t[0] = 0; - m->t[1] = 0; - m->t[2] = 0; -} - -/** @brief Post-multiply @p m by a rotation of @p angle about X. */ -static void func_801A0644(MATRIX *m, s32 angle) { - EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); - - rot->sin = rsin(angle); - rot->cos = rsin((angle + 0x400) & 0xFFF); - rot->m.m[0][0] = ONE; - rot->m.m[0][1] = 0; - rot->m.m[0][2] = 0; - rot->m.m[1][0] = 0; - rot->m.m[1][1] = rot->cos; - rot->m.m[1][2] = -rot->sin; - rot->m.m[2][0] = 0; - rot->m.m[2][1] = rot->sin; - rot->m.m[2][2] = rot->cos; - gte_MulMatrix0(m, &rot->m, m); - func_800B36B8(sizeof(EffectRotScratch)); -} - -/** - * @brief Compose a rotation about the Y axis into @p m. - * - * @param m Matrix rotated in place. - * @param angle Rotation, in the 0x1000-per-turn units @ref rsin takes. - */ -static void func_801A07D4(MATRIX *m, s32 angle) { - EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); - - rot->sin = rsin(angle); - rot->cos = rsin((angle + 0x400) & 0xFFF); - rot->m.m[0][0] = rot->cos; - rot->m.m[0][1] = 0; - rot->m.m[0][2] = rot->sin; - rot->m.m[1][0] = 0; - rot->m.m[1][1] = ONE; - rot->m.m[1][2] = 0; - rot->m.m[2][0] = -rot->sin; - rot->m.m[2][1] = 0; - rot->m.m[2][2] = rot->cos; - gte_MulMatrix0(m, &rot->m, m); - func_800B36B8(sizeof(EffectRotScratch)); -} - -/** @brief Build a matrix in @p m that aims along @p dir with @p up as the roll. */ -static void func_801A0960(MATRIX *m, SVECTOR *dir, SVECTOR *up) { - MATRIX *basis = func_800B3698(sizeof(MATRIX)); - SVECTOR *row1 = (SVECTOR *)basis->m[1]; - SVECTOR *row2 = (SVECTOR *)basis->m[2]; - - /* A matrix row is 6 bytes, so the whole-vector store spills one halfword - into row 2 -- harmless, row 2 is written next. The width is load-bearing: - it is the unaligned 8-byte copy the target does here, where the - element-wise stores below are three halfwords. */ - *row1 = *up; - row2->vx = dir->vx; - row2->vy = dir->vy; - row2->vz = dir->vz; - gte_ldopv1SV(row1); - gte_ldopv2SV(row2); - gte_op12(); - gte_stsv(basis->m[0]); - MatrixNormal_2(basis, basis); - TransposeMatrix(basis, m); - func_800B36B8(sizeof(MATRIX)); -} - -/** @brief Aim @p m along the vector from @p from to @p to, Y up. */ -static void func_801A0A6C(MATRIX *m, SVECTOR *from, SVECTOR *to) { - SVECTOR up; - SVECTOR delta; - - up.vx = 0; - up.vy = ONE; - up.vz = 0; - delta.vx = to->vx - from->vx; - delta.vy = to->vy - from->vy; - delta.vz = to->vz - from->vz; - func_801A0960(m, &delta, &up); -} - -/** @brief Opcode handler: release one frame of the linked script's wait. */ -static void func_801A0AD8(EffectEntity *entity) { - if (entity->unk018 != NULL) { - entity->unk018->wait--; - } -} - -/** - * @brief Allocate an effect entity from @p pool and hang it off @p owner. - * - * The pool keeps its own 12-byte task header ahead of the entity fields, so - * only the remainder of @p stride is cleared. A child inherits the owner's - * model, animation set and position; an entity with no owner becomes its own - * model. - * - * @param pool Task pool to allocate from. - * @param task Per-frame step to install on the new entity. - * @param stride Total size of the allocation, header included. - * @param owner Parent entity, or NULL for a root. - * @return The new entity, or NULL if the pool is full. - */ -static EffectEntity *func_801A0B00(void *pool, void *task, s32 stride, - EffectEntity *owner) { - EffectEntity *node = func_800B2A84(pool, task); - s32 *clear; - s32 words; - s32 i; - - if (node == NULL) { - return NULL; - } - clear = (s32 *)&node->animSet; - stride = stride - 0xC; - words = stride / 4; - for (i = 0; i < words; i++) { - *clear = 0; - clear++; - } - if (owner == NULL) { - node->unk018 = NULL; - node->unk014 = NULL; - /* A root drives its own geometry, so it is its own model. */ - node->unk010 = (EffectModel *)node; - } else { - if (owner->unk014 == NULL) { - node->unk014 = (EffectModel *)node; - } else { - node->unk014 = owner->unk014; - } - node->unk018 = owner; - node->unk010 = owner->unk010; - node->animSet = owner->animSet; - node->pos = owner->pos; - node->unk02A = owner->unk02A; - node->unk02B = owner->unk02B; - node->unk02E = owner->unk02E; - node->unk02F = owner->unk02F; - owner->wait++; - node->unk02C = node->animSet->anims[node->unk02A].unk000; - node->unk02D = - node->animSet->anims[node->unk02A].parts[node->unk02B].unk000; - } - return node; -} - -/** @brief Cache the battle slot's two anchor points and their midpoint. */ -static void func_801A0C8C(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - SVECTOR top; - SVECTOR bottom; - - if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { - return; - } - func_800B3960(slot, 0xF0, 0, &top); - func_800B3960(slot, 0xF1, 0, &bottom); - entity->unk040.vx = top.vx; - entity->unk040.vy = top.vy; - entity->unk040.vz = top.vz; - entity->unk038.vx = (top.vx + bottom.vx) / 2; - entity->unk038.vy = (top.vy + bottom.vy) / 2; - entity->unk038.vz = (top.vz + bottom.vz) / 2; - entity->unk030.vx = bottom.vx; - entity->unk030.vy = slot->unk024; - entity->unk030.vz = bottom.vz; -} - -/** @brief Copy @c unk030 of the linked model out to @p out. */ -static void func_801A0DC0(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk014->unk030; -} - -/** @brief Copy @c unk038 of the linked model out to @p out. */ -static void func_801A0DF0(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk014->unk038; -} - -/** @brief Copy @c unk040 of the linked model out to @p out. */ -static void func_801A0E20(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk014->unk040; -} - -/** @brief As @ref func_801A0C8C, but for the slot the animation set names. */ -static void func_801A0E50(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->animSet->slot]; - SVECTOR top; - SVECTOR bottom; - - if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { - return; - } - func_800B3960(slot, 0xF0, 0, &top); - func_800B3960(slot, 0xF1, 0, &bottom); - entity->unk040.vx = top.vx; - entity->unk040.vy = top.vy; - entity->unk040.vz = top.vz; - entity->unk038.vx = (top.vx + bottom.vx) / 2; - entity->unk038.vy = (top.vy + bottom.vy) / 2; - entity->unk038.vz = (top.vz + bottom.vz) / 2; - entity->unk030.vx = bottom.vx; - entity->unk030.vy = slot->unk024; - entity->unk030.vz = bottom.vz; -} - -/** @brief Copy @c unk030 of the linked model out to @p out. */ -static void func_801A0F8C(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk010->unk030; -} - -/** @brief Copy @c unk038 of the linked model out to @p out. */ -static void func_801A0FBC(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk010->unk038; -} - -/** @brief Copy @c unk040 of the linked model out to @p out. */ -static void func_801A0FEC(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk010->unk040; -} - -/** - * @brief Recompute the model's bounding box from its posed skeleton. - * - * Transforms every joint after the root through the GTE and keeps the running - * minimum and maximum, then publishes the box back onto the entity. - * - * @param entity Entity whose model is measured. - */ -static void func_801A101C(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - EffectSkeleton *skeleton = slot->mesh->skeleton; - EffectJoint *joint = skeleton->joints; - EffectBoundsScratch *b = func_800B3698(sizeof(EffectBoundsScratch)); - SVECTOR *bounds; - s32 i; - s16 x = joint->mtx.t[0]; - s16 y; - s16 z; - - b->max.vx = x; - b->min.vx = x; - y = joint->mtx.t[1]; - b->max.vy = y; - b->min.vy = y; - z = joint->mtx.t[2]; - b->max.vz = z; - b->min.vz = z; - b->v.vx = 0; - b->v.vy = 0; - joint = &skeleton->joints[1]; - for (i = 1; i < skeleton->count; i++) { - SetRotMatrix(&joint->mtx); - SetTransMatrix(&joint->mtx); - b->v.vz = -joint->unk002; - gte_ldv0(&b->v); - gte_mvmva(1, 0, 0, 0, 0); - gte_stlvnl(&b->pos); - gte_stflg(&b->flag); - if (b->pos.vx < b->min.vx) { - b->min.vx = b->pos.vx; - } else if (b->max.vx < b->pos.vx) { - b->max.vx = b->pos.vx; - } - if (b->pos.vy < b->min.vy) { - b->min.vy = b->pos.vy; - } else if (b->max.vy < b->pos.vy) { - b->max.vy = b->pos.vy; - } - if (b->pos.vz < b->min.vz) { - b->min.vz = b->pos.vz; - } else if (b->max.vz < b->pos.vz) { - b->max.vz = b->pos.vz; - } - joint++; - } - /* The box overlays unk048/unk04C, which the script opcodes use as a table - pointer -- the two never overlap in time. */ - bounds = (SVECTOR *)entity->unk048; - bounds[0] = b->min; - bounds[1] = b->max; - func_800B36B8(sizeof(EffectBoundsScratch)); -} - -/** @brief Copy the linked model's bounding box out to @p min and @p max. */ -static void func_801A1268(EffectEntity *entity, SVECTOR *min, SVECTOR *max) { - EffectModel *model = entity->unk014; - - *min = model->boundsMin; - *max = model->boundsMax; -} - -/** - * @brief Half the longest side of the model's bounding box. - * - * @param entity Entity whose model is measured. - * @return Half the largest of the box's three extents. - */ -static s32 func_801A12B8(EffectEntity *entity) { - EffectModel *model = entity->unk014; - s16 dx = model->boundsMax.vx - model->boundsMin.vx; - s16 dy = model->boundsMax.vy - model->boundsMin.vy; - s16 dz = model->boundsMax.vz - model->boundsMin.vz; - if (dx > dy) { - if (dx > dz) { - return dx / 2; - } - return dz / 2; - } - if (dy > dz) { - return dy / 2; - } - return dz / 2; -} - -/** @brief Half the height of the linked model's bounding box. */ -static s16 func_801A135C(EffectEntity *entity) { - EffectModel *model = entity->unk014; - - return (s16)(model->boundsMax.vy - model->boundsMin.vy) / 2; -} - -/** @brief Half the linked model's larger horizontal extent. */ -static s16 func_801A138C(EffectEntity *entity) { - EffectModel *model = entity->unk014; - s16 spanX = model->boundsMax.vx - model->boundsMin.vx; - s16 spanZ = model->boundsMax.vz - model->boundsMin.vz; - /* Compared as `span` but returned as `spanX`: testing a separate copy is - what puts the wider-X arm on the taken branch. */ - s16 span = spanX; - - return (span <= spanZ ? spanZ : spanX) / 2; -} - -/** @brief Height of the linked model's bounding box. */ -static s16 func_801A13E4(EffectEntity *entity) { - return entity->unk014->boundsMax.vy - entity->unk014->boundsMin.vy; -} - -/** @brief Y of the top of the linked model, in battle-entity space. */ -static s16 func_801A1408(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - - return slot->pos.vy + entity->unk014->boundsMax.vy; -} - -/** @brief Y of the bottom of the linked model, in battle-entity space. */ -static s16 func_801A1450(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - - return slot->pos.vy + entity->unk014->boundsMin.vy; -} - -/** @brief A random point up the linked model, in battle-entity space. */ -static s16 func_801A1498(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - EffectModel *model = entity->unk014; - s16 height = model->boundsMax.vy - model->boundsMin.vy; - s16 offset = (rand() & 0xFFF) * height / 4096; - - return slot->pos.vy + model->boundsMax.vy - offset / 2; -} - -/** @brief Y of the centre of the linked model, in battle-entity space. */ -static s16 func_801A1550(EffectEntity *entity) { - EffectModel *model = entity->unk014; - - return D_800EF2D0[entity->unk02D].pos.vy + - (model->boundsMax.vy + model->boundsMin.vy) / 2; -} - -/** @brief Centre of the linked model's bounding box. */ -static void func_801A15A4(EffectEntity *entity, SVECTOR *out) { - EffectModel *model = entity->unk014; - - out->vx = (model->boundsMax.vx + model->boundsMin.vx) / 2; - out->vy = (model->boundsMax.vy + model->boundsMin.vy) / 2; - out->vz = (model->boundsMax.vz + model->boundsMin.vz) / 2; -} - -/** @brief Zero @p size bytes' worth of words starting at @p dst. */ -static void func_801A1610(s32 *dst, s32 size) { - s32 i; - - for (i = 0; i < size / 4; i++) { - *dst = 0; - dst++; - } -} - -/** @brief Build this effect's primitive and link it into the battle display list. */ -static void func_801A1648(EffectEntity *entity) { - s16 count; - BattleSpritePrim *prim; - - if (entity->flags & EFFECT_FLAG_DONE) { - return; - } - prim = func_800B3698(sizeof(BattleSpritePrim)); - func_800C96E4(&entity->pos, ONE, entity->unk054); - prim->anim = entity->unk04C; - count = entity->unk050; - prim->flags = 0; - prim->frame = count; - D_801C58F4 = func_800C9E10(prim, D_800FA5E8->ot, 2, D_801C58F4); - func_800B36B8(sizeof(BattleSpritePrim)); -} - -/** @brief Advance the script's counter, clamping at its limit. */ -static s32 func_801A16E4(EffectEntity *entity) { - entity->unk050++; - if (entity->unk052 < entity->unk050) { - entity->unk050 = entity->unk052; - entity->flags |= EFFECT_FLAG_DONE; - return 1; - } - return 0; -} - -/** @brief Draw @p anim at the entity's position in @p colour and link it into the OT. */ -static void func_801A172C(EffectEntity *entity, BattleSpriteAnim *anim, - CVECTOR *colour) { - MATRIX m; - void **head; - BattleSpritePrim *prim; - - if (entity->flags & EFFECT_FLAG_DONE) { - return; - } - func_801A060C(&m); - func_801A0644(&m, 0x400); - m.t[0] = entity->pos.vx; - m.t[1] = 0; - m.t[2] = entity->pos.vz; - CompMatrix(&D_800F02C8, &m, &m); - SetRotMatrix(&m); - SetTransMatrix(&m); - prim = func_800B3698(sizeof(BattleSpritePrim)); - head = &D_801C58F4; - prim->anim = anim; - prim->frame = 0; - prim->flags = BATTLE_SPRITE_FLAG_COLOUR; - prim->colour = *colour; - *head = func_800C9E10(prim, D_800FA5E8->ot, 2, *head); - func_800B36B8(sizeof(BattleSpritePrim)); -} - -/** @brief Refresh the render matrices from @p pose and apply the offset. */ -static void func_801A181C(EffectRender *render, BattleEffectSlot *slot) { - SVECTOR offset; - - render->unk014 = slot->mtx; - render->unk054 = render->unk014; - switch (render->unk0D2) { - case 0: - ApplyMatrixSV(&render->unk054, &render->unk0B4, &offset); - render->unk054.t[0] += offset.vx; - render->unk054.t[1] += offset.vy; - render->unk054.t[2] += offset.vz; - break; - case 1: - render->unk054.t[0] += render->unk0B4.vx; - render->unk054.t[1] += render->unk0B4.vy; - render->unk054.t[2] += render->unk0B4.vz; - break; - } -} - -/** @brief Compose each joint's matrix through the pose and the per-joint offset. */ -static void func_801A1960(EffectSkeletonRef *ref, EffectPose *pose) { - MATRIX *offset = D_801D5D20; - EffectSkeleton *skeleton = ref->mesh->skeleton; - EffectJoint *joint = skeleton->joints; - s32 i; - - for (i = 0; i < skeleton->count; i++) { - CompMatrix(&pose->world, &joint->mtx, offset); - CompMatrix(&pose->view, &joint->mtx, &joint->mtx); - joint++; - offset++; - } -} - -/** @brief Rebase a table of absolute pointers into offsets from its own head. */ -static void func_801A1A0C(s32 *table) { - s32 *base; - s32 count; - s32 i; - - base = table; - count = *table; - for (i = 0; i < count; i++) { - table++; - *table -= (s32)base; - } -} - -/** @brief Aim the render's model at the camera reference point. */ -static void func_801A1A4C(EffectRender *render) { - EffectAimScratch *a = func_800B3698(sizeof(EffectAimScratch)); - s32 dx; - s32 dy; - s32 dz; - - a->pos.vx = render->unk054.t[0]; - a->pos.vy = render->unk054.t[1]; - a->pos.vz = render->unk054.t[2]; - dx = a->pos.vx - render->unk0DC; - a->delta.vx = dx; - dy = a->pos.vy - render->unk0DE; - a->delta.vy = dy; - dz = a->pos.vz - render->unk0E0; - a->delta.vz = dz; - a->distSq = dx * dx + dy * dy + dz * dz; - a->dist = SquareRoot0(a->distSq); - a->up.vx = 0; - a->up.vy = ONE; - a->up.vz = 0; - VectorNormalS(&a->delta, &a->dir); - func_801A0960(&a->rot, &a->dir, &a->up); - TransposeMatrix(&a->rot, &a->out); - a->out.t[0] = 0; - a->out.t[1] = 0; - a->out.t[2] = render->scale; - a->rot = render->unk014; - a->rot.t[0] = render->unk014.t[0] - render->unk054.t[0]; - a->rot.t[1] = render->unk014.t[1] - render->unk054.t[1]; - a->rot.t[2] = render->unk014.t[2] - render->unk054.t[2]; - gte_MulMatrix0(&a->out, &a->rot, &render->unk074); - gte_SetTransMatrix(&a->out); - gte_ldlv0(a->rot.t); - gte_mvmva(1, 0, 0, 0, 0); - gte_stlvnl(render->unk074.t); - func_800B36B8(sizeof(EffectAimScratch)); -} - -/** @brief Reset a render request to its default pose, white, and unit scale. */ -static void func_801A1D38(EffectRender *render, void *pose, EffectRenderPart *part, - void *texture, BattleEffectSlot *source, s32 frame) { - render->unk00C = &D_800FA5F0; - render->unk000 = pose; - render->part = part; - render->unk010 = &D_801C58F4; - render->unk0B4.vx = 0; - render->unk0B4.vy = 0; - render->unk0B4.vz = 0; - render->r = 0x80; - render->g = 0x80; - render->b = 0x80; - render->scale = 0x2000; - render->unk0D2 = 1; - render->unk0CE = 0; - render->unk0F2 = 0; - render->unk0A4.vx = 0; - render->unk0A4.vy = 0; - render->unk0A4.vz = 0; - render->unk0AC.vx = 0; - render->unk0AC.vy = 0; - render->unk0AC.vz = 0; - render->unk0BC.vx = 0; - render->unk0BC.vy = 0; - render->unk0BC.vz = 0; - render->slot = source; - render->unk0DA = frame; - part->unk018 = 0x140; - part->unk004 = texture; - part->unk014 = 0; - part->unk016 = 0; - part->unk01A = 0; - part->r = 0x80; - part->g = 0x80; - part->b = 0x80; - part->unk020 = -1; - part->unk024 = 0; -} - -/** @brief Point a render request at a texture page, CLUT and source rectangle. */ -static void func_801A1DF4(EffectRender *render, SVECTOR *pos, s16 tx, s16 ty, - s16 clutX, s16 clutY, s16 w, s16 h) { - POLY_FT3 poly; - - render->unk0DC = pos->vx; - render->unk0DE = pos->vy; - render->unk0E0 = pos->vz; - setPolyFT3(&poly); - setSemiTrans(&poly, 1); - setShadeTex(&poly, 1); - poly.tpage = getTPage(1, 1, tx, ty); - poly.clut = getClut(clutX, clutY); - render->tpage = poly.tpage; - render->clut = poly.clut; - render->unk0E4 = w; - render->unk0E6 = h; - /* The u coordinate within the page, doubled for the 4bpp texture. */ - render->unk0E8 = ((s16)tx - (s16)(tx & ~0x3F)) * 2; - render->unk0EA = ty & 0xFF; -} - -/** - * @brief Draw one mesh: pose its vertices by joint, then emit its triangles and - * quads into the ordering table. - * - * Each part of the mesh is a command stream. Its first section lists vertex - * groups, each posed through one joint of the skeleton with the GTE's light - * matrix (the part's vertices are normals: @c mvmva against the loaded joint). - * The aligned tail holds a triangle list and a quad list. Every primitive is - * transformed twice: once with the view matrix, which gives the screen - * coordinates and depth for BOTH the shaded prim (whose UVs are the screen - * coordinates -- an environment map) and the textured prim; then with the - * light matrix, whose winding decides the back-face cull when the render asks - * for it. A primitive is also rejected when any projected vertex leaves the - * clip rectangle, in which case only the textured prim is emitted. - * - * The prim cursors come from and return to the render's two list heads; the - * scratch record lives in the battle scratchpad for the duration. - * - * @param mesh Skeleton and part table to draw. - * @param ot Ordering table the prims are linked into. - * @param mode Unused. - * @param render Render state: matrices, colour, texture page and clip rect. - */ -static void func_801A1EBC(EffectMesh *mesh, u32 *ot, s32 mode, - EffectRender *render) { - EffectMeshVertex *vbuf = render->part->unk004; - EffectJoint *joints = mesh->skeleton->joints; - u32 *parts = mesh->parts; - s32 count = parts[0]; - /* battle.bin keeps this list head as a word (see D_800FA5F0). */ - POLY_FT3 *ft3 = (POLY_FT3 *)render->unk00C[0]; - POLY_GT3 *gt3 = render->unk010[0]; - BattleEffectSlot *slot = render->slot; - EffectMeshScratch *s = func_800B3698(sizeof(EffectMeshScratch)); - s32 part; - POLY_FT4 *ft4; - POLY_GT4 *gt4; - u32 colour; - - s->visible = slot->unk07C; - s->unk0CE = render->unk0F4; - s->tpage = render->tpage; - s->clut = render->clut; - s->clipX0 = -(render->unk0E4 / 2); - s->clipX1 = render->unk0E4 / 2 - 1; - s->clipY0 = -(render->unk0E6 / 2); - s->clipY1 = render->unk0E6 / 2 - 1; - s->clipX0 += EFFECT_SCREEN_CX; - s->clipX1 += EFFECT_SCREEN_CX; - s->clipY0 += EFFECT_SCREEN_CY; - s->clipY1 += EFFECT_SCREEN_CY; - s->offX = render->unk0E8 + render->unk0E4 / 2 - EFFECT_SCREEN_CX; - s->offY = render->unk0EA + render->unk0E6 / 2 - EFFECT_SCREEN_CY; - /* The shaded prims are semi-transparent (code bit 2), the textured are not. */ - s->colour = *(u32 *)&render->r & EFFECT_PRIM_RGB; - s->unk0AC = s->colour | EFFECT_PRIM_CODE(0x3C | 0x02); - s->colour = s->colour | EFFECT_PRIM_CODE(0x34 | 0x02); - s->unk0B0 = slot->unk028 & EFFECT_PRIM_RGB; - s->unk0B4 = s->unk0B0 | EFFECT_PRIM_CODE(0x2C); - s->unk0B0 = s->unk0B0 | EFFECT_PRIM_CODE(0x24); - s->view = render->unk034; - s->light = render->unk074; - s->unk0A4 = render->unk0CE; - s->unk0A6 = render->unk0CC; - parts++; - gte_SetRotMatrix(&s->view); - gte_SetTransMatrix(&s->view); - for (part = 0; part < count; part++) { - EffectMeshVertex *vb = vbuf; - s16 *stream = (s16 *)((u8 *)mesh->parts + *parts++); - s32 groups; - s32 g; - s32 n; - s32 v; - s32 tris; - EffectMeshTri *tri; - MATRIX *jm; - EffectMeshQuad *quad; - s32 quads; - POLY_GT3 *gt; - POLY_FT3 *ft; - - if (!((s->visible >> part) & 1)) { - continue; - } - groups = *stream++; - for (g = 0; g < groups; g++) { - jm = &joints[*stream++].mtx; - gte_SetLightMatrix(jm); - gte_ldbkdir(jm->t[0], jm->t[1], jm->t[2]); - n = *stream++; - for (v = 0; v < n; v++) { - s->normal.vx = *stream++; - s->normal.vy = *stream++; - s->normal.vz = *stream++; - gte_ldv0(&s->normal); - gte_mvmva(1, 1, 0, 1, 0); - gte_stsv(&vb->pos); - vb++; - } - } - stream = (s16 *)(((u32)stream + 3) & ~3); - tris = *stream++; - quads = *stream++; - stream += 4; - tri = (EffectMeshTri *)stream; - vb = vbuf; - gt = gt3; - ft = ft3; - for (g = 0; g < tris; g++) { - s->idx0 = tri->idx0 & EFFECT_MESH_INDEX_MASK; - s->idx1 = tri->idx1 & EFFECT_MESH_INDEX_MASK; - s->idx2 = tri->idx2 & EFFECT_MESH_INDEX_MASK; - gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, &vb[s->idx2].pos); - gte_rtpt(); - gte_nclip(); - gte_stopz(&s->nclip); - if (s->nclip > 0) { - gte_avsz3(); - gte_stotz(&s->otz); - s->otz = (u32)s->otz >> 2; - gte_stsxy3_gt3(gt); - gte_stsxy3_ft3(ft); - gte_SetRotMatrix(&s->light); - gte_SetTransMatrix(&s->light); - gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, - &vb[s->idx2].pos); - gte_rtpt(); - s->reject = 0; - gte_nclip(); - gte_stopz(&s->nclip); - if (s->unk0CE == 1 && s->nclip <= 0) { - s->reject = 1; - } - if (!s->reject) { - gte_stsxy3c(s->sxy); - for (v = 0; v < 3; v++) { - if (s->sxy[v].vx < s->clipX0) { - s->reject = 1; - break; - } - if (s->clipX1 < s->sxy[v].vx) { - s->reject = 1; - break; - } - if (s->sxy[v].vy < s->clipY0) { - s->reject = 1; - break; - } - if (s->clipY1 < s->sxy[v].vy) { - s->reject = 1; - break; - } - s->sxy[v].vx += s->offX; - s->sxy[v].vy += s->offY; - } - } - if (!s->reject) { - gt->tag = EFFECT_PRIM_TAG(POLY_GT3); - gt->u0 = s->sxy[0].vx; - gt->v0 = s->sxy[0].vy; - gt->u1 = s->sxy[1].vx; - gt->v1 = s->sxy[1].vy; - gt->u2 = s->sxy[2].vx; - gt->v2 = s->sxy[2].vy; - gt->tpage = s->tpage; - gt->clut = s->clut; - /* Colour and code as one word; four byte stores do not match. */ - colour = s->colour; - *(u32 *)>->r2 = colour; - *(u32 *)>->r1 = colour; - *(u32 *)>->r0 = colour; - addPrim(&ot[s->otz], gt); - gt++; - } - ft->tag = EFFECT_PRIM_TAG(POLY_FT3); - *(u32 *)&ft->u0 = tri->uv0; - *(u32 *)&ft->u1 = *(u32 *)&tri->uv1; - *(u16 *)&ft->u2 = tri->uv2; - *(u32 *)&ft->r0 = s->unk0B0; - if (tri->tpage & EFFECT_MESH_TPAGE_ABE) { - ft->code |= 2; - } - addPrim(&ot[s->otz], ft); - ft++; - gte_SetRotMatrix(&s->view); - gte_SetTransMatrix(&s->view); - } - tri++; - } - quad = (EffectMeshQuad *)tri; - ft4 = (POLY_FT4 *)ft; - gt4 = (POLY_GT4 *)gt; - for (g = 0; g < quads; g++) { - s->idx0 = quad->idx0 & EFFECT_MESH_INDEX_MASK; - s->idx1 = quad->idx1 & EFFECT_MESH_INDEX_MASK; - s->idx2 = quad->idx2 & EFFECT_MESH_INDEX_MASK; - gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, &vb[s->idx2].pos); - gte_rtpt(); - gte_nclip(); - gte_stopz(&s->nclip); - if (s->nclip > 0) { - gte_stsxy3_gt3(gt4); - gte_stsxy3_ft3(ft4); - s->idx3 = quad->idx3 & EFFECT_MESH_INDEX_MASK; - gte_ldv0(&vb[s->idx3].pos); - gte_rtps(); - gte_stsxy(>4->x3); - gte_stsxy(&ft4->x3); - gte_avsz4(); - gte_stotz(&s->otz); - s->otz = (u32)s->otz >> 2; - gte_SetRotMatrix(&s->light); - gte_SetTransMatrix(&s->light); - gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, - &vb[s->idx2].pos); - gte_rtpt(); - s->reject = 0; - gte_nclip(); - gte_stopz(&s->nclip); - if (s->unk0CE == 1 && s->nclip <= 0) { - s->reject = 1; - } - if (!s->reject) { - gte_stsxy3c(s->sxy); - gte_ldv0(&vb[s->idx3].pos); - gte_rtps(); - gte_stsxy(&s->sxy[3]); - for (v = 0; v < 4; v++) { - if (s->sxy[v].vx < s->clipX0) { - s->reject = 1; - break; - } - if (s->clipX1 < s->sxy[v].vx) { - s->reject = 1; - break; - } - if (s->sxy[v].vy < s->clipY0) { - s->reject = 1; - break; - } - if (s->clipY1 < s->sxy[v].vy) { - s->reject = 1; - break; - } - s->sxy[v].vx += s->offX; - s->sxy[v].vy += s->offY; - } - } - if (!s->reject) { - gt4->tag = EFFECT_PRIM_TAG(POLY_GT4); - gt4->u0 = s->sxy[0].vx; - gt4->v0 = s->sxy[0].vy; - gt4->u1 = s->sxy[1].vx; - gt4->v1 = s->sxy[1].vy; - gt4->u2 = s->sxy[2].vx; - gt4->v2 = s->sxy[2].vy; - gt4->u3 = s->sxy[3].vx; - gt4->v3 = s->sxy[3].vy; - gt4->tpage = s->tpage; - gt4->clut = s->clut; - colour = s->unk0AC; - *(u32 *)>4->r3 = colour; - *(u32 *)>4->r2 = colour; - *(u32 *)>4->r1 = colour; - *(u32 *)>4->r0 = colour; - addPrim(&ot[s->otz], gt4); - gt4++; - } - ft4->tag = EFFECT_PRIM_TAG(POLY_FT4); - *(u32 *)&ft4->u0 = quad->uv0; - *(u32 *)&ft4->u1 = *(u32 *)&quad->uv1; - *(u16 *)&ft4->u2 = quad->uv2; - *(u16 *)&ft4->u3 = quad->uv3; - *(u32 *)&ft4->r0 = s->unk0B4; - if (quad->tpage & EFFECT_MESH_TPAGE_ABE) { - ft4->code |= 2; - } - addPrim(&ot[s->otz], ft4); - ft4++; - gte_SetRotMatrix(&s->view); - gte_SetTransMatrix(&s->view); - } - quad++; - } - ft3 = (POLY_FT3 *)ft4; - gt3 = (POLY_GT3 *)gt4; - } - render->unk00C[0] = (s32)ft3; - render->unk010[0] = gt3; - func_800B36B8(sizeof(EffectMeshScratch)); -} - -/** @brief Pose the effect's model and link its skeletons into the battle OT. */ -static void func_801A2D0C(EffectRender *render) { - BattleEffectSlot *slot = render->slot; - - func_801A181C(render, slot); - if (render->unk0DA != 0xFF) { - func_800BC420(slot->unk060); - } else if (func_800BCA3C(slot->unk060, slot->unk06C) != 0) { - func_800BCF6C(slot->unk060, slot->unk06C, render->unk0D8); - } - CompMatrix(&D_800F02C8, &render->unk014, &render->unk034); - if (!(slot->flags & BATTLE_SLOT_FLAG_UNK20)) { - *render->unk00C = func_800BC060(slot, D_800FA5E8->unk4040, 0x10, *render->unk00C); - } - func_801A1A4C(render); - func_801A1EBC(slot->mesh, D_800FA5E8->ot, 4, render); - if (slot->unk078 != NULL) { - func_801A1EBC(slot->unk078->mesh, D_800FA5E8->ot, 4, render); - } - if (render->unk0DA != 0xFF) { - func_800BC420(slot->unk060); - } -} /** * @brief Per-frame step for one impact spark. @@ -1141,7 +37,7 @@ static void func_801A2D0C(EffectRender *render) { */ static s32 func_801A2E48(EffectSpark *spark) { u32 *ot = D_800FA5E8->ot; - POLY_G3 *prim = D_801C58F4; + POLY_G3 *prim = g_effectPrimCursor; POLY_G4 *quad; DR_MODE *mode; void *next; @@ -1152,8 +48,8 @@ static s32 func_801A2E48(EffectSpark *spark) { s32 otz; s32 i; - func_801A0DF0((EffectEntity *)spark, &spark->pos); - spark->pos.vy = func_801A1550((EffectEntity *)spark); + effectSlotAnchor1((EffectEntity *)spark, &spark->pos); + spark->pos.vy = effectModelCentreY((EffectEntity *)spark); switch (spark->pc) { case 0: spark->unk038 = 0; @@ -1274,12 +170,12 @@ static s32 func_801A2E48(EffectSpark *spark) { SetDrawMode(mode, 0, 0, GetTPage(0, 1, 0x280, 0), NULL); AddPrim(&ot[otz], mode); next = mode + 1; - D_801C58F4 = next; + g_effectPrimCursor = next; } spark->unk024++; if (spark->flags & EFFECT_FLAG_STOP) { if (spark->wait == 0) { - func_801A0AD8((EffectEntity *)spark); + effectReleaseWait((EffectEntity *)spark); return 2; } } @@ -1312,9 +208,9 @@ static void func_801A351C(EffectEntity *entity) { count = 2; } for (i = 0; i < count; i++) { - spark = (EffectSpark *)func_801A0B00(&D_801D5CF0, - func_801A2E48, 0x5C, - entity); + spark = effectSpawnTask(&D_801D5CF0, + func_801A2E48, 0x5C, + entity); spark->unk056 = 8; spark->r = 0x10; spark->g = 0x10; @@ -1360,16 +256,16 @@ static void func_801A3640(EffectEntity *entity) { for (i = 0; i < count; i++) { s32 bearing = rand() & EFFECT_MESH_INDEX_MASK; EffectEntity *spark = - func_801A0B00(&D_801CD9F0, func_801A3924, 0x6C, entity); + effectSpawnTask(&D_801CD9F0, func_801A3924, 0x6C, entity); s16 base; s16 angle; - func_801A0DC0(spark, &spark->pos); + effectSlotAnchor0(spark, &spark->pos); spark->pos.vy = entity->pos.vy; - base = func_801A1408(entity); + base = effectModelTop(entity); spark->pos.vy -= (entity->pos.vy - base) * (rand() & 0x1F) / 48; angle = bearing; - spark->unk068 = func_801A138C(entity) * 3 / 2; + spark->unk068 = effectModelHalfWidth(entity) * 3 / 2; if (spark->unk068 >= 0x401) { spark->unk068 = 0x400; } @@ -1395,7 +291,7 @@ static void func_801A38A0(EffectEntity *entity) { entity->pos.vx += entity->unk058; entity->pos.vy += entity->unk05A; entity->pos.vz += entity->unk05C; - if (func_801A16E4(entity) != 0) { + if (effectStepCounter(entity) != 0) { entity->flags |= EFFECT_FLAG_STOP; entity->pc++; } @@ -1410,13 +306,13 @@ static s32 func_801A3924(EffectEntity *entity) { EffectHandler handlers[3] = { func_801A3840, func_801A38A0, func_801A391C }; handlers[entity->pc](entity); - func_801A1648(entity); + effectDrawSprite(entity); entity->unk024++; if (entity->flags & EFFECT_FLAG_STOP) { if (entity->wait != 0) { return 0; } - func_801A0AD8(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -1448,7 +344,7 @@ static void func_801A39CC(EffectEntity *entity) { count = 1; } for (i = 0; i < count; i++) { - func_801A0B00(&D_801CD9F0, func_801A3B6C, 0x6C, entity); + effectSpawnTask(&D_801CD9F0, func_801A3B6C, 0x6C, entity); } } @@ -1467,7 +363,7 @@ static void func_801A3AE8(EffectEntity *entity) { entity->pos.vx += entity->unk058; entity->pos.vy += entity->unk05A; entity->pos.vz += entity->unk05C; - if (func_801A16E4(entity) != 0) { + if (effectStepCounter(entity) != 0) { entity->flags |= EFFECT_FLAG_STOP; entity->pc++; } @@ -1482,13 +378,13 @@ static s32 func_801A3B6C(EffectEntity *entity) { EffectHandler handlers[3] = { func_801A3A74, func_801A3AE8, func_801A3B64 }; handlers[entity->pc](entity); - func_801A1648(entity); + effectDrawSprite(entity); entity->unk024++; if (entity->flags & EFFECT_FLAG_STOP) { if (entity->wait != 0) { return 0; } - func_801A0AD8(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -1503,17 +399,17 @@ static void func_801A3C14(EffectEntity *entity) { entity->flags |= EFFECT_FLAG_UNK02; /* The aim point overlays unk060/unk063, which the loop opcodes use as a counter -- the two never overlap in time. */ - func_801A0DC0(entity, (SVECTOR *)entity->unk060); - ((SVECTOR *)entity->unk060)->vy = func_801A1408(entity); - span = func_801A138C(entity); + effectSlotAnchor0(entity, (SVECTOR *)entity->unk060); + ((SVECTOR *)entity->unk060)->vy = effectModelTop(entity); + span = effectModelHalfWidth(entity); entity->unk068 = span * 2; if ((s16)(span * 2) >= 0x401) { entity->unk068 = 0x400; } - entity->unk05A = -(func_801A13E4(entity) + 0x100) / 20; + entity->unk05A = -(effectModelHeight(entity) + 0x100) / 20; entity->unk05A = entity->unk05A / 4; - func_801A1D38(&D_801D3800, &D_801D37F0, &D_801CDA00, &D_801CDA30, - &D_800EF2D0[entity->unk02D], entity->unk02D); + effectRenderReset(&D_801D3800, &D_801D37F0, &D_801CDA00, &D_801CDA30, + &D_800EF2D0[entity->unk02D], entity->unk02D); entity->flags |= EFFECT_FLAG_UNK08; slot->flags |= BATTLE_SLOT_FLAG_UNK04; entity->pc++; @@ -1533,7 +429,7 @@ static void func_801A3DCC(EffectEntity *entity) { BattleEffectSlot *slot; EffectModel *model; - if (func_801A16E4(entity) != 0) { + if (effectStepCounter(entity) != 0) { slot = &D_800EF2D0[entity->unk02D]; model = entity->unk010; entity->flags &= ~EFFECT_FLAG_UNK08; @@ -1590,21 +486,21 @@ static s32 func_801A3E64(EffectEntity *entity) { } if (entity->flags & EFFECT_FLAG_UNK08) { D_801D3800.unk0B4.vy = entity->pos.vy; - func_801A1DF4(&D_801D3800, &entity->pos, 0x200, 0x180, 0x140, 0xF1, 0x80, - 0x80); - func_801A2D0C(&D_801D3800); + effectRenderSetTexture(&D_801D3800, &entity->pos, 0x200, 0x180, 0x140, 0xF1, 0x80, + 0x80); + effectRenderModel(&D_801D3800); } if (entity->unk024 < 20) { tint.r = tint.g = tint.b = (20 - entity->unk024) * 3; - func_801A1648(entity); + effectDrawSprite(entity); func_801A172C(entity, &D_801C58B8, &tint); } else { - func_801A1648(entity); + effectDrawSprite(entity); } entity->unk024++; if (entity->flags & EFFECT_FLAG_STOP) { if (entity->wait == 0) { - func_801A0AD8(entity); + effectReleaseWait(entity); return 2; } } @@ -1613,7 +509,7 @@ static s32 func_801A3E64(EffectEntity *entity) { /** @brief Opcode handler: start a task running @ref func_801A3E64. */ static void func_801A40D4(EffectEntity *entity) { - func_801A0B00(&D_801CD9F0, func_801A3E64, 0x6C, entity); + effectSpawnTask(&D_801CD9F0, func_801A3E64, 0x6C, entity); entity->pc++; } @@ -1640,8 +536,8 @@ static s32 func_801A41C8(EffectEntity *entity) { EffectHandler handlers[4] = { func_801A40D4, func_801A4124, func_801A4138, func_801A41C0 }; - func_801A0C8C(entity); - func_801A101C(entity); + effectCacheSlotAnchors(entity); + effectUpdateModelBounds(entity); handlers[entity->pc](entity); if (entity->unk024 == 0) { func_800C4764(D_801A4678, 0, 0x80); @@ -1651,7 +547,7 @@ static s32 func_801A41C8(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A0AD8(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -1677,7 +573,7 @@ static void func_801A42CC(EffectEntity *entity) { /** @brief Opcode handler: start a task running @ref func_801A41C8. */ static void func_801A42F4(EffectEntity *entity) { entity->unk063 = 1; - func_801A0B00(&D_801C5B50, func_801A41C8, 0x58, entity); + effectSpawnTask(&D_801C5B50, func_801A41C8, 0x58, entity); entity->pc++; } @@ -1738,7 +634,7 @@ static void func_801A442C(EffectEntity *entity) { * @param entity Script whose frame is being run. * @return 2 once the script has stopped and drained, 0 while it is still live. */ -static s32 func_801A4434(EffectEntity *entity) { +s32 func_801A4434(EffectEntity *entity) { EffectHandler handlers[] = { func_801A42A4, func_801A42B8, func_801A42CC, func_801A42F4, func_801A434C, func_801A43AC, func_801A43C0, func_801A43E8, @@ -1750,13 +646,13 @@ static s32 func_801A4434(EffectEntity *entity) { D_801C58EC = world; D_801C58E8 = world; if (entity->unk05C & 1) { - D_801C58F4 = D_801C58FC; + g_effectPrimCursor = D_801C58FC; D_801C58F8 = D_801C5904; } else { - D_801C58F4 = D_801C5900; + g_effectPrimCursor = D_801C5900; D_801C58F8 = D_801C5908; } - func_801A0E50(entity); + effectCacheAnimSlotAnchors(entity); handlers[entity->pc](entity); entity->unk05E = 0; entity->unk05E += func_800B2B68(&D_801C5B50); @@ -1766,7 +662,7 @@ static s32 func_801A4434(EffectEntity *entity) { entity->unk024++; if (entity->flags & EFFECT_FLAG_STOP) { if (entity->wait == 0) { - func_801A0AD8(entity); + effectReleaseWait(entity); return 2; } } diff --git a/src/effect/effect_001_anim.c b/src/effect/effect_001_anim.c new file mode 100644 index 00000000..f994c438 --- /dev/null +++ b/src/effect/effect_001_anim.c @@ -0,0 +1,36 @@ +/** + * @file effect_001_anim.c + * @brief Cure: draw an animation frame at the entity. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/common.h" +#include "effect/effect_001_anim.h" + +/** @brief Draw @p anim at the entity's position in @p colour and link it into the OT. */ +void func_801A172C(EffectEntity *entity, BattleSpriteAnim *anim, + CVECTOR *colour) { + MATRIX m; + void **head; + BattleSpritePrim *prim; + + if (entity->flags & EFFECT_FLAG_DONE) { + return; + } + effectMatrixIdentity(&m); + effectMatrixRotX(&m, 0x400); + m.t[0] = entity->pos.vx; + m.t[1] = 0; + m.t[2] = entity->pos.vz; + CompMatrix(&D_800F02C8, &m, &m); + SetRotMatrix(&m); + SetTransMatrix(&m); + prim = func_800B3698(sizeof(BattleSpritePrim)); + head = &g_effectPrimCursor; + prim->anim = anim; + prim->frame = 0; + prim->flags = BATTLE_SPRITE_FLAG_COLOUR; + prim->colour = *colour; + *head = func_800C9E10(prim, D_800FA5E8->ot, 2, *head); + func_800B36B8(sizeof(BattleSpritePrim)); +} diff --git a/src/effect/effect_001_entry.c b/src/effect/effect_001_entry.c new file mode 100644 index 00000000..721859ff --- /dev/null +++ b/src/effect/effect_001_entry.c @@ -0,0 +1,63 @@ +/** + * @file effect_001_entry.c + * @brief Cure: the overlay entry point. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/entity.h" +#include "effect/effect_001.h" +#include "effect/effect_001_entry.h" + +/** @brief Stride between the prim and frame banks carved out of the TIM. */ +#define EFFECT_BANK_SIZE 0x10000 + +/** + * @brief Start an effect script and hand back its task pool. + * + * Builds the four task pools, allocates the root entity, seeds it from the + * animation set, and publishes the three frame banks the renderer reads from. + * + * @param animSet Animation set the effect plays. + * @return The pool the root entity lives in. + */ +void *func_801A0000(EffectAnimSet *animSet) { + u8 *bank0; + EffectEntity *entity; + u8 *bank1; + u8 *frames; + + D_801C58E0 = 0; + D_801C58E4 = 0; + func_800B2A00(&D_801C59E0, &D_801C5910, 0x64, 2); + entity = effectSpawnTask(&D_801C59E0, func_801A4434, 0x64, NULL); + entity->animSet = animSet; + entity->unk02C = animSet->anims[entity->unk02A].unk000; + entity->unk02D = + entity->animSet->anims[entity->unk02A].parts[entity->unk02B].unk000; + entity->pc = 0; + entity->unk05A = animSet->anims->unk010; + entity->unk058 = animSet->anims->unk011; + entity->unk02F = entity->unk05A - 1; + if (entity->unk02F < entity->unk058) { + entity->unk02F = entity->unk058; + } + if (!(animSet->flags & EFFECT_ANIMSET_FLAG_LOADED)) { + func_800C3BE0(&D_801A475C); + func_800BB084(&D_801A4F0C); + } + D_801C58F0 = &D_801A4F0C; + bank0 = &D_801A4F0C; + D_801C58FC = bank0; + bank1 = bank0 + EFFECT_BANK_SIZE; + D_801C5904 = bank0 + EFFECT_BANK_SIZE; + D_801C58F0 = bank0 + EFFECT_BANK_SIZE; + frames = bank1; + D_801C5900 = frames; + frames += EFFECT_BANK_SIZE; + D_801C58F0 = frames; + D_801C5908 = frames; + func_800B2A00(&D_801C5B50, &D_801C59F0, 0x58, 4); + func_800B2A00(&D_801CD9F0, &D_801C5B60, 0x6C, 0x12C); + func_800B2A00(&D_801D5CF0, &D_801D3900, 0x5C, 0x64); + return &D_801C59E0; +} diff --git a/src/effect/effect_025.c b/src/effect/effect_025.c index 8a61d440..f707faa9 100644 --- a/src/effect/effect_025.c +++ b/src/effect/effect_025.c @@ -9,69 +9,21 @@ #include "psxsdk/libgpu.h" #include "psxsdk/inline_c.h" #include "effect/effect_025.h" +#include "effect/effect_025_anim.h" +#include "effect/effect_025_motes.h" +#include "effect/effect_025_scroll.h" +#include "effect/lib/common.h" +#include "effect/lib/heading.h" +#include "effect/lib/entity.h" +#include "effect/lib/sprite.h" +#include "effect/lib/step.h" +#include "effect/lib/render.h" +#include "effect/lib/pose.h" +#include "effect/lib/interpolate.h" +#include "effect/lib/scatter.h" +#include "effect/lib/emit_mode.h" #include "btl_entity.h" -/** - * @name Pose channel flags - * - * @ref EffectPosePart::flags carries one trio per channel: the channel is - * keyed, it also carries a velocity, it also carries an acceleration. The - * trios are the translation, the rotation, the scale and the colour, in the - * order @ref func_801A6354 walks them; the top two bits are single channels - * of their own, the depth ramp and the vertex-animation cursor. - * @{ - */ -#define EFFECT_POSE_KEY0 0x1 -#define EFFECT_POSE_VEL0 0x2 -#define EFFECT_POSE_ACC0 0x4 -#define EFFECT_POSE_KEY1 0x8 -#define EFFECT_POSE_VEL1 0x10 -#define EFFECT_POSE_ACC1 0x20 -#define EFFECT_POSE_SCALE_KEY 0x40 -#define EFFECT_POSE_SCALE_VEL 0x80 -#define EFFECT_POSE_SCALE_ACC 0x100 -#define EFFECT_POSE_KEY3 0x200 -#define EFFECT_POSE_VEL3 0x400 -#define EFFECT_POSE_ACC3 0x800 -#define EFFECT_POSE_DEPTH 0x1000 -#define EFFECT_POSE_ANIM 0x2000 -/** @} */ - -/** @brief Ceiling of the depth and vertex-animation ramps: @ref ONE in 16.16. */ -#define EFFECT_POSE_RAMP_MAX 0x10000000 - -/** - * @name Scrolled UV words - * - * A UV pair is one halfword, U in the low byte and V in the high one, so a - * step of one V unit is 0x100; the third and fourth pairs of a quad share a - * word. A scroll masks V out, steps it and puts the rest back, and a step that - * carries out of the byte has wrapped. - * @{ - */ -#define EFFECT_UV_V 0xFF00 /**< V of a pair, in place. */ -#define EFFECT_UV_KEEP 0xFFFF00FF /**< What a scroll of one pair leaves alone. */ -#define EFFECT_UV_KEEP23 0x00FF00FF /**< Both U bytes of the shared third and fourth pair. */ -#define EFFECT_UV_WRAP 0x8000 /**< One V byte's worth of wrap. */ -#define EFFECT_UV_SCROLL_STEP (4 << 8) /**< The fixed scroll: four whole V units. */ -/** @} */ - -/** - * @brief Words of a mesh stream's block header ahead of the quad list's count. - * - * The scroll walkers step over the stream's own header, then this, to reach - * the count of gouraud textured quads and the quads themselves. - */ -#define EFFECT_QUAD_LIST_HEADER 7 - -/** - * @name Prim emitter flags -- @ref EffectPrimBuild::flags. - * @{ - */ -#define EFFECT_EMIT_SEMITRANS 0x1 -#define EFFECT_EMIT_OPAQUE 0x4 -#define EFFECT_EMIT_TWO_SIDED 0x10 -#define EFFECT_EMIT_DEPTH_CUE 0x40 /** * @name Per-part draw flags -- @ref EffectDrawRequest::colours * @@ -84,3476 +36,6 @@ #define EFFECT_PART_DEPTH_RAMP 0x40000000 /** @} */ -/* The gouraud emitters read their own copy of each of the four bits above. */ -#define EFFECT_EMIT_G_SEMITRANS 0x2 -#define EFFECT_EMIT_G_OPAQUE 0x8 -#define EFFECT_EMIT_G_TWO_SIDED 0x20 -#define EFFECT_EMIT_G_DEPTH_CUE 0x80 -#define EFFECT_EMIT_TPAGE_SET 0x100 -#define EFFECT_EMIT_CLUT_SET 0x200 -#define EFFECT_EMIT_TPAGE_ADD 0x400 -#define EFFECT_EMIT_CLUT_ADD 0x800 -#define EFFECT_EMIT_UNK1000 0x1000 -#define EFFECT_EMIT_UNK2000 0x2000 -/** @} */ - -/** - * @name Prim emitter clip result - * - * One bit per projected vertex per axis, accumulated in the emitters' @c reject. - * A prim is dropped only when every one of its vertices left the screen on the - * same axis, so a prim straddling an edge still draws. - * @{ - */ -#define EFFECT_CLIP_X0 0x1 -#define EFFECT_CLIP_X1 0x2 -#define EFFECT_CLIP_X2 0x4 -#define EFFECT_CLIP_X3 0x8 -#define EFFECT_CLIP_Y0 0x10 -#define EFFECT_CLIP_Y1 0x20 -#define EFFECT_CLIP_Y2 0x40 -#define EFFECT_CLIP_Y3 0x80 -#define EFFECT_CLIP_TRI_X (EFFECT_CLIP_X0 | EFFECT_CLIP_X1 | EFFECT_CLIP_X2) -#define EFFECT_CLIP_TRI_Y (EFFECT_CLIP_Y0 | EFFECT_CLIP_Y1 | EFFECT_CLIP_Y2) -#define EFFECT_CLIP_QUAD_X (EFFECT_CLIP_TRI_X | EFFECT_CLIP_X3) -#define EFFECT_CLIP_QUAD_Y (EFFECT_CLIP_TRI_Y | EFFECT_CLIP_Y3) -/** @} */ - -/** - * @name Prim emitter clip test - * - * A projected vertex is off screen once its unsigned screen coordinate reaches - * these; a prim is dropped only when all of its vertices fail the same axis. - * @{ - */ -#define EFFECT_EMIT_CLIP_X 0x141 -#define EFFECT_EMIT_CLIP_Y 0xD9 -/** @} */ - -/** @brief GTE @c FLAG bits 18 and 17: SZ3/OTZ saturated, divide overflow. */ -#define EFFECT_GTE_OUT_OF_RANGE ((1 << 18) | (1 << 17)) - -/** @brief What the prim emitters walk: the mesh stream and the GTE results. */ -typedef struct { - /* 0x00 */ s32 *stream; - /* 0x04 */ u32 *verts; - /* 0x08 */ u8 r; /**< Far colour the depth cue fades toward. */ - /* 0x09 */ u8 g; - /* 0x0A */ u8 b; - /* 0x0B */ u8 pad00B[0xC - 0xB]; - /* 0x0C */ s32 depth; /**< Depth-cue weight handed to DPCS. */ - /* 0x10 */ u16 tpage; /**< Texture page the textured prims take. */ - /* 0x12 */ u8 pad012[0x14 - 0x12]; - /* 0x14 */ u16 clut; /**< CLUT id the textured prims take. */ - /* 0x16 */ u8 pad016[0x18 - 0x16]; - /* 0x18 */ s32 unk018; - /* 0x1C */ u32 flags; - /* 0x20 */ s32 *cursor; /**< Walks the stream, one entry per prim kind. */ - /* 0x24 */ s32 nclip; - /* 0x28 */ u8 pad028[0x2C - 0x28]; - /* 0x2C */ s32 otz; - /* 0x30 */ u32 gteFlag; -} EffectPrimBuild; /* 0x34 */ - -/** @brief The flat triangle an emitter writes; six words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ u16 x1; - /* 0x0E */ u16 y1; - /* 0x10 */ u16 x2; - /* 0x12 */ u16 y2; - /* 0x14 */ u32 unk014; -} EffectTri; /* 0x18 */ - -/** @brief One flat triangle in the mesh stream: a colour and three vertex slots. */ -typedef struct { - /* 0x00 */ u32 colour; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u8 pad00A[0xC - 0xA]; -} EffectEmitTri; /* 0xC */ - -/** @brief The flat quad an emitter writes; seven words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ u16 x1; - /* 0x0E */ u16 y1; - /* 0x10 */ u16 x2; - /* 0x12 */ u16 y2; - /* 0x14 */ u16 x3; - /* 0x16 */ u16 y3; - /* 0x18 */ u32 unk018; -} EffectQuad; /* 0x1C */ - -/** @brief One flat quad in the mesh stream: a colour and four vertex slots. */ -typedef struct { - /* 0x00 */ u32 colour; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u16 idx3; -} EffectEmitQuad; /* 0xC */ - -/** - * @brief A prim's UV pair and the id that shares its word. - * - * The stream carries both halves together, so the emitters add the build's - * offset to the whole word and only then patch the id half. - */ -typedef union { - u32 word; - struct { - /* 0x00 */ u16 uv; /**< U in the low byte, V in the high. */ - /* 0x02 */ u16 id; /**< CLUT id or texture page, by slot. */ - } h; -} EffectPrimUV; /* 0x4 */ - -/** @brief The textured triangle an emitter writes; nine words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ - /* 0x10 */ u16 x1; - /* 0x12 */ u16 y1; - /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ - /* 0x18 */ u16 x2; - /* 0x1A */ u16 y2; - /* 0x1C */ EffectPrimUV uv2; - /* 0x20 */ u32 unk020; -} EffectTexTri; /* 0x24 */ - -/** @brief One textured triangle in the mesh stream. */ -typedef struct { - /* 0x00 */ u32 colour; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ - /* 0x0C */ u32 uv0; - /* 0x10 */ u32 uv1; -} EffectEmitTexTri; /* 0x14 */ - -/** @brief The gouraud triangle an emitter writes; eight words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb0; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ u32 rgb1; - /* 0x10 */ u16 x1; - /* 0x12 */ u16 y1; - /* 0x14 */ u32 rgb2; - /* 0x18 */ u16 x2; - /* 0x1A */ u16 y2; - /* 0x1C */ u32 unk01C; -} EffectGouraudTri; /* 0x20 */ - -/** @brief One gouraud triangle in the mesh stream: three corner colours. */ -typedef struct { - /* 0x00 */ u32 colour0; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u8 pad00A[0xC - 0xA]; - /* 0x0C */ u32 colour1; - /* 0x10 */ u32 colour2; -} EffectEmitGouraudTri; /* 0x14 */ - -/** @brief The gouraud quad an emitter writes; ten words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb0; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ u32 rgb1; - /* 0x10 */ u16 x1; - /* 0x12 */ u16 y1; - /* 0x14 */ u32 rgb2; - /* 0x18 */ u16 x2; - /* 0x1A */ u16 y2; - /* 0x1C */ u32 rgb3; - /* 0x20 */ u16 x3; - /* 0x22 */ u16 y3; - /* 0x24 */ u32 unk024; -} EffectGouraudQuad; /* 0x28 */ - -/** @brief One gouraud quad in the mesh stream: four corner colours. */ -typedef struct { - /* 0x00 */ u32 colour0; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u16 idx3; - /* 0x0C */ u32 colour1; - /* 0x10 */ u32 colour2; - /* 0x14 */ u32 colour3; -} EffectEmitGouraudQuad; /* 0x18 */ - -/** @brief The gouraud textured triangle an emitter writes; eleven words. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb0; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ - /* 0x10 */ u32 rgb1; - /* 0x14 */ u16 x1; - /* 0x16 */ u16 y1; - /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ - /* 0x1C */ u32 rgb2; - /* 0x20 */ u16 x2; - /* 0x22 */ u16 y2; - /* 0x24 */ EffectPrimUV uv2; - /* 0x28 */ u32 unk028; -} EffectGouraudTexTri; /* 0x2C */ - -/** @brief One gouraud textured triangle in the mesh stream. */ -typedef struct { - /* 0x00 */ u32 colour0; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ - /* 0x0C */ u32 uv0; - /* 0x10 */ u32 uv1; - /* 0x14 */ u32 colour1; - /* 0x18 */ u32 colour2; -} EffectEmitGouraudTexTri; /* 0x1C */ - -/** @brief The gouraud textured quad an emitter writes; fourteen words. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb0; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ - /* 0x10 */ u32 rgb1; - /* 0x14 */ u16 x1; - /* 0x16 */ u16 y1; - /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ - /* 0x1C */ u32 rgb2; - /* 0x20 */ u16 x2; - /* 0x22 */ u16 y2; - /* 0x24 */ EffectPrimUV uv2; - /* 0x28 */ u32 rgb3; - /* 0x2C */ u16 x3; - /* 0x2E */ u16 y3; - /* 0x30 */ EffectPrimUV uv3; - /* 0x34 */ u32 unk034; -} EffectGouraudTexQuad; /* 0x38 */ - -/** - * @brief One gouraud textured quad in the mesh stream. - * - * The last two UV pairs share a word, so the build's offset is doubled into - * both halves and the sum split again on the way out. - */ -typedef struct { - /* 0x00 */ u32 colour0; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u16 idx3; - /* 0x0C */ u32 uv0; - /* 0x10 */ u32 uv1; - /* 0x14 */ u32 uv23; - /* 0x18 */ u32 colour1; - /* 0x1C */ u32 colour2; - /* 0x20 */ u32 colour3; -} EffectEmitGouraudTexQuad; /* 0x24 */ - -/** @brief The textured quad an emitter writes; eleven words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ - /* 0x10 */ u16 x1; - /* 0x12 */ u16 y1; - /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ - /* 0x18 */ u16 x2; - /* 0x1A */ u16 y2; - /* 0x1C */ EffectPrimUV uv2; - /* 0x20 */ u16 x3; - /* 0x22 */ u16 y3; - /* 0x24 */ EffectPrimUV uv3; - /* 0x28 */ u32 unk028; -} EffectTexQuad; /* 0x2C */ - -/** @brief One textured quad in the mesh stream; its last two UV pairs share a word. */ -typedef struct { - /* 0x00 */ u32 colour; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u16 idx3; - /* 0x0C */ u32 uv0; - /* 0x10 */ u32 uv1; - /* 0x14 */ u32 uv23; -} EffectEmitTexQuad; /* 0x18 */ - -/** @brief A vertex animation: a header, then @c count posed points per frame. */ -typedef struct { - /* 0x00 */ s32 unk000; - /* 0x04 */ s32 count; - /* 0x08 */ s32 unk008; - /* 0x0C */ SVECTOR frames[1]; -} EffectVertexAnim; - -/** - * @brief One posed part, rebuilt from the pose script for every draw. - * - * @ref func_801A6354 zeroes this, walks the script into it and hands it to the - * per-part callback; the fields past @c colour are the animation cursor. - */ -typedef struct { - /* 0x00 */ s16 index; /**< The part's own slot in the pose script. */ - /* 0x02 */ s16 mesh; /**< Which of the model's meshes it draws. */ - /* 0x04 */ u32 unk004; /**< The part entry's header word; @c mesh is its low byte. */ - /* 0x08 */ s16 pos[3]; - /* 0x0E */ u8 pad00E[0x10 - 0xE]; - /* 0x10 */ s16 rot[3]; - /* 0x16 */ u8 pad016[0x18 - 0x16]; - /* 0x18 */ s16 scale[3]; - /* 0x1E */ u8 pad01E[0x20 - 0x1E]; - /* 0x20 */ CVECTOR colour; - /* 0x24 */ s16 depth; - /* 0x26 */ s16 weight; /**< Blend between @c frameA and @c frameB. */ - /* 0x28 */ s16 frameA; - /* 0x2A */ s16 frameB; -} EffectPartPose; /* 0x2C */ - -/** @brief What one pose walk draws: the matrix, its colours and its texture. */ -typedef struct { - /* 0x00 */ MATRIX *m; - /* 0x04 */ u32 *colours; - /* 0x08 */ void *unk08; - /* 0x0C */ s32 unk00C; - /* 0x10 */ s16 unk010; - /* 0x12 */ u8 pad012[0x14 - 0x12]; -} EffectDrawRequest; /* 0x14 */ - -/** @brief What @ref func_801A6354 hands each part it has posed to. */ -typedef void (*EffectPartFn)(s32 *pose, EffectPartPose *part, - EffectDrawRequest *req); - -static EffectEntity *func_801A21C0(void *pool, void *task, s32 stride, - EffectEntity *owner); -static s32 func_801A6354(s32 *pose, EffectPartFn fn, EffectDrawRequest *req); -static EffectTri *func_801A2D08(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *prim); -static EffectTri *func_801A2FE8(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *prim); -static EffectTri *func_801A3320(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *prim); -static EffectTri *func_801A36B4(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *prim); -static EffectTri *func_801A3AAC(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *prim); -static EffectTri *func_801A3DD4(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *prim); -static EffectTri *func_801A4174(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *prim); -static EffectTri *func_801A4548(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *prim); -static s32 func_801A81D0(EffectEntity *entity); - -/** - * @brief Start the effect's script and hand back its task pool. - * - * Builds the four task pools, allocates the root entity, seeds it from the - * animation set, and publishes the two prim banks the renderer draws through. - * - * @param animSet Animation set the effect plays. - * @return The pool the root entity lives in. - */ -void *func_801A0000(EffectAnimSet *animSet) { - EffectEntity *entity; - u8 slot; - - slot = animSet->anims->parts->unk000; - func_800B2A00(&D_801D3F94, &D_801D3EC4, 0x64, 2); - entity = func_801A21C0(&D_801D3F94, func_801A81D0, 0x64, NULL); - entity->animSet = animSet; - entity->unk02D = slot; - entity->unk05A = animSet->anims->unk010; - entity->unk058 = animSet->anims->unk011; - if (!(animSet->flags & EFFECT_ANIMSET_FLAG_LOADED)) { - func_800C3BE0(&D_801A8E50); - func_800BB084(&D_801A9698); - } - D_801D3EBC = &D_801A9698; - D_801D3EC0 = &D_801BB698; - func_800B2A00(&D_801D4104, &D_801D3FA4, 0x58, 4); - func_800B2A00(&D_801D7314, &D_801D4114, 0xC8, 0x40); - func_800B2A00(&D_801E74F4, &D_801D7324, 0x84, 0x1F4); - return &D_801D3F94; -} - -/** - * @brief Cache the sixteen hex digit glyphs and reset the debug text cursor. - * - * @note The lookup runs once per glyph -- the target holds 16 separate calls, - * so a loop or a cached pointer does not match. - */ -static void func_801A014C(void) { - D_801ED404[0] = getMenuString(11)[1]; - D_801ED404[1] = getMenuString(11)[2]; - D_801ED404[2] = getMenuString(11)[3]; - D_801ED404[3] = getMenuString(11)[4]; - D_801ED404[4] = getMenuString(11)[5]; - D_801ED404[5] = getMenuString(11)[6]; - D_801ED404[6] = getMenuString(11)[7]; - D_801ED404[7] = getMenuString(11)[8]; - D_801ED404[8] = getMenuString(11)[9]; - D_801ED404[9] = getMenuString(11)[10]; - D_801ED404[10] = getMenuString(11)[11]; - D_801ED404[11] = getMenuString(11)[12]; - D_801ED404[12] = getMenuString(11)[13]; - D_801ED404[13] = getMenuString(11)[14]; - D_801ED404[14] = getMenuString(11)[15]; - D_801ED404[15] = getMenuString(11)[16]; - D_801ED404[16] = 0; - D_801ED418 = 10; - D_801ED41C = 20; - D_801ED420 = 3; -} - -/** @brief Draw @p value as eight hex glyphs at the current debug text cursor. */ -static void func_801A029C(u32 value) { - u32 *ot = D_800FA5E8->frontOT; - u8 text[9]; - s32 i; - - for (i = 7; i >= 0; i--) { - text[i] = D_801ED404[value & 0xF]; - value >>= 4; - } - text[8] = 0; - D_801D3EB8 = func_8002C56C(ot, D_801D3EB8, D_801ED418, D_801ED41C, - text, D_801ED420); - D_801ED418 += 0x48; -} - -/** @brief Opcode handler: arm the frame delay and advance the running total. */ -static void func_801A0348(void) { - D_801ED418 = 10; - D_801ED41C += 10; -} - -/** @brief Opcode handler: no-op. */ -static void func_801A0368(EffectEntity *entity) { -} - -/** - * @brief Emit a 128x128 textured quad at the head of the prim buffer. - * - * @param x Left edge in screen space. - * @param y Top edge in screen space. - * @param tx Texture page X. - * @param ty Texture page Y. - * @param clutX Palette X. - * @param clutY Palette Y. - * @param abr Semi-transparency rate for the page. - */ -static void func_801A0370(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, - s32 abr) { - POLY_FT4 *poly = D_801D3EB8; - - setPolyFT4(poly); - setSemiTrans(poly, 1); - setShadeTex(poly, 1); - poly->tpage = getTPage(1, abr, tx, ty); - poly->clut = getClut(clutX, clutY); - poly->u0 = poly->u2 = 0; - poly->u1 = poly->u3 = 0xFF; - poly->v0 = poly->v1 = 0; - poly->v2 = poly->v3 = 0xFF; - poly->x0 = poly->x2 = x; - poly->x1 = poly->x3 = x + 0x7F; - poly->y0 = poly->y1 = y; - poly->y2 = poly->y3 = y + 0x7F; - AddPrim(D_800FA5E8, poly); - poly++; - D_801D3EB8 = poly; -} - -/** - * @brief Emit a 256x256 textured quad at the head of the prim buffer. - * - * @param x Left edge in screen space. - * @param y Top edge in screen space. - * @param tx Texture page X. - * @param ty Texture page Y. - * @param clutX Palette X. - * @param clutY Palette Y. - * @param abr Semi-transparency rate for the page. - */ -static void func_801A046C(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, - s32 abr) { - POLY_FT4 *poly = D_801D3EB8; - - setPolyFT4(poly); - setSemiTrans(poly, 1); - setShadeTex(poly, 1); - poly->tpage = getTPage(1, abr, tx, ty); - poly->clut = getClut(clutX, clutY); - poly->u0 = poly->u2 = 0; - poly->u1 = poly->u3 = 0xFF; - poly->v0 = poly->v1 = 0; - poly->v2 = poly->v3 = 0xFF; - poly->x0 = poly->x2 = x; - poly->x1 = poly->x3 = x + 0xFF; - poly->y0 = poly->y1 = y; - poly->y2 = poly->y3 = y + 0xFF; - AddPrim(D_800FA5E8, poly); - poly++; - D_801D3EB8 = poly; -} - -/** @brief Load the identity matrix. */ -static void func_801A0568(MATRIX *m) { - m->m[0][0] = ONE; - m->m[1][0] = 0; - m->m[2][0] = 0; - m->m[0][1] = 0; - m->m[1][1] = ONE; - m->m[2][1] = 0; - m->m[0][2] = 0; - m->m[1][2] = 0; - m->m[2][2] = ONE; - m->t[0] = 0; - m->t[1] = 0; - m->t[2] = 0; -} - -/** @brief Post-multiply @p m by a rotation of @p angle about X. */ -static void func_801A05A0(MATRIX *m, s32 angle) { - EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); - - rot->sin = rsin(angle); - rot->cos = rsin((angle + 0x400) & 0xFFF); - rot->m.m[0][0] = ONE; - rot->m.m[0][1] = 0; - rot->m.m[0][2] = 0; - rot->m.m[1][0] = 0; - rot->m.m[1][1] = rot->cos; - rot->m.m[1][2] = -rot->sin; - rot->m.m[2][0] = 0; - rot->m.m[2][1] = rot->sin; - rot->m.m[2][2] = rot->cos; - gte_MulMatrix0(m, &rot->m, m); - func_800B36B8(sizeof(EffectRotScratch)); -} - -/** - * @brief Compose a rotation about the Y axis into @p m. - * - * @param m Matrix rotated in place. - * @param angle Rotation, in the 0x1000-per-turn units @ref rsin takes. - */ -static void func_801A0730(MATRIX *m, s32 angle) { - EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); - - rot->sin = rsin(angle); - rot->cos = rsin((angle + 0x400) & 0xFFF); - rot->m.m[0][0] = rot->cos; - rot->m.m[0][1] = 0; - rot->m.m[0][2] = rot->sin; - rot->m.m[1][0] = 0; - rot->m.m[1][1] = ONE; - rot->m.m[1][2] = 0; - rot->m.m[2][0] = -rot->sin; - rot->m.m[2][1] = 0; - rot->m.m[2][2] = rot->cos; - gte_MulMatrix0(m, &rot->m, m); - func_800B36B8(sizeof(EffectRotScratch)); -} - -/** @brief Build a matrix in @p m that aims along @p dir with @p up as the roll. */ -static void func_801A08BC(MATRIX *m, SVECTOR *dir, SVECTOR *up) { - MATRIX *basis = func_800B3698(sizeof(MATRIX)); - SVECTOR *row1 = (SVECTOR *)basis->m[1]; - SVECTOR *row2 = (SVECTOR *)basis->m[2]; - - /* A matrix row is 6 bytes, so the whole-vector store spills one halfword - into row 2 -- harmless, row 2 is written next. The width is load-bearing: - it is the unaligned 8-byte copy the target does here, where the - element-wise stores below are three halfwords. */ - *row1 = *up; - row2->vx = dir->vx; - row2->vy = dir->vy; - row2->vz = dir->vz; - gte_ldopv1SV(row1); - gte_ldopv2SV(row2); - gte_op12(); - gte_stsv(basis->m[0]); - MatrixNormal_2(basis, basis); - TransposeMatrix(basis, m); - func_800B36B8(sizeof(MATRIX)); -} - -/** @brief Aim @p m along the vector from @p from to @p to, Y up. */ -static void func_801A09C8(MATRIX *m, SVECTOR *from, SVECTOR *to) { - SVECTOR up; - SVECTOR delta; - - up.vx = 0; - up.vy = ONE; - up.vz = 0; - delta.vx = to->vx - from->vx; - delta.vy = to->vy - from->vy; - delta.vz = to->vz - from->vz; - func_801A08BC(m, &delta, &up); -} - -/** @brief The heading @p m faces, a quarter turn ahead of its Z axis. */ -static s32 func_801A0A34(MATRIX *m) { - return (ratan2(m->m[2][2], m->m[0][2]) + 0x400) & 0xFFF; -} - -/** @brief Push @p entity to a random point within @p radius of where it is. */ -static void func_801A0A64(EffectEntity *entity, s16 radius) { - MATRIX m; - SVECTOR angles; - SVECTOR in; - SVECTOR out; - - if (radius == 0) { - radius = 1; - } - angles.vy = rand() & 0xFFF; - angles.vx = rand() & 0xFFF; - func_801A0568(&m); - func_801A0730(&m, angles.vy); - func_801A05A0(&m, angles.vx); - in.vx = 0; - in.vy = 0; - in.vz = rand() % radius; - ApplyMatrixSV(&m, &in, &out); - entity->pos.vx += out.vx; - entity->pos.vy += out.vy; - entity->pos.vz += out.vz; -} - -/** @brief Refresh the render matrices from @p pose and apply the offset. */ -static void func_801A0B6C(EffectRender *render, BattleEffectSlot *slot) { - SVECTOR offset; - - render->unk014 = slot->mtx; - render->unk054 = render->unk014; - switch (render->unk0D2) { - case 0: - ApplyMatrixSV(&render->unk054, &render->unk0B4, &offset); - render->unk054.t[0] += offset.vx; - render->unk054.t[1] += offset.vy; - render->unk054.t[2] += offset.vz; - break; - case 1: - render->unk054.t[0] += render->unk0B4.vx; - render->unk054.t[1] += render->unk0B4.vy; - render->unk054.t[2] += render->unk0B4.vz; - break; - } -} - -/** @brief Compose each joint's matrix through the pose and the per-joint offset. */ -static void func_801A0CB0(EffectSkeletonRef *ref, EffectPose *pose) { - MATRIX *offset = D_801ED424; - EffectSkeleton *skeleton = ref->mesh->skeleton; - EffectJoint *joint = skeleton->joints; - s32 i; - - for (i = 0; i < skeleton->count; i++) { - CompMatrix(&pose->world, &joint->mtx, offset); - CompMatrix(&pose->view, &joint->mtx, &joint->mtx); - joint++; - offset++; - } -} - -/** @brief Rebase a table of absolute pointers into offsets from its own head. */ -static void func_801A0D5C(s32 *table) { - s32 *base; - s32 count; - s32 i; - - base = table; - count = *table; - for (i = 0; i < count; i++) { - table++; - *table -= (s32)base; - } -} - -/** @brief Aim the render's model at the camera reference point. */ -static void func_801A0D9C(EffectRender *render) { - EffectAimScratch *a = func_800B3698(sizeof(EffectAimScratch)); - s32 dx; - s32 dy; - s32 dz; - - a->pos.vx = render->unk054.t[0]; - a->pos.vy = render->unk054.t[1]; - a->pos.vz = render->unk054.t[2]; - dx = a->pos.vx - render->unk0DC; - a->delta.vx = dx; - dy = a->pos.vy - render->unk0DE; - a->delta.vy = dy; - dz = a->pos.vz - render->unk0E0; - a->delta.vz = dz; - a->distSq = dx * dx + dy * dy + dz * dz; - a->dist = SquareRoot0(a->distSq); - a->up.vx = 0; - a->up.vy = ONE; - a->up.vz = 0; - VectorNormalS(&a->delta, &a->dir); - func_801A08BC(&a->rot, &a->dir, &a->up); - TransposeMatrix(&a->rot, &a->out); - a->out.t[0] = 0; - a->out.t[1] = 0; - a->out.t[2] = render->scale; - a->rot = render->unk014; - a->rot.t[0] = render->unk014.t[0] - render->unk054.t[0]; - a->rot.t[1] = render->unk014.t[1] - render->unk054.t[1]; - a->rot.t[2] = render->unk014.t[2] - render->unk054.t[2]; - gte_MulMatrix0(&a->out, &a->rot, &render->unk074); - gte_SetTransMatrix(&a->out); - gte_ldlv0(a->rot.t); - gte_mvmva(1, 0, 0, 0, 0); - gte_stlvnl(render->unk074.t); - func_800B36B8(sizeof(EffectAimScratch)); -} - -/** @brief Reset a render request to its default pose, white, and unit scale. */ -static void func_801A1088(EffectRender *render, void *pose, EffectRenderPart *part, - void *texture, BattleEffectSlot *source, s32 frame) { - render->unk00C = &D_800FA5F0; - render->unk000 = pose; - render->part = part; - render->unk010 = &D_801D3EB8; - render->unk0B4.vx = 0; - render->unk0B4.vy = 0; - render->unk0B4.vz = 0; - render->r = 0x80; - render->g = 0x80; - render->b = 0x80; - render->scale = 0x2000; - render->unk0D2 = 1; - render->unk0CE = 0; - render->unk0F2 = 0; - render->unk0A4.vx = 0; - render->unk0A4.vy = 0; - render->unk0A4.vz = 0; - render->unk0AC.vx = 0; - render->unk0AC.vy = 0; - render->unk0AC.vz = 0; - render->unk0BC.vx = 0; - render->unk0BC.vy = 0; - render->unk0BC.vz = 0; - render->slot = source; - render->unk0DA = frame; - part->unk018 = 0x140; - part->unk004 = texture; - part->unk014 = 0; - part->unk016 = 0; - part->unk01A = 0; - part->r = 0x80; - part->g = 0x80; - part->b = 0x80; - part->unk020 = -1; - part->unk024 = 0; -} - -/** @brief Point a render request at a texture page, CLUT and source rectangle. */ -static void func_801A1144(EffectRender *render, SVECTOR *pos, s16 tx, s16 ty, - s16 clutX, s16 clutY, s16 w, s16 h) { - POLY_FT3 poly; - - render->unk0DC = pos->vx; - render->unk0DE = pos->vy; - render->unk0E0 = pos->vz; - setPolyFT3(&poly); - setSemiTrans(&poly, 1); - setShadeTex(&poly, 1); - poly.tpage = getTPage(1, 1, tx, ty); - poly.clut = getClut(clutX, clutY); - render->tpage = poly.tpage; - render->clut = poly.clut; - render->unk0E4 = w; - render->unk0E6 = h; - /* The u coordinate within the page, doubled for the 4bpp texture. */ - render->unk0E8 = ((s16)tx - (s16)(tx & ~0x3F)) * 2; - render->unk0EA = ty & 0xFF; -} - -/** - * @brief Draw one mesh: pose its vertices by joint, then emit its triangles and - * quads into the ordering table. - * - * Each part of the mesh is a command stream. Its first section lists vertex - * groups, each posed through one joint of the skeleton with the GTE's light - * matrix (the part's vertices are normals: @c mvmva against the loaded joint). - * The aligned tail holds a triangle list and a quad list. Every primitive is - * transformed twice: once with the view matrix, which gives the screen - * coordinates and depth for BOTH the shaded prim (whose UVs are the screen - * coordinates -- an environment map) and the textured prim; then with the - * light matrix, whose winding decides the back-face cull when the render asks - * for it. A primitive is also rejected when any projected vertex leaves the - * clip rectangle, in which case only the textured prim is emitted. - * - * The prim cursors come from and return to the render's two list heads; the - * scratch record lives in the battle scratchpad for the duration. - * - * @param mesh Skeleton and part table to draw. - * @param ot Ordering table the prims are linked into. - * @param mode Unused. - * @param render Render state: matrices, colour, texture page and clip rect. - */ -static void func_801A120C(EffectMesh *mesh, u32 *ot, s32 mode, - EffectRender *render) { - EffectMeshVertex *vbuf = render->part->unk004; - EffectJoint *joints = mesh->skeleton->joints; - u32 *parts = mesh->parts; - s32 count = parts[0]; - /* battle.bin keeps this list head as a word (see D_800FA5F0). */ - POLY_FT3 *ft3 = (POLY_FT3 *)render->unk00C[0]; - POLY_GT3 *gt3 = render->unk010[0]; - BattleEffectSlot *slot = render->slot; - EffectMeshScratch *s = func_800B3698(sizeof(EffectMeshScratch)); - s32 part; - POLY_FT4 *ft4; - POLY_GT4 *gt4; - u32 colour; - - s->visible = slot->unk07C; - s->unk0CE = render->unk0F4; - s->tpage = render->tpage; - s->clut = render->clut; - s->clipX0 = -(render->unk0E4 / 2); - s->clipX1 = render->unk0E4 / 2 - 1; - s->clipY0 = -(render->unk0E6 / 2); - s->clipY1 = render->unk0E6 / 2 - 1; - s->clipX0 += EFFECT_SCREEN_CX; - s->clipX1 += EFFECT_SCREEN_CX; - s->clipY0 += EFFECT_SCREEN_CY; - s->clipY1 += EFFECT_SCREEN_CY; - s->offX = render->unk0E8 + render->unk0E4 / 2 - EFFECT_SCREEN_CX; - s->offY = render->unk0EA + render->unk0E6 / 2 - EFFECT_SCREEN_CY; - /* The shaded prims are semi-transparent (code bit 2), the textured are not. */ - s->colour = *(u32 *)&render->r & EFFECT_PRIM_RGB; - s->unk0AC = s->colour | EFFECT_PRIM_CODE(0x3C | 0x02); - s->colour = s->colour | EFFECT_PRIM_CODE(0x34 | 0x02); - s->unk0B0 = slot->unk028 & EFFECT_PRIM_RGB; - s->unk0B4 = s->unk0B0 | EFFECT_PRIM_CODE(0x2C); - s->unk0B0 = s->unk0B0 | EFFECT_PRIM_CODE(0x24); - s->view = render->unk034; - s->light = render->unk074; - s->unk0A4 = render->unk0CE; - s->unk0A6 = render->unk0CC; - parts++; - gte_SetRotMatrix(&s->view); - gte_SetTransMatrix(&s->view); - for (part = 0; part < count; part++) { - EffectMeshVertex *vb = vbuf; - s16 *stream = (s16 *)((u8 *)mesh->parts + *parts++); - s32 groups; - s32 g; - s32 n; - s32 v; - s32 tris; - EffectMeshTri *tri; - MATRIX *jm; - EffectMeshQuad *quad; - s32 quads; - POLY_GT3 *gt; - POLY_FT3 *ft; - - if (!((s->visible >> part) & 1)) { - continue; - } - groups = *stream++; - for (g = 0; g < groups; g++) { - jm = &joints[*stream++].mtx; - gte_SetLightMatrix(jm); - gte_ldbkdir(jm->t[0], jm->t[1], jm->t[2]); - n = *stream++; - for (v = 0; v < n; v++) { - s->normal.vx = *stream++; - s->normal.vy = *stream++; - s->normal.vz = *stream++; - gte_ldv0(&s->normal); - gte_mvmva(1, 1, 0, 1, 0); - gte_stsv(&vb->pos); - vb++; - } - } - stream = (s16 *)(((u32)stream + 3) & ~3); - tris = *stream++; - quads = *stream++; - stream += 4; - tri = (EffectMeshTri *)stream; - vb = vbuf; - gt = gt3; - ft = ft3; - for (g = 0; g < tris; g++) { - s->idx0 = tri->idx0 & EFFECT_MESH_INDEX_MASK; - s->idx1 = tri->idx1 & EFFECT_MESH_INDEX_MASK; - s->idx2 = tri->idx2 & EFFECT_MESH_INDEX_MASK; - gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, &vb[s->idx2].pos); - gte_rtpt(); - gte_nclip(); - gte_stopz(&s->nclip); - if (s->nclip > 0) { - gte_avsz3(); - gte_stotz(&s->otz); - s->otz = (u32)s->otz >> 2; - gte_stsxy3_gt3(gt); - gte_stsxy3_ft3(ft); - gte_SetRotMatrix(&s->light); - gte_SetTransMatrix(&s->light); - gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, - &vb[s->idx2].pos); - gte_rtpt(); - s->reject = 0; - gte_nclip(); - gte_stopz(&s->nclip); - if (s->unk0CE == 1 && s->nclip <= 0) { - s->reject = 1; - } - if (!s->reject) { - gte_stsxy3c(s->sxy); - for (v = 0; v < 3; v++) { - if (s->sxy[v].vx < s->clipX0) { - s->reject = 1; - break; - } - if (s->clipX1 < s->sxy[v].vx) { - s->reject = 1; - break; - } - if (s->sxy[v].vy < s->clipY0) { - s->reject = 1; - break; - } - if (s->clipY1 < s->sxy[v].vy) { - s->reject = 1; - break; - } - s->sxy[v].vx += s->offX; - s->sxy[v].vy += s->offY; - } - } - if (!s->reject) { - gt->tag = EFFECT_PRIM_TAG(POLY_GT3); - gt->u0 = s->sxy[0].vx; - gt->v0 = s->sxy[0].vy; - gt->u1 = s->sxy[1].vx; - gt->v1 = s->sxy[1].vy; - gt->u2 = s->sxy[2].vx; - gt->v2 = s->sxy[2].vy; - gt->tpage = s->tpage; - gt->clut = s->clut; - /* Colour and code as one word; four byte stores do not match. */ - colour = s->colour; - *(u32 *)>->r2 = colour; - *(u32 *)>->r1 = colour; - *(u32 *)>->r0 = colour; - addPrim(&ot[s->otz], gt); - gt++; - } - ft->tag = EFFECT_PRIM_TAG(POLY_FT3); - *(u32 *)&ft->u0 = tri->uv0; - *(u32 *)&ft->u1 = *(u32 *)&tri->uv1; - *(u16 *)&ft->u2 = tri->uv2; - *(u32 *)&ft->r0 = s->unk0B0; - if (tri->tpage & EFFECT_MESH_TPAGE_ABE) { - ft->code |= 2; - } - addPrim(&ot[s->otz], ft); - ft++; - gte_SetRotMatrix(&s->view); - gte_SetTransMatrix(&s->view); - } - tri++; - } - quad = (EffectMeshQuad *)tri; - ft4 = (POLY_FT4 *)ft; - gt4 = (POLY_GT4 *)gt; - for (g = 0; g < quads; g++) { - s->idx0 = quad->idx0 & EFFECT_MESH_INDEX_MASK; - s->idx1 = quad->idx1 & EFFECT_MESH_INDEX_MASK; - s->idx2 = quad->idx2 & EFFECT_MESH_INDEX_MASK; - gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, &vb[s->idx2].pos); - gte_rtpt(); - gte_nclip(); - gte_stopz(&s->nclip); - if (s->nclip > 0) { - gte_stsxy3_gt3(gt4); - gte_stsxy3_ft3(ft4); - s->idx3 = quad->idx3 & EFFECT_MESH_INDEX_MASK; - gte_ldv0(&vb[s->idx3].pos); - gte_rtps(); - gte_stsxy(>4->x3); - gte_stsxy(&ft4->x3); - gte_avsz4(); - gte_stotz(&s->otz); - s->otz = (u32)s->otz >> 2; - gte_SetRotMatrix(&s->light); - gte_SetTransMatrix(&s->light); - gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, - &vb[s->idx2].pos); - gte_rtpt(); - s->reject = 0; - gte_nclip(); - gte_stopz(&s->nclip); - if (s->unk0CE == 1 && s->nclip <= 0) { - s->reject = 1; - } - if (!s->reject) { - gte_stsxy3c(s->sxy); - gte_ldv0(&vb[s->idx3].pos); - gte_rtps(); - gte_stsxy(&s->sxy[3]); - for (v = 0; v < 4; v++) { - if (s->sxy[v].vx < s->clipX0) { - s->reject = 1; - break; - } - if (s->clipX1 < s->sxy[v].vx) { - s->reject = 1; - break; - } - if (s->sxy[v].vy < s->clipY0) { - s->reject = 1; - break; - } - if (s->clipY1 < s->sxy[v].vy) { - s->reject = 1; - break; - } - s->sxy[v].vx += s->offX; - s->sxy[v].vy += s->offY; - } - } - if (!s->reject) { - gt4->tag = EFFECT_PRIM_TAG(POLY_GT4); - gt4->u0 = s->sxy[0].vx; - gt4->v0 = s->sxy[0].vy; - gt4->u1 = s->sxy[1].vx; - gt4->v1 = s->sxy[1].vy; - gt4->u2 = s->sxy[2].vx; - gt4->v2 = s->sxy[2].vy; - gt4->u3 = s->sxy[3].vx; - gt4->v3 = s->sxy[3].vy; - gt4->tpage = s->tpage; - gt4->clut = s->clut; - colour = s->unk0AC; - *(u32 *)>4->r3 = colour; - *(u32 *)>4->r2 = colour; - *(u32 *)>4->r1 = colour; - *(u32 *)>4->r0 = colour; - addPrim(&ot[s->otz], gt4); - gt4++; - } - ft4->tag = EFFECT_PRIM_TAG(POLY_FT4); - *(u32 *)&ft4->u0 = quad->uv0; - *(u32 *)&ft4->u1 = *(u32 *)&quad->uv1; - *(u16 *)&ft4->u2 = quad->uv2; - *(u16 *)&ft4->u3 = quad->uv3; - *(u32 *)&ft4->r0 = s->unk0B4; - if (quad->tpage & EFFECT_MESH_TPAGE_ABE) { - ft4->code |= 2; - } - addPrim(&ot[s->otz], ft4); - ft4++; - gte_SetRotMatrix(&s->view); - gte_SetTransMatrix(&s->view); - } - quad++; - } - ft3 = (POLY_FT3 *)ft4; - gt3 = (POLY_GT3 *)gt4; - } - render->unk00C[0] = (s32)ft3; - render->unk010[0] = gt3; - func_800B36B8(sizeof(EffectMeshScratch)); -} - -/** @brief Pose the effect's model and link its skeletons into the battle OT. */ -static void func_801A205C(EffectRender *render) { - BattleEffectSlot *slot = render->slot; - - func_801A0B6C(render, slot); - if (render->unk0DA != 0xFF) { - func_800BC420(slot->unk060); - } else if (func_800BCA3C(slot->unk060, slot->unk06C) != 0) { - func_800BCF6C(slot->unk060, slot->unk06C, render->unk0D8); - } - CompMatrix(&D_800F02C8, &render->unk014, &render->unk034); - if (!(slot->flags & BATTLE_SLOT_FLAG_UNK20)) { - *render->unk00C = func_800BC060(slot, D_800FA5E8->unk4040, 0x10, *render->unk00C); - } - func_801A0D9C(render); - func_801A120C(slot->mesh, D_800FA5E8->ot, 4, render); - if (slot->unk078 != NULL) { - func_801A120C(slot->unk078->mesh, D_800FA5E8->ot, 4, render); - } - if (render->unk0DA != 0xFF) { - func_800BC420(slot->unk060); - } -} - -/** @brief Opcode handler: release one frame of the linked script's wait. */ -static void func_801A2198(EffectEntity *entity) { - if (entity->unk018 != NULL) { - entity->unk018->wait--; - } -} - -/** - * @brief Allocate an effect entity from @p pool and hang it off @p owner. - * - * The pool keeps its own 12-byte task header ahead of the entity fields, so - * only the remainder of @p stride is cleared. A child inherits the owner's - * model, animation set and position; an entity with no owner becomes its own - * model. - * - * @param pool Task pool to allocate from. - * @param task Per-frame step to install on the new entity. - * @param stride Total size of the allocation, header included. - * @param owner Parent entity, or NULL for a root. - * @return The new entity, or NULL if the pool is full. - */ -static EffectEntity *func_801A21C0(void *pool, void *task, s32 stride, - EffectEntity *owner) { - EffectEntity *node = func_800B2A84(pool, task); - s32 *clear; - s32 words; - s32 i; - - if (node == NULL) { - return NULL; - } - clear = (s32 *)&node->animSet; - stride = stride - 0xC; - words = stride / 4; - for (i = 0; i < words; i++) { - *clear = 0; - clear++; - } - if (owner == NULL) { - node->unk018 = NULL; - node->unk014 = NULL; - /* A root drives its own geometry, so it is its own model. */ - node->unk010 = (EffectModel *)node; - } else { - if (owner->unk014 == NULL) { - node->unk014 = (EffectModel *)node; - } else { - node->unk014 = owner->unk014; - } - node->unk018 = owner; - node->unk010 = owner->unk010; - node->animSet = owner->animSet; - node->pos = owner->pos; - node->unk02A = owner->unk02A; - node->unk02B = owner->unk02B; - node->unk02E = owner->unk02E; - node->unk02F = owner->unk02F; - owner->wait++; - node->unk02C = node->animSet->anims[node->unk02A].unk000; - node->unk02D = - node->animSet->anims[node->unk02A].parts[node->unk02B].unk000; - } - return node; -} - -/** @brief Cache the battle slot's two anchor points and their midpoint. */ -static void func_801A234C(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - SVECTOR top; - SVECTOR bottom; - - if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { - return; - } - func_800B3960(slot, 0xF0, 0, &top); - func_800B3960(slot, 0xF1, 0, &bottom); - entity->unk040.vx = top.vx; - entity->unk040.vy = top.vy; - entity->unk040.vz = top.vz; - entity->unk038.vx = (top.vx + bottom.vx) / 2; - entity->unk038.vy = (top.vy + bottom.vy) / 2; - entity->unk038.vz = (top.vz + bottom.vz) / 2; - entity->unk030.vx = bottom.vx; - entity->unk030.vy = slot->unk024; - entity->unk030.vz = bottom.vz; -} - -/** @brief Copy @c unk030 of the linked model out to @p out. */ -static void func_801A2480(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk014->unk030; -} - -/** @brief Copy @c unk038 of the linked model out to @p out. */ -static void func_801A24B0(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk014->unk038; -} - -/** @brief Copy @c unk040 of the linked model out to @p out. */ -static void func_801A24E0(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk014->unk040; -} - -/** @brief As @ref func_801A234C, but for the slot the animation set names. */ -static void func_801A2510(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->animSet->slot]; - SVECTOR top; - SVECTOR bottom; - - if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { - return; - } - func_800B3960(slot, 0xF0, 0, &top); - func_800B3960(slot, 0xF1, 0, &bottom); - entity->unk040.vx = top.vx; - entity->unk040.vy = top.vy; - entity->unk040.vz = top.vz; - entity->unk038.vx = (top.vx + bottom.vx) / 2; - entity->unk038.vy = (top.vy + bottom.vy) / 2; - entity->unk038.vz = (top.vz + bottom.vz) / 2; - entity->unk030.vx = bottom.vx; - entity->unk030.vy = slot->unk024; - entity->unk030.vz = bottom.vz; -} - -/** @brief Copy @c unk030 of the linked model out to @p out. */ -static void func_801A264C(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk010->unk030; -} - -/** @brief Copy @c unk038 of the linked model out to @p out. */ -static void func_801A267C(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk010->unk038; -} - -/** @brief Copy @c unk040 of the linked model out to @p out. */ -static void func_801A26AC(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk010->unk040; -} - -/** - * @brief Recompute the model's bounding box from its posed skeleton. - * - * Transforms every joint after the root through the GTE and keeps the running - * minimum and maximum, then publishes the box back onto the entity. - * - * @param entity Entity whose model is measured. - */ -static void func_801A26DC(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - EffectSkeleton *skeleton = slot->mesh->skeleton; - EffectJoint *joint = skeleton->joints; - EffectBoundsScratch *b = func_800B3698(sizeof(EffectBoundsScratch)); - SVECTOR *bounds; - s32 i; - s16 x = joint->mtx.t[0]; - s16 y; - s16 z; - - b->max.vx = x; - b->min.vx = x; - y = joint->mtx.t[1]; - b->max.vy = y; - b->min.vy = y; - z = joint->mtx.t[2]; - b->max.vz = z; - b->min.vz = z; - b->v.vx = 0; - b->v.vy = 0; - joint = &skeleton->joints[1]; - for (i = 1; i < skeleton->count; i++) { - SetRotMatrix(&joint->mtx); - SetTransMatrix(&joint->mtx); - b->v.vz = -joint->unk002; - gte_ldv0(&b->v); - gte_mvmva(1, 0, 0, 0, 0); - gte_stlvnl(&b->pos); - gte_stflg(&b->flag); - if (b->pos.vx < b->min.vx) { - b->min.vx = b->pos.vx; - } else if (b->max.vx < b->pos.vx) { - b->max.vx = b->pos.vx; - } - if (b->pos.vy < b->min.vy) { - b->min.vy = b->pos.vy; - } else if (b->max.vy < b->pos.vy) { - b->max.vy = b->pos.vy; - } - if (b->pos.vz < b->min.vz) { - b->min.vz = b->pos.vz; - } else if (b->max.vz < b->pos.vz) { - b->max.vz = b->pos.vz; - } - joint++; - } - /* The box overlays unk048/unk04C, which the script opcodes use as a table - pointer -- the two never overlap in time. */ - bounds = (SVECTOR *)entity->unk048; - bounds[0] = b->min; - bounds[1] = b->max; - func_800B36B8(sizeof(EffectBoundsScratch)); -} - -/** @brief Copy the linked model's bounding box out to @p min and @p max. */ -static void func_801A2928(EffectEntity *entity, SVECTOR *min, SVECTOR *max) { - EffectModel *model = entity->unk014; - - *min = model->boundsMin; - *max = model->boundsMax; -} - -/** - * @brief Half the longest side of the model's bounding box. - * - * @param entity Entity whose model is measured. - * @return Half the largest of the box's three extents. - */ -static s32 func_801A2978(EffectEntity *entity) { - EffectModel *model = entity->unk014; - s16 dx = model->boundsMax.vx - model->boundsMin.vx; - s16 dy = model->boundsMax.vy - model->boundsMin.vy; - s16 dz = model->boundsMax.vz - model->boundsMin.vz; - if (dx > dy) { - if (dx > dz) { - return dx / 2; - } - return dz / 2; - } - if (dy > dz) { - return dy / 2; - } - return dz / 2; -} - -/** @brief Half the height of the linked model's bounding box. */ -static s16 func_801A2A1C(EffectEntity *entity) { - EffectModel *model = entity->unk014; - - return (s16)(model->boundsMax.vy - model->boundsMin.vy) / 2; -} - -/** @brief Half the linked model's larger horizontal extent. */ -static s16 func_801A2A4C(EffectEntity *entity) { - EffectModel *model = entity->unk014; - s16 spanX = model->boundsMax.vx - model->boundsMin.vx; - s16 spanZ = model->boundsMax.vz - model->boundsMin.vz; - /* Compared as `span` but returned as `spanX`: testing a separate copy is - what puts the wider-X arm on the taken branch. */ - s16 span = spanX; - - return (span <= spanZ ? spanZ : spanX) / 2; -} - -/** @brief Height of the linked model's bounding box. */ -static s16 func_801A2AA4(EffectEntity *entity) { - return entity->unk014->boundsMax.vy - entity->unk014->boundsMin.vy; -} - -/** @brief Y of the top of the linked model, in battle-entity space. */ -static s16 func_801A2AC8(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - - return slot->pos.vy + entity->unk014->boundsMax.vy; -} - -/** @brief Y of the bottom of the linked model, in battle-entity space. */ -static s16 func_801A2B10(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - - return slot->pos.vy + entity->unk014->boundsMin.vy; -} - -/** @brief A random point up the linked model, in battle-entity space. */ -static s16 func_801A2B58(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - EffectModel *model = entity->unk014; - s16 height = model->boundsMax.vy - model->boundsMin.vy; - s16 offset = (rand() & 0xFFF) * height / 4096; - - return slot->pos.vy + model->boundsMax.vy - offset / 2; -} - -/** @brief Y of the centre of the linked model, in battle-entity space. */ -static s16 func_801A2C10(EffectEntity *entity) { - EffectModel *model = entity->unk014; - - return D_800EF2D0[entity->unk02D].pos.vy + - (model->boundsMax.vy + model->boundsMin.vy) / 2; -} - -/** @brief Centre of the linked model's bounding box. */ -static void func_801A2C64(EffectEntity *entity, SVECTOR *out) { - EffectModel *model = entity->unk014; - - out->vx = (model->boundsMax.vx + model->boundsMin.vx) / 2; - out->vy = (model->boundsMax.vy + model->boundsMin.vy) / 2; - out->vz = (model->boundsMax.vz + model->boundsMin.vz) / 2; -} - -/** @brief Zero @p size bytes' worth of words starting at @p dst. */ -static void func_801A2CD0(s32 *dst, s32 size) { - s32 i; - - for (i = 0; i < size / 4; i++) { - *dst = 0; - dst++; - } -} - -/** - * @brief Emit the flat triangles the stream cursor points at. - * - * The entry starts with a triangle count; the cursor is stepped past it before - * anything is drawn, so the caller always resumes at the next entry. Each - * triangle is projected, given the build's colour with the semi-transparency - * bit forced on or off, and dropped when the GTE could not project it, when it - * faces away and the build is not two-sided, or when all three vertices leave - * the screen on the same axis. Survivors optionally take a depth cue and are - * linked into @p ot at their own depth. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - * - * @note Two spellings here are load-bearing for the whole emitter family. The - * empty @c do/while(0) wraps carry no code: they raise the loop depth - * flow.c weights register references by, which is what puts each - * variable in the register the original picked, and which statement - * needs one differs per emitter. The prim and stream cursors are cast on - * the way in and out because the driver chains one cursor type through - * eight emitters that each write a different prim. - */ -static EffectTri *func_801A2D08(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitTri *tri; - EffectTri *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - tri = (EffectEmitTri *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], - &verts[tri->idx2]); - } while (0); - gte_rtpt(); - setlen(prim, 5); - colour = tri->colour; - prim->rgb = colour; - if (s->flags & EFFECT_EMIT_SEMITRANS) { - prim->rgb = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_OPAQUE) { - prim->rgb &= ~EFFECT_PRIM_CODE(2); - } - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip != 0 && - (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED))) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_avsz3(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && - (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_DEPTH_CUE) { - gte_ldrgb(&prim->rgb); - gte_lddp(s->depth); - gte_dpcs(); - gte_strgb(&prim->rgb); - } - prim->unk014 = D_801EEA0C; - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - tri++; - } while (i < count); - } - s->cursor = (s32 *)tri; - return prim; -} - -/** - * @brief Emit the flat quads the stream cursor points at. - * - * Same walk as @ref func_801A2D08 with a fourth vertex: the first three are - * projected together, the fourth on its own, and the average Z takes all four. - * A quad is dropped when every vertex leaves the screen on the same axis. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A2FE8(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitQuad *quad; - EffectQuad *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectQuad *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - quad = (EffectEmitQuad *)next; - if (count > 0) { - do { - gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], - &verts[quad->idx2]); - gte_rtpt(); - setlen(prim, 6); - colour = quad->colour; - prim->rgb = colour; - if (s->flags & EFFECT_EMIT_SEMITRANS) { - prim->rgb = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_OPAQUE) { - prim->rgb &= ~EFFECT_PRIM_CODE(2); - } - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip != 0 && - (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED))) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_ldv0(&verts[quad->idx3]); - gte_rtps(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - gte_stsxy(&prim->x3); - gte_avsz4(); - if (prim->x3 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X3; - } - if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y3; - } - if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && - (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_DEPTH_CUE) { - gte_ldrgb(&prim->rgb); - gte_lddp(s->depth); - gte_dpcs(); - gte_strgb(&prim->rgb); - } - prim->unk018 = D_801EEA0C; - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - quad++; - } while (i < count); - } - s->cursor = (s32 *)quad; - return (EffectTri *)prim; -} - -/** - * @brief Emit the textured triangles the stream cursor points at. - * - * Same walk as @ref func_801A2D08 with texture coordinates: each UV word comes - * from the stream biased by the build's offset, and the texture page and CLUT - * that share the first two UV words are either left alone, offset by the - * build's own, or replaced by it. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A3320(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitTexTri *tri; - EffectTexTri *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectTexTri *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - tri = (EffectEmitTexTri *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], - &verts[(u16)tri->idx2uv2]); - } while (0); - gte_rtpt(); - setlen(prim, 8); - colour = tri->colour; - prim->rgb = colour; - if (s->flags & EFFECT_EMIT_SEMITRANS) { - prim->rgb = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_OPAQUE) { - prim->rgb &= ~EFFECT_PRIM_CODE(2); - } - prim->uv0.word = tri->uv0 + s->unk018; - prim->uv1.word = tri->uv1 + s->unk018; - prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - if (s->flags & EFFECT_EMIT_TPAGE_ADD) { - prim->uv1.h.id = prim->uv1.h.id + s->tpage; - } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { - prim->uv1.h.id = s->tpage; - } - if (s->flags & EFFECT_EMIT_CLUT_ADD) { - prim->uv0.h.id = prim->uv0.h.id + s->clut; - } else if (s->flags & EFFECT_EMIT_CLUT_SET) { - prim->uv0.h.id = s->clut; - } - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip != 0 && - (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED))) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_avsz3(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && - (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_DEPTH_CUE) { - gte_ldrgb(&prim->rgb); - gte_lddp(s->depth); - gte_dpcs(); - gte_strgb(&prim->rgb); - } - prim->unk020 = D_801EEA0C; - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - tri++; - } while (i < count); - } - s->cursor = (s32 *)tri; - return (EffectTri *)prim; -} - -/** - * @brief Emit the textured quads the stream cursor points at. - * - * @ref func_801A2FE8 with texture coordinates, laid out like - * @ref func_801A4548's but with one flat colour for the whole quad. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A36B4(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitTexQuad *quad; - EffectTexQuad *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - u32 uv; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectTexQuad *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - quad = (EffectEmitTexQuad *)next; - if (count > 0) { - do { - gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], - &verts[quad->idx2]); - gte_rtpt(); - setlen(prim, 0xA); - colour = quad->colour; - prim->rgb = colour; - if (s->flags & EFFECT_EMIT_SEMITRANS) { - prim->rgb = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_OPAQUE) { - prim->rgb &= ~EFFECT_PRIM_CODE(2); - } - prim->uv0.word = quad->uv0 + s->unk018; - prim->uv1.word = quad->uv1 + s->unk018; - uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); - prim->uv2.word = uv; - prim->uv3.word = uv >> 16; - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - if (s->flags & EFFECT_EMIT_TPAGE_ADD) { - prim->uv1.h.id = prim->uv1.h.id + s->tpage; - } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { - prim->uv1.h.id = s->tpage; - } - if (s->flags & EFFECT_EMIT_CLUT_ADD) { - prim->uv0.h.id = prim->uv0.h.id + s->clut; - } else if (s->flags & EFFECT_EMIT_CLUT_SET) { - prim->uv0.h.id = s->clut; - } - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip != 0 && - (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED))) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_ldv0(&verts[quad->idx3]); - gte_rtps(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - gte_stsxy(&prim->x3); - gte_avsz4(); - if (prim->x3 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X3; - } - if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y3; - } - if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && - (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_DEPTH_CUE) { - gte_ldrgb(&prim->rgb); - gte_lddp(s->depth); - gte_dpcs(); - gte_strgb(&prim->rgb); - } - prim->unk028 = D_801EEA0C; - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - quad++; - } while (i < count); - } - s->cursor = (s32 *)quad; - return (EffectTri *)prim; -} - -/** - * @brief Emit the gouraud triangles the stream cursor points at. - * - * Same walk as @ref func_801A2D08 with a colour per corner. When the build - * asks for a depth cue all three corners go through the GTE together, which - * also refreshes the first corner; otherwise the two extra corners are copied - * straight out of the stream. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A3AAC(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitGouraudTri *tri; - EffectGouraudTri *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectGouraudTri *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - tri = (EffectEmitGouraudTri *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], - &verts[tri->idx2]); - } while (0); - gte_rtpt(); - setlen(prim, 7); - colour = tri->colour0; - prim->rgb0 = colour; - if (s->flags & EFFECT_EMIT_G_SEMITRANS) { - prim->rgb0 = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_G_OPAQUE) { - prim->rgb0 &= ~EFFECT_PRIM_CODE(2); - } - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip != 0 && - (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED))) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_avsz3(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && - (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { - gte_ldrgb3(&tri->colour1, &tri->colour2, - &prim->rgb0); - gte_lddp(s->depth); - gte_dpct(); - gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); - } else { - prim->rgb1 = tri->colour1; - prim->rgb2 = tri->colour2; - } - prim->unk01C = D_801EEA0C; - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - tri++; - } while (i < count); - } - s->cursor = (s32 *)tri; - return (EffectTri *)prim; -} - -/** - * @brief Emit the gouraud quads the stream cursor points at. - * - * @ref func_801A3AAC with a fourth corner: the first three vertices are - * projected together and the fourth on its own, and the depth cue runs over - * the three stream colours together before the prim's own colour separately. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A3DD4(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitGouraudQuad *quad; - EffectGouraudQuad *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectGouraudQuad *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - quad = (EffectEmitGouraudQuad *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], - &verts[quad->idx2]); - } while (0); - gte_rtpt(); - setlen(prim, 9); - colour = quad->colour0; - prim->rgb0 = colour; - if (s->flags & EFFECT_EMIT_G_SEMITRANS) { - prim->rgb0 = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_G_OPAQUE) { - prim->rgb0 &= ~EFFECT_PRIM_CODE(2); - } - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip != 0 && - (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED))) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_ldv0(&verts[quad->idx3]); - gte_rtps(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - gte_stsxy(&prim->x3); - gte_avsz4(); - if (prim->x3 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X3; - } - if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y3; - } - if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && - (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { - gte_ldrgb3(&quad->colour1, &quad->colour2, - &quad->colour3); - gte_lddp(s->depth); - gte_dpct(); - gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); - gte_ldrgb(&prim->rgb0); - gte_dpcs(); - gte_strgb(&prim->rgb0); - } else { - prim->rgb1 = quad->colour1; - prim->rgb2 = quad->colour2; - prim->rgb3 = quad->colour3; - } - prim->unk024 = D_801EEA0C; - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - quad++; - } while (i < count); - } - s->cursor = (s32 *)quad; - return (EffectTri *)prim; -} - -/** - * @brief Emit the gouraud textured triangles the stream cursor points at. - * - * @ref func_801A3AAC with texture coordinates: the UV words come from the - * stream biased by the build's offset, and the texture page and CLUT sharing - * the first two are left alone, offset, or replaced as the flags ask. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A4174(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitGouraudTexTri *tri; - EffectGouraudTexTri *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectGouraudTexTri *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - tri = (EffectEmitGouraudTexTri *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], - &verts[(u16)tri->idx2uv2]); - } while (0); - gte_rtpt(); - setlen(prim, 0xA); - colour = tri->colour0; - prim->rgb0 = colour; - if (s->flags & EFFECT_EMIT_G_SEMITRANS) { - prim->rgb0 = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_G_OPAQUE) { - prim->rgb0 &= ~EFFECT_PRIM_CODE(2); - } - prim->uv0.word = tri->uv0 + s->unk018; - prim->uv1.word = tri->uv1 + s->unk018; - prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - if (s->flags & EFFECT_EMIT_TPAGE_ADD) { - prim->uv1.h.id = prim->uv1.h.id + s->tpage; - } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { - prim->uv1.h.id = s->tpage; - } - if (s->flags & EFFECT_EMIT_CLUT_ADD) { - prim->uv0.h.id = prim->uv0.h.id + s->clut; - } else if (s->flags & EFFECT_EMIT_CLUT_SET) { - prim->uv0.h.id = s->clut; - } - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip != 0 && - (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED))) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_avsz3(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && - (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { - gte_ldrgb3(&tri->colour1, &tri->colour2, - &prim->rgb0); - gte_lddp(s->depth); - gte_dpct(); - gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); - } else { - prim->rgb1 = tri->colour1; - prim->rgb2 = tri->colour2; - } - prim->unk028 = D_801EEA0C; - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - tri++; - } while (i < count); - } - s->cursor = (s32 *)tri; - return (EffectTri *)prim; -} - -/** - * @brief Emit the gouraud textured quads the stream cursor points at. - * - * The widest of the family: four corners, each with its own colour and UV - * pair. @ref func_801A4174 with a fourth vertex. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A4548(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitGouraudTexQuad *quad; - EffectGouraudTexQuad *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - u32 uv; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectGouraudTexQuad *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - quad = (EffectEmitGouraudTexQuad *)next; - if (count > 0) { - do { - gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], - &verts[quad->idx2]); - gte_rtpt(); - setlen(prim, 0xD); - colour = quad->colour0; - prim->rgb0 = colour; - if (s->flags & EFFECT_EMIT_G_SEMITRANS) { - prim->rgb0 = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_G_OPAQUE) { - prim->rgb0 &= ~EFFECT_PRIM_CODE(2); - } - prim->uv0.word = quad->uv0 + s->unk018; - prim->uv1.word = quad->uv1 + s->unk018; - uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); - prim->uv2.word = uv; - prim->uv3.word = uv >> 16; - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - if (s->flags & EFFECT_EMIT_TPAGE_ADD) { - prim->uv1.h.id = prim->uv1.h.id + s->tpage; - } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { - prim->uv1.h.id = s->tpage; - } - if (s->flags & EFFECT_EMIT_CLUT_ADD) { - prim->uv0.h.id = prim->uv0.h.id + s->clut; - } else if (s->flags & EFFECT_EMIT_CLUT_SET) { - prim->uv0.h.id = s->clut; - } - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip != 0 && - (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED))) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_ldv0(&verts[quad->idx3]); - gte_rtps(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - gte_stsxy(&prim->x3); - gte_avsz4(); - if (prim->x3 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X3; - } - if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y3; - } - if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && - (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { - gte_ldrgb3(&quad->colour1, &quad->colour2, - &quad->colour3); - gte_lddp(s->depth); - gte_dpct(); - gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); - gte_ldrgb(&prim->rgb0); - gte_dpcs(); - gte_strgb(&prim->rgb0); - } else { - prim->rgb1 = quad->colour1; - prim->rgb2 = quad->colour2; - prim->rgb3 = quad->colour3; - } - prim->unk034 = D_801EEA0C; - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - quad++; - } while (i < count); - } - s->cursor = (s32 *)quad; - return (EffectTri *)prim; -} - -/** - * @brief Run every prim kind of one mesh stream into the ordering table. - * - * The stream names one entry per kind; a zero entry means the mesh has none - * of that kind, and the emitters hand the prim cursor on between them. - * - * @return The prim cursor past everything emitted. - */ -static EffectTri *func_801A49A8(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *prim) { - if (!(s->flags & EFFECT_EMIT_UNK2000)) { - s->verts = (u32 *)(s->stream + 2); - } - s->cursor = (s32 *)((u8 *)s->stream + s->stream[0]); - if (!(s->flags & EFFECT_EMIT_UNK1000)) { - s->unk018 = 0; - } - D_801EEA0C = 0xE1000220; - gte_SetFarColor(s->r, s->g, s->b); - if (*s->cursor != 0) { - prim = func_801A2D08(s, ot, otShift, prim); - } else { - s->cursor++; - } - if (*s->cursor != 0) { - prim = func_801A2FE8(s, ot, otShift, prim); - } else { - s->cursor++; - } - if (*s->cursor != 0) { - prim = func_801A3320(s, ot, otShift, prim); - } else { - s->cursor++; - } - if (*s->cursor != 0) { - prim = func_801A36B4(s, ot, otShift, prim); - } else { - s->cursor++; - } - if (*s->cursor != 0) { - prim = func_801A3AAC(s, ot, otShift, prim); - } else { - s->cursor++; - } - if (*s->cursor != 0) { - prim = func_801A3DD4(s, ot, otShift, prim); - } else { - s->cursor++; - } - if (*s->cursor != 0) { - prim = func_801A4174(s, ot, otShift, prim); - } else { - s->cursor++; - } - if (*s->cursor != 0) { - prim = func_801A4548(s, ot, otShift, prim); - } else { - s->cursor++; - } - return prim; -} - -/** - * @brief Scroll the textured quad list's V coordinates by one fixed step. - * - * The list @p header names holds the gouraud textured quads; each carries four - * UV pairs, the last two packed into one word. Only the V byte of each moves, - * and all four wrap together so a quad never tears. - * - * @param header Mesh stream to walk. - */ -static void func_801A4BFC(s32 *header) { - EffectEmitGouraudTexQuad *e; - u32 keep[3]; - u32 ch[4]; - u32 uv23; - s32 count; - s32 i; - - header = (s32 *)((u8 *)header + header[0] / 4 * 4); - header += EFFECT_QUAD_LIST_HEADER; - count = *header; - header++; - for (i = 0; i < count; i++) { - e = (EffectEmitGouraudTexQuad *)header; - ch[0] = e->uv0; - keep[0] = ch[0] & EFFECT_UV_KEEP; - ch[0] &= EFFECT_UV_V; - ch[1] = e->uv1; - keep[1] = ch[1] & EFFECT_UV_KEEP; - ch[1] &= EFFECT_UV_V; - uv23 = e->uv23; - ch[3] = uv23; - ch[2] = uv23; - keep[2] = uv23 & EFFECT_UV_KEEP23; - ch[2] &= EFFECT_UV_V; - ch[3] = (ch[3] >> 16) & EFFECT_UV_V; - ch[0] -= EFFECT_UV_SCROLL_STEP; - ch[1] -= EFFECT_UV_SCROLL_STEP; - ch[2] -= EFFECT_UV_SCROLL_STEP; - ch[3] -= EFFECT_UV_SCROLL_STEP; - if (ch[0] > EFFECT_UV_V || ch[1] > EFFECT_UV_V || ch[2] > EFFECT_UV_V || ch[3] > EFFECT_UV_V) { - ch[0] += EFFECT_UV_WRAP; - ch[1] += EFFECT_UV_WRAP; - ch[2] += EFFECT_UV_WRAP; - ch[3] += EFFECT_UV_WRAP; - } - ch[0] &= EFFECT_UV_V; - ch[1] &= EFFECT_UV_V; - ch[2] &= EFFECT_UV_V; - ch[3] = (ch[3] & EFFECT_UV_V) << 16; - e->uv0 = keep[0] | ch[0]; - e->uv1 = keep[1] | ch[1]; - e->uv23 = keep[2] | ch[2] | ch[3]; - header += sizeof(EffectEmitGouraudTexQuad) / sizeof(s32); - } -} - -/** - * @brief Scroll the textured quad list's V coordinates by @p delta. - * - * @ref func_801A4BFC with the step supplied by the caller. - * - * @param header Mesh stream to walk. - * @param delta Step in whole V units. - */ -static void func_801A4D9C(s32 *header, s32 delta) { - EffectEmitGouraudTexQuad *e; - u32 keep[3]; - u32 ch[4]; - u32 uv23; - s32 count; - s32 i; - - header = (s32 *)((u8 *)header + header[0] / 4 * 4); - header += EFFECT_QUAD_LIST_HEADER; - count = *header; - header++; - delta <<= 8; - for (i = 0; i < count; i++) { - e = (EffectEmitGouraudTexQuad *)header; - ch[0] = e->uv0; - keep[0] = ch[0] & EFFECT_UV_KEEP; - ch[0] &= EFFECT_UV_V; - ch[1] = e->uv1; - keep[1] = ch[1] & EFFECT_UV_KEEP; - ch[1] &= EFFECT_UV_V; - uv23 = e->uv23; - ch[3] = uv23; - ch[2] = uv23; - keep[2] = uv23 & EFFECT_UV_KEEP23; - ch[2] &= EFFECT_UV_V; - ch[3] = (ch[3] >> 16) & EFFECT_UV_V; - ch[0] += delta; - ch[1] += delta; - ch[2] += delta; - ch[3] += delta; - if (ch[0] > EFFECT_UV_V || ch[1] > EFFECT_UV_V || ch[2] > EFFECT_UV_V || ch[3] > EFFECT_UV_V) { - ch[0] -= EFFECT_UV_WRAP; - ch[1] -= EFFECT_UV_WRAP; - ch[2] -= EFFECT_UV_WRAP; - ch[3] -= EFFECT_UV_WRAP; - } - ch[0] &= EFFECT_UV_V; - ch[1] &= EFFECT_UV_V; - ch[2] &= EFFECT_UV_V; - ch[3] = (ch[3] & EFFECT_UV_V) << 16; - e->uv0 = keep[0] | ch[0]; - e->uv1 = keep[1] | ch[1]; - e->uv23 = keep[2] | ch[2] | ch[3]; - header += sizeof(EffectEmitGouraudTexQuad) / sizeof(s32); - } -} - -/** @brief Build this effect's primitive and link it into the battle display list. */ -static void func_801A4F44(EffectEntity *entity) { - s16 count; - BattleSpritePrim *prim; - - if (entity->flags & EFFECT_FLAG_DONE) { - return; - } - prim = func_800B3698(sizeof(BattleSpritePrim)); - func_800C96E4(&entity->pos, ONE, entity->unk054); - prim->anim = entity->unk04C; - count = entity->unk050; - prim->flags = 0; - prim->frame = count; - D_801D3EB8 = func_800C9E10(prim, D_800FA5E8->ot, 2, D_801D3EB8); - func_800B36B8(sizeof(BattleSpritePrim)); -} - -/** @brief Draw @p anim upright at the entity's position, tinted with @p colour. */ -static void func_801A4FE0(EffectEntity *entity, BattleSpriteAnim *anim, - CVECTOR *colour) { - MATRIX m; - BattleSpritePrim *prim; - - if (entity->flags & EFFECT_FLAG_DONE) { - return; - } - func_801A0568(&m); - func_801A05A0(&m, 0x400); - m.t[0] = entity->pos.vx; - m.t[1] = 0; - m.t[2] = entity->pos.vz; - CompMatrix(&D_800F02C8, &m, &m); - SetRotMatrix(&m); - SetTransMatrix(&m); - prim = func_800B3698(sizeof(BattleSpritePrim)); - prim->anim = anim; - prim->frame = entity->unk050; - prim->flags = BATTLE_SPRITE_FLAG_COLOUR; - prim->colour = *colour; - D_801D3EB8 = func_800C9E10(prim, D_800FA5E8->ot, 2, D_801D3EB8); - func_800B36B8(sizeof(BattleSpritePrim)); -} - -/** @brief As @ref func_801A4FE0, rolled by @p angle and scaled by @p scale. */ -static void func_801A50D4(EffectEntity *entity, BattleSpriteAnim *anim, - CVECTOR *colour, s16 angle, s16 scale) { - MATRIX m; - VECTOR scaleVec; - BattleSpritePrim *prim; - - if (entity->flags & EFFECT_FLAG_DONE) { - return; - } - scaleVec.vz = scale; - scaleVec.vy = scale; - scaleVec.vx = scale; - func_801A0568(&m); - func_801A0730(&m, angle); - func_801A05A0(&m, 0x400); - ScaleMatrix(&m, &scaleVec); - m.t[0] = entity->pos.vx; - m.t[1] = 0; - m.t[2] = entity->pos.vz; - CompMatrix(&D_800F02C8, &m, &m); - SetRotMatrix(&m); - SetTransMatrix(&m); - prim = func_800B3698(sizeof(BattleSpritePrim)); - prim->anim = anim; - prim->frame = entity->unk050; - prim->flags = BATTLE_SPRITE_FLAG_COLOUR; - prim->colour = *colour; - D_801D3EB8 = func_800C9E10(prim, D_800FA5E8->ot, 2, D_801D3EB8); - func_800B36B8(sizeof(BattleSpritePrim)); -} - -/** @brief Advance the script's counter, clamping at its limit. */ -static s32 func_801A5204(EffectEntity *entity) { - entity->unk050++; - if (entity->unk052 < entity->unk050) { - entity->unk050 = entity->unk052; - entity->flags |= EFFECT_FLAG_DONE; - return 1; - } - return 0; -} - -/** @brief Advance the animation frame, wrapping at its limit. */ -static void func_801A524C(EffectEntity *entity) { - entity->unk050++; - if (entity->unk052 < entity->unk050) { - entity->unk050 = 0; - } -} - -/** @brief Opcode handler: start a rise with a randomised speed. */ -static void func_801A527C(EffectEntity *entity) { - entity->unk04C = &D_801A8820; - entity->unk052 = 0xA; - entity->unk054 = -0x400; - entity->unk05A = (rand() & 7) + 4; - entity->pc++; -} - -/** @brief Opcode handler: rise by the current speed until the count runs out. */ -static void func_801A52D8(EffectEntity *entity) { - entity->pos.vy += entity->unk05A; - if (func_801A5204(entity) != 0) { - entity->flags |= EFFECT_FLAG_STOP; - entity->pc++; - } -} - -/** @brief Opcode handler: no-op. */ -static void func_801A5334(EffectEntity *entity) { -} - -/** - * @brief Script dispatcher: run this frame's step, draw, and retire the entity. - * - * @return 2 once the script has stopped and its children have drained, 0 while - * it is still running. - */ -static s32 func_801A533C(EffectEntity *entity) { - EffectHandler handlers[3] = { func_801A527C, func_801A52D8, func_801A5334 }; - - handlers[entity->pc](entity); - if (entity->pos.vy > 0) { - entity->flags |= EFFECT_FLAG_STOP | EFFECT_FLAG_DONE; - } - func_801A4F44(entity); - entity->unk024++; - if (entity->flags & EFFECT_FLAG_STOP) { - if (entity->wait != 0) { - return 0; - } - func_801A2198(entity); - return 2; - } - return 0; -} - -/** @brief Opcode handler: pick one of two table pairs at random and start a fall. */ -static void func_801A5404(EffectEntity *entity) { - if (rand() & 1) { - entity->unk04C = &D_801A84E8; - entity->unk070 = &D_801A8AF8; - } else { - entity->unk04C = &D_801A894C; - entity->unk070 = &D_801A8CA4; - } - entity->unk07A = rand() & 0xFFF; - entity->unk052 = 0xF; - entity->unk054 = -0x200; - entity->pc++; -} - -/** @brief Opcode handler: hold still until the count runs out. */ -static void func_801A548C(EffectEntity *entity) { - if (func_801A5204(entity) != 0) { - entity->flags |= EFFECT_FLAG_STOP; - entity->pc++; - } -} - -/** @brief Opcode handler: no-op. */ -static void func_801A54D4(EffectEntity *entity) { -} - -/** @brief Script dispatcher: the falling-mote script, drawn spun and tinted. */ -static s32 func_801A54DC(EffectEntity *entity) { - EffectHandler handlers[3] = { func_801A5404, func_801A548C, func_801A54D4 }; - CVECTOR colour; - - handlers[entity->pc](entity); - func_801A4F44(entity); - entity->unk07A = (entity->unk07A + 0x40) & 0xFFF; - colour.b = 0x30; - colour.g = 0x30; - colour.r = 0x30; - func_801A50D4(entity, entity->unk070, &colour, entity->unk07A, 0x2000); - entity->unk024++; - if (entity->flags & EFFECT_FLAG_STOP) { - if (entity->wait != 0) { - return 0; - } - func_801A2198(entity); - return 2; - } - return 0; -} - -/** @brief Opcode handler: start a fast rise that gravity pulls back. */ -static void func_801A55C0(EffectEntity *entity) { - entity->unk04C = &D_801A8820; - entity->unk050 = 0; - entity->unk052 = 0xA; - entity->unk05A = (rand() & 7) + 0x20; - ((SVECTOR *)entity->unk060)->vy = -3; - entity->pc++; -} - -/** @brief Opcode handler: apply gravity, then rise until the count runs out. */ -static void func_801A5620(EffectEntity *entity) { - entity->unk05A += ((SVECTOR *)entity->unk060)->vy; - if (entity->unk05A < 2) { - entity->unk05A = 2; - } - entity->pos.vy += entity->unk05A; - if (func_801A5204(entity) != 0) { - entity->flags |= EFFECT_FLAG_STOP; - entity->pc++; - } -} - -/** @brief Opcode handler: no-op. */ -static void func_801A56A8(EffectEntity *entity) { -} - -/** - * @brief Script dispatcher: run this frame's step, draw, and retire the entity. - * - * @return 2 once the script has stopped and its children have drained, 0 while - * it is still running. - */ -static s32 func_801A56B0(EffectEntity *entity) { - EffectHandler handlers[3] = { func_801A55C0, func_801A5620, func_801A56A8 }; - - handlers[entity->pc](entity); - if (entity->pos.vy > 0) { - entity->flags |= EFFECT_FLAG_STOP | EFFECT_FLAG_DONE; - } - func_801A4F44(entity); - entity->unk024++; - if (entity->flags & EFFECT_FLAG_STOP) { - if (entity->wait != 0) { - return 0; - } - func_801A2198(entity); - return 2; - } - return 0; -} - -/** @brief Opcode handler: keep the start point and start a randomised arc. */ -static void func_801A5778(EffectEntity *entity) { - entity->unk078 = rand() & 0xFFF; - *(SVECTOR *)&entity->unk068 = entity->pos; - entity->unk04C = &D_801A8708; - entity->unk052 = 1; - entity->unk05A = (rand() & 0x3F) - 0x30; - entity->unk07C = (rand() & 0x1F) + 0x50; - entity->unk07E = -entity->unk07C / 16; - entity->pc++; -} - -/** @brief Opcode handler: switch to the burst table once the frame count passes 16. */ -static void func_801A581C(EffectEntity *entity) { - func_801A524C(entity); - if (entity->unk024 >= 0x10) { - entity->unk04C = &D_801A875C; - entity->unk052 = 6; - entity->unk050 = 0; - entity->flags |= EFFECT_FLAG_UNK10; - entity->pc++; - } -} - -/** @brief Opcode handler: hold still until the count runs out. */ -static void func_801A5884(EffectEntity *entity) { - if (func_801A5204(entity) != 0) { - entity->flags |= EFFECT_FLAG_STOP; - entity->pc++; - } -} - -/** @brief Opcode handler: no-op. */ -static void func_801A58CC(EffectEntity *entity) { -} - -/** @brief Script dispatcher: an arc under gravity that trails motes behind it. */ -static s32 func_801A58D4(EffectEntity *entity) { - EffectHandler handlers[4] = { func_801A5778, func_801A581C, func_801A5884, - func_801A58CC }; - EffectEntity *spark; - CVECTOR colour; - - handlers[entity->pc](entity); - entity->unk07C += entity->unk07E; - if (entity->unk07C < 0) { - entity->unk07C = 0; - } - entity->unk068 += rsin(entity->unk040.vy) * entity->unk07C / 4096; - entity->unk06C += rcos(entity->unk040.vy) * entity->unk07C / 4096; - entity->pos.vx = entity->unk068; - entity->pos.vz = entity->unk06C; - entity->unk05A += 0xA; - entity->pos.vy += entity->unk05A; - if (!(entity->flags & EFFECT_FLAG_UNK10) && (entity->unk024 & 1)) { - spark = func_801A21C0(&D_801E74F4, func_801A56B0, 0x84, entity); - func_801A0A64(spark, 0x80); - spark->pos.vy = entity->pos.vy; - ((VECTOR *)&spark->unk030)->vx = ONE; - ((VECTOR *)&spark->unk030)->vy = ONE; - ((VECTOR *)&spark->unk030)->vz = ONE; - } - if (entity->pos.vy > 0) { - entity->flags |= EFFECT_FLAG_STOP | EFFECT_FLAG_DONE; - } - func_801A4F44(entity); - colour.b = 0x50; - colour.g = 0x50; - colour.r = 0x50; - func_801A4FE0(entity, entity->unk04C, &colour); - entity->unk024++; - if (entity->flags & EFFECT_FLAG_STOP) { - if (entity->wait != 0) { - return 0; - } - func_801A2198(entity); - return 2; - } - return 0; -} - -/** @brief Opcode handler: ring eight sparks around the model's first point. */ -static void func_801A5AE8(EffectEntity *entity) { - EffectEntity *spark; - s32 angle; - s32 i; - - func_801A2480(entity, &entity->pos); - entity->unk04C = &D_801A8694; - entity->unk052 = 3; - entity->pos.vy -= 0x600; - angle = rand() & 0xFFF; - for (i = 0; i < 8; i++) { - spark = func_801A21C0(&D_801E74F4, func_801A58D4, 0x84, entity); - ((VECTOR *)&spark->unk030)->vx = ONE; - ((VECTOR *)&spark->unk030)->vy = ONE; - ((VECTOR *)&spark->unk030)->vz = ONE; - spark->unk040.vy = angle + (rand() & 0x7F); - angle = (angle + 0x200) & 0xFFF; - } - entity->pc++; -} - -/** @brief Opcode handler: hold still until the count runs out. */ -static void func_801A5BD4(EffectEntity *entity) { - if (func_801A5204(entity) != 0) { - entity->flags |= EFFECT_FLAG_STOP; - entity->pc++; - } -} - -/** @brief Opcode handler: no-op. */ -static void func_801A5C1C(EffectEntity *entity) { -} - -/** @brief Script dispatcher: the rising-mote script, drawn tinted. */ -static s32 func_801A5C24(EffectEntity *entity) { - EffectHandler handlers[3] = { func_801A5AE8, func_801A5BD4, func_801A5C1C }; - CVECTOR colour; - - handlers[entity->pc](entity); - func_801A4F44(entity); - colour.b = 0x20; - colour.g = 0x20; - colour.r = 0x20; - func_801A4FE0(entity, entity->unk04C, &colour); - entity->unk024++; - if (entity->flags & EFFECT_FLAG_STOP) { - if (entity->wait != 0) { - return 0; - } - func_801A2198(entity); - return 2; - } - return 0; -} - -/** @brief Fill @p count words at @p dst with @p value. */ -static void func_801A5CEC(s32 *dst, s32 value, s32 count) { - s32 i; - - for (i = count - 1; i != -1; i--) { - *dst++ = value; - } -} - -/** @brief Build the rotation of @p angle about X in @p m. */ -static void func_801A5D10(s16 angle, MATRIX *m) { - s32 *p = (s32 *)m; - s32 i; - s16 sin; - s16 cos; - - for (i = 5; i != 0; i--) { - *p++ = 0; - } - sin = rsin(angle); - cos = rcos(angle); - m->m[0][0] = ONE; - m->m[1][1] = cos; - m->m[2][1] = -sin; - m->m[1][2] = sin; - m->m[2][2] = cos; -} - -/** @brief Build the rotation of @p angle about Y in @p m. */ -static void func_801A5D8C(s16 angle, MATRIX *m) { - s32 *p = (s32 *)m; - s32 i; - s16 sin; - s16 cos; - - for (i = 5; i != 0; i--) { - *p++ = 0; - } - sin = rsin(angle); - cos = rcos(angle); - m->m[2][0] = sin; - m->m[0][0] = cos; - m->m[1][1] = ONE; - m->m[0][2] = -sin; - m->m[2][2] = cos; -} - -/** @brief Build the rotation of @p angle about Z in @p m. */ -static void func_801A5E08(s16 angle, MATRIX *m) { - s32 *p = (s32 *)m; - s32 i; - s16 sin; - s16 cos; - - for (i = 5; i != 0; i--) { - *p++ = 0; - } - sin = rsin(angle); - cos = rcos(angle); - m->m[1][0] = -sin; - m->m[0][0] = cos; - m->m[1][1] = cos; - m->m[0][1] = sin; - m->m[2][2] = ONE; -} - -/** @brief Build the Z, X then Y rotation of @p angles in @p out. */ -static void func_801A5E84(SVECTOR *angles, MATRIX *out) { - MATRIX rz; - MATRIX rx; - MATRIX ry; - MATRIX zx; - - func_801A5E08(angles->vz, &rz); - func_801A5D10(angles->vx, &rx); - MulMatrix0(&rz, &rx, &zx); - func_801A5D8C(angles->vy, &ry); - MulMatrix0(&zx, &ry, out); -} - -/** - * @brief Clear @p size bytes of pose state at @p out and seed it from @p model. - * - * Every part of the model takes a slot sized by its flags; the three-word - * scale slot starts at 1.0 rather than zero. - */ -static void func_801A5F14(EffectPoseModel *model, s32 *out, s32 size) { - EffectPoseParts *parts; - EffectPosePart *part; - s32 *offset; - s32 *slot; - s32 count; - s32 flags; - s32 i; - - func_801A5CEC(out, 0, size >> 2); - out[0] = (s32)model; - slot = out + 2; - parts = (EffectPoseParts *)((u8 *)model + model->partsOffset); - count = parts->count; - offset = parts->offsets; - for (i = 0; i < count; i++) { - part = (EffectPosePart *)((u8 *)parts + *offset++); - flags = part->flags; - if (flags & EFFECT_POSE_ACC0) { - slot += 9; - } else if (flags & EFFECT_POSE_VEL0) { - slot += 6; - } else if (flags & EFFECT_POSE_KEY0) { - slot += 3; - } - if (flags & EFFECT_POSE_ACC1) { - slot += 9; - } else if (flags & EFFECT_POSE_VEL1) { - slot += 6; - } else if (flags & EFFECT_POSE_KEY1) { - slot += 3; - } - if (flags & (EFFECT_POSE_SCALE_KEY | EFFECT_POSE_SCALE_VEL | - EFFECT_POSE_SCALE_ACC)) { - slot[2] = ONE << 16; - slot[1] = ONE << 16; - slot[0] = ONE << 16; - } - if (flags & EFFECT_POSE_SCALE_ACC) { - slot += 9; - } else if (flags & EFFECT_POSE_SCALE_VEL) { - slot += 6; - } else if (flags & EFFECT_POSE_SCALE_KEY) { - slot += 3; - } - if (flags & EFFECT_POSE_ACC3) { - slot += 6; - } else if (flags & EFFECT_POSE_VEL3) { - slot += 4; - } else if (flags & EFFECT_POSE_KEY3) { - slot += 2; - } - if (flags & EFFECT_POSE_DEPTH) { - slot += 3; - } - if (flags & EFFECT_POSE_ANIM) { - slot += 4; - } - } -} - -/** - * @brief Look @p key up in a table of 12-byte entries and copy its three - * halfwords out. - * - * The entries are ordered by key and closed by -1; nothing is written when the - * key is missing. - */ -static void func_801A6094(s32 key, s32 *table, s16 *out) { - s32 k = *table; - - while (k < key) { - if (-1 == k) { - return; - } - table += 3; - k = *table; - } - if (k == key) { - table++; - *out++ = ((s16 *)table)[0]; - *out++ = ((s16 *)table)[1]; - *out = ((s16 *)table)[2]; - } -} - -/** @brief As @ref func_801A6094, for 16-byte entries holding three words. */ -static void func_801A60FC(s32 key, s32 *table, s32 *out) { - s32 k = *table; - - while (k < key) { - if (-1 == k) { - return; - } - table += 4; - k = *table; - } - if (k == key) { - table++; - *out++ = *table++; - *out = table[0]; - out[1] = table[1]; - } -} - -/** - * @brief Step one channel of a pose and hand its current value to @p out. - * - * @param flags Bit 0 keyed, bit 1 velocity, bit 2 acceleration. - * @param base The keyed tables are at byte offsets from here. - * @param cursorPtr Walks the channel's table offsets; advanced past the ones read. - * @param slot The channel's state; position, then velocity, then acceleration. - * @param key Frame the keyed tables are looked up at. - * @param out Receives the channel's three components. - * @return The state past this channel. - * - * @note The gotos are the shape the original had: each arm falls into the next - * one's body, so the move and emit blocks exist once. Written as an - * if/else-if chain the compiler copies them instead (143 instructions - * against 124); written as three independent tests it reaches the emit - * block through a mask of 6 and 7 rather than 2 and 1. - */ -static s32 *func_801A6164(s32 flags, u8 *base, s32 **cursorPtr, s32 *slot, s32 key, - s16 *out) { - s32 *cursor = *cursorPtr; - s32 *vel; - s32 *acc; - s16 *p; - s32 v; - - if (flags & EFFECT_POSE_ACC0) { - vel = slot + 3; - acc = slot + 6; - vel[0] += acc[0]; - vel[1] += acc[1]; - vel[2] += acc[2]; - goto move; - } - if (flags & EFFECT_POSE_VEL0) { - vel = slot + 3; -move: - slot[0] += vel[0]; - slot[1] += vel[1]; - slot[2] += vel[2]; - p = (s16 *)slot; - goto emit; - } - if (flags & EFFECT_POSE_KEY0) { - p = (s16 *)slot; -emit: - v = p[1]; - out[0] = v; - v = p[3]; - out[1] = v; - v = p[5]; - out[2] = v; - } - if (flags & EFFECT_POSE_KEY0) { - func_801A60FC(key, (s32 *)(base + *cursor++), slot); - v = ((s16 *)slot)[1]; - slot++; - out[0] = v; - v = ((s16 *)slot)[1]; - slot++; - out[1] = v; - v = ((s16 *)slot)[1]; - slot++; - out[2] = v; - } else if (flags & (EFFECT_POSE_VEL0 | EFFECT_POSE_ACC0)) { - slot += 3; - } - if (flags & EFFECT_POSE_VEL0) { - func_801A60FC(key, (s32 *)(base + *cursor++), slot); - slot += 3; - } else if (flags & EFFECT_POSE_ACC0) { - slot += 3; - } - if (flags & EFFECT_POSE_ACC0) { - func_801A60FC(key, (s32 *)(base + *cursor++), slot); - slot += 3; - } - *cursorPtr = cursor; - return slot; -} - -/** - * @brief Walk one pose script and hand every part it poses to @p fn. - * - * The script opens with a part count and a frame count, then one byte offset - * per part. A part entry names the model part it drives and the channels it - * carries, and is followed by one keyed table per channel the flags claim. - * @p pose holds the model, the frame the script is on, and then the running - * state of every channel of every part -- the layout @ref func_801A5FA4 sizes. - * That state is one array of words, but a channel holds either words or pairs - * of halfwords, which is what every cast of @c slot below is reading. - * - * A channel that carries an acceleration steps its velocity by it, and one - * that carries a velocity steps its value; the keyed table is read after that, - * and overwrites the state only on a frame it holds. Channels the flags leave - * out keep whatever the zeroed pose gives them, scale excepted -- that starts - * at 1.0. - * - * @param pose The model and the frame the script is on. - * @param fn Called once per part, with the pose the script left. - * @param req Draw state handed straight to @p fn. - * @return The frames left after this one; 0 once the script has run out. - * - * @note The gotos are the shape the original had. The colour channel's three - * arms fall into each other -- acceleration into the velocity ramp, and - * that into the keyed arm's colour store -- so each body exists once; - * spelled as an if/else-if chain the compiler copies them instead, the - * same way it does in @ref func_801A6164. The two out of the table walks - * land on the code that keeps the stepped value: reaching it with a - * @c break costs an instruction, because the frame comparison the walk - * falls out to is then on the path. - */ -static s32 func_801A6354(s32 *pose, EffectPartFn fn, EffectDrawRequest *req) { - EffectPartPose part; - s32 *cursor; - s32 *script; - s32 *offsets; - s32 *depthTab; - u8 *entry; - s32 *slot; - u16 *vel; - u16 *rgb; - u16 frame; - u16 *acc; - s32 count; - s32 frames; - u32 flags; - s32 depthKey; - s32 i; - s32 v; - s32 *animTab; - s32 animKey; - s32 value; - s32 limit; - - script = (s32 *)((u8 *)pose[0] + ((s32 *)pose[0])[1]); - frames = script[1]; - if (pose[1] >= frames) { - return 0; - } - offsets = script + 2; - slot = pose + 2; - count = script[0]; - for (i = 0; i < count; i++) { - func_801A5CEC((s32 *)&part, 0, sizeof(part) / sizeof(s32)); - entry = (u8 *)script + *offsets++; - part.index = i; - part.scale[2] = ONE; - part.scale[1] = ONE; - part.scale[0] = ONE; - cursor = (s32 *)entry; - part.unk004 = *cursor++; - part.mesh = (u8)part.unk004; - flags = *cursor++; - slot = func_801A6164(flags, entry, &cursor, slot, pose[1], part.pos); - slot = func_801A6164(flags >> 3, entry, &cursor, slot, pose[1], part.rot); - slot = func_801A6164(flags >> 6, entry, &cursor, slot, pose[1], part.scale); - if (flags & EFFECT_POSE_ACC3) { - vel = (u16 *)slot + 4; - acc = (u16 *)slot + 8; - vel[0] += acc[0]; - vel[1] += acc[1]; - vel[2] += acc[2]; - goto ramp; - } - if (flags & EFFECT_POSE_VEL3) { - vel = (u16 *)slot + 4; -ramp: - rgb = (u16 *)slot; - v = rgb[0] + (s16)vel[0]; - if (v < 0) { - v = 0; - } else if (v > 0xFFFF) { - v = 0xFFFF; - } - rgb[0] = v; - v = rgb[1] + (s16)vel[1]; - if (v < 0) { - v = 0; - } else if (v > 0xFFFF) { - v = 0xFFFF; - } - rgb[1] = v; - v = rgb[2] + (s16)vel[2]; - if (v < 0) { - v = 0; - } else if (v > 0xFFFF) { - v = 0xFFFF; - } - rgb[2] = v; - goto emit; - } - if (flags & EFFECT_POSE_KEY3) { - rgb = (u16 *)slot; -emit: - part.colour.r = rgb[0] >> 8; - part.colour.g = rgb[1] >> 8; - part.colour.b = rgb[2] >> 8; - part.colour.cd = 0; - } - if (flags & EFFECT_POSE_KEY3) { - func_801A6094(pose[1], (s32 *)(entry + *cursor++), (s16 *)slot); - part.colour.r = *(u16 *)slot >> 8; - slot = (s32 *)((u16 *)slot + 1); - part.colour.g = *(u16 *)slot >> 8; - slot = (s32 *)((u16 *)slot + 1); - part.colour.b = *(u16 *)slot >> 8; - part.colour.cd = 0; - slot++; - } else if (flags & (EFFECT_POSE_VEL3 | EFFECT_POSE_ACC3)) { - slot += 2; - } - if (flags & EFFECT_POSE_VEL3) { - func_801A6094(pose[1], (s32 *)(entry + *cursor++), (s16 *)slot); - slot += 2; - } else if (flags & EFFECT_POSE_ACC3) { - slot += 2; - } - if (flags & EFFECT_POSE_ACC3) { - func_801A6094(pose[1], (s32 *)(entry + *cursor++), (s16 *)slot); - slot += 2; - } - if (flags & EFFECT_POSE_DEPTH) { - slot[1] += slot[2]; - slot[0] += slot[1]; - if (slot[0] < 0) { - slot[0] = 0; - } else { - limit = EFFECT_POSE_RAMP_MAX; - if (slot[0] > limit) { - slot[0] = limit; - } - } - depthTab = (s32 *)(entry + *cursor++); - depthKey = *depthTab; - while (depthKey < pose[1]) { - if (depthKey == -1) { - goto noDepthKey; - } - depthTab += 4; - depthKey = *depthTab; - } - if (depthKey == pose[1]) { - depthTab++; - value = *depthTab++; - *slot++ = value; - part.depth = value >> 16; - *slot++ = *depthTab; - *slot++ = depthTab[1]; - } else { -noDepthKey: - value = ((s16 *)slot)[1]; - part.depth = value; - slot += 3; - } - } - if (flags & EFFECT_POSE_ANIM) { - slot[2] += slot[3]; - slot[1] += slot[2]; - if (slot[1] < 0) { - slot[1] = 0; - } else { - limit = EFFECT_POSE_RAMP_MAX; - if (slot[1] > limit) { - slot[1] = limit; - } - } - animTab = (s32 *)(entry + *cursor++); - animKey = *animTab; - while (animKey < pose[1]) { - if (animKey == -1) { - goto noAnimKey; - } - animTab += 5; - animKey = *animTab; - } - if (animKey == pose[1]) { - animTab++; - frame = *(u16 *)animTab; - *(u16 *)slot = frame; - part.frameA = frame; - slot = (s32 *)((u16 *)slot + 1); - animTab = (s32 *)((u16 *)animTab + 1); - frame = *(u16 *)animTab; - *(u16 *)slot = frame; - part.frameB = frame; - slot = (s32 *)((u16 *)slot + 1); - animTab = (s32 *)((u16 *)animTab + 1); - value = *animTab++; - *slot++ = value; - part.weight = value >> 16; - *slot++ = *animTab; - *slot++ = animTab[1]; - } else { -noAnimKey: - part.frameA = *(u16 *)slot; - slot = (s32 *)((u16 *)slot + 1); - part.frameB = *(u16 *)slot; - slot = (s32 *)((u16 *)slot + 1); - value = ((s16 *)slot)[1]; - part.weight = value; - slot += 3; - } - } - fn(pose, &part, req); - } - pose[1]++; - return frames - pose[1]; -} - -/** - * @brief Interpolate every point of @p anim between two of its frames. - * - * @param frameA First frame; 0 selects the rest pose. - * @param frameB Second frame. - * @param t Weight of @p frameB, 0x1000 being all of it. - * @param out Receives @c anim->count points. - */ -static void func_801A6910(EffectVertexAnim *anim, s32 frameA, s32 frameB, s32 t, - SVECTOR *out) { - SVECTOR *a; - SVECTOR *b; - s32 n; - s32 s = ONE - t; - - if (frameA == 0) { - a = anim->frames; - } else { - a = &anim->frames[frameA * anim->count]; - } - if (frameB == 0) { - b = anim->frames; - } else { - b = &anim->frames[frameB * anim->count]; - } - n = anim->count; - while (--n != -1) { - gte_lddp(s); - gte_ldsv(a); - gte_gpf1(); - gte_lddp(t); - gte_ldsv(b); - gte_gpl1(); - gte_stsv(out); - out++; - a++; - b++; - } -} - -/* - * battle.bin's own header spells this with its view of the 0x58-byte packet - * and keeps the list head in a word; the overlay passes its own view and a - * pointer, so the prototype it compiles against stays file-local. - */ -extern void *func_800CBC68(EffectPrimBuild *prim, u32 *ot, s32 mode, void *head); - /** * @brief Build and submit one posed part's primitive packet. * @@ -3611,8 +93,8 @@ static void func_801A6A00(s32 *pose, EffectPartPose *part, } prim->verts = (u32 *)anim; } else { - func_801A6910((EffectVertexAnim *)prim->stream, part->frameA, part->frameB, - part->weight, req->unk08); + effectInterpolateFrames((EffectVertexAnim *)prim->stream, part->frameA, part->frameB, + part->weight, req->unk08); prim->verts = (u32 *)req->unk08; } RotMatrixYXZ((SVECTOR *)part->rot, &m); @@ -3649,14 +131,14 @@ static void func_801A6A00(s32 *pose, EffectPartPose *part, if (req->colours[part->index] & EFFECT_PART_SCROLL_UV) { func_801A4D9C(prim->stream, ((s16 *)req->unk00C)[part->index]); } - head = D_801D3EB8; - D_801D3EB8 = func_800CBC68(prim, D_800FA5E8->ot, 2, head); + head = g_effectPrimCursor; + g_effectPrimCursor = func_800CBC68(prim, D_800FA5E8->ot, 2, head); func_800B36B8(0x58); } /** @brief Opcode handler: load the 0x34-point aim set. */ static void func_801A6D68(EffectEntity *entity) { - func_801A5F14(&D_801D20B8, (s32 *)entity->unk060, 0x34); + effectPoseInit(&D_801D20B8, (s32 *)entity->unk060, 0x34); entity->pc++; } @@ -3670,7 +152,7 @@ static void func_801A6DB0(EffectEntity *entity) { for (i = 0; i <= 0; i++) { colours[i] = 0x30; } - func_801A24B0(entity, &entity->pos); + effectSlotAnchor1(entity, &entity->pos); RotMatrixYXZ((SVECTOR *)&entity->unk050, &m); req.m = &m; m.t[0] = entity->pos.vx; @@ -3680,7 +162,7 @@ static void func_801A6DB0(EffectEntity *entity) { CompMatrix(&D_800F02C8, &m, &m); req.colours = colours; req.unk08 = &D_801D3EA4; - if (func_801A6354((s32 *)entity->unk060, func_801A6A00, &req) == 0) { + if (effectPoseWalk((s32 *)entity->unk060, func_801A6A00, &req) == 0) { entity->flags |= EFFECT_FLAG_STOP; entity->pc++; } @@ -3704,7 +186,7 @@ static s32 func_801A6EA4(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A2198(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -3715,7 +197,7 @@ static void func_801A6F7C(EffectEntity *entity) { ((VECTOR *)&entity->unk030)->vx = ONE; ((VECTOR *)&entity->unk030)->vy = ONE; ((VECTOR *)&entity->unk030)->vz = ONE; - func_801A5F14(&D_801D238C, (s32 *)entity->unk060, 0x64); + effectPoseInit(&D_801D238C, (s32 *)entity->unk060, 0x64); entity->pc++; } @@ -3730,8 +212,8 @@ static void func_801A6FD4(EffectEntity *entity) { colours[i] = 0x40000030; } req.unk010 = entity->unk0C4; - func_801A2480(entity, &entity->pos); - func_801A0568(&m); + effectSlotAnchor0(entity, &entity->pos); + effectMatrixIdentity(&m); req.m = &m; m.t[0] = entity->pos.vx; m.t[1] = entity->pos.vy; @@ -3740,7 +222,7 @@ static void func_801A6FD4(EffectEntity *entity) { CompMatrix(&D_800F02C8, &m, &m); req.colours = colours; req.unk08 = &D_801D3EA4; - if (func_801A6354((s32 *)entity->unk060, func_801A6A00, &req) == 0) { + if (effectPoseWalk((s32 *)entity->unk060, func_801A6A00, &req) == 0) { entity->flags |= EFFECT_FLAG_STOP; entity->pc++; } @@ -3765,7 +247,7 @@ static s32 func_801A70D0(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A2198(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -3843,7 +325,7 @@ static s32 func_801A72A4(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A2198(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -3851,7 +333,7 @@ static s32 func_801A72A4(EffectEntity *entity) { /** @brief Opcode handler: move the entity onto the model's second point. */ static void func_801A7354(EffectEntity *entity) { - func_801A24B0(entity, &entity->pos); + effectSlotAnchor1(entity, &entity->pos); entity->pc++; } @@ -3861,7 +343,7 @@ static void func_801A7390(EffectEntity *entity) { s32 i; for (i = 0; i < D_801D3DF0[entity->unk024]; i++) { - child = func_801A21C0(&D_801D7314, func_801A6EA4, 0xC8, entity); + child = effectSpawnTask(&D_801D7314, func_801A6EA4, 0xC8, entity); ((VECTOR *)&child->unk030)->vx = ONE; ((VECTOR *)&child->unk030)->vy = ONE; ((VECTOR *)&child->unk030)->vz = ONE; @@ -3901,7 +383,7 @@ static s32 func_801A7500(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A2198(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -3911,8 +393,8 @@ static s32 func_801A7500(EffectEntity *entity) { static void func_801A75A0(EffectEntity *entity) { s16 y; - func_801A2480(entity, &entity->pos); - y = func_801A2B10(entity); + effectSlotAnchor0(entity, &entity->pos); + y = effectModelBottom(entity); entity->pos.vy = y; entity->unk040.vy = (0x100 - y) / 18; entity->pc++; @@ -3925,8 +407,8 @@ static void func_801A7614(EffectEntity *entity) { entity->pos.vy += entity->unk040.vy; for (i = 0; i < D_801D3E2C[entity->unk024]; i++) { - spark = func_801A21C0(&D_801E74F4, func_801A54DC, 0x84, entity); - func_801A0A64(spark, 0x200); + spark = effectSpawnTask(&D_801E74F4, func_801A54DC, 0x84, entity); + effectScatter(spark, 0x200); spark->pos.vy = entity->pos.vy; ((VECTOR *)&spark->unk030)->vx = ONE; ((VECTOR *)&spark->unk030)->vy = ONE; @@ -3957,7 +439,7 @@ static s32 func_801A7730(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A2198(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -3967,8 +449,8 @@ static s32 func_801A7730(EffectEntity *entity) { static void func_801A77D0(EffectEntity *entity) { s16 y; - func_801A2480(entity, &entity->pos); - y = func_801A2B10(entity); + effectSlotAnchor0(entity, &entity->pos); + y = effectModelBottom(entity); entity->pos.vy = y; entity->unk040.vy = (0x100 - y) / 20; entity->pc++; @@ -3981,9 +463,9 @@ static void func_801A7844(EffectEntity *entity) { entity->pos.vy += entity->unk040.vy; for (i = 0; i < D_801D3E68[entity->unk024]; i++) { - spark = func_801A21C0(&D_801E74F4, func_801A533C, 0x84, entity); + spark = effectSpawnTask(&D_801E74F4, func_801A533C, 0x84, entity); spark->pos.vy = entity->pos.vy - 0x180; - func_801A0A64(spark, 0x200); + effectScatter(spark, 0x200); } if (entity->unk024 >= 0x14) { entity->flags |= EFFECT_FLAG_STOP; @@ -4010,7 +492,7 @@ static s32 func_801A7948(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A2198(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -4022,12 +504,12 @@ static void func_801A79E8(EffectEntity *entity) { EffectRender *render = &D_801ED304; s16 y; - func_801A2480(entity, &entity->pos); + effectSlotAnchor0(entity, &entity->pos); y = entity->pos.vy - 0x600; entity->pos.vy = y; entity->unk040.vy = (0x100 - y) / 18; - func_801A1088(render, &D_801ED2F4, &D_801E7504, &D_801E7534, - &D_800EF2D0[entity->unk02D], entity->unk02D); + effectRenderReset(render, &D_801ED2F4, &D_801E7504, &D_801E7534, + &D_800EF2D0[entity->unk02D], entity->unk02D); render->r = 0x40; render->g = 0x40; render->b = 0x40; @@ -4069,20 +551,20 @@ static s32 func_801A7BA8(EffectEntity *entity) { handlers[entity->pc](entity); if (entity->flags & EFFECT_FLAG_UNK08) { render = &D_801ED304; - func_801A2480(entity, &point); - entity->unk052 = func_801A0A34(&D_800F02C8); + effectSlotAnchor0(entity, &point); + entity->unk052 = effectMatrixHeading(&D_800F02C8); entity->pos.vx = point.vx + rsin(entity->unk052) / 4; entity->pos.vz = point.vz + rcos(entity->unk052) / 4; render->unk0B4.vy = entity->pos.vy; - func_801A1144(render, &entity->pos, 0x280, 0x1C0, 0x140, 0xF4, 0x80, 0x40); - func_801A205C(render); + effectRenderSetTexture(render, &entity->pos, 0x280, 0x1C0, 0x140, 0xF4, 0x80, 0x40); + effectRenderModel(render); } entity->unk024++; if (entity->flags & EFFECT_FLAG_STOP) { if (entity->wait != 0) { return 0; } - func_801A2198(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -4092,27 +574,27 @@ static s32 func_801A7BA8(EffectEntity *entity) { static void func_801A7D18(EffectEntity *entity) { EffectEntity *child; - func_801A21C0(&D_801E74F4, func_801A5C24, 0x84, entity); - child = func_801A21C0(&D_801D7314, func_801A70D0, 0xC8, entity); + effectSpawnTask(&D_801E74F4, func_801A5C24, 0x84, entity); + child = effectSpawnTask(&D_801D7314, func_801A70D0, 0xC8, entity); child->unk0C4 = 0; - func_801A21C0(&D_801D7314, func_801A7500, 0xC8, entity); - func_801A21C0(&D_801D7314, func_801A7730, 0xC8, entity); - func_801A21C0(&D_801D7314, func_801A72A4, 0xC8, entity); + effectSpawnTask(&D_801D7314, func_801A7500, 0xC8, entity); + effectSpawnTask(&D_801D7314, func_801A7730, 0xC8, entity); + effectSpawnTask(&D_801D7314, func_801A72A4, 0xC8, entity); entity->pc++; } /** @brief Opcode handler: start the two child scripts this step drives. */ static void func_801A7DDC(EffectEntity *entity) { - EffectEntity *child = func_801A21C0(&D_801D7314, func_801A70D0, 0xC8, entity); + EffectEntity *child = effectSpawnTask(&D_801D7314, func_801A70D0, 0xC8, entity); child->unk0C4 = 0x800; - func_801A21C0(&D_801D7314, func_801A7BA8, 0xC8, entity); + effectSpawnTask(&D_801D7314, func_801A7BA8, 0xC8, entity); entity->pc++; } /** @brief Opcode handler: start a child script in mode @c 0xC00. */ static void func_801A7E58(EffectEntity *entity) { - EffectEntity *child = func_801A21C0(&D_801D7314, func_801A70D0, 0xC8, entity); + EffectEntity *child = effectSpawnTask(&D_801D7314, func_801A70D0, 0xC8, entity); child->unk0C4 = 0xC00; entity->pc++; @@ -4120,7 +602,7 @@ static void func_801A7E58(EffectEntity *entity) { /** @brief Opcode handler: start a child script in mode @c 0xE00. */ static void func_801A7EB0(EffectEntity *entity) { - EffectEntity *child = func_801A21C0(&D_801D7314, func_801A70D0, 0xC8, entity); + EffectEntity *child = effectSpawnTask(&D_801D7314, func_801A70D0, 0xC8, entity); child->unk0C4 = 0xE00; entity->pc++; @@ -4129,7 +611,7 @@ static void func_801A7EB0(EffectEntity *entity) { /** @brief Opcode handler: start the third child script once the count passes 12. */ static void func_801A7F08(EffectEntity *entity) { if (entity->unk024 >= 0xC) { - func_801A21C0(&D_801D7314, func_801A7948, 0xC8, entity); + effectSpawnTask(&D_801D7314, func_801A7948, 0xC8, entity); entity->pc++; } } @@ -4153,8 +635,8 @@ static s32 func_801A7FE8(EffectEntity *entity) { func_801A7EB0, func_801A7F08, func_801A7F68, func_801A7FE0 }; - func_801A234C(entity); - func_801A26DC(entity); + effectCacheSlotAnchors(entity); + effectUpdateModelBounds(entity); handlers[entity->pc](entity); if (entity->unk024 == 0) { func_800C4764(D_801A83D0, 0, 0x80); @@ -4164,7 +646,7 @@ static s32 func_801A7FE8(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A2198(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -4178,7 +660,7 @@ static void func_801A80DC(EffectEntity *entity) { /** @brief Opcode handler: start a task running @ref func_801A7FE8. */ static void func_801A80F0(EffectEntity *entity) { entity->unk063 = 1; - func_801A21C0(&D_801D4104, func_801A7FE8, 0x58, entity); + effectSpawnTask(&D_801D4104, func_801A7FE8, 0x58, entity); entity->pc++; } @@ -4213,16 +695,16 @@ static void func_801A81C8(EffectEntity *entity) { * * @return 2 once the script has stopped and its children have drained. */ -static s32 func_801A81D0(EffectEntity *entity) { +s32 func_801A81D0(EffectEntity *entity) { EffectHandler handlers[5] = { func_801A80DC, func_801A80F0, func_801A8148, func_801A8198, func_801A81C8 }; if (entity->unk05C & 1) { - D_801D3EB8 = D_801D3EBC; + g_effectPrimCursor = D_801D3EBC; } else { - D_801D3EB8 = D_801D3EC0; + g_effectPrimCursor = D_801D3EC0; } - func_801A2510(entity); + effectCacheAnimSlotAnchors(entity); handlers[entity->pc](entity); entity->unk05E = func_800B2B68(&D_801D4104); entity->unk05E += func_800B2B68(&D_801D7314); @@ -4233,7 +715,7 @@ static s32 func_801A81D0(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A2198(entity); + effectReleaseWait(entity); return 2; } return 0; diff --git a/src/effect/effect_025_anim.c b/src/effect/effect_025_anim.c new file mode 100644 index 00000000..09bfd2f7 --- /dev/null +++ b/src/effect/effect_025_anim.c @@ -0,0 +1,66 @@ +/** + * @file effect_025_anim.c + * @brief Cura: draw an animation frame at the entity, upright or rolled. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/common.h" +#include "effect/effect_025_anim.h" + +/** @brief Draw @p anim upright at the entity's position, tinted with @p colour. */ +void func_801A4FE0(EffectEntity *entity, BattleSpriteAnim *anim, + CVECTOR *colour) { + MATRIX m; + BattleSpritePrim *prim; + + if (entity->flags & EFFECT_FLAG_DONE) { + return; + } + effectMatrixIdentity(&m); + effectMatrixRotX(&m, 0x400); + m.t[0] = entity->pos.vx; + m.t[1] = 0; + m.t[2] = entity->pos.vz; + CompMatrix(&D_800F02C8, &m, &m); + SetRotMatrix(&m); + SetTransMatrix(&m); + prim = func_800B3698(sizeof(BattleSpritePrim)); + prim->anim = anim; + prim->frame = entity->unk050; + prim->flags = BATTLE_SPRITE_FLAG_COLOUR; + prim->colour = *colour; + g_effectPrimCursor = func_800C9E10(prim, D_800FA5E8->ot, 2, g_effectPrimCursor); + func_800B36B8(sizeof(BattleSpritePrim)); +} + +/** @brief As @ref func_801A4FE0, rolled by @p angle and scaled by @p scale. */ +void func_801A50D4(EffectEntity *entity, BattleSpriteAnim *anim, + CVECTOR *colour, s16 angle, s16 scale) { + MATRIX m; + VECTOR scaleVec; + BattleSpritePrim *prim; + + if (entity->flags & EFFECT_FLAG_DONE) { + return; + } + scaleVec.vz = scale; + scaleVec.vy = scale; + scaleVec.vx = scale; + effectMatrixIdentity(&m); + effectMatrixRotY(&m, angle); + effectMatrixRotX(&m, 0x400); + ScaleMatrix(&m, &scaleVec); + m.t[0] = entity->pos.vx; + m.t[1] = 0; + m.t[2] = entity->pos.vz; + CompMatrix(&D_800F02C8, &m, &m); + SetRotMatrix(&m); + SetTransMatrix(&m); + prim = func_800B3698(sizeof(BattleSpritePrim)); + prim->anim = anim; + prim->frame = entity->unk050; + prim->flags = BATTLE_SPRITE_FLAG_COLOUR; + prim->colour = *colour; + g_effectPrimCursor = func_800C9E10(prim, D_800FA5E8->ot, 2, g_effectPrimCursor); + func_800B36B8(sizeof(BattleSpritePrim)); +} diff --git a/src/effect/effect_025_entry.c b/src/effect/effect_025_entry.c new file mode 100644 index 00000000..74647273 --- /dev/null +++ b/src/effect/effect_025_entry.c @@ -0,0 +1,41 @@ +/** + * @file effect_025_entry.c + * @brief Cura: the overlay entry point. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/entity.h" +#include "effect/effect_025.h" +#include "effect/effect_025_entry.h" + +/** + * @brief Start the effect's script and hand back its task pool. + * + * Builds the four task pools, allocates the root entity, seeds it from the + * animation set, and publishes the two prim banks the renderer draws through. + * + * @param animSet Animation set the effect plays. + * @return The pool the root entity lives in. + */ +void *func_801A0000(EffectAnimSet *animSet) { + EffectEntity *entity; + u8 slot; + + slot = animSet->anims->parts->unk000; + func_800B2A00(&D_801D3F94, &D_801D3EC4, 0x64, 2); + entity = effectSpawnTask(&D_801D3F94, func_801A81D0, 0x64, NULL); + entity->animSet = animSet; + entity->unk02D = slot; + entity->unk05A = animSet->anims->unk010; + entity->unk058 = animSet->anims->unk011; + if (!(animSet->flags & EFFECT_ANIMSET_FLAG_LOADED)) { + func_800C3BE0(&D_801A8E50); + func_800BB084(&D_801A9698); + } + D_801D3EBC = &D_801A9698; + D_801D3EC0 = &D_801BB698; + func_800B2A00(&D_801D4104, &D_801D3FA4, 0x58, 4); + func_800B2A00(&D_801D7314, &D_801D4114, 0xC8, 0x40); + func_800B2A00(&D_801E74F4, &D_801D7324, 0x84, 0x1F4); + return &D_801D3F94; +} diff --git a/src/effect/effect_025_motes.c b/src/effect/effect_025_motes.c new file mode 100644 index 00000000..fc8c0a3f --- /dev/null +++ b/src/effect/effect_025_motes.c @@ -0,0 +1,328 @@ +/** + * @file effect_025_motes.c + * @brief Cura: the rising, falling and arc mote scripts. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libc.h" +#include "effect/lib/entity.h" +#include "effect/lib/sprite.h" +#include "effect/lib/step.h" +#include "effect/lib/scatter.h" +#include "effect/effect_025.h" +#include "effect/effect_025_anim.h" +#include "effect/effect_025_motes.h" + +static void func_801A524C(EffectEntity *entity); +static void func_801A527C(EffectEntity *entity); +static void func_801A52D8(EffectEntity *entity); +static void func_801A5334(EffectEntity *entity); +static void func_801A5404(EffectEntity *entity); +static void func_801A548C(EffectEntity *entity); +static void func_801A54D4(EffectEntity *entity); +static void func_801A55C0(EffectEntity *entity); +static void func_801A5620(EffectEntity *entity); +static void func_801A56A8(EffectEntity *entity); +static s32 func_801A56B0(EffectEntity *entity); +static void func_801A5778(EffectEntity *entity); +static void func_801A581C(EffectEntity *entity); +static void func_801A5884(EffectEntity *entity); +static void func_801A58CC(EffectEntity *entity); +static s32 func_801A58D4(EffectEntity *entity); +static void func_801A5AE8(EffectEntity *entity); +static void func_801A5BD4(EffectEntity *entity); +static void func_801A5C1C(EffectEntity *entity); + +/** @brief Advance the animation frame, wrapping at its limit. */ +static void func_801A524C(EffectEntity *entity) { + entity->unk050++; + if (entity->unk052 < entity->unk050) { + entity->unk050 = 0; + } +} + +/** @brief Opcode handler: start a rise with a randomised speed. */ +static void func_801A527C(EffectEntity *entity) { + entity->unk04C = &D_801A8820; + entity->unk052 = 0xA; + entity->unk054 = -0x400; + entity->unk05A = (rand() & 7) + 4; + entity->pc++; +} + +/** @brief Opcode handler: rise by the current speed until the count runs out. */ +static void func_801A52D8(EffectEntity *entity) { + entity->pos.vy += entity->unk05A; + if (effectStepCounter(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} + +/** @brief Opcode handler: no-op. */ +static void func_801A5334(EffectEntity *entity) { +} + +/** + * @brief Script dispatcher: run this frame's step, draw, and retire the entity. + * + * @return 2 once the script has stopped and its children have drained, 0 while + * it is still running. + */ +s32 func_801A533C(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A527C, func_801A52D8, func_801A5334 }; + + handlers[entity->pc](entity); + if (entity->pos.vy > 0) { + entity->flags |= EFFECT_FLAG_STOP | EFFECT_FLAG_DONE; + } + effectDrawSprite(entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + effectReleaseWait(entity); + return 2; + } + return 0; +} + +/** @brief Opcode handler: pick one of two table pairs at random and start a fall. */ +static void func_801A5404(EffectEntity *entity) { + if (rand() & 1) { + entity->unk04C = &D_801A84E8; + entity->unk070 = &D_801A8AF8; + } else { + entity->unk04C = &D_801A894C; + entity->unk070 = &D_801A8CA4; + } + entity->unk07A = rand() & 0xFFF; + entity->unk052 = 0xF; + entity->unk054 = -0x200; + entity->pc++; +} + +/** @brief Opcode handler: hold still until the count runs out. */ +static void func_801A548C(EffectEntity *entity) { + if (effectStepCounter(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} + +/** @brief Opcode handler: no-op. */ +static void func_801A54D4(EffectEntity *entity) { +} + +/** @brief Script dispatcher: the falling-mote script, drawn spun and tinted. */ +s32 func_801A54DC(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A5404, func_801A548C, func_801A54D4 }; + CVECTOR colour; + + handlers[entity->pc](entity); + effectDrawSprite(entity); + entity->unk07A = (entity->unk07A + 0x40) & 0xFFF; + colour.b = 0x30; + colour.g = 0x30; + colour.r = 0x30; + func_801A50D4(entity, entity->unk070, &colour, entity->unk07A, 0x2000); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + effectReleaseWait(entity); + return 2; + } + return 0; +} + +/** @brief Opcode handler: start a fast rise that gravity pulls back. */ +static void func_801A55C0(EffectEntity *entity) { + entity->unk04C = &D_801A8820; + entity->unk050 = 0; + entity->unk052 = 0xA; + entity->unk05A = (rand() & 7) + 0x20; + ((SVECTOR *)entity->unk060)->vy = -3; + entity->pc++; +} + +/** @brief Opcode handler: apply gravity, then rise until the count runs out. */ +static void func_801A5620(EffectEntity *entity) { + entity->unk05A += ((SVECTOR *)entity->unk060)->vy; + if (entity->unk05A < 2) { + entity->unk05A = 2; + } + entity->pos.vy += entity->unk05A; + if (effectStepCounter(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} + +/** @brief Opcode handler: no-op. */ +static void func_801A56A8(EffectEntity *entity) { +} + +/** + * @brief Script dispatcher: run this frame's step, draw, and retire the entity. + * + * @return 2 once the script has stopped and its children have drained, 0 while + * it is still running. + */ +static s32 func_801A56B0(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A55C0, func_801A5620, func_801A56A8 }; + + handlers[entity->pc](entity); + if (entity->pos.vy > 0) { + entity->flags |= EFFECT_FLAG_STOP | EFFECT_FLAG_DONE; + } + effectDrawSprite(entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + effectReleaseWait(entity); + return 2; + } + return 0; +} + +/** @brief Opcode handler: keep the start point and start a randomised arc. */ +static void func_801A5778(EffectEntity *entity) { + entity->unk078 = rand() & 0xFFF; + *(SVECTOR *)&entity->unk068 = entity->pos; + entity->unk04C = &D_801A8708; + entity->unk052 = 1; + entity->unk05A = (rand() & 0x3F) - 0x30; + entity->unk07C = (rand() & 0x1F) + 0x50; + entity->unk07E = -entity->unk07C / 16; + entity->pc++; +} + +/** @brief Opcode handler: switch to the burst table once the frame count passes 16. */ +static void func_801A581C(EffectEntity *entity) { + func_801A524C(entity); + if (entity->unk024 >= 0x10) { + entity->unk04C = &D_801A875C; + entity->unk052 = 6; + entity->unk050 = 0; + entity->flags |= EFFECT_FLAG_UNK10; + entity->pc++; + } +} + +/** @brief Opcode handler: hold still until the count runs out. */ +static void func_801A5884(EffectEntity *entity) { + if (effectStepCounter(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} + +/** @brief Opcode handler: no-op. */ +static void func_801A58CC(EffectEntity *entity) { +} + +/** @brief Script dispatcher: an arc under gravity that trails motes behind it. */ +static s32 func_801A58D4(EffectEntity *entity) { + EffectHandler handlers[4] = { func_801A5778, func_801A581C, func_801A5884, + func_801A58CC }; + EffectEntity *spark; + CVECTOR colour; + + handlers[entity->pc](entity); + entity->unk07C += entity->unk07E; + if (entity->unk07C < 0) { + entity->unk07C = 0; + } + entity->unk068 += rsin(entity->unk040.vy) * entity->unk07C / 4096; + entity->unk06C += rcos(entity->unk040.vy) * entity->unk07C / 4096; + entity->pos.vx = entity->unk068; + entity->pos.vz = entity->unk06C; + entity->unk05A += 0xA; + entity->pos.vy += entity->unk05A; + if (!(entity->flags & EFFECT_FLAG_UNK10) && (entity->unk024 & 1)) { + spark = effectSpawnTask(&D_801E74F4, func_801A56B0, 0x84, entity); + effectScatter(spark, 0x80); + spark->pos.vy = entity->pos.vy; + ((VECTOR *)&spark->unk030)->vx = ONE; + ((VECTOR *)&spark->unk030)->vy = ONE; + ((VECTOR *)&spark->unk030)->vz = ONE; + } + if (entity->pos.vy > 0) { + entity->flags |= EFFECT_FLAG_STOP | EFFECT_FLAG_DONE; + } + effectDrawSprite(entity); + colour.b = 0x50; + colour.g = 0x50; + colour.r = 0x50; + func_801A4FE0(entity, entity->unk04C, &colour); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + effectReleaseWait(entity); + return 2; + } + return 0; +} + +/** @brief Opcode handler: ring eight sparks around the model's first point. */ +static void func_801A5AE8(EffectEntity *entity) { + EffectEntity *spark; + s32 angle; + s32 i; + + effectSlotAnchor0(entity, &entity->pos); + entity->unk04C = &D_801A8694; + entity->unk052 = 3; + entity->pos.vy -= 0x600; + angle = rand() & 0xFFF; + for (i = 0; i < 8; i++) { + spark = effectSpawnTask(&D_801E74F4, func_801A58D4, 0x84, entity); + ((VECTOR *)&spark->unk030)->vx = ONE; + ((VECTOR *)&spark->unk030)->vy = ONE; + ((VECTOR *)&spark->unk030)->vz = ONE; + spark->unk040.vy = angle + (rand() & 0x7F); + angle = (angle + 0x200) & 0xFFF; + } + entity->pc++; +} + +/** @brief Opcode handler: hold still until the count runs out. */ +static void func_801A5BD4(EffectEntity *entity) { + if (effectStepCounter(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} + +/** @brief Opcode handler: no-op. */ +static void func_801A5C1C(EffectEntity *entity) { +} + +/** @brief Script dispatcher: the rising-mote script, drawn tinted. */ +s32 func_801A5C24(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A5AE8, func_801A5BD4, func_801A5C1C }; + CVECTOR colour; + + handlers[entity->pc](entity); + effectDrawSprite(entity); + colour.b = 0x20; + colour.g = 0x20; + colour.r = 0x20; + func_801A4FE0(entity, entity->unk04C, &colour); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + effectReleaseWait(entity); + return 2; + } + return 0; +} diff --git a/src/effect/effect_025_scroll.c b/src/effect/effect_025_scroll.c new file mode 100644 index 00000000..06e2c037 --- /dev/null +++ b/src/effect/effect_025_scroll.c @@ -0,0 +1,146 @@ +/** + * @file effect_025_scroll.c + * @brief Cura: scroll the textured quad list's V coordinates. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/emit_mode.h" +#include "effect/effect_025_scroll.h" + +/** + * @name Scrolled UV words + * + * A UV pair is one halfword, U in the low byte and V in the high one, so a + * step of one V unit is 0x100; the third and fourth pairs of a quad share a + * word. A scroll masks V out, steps it and puts the rest back, and a step that + * carries out of the byte has wrapped. + * @{ + */ +#define EFFECT_UV_V 0xFF00 /**< V of a pair, in place. */ +#define EFFECT_UV_KEEP 0xFFFF00FF /**< What a scroll of one pair leaves alone. */ +#define EFFECT_UV_KEEP23 0x00FF00FF /**< Both U bytes of the shared third and fourth pair. */ +#define EFFECT_UV_WRAP 0x8000 /**< One V byte's worth of wrap. */ +#define EFFECT_UV_SCROLL_STEP (4 << 8) /**< The fixed scroll: four whole V units. */ +/** @} */ + +/** + * @brief Words of a mesh stream's block header ahead of the quad list's count. + * + * The scroll walkers step over the stream's own header, then this, to reach + * the count of gouraud textured quads and the quads themselves. + */ +#define EFFECT_QUAD_LIST_HEADER 7 + +static void func_801A4BFC(s32 *header); + +/** + * @brief Scroll the textured quad list's V coordinates by one fixed step. + * + * The list @p header names holds the gouraud textured quads; each carries four + * UV pairs, the last two packed into one word. Only the V byte of each moves, + * and all four wrap together so a quad never tears. + * + * @param header Mesh stream to walk. + */ +static void func_801A4BFC(s32 *header) { + EffectEmitGouraudTexQuad *e; + u32 keep[3]; + u32 ch[4]; + u32 uv23; + s32 count; + s32 i; + + header = (s32 *)((u8 *)header + header[0] / 4 * 4); + header += EFFECT_QUAD_LIST_HEADER; + count = *header; + header++; + for (i = 0; i < count; i++) { + e = (EffectEmitGouraudTexQuad *)header; + ch[0] = e->uv0; + keep[0] = ch[0] & EFFECT_UV_KEEP; + ch[0] &= EFFECT_UV_V; + ch[1] = e->uv1; + keep[1] = ch[1] & EFFECT_UV_KEEP; + ch[1] &= EFFECT_UV_V; + uv23 = e->uv23; + ch[3] = uv23; + ch[2] = uv23; + keep[2] = uv23 & EFFECT_UV_KEEP23; + ch[2] &= EFFECT_UV_V; + ch[3] = (ch[3] >> 16) & EFFECT_UV_V; + ch[0] -= EFFECT_UV_SCROLL_STEP; + ch[1] -= EFFECT_UV_SCROLL_STEP; + ch[2] -= EFFECT_UV_SCROLL_STEP; + ch[3] -= EFFECT_UV_SCROLL_STEP; + if (ch[0] > EFFECT_UV_V || ch[1] > EFFECT_UV_V || ch[2] > EFFECT_UV_V || ch[3] > EFFECT_UV_V) { + ch[0] += EFFECT_UV_WRAP; + ch[1] += EFFECT_UV_WRAP; + ch[2] += EFFECT_UV_WRAP; + ch[3] += EFFECT_UV_WRAP; + } + ch[0] &= EFFECT_UV_V; + ch[1] &= EFFECT_UV_V; + ch[2] &= EFFECT_UV_V; + ch[3] = (ch[3] & EFFECT_UV_V) << 16; + e->uv0 = keep[0] | ch[0]; + e->uv1 = keep[1] | ch[1]; + e->uv23 = keep[2] | ch[2] | ch[3]; + header += sizeof(EffectEmitGouraudTexQuad) / sizeof(s32); + } +} + +/** + * @brief Scroll the textured quad list's V coordinates by @p delta. + * + * @ref func_801A4BFC with the step supplied by the caller. + * + * @param header Mesh stream to walk. + * @param delta Step in whole V units. + */ +void func_801A4D9C(s32 *header, s32 delta) { + EffectEmitGouraudTexQuad *e; + u32 keep[3]; + u32 ch[4]; + u32 uv23; + s32 count; + s32 i; + + header = (s32 *)((u8 *)header + header[0] / 4 * 4); + header += EFFECT_QUAD_LIST_HEADER; + count = *header; + header++; + delta <<= 8; + for (i = 0; i < count; i++) { + e = (EffectEmitGouraudTexQuad *)header; + ch[0] = e->uv0; + keep[0] = ch[0] & EFFECT_UV_KEEP; + ch[0] &= EFFECT_UV_V; + ch[1] = e->uv1; + keep[1] = ch[1] & EFFECT_UV_KEEP; + ch[1] &= EFFECT_UV_V; + uv23 = e->uv23; + ch[3] = uv23; + ch[2] = uv23; + keep[2] = uv23 & EFFECT_UV_KEEP23; + ch[2] &= EFFECT_UV_V; + ch[3] = (ch[3] >> 16) & EFFECT_UV_V; + ch[0] += delta; + ch[1] += delta; + ch[2] += delta; + ch[3] += delta; + if (ch[0] > EFFECT_UV_V || ch[1] > EFFECT_UV_V || ch[2] > EFFECT_UV_V || ch[3] > EFFECT_UV_V) { + ch[0] -= EFFECT_UV_WRAP; + ch[1] -= EFFECT_UV_WRAP; + ch[2] -= EFFECT_UV_WRAP; + ch[3] -= EFFECT_UV_WRAP; + } + ch[0] &= EFFECT_UV_V; + ch[1] &= EFFECT_UV_V; + ch[2] &= EFFECT_UV_V; + ch[3] = (ch[3] & EFFECT_UV_V) << 16; + e->uv0 = keep[0] | ch[0]; + e->uv1 = keep[1] | ch[1]; + e->uv23 = keep[2] | ch[2] | ch[3]; + header += sizeof(EffectEmitGouraudTexQuad) / sizeof(s32); + } +} diff --git a/src/effect/effect_028.c b/src/effect/effect_028.c index 7eca2a5c..929f8133 100644 --- a/src/effect/effect_028.c +++ b/src/effect/effect_028.c @@ -9,6 +9,16 @@ #include "psxsdk/libgpu.h" #include "psxsdk/inline_c.h" #include "effect/effect_028.h" +#include "effect/lib/tint.h" +#include "effect/lib/bankclear.h" +#include "effect/lib/tables.h" +#include "effect/lib/drawscript.h" +#include "effect/lib/particle.h" +#include "effect_params.h" +#include "effect/lib/common.h" +#include "effect/lib/rotz.h" +#include "effect/lib/heading.h" +#include "effect/lib/entity.h" #include "btl_entity.h" #include "battle/bc_object11.h" #include "battle/bc_object15.h" @@ -17,4158 +27,37 @@ /** @brief Where in the scratchpad the effect keeps its view matrix. */ #define EFFECT_SCRATCHPAD ((MATRIX *)0x1F8002E0) -/** @brief Stride between the prim and frame banks carved out of the TIM. */ -#define EFFECT_BANK_SIZE 0x6000 - /** @brief @ref EffectEntity::unk05C -- the frame counter's low bit picks a bank. */ #define EFFECT_FRAME_ODD 0x1 -/** @brief Pack a screen corner the way the tint quads carry them. */ -#define EFFECT_TINT_XY(x, y) ((x) | ((y) << 16)) - -/** @brief Prim code of a tint quad: a semi-transparent flat quad. */ -#define EFFECT_TINT_CODE 0x2A - -/** @brief Bytes cleared in each prim bank when the script restarts. */ -#define EFFECT_BANK0_BYTES 0x25F8 -/** @brief How far the mote ring runs before it wraps. */ -#define EFFECT_MOTE_COUNT 0x59 -/** @brief How far the node ring runs before it wraps. */ -#define EFFECT_NODE_COUNT 0x8B -#define EFFECT_BANK1_BYTES 0x16F80 - - -/** - * @name Prim emitter flags -- @ref EffectPrimBuild::flags. - * @{ - */ -#define EFFECT_EMIT_SEMITRANS 0x1 -#define EFFECT_EMIT_OPAQUE 0x4 -#define EFFECT_EMIT_TWO_SIDED 0x10 -#define EFFECT_EMIT_DEPTH_CUE 0x40 - -/* The gouraud emitters read their own copy of each of the four bits above. */ -#define EFFECT_EMIT_G_SEMITRANS 0x2 -#define EFFECT_EMIT_G_OPAQUE 0x8 -#define EFFECT_EMIT_G_TWO_SIDED 0x20 -#define EFFECT_EMIT_G_DEPTH_CUE 0x80 -#define EFFECT_EMIT_TPAGE_SET 0x100 -#define EFFECT_EMIT_CLUT_SET 0x200 -#define EFFECT_EMIT_TPAGE_ADD 0x400 -#define EFFECT_EMIT_CLUT_ADD 0x800 -/** @brief Set, the vertex buffer the stream names is already resolved. */ -#define EFFECT_EMIT_VERTS_SET 0x2000 -/** @brief Set, the emitters keep the depth the packet came in with. */ -#define EFFECT_EMIT_KEEP_DEPTH 0x1000 -/** @} */ - -/** - * @name Prim emitter clip result - * - * One bit per projected vertex per axis, accumulated in the emitters' @c reject. - * A prim is dropped only when every one of its vertices left the screen on the - * same axis, so a prim straddling an edge still draws. - * @{ - */ -#define EFFECT_CLIP_X0 0x1 -#define EFFECT_CLIP_X1 0x2 -#define EFFECT_CLIP_X2 0x4 -#define EFFECT_CLIP_X3 0x8 -#define EFFECT_CLIP_Y0 0x10 -#define EFFECT_CLIP_Y1 0x20 -#define EFFECT_CLIP_Y2 0x40 -#define EFFECT_CLIP_Y3 0x80 -#define EFFECT_CLIP_TRI_X (EFFECT_CLIP_X0 | EFFECT_CLIP_X1 | EFFECT_CLIP_X2) -#define EFFECT_CLIP_TRI_Y (EFFECT_CLIP_Y0 | EFFECT_CLIP_Y1 | EFFECT_CLIP_Y2) -#define EFFECT_CLIP_QUAD_X (EFFECT_CLIP_TRI_X | EFFECT_CLIP_X3) -#define EFFECT_CLIP_QUAD_Y (EFFECT_CLIP_TRI_Y | EFFECT_CLIP_Y3) -/** @} */ - -/** - * @name Prim emitter clip test - * - * A projected vertex is off screen once its unsigned screen coordinate reaches - * these; a prim is dropped only when all of its vertices fail the same axis. - * @{ - */ -#define EFFECT_EMIT_CLIP_X 0x141 -#define EFFECT_EMIT_CLIP_Y 0xD9 -/** @} */ - -/** @brief GTE @c FLAG bits 18 and 17: SZ3/OTZ saturated, divide overflow. */ -#define EFFECT_GTE_OUT_OF_RANGE ((1 << 18) | (1 << 17)) - - - - -/** @brief The flat triangle an emitter writes; six words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ u16 x1; - /* 0x0E */ u16 y1; - /* 0x10 */ u16 x2; - /* 0x12 */ u16 y2; -} EffectTri; /* 0x14 */ - -/** @brief One flat triangle in the mesh stream: a colour and three vertex slots. */ -typedef struct { - /* 0x00 */ u32 colour; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u8 pad00A[0xC - 0xA]; -} EffectEmitTri; /* 0xC */ - -/** @brief The flat quad an emitter writes; seven words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ u16 x1; - /* 0x0E */ u16 y1; - /* 0x10 */ u16 x2; - /* 0x12 */ u16 y2; - /* 0x14 */ u16 x3; - /* 0x16 */ u16 y3; -} EffectQuad; /* 0x18 */ - -/** @brief One flat quad in the mesh stream: a colour and four vertex slots. */ -typedef struct { - /* 0x00 */ u32 colour; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u16 idx3; -} EffectEmitQuad; /* 0xC */ - -/** - * @brief A prim's UV pair and the id that shares its word. - * - * The stream carries both halves together, so the emitters add the build's - * offset to the whole word and only then patch the id half. - */ -typedef union { - u32 word; - struct { - /* 0x00 */ u16 uv; /**< U in the low byte, V in the high. */ - /* 0x02 */ u16 id; /**< CLUT id or texture page, by slot. */ - } h; -} EffectPrimUV; /* 0x4 */ - -/** @brief The textured triangle an emitter writes; nine words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ - /* 0x10 */ u16 x1; - /* 0x12 */ u16 y1; - /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ - /* 0x18 */ u16 x2; - /* 0x1A */ u16 y2; - /* 0x1C */ EffectPrimUV uv2; -} EffectTexTri; /* 0x20 */ - -/** @brief One textured triangle in the mesh stream. */ -typedef struct { - /* 0x00 */ u32 colour; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ - /* 0x0C */ u32 uv0; - /* 0x10 */ u32 uv1; -} EffectEmitTexTri; /* 0x14 */ - -/** @brief The gouraud triangle an emitter writes; eight words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb0; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ u32 rgb1; - /* 0x10 */ u16 x1; - /* 0x12 */ u16 y1; - /* 0x14 */ u32 rgb2; - /* 0x18 */ u16 x2; - /* 0x1A */ u16 y2; -} EffectGouraudTri; /* 0x1C */ - -/** @brief One gouraud triangle in the mesh stream: three corner colours. */ -typedef struct { - /* 0x00 */ u32 colour0; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u8 pad00A[0xC - 0xA]; - /* 0x0C */ u32 colour1; - /* 0x10 */ u32 colour2; -} EffectEmitGouraudTri; /* 0x14 */ - -/** @brief The gouraud quad an emitter writes; ten words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb0; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ u32 rgb1; - /* 0x10 */ u16 x1; - /* 0x12 */ u16 y1; - /* 0x14 */ u32 rgb2; - /* 0x18 */ u16 x2; - /* 0x1A */ u16 y2; - /* 0x1C */ u32 rgb3; - /* 0x20 */ u16 x3; - /* 0x22 */ u16 y3; -} EffectGouraudQuad; /* 0x24 */ - -/** @brief One gouraud quad in the mesh stream: four corner colours. */ -typedef struct { - /* 0x00 */ u32 colour0; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u16 idx3; - /* 0x0C */ u32 colour1; - /* 0x10 */ u32 colour2; - /* 0x14 */ u32 colour3; -} EffectEmitGouraudQuad; /* 0x18 */ - -/** @brief The gouraud textured triangle an emitter writes; eleven words. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb0; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ - /* 0x10 */ u32 rgb1; - /* 0x14 */ u16 x1; - /* 0x16 */ u16 y1; - /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ - /* 0x1C */ u32 rgb2; - /* 0x20 */ u16 x2; - /* 0x22 */ u16 y2; - /* 0x24 */ EffectPrimUV uv2; -} EffectGouraudTexTri; /* 0x28 */ - -/** @brief One gouraud textured triangle in the mesh stream. */ -typedef struct { - /* 0x00 */ u32 colour0; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ - /* 0x0C */ u32 uv0; - /* 0x10 */ u32 uv1; - /* 0x14 */ u32 colour1; - /* 0x18 */ u32 colour2; -} EffectEmitGouraudTexTri; /* 0x1C */ - -/** @brief The gouraud textured quad an emitter writes; fourteen words. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb0; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ - /* 0x10 */ u32 rgb1; - /* 0x14 */ u16 x1; - /* 0x16 */ u16 y1; - /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ - /* 0x1C */ u32 rgb2; - /* 0x20 */ u16 x2; - /* 0x22 */ u16 y2; - /* 0x24 */ EffectPrimUV uv2; - /* 0x28 */ u32 rgb3; - /* 0x2C */ u16 x3; - /* 0x2E */ u16 y3; - /* 0x30 */ EffectPrimUV uv3; -} EffectGouraudTexQuad; /* 0x34 */ - -/** - * @brief One gouraud textured quad in the mesh stream. - * - * The last two UV pairs share a word, so the build's offset is doubled into - * both halves and the sum split again on the way out. - */ -typedef struct { - /* 0x00 */ u32 colour0; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u16 idx3; - /* 0x0C */ u32 uv0; - /* 0x10 */ u32 uv1; - /* 0x14 */ u32 uv23; - /* 0x18 */ u32 colour1; - /* 0x1C */ u32 colour2; - /* 0x20 */ u32 colour3; -} EffectEmitGouraudTexQuad; /* 0x24 */ - -/** @brief The textured quad an emitter writes; eleven words including its tag. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb; - /* 0x08 */ u16 x0; - /* 0x0A */ u16 y0; - /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ - /* 0x10 */ u16 x1; - /* 0x12 */ u16 y1; - /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ - /* 0x18 */ u16 x2; - /* 0x1A */ u16 y2; - /* 0x1C */ EffectPrimUV uv2; - /* 0x20 */ u16 x3; - /* 0x22 */ u16 y3; - /* 0x24 */ EffectPrimUV uv3; -} EffectTexQuad; /* 0x28 */ - -/** @brief One textured quad in the mesh stream; its last two UV pairs share a word. */ -typedef struct { - /* 0x00 */ u32 colour; - /* 0x04 */ u16 idx0; - /* 0x06 */ u16 idx1; - /* 0x08 */ u16 idx2; - /* 0x0A */ u16 idx3; - /* 0x0C */ u32 uv0; - /* 0x10 */ u32 uv1; - /* 0x14 */ u32 uv23; -} EffectEmitTexQuad; /* 0x18 */ - - -/** - * @brief What @ref func_801A3CDC walks: the mesh stream and how to shade it. - * - * Built on the scratchpad stack @ref D_801D3CD8 walks. The overlay reserves - * battle's 0x58 bytes for it even though it fills far less. - */ -typedef struct { - /* 0x00 */ s32 *stream; /**< The mesh the packet draws. */ - /* 0x04 */ u32 *verts; /**< Where the projected points are kept. */ - /* 0x08 */ EffectPrimColor color; /**< Far colour the depth cue fades toward. */ - /* 0x0C */ s32 depth; /**< Depth-cue weight handed to DPCS. */ - /* 0x10 */ u16 tpage; /**< Texture page the textured prims take. */ - /* 0x12 */ u8 pad012[0x14 - 0x12]; - /* 0x14 */ u16 clut; /**< CLUT id the textured prims take. */ - /* 0x16 */ u8 pad016[0x18 - 0x16]; - /* 0x18 */ s32 unk018; - /* 0x1C */ u32 flags; - /* 0x20 */ s32 *cursor; /**< Walks the stream, one entry per prim kind. */ - /* 0x24 */ s32 nclip; - /* 0x28 */ u8 pad028[0x2C - 0x28]; - /* 0x2C */ s32 otz; - /* 0x30 */ u32 gteFlag; - /* 0x34 */ u8 pad034[0x58 - 0x34]; -} EffectPrimBuild; /* 0x58 */ - - -/** @brief The scratch a spawned node's aim and speed are worked out in. */ -typedef struct { - /* 0x00 */ MATRIX m; - /* 0x20 */ SVECTOR dir; /**< Straight down, then turned by @c m. */ - /* 0x28 */ SVECTOR angle; /**< The turn @c m was built from. */ - /* 0x30 */ SVECTOR spin; /**< The turn the node's own pose takes. */ - /* 0x38 */ VECTOR vel; - /* 0x48 */ s16 speedX; - /* 0x4A */ s16 speedY; - /* 0x4C */ s16 speedZ; - /* 0x4E */ s16 frame; /**< The mote's age, which indexes the tables. */ -} EffectSpawnScratch; /* 0x50 */ - - -/** - * @name Script records - * - * @ref func_801A0B5C hands back as many bytes as the script asks for, with an - * @ref EffectEntity at the front of them, so a script owns everything the - * engine leaves alone. These two lay their own state over the pose fields. - * @{ - */ - -/** @brief The entity of the script that builds and draws the list. */ -typedef struct { - /* 0x000 */ u8 pad000[0xC]; /**< The @ref EffectEntity the engine steps. */ - /* 0x00C */ EffectAnimSet *animSet; - /* 0x010 */ EffectModel *model; - /* 0x014 */ u8 pad014[0x2A - 0x14]; - /* 0x02A */ u8 anim; /**< Which of the set's animations is playing. */ - /* 0x02B */ u8 pad02B; - /* 0x02C */ u8 slot; /**< Battle slot the effect is aimed at. */ - /* 0x02D */ u8 pad02D[0x30 - 0x2D]; - /* 0x030 */ EffectPoseTables *tables; /**< The tables the script was started on. */ - /* 0x034 */ EffectDrawList list; - /* 0x268 */ u8 pad268[0x288 - 0x268]; - /* 0x288 */ s16 unk288; /**< Where the strands are seeded from. */ - /* 0x28A */ s16 unk28A; - /* 0x28C */ s16 unk28C; - /* 0x28E */ u8 pad28E[0x290 - 0x28E]; - /* 0x290 */ s16 unk290; /**< Where the trail's newest point goes. */ - /* 0x292 */ s16 unk292; - /* 0x294 */ s16 unk294; - /* 0x296 */ u8 pad296[0x298 - 0x296]; - /* 0x298 */ s16 stopFrame; /**< Frame the script starts drawing on. */ - /* 0x29A */ u16 unk29A; - /* 0x29C */ s16 unk29C; - /* 0x29E */ s16 unk29E; - /* 0x2A0 */ u8 pad2A0[0x2A4 - 0x2A0]; -} EffectDrawScript; /* 0x2A4 */ - - -/** @brief The full-screen quad the tint script draws, four to a frame. */ -typedef struct { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 rgb; /**< The step's colour; its top byte is the prim code. */ - /* 0x08 */ u32 xy0; /**< X in the low half, Y in the high. */ - /* 0x0C */ u32 xy1; - /* 0x10 */ u32 xy2; - /* 0x14 */ u32 xy3; -} EffectTintQuad; /* 0x18 */ - - -/** @brief The entity of the script that tints the screen. */ -typedef struct { - /* 0x00 */ u8 pad000[0x24]; /**< The @ref EffectEntity the engine steps. */ - /* 0x24 */ s16 frame; /**< Frames the script has run. */ - /* 0x26 */ u16 flags; /**< See @c EFFECT_FLAG_*. */ - /* 0x28 */ u8 wait; /**< Live children. */ - /* 0x29 */ u8 pad029[0x30 - 0x29]; - /* 0x30 */ EffectTintStep *steps; - /* 0x34 */ EffectTintStep tint; /**< The colour this frame's quads take. */ - /* 0x38 */ s16 step; /**< How far into @c steps the play has got. */ - /* 0x3A */ u8 pad03A[0x3C - 0x3A]; -} EffectTintScript; /* 0x3C */ - -/** @} */ - -/** @brief GTE @c FLAG bits 18 and 17: SZ3/OTZ saturated, divide overflow. */ -#define BATTLE_SPRITE_GTE_OUT_OF_RANGE ((1 << 18) | (1 << 17)) - -/** @brief The draw-mode packet an additive sprite set is linked behind. */ -#define BATTLE_SPRITE_ADDITIVE_MODE (0xE1000000 | (1 << 9) | getTPage(0, 1, 0, 0)) - -/** @brief Highest texture coordinate a sprite's right or bottom edge takes. */ -#define BATTLE_SPRITE_UV_MAX 0xFF - -static void func_801A80A4(EffectDrawNode *node); -static void func_801A9050(EffectDrawScript *script); -static void func_801A8184(EffectMote *mote); -static void func_801A87F8(void); -static void func_801A71E0(EffectMote *mote); -static void func_801A6F58(EffectMote *mote); -static void func_801A75F8(EffectMote *mote); -static void func_801A73E8(EffectMote *mote); -static EffectTri *func_801A3CDC(EffectPrimBuild *prim, u32 *ot, s32 otShift, EffectTri *head); -static EffectTri *func_801A20EC(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); -static EffectTri *func_801A23B8(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); -static EffectTri *func_801A26DC(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); -static EffectTri *func_801A2A64(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); -static EffectTri *func_801A2E48(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); -static EffectTri *func_801A3150(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); -static EffectTri *func_801A34D8(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); -static EffectTri *func_801A3894(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); -static void func_801A1F58(EffectDrawNode *node, EffectPoseStep *step); -static void func_801A8904(EffectDrawScript *script); -static void func_801A8CE4(EffectPoseTables *tables); -static void func_801A8A1C(EffectDrawScript *script); -static void func_801A85A8(EffectDrawScript *script); -static EffectDrawNode *func_801A541C(EffectMote *owner, u8 source); -static void func_801A846C(void); -static void func_801A6074(void); -static void func_801A5FB4(EffectDrawNode *node, EffectPoseStep *step); -static POLY_FT4 *func_801A5D4C(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head); -static POLY_FT4 *func_801A5720(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head); -static s32 func_801A16A4(EffectTintScript *script); -static EffectMote *func_801A6194(void *owner, s16 source, s16 index); -static void func_801A8068(EffectDrawNode *node, s16 value); -static void func_801A631C(EffectMote *node, EffectPoseStep *step, s32 n); -static void func_801A41E0(EffectDrawNode *node, EffectPoseStep *step); -static void func_801A4ADC(EffectDrawNode *node, EffectPoseStep *step); -static void func_801A0978(MATRIX *m, s32 angle); -static void func_801A3F20(EffectDrawNode *node, EffectPoseStep *step); -void func_801A73E8(EffectMote *node); -static void func_801A75F8(EffectMote *node); - - -static void func_801A0208(void); -static void func_801A0358(u32 value); -static void func_801A0404(void); -static void func_801A0424(void); -static void func_801A042C(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, s32 abr); -static void func_801A0528(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, s32 abr); -static void func_801A0624(MATRIX *m); -static void func_801A065C(MATRIX *m, s32 angle); -static void func_801A07EC(MATRIX *m, s32 angle); -static s32 func_801A0B04(MATRIX *m); -static void *func_801A0B5C(void *pool, void *task, s32 stride, EffectEntity *owner); -static s32 func_801A92F8(EffectDrawScript *script); -static void func_801A0E1C(EffectEntity *entity, SVECTOR *out); -static void func_801A0E4C(EffectEntity *entity, SVECTOR *out); -static void func_801A0E7C(EffectEntity *entity, SVECTOR *out); -static void func_801A0FE8(EffectEntity *entity, SVECTOR *out); -static void func_801A1018(EffectEntity *entity, SVECTOR *out); -static void func_801A1048(EffectEntity *entity, SVECTOR *out); -static void func_801A12C4(EffectEntity *entity, SVECTOR *min, SVECTOR *max); -static s16 func_801A13B8(EffectEntity *entity); -static s16 func_801A13E8(EffectEntity *entity); -static s16 func_801A1440(EffectEntity *entity); -static s16 func_801A1464(EffectEntity *entity); -static s16 func_801A14AC(EffectEntity *entity); -static s16 func_801A14F4(EffectEntity *entity); -static s16 func_801A15AC(EffectEntity *entity); -static void func_801A1600(EffectEntity *entity, SVECTOR *out); -static void func_801A166C(void *dst, s32 size); +static void func_801A94B4(EffectEntity *entity); +static void func_801A9500(EffectEntity *entity); static void func_801A9568(EffectEntity *entity); +static s32 func_801A9570(EffectEntity *entity); +static void func_801A9610(EffectEntity *entity); +static void func_801A9628(EffectEntity *entity); static void func_801A9684(EffectEntity *entity); +static s32 func_801A968C(EffectEntity *entity); +static void func_801A972C(EffectEntity *entity); +static void func_801A9788(EffectEntity *entity); static void func_801A97D0(EffectEntity *entity); +static s32 func_801A97D8(EffectEntity *entity); +static void func_801A9878(EffectEntity *entity); +static void func_801A98D0(EffectEntity *entity); +static void func_801A9904(EffectEntity *entity); static void func_801A998C(EffectEntity *entity); +static s32 func_801A9994(EffectEntity *entity); +static void func_801A9AA4(EffectEntity *entity); +static void func_801A9AB8(EffectEntity *entity); +static void func_801A9ACC(EffectEntity *entity); +static void func_801A9B5C(EffectEntity *entity); +static void func_801A9BB4(EffectEntity *entity); +static void func_801A9C18(EffectEntity *entity); +static void func_801A9C80(EffectEntity *entity); +static void func_801A9CA8(EffectEntity *entity); +static void func_801A9CBC(EffectEntity *entity); +static void func_801A9CD0(EffectEntity *entity); static void func_801A9CEC(EffectEntity *entity); -static void func_801A83B8(EffectDrawNode *node, EffectPoseStep *step); -static s32 func_801A9CF4(EffectEntity *entity); - -/** - * @brief Start the effect's script and hand back its task pool. - * - * @param animSet Animation set the effect plays. - * @return The pool the root entity lives in. - */ -void *func_801A0000(EffectAnimSet *animSet) { - u8 *bank0; - EffectEntity *entity; - u8 *bank1; - u8 *frames; - - D_801D3CCC = 0; - D_801D3CD0 = 0; - func_800B2A00(&D_801D488C, &D_801D47BC, 0x64, 2); - entity = func_801A0B5C(&D_801D488C, func_801A9CF4, 0x64, NULL); - entity->animSet = animSet; - entity->unk02C = animSet->anims[entity->unk02A].unk000; - entity->unk02D = - entity->animSet->anims[entity->unk02A].parts[entity->unk02B].unk000; - entity->pc = 0; - entity->unk05A = animSet->anims->unk010; - entity->unk058 = animSet->anims->unk011; - entity->unk02F = entity->unk05A - 1; - if (entity->unk02F < entity->unk058) { - entity->unk02F = entity->unk058; - } - if (!(animSet->flags & EFFECT_ANIMSET_FLAG_LOADED)) { - func_800C3BE0(&D_801AA10C); - func_800BB084(&D_801AACAC); - } - D_801D3CDC = &D_801AACAC; - bank0 = &D_801AACAC; - D_801D47A4 = bank0; - bank1 = bank0 + EFFECT_BANK_SIZE; - D_801D47AC = bank0 + EFFECT_BANK_SIZE; - D_801D3CDC = bank0 + EFFECT_BANK_SIZE; - frames = bank1; - D_801D47A8 = frames; - frames += EFFECT_BANK_SIZE; - D_801D3CDC = frames; - D_801D47B0 = frames; - func_800B2A00(&D_801D49FC, &D_801D489C, 0x58, 4); - func_800B2A00(&D_801D4ACC, &D_801D4A0C, 0x3C, 3); - func_800B2A00(&D_801D478C, &D_801D3F9C, 0x2A4, 3); - func_800B2A00(&D_801D3F6C, &D_801D3CEC, 0x40, 0xA); - return &D_801D488C; -} - -/** - * @brief Cache the sixteen hex digit glyphs and reset the debug text cursor. - * - * @note The lookup runs once per glyph -- the target holds 16 separate calls, - * so a loop or a cached pointer does not match. - */ -static void func_801A0208(void) { - D_801D4ADC[0] = getMenuString(11)[1]; - D_801D4ADC[1] = getMenuString(11)[2]; - D_801D4ADC[2] = getMenuString(11)[3]; - D_801D4ADC[3] = getMenuString(11)[4]; - D_801D4ADC[4] = getMenuString(11)[5]; - D_801D4ADC[5] = getMenuString(11)[6]; - D_801D4ADC[6] = getMenuString(11)[7]; - D_801D4ADC[7] = getMenuString(11)[8]; - D_801D4ADC[8] = getMenuString(11)[9]; - D_801D4ADC[9] = getMenuString(11)[10]; - D_801D4ADC[10] = getMenuString(11)[11]; - D_801D4ADC[11] = getMenuString(11)[12]; - D_801D4ADC[12] = getMenuString(11)[13]; - D_801D4ADC[13] = getMenuString(11)[14]; - D_801D4ADC[14] = getMenuString(11)[15]; - D_801D4ADC[15] = getMenuString(11)[16]; - D_801D4ADC[16] = 0; - D_801D4AF0 = 10; - D_801D4AF4 = 20; - D_801D4AF8 = 3; -} - -/** @brief Draw @p value as eight hex glyphs at the current debug text cursor. */ -static void func_801A0358(u32 value) { - u32 *ot = D_800FA5E8->frontOT; - u8 text[9]; - s32 i; - - for (i = 7; i >= 0; i--) { - text[i] = D_801D4ADC[value & 0xF]; - value >>= 4; - } - text[8] = 0; - D_801D479C = func_8002C56C(ot, D_801D479C, D_801D4AF0, D_801D4AF4, - text, D_801D4AF8); - D_801D4AF0 += 0x48; -} - -/** @brief Opcode handler: arm the frame delay and advance the running total. */ -static void func_801A0404(void) { - D_801D4AF0 = 10; - D_801D4AF4 += 10; -} - -/** @brief Opcode handler: no-op. */ -static void func_801A0424(void) { -} - -/** - * @brief Emit a 128x128 textured quad at the head of the prim buffer. - * - * @param x Left edge in screen space. - * @param y Top edge in screen space. - * @param tx Texture page X. - * @param ty Texture page Y. - * @param clutX Palette X. - * @param clutY Palette Y. - * @param abr Semi-transparency rate for the page. - */ -static void func_801A042C(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, - s32 abr) { - POLY_FT4 *poly = D_801D479C; - - setPolyFT4(poly); - setSemiTrans(poly, 1); - setShadeTex(poly, 1); - poly->tpage = getTPage(1, abr, tx, ty); - poly->clut = getClut(clutX, clutY); - poly->u0 = poly->u2 = 0; - poly->u1 = poly->u3 = 0xFF; - poly->v0 = poly->v1 = 0; - poly->v2 = poly->v3 = 0xFF; - poly->x0 = poly->x2 = x; - poly->x1 = poly->x3 = x + 0x7F; - poly->y0 = poly->y1 = y; - poly->y2 = poly->y3 = y + 0x7F; - AddPrim(D_800FA5E8, poly); - poly++; - D_801D479C = poly; -} - -/** - * @brief Emit a 256x256 textured quad at the head of the prim buffer. - * - * @param x Left edge in screen space. - * @param y Top edge in screen space. - * @param tx Texture page X. - * @param ty Texture page Y. - * @param clutX Palette X. - * @param clutY Palette Y. - * @param abr Semi-transparency rate for the page. - */ -static void func_801A0528(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, - s32 abr) { - POLY_FT4 *poly = D_801D479C; - - setPolyFT4(poly); - setSemiTrans(poly, 1); - setShadeTex(poly, 1); - poly->tpage = getTPage(1, abr, tx, ty); - poly->clut = getClut(clutX, clutY); - poly->u0 = poly->u2 = 0; - poly->u1 = poly->u3 = 0xFF; - poly->v0 = poly->v1 = 0; - poly->v2 = poly->v3 = 0xFF; - poly->x0 = poly->x2 = x; - poly->x1 = poly->x3 = x + 0xFF; - poly->y0 = poly->y1 = y; - poly->y2 = poly->y3 = y + 0xFF; - AddPrim(D_800FA5E8, poly); - poly++; - D_801D479C = poly; -} - -/** @brief Load the identity matrix. */ -static void func_801A0624(MATRIX *m) { - m->m[0][0] = ONE; - m->m[1][0] = 0; - m->m[2][0] = 0; - m->m[0][1] = 0; - m->m[1][1] = ONE; - m->m[2][1] = 0; - m->m[0][2] = 0; - m->m[1][2] = 0; - m->m[2][2] = ONE; - m->t[0] = 0; - m->t[1] = 0; - m->t[2] = 0; -} - -/** @brief Post-multiply @p m by a rotation of @p angle about X. */ -static void func_801A065C(MATRIX *m, s32 angle) { - EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); - - rot->sin = rsin(angle); - rot->cos = rsin((angle + 0x400) & 0xFFF); - rot->m.m[0][0] = ONE; - rot->m.m[0][1] = 0; - rot->m.m[0][2] = 0; - rot->m.m[1][0] = 0; - rot->m.m[1][1] = rot->cos; - rot->m.m[1][2] = -rot->sin; - rot->m.m[2][0] = 0; - rot->m.m[2][1] = rot->sin; - rot->m.m[2][2] = rot->cos; - gte_MulMatrix0(m, &rot->m, m); - func_800B36B8(sizeof(EffectRotScratch)); -} - -/** - * @brief Compose a rotation about the Y axis into @p m. - * - * @param m Matrix rotated in place. - * @param angle Rotation, in the 0x1000-per-turn units @ref rsin takes. - */ -static void func_801A07EC(MATRIX *m, s32 angle) { - EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); - - rot->sin = rsin(angle); - rot->cos = rsin((angle + 0x400) & 0xFFF); - rot->m.m[0][0] = rot->cos; - rot->m.m[0][1] = 0; - rot->m.m[0][2] = rot->sin; - rot->m.m[1][0] = 0; - rot->m.m[1][1] = ONE; - rot->m.m[1][2] = 0; - rot->m.m[2][0] = -rot->sin; - rot->m.m[2][1] = 0; - rot->m.m[2][2] = rot->cos; - gte_MulMatrix0(m, &rot->m, m); - func_800B36B8(sizeof(EffectRotScratch)); -} - -/** @brief Turn @p m about Z by @p angle. */ -static void func_801A0978(MATRIX *m, s32 angle) { - EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); - - rot->sin = rsin(angle); - rot->cos = rsin((angle + 0x400) & 0xFFF); - rot->m.m[0][0] = rot->cos; - rot->m.m[0][1] = -rot->sin; - rot->m.m[0][2] = 0; - rot->m.m[1][0] = rot->sin; - rot->m.m[1][1] = rot->cos; - rot->m.m[1][2] = 0; - rot->m.m[2][0] = 0; - rot->m.m[2][1] = 0; - rot->m.m[2][2] = ONE; - gte_MulMatrix0(m, &rot->m, m); - func_800B36B8(sizeof(EffectRotScratch)); -} - -/** @brief The heading @p m faces, a quarter turn ahead of its Z axis. */ -static s32 func_801A0B04(MATRIX *m) { - return (ratan2(m->m[2][2], m->m[0][2]) + 0x400) & 0xFFF; -} - -/** @brief Opcode handler: release one frame of the linked script's wait. */ -static void func_801A0B34(EffectEntity *entity) { - if (entity->unk018 != NULL) { - entity->unk018->wait--; - } -} - -/** - * @brief Allocate an effect entity from @p pool and hang it off @p owner. - * - * The pool keeps its own 12-byte task header ahead of the entity fields, so - * only the remainder of @p stride is cleared. A child inherits the owner's - * model, animation set and position; an entity with no owner becomes its own - * model. - * - * @param pool Task pool to allocate from. - * @param task Per-frame step to install on the new entity. - * @param stride Total size of the allocation, header included. - * @param owner Parent entity, or NULL for a root. - * @return The new entity, or NULL if the pool is full. - */ -static void *func_801A0B5C(void *pool, void *task, s32 stride, - EffectEntity *owner) { - EffectEntity *node = func_800B2A84(pool, task); - s32 *clear; - s32 words; - s32 i; - - if (node == NULL) { - return NULL; - } - clear = (s32 *)&node->animSet; - stride = stride - 0xC; - words = stride / 4; - for (i = 0; i < words; i++) { - *clear = 0; - clear++; - } - if (owner == NULL) { - node->unk018 = NULL; - node->unk014 = NULL; - /* A root drives its own geometry, so it is its own model. */ - node->unk010 = (EffectModel *)node; - } else { - if (owner->unk014 == NULL) { - node->unk014 = (EffectModel *)node; - } else { - node->unk014 = owner->unk014; - } - node->unk018 = owner; - node->unk010 = owner->unk010; - node->animSet = owner->animSet; - node->pos = owner->pos; - node->unk02A = owner->unk02A; - node->unk02B = owner->unk02B; - node->unk02E = owner->unk02E; - node->unk02F = owner->unk02F; - owner->wait++; - node->unk02C = node->animSet->anims[node->unk02A].unk000; - node->unk02D = - node->animSet->anims[node->unk02A].parts[node->unk02B].unk000; - } - return node; -} - -/** @brief Cache the battle slot's two anchor points and their midpoint. */ -static void func_801A0CE8(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - SVECTOR top; - SVECTOR bottom; - - if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { - return; - } - func_800B3960(slot, 0xF0, 0, &top); - func_800B3960(slot, 0xF1, 0, &bottom); - entity->unk040.vx = top.vx; - entity->unk040.vy = top.vy; - entity->unk040.vz = top.vz; - entity->unk038.vx = (top.vx + bottom.vx) / 2; - entity->unk038.vy = (top.vy + bottom.vy) / 2; - entity->unk038.vz = (top.vz + bottom.vz) / 2; - entity->unk030.vx = bottom.vx; - entity->unk030.vy = slot->unk024; - entity->unk030.vz = bottom.vz; -} - -/** @brief Copy @c unk030 of the linked model out to @p out. */ -static void func_801A0E1C(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk014->unk030; -} - -/** @brief Copy @c unk038 of the linked model out to @p out. */ -static void func_801A0E4C(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk014->unk038; -} - -/** @brief Copy @c unk040 of the linked model out to @p out. */ -static void func_801A0E7C(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk014->unk040; -} - -/** @brief As @ref func_801A0CE8, but for the slot the animation set names. */ -static void func_801A0EAC(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->animSet->slot]; - SVECTOR top; - SVECTOR bottom; - - if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { - return; - } - func_800B3960(slot, 0xF0, 0, &top); - func_800B3960(slot, 0xF1, 0, &bottom); - entity->unk040.vx = top.vx; - entity->unk040.vy = top.vy; - entity->unk040.vz = top.vz; - entity->unk038.vx = (top.vx + bottom.vx) / 2; - entity->unk038.vy = (top.vy + bottom.vy) / 2; - entity->unk038.vz = (top.vz + bottom.vz) / 2; - entity->unk030.vx = bottom.vx; - entity->unk030.vy = slot->unk024; - entity->unk030.vz = bottom.vz; -} - -/** @brief Copy @c unk030 of the linked model out to @p out. */ -static void func_801A0FE8(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk010->unk030; -} - -/** @brief Copy @c unk038 of the linked model out to @p out. */ -static void func_801A1018(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk010->unk038; -} - -/** @brief Copy @c unk040 of the linked model out to @p out. */ -static void func_801A1048(EffectEntity *entity, SVECTOR *out) { - *out = entity->unk010->unk040; -} - -/** - * @brief Recompute the model's bounding box from its posed skeleton. - * - * Transforms every joint after the root through the GTE and keeps the running - * minimum and maximum, then publishes the box back onto the entity. - * - * @param entity Entity whose model is measured. - */ -static void func_801A1078(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - EffectSkeleton *skeleton = slot->mesh->skeleton; - EffectJoint *joint = skeleton->joints; - EffectBoundsScratch *b = func_800B3698(sizeof(EffectBoundsScratch)); - SVECTOR *bounds; - s32 i; - s16 x = joint->mtx.t[0]; - s16 y; - s16 z; - - b->max.vx = x; - b->min.vx = x; - y = joint->mtx.t[1]; - b->max.vy = y; - b->min.vy = y; - z = joint->mtx.t[2]; - b->max.vz = z; - b->min.vz = z; - b->v.vx = 0; - b->v.vy = 0; - joint = &skeleton->joints[1]; - for (i = 1; i < skeleton->count; i++) { - SetRotMatrix(&joint->mtx); - SetTransMatrix(&joint->mtx); - b->v.vz = -joint->unk002; - gte_ldv0(&b->v); - gte_mvmva(1, 0, 0, 0, 0); - gte_stlvnl(&b->pos); - gte_stflg(&b->flag); - if (b->pos.vx < b->min.vx) { - b->min.vx = b->pos.vx; - } else if (b->max.vx < b->pos.vx) { - b->max.vx = b->pos.vx; - } - if (b->pos.vy < b->min.vy) { - b->min.vy = b->pos.vy; - } else if (b->max.vy < b->pos.vy) { - b->max.vy = b->pos.vy; - } - if (b->pos.vz < b->min.vz) { - b->min.vz = b->pos.vz; - } else if (b->max.vz < b->pos.vz) { - b->max.vz = b->pos.vz; - } - joint++; - } - /* The box overlays unk048/unk04C, which the script opcodes use as a table - pointer -- the two never overlap in time. */ - bounds = (SVECTOR *)entity->unk048; - bounds[0] = b->min; - bounds[1] = b->max; - func_800B36B8(sizeof(EffectBoundsScratch)); -} - -/** @brief Copy the linked model's bounding box out to @p min and @p max. */ -static void func_801A12C4(EffectEntity *entity, SVECTOR *min, SVECTOR *max) { - EffectModel *model = entity->unk014; - - *min = model->boundsMin; - *max = model->boundsMax; -} - -/** - * @brief Half the longest side of the model's bounding box. - * - * @param entity Entity whose model is measured. - * @return Half the largest of the box's three extents. - */ -static s32 func_801A1314(EffectEntity *entity) { - EffectModel *model = entity->unk014; - s16 dx = model->boundsMax.vx - model->boundsMin.vx; - s16 dy = model->boundsMax.vy - model->boundsMin.vy; - s16 dz = model->boundsMax.vz - model->boundsMin.vz; - if (dx > dy) { - if (dx > dz) { - return dx / 2; - } - return dz / 2; - } - if (dy > dz) { - return dy / 2; - } - return dz / 2; -} - -/** @brief Half the height of the linked model's bounding box. */ -static s16 func_801A13B8(EffectEntity *entity) { - EffectModel *model = entity->unk014; - - return (s16)(model->boundsMax.vy - model->boundsMin.vy) / 2; -} - -/** @brief Half the linked model's larger horizontal extent. */ -static s16 func_801A13E8(EffectEntity *entity) { - EffectModel *model = entity->unk014; - s16 spanX = model->boundsMax.vx - model->boundsMin.vx; - s16 spanZ = model->boundsMax.vz - model->boundsMin.vz; - /* Compared as `span` but returned as `spanX`: testing a separate copy is - what puts the wider-X arm on the taken branch. */ - s16 span = spanX; - - return (span <= spanZ ? spanZ : spanX) / 2; -} - -/** @brief Height of the linked model's bounding box. */ -static s16 func_801A1440(EffectEntity *entity) { - return entity->unk014->boundsMax.vy - entity->unk014->boundsMin.vy; -} - -/** @brief Y of the top of the linked model, in battle-entity space. */ -static s16 func_801A1464(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - - return slot->pos.vy + entity->unk014->boundsMax.vy; -} - -/** @brief Y of the bottom of the linked model, in battle-entity space. */ -static s16 func_801A14AC(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - - return slot->pos.vy + entity->unk014->boundsMin.vy; -} - -/** @brief A random point up the linked model, in battle-entity space. */ -static s16 func_801A14F4(EffectEntity *entity) { - BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - EffectModel *model = entity->unk014; - s16 height = model->boundsMax.vy - model->boundsMin.vy; - s16 offset = (rand() & 0xFFF) * height / 4096; - - return slot->pos.vy + model->boundsMax.vy - offset / 2; -} - -/** @brief Y of the centre of the linked model, in battle-entity space. */ -static s16 func_801A15AC(EffectEntity *entity) { - EffectModel *model = entity->unk014; - - return D_800EF2D0[entity->unk02D].pos.vy + - (model->boundsMax.vy + model->boundsMin.vy) / 2; -} - -/** @brief Centre of the linked model's bounding box. */ -static void func_801A1600(EffectEntity *entity, SVECTOR *out) { - EffectModel *model = entity->unk014; - - out->vx = (model->boundsMax.vx + model->boundsMin.vx) / 2; - out->vy = (model->boundsMax.vy + model->boundsMin.vy) / 2; - out->vz = (model->boundsMax.vz + model->boundsMin.vz) / 2; -} - -/** @brief Zero @p size bytes' worth of words starting at @p dst. */ -static void func_801A166C(void *dst, s32 size) { - s32 *p = dst; - s32 i; - - for (i = 0; i < size / 4; i++) { - *p = 0; - p++; - } -} - -/** - * @brief Play one step of the screen tint and lay four quads over the frame. - * - * @return 2 once the script has stopped and its children have drained. - */ -static s32 func_801A16A4(EffectTintScript *script) { - EffectTintStep step = script->steps[script->step]; - s32 draw = 1; - - if (step.op == 0xFF) { - script->flags |= EFFECT_FLAG_STOP; - draw = 0; - } else if (step.op == 0xFE) { - if (script->frame >= step.b) { - script->step++; - script->tint = script->steps[script->step]; - } - } else { - script->tint = script->steps[script->step]; - script->step++; - } - if (draw == 1) { - /* The step's four bytes are the quad's colour word, code byte and all. */ - u32 *ot = &D_800FA5E8->frontOT[2]; - EffectTintQuad *quad = D_801D479C; - DR_MODE *mode; - - quad->rgb = *(u32 *)&script->tint; - setlen(quad, 5); - setcode(quad, EFFECT_TINT_CODE); - quad->xy0 = EFFECT_TINT_XY(0, 0); - quad->xy1 = EFFECT_TINT_XY(0xA0, 0); - quad->xy2 = EFFECT_TINT_XY(0, 0x78); - quad->xy3 = EFFECT_TINT_XY(0xA0, 0x78); - AddPrim(ot, quad); - quad++; - quad->rgb = *(u32 *)&script->tint; - setlen(quad, 5); - setcode(quad, EFFECT_TINT_CODE); - quad->xy0 = EFFECT_TINT_XY(0xA0, 0); - quad->xy1 = EFFECT_TINT_XY(0x140, 0); - quad->xy2 = EFFECT_TINT_XY(0xA0, 0x78); - quad->xy3 = EFFECT_TINT_XY(0x140, 0x78); - AddPrim(ot, quad); - quad++; - quad->rgb = *(u32 *)&script->tint; - setlen(quad, 5); - setcode(quad, EFFECT_TINT_CODE); - quad->xy0 = EFFECT_TINT_XY(0, 0x78); - quad->xy1 = EFFECT_TINT_XY(0xA0, 0x78); - quad->xy2 = EFFECT_TINT_XY(0, 0xF0); - quad->xy3 = EFFECT_TINT_XY(0xA0, 0xF0); - AddPrim(ot, quad); - quad++; - quad->rgb = *(u32 *)&script->tint; - setlen(quad, 5); - setcode(quad, EFFECT_TINT_CODE); - quad->xy0 = EFFECT_TINT_XY(0xA0, 0x78); - quad->xy1 = EFFECT_TINT_XY(0x140, 0x78); - quad->xy2 = EFFECT_TINT_XY(0xA0, 0xF0); - quad->xy3 = EFFECT_TINT_XY(0x140, 0xF0); - AddPrim(ot, quad); - quad++; - mode = (DR_MODE *)quad; - SetDrawMode(mode, 0, 0, GetTPage(0, 1, 0x280, 0), NULL); - AddPrim(ot, mode); - D_801D479C = mode + 1; - } - script->frame++; - if (script->flags & EFFECT_FLAG_STOP) { - if (script->wait == 0) { - func_801A0B34((EffectEntity *)script); - return 2; - } - } - return 0; -} - -/** @brief A random value between @p lo and @p hi. */ -static s32 func_801A1980(s32 lo, s32 hi) { - s32 span; - s32 r; - - if (lo == hi) { - return lo; - } - span = hi - lo; - r = rand() % span; - if (span >= 0) { - r = r + lo; - } else { - r = lo - r; - } - return r; -} - -/** @brief A random value between @p lo and @p hi, from a product of two draws. */ -static s32 func_801A1A04(s32 lo, s32 hi) { - s32 span; - s32 r; - - if (lo == hi) { - return lo; - } - span = hi - lo; - r = rand() * rand() % span; - if (span >= 0) { - r = r + lo; - } else { - r = lo - r; - } - return r; -} - -/** @brief Combine @p a and @p b component-wise through @ref func_801A1A04. */ -static void func_801A1AA8(VECTOR *out, VECTOR *a, VECTOR *b) { - out->vx = func_801A1A04(a->vx, b->vx); - out->vy = func_801A1A04(a->vy, b->vy); - out->vz = func_801A1A04(a->vz, b->vz); -} - -/** @brief Build @p m from @p angles, applying only the turns that are non-zero. */ -static void func_801A1B20(VECTOR *angles, MATRIX *m) { - SVECTOR a; - - a.vx = angles->vx / 0x10000; - a.vy = angles->vy / 0x10000; - a.vz = angles->vz / 0x10000; - func_801A0624(m); - if (a.vz != 0) { - func_801A0978(m, a.vz); - } - if (a.vx != 0) { - func_801A065C(m, a.vx); - } - if (a.vy != 0) { - func_801A07EC(m, a.vy); - } -} - -/** @brief Scatter @p base by up to half of @p spread, wrapped to one turn. */ -static s32 func_801A1BEC(s32 base, s32 spread) { - s32 half = spread / 2; - s32 v = func_801A1A04(-half, half) + base; - - if (v >= 0) { - while (v > 0xFFFFFFF) { - v -= 0x10000000; - } - } else { - while (v <= 0) { - v += 0x10000000; - } - } - return v; -} - -/** @brief Combine @p a and @p b component-wise through @ref func_801A1BEC. */ -static void func_801A1C80(VECTOR *out, VECTOR *a, VECTOR *b) { - out->vx = func_801A1BEC(a->vx, b->vx); - out->vy = func_801A1BEC(a->vy, b->vy); - out->vz = func_801A1BEC(a->vz, b->vz); -} - -/** @brief Scatter @p base by up to half of @p spread, wrapped to one turn. */ -static s32 func_801A1CF8(s16 base, s16 spread) { - s16 half = spread / 2; - s32 v = func_801A1980(-half, half) + base; - - if (v >= 0) { - while (v >= 0x1000) { - v -= 0x1000; - } - } else { - while (v <= 0) { - v += 0x1000; - } - } - return v; -} - -/** @brief Combine @p a and @p b component-wise through @ref func_801A1CF8. */ -static void func_801A1D88(SVECTOR *out, SVECTOR *a, SVECTOR *b) { - out->vx = func_801A1CF8(a->vx, b->vx); - out->vy = func_801A1CF8(a->vy, b->vy); - out->vz = func_801A1CF8(a->vz, b->vz); -} - -/** @brief Copy the three words at @p src to @p dst. */ -static void func_801A1E00(s32 *src, s32 *dst) { - /* The original reserved a slot here and never read it; dropping it moves - every local below and the frame no longer matches. */ - VECTOR unused; - - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; -} - -/** - * @brief Fade @p value towards zero by @p t, a 16.16 fraction of it. - * - * @param t Clamped to the closed range 0 to 1.0. - * @param value Read and written in place. - */ -static void func_801A1E30(s32 t, s32 *value) { - s32 v; - - if (t > 0x10000) { - t = 0x10000; - } - if (t < 0) { - t = 0; - } - v = *value; - *value = v - (v / 256 * t / 256); -} - -/** @brief Fade all three components of @p v towards zero by @p t. */ -static void func_801A1E8C(s32 t, VECTOR *v) { - /* The original reserved a slot here and never read it. */ - VECTOR unused; - s32 x; - s32 y; - s32 z; - - if (t > 0x10000) { - t = 0x10000; - } - if (t < 0) { - t = 0; - } - x = v->vx; - v->vx = x - (x / 256 * t / 256); - y = v->vy; - v->vy = y - (y / 256 * t / 256); - z = v->vz; - v->vz = z - (z / 256 * t / 256); -} - -/** @brief Blend this frame's two source point sets into the node's target set. */ -static void func_801A1F58(EffectDrawNode *node, EffectPoseStep *step) { - u8 *dstId = &step->targets[node->unk1CA]; - u8 *srcAId = &step->framesA[node->unk1CA]; - u8 *srcBId = &step->framesB[node->unk1CA]; - EffectPointSet *target = D_801D3F84->targets[*dstId]; - EffectPointSet *a = D_801D3F84->frames[*srcAId]; - EffectPointSet *b = D_801D3F84->frames[*srcBId]; - - if (a != NULL && b != NULL) { - s32 *weight = func_800B3698(8); - SVECTOR *out = target->points; - SVECTOR *pa = a->points; - SVECTOR *pb = b->points; - s32 i = 0; - s32 t = step->weights[node->unk1CA]; - s32 n = target->count; - - weight[1] = t; - weight[0] = ONE - t; - for (; i < n; i++) { - gte_lddp(weight[0]); - gte_ldsv(pa); - gte_gpf1(); - pa++; - gte_lddp(weight[1]); - gte_ldsv(pb); - gte_gpl1(); - pb++; - gte_stsv(out); - out++; - } - func_800B36B8(8); - } -} - -/** - * @brief Emit the flat triangles the stream cursor points at. - * - * The entry starts with a triangle count; the cursor is stepped past it before - * anything is drawn, so the caller always resumes at the next entry. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - * - * @note Two spellings here are load-bearing for the whole emitter family. The - * empty @c do/while(0) wraps carry no code: they raise the loop depth - * flow.c weights register references by, which is what puts each - * variable in the register the original picked, and which statement - * needs one differs per emitter. The prim and stream cursors are cast on - * the way in and out because the driver chains one cursor type through - * eight emitters that each write a different prim. - */ -static EffectTri *func_801A20EC(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitTri *tri; - EffectTri *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - tri = (EffectEmitTri *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], - &verts[tri->idx2]); - } while (0); - gte_rtpt(); - setlen(prim, 4); - colour = tri->colour; - prim->rgb = colour; - if (s->flags & EFFECT_EMIT_SEMITRANS) { - prim->rgb = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_OPAQUE) { - prim->rgb &= ~EFFECT_PRIM_CODE(2); - } - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_avsz3(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && - (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_DEPTH_CUE) { - gte_ldrgb(&prim->rgb); - gte_lddp(s->depth); - gte_dpcs(); - gte_strgb(&prim->rgb); - } - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - tri++; - } while (i < count); - } - s->cursor = (s32 *)tri; - return prim; -} - -/** - * @brief Emit the flat quads the stream cursor points at. - * - * Same walk as @ref func_801A2D08 with a fourth vertex: the first three are - * projected together, the fourth on its own, and the average Z takes all four. - * A quad is dropped when every vertex leaves the screen on the same axis. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A23B8(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitQuad *quad; - EffectQuad *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectQuad *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - quad = (EffectEmitQuad *)next; - if (count > 0) { - do { - gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], - &verts[quad->idx2]); - gte_rtpt(); - setlen(prim, 5); - colour = quad->colour; - prim->rgb = colour; - if (s->flags & EFFECT_EMIT_SEMITRANS) { - prim->rgb = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_OPAQUE) { - prim->rgb &= ~EFFECT_PRIM_CODE(2); - } - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_ldv0(&verts[quad->idx3]); - gte_rtps(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - gte_stsxy(&prim->x3); - gte_avsz4(); - if (prim->x3 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X3; - } - if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y3; - } - if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && - (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_DEPTH_CUE) { - gte_ldrgb(&prim->rgb); - gte_lddp(s->depth); - gte_dpcs(); - gte_strgb(&prim->rgb); - } - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - quad++; - } while (i < count); - } - s->cursor = (s32 *)quad; - return (EffectTri *)prim; -} - -/** - * @brief Emit the textured triangles the stream cursor points at. - * - * Same walk as @ref func_801A2D08 with texture coordinates: each UV word comes - * from the stream biased by the build's offset, and the texture page and CLUT - * that share the first two UV words are either left alone, offset by the - * build's own, or replaced by it. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A26DC(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitTexTri *tri; - EffectTexTri *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectTexTri *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - tri = (EffectEmitTexTri *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], - &verts[(u16)tri->idx2uv2]); - } while (0); - gte_rtpt(); - setlen(prim, 7); - colour = tri->colour; - prim->rgb = colour; - if (s->flags & EFFECT_EMIT_SEMITRANS) { - prim->rgb = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_OPAQUE) { - prim->rgb &= ~EFFECT_PRIM_CODE(2); - } - prim->uv0.word = tri->uv0 + s->unk018; - prim->uv1.word = tri->uv1 + s->unk018; - prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - if (s->flags & EFFECT_EMIT_TPAGE_ADD) { - prim->uv1.h.id = prim->uv1.h.id + s->tpage; - } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { - prim->uv1.h.id = s->tpage; - } - if (s->flags & EFFECT_EMIT_CLUT_ADD) { - prim->uv0.h.id = prim->uv0.h.id + s->clut; - } else if (s->flags & EFFECT_EMIT_CLUT_SET) { - prim->uv0.h.id = s->clut; - } - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_avsz3(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && - (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_DEPTH_CUE) { - gte_ldrgb(&prim->rgb); - gte_lddp(s->depth); - gte_dpcs(); - gte_strgb(&prim->rgb); - } - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - tri++; - } while (i < count); - } - s->cursor = (s32 *)tri; - return (EffectTri *)prim; -} - -/** - * @brief Emit the textured quads the stream cursor points at. - * - * @ref func_801A2FE8 with texture coordinates, laid out like - * @ref func_801A4548's but with one flat colour for the whole quad. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A2A64(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitTexQuad *quad; - EffectTexQuad *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - u32 uv; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectTexQuad *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - quad = (EffectEmitTexQuad *)next; - if (count > 0) { - do { - gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], - &verts[quad->idx2]); - gte_rtpt(); - setlen(prim, 9); - colour = quad->colour; - prim->rgb = colour; - if (s->flags & EFFECT_EMIT_SEMITRANS) { - prim->rgb = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_OPAQUE) { - prim->rgb &= ~EFFECT_PRIM_CODE(2); - } - prim->uv0.word = quad->uv0 + s->unk018; - prim->uv1.word = quad->uv1 + s->unk018; - uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); - prim->uv2.word = uv; - prim->uv3.word = uv >> 16; - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - if (s->flags & EFFECT_EMIT_TPAGE_ADD) { - prim->uv1.h.id = prim->uv1.h.id + s->tpage; - } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { - prim->uv1.h.id = s->tpage; - } - if (s->flags & EFFECT_EMIT_CLUT_ADD) { - prim->uv0.h.id = prim->uv0.h.id + s->clut; - } else if (s->flags & EFFECT_EMIT_CLUT_SET) { - prim->uv0.h.id = s->clut; - } - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_ldv0(&verts[quad->idx3]); - gte_rtps(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - gte_stsxy(&prim->x3); - gte_avsz4(); - if (prim->x3 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X3; - } - if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y3; - } - if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && - (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_DEPTH_CUE) { - gte_ldrgb(&prim->rgb); - gte_lddp(s->depth); - gte_dpcs(); - gte_strgb(&prim->rgb); - } - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - quad++; - } while (i < count); - } - s->cursor = (s32 *)quad; - return (EffectTri *)prim; -} - -/** - * @brief Emit the gouraud triangles the stream cursor points at. - * - * Same walk as @ref func_801A2D08 with a colour per corner. When the build - * asks for a depth cue all three corners go through the GTE together, which - * also refreshes the first corner; otherwise the two extra corners are copied - * straight out of the stream. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A2E48(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitGouraudTri *tri; - EffectGouraudTri *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectGouraudTri *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - tri = (EffectEmitGouraudTri *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], - &verts[tri->idx2]); - } while (0); - gte_rtpt(); - setlen(prim, 6); - colour = tri->colour0; - prim->rgb0 = colour; - if (s->flags & EFFECT_EMIT_G_SEMITRANS) { - prim->rgb0 = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_G_OPAQUE) { - prim->rgb0 &= ~EFFECT_PRIM_CODE(2); - } - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_avsz3(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && - (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { - gte_ldrgb3(&tri->colour1, &tri->colour2, - &prim->rgb0); - gte_lddp(s->depth); - gte_dpct(); - gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); - } else { - prim->rgb1 = tri->colour1; - prim->rgb2 = tri->colour2; - } - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - tri++; - } while (i < count); - } - s->cursor = (s32 *)tri; - return (EffectTri *)prim; -} - -/** - * @brief Emit the gouraud quads the stream cursor points at. - * - * @ref func_801A3AAC with a fourth corner: the first three vertices are - * projected together and the fourth on its own, and the depth cue runs over - * the three stream colours together before the prim's own colour separately. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A3150(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitGouraudQuad *quad; - EffectGouraudQuad *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectGouraudQuad *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - quad = (EffectEmitGouraudQuad *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], - &verts[quad->idx2]); - } while (0); - gte_rtpt(); - setlen(prim, 8); - colour = quad->colour0; - prim->rgb0 = colour; - if (s->flags & EFFECT_EMIT_G_SEMITRANS) { - prim->rgb0 = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_G_OPAQUE) { - prim->rgb0 &= ~EFFECT_PRIM_CODE(2); - } - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_ldv0(&verts[quad->idx3]); - gte_rtps(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - gte_stsxy(&prim->x3); - gte_avsz4(); - if (prim->x3 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X3; - } - if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y3; - } - if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && - (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { - gte_ldrgb3(&quad->colour1, &quad->colour2, - &quad->colour3); - gte_lddp(s->depth); - gte_dpct(); - gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); - gte_ldrgb(&prim->rgb0); - gte_dpcs(); - gte_strgb(&prim->rgb0); - } else { - prim->rgb1 = quad->colour1; - prim->rgb2 = quad->colour2; - prim->rgb3 = quad->colour3; - } - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - quad++; - } while (i < count); - } - s->cursor = (s32 *)quad; - return (EffectTri *)prim; -} - -/** - * @brief Emit the gouraud textured triangles the stream cursor points at. - * - * @ref func_801A3AAC with texture coordinates: the UV words come from the - * stream biased by the build's offset, and the texture page and CLUT sharing - * the first two are left alone, offset, or replaced as the flags ask. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A34D8(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitGouraudTexTri *tri; - EffectGouraudTexTri *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectGouraudTexTri *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - tri = (EffectEmitGouraudTexTri *)next; - if (count > 0) { - do { - do { - gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], - &verts[(u16)tri->idx2uv2]); - } while (0); - gte_rtpt(); - setlen(prim, 9); - colour = tri->colour0; - prim->rgb0 = colour; - if (s->flags & EFFECT_EMIT_G_SEMITRANS) { - prim->rgb0 = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_G_OPAQUE) { - prim->rgb0 &= ~EFFECT_PRIM_CODE(2); - } - prim->uv0.word = tri->uv0 + s->unk018; - prim->uv1.word = tri->uv1 + s->unk018; - prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - if (s->flags & EFFECT_EMIT_TPAGE_ADD) { - prim->uv1.h.id = prim->uv1.h.id + s->tpage; - } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { - prim->uv1.h.id = s->tpage; - } - if (s->flags & EFFECT_EMIT_CLUT_ADD) { - prim->uv0.h.id = prim->uv0.h.id + s->clut; - } else if (s->flags & EFFECT_EMIT_CLUT_SET) { - prim->uv0.h.id = s->clut; - } - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_avsz3(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && - (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { - gte_ldrgb3(&tri->colour1, &tri->colour2, - &prim->rgb0); - gte_lddp(s->depth); - gte_dpct(); - gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); - } else { - prim->rgb1 = tri->colour1; - prim->rgb2 = tri->colour2; - } - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - tri++; - } while (i < count); - } - s->cursor = (s32 *)tri; - return (EffectTri *)prim; -} - -/** - * @brief Emit the gouraud textured quads the stream cursor points at. - * - * The widest of the family: four corners, each with its own colour and UV - * pair. @ref func_801A4174 with a fourth vertex. - * - * @param s Emitter state: the stream cursor, the vertex buffer and the flags. - * @param ot Ordering table the prims are linked into. - * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. - * @param poly Prim cursor to write from. - * @return The prim cursor advanced past everything written. - */ -static EffectTri *func_801A3894(EffectPrimBuild *s, u32 *ot, s32 otShift, - EffectTri *poly) { - EffectEmitGouraudTexQuad *quad; - EffectGouraudTexQuad *prim; - s32 *cursor; - s32 *next; - u32 *verts; - u32 colour; - u32 uv; - s32 count; - s32 reject; - s32 i; - - i = 0; - prim = (EffectGouraudTexQuad *)poly; - cursor = s->cursor; - verts = s->verts; - count = cursor[0]; - next = cursor + 1; - s->cursor = next; - quad = (EffectEmitGouraudTexQuad *)next; - if (count > 0) { - do { - gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], - &verts[quad->idx2]); - gte_rtpt(); - setlen(prim, 0xC); - colour = quad->colour0; - prim->rgb0 = colour; - if (s->flags & EFFECT_EMIT_G_SEMITRANS) { - prim->rgb0 = colour | EFFECT_PRIM_CODE(2); - } - if (s->flags & EFFECT_EMIT_G_OPAQUE) { - prim->rgb0 &= ~EFFECT_PRIM_CODE(2); - } - prim->uv0.word = quad->uv0 + s->unk018; - prim->uv1.word = quad->uv1 + s->unk018; - uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); - prim->uv2.word = uv; - prim->uv3.word = uv >> 16; - gte_stflg(&s->gteFlag); - if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { - gte_nclip(); - if (s->flags & EFFECT_EMIT_TPAGE_ADD) { - prim->uv1.h.id = prim->uv1.h.id + s->tpage; - } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { - prim->uv1.h.id = s->tpage; - } - if (s->flags & EFFECT_EMIT_CLUT_ADD) { - prim->uv0.h.id = prim->uv0.h.id + s->clut; - } else if (s->flags & EFFECT_EMIT_CLUT_SET) { - prim->uv0.h.id = s->clut; - } - reject = 0; - gte_stopz(&s->nclip); - if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { - gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); - gte_ldv0(&verts[quad->idx3]); - gte_rtps(); - if (prim->x0 >= EFFECT_EMIT_CLIP_X) { - reject = EFFECT_CLIP_X0; - } - if (prim->x1 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X1; - } - if (prim->x2 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X2; - } - if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y0; - } - if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y1; - } - if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y2; - } - gte_stsxy(&prim->x3); - gte_avsz4(); - if (prim->x3 >= EFFECT_EMIT_CLIP_X) { - reject |= EFFECT_CLIP_X3; - } - if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { - reject |= EFFECT_CLIP_Y3; - } - if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && - (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { - gte_stotz(&s->otz); - if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { - gte_ldrgb3(&quad->colour1, &quad->colour2, - &quad->colour3); - gte_lddp(s->depth); - gte_dpct(); - gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); - gte_ldrgb(&prim->rgb0); - gte_dpcs(); - gte_strgb(&prim->rgb0); - } else { - prim->rgb1 = quad->colour1; - prim->rgb2 = quad->colour2; - prim->rgb3 = quad->colour3; - } - addPrim(&ot[s->otz >> otShift], prim); - prim++; - } - } - } - do { - i++; - } while (0); - quad++; - } while (i < count); - } - s->cursor = (s32 *)quad; - return (EffectTri *)prim; -} - -/** - * @brief Walk @p prim's mesh stream, letting each emitter draw its own prims. - * - * @return The prim list head, advanced past everything the emitters wrote. - */ -static EffectTri *func_801A3CDC(EffectPrimBuild *prim, u32 *ot, s32 otShift, EffectTri *head) { - if (!(prim->flags & EFFECT_EMIT_VERTS_SET)) { - prim->verts = (u32 *)(prim->stream + 2); - } - prim->cursor = (s32 *)((u8 *)prim->stream + prim->stream[0]); - if (!(prim->flags & EFFECT_EMIT_KEEP_DEPTH)) { - prim->unk018 = 0; - } - gte_ldfcb(prim->color.r, prim->color.g, prim->color.b); - if (*prim->cursor != 0) { - head = func_801A20EC(prim, ot, otShift, head); - } else { - prim->cursor++; - } - if (*prim->cursor != 0) { - head = func_801A23B8(prim, ot, otShift, head); - } else { - prim->cursor++; - } - if (*prim->cursor != 0) { - head = func_801A26DC(prim, ot, otShift, head); - } else { - prim->cursor++; - } - if (*prim->cursor != 0) { - head = func_801A2A64(prim, ot, otShift, head); - } else { - prim->cursor++; - } - if (*prim->cursor != 0) { - head = func_801A2E48(prim, ot, otShift, head); - } else { - prim->cursor++; - } - if (*prim->cursor != 0) { - head = func_801A3150(prim, ot, otShift, head); - } else { - prim->cursor++; - } - if (*prim->cursor != 0) { - head = func_801A34D8(prim, ot, otShift, head); - } else { - prim->cursor++; - } - if (*prim->cursor != 0) { - head = func_801A3894(prim, ot, otShift, head); - } else { - prim->cursor++; - } - return head; -} - -/** @brief Build one mesh packet for @p node and link every copy into the OT. */ -static void func_801A3F20(EffectDrawNode *node, EffectPoseStep *step) { - EffectPrimBuild *prim; - - D_801D3CD8 -= sizeof(EffectPrimBuild); - prim = (EffectPrimBuild *)D_801D3CD8; - prim->stream = node->stream; - prim->flags = 0; - if (step->unk03A == 0) { - prim->flags = EFFECT_EMIT_TWO_SIDED | EFFECT_EMIT_G_TWO_SIDED; - } - if (node->unk1CE != 0) { - prim->color = node->color; - prim->flags |= EFFECT_EMIT_DEPTH_CUE | EFFECT_EMIT_G_DEPTH_CUE; - prim->depth = node->unk1CE; - } - if (node->copies == 1) { - - gte_SetRotMatrix(&node->mtx); - gte_SetTransMatrix(&node->mtx); - D_801D479C = func_801A3CDC(prim, D_800FA5E8->ot, 2, D_801D479C); - } else { - s32 i; - - for (i = 0; i < node->copies; i++) { - node->mtx.t[0] = node->offsets[i].vx; - node->mtx.t[1] = node->offsets[i].vy; - node->mtx.t[2] = node->offsets[i].vz; - gte_SetRotMatrix(&node->mtx); - gte_SetTransMatrix(&node->mtx); - D_801D479C = func_801A3CDC(prim, D_800FA5E8->ot, 2, D_801D479C); - } - } - D_801D3CD8 += sizeof(EffectPrimBuild); -} - -/** @brief Run the late step on every node whose source is in the right mode. */ -static void func_801A4128(void) { - EffectDrawNode *node = D_801D3F84->head; - - while (node != NULL) { - if (node->unk008 == 1) { - s32 idx = node->unk1D6; - EffectPoseStep *step = D_801D3F84->sources[idx]; - - if ((u32)(step->mode - 4) < 2 && node->stream != NULL) { - func_801A3F20(node, step); - } - } - node = node->next; - } -} - -/** @brief Advance every copy of @p node: spin it, drift it and turn its pose. */ -static void func_801A41E0(EffectDrawNode *node, EffectPoseStep *step) { - VECTOR posed[4]; - VECTOR spinOffset; - VECTOR spun; - MATRIX m; - s32 i; - - node->angle = node->angleBase; - node->angle.vx += step->unk0D8[node->unk1CA]; - node->angle.vy += step->unk0DC[node->unk1CA]; - node->angle.vz += step->unk0E0[node->unk1CA]; - node->angle.vx &= 0xFFF; - node->angle.vy &= 0xFFF; - node->angle.vz &= 0xFFF; - node->lastPos = node->pos[0]; - switch (step->unk027) { - case 0: - for (i = 0; i < node->copies; i++) { - if (node->spinRate != NULL || node->spin != NULL) { - node->spin[i] += node->spinRate[i]; - if (node->unk1DA != 0) { - func_801A1E30(node->unk1DA, &node->spin[i]); - } - spinOffset.vx = 0; - spinOffset.vy = -node->spin[i]; - spinOffset.vz = 0; - ApplyMatrixLV(&node->poses[i], &spinOffset, &spun); - node->offset[i].vx += spun.vx; - node->offset[i].vy += spun.vy; - node->offset[i].vz += spun.vz; - } - node->accel[i].vx += node->jerk[i].vx; - node->accel[i].vy += node->jerk[i].vy; - node->accel[i].vz += node->jerk[i].vz; - node->vel[i].vx += node->accel[i].vx; - node->vel[i].vy += node->accel[i].vy; - node->vel[i].vz += node->accel[i].vz; - node->vel[i].vy += node->unk1DC; - if (node->unk1DA != 0) { - func_801A1E8C(node->unk1DA, &node->vel[i]); - } - node->offset[i].vx += node->vel[i].vx; - node->offset[i].vy += node->vel[i].vy; - node->offset[i].vz += node->vel[i].vz; - } - break; - case 1: - for (i = 0; i < node->copies; i++) { - node->offset[i].vx = step->unk13C[node->unk1CA] << 16; - node->offset[i].vy = step->unk140[node->unk1CA] << 16; - node->offset[i].vz = step->unk144[node->unk1CA] << 16; - if (node->owner != NULL) { - m = node->owner->mtx; - } - func_801A0624(&m); - ApplyMatrixLV(&m, &node->offset[i], &node->offset[i]); - } - break; - } - if (step->unk026 == 1) { - EffectMote *owner = node->owner; - - switch (step->unk01D) { - case 0: - func_801A0624(&m); - if (owner != NULL) { - m = owner->mtx; - } - if (node->angle.vz != 0) { - func_801A0978(&m, node->angle.vz); - } - if (node->angle.vx != 0) { - func_801A065C(&m, node->angle.vx); - } - if (node->angle.vy != 0) { - func_801A07EC(&m, node->angle.vy); - } - break; - case 1: - func_801A0624(&m); - if (owner != NULL) { - m = owner->mtx; - } - if (node->angle.vy != 0) { - func_801A07EC(&m, node->angle.vy); - } - if (node->angle.vx != 0) { - func_801A065C(&m, node->angle.vx); - } - if (node->angle.vz != 0) { - func_801A0978(&m, node->angle.vz); - } - break; - case 2: - TransposeMatrix(D_801D3CD4, &m); - if (node->angle.vz != 0) { - func_801A0978(&m, node->angle.vz); - } - if (node->angle.vx != 0) { - func_801A065C(&m, node->angle.vx); - } - if (node->angle.vy != 0) { - func_801A07EC(&m, node->angle.vy); - } - break; - case 3: - TransposeMatrix(D_801D3CD4, &m); - if (node->angle.vy != 0) { - func_801A07EC(&m, node->angle.vy); - } - if (node->angle.vx != 0) { - func_801A065C(&m, node->angle.vx); - } - if (node->angle.vz != 0) { - func_801A0978(&m, node->angle.vz); - } - break; - } - for (i = 0; i < node->copies; i++) { - ApplyMatrixLV(&m, &node->offset[i], &posed[i]); - } - } else { - for (i = 0; i < node->copies; i++) { - posed[i] = node->offset[i]; - } - } - switch (step->unk022) { - case 0: - for (i = 0; i < node->copies; i++) { - node->pos[i] = posed[i]; - } - break; - case 1: { - EffectMote *owner = node->owner; - - if (owner != NULL) { - for (i = 0; i < node->copies; i++) { - node->pos[i] = owner->pos; - node->pos[i].vx += posed[i].vx; - node->pos[i].vy += posed[i].vy; - node->pos[i].vz += posed[i].vz; - } - } - break; - } - } - if (step->unk01B == 1) { - for (i = 0; i < node->copies; i++) { - node->pos[i].vy = 0; - } - } -} - -/** @brief Age @p node's hold on its current step and advance when it expires. */ -static void func_801A49E8(EffectDrawNode *node, EffectPoseStep *step) { - switch (step->mode) { - case 0: - node->unk1D0++; - if (node->unk1D1 < node->unk1D0) { - node->unk1D0 = 0; - node->unk1D2 = 1; - } - break; - case 2: - node->unk1D0++; - if (node->unk1D4 < node->unk1D0) { - if (node->unk1D5 != 0) { - node->unk1D5--; - node->unk1D0 = node->unk1D3; - } - } - if (node->unk1D0 > node->unk1D1) { - node->unk1D0 = 0; - node->unk1D2 = 1; - } - break; - case 1: - node->unk1D0++; - if (node->unk1D1 < node->unk1D0) { - node->unk1D0 = 0; - } - break; - } -} - -/** @brief Build @p node's pose: orient it, colour it, scale it and place it. */ -static void func_801A4ADC(EffectDrawNode *node, EffectPoseStep *step) { - VECTOR scale; - /* The original reserved a second slot here and never read it. */ - VECTOR unused; - s32 i; - - if (step->mode == 4 || step->mode == 5) { - u8 *target = &step->targets[node->unk1CA]; - - node->stream = D_801D3F84->targets[*target]; - if (step->mode == 5) { - /* Called with no arguments: node and step are already in $a0/$a1. */ - ((void (*)())func_801A1F58)(); - } - } - switch (step->unk01B) { - case 0: - TransposeMatrix(D_801D3CD4, &node->orient); - if (node->angle.vz != 0) { - func_801A0978(&node->orient, node->angle.vz); - } - break; - case 3: { - EffectMote *owner = node->owner; - - switch (step->unk01D) { - case 0: - if (owner != NULL) { - node->orient = owner->mtx; - } else { - func_801A0624(&node->orient); - } - if (node->angle.vz != 0) { - func_801A0978(&node->orient, node->angle.vz); - } - if (node->angle.vx != 0) { - func_801A065C(&node->orient, node->angle.vx); - } - if (node->angle.vy != 0) { - func_801A07EC(&node->orient, node->angle.vy); - } - break; - case 1: - if (owner != NULL) { - node->orient = owner->mtx; - } else { - func_801A0624(&node->orient); - } - if (node->angle.vy != 0) { - func_801A07EC(&node->orient, node->angle.vy); - } - if (node->angle.vx != 0) { - func_801A065C(&node->orient, node->angle.vx); - } - if (node->angle.vz != 0) { - func_801A0978(&node->orient, node->angle.vz); - } - break; - case 2: - TransposeMatrix(D_801D3CD4, &node->orient); - if (node->angle.vz != 0) { - func_801A0978(&node->orient, node->angle.vz); - } - if (node->angle.vx != 0) { - func_801A065C(&node->orient, node->angle.vx); - } - if (node->angle.vy != 0) { - func_801A07EC(&node->orient, node->angle.vy); - } - break; - case 3: - TransposeMatrix(D_801D3CD4, &node->orient); - if (node->angle.vy != 0) { - func_801A07EC(&node->orient, node->angle.vy); - } - if (node->angle.vx != 0) { - func_801A065C(&node->orient, node->angle.vx); - } - if (node->angle.vz != 0) { - func_801A0978(&node->orient, node->angle.vz); - } - break; - } - break; - } - case 1: - func_801A0624(&node->orient); - func_801A065C(&node->orient, 0x400); - break; - case 2: { - s16 angle = func_801A0B04(D_801D3CD4); - - func_801A0624(&node->orient); - if (angle != 0) { - func_801A07EC(&node->orient, angle); - } - break; - } - case 4: { - EffectMote *owner = node->owner; - - switch (step->unk01D) { - case 0: - if (owner != NULL) { - node->orient = owner->mtx; - } else { - func_801A0624(&node->orient); - } - if (node->angle.vz != 0) { - func_801A0978(&node->orient, node->angle.vz); - } - if (node->angle.vx != 0) { - func_801A065C(&node->orient, node->angle.vx); - } - if (node->angle.vy != 0) { - func_801A07EC(&node->orient, node->angle.vy); - } - break; - case 1: - if (owner != NULL) { - node->orient = owner->mtx; - } else { - func_801A0624(&node->orient); - } - if (node->angle.vy != 0) { - func_801A07EC(&node->orient, node->angle.vy); - } - if (node->angle.vx != 0) { - func_801A065C(&node->orient, node->angle.vx); - } - if (node->angle.vz != 0) { - func_801A0978(&node->orient, node->angle.vz); - } - break; - case 2: - TransposeMatrix(D_801D3CD4, &node->orient); - if (node->angle.vz != 0) { - func_801A0978(&node->orient, node->angle.vz); - } - if (node->angle.vx != 0) { - func_801A065C(&node->orient, node->angle.vx); - } - if (node->angle.vy != 0) { - func_801A07EC(&node->orient, node->angle.vy); - } - break; - case 3: - TransposeMatrix(D_801D3CD4, &node->orient); - if (node->angle.vy != 0) { - func_801A07EC(&node->orient, node->angle.vy); - } - if (node->angle.vx != 0) { - func_801A065C(&node->orient, node->angle.vx); - } - if (node->angle.vz != 0) { - func_801A0978(&node->orient, node->angle.vz); - } - break; - } - break; - } - } - if (step->mode == 4 || step->mode == 5) { - node->color.r = step->unk0F0[node->unk1CA]; - node->color.g = step->unk0F4[node->unk1CA]; - node->color.b = step->unk0F8[node->unk1CA]; - node->unk1CE = step->unk0FC[node->unk1CA]; - node->scaleX = step->unk0E4[node->unk1CA]; - node->scaleY = step->unk0E8[node->unk1CA]; - node->scaleZ = step->unk0EC[node->unk1CA]; - scale.vx = node->scaleX; - scale.vy = node->scaleY; - scale.vz = node->scaleZ; - ScaleMatrix(&node->orient, &scale); - } - if (node->copies == 1) { - node->orient.t[0] = node->pos[0].vx / 0x10000; - node->orient.t[1] = node->pos[0].vy / 0x10000; - node->orient.t[2] = node->pos[0].vz / 0x10000; - gte_MulMatrix0(D_801D3CD4, &node->orient, &node->mtx); - gte_SetTransMatrix(D_801D3CD4); - gte_ldlv0(node->orient.t); - gte_rt(); - gte_stlvnl(node->mtx.t); - node->mtx.t[2] += step->unk038; - } else { - for (i = 0; i < node->copies; i++) { - node->orient.t[0] = node->pos[i].vx / 0x10000; - node->orient.t[1] = node->pos[i].vy / 0x10000; - node->orient.t[2] = node->pos[i].vz / 0x10000; - gte_MulMatrix0(D_801D3CD4, &node->orient, &node->mtx); - gte_SetTransMatrix(D_801D3CD4); - gte_ldlv0(node->orient.t); - gte_rt(); - gte_stlvnl(node->mtx.t); - node->offsets[i].vx = node->mtx.t[0]; - node->offsets[i].vy = node->mtx.t[1]; - node->offsets[i].vz = node->mtx.t[2]; - node->offsets[i].vz += step->unk038; - } - } -} - -/** - * @brief Take the next free node off bank 1 and put it on the draw list. - * - * The search starts where the last one left off and gives up after one lap. - * - * @return The node, or NULL when every slot is taken. - */ -static EffectDrawNode *func_801A541C(EffectMote *owner, u8 source) { - EffectDrawNode *found = NULL; - s32 i = D_801D3F8A; - s32 n; - - for (n = 0; n < 0x8C; n++) { - if (D_801D3F80[i].unk1D7 == 0) { - EffectDrawNode *node = &D_801D3F80[i]; - - found = node; - func_801A166C(node, sizeof(EffectDrawNode)); - node->unk1D7 = 1; - node->unk1D6 = source; - node->owner = owner; - node->unk1D9 = owner->unk06B; - D_801D3F84->live++; - func_801A8068(node, 1); - break; - } - i++; - if (i >= EFFECT_NODE_COUNT) { - i = 0; - } - } - i++; - if (i >= EFFECT_NODE_COUNT) { - i = 0; - } - D_801D3F8A = i; - return found; -} - -/** @brief Run @p step when it is the kind this handler serves. */ -static void func_801A5538(EffectDrawNode *node, EffectPoseStep *step) { - if (step->unk030 == 1) { - func_801A6194(node, step->unk031, node->unk1D9); - } -} - -/** - * @brief Decide whether @p step fires this frame, and run it if it does. - * @return 1 when the step ran, 0 otherwise. - */ -static s32 func_801A5570(EffectDrawNode *node, EffectPoseStep *step) { - switch (step->kind) { - case 0: - if (node->unk1D2 != 1) { - return 0; - } - break; - case 1: - node->unk1C8--; - if (node->unk1C8 >= 0) { - return 0; - } - func_801A5538(node, step); - return 1; - case 2: - node->unk1C8--; - if (node->unk1C8 < 0) { - break; - } - if (node->unk1D2 != 1) { - return 0; - } - break; - default: - return 0; - } - func_801A5538(node, step); - return 1; -} - -/** @brief Step one node of the pose list, or retire it once its phase is over. */ -static void func_801A561C(EffectDrawNode *node) { - EffectPoseStep *step = D_801D3F84->sources[node->unk1D6]; - - switch (node->unk1CC) { - case 0: - func_801A49E8(node, step); - if (func_801A5570(node, step) == 0) { - func_801A41E0(node, step); - func_801A4ADC(node, step); - node->unk1CA++; - } else { - node->stream = NULL; - node->unk1CC++; - } - break; - case 1: - func_801A80A4(node); - node->unk1D7 = 0; - D_801D3F84->live--; - break; - } -} - -/** @brief One less than @p node's count, as a signed step. */ -static s16 func_801A5708(EffectMote *node) { - return node->unk008 - 1; -} - -/** - * @brief The overlay's own copy of @ref func_800C97E4: emit the frame's sprites. - * - * @return The prim buffer cursor past the packets emitted. - */ -static POLY_FT4 *func_801A5720(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head) { - POLY_FT4 *p = head; - POLY_FT4 *next; - BattleSprite *sprite = prim->sprites; - s32 count = prim->spriteCount; - s32 i; - s32 flags; - s32 value; /* angle, then each scale: one local, the target keeps all three in s0 */ - s32 w; - s32 h; - s32 uv; - u32 shade; - DR_MODE *tp; - - for (i = 0; i < count; i++, sprite++) { - prim->m.m[2][1] = 0; - prim->m.m[2][0] = 0; - prim->m.m[1][2] = 0; - prim->m.m[1][0] = 0; - prim->m.m[0][2] = 0; - prim->m.m[0][1] = 0; - prim->m.m[2][2] = ONE; - flags = sprite->flags; - if (flags & BATTLE_SPRITE_TRANSFORMED) { - value = sprite->angle; - if (value == 0) { - prim->cos = ONE; - prim->sin = 0; - prim->lastAngle = 0; - } else if (value != prim->lastAngle) { - prim->cos = rcos(value); - prim->sin = rsin(value); - prim->lastAngle = value; - } - value = sprite->scaleX; - prim->m.m[0][0] = prim->cos * value >> 12; - prim->m.m[1][0] = prim->sin * value >> 12; - value = sprite->scaleY; - prim->m.m[0][1] = -prim->sin * value >> 12; - prim->m.m[1][1] = prim->cos * value >> 12; - } else { - prim->m.m[1][1] = ONE; - prim->m.m[0][0] = ONE; - } - w = sprite->w; - h = sprite->h; - prim->corners[2].vx = -(w << 3); - prim->corners[0].vx = -(w << 3); - prim->corners[3].vx = w << 3; - prim->corners[1].vx = w << 3; - prim->corners[1].vy = -(h << 3); - prim->corners[0].vy = -(h << 3); - prim->corners[3].vy = h << 3; - prim->corners[2].vy = h << 3; - prim->m.t[0] = (sprite->x << 4) + (w << 3); - prim->m.t[1] = (sprite->y << 4) + (h << 3); - prim->m.t[2] = 0; - /* m = mtx * m, a column at a time, with the packet filled in the GTE's shadow. */ - gte_SetRotMatrix(&prim->mtx); - gte_ldclmv(&prim->m.m[0][0]); - gte_rtir(); - setlen(p, 9); - /* One word each: w, shade, h, code and u, v, clut are laid out as the packet's. */ - *(u32 *)&p->r0 = *(u32 *)&sprite->w; - *(u32 *)&p->u0 = *(u32 *)&sprite->u; - gte_stclmv(&prim->m.m[0][0]); - gte_ldclmv(&prim->m.m[0][1]); - gte_rtir(); - p->r0 = p->b0 = p->g0; - shade = p->r0 * prim->colour.r; - p->r0 = shade >> 7; - gte_stclmv(&prim->m.m[0][1]); - gte_ldclmv(&prim->m.m[0][2]); - gte_rtir(); - p->u2 = sprite->u; - shade = p->g0 * prim->colour.g; - p->g0 = shade >> 7; - gte_stclmv(&prim->m.m[0][2]); - gte_SetTransMatrix(&prim->mtx); - gte_ldlv0(prim->m.t); - gte_rt(); - p->v1 = sprite->v; - shade = p->b0 * prim->colour.b; - p->b0 = shade >> 7; - gte_stlvnl(prim->m.t); - gte_SetRotMatrix(&prim->m); - gte_SetTransMatrix(&prim->m); - gte_ldv3(&prim->corners[0], &prim->corners[1], &prim->corners[2]); - gte_rtpt(); - uv = sprite->u + w - prim->uvInset; - if (uv > BATTLE_SPRITE_UV_MAX) { - uv = BATTLE_SPRITE_UV_MAX; - } - p->u3 = uv; - p->u1 = uv; - /* The row select is the flag word's top nibble, unsigned. */ - p->clut += getClut(0, prim->clutRow[(u16)flags >> BATTLE_SPRITE_CLUT_ROW_SHIFT]); - gte_stflg(&prim->gteFlag); - if (!(prim->gteFlag & BATTLE_SPRITE_GTE_OUT_OF_RANGE)) { - p->tpage = flags & BATTLE_SPRITE_TPAGE_MASK; - gte_stsxy3(&p->x0, &p->x1, &p->x2); - gte_ldv0(&prim->corners[3]); - gte_rtps(); - uv = sprite->v + h - prim->uvInset; - if (uv > BATTLE_SPRITE_UV_MAX) { - uv = BATTLE_SPRITE_UV_MAX; - } - p->v3 = uv; - p->v2 = uv; - gte_stsxy(&p->x3); - if (prim->otz >= 0) { - addPrim(&ot[otShift], p); - p++; - } else { - gte_avsz4(); - gte_stotz(&prim->otz); - otShift = prim->otz >> otShift; - if (prim->flags & BATTLE_SPRITE_FLAG_ADDITIVE) { - next = p + 1; - setlen(next, 1); - tp = (DR_MODE *)next; - next = (POLY_FT4 *)(tp + 1); - tp->code[0] = BATTLE_SPRITE_ADDITIVE_MODE; - addPrim(&ot[otShift], tp); - addPrim(&ot[otShift], p); - p = next; - } else { - addPrim(&ot[otShift], p); - p++; - } - } - } - } - return p; -} - -/** - * @brief The overlay's own copy of @ref func_800C9E10: set a sprite packet up. - * - * @return The prim buffer cursor past the sprites emitted. - */ -static POLY_FT4 *func_801A5D4C(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head) { - BattleSpriteAnim *anim = prim->anim; - u16 *offsets = anim->offsets; - u8 *frame; - - prim->frameCount = anim->frameCount; - frame = (u8 *)anim + offsets[prim->frame]; - prim->sprites = (BattleSprite *)frame; - prim->spriteCount = *(s32 *)frame; - prim->sprites = (BattleSprite *)((s32 *)prim->sprites + 1); - prim->nextOffset = offsets[prim->frame + 1]; - if (prim->spriteCount < 0) { - prim->spriteCount &= ~BATTLE_SPRITE_COUNT_FLAG; - prim->flags |= BATTLE_SPRITE_FLAG_ADDITIVE; - } - prim->corners[3].vz = 0; - prim->corners[2].vz = 0; - prim->corners[1].vz = 0; - prim->corners[0].vz = 0; - gte_ReadRotMatrix(&prim->mtx); - if (prim->flags & BATTLE_SPRITE_FLAG_ROTATE) { - RotMatrixZ(prim->angle - D_800F02A0, &prim->mtx); - } else if (!(prim->flags & BATTLE_SPRITE_FLAG_NO_ROLL)) { - RotMatrixZ(-D_800F02A0, &prim->mtx); - } - if (prim->flags & BATTLE_SPRITE_FLAG_SCALE) { - prim->scale.vz = ONE; - ScaleMatrixL(&prim->mtx, &prim->scale); - } - if (!(prim->flags & BATTLE_SPRITE_FLAG_COLOUR)) { - *(u32 *)&prim->colour = BATTLE_SPRITE_COLOUR_DEFAULT; - } - if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT0)) { - prim->clutRow[0] = anim->clutRow[0]; - } - if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT1)) { - prim->clutRow[1] = anim->clutRow[1]; - } - if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT2)) { - prim->clutRow[2] = anim->clutRow[2]; - } - if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT3)) { - prim->clutRow[3] = anim->clutRow[3]; - } - if (prim->flags & BATTLE_SPRITE_FLAG_UV_INSET) { - prim->uvInset = 1; - } else { - prim->uvInset = 0; - } - prim->cos = ONE; - prim->lastAngle = 0; - prim->sin = 0; - prim->otz = -1; - return func_801A5720(prim, ot, otShift, head); -} - -/** - * @brief Build one draw packet for @p node's model and link it into the OT. - * - * @note @p step is not read; every caller has it to hand and passes it. - */ -static void func_801A5FB4(EffectDrawNode *node, EffectPoseStep *step) { - BattleSpritePrim *prim; - - D_801D3CD8 -= sizeof(BattleSpritePrim); - prim = (BattleSpritePrim *)D_801D3CD8; - gte_SetRotMatrix(&node->mtx); - gte_SetTransMatrix(&node->mtx); - prim->anim = node->stream; - prim->frame = node->unk1D0; - prim->flags = 0; - D_801D479C = func_801A5D4C(prim, D_800FA5E8->ot, 2, D_801D479C); - D_801D3CD8 += sizeof(BattleSpritePrim); -} - -/** @brief Draw every node on the list that has a model and is still posing. */ -static void func_801A6074(void) { - EffectDrawNode *node = D_801D3F84->head; - - while (node != NULL) { - if (node->unk008 == 1) { - EffectPoseStep *step = D_801D3F84->sources[node->unk1D6]; - EffectDrawNode *n = node; - - if (step->mode < 3 && node->stream != NULL) { - if (node->copies == 1) { - func_801A5FB4(node, step); - } else { - s32 i; - - for (i = 0; i < n->copies; i++) { - n->mtx.t[0] = n->offsets[i].vx; - n->mtx.t[1] = n->offsets[i].vy; - n->mtx.t[2] = n->offsets[i].vz; - func_801A5FB4(n, step); - } - } - } - } - node = node->next; - } -} - -/** - * @brief Take the next free mote off bank 0 and put it on the draw list. - * - * The search starts where the last one left off and gives up after one lap. - * - * @return The mote, or NULL when every slot is taken. - */ -static EffectMote *func_801A6194(void *owner, s16 source, s16 index) { - EffectMote *found = NULL; - s32 i = D_801D3F88; - s32 n; - - for (n = 0; n < 0x5A; n++) { - if (D_801D3F7C[i].taken == 0) { - EffectMote *mote = &D_801D3F7C[i]; - - found = mote; - func_801A166C(mote, sizeof(EffectMote)); - mote->taken = 1; - mote->unk06A = source; - mote->unk06B = index; - mote->owner = owner; - D_801D3F84->count++; - func_801A8068((EffectDrawNode *)mote, 0); - break; - } - i++; - if (i >= EFFECT_MOTE_COUNT) { - i = 0; - } - } - i++; - if (i >= EFFECT_MOTE_COUNT) { - i = 0; - } - D_801D3F88 = i; - return found; -} - - -/** @brief Step @p node's life and retire it when it runs out. */ -static void func_801A62B4(EffectMote *node) { - node->life--; - if (node->life <= 0) { - func_801A80A4((EffectDrawNode *)node); - node->taken = 0; - D_801D3F84->count--; - } -} - -/** @brief Spawn a node off @p mote and give each of its @p copies its own aim. */ -static void func_801A631C(EffectMote *mote, EffectPoseStep *step, s32 copies) { - EffectSpawnScratch *s; - EffectDrawNode *node; - s32 i; - - D_801D3CD8 -= sizeof(EffectSpawnScratch); - s = (EffectSpawnScratch *)D_801D3CD8; - node = func_801A541C(mote, mote->unk06A); - node->copies = copies; - s->frame = mote->unk060; - if (step->mode < 3) { - node->stream = D_801D3F84->unk228[mote->unk06A]; - node->unk1D1 = func_801A5708(node->stream); - if (step->mode == 2) { - node->unk1D3 = step->unk017; - node->unk1D4 = step->unk018; - node->unk1D5 = func_801A1980(step->unk019, step->unk01A); - } - } - node->unk1DA = step->unk064; - for (i = 0; i < copies; i++) { - node->pos[i] = mote->pos; - if (step->unk022 == 0) { - node->offset[i] = mote->pos; - } - if (step->unk034 == 1) { - s->angle.vx = rand() & 0xFFF; - s->angle.vy = rand() & 0xFFF; - s->angle.vz = rand() & 0xFFF; - func_801A0624(&s->m); - if (s->angle.vz != 0) { - func_801A0978(&s->m, s->angle.vz); - } - if (s->angle.vx != 0) { - func_801A065C(&s->m, s->angle.vx); - } - if (s->angle.vy != 0) { - func_801A07EC(&s->m, s->angle.vy); - } - if (step->unk033 == 0) { - s->speedX = step->unk0CC[s->frame]; - s->speedY = step->unk0D0[s->frame]; - s->speedZ = step->unk0D4[s->frame]; - if (s->speedX != 0) { - s->speedX = rand() % s->speedX; - } - if (s->speedY != 0) { - s->speedY = rand() % s->speedY; - } - if (s->speedZ != 0) { - s->speedZ = rand() % s->speedZ; - } - } else { - s->speedX = step->unk0CC[s->frame]; - s->speedY = step->unk0D0[s->frame]; - s->speedZ = step->unk0D4[s->frame]; - } - s->dir.vx = 0; - s->dir.vy = -0x1000; - s->dir.vz = 0; - gte_SetRotMatrix(&s->m); - gte_ldv0(&s->dir); - gte_mvmva(1, 0, 0, 3, 0); - gte_stsv(&s->dir); - s->vel.vx = s->dir.vx * s->speedX << 4; - s->vel.vy = s->dir.vy * s->speedY << 4; - s->vel.vz = s->dir.vz * s->speedZ << 4; - node->offset[i].vx += s->vel.vx; - node->offset[i].vy += s->vel.vy; - node->offset[i].vz += s->vel.vz; - } else if (step->unk034 == 2) { - s->angle.vx = rand() & 0xFFF; - s->angle.vy = 0; - s->angle.vz = 0x400; - func_801A0624(&s->m); - if (s->angle.vz != 0) { - func_801A0978(&s->m, s->angle.vz); - } - if (s->angle.vx != 0) { - func_801A065C(&s->m, s->angle.vx); - } - if (s->angle.vy != 0) { - func_801A07EC(&s->m, s->angle.vy); - } - if (step->unk033 == 0) { - s->speedX = step->unk0CC[s->frame]; - s->speedY = step->unk0D0[s->frame]; - s->speedZ = step->unk0D4[s->frame]; - if (s->speedX != 0) { - s->speedX = rand() % s->speedX; - } - if (s->speedY != 0) { - s->speedY = rand() % s->speedY; - } - if (s->speedZ != 0) { - s->speedZ = rand() % s->speedZ; - } - } else if (step->unk033 != 1) { - s->speedX = step->unk0CC[s->frame]; - s->speedY = step->unk0D0[s->frame]; - s->speedZ = step->unk0D4[s->frame]; - if (s->speedX != 0) { - s->speedX = rand() % s->speedX; - } - if (s->speedY != 0) { - s->speedY = rand() % s->speedY; - } - if (s->speedZ != 0) { - s->speedZ = rand() % s->speedZ; - } - } else { - s->speedX = step->unk0CC[s->frame]; - s->speedY = step->unk0D0[s->frame]; - if (s->speedY != 0) { - s->speedY = rand() % s->speedY; - } - s->speedZ = step->unk0D4[s->frame]; - } - if (rand() & 1) { - s32 speed = s->speedY; - - s->speedY = -speed; - } - s->dir.vx = 0; - s->dir.vy = -0x1000; - s->dir.vz = 0; - gte_SetRotMatrix(&s->m); - gte_ldv0(&s->dir); - gte_mvmva(1, 0, 0, 3, 0); - gte_stsv(&s->dir); - s->vel.vx = s->dir.vx * s->speedX << 4; - s->vel.vy = s->speedY << 16; - s->vel.vz = s->dir.vz * s->speedZ << 4; - node->offset[i].vx += s->vel.vx; - node->offset[i].vy += s->vel.vy; - node->offset[i].vz += s->vel.vz; - } - switch (step->unk021) { - case 0: - func_801A1D88(&s->spin, &step->unk03C, &step->unk044); - break; - case 1: - s->spin = s->angle; - break; - } - func_801A0624(&node->poses[i]); - if (s->spin.vz != 0) { - func_801A0978(&node->poses[i], s->spin.vz); - } - if (s->spin.vx != 0) { - func_801A065C(&node->poses[i], s->spin.vx); - } - if (s->spin.vy != 0) { - func_801A07EC(&node->poses[i], s->spin.vy); - } - gte_MulMatrix0(&mote->mtx, &node->poses[i], &node->poses[i]); - if (step->unk027 == 0) { - node->spin[i] = func_801A1A04(step->unk04C, step->unk050); - node->spinRate[i] = func_801A1A04(step->unk054, step->unk058); - } - } - func_801A1D88(&node->angleBase, &step->unk148, &step->unk150); - if (step->kind == 1 || step->kind == 2) { - node->unk1C8 = func_801A1980(step->unk01F, step->unk020); - } - if (step->unk027 == 0) { - node->unk1DC = func_801A1A04(step->unk05C, step->unk060); - for (i = 0; i < copies; i++) { - func_801A1AA8(&node->vel[i], &step->unk068, &step->unk078); - func_801A1AA8(&node->accel[i], &step->unk088, &step->unk098); - func_801A1AA8(&node->jerk[i], &step->unk0A8, &step->unk0B8); - } - } - func_801A83B8(node, step); - D_801D3CD8 += sizeof(EffectSpawnScratch); -} - -/** @brief Emit this frame's share of @p node's work, in chunks its source sizes. */ -static void func_801A6DB8(EffectMote *node) { - EffectPoseStep *step = D_801D3F84->sources[node->unk06A]; - s32 age = node->unk060; - s32 want; - s32 full; - s32 rest; - s32 i; - - if (age < 0x28) { - want = step->unk0C8[age]; - if (want != 0) { - full = want / step->unk036; - rest = want % step->unk036; - for (i = 0; i < full; i++) { - func_801A631C(node, step, step->unk036); - } - if (rest > 0) { - func_801A631C(node, step, rest); - } - } - } -} - -/** @brief Step one drawn node: set it up once, run its kind, then age it. */ -static void func_801A6EB8(EffectMote *node) { - if (node->unk062 == 0) { - /* Called with no argument: the mote is already in $a0 and stays there. */ - ((void (*)())func_801A8184)(); - node->unk062++; - } - switch (node->kind) { - case 3: - func_801A73E8(node); - break; - case 4: - func_801A75F8(node); - break; - } - func_801A6DB8(node); - node->unk060++; - func_801A62B4(node); -} - -/** @brief Put @p mote on the strand point its source names, plus that source's offset. */ -static void func_801A6F58(EffectMote *mote) { - EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; - SVECTOR offset; - SVECTOR turned; - MATRIX m; - - switch (step->anchor) { - case 0: - mote->pos = D_801D3F84->strands[1][mote->unk06B]; - break; - case 1: - mote->pos = D_801D3F84->strands[2][mote->unk06B]; - break; - case 2: - mote->pos = D_801D3F84->strands[3][mote->unk06B]; - break; - case 3: - mote->pos = D_801D3F84->strands[4][mote->unk06B]; - break; - } - offset.vx = step->offsetX / 0x10000; - offset.vy = step->offsetY / 0x10000; - offset.vz = step->offsetZ / 0x10000; - m = mote->mtx; - gte_SetRotMatrix(&m); - gte_ldv0(&offset); - gte_mvmva(1, 0, 0, 3, 0); - gte_stsv(&turned); - mote->pos.vx += turned.vx << 16; - mote->pos.vy += turned.vy << 16; - mote->pos.vz += turned.vz << 16; -} - -/** @brief Put @p mote on the trail point its source names, plus that source's offset. */ -static void func_801A71E0(EffectMote *mote) { - EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; - SVECTOR offset; - SVECTOR turned; - - switch (step->anchor) { - case 0: - mote->pos = D_801D3F84->trail[2]; - break; - case 1: - mote->pos = D_801D3F84->trail[3]; - break; - case 2: - mote->pos = D_801D3F84->trail[4]; - break; - case 3: - mote->pos = D_801D3F84->trail[5]; - break; - } - offset.vx = step->offsetX / 0x10000; - offset.vy = step->offsetY / 0x10000; - offset.vz = step->offsetZ / 0x10000; - gte_SetRotMatrix(&mote->mtx); - gte_ldv0(&offset); - gte_mvmva(1, 0, 0, 3, 0); - gte_stsv(&turned); - mote->pos.vx += turned.vx << 16; - mote->pos.vy += turned.vy << 16; - mote->pos.vz += turned.vz << 16; -} - -/** @brief Put @p mote on the node that owns it, plus that source's turned offset. */ -static void func_801A73E8(EffectMote *mote) { - EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; - EffectDrawNode *owner = mote->owner; - - if (owner != NULL) { - SVECTOR offset; - SVECTOR turned; - - if (step->midpoint == 1) { - mote->pos.vx = (owner->pos[0].vx + owner->lastPos.vx) / 2; - mote->pos.vy = (owner->pos[0].vy + owner->lastPos.vy) / 2; - mote->pos.vz = (owner->pos[0].vz + owner->lastPos.vz) / 2; - } else { - mote->pos = owner->pos[0]; - } - if (step->unk01C == 3) { - mote->mtx = owner->orient; - offset.vx = step->offsetX / 0x10000; - offset.vy = step->offsetY / 0x10000; - offset.vz = step->offsetZ / 0x10000; - gte_SetRotMatrix(&mote->mtx); - gte_ldv0(&offset); - gte_mvmva(1, 0, 0, 3, 0); - gte_stsv(&turned); - mote->pos.vx += turned.vx << 16; - mote->pos.vy += turned.vy << 16; - mote->pos.vz += turned.vz << 16; - } - } -} - -/** @brief Slide @p mote between the two points its source names. */ -static void func_801A75F8(EffectMote *mote) { - EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; - u8 kind; - s32 index; - SVECTOR from; - SVECTOR to; - SVECTOR offset; - SVECTOR turned; - s32 t; - - kind = step->unk100[mote->unk060]; - index = step->unk108[mote->unk060]; - switch (kind) { - case 0: - switch (index) { - case 0: - from.vx = D_801D3F84->strands[1][mote->unk06B].vx / 0x10000; - from.vy = D_801D3F84->strands[1][mote->unk06B].vy / 0x10000; - from.vz = D_801D3F84->strands[1][mote->unk06B].vz / 0x10000; - break; - case 1: - from.vx = D_801D3F84->strands[2][mote->unk06B].vx / 0x10000; - from.vy = D_801D3F84->strands[2][mote->unk06B].vy / 0x10000; - from.vz = D_801D3F84->strands[2][mote->unk06B].vz / 0x10000; - break; - case 2: - from.vx = D_801D3F84->strands[3][mote->unk06B].vx / 0x10000; - from.vy = D_801D3F84->strands[3][mote->unk06B].vy / 0x10000; - from.vz = D_801D3F84->strands[3][mote->unk06B].vz / 0x10000; - break; - case 3: - from.vx = D_801D3F84->strands[4][mote->unk06B].vx / 0x10000; - from.vy = D_801D3F84->strands[4][mote->unk06B].vy / 0x10000; - from.vz = D_801D3F84->strands[4][mote->unk06B].vz / 0x10000; - break; - } - break; - case 1: - switch (index) { - case 0: - from.vx = D_801D3F84->trail[2].vx / 0x10000; - from.vy = D_801D3F84->trail[2].vy / 0x10000; - from.vz = D_801D3F84->trail[2].vz / 0x10000; - break; - case 1: - from.vx = D_801D3F84->trail[3].vx / 0x10000; - from.vy = D_801D3F84->trail[3].vy / 0x10000; - from.vz = D_801D3F84->trail[3].vz / 0x10000; - break; - case 2: - from.vx = D_801D3F84->trail[4].vx / 0x10000; - from.vy = D_801D3F84->trail[4].vy / 0x10000; - from.vz = D_801D3F84->trail[4].vz / 0x10000; - break; - case 3: - from.vx = D_801D3F84->trail[5].vx / 0x10000; - from.vy = D_801D3F84->trail[5].vy / 0x10000; - from.vz = D_801D3F84->trail[5].vz / 0x10000; - break; - } - break; - } - offset.vx = step->unk110[mote->unk060]; - offset.vy = step->unk114[mote->unk060]; - offset.vz = step->unk118[mote->unk060]; - gte_SetRotMatrix(&mote->mtx); - gte_ldv0(&offset); - gte_mvmva(1, 0, 0, 3, 0); - gte_stsv(&turned); - from.vx += turned.vx; - from.vy += turned.vy; - from.vz += turned.vz; - kind = step->unk104[mote->unk060]; - index = step->unk10C[mote->unk060]; - switch (kind) { - case 0: - switch (index) { - case 0: - to.vx = D_801D3F84->strands[1][mote->unk06B].vx / 0x10000; - to.vy = D_801D3F84->strands[1][mote->unk06B].vy / 0x10000; - to.vz = D_801D3F84->strands[1][mote->unk06B].vz / 0x10000; - break; - case 1: - to.vx = D_801D3F84->strands[2][mote->unk06B].vx / 0x10000; - to.vy = D_801D3F84->strands[2][mote->unk06B].vy / 0x10000; - to.vz = D_801D3F84->strands[2][mote->unk06B].vz / 0x10000; - break; - case 2: - to.vx = D_801D3F84->strands[3][mote->unk06B].vx / 0x10000; - to.vy = D_801D3F84->strands[3][mote->unk06B].vy / 0x10000; - to.vz = D_801D3F84->strands[3][mote->unk06B].vz / 0x10000; - break; - case 3: - to.vx = D_801D3F84->strands[4][mote->unk06B].vx / 0x10000; - to.vy = D_801D3F84->strands[4][mote->unk06B].vy / 0x10000; - to.vz = D_801D3F84->strands[4][mote->unk06B].vz / 0x10000; - break; - } - break; - case 1: - switch (index) { - case 0: - to.vx = D_801D3F84->trail[2].vx / 0x10000; - to.vy = D_801D3F84->trail[2].vy / 0x10000; - to.vz = D_801D3F84->trail[2].vz / 0x10000; - break; - case 1: - to.vx = D_801D3F84->trail[3].vx / 0x10000; - to.vy = D_801D3F84->trail[3].vy / 0x10000; - to.vz = D_801D3F84->trail[3].vz / 0x10000; - break; - case 2: - to.vx = D_801D3F84->trail[4].vx / 0x10000; - to.vy = D_801D3F84->trail[4].vy / 0x10000; - to.vz = D_801D3F84->trail[4].vz / 0x10000; - break; - case 3: - to.vx = D_801D3F84->trail[5].vx / 0x10000; - to.vy = D_801D3F84->trail[5].vy / 0x10000; - to.vz = D_801D3F84->trail[5].vz / 0x10000; - break; - } - break; - } - offset.vx = step->unk11C[mote->unk060]; - offset.vy = step->unk120[mote->unk060]; - offset.vz = step->unk124[mote->unk060]; - gte_SetRotMatrix(&mote->mtx); - gte_ldv0(&offset); - gte_mvmva(1, 0, 0, 3, 0); - gte_stsv(&turned); - to.vx += turned.vx; - to.vy += turned.vy; - to.vz += turned.vz; - t = step->unk128[mote->unk060]; - mote->pos.vx = (from.vx << 16) + ((to.vx - from.vx) * t << 4); - mote->pos.vy = (from.vy << 16) + ((to.vy - from.vy) * t << 4); - mote->pos.vz = (from.vz << 16) + ((to.vz - from.vz) * t << 4); -} - -/** - * @brief Give @p node the count @p value and put it on the tail of the draw list. - * - * @param node Either record kind: both start with the same three link fields, - * and @c unk008 is the tag the walker later dispatches on. - */ -static void func_801A8068(EffectDrawNode *node, s16 value) { - EffectDrawList *list = D_801D3F84; - - node->unk008 = value; - if (list->head == NULL) { - list->head = node; - } else { - EffectDrawNode *tail = list->tail; - - tail->next = node; - node->prev = tail; - } - list->tail = node; -} - -/** - * @brief Take @p node off the draw list, mending both links. - * - * @param node Either record kind; see @ref func_801A8068. - */ -static void func_801A80A4(EffectDrawNode *node) { - EffectDrawNode *prev = node->prev; - EffectDrawNode *next = node->next; - - if (prev == NULL) { - D_801D3F84->head = next; - } else { - prev->next = next; - } - if (next == NULL) { - D_801D3F84->tail = prev; - } else { - next->prev = prev; - } -} - -/** @brief Empty the draw list. */ -static void func_801A80E4(void) { - D_801D3F84->head = NULL; - D_801D3F84->tail = NULL; -} - -/** @brief Run every node on the draw list through the step its kind wants. */ -static void func_801A80FC(void) { - EffectDrawNode *node = D_801D3F84->head; - - while (node != NULL) { - /* Read signed here and unsigned where it is stepped; it is also the - tag saying which record kind the walker is standing on. */ - switch (node->unk008) { - case 0: - func_801A6EB8((EffectMote *)node); - break; - case 1: - func_801A561C(node); - break; - } - node = node->next; - } -} - -/** @brief Set @p mote up for this frame: turn it, place it, and reset its life. */ -static void func_801A8184(EffectMote *mote) { - EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; - EffectDrawNode *owner = mote->owner; - s32 angle; - - mote->kind = step->unk011; - switch (step->unk01C) { - case 0: - func_801A0624(&mote->mtx); - break; - case 1: - func_801A0624(&mote->mtx); - angle = func_80041E84( - D_801D3F84->trail[1].vx - D_801D3F84->strands[0][mote->unk06B].vx, - D_801D3F84->trail[1].vz - D_801D3F84->strands[0][mote->unk06B].vz); - if (angle != 0) { - func_801A07EC(&mote->mtx, angle); - } - break; - case 2: - func_801A0624(&mote->mtx); - angle = func_80041E84( - D_801D3F84->strands[0][mote->unk06B].vx - D_801D3F84->trail[1].vx, - D_801D3F84->strands[0][mote->unk06B].vz - D_801D3F84->trail[1].vz); - if (angle != 0) { - func_801A07EC(&mote->mtx, angle); - } - break; - case 3: - mote->mtx = owner->orient; - break; - case 4: - func_801A0624(&mote->mtx); - angle = D_800EF2D0[D_801D3F84->slot].facing; - if (angle != 0) { - func_801A07EC(&mote->mtx, angle); - } - break; - } - switch (mote->kind) { - case 0: - func_801A6F58(mote); - break; - case 1: - func_801A71E0(mote); - break; - case 2: - func_801A73E8(mote); - break; - case 3: - func_801A73E8(mote); - break; - case 4: - func_801A75F8(mote); - break; - } - mote->life = step->unk012; -} - -/** @brief Fire each of @p step's four slots that is armed. */ -static void func_801A83B8(EffectDrawNode *node, EffectPoseStep *step) { - if (step->unk028 == 1) { - func_801A6194(node, step->unk02C, node->unk1D9); - } - if (step->unk029 == 1) { - func_801A6194(node, step->unk02D, node->unk1D9); - } - if (step->unk02A == 1) { - func_801A6194(node, step->unk02E, node->unk1D9); - } - if (step->unk02B == 1) { - func_801A6194(node, step->unk02F, node->unk1D9); - } -} - -/** @brief Take a mote for every source whose cue falls on this frame. */ -static void func_801A846C(void) { - s32 source; - - for (source = 0; source < 0x10; source++) { - EffectPoseStep *step = D_801D3F84->sources[source]; - - if (step != NULL && !((D_801D3F84->held >> source) & 1)) { - if (step->unk011 < 2 || step->unk011 == 4) { - if (step->unk013 == D_801D3F84->age) { - if (step->unk037 == 1) { - s32 i; - - for (i = 0; i < D_801D3F84->strandLen; i++) { - func_801A6194(NULL, source, i); - } - } else { - func_801A6194(NULL, source, 0); - } - } - } - } - } -} - -/** @brief Push the node's current position onto the head of the trail. */ -static void func_801A85A8(EffectDrawScript *script) { - EffectDrawList *list = D_801D3F84; - - list->trail[0].vx = script->unk290 << 16; - list->trail[0].vy = script->unk292 << 16; - list->trail[0].vz = script->unk294 << 16; - list->trail[5] = list->trail[0]; - list->trail[4] = list->trail[5]; - list->trail[3] = list->trail[4]; - list->trail[2] = list->trail[3]; - list->trail[1] = list->trail[2]; -} - -/** @brief Aim the trail at the battle slot: its two points and its height. */ -static void func_801A8684(void) { - BattleEffectSlot *slot = &D_800EF2D0[D_801D3F84->slot]; - SVECTOR pt; - - if (D_801D3F84->unk024 != 2) { - func_800B3960(slot, 0xF1, 0, &pt); - } - pt.vy = slot->unk024; - D_801D3F84->trail[1].vx = pt.vx << 16; - D_801D3F84->trail[1].vy = pt.vy << 16; - D_801D3F84->trail[1].vz = pt.vz << 16; - D_801D3F84->trail[2].vx = D_801D3F84->trail[1].vx; - D_801D3F84->trail[2].vy = 0; - D_801D3F84->trail[2].vz = D_801D3F84->trail[1].vz; - if (D_801D3F84->unk024 != 2) { - func_800B3960(slot, 0xF1, 0, &pt); - } - D_801D3F84->trail[3].vx = pt.vx << 16; - D_801D3F84->trail[3].vy = pt.vy << 16; - D_801D3F84->trail[3].vz = pt.vz << 16; - if (D_801D3F84->unk024 != 2) { - func_800B3960(slot, 0xF0, 0, &pt); - } - D_801D3F84->trail[4].vx = pt.vx << 16; - D_801D3F84->trail[4].vy = pt.vy << 16; - D_801D3F84->trail[4].vz = pt.vz << 16; - D_801D3F84->trail[5].vx = D_801D3F84->trail[1].vx; - D_801D3F84->trail[5].vy = slot->unk03C << 16; - D_801D3F84->trail[5].vz = D_801D3F84->trail[1].vz; -} - -/** @brief Seed every point of all five strands from the list's seed point. */ -static void func_801A87F8(void) { - s32 i; - - for (i = 0; i < D_801D3F84->strandLen; i++) { - D_801D3F84->strands[0][i] = D_801D3F84->strandSeed; - D_801D3F84->strands[1][i] = D_801D3F84->strandSeed; - D_801D3F84->strands[2][i] = D_801D3F84->strandSeed; - D_801D3F84->strands[3][i] = D_801D3F84->strandSeed; - D_801D3F84->strands[4][i] = D_801D3F84->strandSeed; - } -} - -/** @brief Push @p node's position onto the head of every strand. */ -static void func_801A8904(EffectDrawScript *script) { - s32 i; - - for (i = 0; i < D_801D3F84->strandLen; i++) { - EffectDrawList *list = D_801D3F84; - - list->strandHead.vx = script->unk288 << 16; - list->strandHead.vy = script->unk28A << 16; - list->strandHead.vz = script->unk28C << 16; - list->strands[4][i] = list->strandHead; - list->strands[3][i] = list->strands[4][i]; - list->strands[2][i] = list->strands[3][i]; - list->strands[1][i] = list->strands[2][i]; - list->strands[0][i] = list->strands[1][i]; - } -} - -/** - * @brief Aim every strand at its own model part, then seed them from the middle. - * - * @note @p script is not read; @ref func_801A9050 passes the entity it has. - */ -static void func_801A8A1C(EffectDrawScript *script) { - SVECTOR pt; - s32 i; - s16 n; - s32 minX; - s32 maxX; - s32 minZ; - s32 maxZ; - - for (i = 0; i < D_801D3F84->strandLen; i++) { - BattleEffectSlot *slot = &D_800EF2D0[D_801D3F84->parts[i]]; - - if (D_801D3F84->unk024 != 2) { - func_800B3960(slot, 0xF1, 0, &pt); - } - pt.vy = slot->unk024; - D_801D3F84->strands[0][i].vx = pt.vx << 16; - D_801D3F84->strands[0][i].vy = pt.vy << 16; - D_801D3F84->strands[0][i].vz = pt.vz << 16; - D_801D3F84->strands[1][i].vx = D_801D3F84->strands[0][i].vx; - D_801D3F84->strands[1][i].vy = 0; - D_801D3F84->strands[1][i].vz = D_801D3F84->strands[0][i].vz; - if (D_801D3F84->unk024 != 2) { - func_800B3960(slot, 0xF1, 0, &pt); - } - D_801D3F84->strands[2][i].vx = pt.vx << 16; - D_801D3F84->strands[2][i].vy = pt.vy << 16; - D_801D3F84->strands[2][i].vz = pt.vz << 16; - if (D_801D3F84->unk024 != 2) { - func_800B3960(slot, 0xF0, 0, &pt); - } - D_801D3F84->strands[3][i].vx = pt.vx << 16; - D_801D3F84->strands[3][i].vy = pt.vy << 16; - D_801D3F84->strands[3][i].vz = pt.vz << 16; - D_801D3F84->strands[4][i].vx = D_801D3F84->strands[0][i].vx; - D_801D3F84->strands[4][i].vy = slot->unk03C << 16; - D_801D3F84->strands[4][i].vz = D_801D3F84->strands[0][i].vz; - } - maxZ = 0; - minZ = 0; - maxX = 0; - minX = 0; - n = 0; - i = 0; - - for (; i < D_801D3F84->strandLen; i++) { - s32 v; - - if (n == 0) { - maxX = D_801D3F84->strands[0][i].vx; - maxZ = D_801D3F84->strands[0][i].vz; - minX = maxX; - minZ = maxZ; - } else { - v = D_801D3F84->strands[0][i].vx; - if (v < minX) { - minX = v; - } else if (maxX < v) { - maxX = v; - } - v = D_801D3F84->strands[0][i].vz; - if (v < minZ) { - minZ = v; - } else if (maxZ < v) { - maxZ = v; - } - } - n++; - } - D_801D3F84->strandSeed.vx = (minX + maxX) / 2; - D_801D3F84->strandSeed.vy = 0; - D_801D3F84->strandSeed.vz = (minZ + maxZ) / 2; -} - -/** - * @brief Turn every offset in @p tables into an address. - * - * The tables are built with everything relative to their own start, so the - * first script to use them walks each of the four arrays -- and then every - * table inside each source -- adding that start. Each array is walked as - * words because the entries are offsets until this pass has run. - */ -static void func_801A8CE4(EffectPoseTables *tables) { - s32 base = (s32)tables; - s32 *entry; - s32 i; - - entry = (s32 *)D_801D3F84->sources; - for (i = 0; i < 16; i++) { - if (entry[i] != 0) { - entry[i] += base; - } - } - entry = (s32 *)D_801D3F84->unk228; - for (i = 0; i < 16; i++) { - if (entry[i] != 0) { - entry[i] += base; - } - } - entry = (s32 *)D_801D3F84->targets; - for (i = 0; i < 16; i++) { - if (entry[i] != 0) { - entry[i] += base; - } - } - entry = (s32 *)D_801D3F84->frames; - for (i = 0; i < 16; i++) { - if (entry[i] != 0) { - entry[i] += base; - } - } - for (i = 0; i < 16; i++) { - EffectPoseStep *step = D_801D3F84->sources[i]; - - if (step != NULL) { - step->unk0C8 += base; - step->unk0CC = (u16 *)((u8 *)step->unk0CC + base); - step->unk0D0 = (u16 *)((u8 *)step->unk0D0 + base); - step->unk0D4 = (u16 *)((u8 *)step->unk0D4 + base); - step->unk0D8 = (u16 *)((u8 *)step->unk0D8 + base); - step->unk0DC = (u16 *)((u8 *)step->unk0DC + base); - step->unk0E0 = (u16 *)((u8 *)step->unk0E0 + base); - step->unk0E4 = (u16 *)((u8 *)step->unk0E4 + base); - step->unk0E8 = (u16 *)((u8 *)step->unk0E8 + base); - step->unk0EC = (u16 *)((u8 *)step->unk0EC + base); - step->unk0F0 += base; - step->unk0F4 += base; - step->unk0F8 += base; - step->unk0FC = (u16 *)((u8 *)step->unk0FC + base); - step->unk100 += base; - step->unk104 += base; - step->unk108 += base; - step->unk10C += base; - step->unk110 = (u16 *)((u8 *)step->unk110 + base); - step->unk114 = (u16 *)((u8 *)step->unk114 + base); - step->unk118 = (u16 *)((u8 *)step->unk118 + base); - step->unk11C = (u16 *)((u8 *)step->unk11C + base); - step->unk120 = (u16 *)((u8 *)step->unk120 + base); - step->unk124 = (u16 *)((u8 *)step->unk124 + base); - step->unk128 = (s16 *)((u8 *)step->unk128 + base); - step->targets += base; - step->framesA += base; - step->framesB += base; - step->weights = (s16 *)((u8 *)step->weights + base); - step->unk13C = (s16 *)((u8 *)step->unk13C + base); - step->unk140 = (s16 *)((u8 *)step->unk140 + base); - step->unk144 = (s16 *)((u8 *)step->unk144 + base); - } - } -} - -/** @brief Start @p task as a child of @p owner and seed its script fields. */ -static void func_801A8F88(EffectEntity *owner, void *task, void *arg, s16 stopFrame, - s16 a, s16 b) { - EffectDrawScript *child = - func_801A0B5C(&D_801D478C, task, sizeof(EffectDrawScript), owner); - - child->tables = arg; - child->stopFrame = stopFrame; - child->unk29C = b; - child->unk29E = a; -} - -/** @brief Clear both prim banks and the script's frame counters. */ -static void func_801A8FF8(void) { - func_801A166C(D_801D3F7C, EFFECT_BANK0_BYTES); - func_801A166C(D_801D3F80, EFFECT_BANK1_BYTES); - D_801D3F88 = 0; - D_801D3F8A = 0; - D_801D3F90 = 0; - D_801D3F92 = 0; -} - -/** @brief Point the draw list at this script's tables and aim it at its slot. */ -static void func_801A9050(EffectDrawScript *script) { - EffectModel *model = script->model; - EffectPoseTables *tables = script->tables; - s32 slot = script->slot; - EffectDrawList *list = &script->list; - SVECTOR from; - SVECTOR to; - s32 i; - - list->held = script->unk29A; - list->unk024 = script->unk29C; - list->sources = tables->sources; - list->unk228 = tables->unk044; - list->targets = tables->targets; - list->frames = tables->frames; - D_801D3F84 = list; - if (tables->ready == 0) { - func_801A8CE4(tables); - tables->ready = 1; - } - D_801D3F84->slot = slot; - D_801D3F84->strandLen = model->unk05A; - for (i = 0; i < D_801D3F84->strandLen; i++) { - D_801D3F84->parts[i] = - script->animSet->anims[script->anim].parts[i].unk000; - } - D_801D3F84->span = script->unk29E; - func_801A8684(); - if (D_801D3F84->unk024 == 4) { - func_801A85A8(script); - } - func_801A8A1C(script); - if (D_801D3F84->unk024 == 1) { - /* Handed the script the others take, though this one reads nothing. */ - ((void (*)())func_801A87F8)(script); - } - if (D_801D3F84->unk024 == 3) { - func_801A8904(script); - } - from = D_800EF2D0[D_801D3F84->slot].pos; - to = D_800EF2D0[D_801D3F84->parts[0]].pos; - from.vx -= to.vx; - from.vy -= to.vy; - from.vz -= to.vz; - D_801D3F84->spread = - SquareRoot0(from.vx * from.vx + from.vy * from.vy + from.vz * from.vz); -} - -/** - * @brief Run one frame of the draw list @p script carries. - * - * @return 1 once the list has finished, 0 while it is still running. - */ -static s32 func_801A92F8(EffectDrawScript *script) { - s32 done = 0; - - D_801D3F84 = &script->list; - switch (D_801D3F84->phase) { - case 0: - D_801D3F84->phase++; - break; - case 1: - func_801A846C(); - func_801A80FC(); - func_801A6074(); - func_801A4128(); - D_801D3F84->age++; - if (D_801D3F84->age >= D_801D3F84->span) { - /* Both counts are tested as one word; neither alone would do. */ - if (*(s32 *)&D_801D3F84->count == 0) { - D_801D3F84->phase++; - } - } - break; - case 2: - done = 1; - break; - } - D_801D3F8C += D_801D3F84->count; - D_801D3F8E += D_801D3F84->live; - return done; -} - - -/** @brief Set the draw list's span from three times the slot's height, clamped. */ -static void func_801A9420(s16 slot) { - BattleEffectSlot *entry = &D_800EF2D0[slot]; - /* Both heights are subtracted as raw halfwords before the span is taken. */ - s16 span = (s16)((u16)entry->unk03C - entry->unk036) * 3; - - if (span > 0x4000) { - span = 0x4000; - } else if (span < 0x400) { - span = 0x400; - } - D_801D3F84->unk21C = span; - D_801D3F84->unk218 = span; - D_801D3F84->unk214 = span; -} /** @brief Opcode handler: clear the screen tint. */ static void func_801A94B4(EffectEntity *entity) { @@ -4222,7 +111,7 @@ static s32 func_801A9570(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A0B34(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -4270,7 +159,7 @@ static s32 func_801A968C(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A0B34(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -4282,15 +171,15 @@ static void func_801A972C(EffectEntity *entity) { if (entity->unk024 >= script->stopFrame) { /* Called with no argument: the entity is already in $a0 and stays there. */ - ((void (*)())func_801A9050)(); - func_801A92F8(script); + ((void (*)())effectDrawScriptStart)(); + effectDrawScriptRun(script); entity->pc++; } } /** @brief Opcode handler: hold still until the count runs out. */ static void func_801A9788(EffectEntity *entity) { - if (func_801A92F8((EffectDrawScript *)entity) != 0) { + if (effectDrawScriptRun((EffectDrawScript *)entity) != 0) { entity->flags |= EFFECT_FLAG_STOP; entity->pc++; } @@ -4315,7 +204,7 @@ static s32 func_801A97D8(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A0B34(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -4323,7 +212,7 @@ static s32 func_801A97D8(EffectEntity *entity) { /** @brief Opcode handler: start the pose script on the taken model. */ static void func_801A9878(EffectEntity *entity) { - func_801A8F88(entity, func_801A97D8, &D_801D4AFC, 0, 0x2D, 0); + effectStartChildScript(entity, func_801A97D8, &D_801D4AFC, 0, 0x2D, 0); entity->pc++; } @@ -4352,15 +241,15 @@ static void func_801A998C(EffectEntity *entity) { static s32 func_801A9994(EffectEntity *entity) { EffectHandler handlers[4] = { func_801A9878, func_801A98D0, func_801A9904, func_801A998C }; - func_801A0CE8(entity); - func_801A1078(entity); + effectCacheSlotAnchors(entity); + effectUpdateModelBounds(entity); handlers[entity->pc](entity); if (entity->unk024 == 0) { func_800C4764(D_801A9FAC, 0, 0x80); } if (entity->unk024 == 20) { EffectTintScript *child = - func_801A0B5C(&D_801D4ACC, func_801A16A4, sizeof(EffectTintScript), entity); + effectSpawnTask(&D_801D4ACC, effectTintPlay, sizeof(EffectTintScript), entity); child->steps = D_801A9FA0; } @@ -4369,7 +258,7 @@ static s32 func_801A9994(EffectEntity *entity) { if (entity->wait != 0) { return 0; } - func_801A0B34(entity); + effectReleaseWait(entity); return 2; } return 0; @@ -4385,16 +274,16 @@ static void func_801A9AB8(EffectEntity *entity) { entity->pc++; } -/** @brief Opcode handler: carve both prim banks and start the draw script. */ +/** @brief Opcode handler: carve both record banks and start the draw script. */ static void func_801A9ACC(EffectEntity *entity) { if (entity->wait == 0) { /* Both banks are carved out of the same byte arena behind the TIM. */ - D_801D3F7C = (EffectMote *)D_801D3CDC; - D_801D3CDC += EFFECT_BANK0_BYTES; - D_801D3F80 = (EffectDrawNode *)D_801D3CDC; - D_801D3CDC += EFFECT_BANK1_BYTES; - func_801A8FF8(); - func_801A0B5C(&D_801D3F6C, func_801A9570, 0x40, entity); + g_effectEmitterBank = (EffectEmitter *)D_801D3CDC; + D_801D3CDC += EFFECT_EMITTER_BANK_BYTES; + g_effectParticleBank = (EffectParticle *)D_801D3CDC; + D_801D3CDC += EFFECT_PARTICLE_BANK_BYTES; + effectBanksClear(); + effectSpawnTask(&D_801D3F6C, func_801A9570, 0x40, entity); entity->pc++; } } @@ -4402,7 +291,7 @@ static void func_801A9ACC(EffectEntity *entity) { /** @brief Opcode handler: start a task running @ref func_801A9994. */ static void func_801A9B5C(EffectEntity *entity) { entity->unk063 = 1; - func_801A0B5C(&D_801D49FC, func_801A9994, 0x58, entity); + effectSpawnTask(&D_801D49FC, func_801A9994, 0x58, entity); entity->pc++; } @@ -4425,7 +314,7 @@ static void func_801A9BB4(EffectEntity *entity) { static void func_801A9C18(EffectEntity *entity) { /* The byte trio at unk060 is this script's countdown, one halfword wide. */ if (--*(s16 *)entity->unk060 <= 0) { - func_801A0B5C(&D_801D3F6C, func_801A968C, 0x40, entity); + effectSpawnTask(&D_801D3F6C, func_801A968C, 0x40, entity); entity->pc++; } } @@ -4462,7 +351,7 @@ static void func_801A9CEC(EffectEntity *entity) { * * @return 2 once the script has stopped and its children have drained. */ -static s32 func_801A9CF4(EffectEntity *entity) { +s32 func_801A9CF4(EffectEntity *entity) { EffectHandler handlers[11] = { func_801A9AA4, func_801A9AB8, func_801A9ACC, func_801A9B5C, func_801A9BB4, func_801A9C18, func_801A9C80, func_801A9CA8, func_801A9CBC, func_801A9CD0, @@ -4471,29 +360,29 @@ static s32 func_801A9CF4(EffectEntity *entity) { MATRIX *view = EFFECT_SCRATCHPAD; *view = D_800F02C8; - D_801D3CD8 = (u8 *)view; - D_801D3CD4 = view; + g_effectStackTop = (u8 *)view; + g_effectStackBase = view; if (entity->unk05C & EFFECT_FRAME_ODD) { - D_801D479C = D_801D47A4; + g_effectPrimCursor = D_801D47A4; D_801D47A0 = D_801D47AC; } else { - D_801D479C = D_801D47A8; + g_effectPrimCursor = D_801D47A8; D_801D47A0 = D_801D47B0; } - func_801A0EAC(entity); + effectCacheAnimSlotAnchors(entity); handlers[entity->pc](entity); - D_801D3F8C = 0; + g_effectParticleTotal = 0; entity->unk05E = 0; - D_801D3F8E = 0; + g_effectLiveTotal = 0; entity->unk05E += func_800B2B68(&D_801D49FC); entity->unk05E += func_800B2B68(&D_801D4ACC); - entity->unk05E += func_800B2B68(&D_801D478C); + entity->unk05E += func_800B2B68(&g_effectChildPool); entity->unk05E += func_800B2B68(&D_801D3F6C); entity->unk05C++; entity->unk024++; if (entity->flags & EFFECT_FLAG_STOP) { if (entity->wait == 0) { - func_801A0B34(entity); + effectReleaseWait(entity); return 2; } } diff --git a/src/effect/effect_028_entry.c b/src/effect/effect_028_entry.c new file mode 100644 index 00000000..edeab11b --- /dev/null +++ b/src/effect/effect_028_entry.c @@ -0,0 +1,62 @@ +/** + * @file effect_028_entry.c + * @brief Curaga: the overlay entry point. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/entity.h" +#include "effect/lib/tables.h" +#include "effect/effect_028.h" +#include "effect/effect_028_entry.h" + +/** @brief Stride between the prim and frame banks carved out of the TIM. */ +#define EFFECT_BANK_SIZE 0x6000 + +/** + * @brief Start the effect's script and hand back its task pool. + * + * @param animSet Animation set the effect plays. + * @return The pool the root entity lives in. + */ +void *func_801A0000(EffectAnimSet *animSet) { + u8 *bank0; + EffectEntity *entity; + u8 *bank1; + u8 *frames; + + D_801D3CCC = 0; + D_801D3CD0 = 0; + func_800B2A00(&D_801D488C, &D_801D47BC, 0x64, 2); + entity = effectSpawnTask(&D_801D488C, func_801A9CF4, 0x64, NULL); + entity->animSet = animSet; + entity->unk02C = animSet->anims[entity->unk02A].unk000; + entity->unk02D = + entity->animSet->anims[entity->unk02A].parts[entity->unk02B].unk000; + entity->pc = 0; + entity->unk05A = animSet->anims->unk010; + entity->unk058 = animSet->anims->unk011; + entity->unk02F = entity->unk05A - 1; + if (entity->unk02F < entity->unk058) { + entity->unk02F = entity->unk058; + } + if (!(animSet->flags & EFFECT_ANIMSET_FLAG_LOADED)) { + func_800C3BE0(&D_801AA10C); + func_800BB084(&D_801AACAC); + } + D_801D3CDC = &D_801AACAC; + bank0 = &D_801AACAC; + D_801D47A4 = bank0; + bank1 = bank0 + EFFECT_BANK_SIZE; + D_801D47AC = bank0 + EFFECT_BANK_SIZE; + D_801D3CDC = bank0 + EFFECT_BANK_SIZE; + frames = bank1; + D_801D47A8 = frames; + frames += EFFECT_BANK_SIZE; + D_801D3CDC = frames; + D_801D47B0 = frames; + func_800B2A00(&D_801D49FC, &D_801D489C, 0x58, 4); + func_800B2A00(&D_801D4ACC, &D_801D4A0C, 0x3C, 3); + func_800B2A00(&g_effectChildPool, &D_801D3F9C, 0x2A4, 3); + func_800B2A00(&D_801D3F6C, &D_801D3CEC, 0x40, 0xA); + return &D_801D488C; +} diff --git a/src/effect/lib/aim.c b/src/effect/lib/aim.c new file mode 100644 index 00000000..9edf4e5c --- /dev/null +++ b/src/effect/lib/aim.c @@ -0,0 +1,45 @@ +/** + * @file aim.c + * @brief Matrices that aim along a direction. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/aim.h" + +/** @brief Build a matrix in @p m that aims along @p dir with @p up as the roll. */ +void effectMatrixAim(MATRIX *m, SVECTOR *dir, SVECTOR *up) { + MATRIX *basis = func_800B3698(sizeof(MATRIX)); + SVECTOR *row1 = (SVECTOR *)basis->m[1]; + SVECTOR *row2 = (SVECTOR *)basis->m[2]; + + /* A matrix row is 6 bytes, so the whole-vector store spills one halfword + into row 2 -- harmless, row 2 is written next. The width is load-bearing: + it is the unaligned 8-byte copy the target does here, where the + element-wise stores below are three halfwords. */ + *row1 = *up; + row2->vx = dir->vx; + row2->vy = dir->vy; + row2->vz = dir->vz; + gte_ldopv1SV(row1); + gte_ldopv2SV(row2); + gte_op12(); + gte_stsv(basis->m[0]); + MatrixNormal_2(basis, basis); + TransposeMatrix(basis, m); + func_800B36B8(sizeof(MATRIX)); +} + +/** @brief Aim @p m along the vector from @p from to @p to, Y up. */ +void effectMatrixLookAt(MATRIX *m, SVECTOR *from, SVECTOR *to) { + SVECTOR up; + SVECTOR delta; + + up.vx = 0; + up.vy = ONE; + up.vz = 0; + delta.vx = to->vx - from->vx; + delta.vy = to->vy - from->vy; + delta.vz = to->vz - from->vz; + effectMatrixAim(m, &delta, &up); +} diff --git a/src/effect/lib/bankclear.c b/src/effect/lib/bankclear.c new file mode 100644 index 00000000..d481a336 --- /dev/null +++ b/src/effect/lib/bankclear.c @@ -0,0 +1,21 @@ +/** + * @file bankclear.c + * @brief Clear both record banks and the ring cursors. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/entity.h" +#include "effect/lib/pose.h" +#include "effect/lib/particle.h" +#include "effect_params.h" +#include "effect/lib/bankclear.h" + +/** @brief Clear both record banks and the script's frame counters. */ +void effectBanksClear(void) { + effectZeroWords(g_effectEmitterBank, EFFECT_EMITTER_BANK_BYTES); + effectZeroWords(g_effectParticleBank, EFFECT_PARTICLE_BANK_BYTES); + g_effectEmitterCursor = 0; + g_effectParticleCursor = 0; + g_effectBankUnk90 = 0; + g_effectBankUnk92 = 0; +} diff --git a/src/effect/lib/common.c b/src/effect/lib/common.c new file mode 100644 index 00000000..a184086a --- /dev/null +++ b/src/effect/lib/common.c @@ -0,0 +1,191 @@ +/** + * @file common.c + * @brief Debug text, screen quads and the basic matrix builders. + */ +#include "common.h" +#include "game.h" +#include "effect.h" +#include "psxsdk/libgpu.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/common.h" +#include "btl_entity.h" + +/** + * @brief Cache the sixteen hex digit glyphs and reset the debug text cursor. + * + * @note The lookup runs once per glyph -- the target holds 16 separate calls, + * so a loop or a cached pointer does not match. + */ +void effectTextInit(void) { + g_effectHexGlyphs[0] = getMenuString(11)[1]; + g_effectHexGlyphs[1] = getMenuString(11)[2]; + g_effectHexGlyphs[2] = getMenuString(11)[3]; + g_effectHexGlyphs[3] = getMenuString(11)[4]; + g_effectHexGlyphs[4] = getMenuString(11)[5]; + g_effectHexGlyphs[5] = getMenuString(11)[6]; + g_effectHexGlyphs[6] = getMenuString(11)[7]; + g_effectHexGlyphs[7] = getMenuString(11)[8]; + g_effectHexGlyphs[8] = getMenuString(11)[9]; + g_effectHexGlyphs[9] = getMenuString(11)[10]; + g_effectHexGlyphs[10] = getMenuString(11)[11]; + g_effectHexGlyphs[11] = getMenuString(11)[12]; + g_effectHexGlyphs[12] = getMenuString(11)[13]; + g_effectHexGlyphs[13] = getMenuString(11)[14]; + g_effectHexGlyphs[14] = getMenuString(11)[15]; + g_effectHexGlyphs[15] = getMenuString(11)[16]; + g_effectHexGlyphs[16] = 0; + g_effectTextX = 10; + g_effectTextY = 20; + g_effectTextColour = 3; +} + +/** @brief Draw @p value as eight hex glyphs at the current debug text cursor. */ +void effectTextHex(u32 value) { + u32 *ot = D_800FA5E8->frontOT; + u8 text[9]; + s32 i; + + for (i = 7; i >= 0; i--) { + text[i] = g_effectHexGlyphs[value & 0xF]; + value >>= 4; + } + text[8] = 0; + g_effectPrimCursor = func_8002C56C(ot, g_effectPrimCursor, g_effectTextX, + g_effectTextY, text, g_effectTextColour); + g_effectTextX += 0x48; +} + +/** @brief Move the debug text cursor to the start of the next line. */ +void effectTextNewline(void) { + g_effectTextX = 10; + g_effectTextY += 10; +} + +/** @brief Opcode handler: no-op. */ +void effectNop(EffectEntity *entity) { +} + +/** + * @brief Emit a 128x128 textured quad at the head of the prim buffer. + * + * @param x Left edge in screen space. + * @param y Top edge in screen space. + * @param tx Texture page X. + * @param ty Texture page Y. + * @param clutX Palette X. + * @param clutY Palette Y. + * @param abr Semi-transparency rate for the page. + */ +void effectDrawQuad128(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, + s32 abr) { + POLY_FT4 *poly = g_effectPrimCursor; + + setPolyFT4(poly); + setSemiTrans(poly, 1); + setShadeTex(poly, 1); + poly->tpage = getTPage(1, abr, tx, ty); + poly->clut = getClut(clutX, clutY); + poly->u0 = poly->u2 = 0; + poly->u1 = poly->u3 = 0xFF; + poly->v0 = poly->v1 = 0; + poly->v2 = poly->v3 = 0xFF; + poly->x0 = poly->x2 = x; + poly->x1 = poly->x3 = x + 0x7F; + poly->y0 = poly->y1 = y; + poly->y2 = poly->y3 = y + 0x7F; + AddPrim(D_800FA5E8, poly); + poly++; + g_effectPrimCursor = poly; +} + +/** + * @brief Emit a 256x256 textured quad at the head of the prim buffer. + * + * @param x Left edge in screen space. + * @param y Top edge in screen space. + * @param tx Texture page X. + * @param ty Texture page Y. + * @param clutX Palette X. + * @param clutY Palette Y. + * @param abr Semi-transparency rate for the page. + */ +void effectDrawQuad256(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, + s32 abr) { + POLY_FT4 *poly = g_effectPrimCursor; + + setPolyFT4(poly); + setSemiTrans(poly, 1); + setShadeTex(poly, 1); + poly->tpage = getTPage(1, abr, tx, ty); + poly->clut = getClut(clutX, clutY); + poly->u0 = poly->u2 = 0; + poly->u1 = poly->u3 = 0xFF; + poly->v0 = poly->v1 = 0; + poly->v2 = poly->v3 = 0xFF; + poly->x0 = poly->x2 = x; + poly->x1 = poly->x3 = x + 0xFF; + poly->y0 = poly->y1 = y; + poly->y2 = poly->y3 = y + 0xFF; + AddPrim(D_800FA5E8, poly); + poly++; + g_effectPrimCursor = poly; +} + +/** @brief Load the identity matrix. */ +void effectMatrixIdentity(MATRIX *m) { + m->m[0][0] = ONE; + m->m[1][0] = 0; + m->m[2][0] = 0; + m->m[0][1] = 0; + m->m[1][1] = ONE; + m->m[2][1] = 0; + m->m[0][2] = 0; + m->m[1][2] = 0; + m->m[2][2] = ONE; + m->t[0] = 0; + m->t[1] = 0; + m->t[2] = 0; +} + +/** @brief Post-multiply @p m by a rotation of @p angle about X. */ +void effectMatrixRotX(MATRIX *m, s32 angle) { + EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); + + rot->sin = rsin(angle); + rot->cos = rsin((angle + 0x400) & 0xFFF); + rot->m.m[0][0] = ONE; + rot->m.m[0][1] = 0; + rot->m.m[0][2] = 0; + rot->m.m[1][0] = 0; + rot->m.m[1][1] = rot->cos; + rot->m.m[1][2] = -rot->sin; + rot->m.m[2][0] = 0; + rot->m.m[2][1] = rot->sin; + rot->m.m[2][2] = rot->cos; + gte_MulMatrix0(m, &rot->m, m); + func_800B36B8(sizeof(EffectRotScratch)); +} + +/** + * @brief Compose a rotation about the Y axis into @p m. + * + * @param m Matrix rotated in place. + * @param angle Rotation, in the 0x1000-per-turn units @ref rsin takes. + */ +void effectMatrixRotY(MATRIX *m, s32 angle) { + EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); + + rot->sin = rsin(angle); + rot->cos = rsin((angle + 0x400) & 0xFFF); + rot->m.m[0][0] = rot->cos; + rot->m.m[0][1] = 0; + rot->m.m[0][2] = rot->sin; + rot->m.m[1][0] = 0; + rot->m.m[1][1] = ONE; + rot->m.m[1][2] = 0; + rot->m.m[2][0] = -rot->sin; + rot->m.m[2][1] = 0; + rot->m.m[2][2] = rot->cos; + gte_MulMatrix0(m, &rot->m, m); + func_800B36B8(sizeof(EffectRotScratch)); +} diff --git a/src/effect/lib/draw.c b/src/effect/lib/draw.c new file mode 100644 index 00000000..a842307b --- /dev/null +++ b/src/effect/lib/draw.c @@ -0,0 +1,351 @@ +/** + * @file draw.c + * @brief Run the steps of the pose list and draw the particles they leave. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libgpu.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/common.h" +#include "effect/lib/pose.h" +#include "effect/lib/particlestep.h" +#include "effect/lib/emitteralloc.h" +#include "effect/lib/list.h" +#include "effect/lib/particle.h" +#include "effect/lib/draw.h" + +/** @brief GTE @c FLAG bits 18 and 17: SZ3/OTZ saturated, divide overflow. */ +#define BATTLE_SPRITE_GTE_OUT_OF_RANGE ((1 << 18) | (1 << 17)) + +/** @brief The draw-mode packet an additive sprite set is linked behind. */ +#define BATTLE_SPRITE_ADDITIVE_MODE (0xE1000000 | (1 << 9) | getTPage(0, 1, 0, 0)) + +/** @brief Highest texture coordinate a sprite's right or bottom edge takes. */ +#define BATTLE_SPRITE_UV_MAX 0xFF + +static void effectStepRunKind(EffectParticle *node, EffectPoseStep *step); +static s32 effectStepFire(EffectParticle *node, EffectPoseStep *step); +static POLY_FT4 *effectSpriteEmit(BattleSpritePrim *prim, u32 *ot, s32 otShift, + POLY_FT4 *head); +static POLY_FT4 *effectSpritePacket(BattleSpritePrim *prim, u32 *ot, + s32 otShift, POLY_FT4 *head); +static void effectParticleDrawPacket(EffectParticle *node, EffectPoseStep *step); + +/** @brief Run @p step when it is the kind this handler serves. */ +static void effectStepRunKind(EffectParticle *particle, EffectPoseStep *step) { + if (step->unk030 == 1) { + effectEmitterAlloc(particle, step->unk031, particle->unk1D9); + } +} + +/** + * @brief Decide whether @p step fires this frame, and run it if it does. + * @return 1 when the step ran, 0 otherwise. + */ +static s32 effectStepFire(EffectParticle *particle, EffectPoseStep *step) { + switch (step->kind) { + case 0: + if (particle->unk1D2 != 1) { + return 0; + } + break; + case 1: + particle->unk1C8--; + if (particle->unk1C8 >= 0) { + return 0; + } + effectStepRunKind(particle, step); + return 1; + case 2: + particle->unk1C8--; + if (particle->unk1C8 < 0) { + break; + } + if (particle->unk1D2 != 1) { + return 0; + } + break; + default: + return 0; + } + effectStepRunKind(particle, step); + return 1; +} + +/** @brief Step one particle of the pose list, or retire it once its phase is over. */ +void effectParticleStep(EffectParticle *particle) { + EffectPoseStep *step = g_effectDrawList->sources[particle->unk1D6]; + + switch (particle->unk1CC) { + case 0: + effectParticleAgeHold(particle, step); + if (effectStepFire(particle, step) == 0) { + effectParticleAdvance(particle, step); + effectParticlePose(particle, step); + particle->unk1CA++; + } else { + particle->stream = NULL; + particle->unk1CC++; + } + break; + case 1: + effectListRemove(particle); + particle->inUse = 0; + g_effectDrawList->live--; + break; + } +} + +/** @brief One less than @p particle's count, as a signed step. */ +s16 effectEmitterCountStep(EffectEmitter *emitter) { + return emitter->unk008 - 1; +} + +/** + * @brief The overlay's own copy of @ref func_800C97E4: emit the frame's sprites. + * + * @return The prim buffer cursor past the packets emitted. + */ +static POLY_FT4 *effectSpriteEmit(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head) { + POLY_FT4 *p = head; + POLY_FT4 *next; + BattleSprite *sprite = prim->sprites; + s32 count = prim->spriteCount; + s32 i; + s32 flags; + s32 value; /* angle, then each scale: one local, the target keeps all three in s0 */ + s32 w; + s32 h; + s32 uv; + u32 shade; + DR_MODE *tp; + + for (i = 0; i < count; i++, sprite++) { + prim->m.m[2][1] = 0; + prim->m.m[2][0] = 0; + prim->m.m[1][2] = 0; + prim->m.m[1][0] = 0; + prim->m.m[0][2] = 0; + prim->m.m[0][1] = 0; + prim->m.m[2][2] = ONE; + flags = sprite->flags; + if (flags & BATTLE_SPRITE_TRANSFORMED) { + value = sprite->angle; + if (value == 0) { + prim->cos = ONE; + prim->sin = 0; + prim->lastAngle = 0; + } else if (value != prim->lastAngle) { + prim->cos = rcos(value); + prim->sin = rsin(value); + prim->lastAngle = value; + } + value = sprite->scaleX; + prim->m.m[0][0] = prim->cos * value >> 12; + prim->m.m[1][0] = prim->sin * value >> 12; + value = sprite->scaleY; + prim->m.m[0][1] = -prim->sin * value >> 12; + prim->m.m[1][1] = prim->cos * value >> 12; + } else { + prim->m.m[1][1] = ONE; + prim->m.m[0][0] = ONE; + } + w = sprite->w; + h = sprite->h; + prim->corners[2].vx = -(w << 3); + prim->corners[0].vx = -(w << 3); + prim->corners[3].vx = w << 3; + prim->corners[1].vx = w << 3; + prim->corners[1].vy = -(h << 3); + prim->corners[0].vy = -(h << 3); + prim->corners[3].vy = h << 3; + prim->corners[2].vy = h << 3; + prim->m.t[0] = (sprite->x << 4) + (w << 3); + prim->m.t[1] = (sprite->y << 4) + (h << 3); + prim->m.t[2] = 0; + /* m = mtx * m, a column at a time, with the packet filled in the GTE's shadow. */ + gte_SetRotMatrix(&prim->mtx); + gte_ldclmv(&prim->m.m[0][0]); + gte_rtir(); + setlen(p, 9); + /* One word each: w, shade, h, code and u, v, clut are laid out as the packet's. */ + *(u32 *)&p->r0 = *(u32 *)&sprite->w; + *(u32 *)&p->u0 = *(u32 *)&sprite->u; + gte_stclmv(&prim->m.m[0][0]); + gte_ldclmv(&prim->m.m[0][1]); + gte_rtir(); + p->r0 = p->b0 = p->g0; + shade = p->r0 * prim->colour.r; + p->r0 = shade >> 7; + gte_stclmv(&prim->m.m[0][1]); + gte_ldclmv(&prim->m.m[0][2]); + gte_rtir(); + p->u2 = sprite->u; + shade = p->g0 * prim->colour.g; + p->g0 = shade >> 7; + gte_stclmv(&prim->m.m[0][2]); + gte_SetTransMatrix(&prim->mtx); + gte_ldlv0(prim->m.t); + gte_rt(); + p->v1 = sprite->v; + shade = p->b0 * prim->colour.b; + p->b0 = shade >> 7; + gte_stlvnl(prim->m.t); + gte_SetRotMatrix(&prim->m); + gte_SetTransMatrix(&prim->m); + gte_ldv3(&prim->corners[0], &prim->corners[1], &prim->corners[2]); + gte_rtpt(); + uv = sprite->u + w - prim->uvInset; + if (uv > BATTLE_SPRITE_UV_MAX) { + uv = BATTLE_SPRITE_UV_MAX; + } + p->u3 = uv; + p->u1 = uv; + /* The row select is the flag word's top nibble, unsigned. */ + p->clut += getClut(0, prim->clutRow[(u16)flags >> BATTLE_SPRITE_CLUT_ROW_SHIFT]); + gte_stflg(&prim->gteFlag); + if (!(prim->gteFlag & BATTLE_SPRITE_GTE_OUT_OF_RANGE)) { + p->tpage = flags & BATTLE_SPRITE_TPAGE_MASK; + gte_stsxy3(&p->x0, &p->x1, &p->x2); + gte_ldv0(&prim->corners[3]); + gte_rtps(); + uv = sprite->v + h - prim->uvInset; + if (uv > BATTLE_SPRITE_UV_MAX) { + uv = BATTLE_SPRITE_UV_MAX; + } + p->v3 = uv; + p->v2 = uv; + gte_stsxy(&p->x3); + if (prim->otz >= 0) { + addPrim(&ot[otShift], p); + p++; + } else { + gte_avsz4(); + gte_stotz(&prim->otz); + otShift = prim->otz >> otShift; + if (prim->flags & BATTLE_SPRITE_FLAG_ADDITIVE) { + next = p + 1; + setlen(next, 1); + tp = (DR_MODE *)next; + next = (POLY_FT4 *)(tp + 1); + tp->code[0] = BATTLE_SPRITE_ADDITIVE_MODE; + addPrim(&ot[otShift], tp); + addPrim(&ot[otShift], p); + p = next; + } else { + addPrim(&ot[otShift], p); + p++; + } + } + } + } + return p; +} + +/** + * @brief The overlay's own copy of @ref func_800C9E10: set a sprite packet up. + * + * @return The prim buffer cursor past the sprites emitted. + */ +static POLY_FT4 *effectSpritePacket(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head) { + BattleSpriteAnim *anim = prim->anim; + u16 *offsets = anim->offsets; + u8 *frame; + + prim->frameCount = anim->frameCount; + frame = (u8 *)anim + offsets[prim->frame]; + prim->sprites = (BattleSprite *)frame; + prim->spriteCount = *(s32 *)frame; + prim->sprites = (BattleSprite *)((s32 *)prim->sprites + 1); + prim->nextOffset = offsets[prim->frame + 1]; + if (prim->spriteCount < 0) { + prim->spriteCount &= ~BATTLE_SPRITE_COUNT_FLAG; + prim->flags |= BATTLE_SPRITE_FLAG_ADDITIVE; + } + prim->corners[3].vz = 0; + prim->corners[2].vz = 0; + prim->corners[1].vz = 0; + prim->corners[0].vz = 0; + gte_ReadRotMatrix(&prim->mtx); + if (prim->flags & BATTLE_SPRITE_FLAG_ROTATE) { + RotMatrixZ(prim->angle - D_800F02A0, &prim->mtx); + } else if (!(prim->flags & BATTLE_SPRITE_FLAG_NO_ROLL)) { + RotMatrixZ(-D_800F02A0, &prim->mtx); + } + if (prim->flags & BATTLE_SPRITE_FLAG_SCALE) { + prim->scale.vz = ONE; + ScaleMatrixL(&prim->mtx, &prim->scale); + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_COLOUR)) { + *(u32 *)&prim->colour = BATTLE_SPRITE_COLOUR_DEFAULT; + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT0)) { + prim->clutRow[0] = anim->clutRow[0]; + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT1)) { + prim->clutRow[1] = anim->clutRow[1]; + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT2)) { + prim->clutRow[2] = anim->clutRow[2]; + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT3)) { + prim->clutRow[3] = anim->clutRow[3]; + } + if (prim->flags & BATTLE_SPRITE_FLAG_UV_INSET) { + prim->uvInset = 1; + } else { + prim->uvInset = 0; + } + prim->cos = ONE; + prim->lastAngle = 0; + prim->sin = 0; + prim->otz = -1; + return effectSpriteEmit(prim, ot, otShift, head); +} + +/** + * @brief Build one draw packet for @p particle's model and link it into the OT. + * + * @note @p step is not read; every caller has it to hand and passes it. + */ +static void effectParticleDrawPacket(EffectParticle *particle, EffectPoseStep *step) { + BattleSpritePrim *prim; + + g_effectStackTop -= sizeof(BattleSpritePrim); + prim = (BattleSpritePrim *)g_effectStackTop; + gte_SetRotMatrix(&particle->mtx); + gte_SetTransMatrix(&particle->mtx); + prim->anim = particle->stream; + prim->frame = particle->unk1D0; + prim->flags = 0; + g_effectPrimCursor = effectSpritePacket(prim, D_800FA5E8->ot, 2, g_effectPrimCursor); + g_effectStackTop += sizeof(BattleSpritePrim); +} + +/** @brief Draw every particle on the list that has a model and is still posing. */ +void effectDrawParticles(void) { + EffectParticle *particle = g_effectDrawList->head; + + while (particle != NULL) { + if (particle->unk008 == 1) { + EffectPoseStep *step = g_effectDrawList->sources[particle->unk1D6]; + EffectParticle *n = particle; + + if (step->mode < 3 && particle->stream != NULL) { + if (particle->copies == 1) { + effectParticleDrawPacket(particle, step); + } else { + s32 i; + + for (i = 0; i < n->copies; i++) { + n->mtx.t[0] = n->offsets[i].vx; + n->mtx.t[1] = n->offsets[i].vy; + n->mtx.t[2] = n->offsets[i].vz; + effectParticleDrawPacket(n, step); + } + } + } + } + particle = particle->next; + } +} diff --git a/src/effect/lib/drawscript.c b/src/effect/lib/drawscript.c new file mode 100644 index 00000000..2d4f3fc8 --- /dev/null +++ b/src/effect/lib/drawscript.c @@ -0,0 +1,115 @@ +/** + * @file drawscript.c + * @brief Run the draw list a script carries. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/pose.h" +#include "effect/lib/particlestep.h" +#include "effect/lib/draw.h" +#include "effect/lib/list.h" +#include "effect/lib/trail.h" +#include "effect/lib/strands.h" +#include "effect/lib/tables.h" +#include "effect/lib/particle.h" +#include "effect/lib/drawscript.h" + +/** @brief Point the draw list at this script's tables and aim it at its slot. */ +void effectDrawScriptStart(EffectDrawScript *script) { + EffectModel *model = script->model; + EffectPoseTables *tables = script->tables; + s32 slot = script->slot; + EffectDrawList *list = &script->list; + SVECTOR from; + SVECTOR to; + s32 i; + + list->held = script->unk29A; + list->unk024 = script->unk29C; + list->sources = tables->sources; + list->unk228 = tables->unk044; + list->targets = tables->targets; + list->frames = tables->frames; + g_effectDrawList = list; + if (tables->ready == 0) { + effectPoseTablesRelocate(tables); + tables->ready = 1; + } + g_effectDrawList->slot = slot; + g_effectDrawList->strandLen = model->unk05A; + for (i = 0; i < g_effectDrawList->strandLen; i++) { + g_effectDrawList->parts[i] = + script->animSet->anims[script->anim].parts[i].unk000; + } + g_effectDrawList->span = script->unk29E; + effectTrailAim(); + if (g_effectDrawList->unk024 == 4) { + effectTrailPush(script); + } + effectStrandsAim(script); + if (g_effectDrawList->unk024 == 1) { + /* Handed the script the others take, though this one reads nothing. */ + ((void (*)())effectStrandsSeed)(script); + } + if (g_effectDrawList->unk024 == 3) { + effectStrandsPush(script); + } + from = D_800EF2D0[g_effectDrawList->slot].pos; + to = D_800EF2D0[g_effectDrawList->parts[0]].pos; + from.vx -= to.vx; + from.vy -= to.vy; + from.vz -= to.vz; + g_effectDrawList->spread = + SquareRoot0(from.vx * from.vx + from.vy * from.vy + from.vz * from.vz); +} + +/** + * @brief Run one frame of the draw list @p script carries. + * + * @return 1 once the list has finished, 0 while it is still running. + */ +s32 effectDrawScriptRun(EffectDrawScript *script) { + s32 done = 0; + + g_effectDrawList = &script->list; + switch (g_effectDrawList->phase) { + case 0: + g_effectDrawList->phase++; + break; + case 1: + effectEmittersOnCue(); + effectListRun(); + effectDrawParticles(); + effectParticleLateStep(); + g_effectDrawList->age++; + if (g_effectDrawList->age >= g_effectDrawList->span) { + /* Both counts are tested as one word; neither alone would do. */ + if (*(s32 *)&g_effectDrawList->count == 0) { + g_effectDrawList->phase++; + } + } + break; + case 2: + done = 1; + break; + } + g_effectParticleTotal += g_effectDrawList->count; + g_effectLiveTotal += g_effectDrawList->live; + return done; +} + +/** @brief Set the draw list's span from three times the slot's height, clamped. */ +void effectDrawListSpan(s16 slot) { + BattleEffectSlot *entry = &D_800EF2D0[slot]; + /* Both heights are subtracted as raw halfwords before the span is taken. */ + s16 span = (s16)((u16)entry->unk03C - entry->unk036) * 3; + + if (span > 0x4000) { + span = 0x4000; + } else if (span < 0x400) { + span = 0x400; + } + g_effectDrawList->unk21C = span; + g_effectDrawList->unk218 = span; + g_effectDrawList->unk214 = span; +} diff --git a/src/effect/lib/emit.c b/src/effect/lib/emit.c new file mode 100644 index 00000000..6adefc92 --- /dev/null +++ b/src/effect/lib/emit.c @@ -0,0 +1,1255 @@ +/** + * @file emit.c + * @brief The mesh prim emitters that write four-word prims. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libgpu.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/emit.h" + +/** + * @name Prim emitter clip result + * + * One bit per projected vertex per axis, accumulated in the emitters' @c reject. + * A prim is dropped only when every one of its vertices left the screen on the + * same axis, so a prim straddling an edge still draws. + * @{ + */ +#define EFFECT_CLIP_X0 0x1 +#define EFFECT_CLIP_X1 0x2 +#define EFFECT_CLIP_X2 0x4 +#define EFFECT_CLIP_X3 0x8 +#define EFFECT_CLIP_Y0 0x10 +#define EFFECT_CLIP_Y1 0x20 +#define EFFECT_CLIP_Y2 0x40 +#define EFFECT_CLIP_Y3 0x80 +#define EFFECT_CLIP_TRI_X (EFFECT_CLIP_X0 | EFFECT_CLIP_X1 | EFFECT_CLIP_X2) +#define EFFECT_CLIP_TRI_Y (EFFECT_CLIP_Y0 | EFFECT_CLIP_Y1 | EFFECT_CLIP_Y2) +#define EFFECT_CLIP_QUAD_X (EFFECT_CLIP_TRI_X | EFFECT_CLIP_X3) +#define EFFECT_CLIP_QUAD_Y (EFFECT_CLIP_TRI_Y | EFFECT_CLIP_Y3) +/** @} */ + +/** + * @name Prim emitter clip test + * + * A projected vertex is off screen once its unsigned screen coordinate reaches + * these; a prim is dropped only when all of its vertices fail the same axis. + * @{ + */ +#define EFFECT_EMIT_CLIP_X 0x141 +#define EFFECT_EMIT_CLIP_Y 0xD9 +/** @} */ + +/** @brief GTE @c FLAG bits 18 and 17: SZ3/OTZ saturated, divide overflow. */ +#define EFFECT_GTE_OUT_OF_RANGE ((1 << 18) | (1 << 17)) + +/** @brief One flat triangle in the mesh stream: a colour and three vertex slots. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u8 pad00A[0xC - 0xA]; +} EffectEmitTri; /* 0xC */ + +/** @brief The flat quad an emitter writes; seven words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u16 x1; + /* 0x0E */ u16 y1; + /* 0x10 */ u16 x2; + /* 0x12 */ u16 y2; + /* 0x14 */ u16 x3; + /* 0x16 */ u16 y3; +} EffectQuad; /* 0x18 */ + +/** @brief One flat quad in the mesh stream: a colour and four vertex slots. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; +} EffectEmitQuad; /* 0xC */ + +/** + * @brief A prim's UV pair and the id that shares its word. + * + * The stream carries both halves together, so the emitters add the build's + * offset to the whole word and only then patch the id half. + */ +typedef union { + u32 word; + struct { + /* 0x00 */ u16 uv; /**< U in the low byte, V in the high. */ + /* 0x02 */ u16 id; /**< CLUT id or texture page, by slot. */ + } h; +} EffectPrimUV; /* 0x4 */ + +/** @brief The textured triangle an emitter writes; nine words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ EffectPrimUV uv2; +} EffectTexTri; /* 0x20 */ + +/** @brief One textured triangle in the mesh stream. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; +} EffectEmitTexTri; /* 0x14 */ + +/** @brief The gouraud triangle an emitter writes; eight words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u32 rgb1; + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ u32 rgb2; + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; +} EffectGouraudTri; /* 0x1C */ + +/** @brief One gouraud triangle in the mesh stream: three corner colours. */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u8 pad00A[0xC - 0xA]; + /* 0x0C */ u32 colour1; + /* 0x10 */ u32 colour2; +} EffectEmitGouraudTri; /* 0x14 */ + +/** @brief The gouraud quad an emitter writes; ten words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u32 rgb1; + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ u32 rgb2; + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ u32 rgb3; + /* 0x20 */ u16 x3; + /* 0x22 */ u16 y3; +} EffectGouraudQuad; /* 0x24 */ + +/** @brief One gouraud quad in the mesh stream: four corner colours. */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; + /* 0x0C */ u32 colour1; + /* 0x10 */ u32 colour2; + /* 0x14 */ u32 colour3; +} EffectEmitGouraudQuad; /* 0x18 */ + +/** @brief The gouraud textured triangle an emitter writes; eleven words. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u32 rgb1; + /* 0x14 */ u16 x1; + /* 0x16 */ u16 y1; + /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x1C */ u32 rgb2; + /* 0x20 */ u16 x2; + /* 0x22 */ u16 y2; + /* 0x24 */ EffectPrimUV uv2; +} EffectGouraudTexTri; /* 0x28 */ + +/** @brief One gouraud textured triangle in the mesh stream. */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; + /* 0x14 */ u32 colour1; + /* 0x18 */ u32 colour2; +} EffectEmitGouraudTexTri; /* 0x1C */ + +/** @brief The gouraud textured quad an emitter writes; fourteen words. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u32 rgb1; + /* 0x14 */ u16 x1; + /* 0x16 */ u16 y1; + /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x1C */ u32 rgb2; + /* 0x20 */ u16 x2; + /* 0x22 */ u16 y2; + /* 0x24 */ EffectPrimUV uv2; + /* 0x28 */ u32 rgb3; + /* 0x2C */ u16 x3; + /* 0x2E */ u16 y3; + /* 0x30 */ EffectPrimUV uv3; +} EffectGouraudTexQuad; /* 0x34 */ + +/** + * @brief One gouraud textured quad in the mesh stream. + * + * The last two UV pairs share a word, so the build's offset is doubled into + * both halves and the sum split again on the way out. + */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; + /* 0x14 */ u32 uv23; + /* 0x18 */ u32 colour1; + /* 0x1C */ u32 colour2; + /* 0x20 */ u32 colour3; +} EffectEmitGouraudTexQuad; /* 0x24 */ + +/** @brief The textured quad an emitter writes; eleven words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ EffectPrimUV uv2; + /* 0x20 */ u16 x3; + /* 0x22 */ u16 y3; + /* 0x24 */ EffectPrimUV uv3; +} EffectTexQuad; /* 0x28 */ + +/** @brief One textured quad in the mesh stream; its last two UV pairs share a word. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; + /* 0x14 */ u32 uv23; +} EffectEmitTexQuad; /* 0x18 */ + +static EffectTri *effectEmitTris(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly); +static EffectTri *effectEmitQuads(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly); +static EffectTri *effectEmitTexTris(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly); +static EffectTri *effectEmitTexQuads(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly); +static EffectTri *effectEmitGouraudTris(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly); +static EffectTri *effectEmitGouraudQuads(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly); +static EffectTri *effectEmitGouraudTexTris(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly); +static EffectTri *effectEmitGouraudTexQuads(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly); + +/** + * @brief Emit the flat triangles the stream cursor points at. + * + * The entry starts with a triangle count; the cursor is stepped past it before + * anything is drawn, so the caller always resumes at the next entry. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + * + * @note Two spellings here are load-bearing for the whole emitter family. The + * empty @c do/while(0) wraps carry no code: they raise the loop depth + * flow.c weights register references by, which is what puts each + * variable in the register the original picked, and which statement + * needs one differs per emitter. The prim and stream cursors are cast on + * the way in and out because the driver chains one cursor type through + * eight emitters that each write a different prim. + */ +static EffectTri *effectEmitTris(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitTri *tri; + EffectTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[tri->idx2]); + } while (0); + gte_rtpt(); + setlen(prim, 4); + colour = tri->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return prim; +} + +/** + * @brief Emit the flat quads the stream cursor points at. + * + * Same walk as @ref effectEmitTris with a fourth vertex: the first three are + * projected together, the fourth on its own, and the average Z takes all four. + * A quad is dropped when every vertex leaves the screen on the same axis. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitQuads(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitQuad *quad; + EffectQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitQuad *)next; + if (count > 0) { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + gte_rtpt(); + setlen(prim, 5); + colour = quad->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @brief Emit the textured triangles the stream cursor points at. + * + * Same walk as @ref effectEmitTris with texture coordinates: each UV word comes + * from the stream biased by the build's offset, and the texture page and CLUT + * that share the first two UV words are either left alone, offset by the + * build's own, or replaced by it. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitTexTris(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitTexTri *tri; + EffectTexTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectTexTri *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitTexTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[(u16)tri->idx2uv2]); + } while (0); + gte_rtpt(); + setlen(prim, 7); + colour = tri->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = tri->uv0 + s->unk018; + prim->uv1.word = tri->uv1 + s->unk018; + prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} + +/** + * @brief Emit the textured quads the stream cursor points at. + * + * @ref effectEmitQuads with texture coordinates, laid out like + * @ref effectEmitGouraudTexQuads's but with one flat colour for the whole quad. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitTexQuads(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitTexQuad *quad; + EffectTexQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + u32 uv; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectTexQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitTexQuad *)next; + if (count > 0) { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + gte_rtpt(); + setlen(prim, 9); + colour = quad->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = quad->uv0 + s->unk018; + prim->uv1.word = quad->uv1 + s->unk018; + uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); + prim->uv2.word = uv; + prim->uv3.word = uv >> 16; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @brief Emit the gouraud triangles the stream cursor points at. + * + * Same walk as @ref effectEmitTris with a colour per corner. When the build + * asks for a depth cue all three corners go through the GTE together, which + * also refreshes the first corner; otherwise the two extra corners are copied + * straight out of the stream. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitGouraudTris(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly) { + EffectEmitGouraudTri *tri; + EffectGouraudTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectGouraudTri *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitGouraudTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[tri->idx2]); + } while (0); + gte_rtpt(); + setlen(prim, 6); + colour = tri->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&tri->colour1, &tri->colour2, + &prim->rgb0); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); + } else { + prim->rgb1 = tri->colour1; + prim->rgb2 = tri->colour2; + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} + +/** + * @brief Emit the gouraud quads the stream cursor points at. + * + * @ref effectEmitGouraudTris with a fourth corner: the first three vertices are + * projected together and the fourth on its own, and the depth cue runs over + * the three stream colours together before the prim's own colour separately. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitGouraudQuads(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly) { + EffectEmitGouraudQuad *quad; + EffectGouraudQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectGouraudQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitGouraudQuad *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + } while (0); + gte_rtpt(); + setlen(prim, 8); + colour = quad->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&quad->colour1, &quad->colour2, + &quad->colour3); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); + gte_ldrgb(&prim->rgb0); + gte_dpcs(); + gte_strgb(&prim->rgb0); + } else { + prim->rgb1 = quad->colour1; + prim->rgb2 = quad->colour2; + prim->rgb3 = quad->colour3; + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @brief Emit the gouraud textured triangles the stream cursor points at. + * + * @ref effectEmitGouraudTris with texture coordinates: the UV words come from the + * stream biased by the build's offset, and the texture page and CLUT sharing + * the first two are left alone, offset, or replaced as the flags ask. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitGouraudTexTris(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly) { + EffectEmitGouraudTexTri *tri; + EffectGouraudTexTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectGouraudTexTri *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitGouraudTexTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[(u16)tri->idx2uv2]); + } while (0); + gte_rtpt(); + setlen(prim, 9); + colour = tri->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = tri->uv0 + s->unk018; + prim->uv1.word = tri->uv1 + s->unk018; + prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&tri->colour1, &tri->colour2, + &prim->rgb0); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); + } else { + prim->rgb1 = tri->colour1; + prim->rgb2 = tri->colour2; + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} + +/** + * @brief Emit the gouraud textured quads the stream cursor points at. + * + * The widest of the family: four corners, each with its own colour and UV + * pair. @ref effectEmitGouraudTexTris with a fourth vertex. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitGouraudTexQuads(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly) { + EffectEmitGouraudTexQuad *quad; + EffectGouraudTexQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + u32 uv; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectGouraudTexQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitGouraudTexQuad *)next; + if (count > 0) { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + gte_rtpt(); + setlen(prim, 0xC); + colour = quad->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = quad->uv0 + s->unk018; + prim->uv1.word = quad->uv1 + s->unk018; + uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); + prim->uv2.word = uv; + prim->uv3.word = uv >> 16; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&quad->colour1, &quad->colour2, + &quad->colour3); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); + gte_ldrgb(&prim->rgb0); + gte_dpcs(); + gte_strgb(&prim->rgb0); + } else { + prim->rgb1 = quad->colour1; + prim->rgb2 = quad->colour2; + prim->rgb3 = quad->colour3; + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @brief Walk @p prim's mesh stream, letting each emitter draw its own prims. + * + * @return The prim list head, advanced past everything the emitters wrote. + */ +EffectTri *effectEmitStream(EffectPrimBuild *prim, u32 *ot, s32 otShift, EffectTri *head) { + if (!(prim->flags & EFFECT_EMIT_VERTS_SET)) { + prim->verts = (u32 *)(prim->stream + 2); + } + prim->cursor = (s32 *)((u8 *)prim->stream + prim->stream[0]); + if (!(prim->flags & EFFECT_EMIT_KEEP_DEPTH)) { + prim->unk018 = 0; + } + gte_ldfcb(prim->color.r, prim->color.g, prim->color.b); + if (*prim->cursor != 0) { + head = effectEmitTris(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = effectEmitQuads(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = effectEmitTexTris(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = effectEmitTexQuads(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = effectEmitGouraudTris(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = effectEmitGouraudQuads(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = effectEmitGouraudTexTris(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = effectEmitGouraudTexQuads(prim, ot, otShift, head); + } else { + prim->cursor++; + } + return head; +} diff --git a/src/effect/lib/emit_mode.c b/src/effect/lib/emit_mode.c new file mode 100644 index 00000000..d5ccb972 --- /dev/null +++ b/src/effect/lib/emit_mode.c @@ -0,0 +1,1268 @@ +/** + * @file emit_mode.c + * @brief The mesh prim emitters whose prims carry a draw-mode word. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libgpu.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/emit_mode.h" + +/** + * @name Prim emitter clip result + * + * One bit per projected vertex per axis, accumulated in the emitters' @c reject. + * A prim is dropped only when every one of its vertices left the screen on the + * same axis, so a prim straddling an edge still draws. + * @{ + */ +#define EFFECT_CLIP_X0 0x1 +#define EFFECT_CLIP_X1 0x2 +#define EFFECT_CLIP_X2 0x4 +#define EFFECT_CLIP_X3 0x8 +#define EFFECT_CLIP_Y0 0x10 +#define EFFECT_CLIP_Y1 0x20 +#define EFFECT_CLIP_Y2 0x40 +#define EFFECT_CLIP_Y3 0x80 +#define EFFECT_CLIP_TRI_X (EFFECT_CLIP_X0 | EFFECT_CLIP_X1 | EFFECT_CLIP_X2) +#define EFFECT_CLIP_TRI_Y (EFFECT_CLIP_Y0 | EFFECT_CLIP_Y1 | EFFECT_CLIP_Y2) +#define EFFECT_CLIP_QUAD_X (EFFECT_CLIP_TRI_X | EFFECT_CLIP_X3) +#define EFFECT_CLIP_QUAD_Y (EFFECT_CLIP_TRI_Y | EFFECT_CLIP_Y3) +/** @} */ + +/** + * @name Prim emitter clip test + * + * A projected vertex is off screen once its unsigned screen coordinate reaches + * these; a prim is dropped only when all of its vertices fail the same axis. + * @{ + */ +#define EFFECT_EMIT_CLIP_X 0x141 +#define EFFECT_EMIT_CLIP_Y 0xD9 +/** @} */ + +/** @brief GTE @c FLAG bits 18 and 17: SZ3/OTZ saturated, divide overflow. */ +#define EFFECT_GTE_OUT_OF_RANGE ((1 << 18) | (1 << 17)) + +/** @brief One flat triangle in the mesh stream: a colour and three vertex slots. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u8 pad00A[0xC - 0xA]; +} EffectEmitTri; /* 0xC */ + +/** @brief The flat quad an emitter writes; seven words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u16 x1; + /* 0x0E */ u16 y1; + /* 0x10 */ u16 x2; + /* 0x12 */ u16 y2; + /* 0x14 */ u16 x3; + /* 0x16 */ u16 y3; + /* 0x18 */ u32 unk018; +} EffectQuad; /* 0x1C */ + +/** @brief One flat quad in the mesh stream: a colour and four vertex slots. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; +} EffectEmitQuad; /* 0xC */ + +/** + * @brief A prim's UV pair and the id that shares its word. + * + * The stream carries both halves together, so the emitters add the build's + * offset to the whole word and only then patch the id half. + */ +typedef union { + u32 word; + struct { + /* 0x00 */ u16 uv; /**< U in the low byte, V in the high. */ + /* 0x02 */ u16 id; /**< CLUT id or texture page, by slot. */ + } h; +} EffectPrimUV; /* 0x4 */ + +/** @brief The textured triangle an emitter writes; nine words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ EffectPrimUV uv2; + /* 0x20 */ u32 unk020; +} EffectTexTri; /* 0x24 */ + +/** @brief One textured triangle in the mesh stream. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; +} EffectEmitTexTri; /* 0x14 */ + +/** @brief The gouraud triangle an emitter writes; eight words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u32 rgb1; + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ u32 rgb2; + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ u32 unk01C; +} EffectGouraudTri; /* 0x20 */ + +/** @brief One gouraud triangle in the mesh stream: three corner colours. */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u8 pad00A[0xC - 0xA]; + /* 0x0C */ u32 colour1; + /* 0x10 */ u32 colour2; +} EffectEmitGouraudTri; /* 0x14 */ + +/** @brief The gouraud quad an emitter writes; ten words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u32 rgb1; + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ u32 rgb2; + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ u32 rgb3; + /* 0x20 */ u16 x3; + /* 0x22 */ u16 y3; + /* 0x24 */ u32 unk024; +} EffectGouraudQuad; /* 0x28 */ + +/** @brief One gouraud quad in the mesh stream: four corner colours. */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; + /* 0x0C */ u32 colour1; + /* 0x10 */ u32 colour2; + /* 0x14 */ u32 colour3; +} EffectEmitGouraudQuad; /* 0x18 */ + +/** @brief The gouraud textured triangle an emitter writes; eleven words. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u32 rgb1; + /* 0x14 */ u16 x1; + /* 0x16 */ u16 y1; + /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x1C */ u32 rgb2; + /* 0x20 */ u16 x2; + /* 0x22 */ u16 y2; + /* 0x24 */ EffectPrimUV uv2; + /* 0x28 */ u32 unk028; +} EffectGouraudTexTri; /* 0x2C */ + +/** @brief One gouraud textured triangle in the mesh stream. */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; + /* 0x14 */ u32 colour1; + /* 0x18 */ u32 colour2; +} EffectEmitGouraudTexTri; /* 0x1C */ + +/** @brief The gouraud textured quad an emitter writes; fourteen words. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u32 rgb1; + /* 0x14 */ u16 x1; + /* 0x16 */ u16 y1; + /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x1C */ u32 rgb2; + /* 0x20 */ u16 x2; + /* 0x22 */ u16 y2; + /* 0x24 */ EffectPrimUV uv2; + /* 0x28 */ u32 rgb3; + /* 0x2C */ u16 x3; + /* 0x2E */ u16 y3; + /* 0x30 */ EffectPrimUV uv3; + /* 0x34 */ u32 unk034; +} EffectGouraudTexQuad; /* 0x38 */ + +/** @brief The textured quad an emitter writes; eleven words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ EffectPrimUV uv2; + /* 0x20 */ u16 x3; + /* 0x22 */ u16 y3; + /* 0x24 */ EffectPrimUV uv3; + /* 0x28 */ u32 unk028; +} EffectTexQuad; /* 0x2C */ + +/** @brief One textured quad in the mesh stream; its last two UV pairs share a word. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; + /* 0x14 */ u32 uv23; +} EffectEmitTexQuad; /* 0x18 */ + +static EffectTri *effectEmitTris(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly); +static EffectTri *effectEmitQuads(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly); +static EffectTri *effectEmitTexTris(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly); +static EffectTri *effectEmitTexQuads(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly); +static EffectTri *effectEmitGouraudTris(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly); +static EffectTri *effectEmitGouraudQuads(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly); +static EffectTri *effectEmitGouraudTexTris(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly); +static EffectTri *effectEmitGouraudTexQuads(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly); + +/** + * @brief Emit the flat triangles the stream cursor points at. + * + * The entry starts with a triangle count; the cursor is stepped past it before + * anything is drawn, so the caller always resumes at the next entry. Each + * triangle is projected, given the build's colour with the semi-transparency + * bit forced on or off, and dropped when the GTE could not project it, when it + * faces away and the build is not two-sided, or when all three vertices leave + * the screen on the same axis. Survivors optionally take a depth cue and are + * linked into @p ot at their own depth. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + * + * @note Two spellings here are load-bearing for the whole emitter family. The + * empty @c do/while(0) wraps carry no code: they raise the loop depth + * flow.c weights register references by, which is what puts each + * variable in the register the original picked, and which statement + * needs one differs per emitter. The prim and stream cursors are cast on + * the way in and out because the driver chains one cursor type through + * eight emitters that each write a different prim. + */ +static EffectTri *effectEmitTris(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitTri *tri; + EffectTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[tri->idx2]); + } while (0); + gte_rtpt(); + setlen(prim, 5); + colour = tri->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip != 0 && + (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED))) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + prim->unk014 = g_effectDrawMode; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return prim; +} + +/** + * @brief Emit the flat quads the stream cursor points at. + * + * Same walk as @ref effectEmitTris with a fourth vertex: the first three are + * projected together, the fourth on its own, and the average Z takes all four. + * A quad is dropped when every vertex leaves the screen on the same axis. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitQuads(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitQuad *quad; + EffectQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitQuad *)next; + if (count > 0) { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + gte_rtpt(); + setlen(prim, 6); + colour = quad->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip != 0 && + (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED))) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + prim->unk018 = g_effectDrawMode; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @brief Emit the textured triangles the stream cursor points at. + * + * Same walk as @ref effectEmitTris with texture coordinates: each UV word comes + * from the stream biased by the build's offset, and the texture page and CLUT + * that share the first two UV words are either left alone, offset by the + * build's own, or replaced by it. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitTexTris(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitTexTri *tri; + EffectTexTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectTexTri *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitTexTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[(u16)tri->idx2uv2]); + } while (0); + gte_rtpt(); + setlen(prim, 8); + colour = tri->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = tri->uv0 + s->unk018; + prim->uv1.word = tri->uv1 + s->unk018; + prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip != 0 && + (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED))) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + prim->unk020 = g_effectDrawMode; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} + +/** + * @brief Emit the textured quads the stream cursor points at. + * + * @ref effectEmitQuads with texture coordinates, laid out like + * @ref effectEmitGouraudTexQuads's but with one flat colour for the whole quad. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitTexQuads(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitTexQuad *quad; + EffectTexQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + u32 uv; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectTexQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitTexQuad *)next; + if (count > 0) { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + gte_rtpt(); + setlen(prim, 0xA); + colour = quad->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = quad->uv0 + s->unk018; + prim->uv1.word = quad->uv1 + s->unk018; + uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); + prim->uv2.word = uv; + prim->uv3.word = uv >> 16; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip != 0 && + (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED))) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + prim->unk028 = g_effectDrawMode; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @brief Emit the gouraud triangles the stream cursor points at. + * + * Same walk as @ref effectEmitTris with a colour per corner. When the build + * asks for a depth cue all three corners go through the GTE together, which + * also refreshes the first corner; otherwise the two extra corners are copied + * straight out of the stream. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitGouraudTris(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly) { + EffectEmitGouraudTri *tri; + EffectGouraudTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectGouraudTri *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitGouraudTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[tri->idx2]); + } while (0); + gte_rtpt(); + setlen(prim, 7); + colour = tri->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip != 0 && + (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED))) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&tri->colour1, &tri->colour2, + &prim->rgb0); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); + } else { + prim->rgb1 = tri->colour1; + prim->rgb2 = tri->colour2; + } + prim->unk01C = g_effectDrawMode; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} + +/** + * @brief Emit the gouraud quads the stream cursor points at. + * + * @ref effectEmitGouraudTris with a fourth corner: the first three vertices are + * projected together and the fourth on its own, and the depth cue runs over + * the three stream colours together before the prim's own colour separately. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitGouraudQuads(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly) { + EffectEmitGouraudQuad *quad; + EffectGouraudQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectGouraudQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitGouraudQuad *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + } while (0); + gte_rtpt(); + setlen(prim, 9); + colour = quad->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip != 0 && + (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED))) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&quad->colour1, &quad->colour2, + &quad->colour3); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); + gte_ldrgb(&prim->rgb0); + gte_dpcs(); + gte_strgb(&prim->rgb0); + } else { + prim->rgb1 = quad->colour1; + prim->rgb2 = quad->colour2; + prim->rgb3 = quad->colour3; + } + prim->unk024 = g_effectDrawMode; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @brief Emit the gouraud textured triangles the stream cursor points at. + * + * @ref effectEmitGouraudTris with texture coordinates: the UV words come from the + * stream biased by the build's offset, and the texture page and CLUT sharing + * the first two are left alone, offset, or replaced as the flags ask. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitGouraudTexTris(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly) { + EffectEmitGouraudTexTri *tri; + EffectGouraudTexTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectGouraudTexTri *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitGouraudTexTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[(u16)tri->idx2uv2]); + } while (0); + gte_rtpt(); + setlen(prim, 0xA); + colour = tri->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = tri->uv0 + s->unk018; + prim->uv1.word = tri->uv1 + s->unk018; + prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip != 0 && + (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED))) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&tri->colour1, &tri->colour2, + &prim->rgb0); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); + } else { + prim->rgb1 = tri->colour1; + prim->rgb2 = tri->colour2; + } + prim->unk028 = g_effectDrawMode; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} + +/** + * @brief Emit the gouraud textured quads the stream cursor points at. + * + * The widest of the family: four corners, each with its own colour and UV + * pair. @ref effectEmitGouraudTexTris with a fourth vertex. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *effectEmitGouraudTexQuads(EffectPrimBuild *s, u32 *ot, + s32 otShift, EffectTri *poly) { + EffectEmitGouraudTexQuad *quad; + EffectGouraudTexQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + u32 uv; + s32 count; + s32 reject; + s32 i; + + i = 0; + prim = (EffectGouraudTexQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitGouraudTexQuad *)next; + if (count > 0) { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + gte_rtpt(); + setlen(prim, 0xD); + colour = quad->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = quad->uv0 + s->unk018; + prim->uv1.word = quad->uv1 + s->unk018; + uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); + prim->uv2.word = uv; + prim->uv3.word = uv >> 16; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip != 0 && + (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED))) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&quad->colour1, &quad->colour2, + &quad->colour3); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); + gte_ldrgb(&prim->rgb0); + gte_dpcs(); + gte_strgb(&prim->rgb0); + } else { + prim->rgb1 = quad->colour1; + prim->rgb2 = quad->colour2; + prim->rgb3 = quad->colour3; + } + prim->unk034 = g_effectDrawMode; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @brief Run every prim kind of one mesh stream into the ordering table. + * + * The stream names one entry per kind; a zero entry means the mesh has none + * of that kind, and the emitters hand the prim cursor on between them. + * + * @return The prim cursor past everything emitted. + */ +EffectTri *effectEmitMesh(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim) { + if (!(s->flags & EFFECT_EMIT_UNK2000)) { + s->verts = (u32 *)(s->stream + 2); + } + s->cursor = (s32 *)((u8 *)s->stream + s->stream[0]); + if (!(s->flags & EFFECT_EMIT_UNK1000)) { + s->unk018 = 0; + } + g_effectDrawMode = 0xE1000220; + gte_SetFarColor(s->r, s->g, s->b); + if (*s->cursor != 0) { + prim = effectEmitTris(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = effectEmitQuads(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = effectEmitTexTris(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = effectEmitTexQuads(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = effectEmitGouraudTris(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = effectEmitGouraudQuads(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = effectEmitGouraudTexTris(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = effectEmitGouraudTexQuads(s, ot, otShift, prim); + } else { + s->cursor++; + } + return prim; +} diff --git a/src/effect/lib/emitteralloc.c b/src/effect/lib/emitteralloc.c new file mode 100644 index 00000000..0b30f13b --- /dev/null +++ b/src/effect/lib/emitteralloc.c @@ -0,0 +1,51 @@ +/** + * @file emitteralloc.c + * @brief Allocate an emitter from its fixed pool of slots. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/entity.h" +#include "effect/lib/pose.h" +#include "effect/lib/list.h" +#include "effect/lib/particle.h" +#include "effect_params.h" +#include "effect/lib/emitteralloc.h" + +/** + * @brief Allocate an emitter from its pool and put it on the draw list. + * + * The search starts where the last one left off and gives up after one lap. + * + * @return The emitter, or NULL when the pool is full. + */ +EffectEmitter *effectEmitterAlloc(void *owner, s16 source, s16 index) { + EffectEmitter *found = NULL; + s32 i = g_effectEmitterCursor; + s32 n; + + for (n = 0; n < 0x5A; n++) { + if (g_effectEmitterBank[i].inUse == 0) { + EffectEmitter *emitter = &g_effectEmitterBank[i]; + + found = emitter; + effectZeroWords(emitter, sizeof(EffectEmitter)); + emitter->inUse = 1; + emitter->unk06A = source; + emitter->unk06B = index; + emitter->owner = owner; + g_effectDrawList->count++; + effectListAppend((EffectParticle *)emitter, 0); + break; + } + i++; + if (i >= EFFECT_EMITTER_COUNT) { + i = 0; + } + } + i++; + if (i >= EFFECT_EMITTER_COUNT) { + i = 0; + } + g_effectEmitterCursor = i; + return found; +} diff --git a/src/effect/lib/entity.c b/src/effect/lib/entity.c new file mode 100644 index 00000000..9e07b1b5 --- /dev/null +++ b/src/effect/lib/entity.c @@ -0,0 +1,321 @@ +/** + * @file entity.c + * @brief Effect entities: task spawning, battle-slot anchors and model bounds. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libc.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/entity.h" + +/** @brief Opcode handler: release one frame of the linked script's wait. */ +void effectReleaseWait(EffectEntity *entity) { + if (entity->unk018 != NULL) { + entity->unk018->wait--; + } +} + +/** + * @brief Allocate an effect entity from @p pool and hang it off @p owner. + * + * The pool keeps its own 12-byte task header ahead of the entity fields, so + * only the remainder of @p stride is cleared. A child inherits the owner's + * model, animation set and position; an entity with no owner becomes its own + * model. + * + * @param pool Task pool to allocate from. + * @param task Per-frame step to install on the new entity. + * @param stride Total size of the allocation, header included. + * @param owner Parent entity, or NULL for a root. + * @return The new entity, or NULL if the pool is full. + */ +void *effectSpawnTask(void *pool, void *task, s32 stride, + EffectEntity *owner) { + EffectEntity *node = func_800B2A84(pool, task); + s32 *clear; + s32 words; + s32 i; + + if (node == NULL) { + return NULL; + } + clear = (s32 *)&node->animSet; + stride = stride - 0xC; + words = stride / 4; + for (i = 0; i < words; i++) { + *clear = 0; + clear++; + } + if (owner == NULL) { + node->unk018 = NULL; + node->unk014 = NULL; + /* A root drives its own geometry, so it is its own model. */ + node->unk010 = (EffectModel *)node; + } else { + if (owner->unk014 == NULL) { + node->unk014 = (EffectModel *)node; + } else { + node->unk014 = owner->unk014; + } + node->unk018 = owner; + node->unk010 = owner->unk010; + node->animSet = owner->animSet; + node->pos = owner->pos; + node->unk02A = owner->unk02A; + node->unk02B = owner->unk02B; + node->unk02E = owner->unk02E; + node->unk02F = owner->unk02F; + owner->wait++; + node->unk02C = node->animSet->anims[node->unk02A].unk000; + node->unk02D = + node->animSet->anims[node->unk02A].parts[node->unk02B].unk000; + } + return node; +} + +/** @brief Cache the battle slot's two anchor points and their midpoint. */ +void effectCacheSlotAnchors(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + SVECTOR top; + SVECTOR bottom; + + if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { + return; + } + func_800B3960(slot, 0xF0, 0, &top); + func_800B3960(slot, 0xF1, 0, &bottom); + entity->unk040.vx = top.vx; + entity->unk040.vy = top.vy; + entity->unk040.vz = top.vz; + entity->unk038.vx = (top.vx + bottom.vx) / 2; + entity->unk038.vy = (top.vy + bottom.vy) / 2; + entity->unk038.vz = (top.vz + bottom.vz) / 2; + entity->unk030.vx = bottom.vx; + entity->unk030.vy = slot->unk024; + entity->unk030.vz = bottom.vz; +} + +/** @brief Copy @c unk030 of the linked model out to @p out. */ +void effectSlotAnchor0(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk014->unk030; +} + +/** @brief Copy @c unk038 of the linked model out to @p out. */ +void effectSlotAnchor1(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk014->unk038; +} + +/** @brief Copy @c unk040 of the linked model out to @p out. */ +void effectSlotAnchorMid(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk014->unk040; +} + +/** + * @brief As @ref effectCacheSlotAnchors, but for the slot the animation set + * names. + */ +void effectCacheAnimSlotAnchors(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->animSet->slot]; + SVECTOR top; + SVECTOR bottom; + + if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { + return; + } + func_800B3960(slot, 0xF0, 0, &top); + func_800B3960(slot, 0xF1, 0, &bottom); + entity->unk040.vx = top.vx; + entity->unk040.vy = top.vy; + entity->unk040.vz = top.vz; + entity->unk038.vx = (top.vx + bottom.vx) / 2; + entity->unk038.vy = (top.vy + bottom.vy) / 2; + entity->unk038.vz = (top.vz + bottom.vz) / 2; + entity->unk030.vx = bottom.vx; + entity->unk030.vy = slot->unk024; + entity->unk030.vz = bottom.vz; +} + +/** @brief Copy @c unk030 of the linked model out to @p out. */ +void effectAnimSlotAnchor0(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk010->unk030; +} + +/** @brief Copy @c unk038 of the linked model out to @p out. */ +void effectAnimSlotAnchor1(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk010->unk038; +} + +/** @brief Copy @c unk040 of the linked model out to @p out. */ +void effectAnimSlotAnchorMid(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk010->unk040; +} + +/** + * @brief Recompute the model's bounding box from its posed skeleton. + * + * Transforms every joint after the root through the GTE and keeps the running + * minimum and maximum, then publishes the box back onto the entity. + * + * @param entity Entity whose model is measured. + */ +void effectUpdateModelBounds(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + EffectSkeleton *skeleton = slot->mesh->skeleton; + EffectJoint *joint = skeleton->joints; + EffectBoundsScratch *b = func_800B3698(sizeof(EffectBoundsScratch)); + SVECTOR *bounds; + s32 i; + s16 x = joint->mtx.t[0]; + s16 y; + s16 z; + + b->max.vx = x; + b->min.vx = x; + y = joint->mtx.t[1]; + b->max.vy = y; + b->min.vy = y; + z = joint->mtx.t[2]; + b->max.vz = z; + b->min.vz = z; + b->v.vx = 0; + b->v.vy = 0; + joint = &skeleton->joints[1]; + for (i = 1; i < skeleton->count; i++) { + SetRotMatrix(&joint->mtx); + SetTransMatrix(&joint->mtx); + b->v.vz = -joint->unk002; + gte_ldv0(&b->v); + gte_mvmva(1, 0, 0, 0, 0); + gte_stlvnl(&b->pos); + gte_stflg(&b->flag); + if (b->pos.vx < b->min.vx) { + b->min.vx = b->pos.vx; + } else if (b->max.vx < b->pos.vx) { + b->max.vx = b->pos.vx; + } + if (b->pos.vy < b->min.vy) { + b->min.vy = b->pos.vy; + } else if (b->max.vy < b->pos.vy) { + b->max.vy = b->pos.vy; + } + if (b->pos.vz < b->min.vz) { + b->min.vz = b->pos.vz; + } else if (b->max.vz < b->pos.vz) { + b->max.vz = b->pos.vz; + } + joint++; + } + /* The box overlays unk048/unk04C, which the script opcodes use as a table + pointer -- the two never overlap in time. */ + bounds = (SVECTOR *)entity->unk048; + bounds[0] = b->min; + bounds[1] = b->max; + func_800B36B8(sizeof(EffectBoundsScratch)); +} + +/** @brief Copy the linked model's bounding box out to @p min and @p max. */ +void effectGetModelBounds(EffectEntity *entity, SVECTOR *min, SVECTOR *max) { + EffectModel *model = entity->unk014; + + *min = model->boundsMin; + *max = model->boundsMax; +} + +/** + * @brief Half the longest side of the model's bounding box. + * + * @param entity Entity whose model is measured. + * @return Half the largest of the box's three extents. + */ +s32 effectModelRadius(EffectEntity *entity) { + EffectModel *model = entity->unk014; + s16 dx = model->boundsMax.vx - model->boundsMin.vx; + s16 dy = model->boundsMax.vy - model->boundsMin.vy; + s16 dz = model->boundsMax.vz - model->boundsMin.vz; + if (dx > dy) { + if (dx > dz) { + return dx / 2; + } + return dz / 2; + } + if (dy > dz) { + return dy / 2; + } + return dz / 2; +} + +/** @brief Half the height of the linked model's bounding box. */ +s16 effectModelHalfHeight(EffectEntity *entity) { + EffectModel *model = entity->unk014; + + return (s16)(model->boundsMax.vy - model->boundsMin.vy) / 2; +} + +/** @brief Half the linked model's larger horizontal extent. */ +s16 effectModelHalfWidth(EffectEntity *entity) { + EffectModel *model = entity->unk014; + s16 spanX = model->boundsMax.vx - model->boundsMin.vx; + s16 spanZ = model->boundsMax.vz - model->boundsMin.vz; + /* Compared as `span` but returned as `spanX`: testing a separate copy is + what puts the wider-X arm on the taken branch. */ + s16 span = spanX; + + return (span <= spanZ ? spanZ : spanX) / 2; +} + +/** @brief Height of the linked model's bounding box. */ +s16 effectModelHeight(EffectEntity *entity) { + return entity->unk014->boundsMax.vy - entity->unk014->boundsMin.vy; +} + +/** @brief Y of the top of the linked model, in battle-entity space. */ +s16 effectModelTop(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + + return slot->pos.vy + entity->unk014->boundsMax.vy; +} + +/** @brief Y of the bottom of the linked model, in battle-entity space. */ +s16 effectModelBottom(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + + return slot->pos.vy + entity->unk014->boundsMin.vy; +} + +/** @brief A random point up the linked model, in battle-entity space. */ +s16 effectModelRandomY(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + EffectModel *model = entity->unk014; + s16 height = model->boundsMax.vy - model->boundsMin.vy; + s16 offset = (rand() & 0xFFF) * height / 4096; + + return slot->pos.vy + model->boundsMax.vy - offset / 2; +} + +/** @brief Y of the centre of the linked model, in battle-entity space. */ +s16 effectModelCentreY(EffectEntity *entity) { + EffectModel *model = entity->unk014; + + return D_800EF2D0[entity->unk02D].pos.vy + + (model->boundsMax.vy + model->boundsMin.vy) / 2; +} + +/** @brief Centre of the linked model's bounding box. */ +void effectModelCentre(EffectEntity *entity, SVECTOR *out) { + EffectModel *model = entity->unk014; + + out->vx = (model->boundsMax.vx + model->boundsMin.vx) / 2; + out->vy = (model->boundsMax.vy + model->boundsMin.vy) / 2; + out->vz = (model->boundsMax.vz + model->boundsMin.vz) / 2; +} + +/** @brief Zero @p size bytes' worth of words starting at @p dst. */ +void effectZeroWords(void *dst, s32 size) { + s32 *p = dst; + s32 i; + + for (i = 0; i < size / 4; i++) { + *p = 0; + p++; + } +} diff --git a/src/effect/lib/fill.c b/src/effect/lib/fill.c new file mode 100644 index 00000000..284c5e71 --- /dev/null +++ b/src/effect/lib/fill.c @@ -0,0 +1,16 @@ +/** + * @file fill.c + * @brief Fill words. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/fill.h" + +/** @brief Fill @p count words at @p dst with @p value. */ +void effectFillWords(s32 *dst, s32 value, s32 count) { + s32 i; + + for (i = count - 1; i != -1; i--) { + *dst++ = value; + } +} diff --git a/src/effect/lib/heading.c b/src/effect/lib/heading.c new file mode 100644 index 00000000..7df5bd50 --- /dev/null +++ b/src/effect/lib/heading.c @@ -0,0 +1,12 @@ +/** + * @file heading.c + * @brief The heading a matrix faces. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/heading.h" + +/** @brief The heading @p m faces, a quarter turn ahead of its Z axis. */ +s32 effectMatrixHeading(MATRIX *m) { + return (ratan2(m->m[2][2], m->m[0][2]) + 0x400) & 0xFFF; +} diff --git a/src/effect/lib/interpolate.c b/src/effect/lib/interpolate.c new file mode 100644 index 00000000..68c28998 --- /dev/null +++ b/src/effect/lib/interpolate.c @@ -0,0 +1,48 @@ +/** + * @file interpolate.c + * @brief Interpolate a vertex animation between two of its frames. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/interpolate.h" + +/** + * @brief Interpolate every point of @p anim between two of its frames. + * + * @param frameA First frame; 0 selects the rest pose. + * @param frameB Second frame. + * @param t Weight of @p frameB, 0x1000 being all of it. + * @param out Receives @c anim->count points. + */ +void effectInterpolateFrames(EffectVertexAnim *anim, s32 frameA, s32 frameB, + s32 t, SVECTOR *out) { + SVECTOR *a; + SVECTOR *b; + s32 n; + s32 s = ONE - t; + + if (frameA == 0) { + a = anim->frames; + } else { + a = &anim->frames[frameA * anim->count]; + } + if (frameB == 0) { + b = anim->frames; + } else { + b = &anim->frames[frameB * anim->count]; + } + n = anim->count; + while (--n != -1) { + gte_lddp(s); + gte_ldsv(a); + gte_gpf1(); + gte_lddp(t); + gte_ldsv(b); + gte_gpl1(); + gte_stsv(out); + out++; + a++; + b++; + } +} diff --git a/src/effect/lib/list.c b/src/effect/lib/list.c new file mode 100644 index 00000000..259458c3 --- /dev/null +++ b/src/effect/lib/list.c @@ -0,0 +1,195 @@ +/** + * @file list.c + * @brief The draw list itself, and the per-frame setup of the emitters on it. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/common.h" +#include "effect/lib/pose.h" +#include "effect/lib/emitteralloc.h" +#include "effect/lib/draw.h" +#include "effect/lib/spawn.h" +#include "effect/lib/particle.h" +#include "effect/lib/list.h" + +/** + * @brief Give @p particle the count @p value and put it on the tail of the draw list. + * + * @param node Either record kind: both start with the same three link fields, + * and @c unk008 is the tag the walker later dispatches on. + */ +void effectListAppend(EffectParticle *particle, s16 value) { + EffectDrawList *list = g_effectDrawList; + + particle->unk008 = value; + if (list->head == NULL) { + list->head = particle; + } else { + EffectParticle *tail = list->tail; + + tail->next = particle; + particle->prev = tail; + } + list->tail = particle; +} + +/** + * @brief Take @p particle off the draw list, mending both links. + * + * @param node Either record kind; see @ref effectListAppend. + */ +void effectListRemove(EffectParticle *particle) { + EffectParticle *prev = particle->prev; + EffectParticle *next = particle->next; + + if (prev == NULL) { + g_effectDrawList->head = next; + } else { + prev->next = next; + } + if (next == NULL) { + g_effectDrawList->tail = prev; + } else { + next->prev = prev; + } +} + +/** @brief Empty the draw list. */ +void effectListClear(void) { + g_effectDrawList->head = NULL; + g_effectDrawList->tail = NULL; +} + +/** @brief Run every record on the draw list through the step its kind wants. */ +void effectListRun(void) { + EffectParticle *particle = g_effectDrawList->head; + + while (particle != NULL) { + /* Read signed here and unsigned where it is stepped; it is also the + tag saying which record kind the walker is standing on. */ + switch (particle->unk008) { + case 0: + effectEmitterStep((EffectEmitter *)particle); + break; + case 1: + effectParticleStep(particle); + break; + } + particle = particle->next; + } +} + +/** @brief Set @p emitter up for this frame: turn it, place it, and reset its life. */ +void effectEmitterSetup(EffectEmitter *emitter) { + EffectPoseStep *step = g_effectDrawList->sources[emitter->unk06A]; + EffectParticle *owner = emitter->owner; + s32 angle; + + emitter->kind = step->unk011; + switch (step->unk01C) { + case 0: + effectMatrixIdentity(&emitter->mtx); + break; + case 1: + effectMatrixIdentity(&emitter->mtx); + angle = func_80041E84( + g_effectDrawList->trail[1].vx - g_effectDrawList->strands[0][emitter->unk06B].vx, + g_effectDrawList->trail[1].vz - g_effectDrawList->strands[0][emitter->unk06B].vz); + if (angle != 0) { + effectMatrixRotY(&emitter->mtx, angle); + } + break; + case 2: + effectMatrixIdentity(&emitter->mtx); + angle = func_80041E84( + g_effectDrawList->strands[0][emitter->unk06B].vx - g_effectDrawList->trail[1].vx, + g_effectDrawList->strands[0][emitter->unk06B].vz - g_effectDrawList->trail[1].vz); + if (angle != 0) { + effectMatrixRotY(&emitter->mtx, angle); + } + break; + case 3: + emitter->mtx = owner->orient; + break; + case 4: + effectMatrixIdentity(&emitter->mtx); + angle = D_800EF2D0[g_effectDrawList->slot].facing; + if (angle != 0) { + effectMatrixRotY(&emitter->mtx, angle); + } + break; + } + switch (emitter->kind) { + case 0: + effectEmitterOnStrand(emitter); + break; + case 1: + effectEmitterOnTrail(emitter); + break; + case 2: + effectEmitterOnOwner(emitter); + break; + case 3: + effectEmitterOnOwner(emitter); + break; + case 4: + effectEmitterSlide(emitter); + break; + } + emitter->life = step->unk012; +} + +/** @brief Fire each of @p step's four slots that is armed. */ +void effectStepFireSlots(EffectParticle *particle, EffectPoseStep *step) { + if (step->unk028 == 1) { + effectEmitterAlloc(particle, step->unk02C, particle->unk1D9); + } + if (step->unk029 == 1) { + effectEmitterAlloc(particle, step->unk02D, particle->unk1D9); + } + if (step->unk02A == 1) { + effectEmitterAlloc(particle, step->unk02E, particle->unk1D9); + } + if (step->unk02B == 1) { + effectEmitterAlloc(particle, step->unk02F, particle->unk1D9); + } +} + +/** @brief Allocate an emitter for every source whose cue falls on this frame. */ +void effectEmittersOnCue(void) { + s32 source; + + for (source = 0; source < 0x10; source++) { + EffectPoseStep *step = g_effectDrawList->sources[source]; + + if (step != NULL && !((g_effectDrawList->held >> source) & 1)) { + if (step->unk011 < 2 || step->unk011 == 4) { + if (step->unk013 == g_effectDrawList->age) { + if (step->unk037 == 1) { + s32 i; + + for (i = 0; i < g_effectDrawList->strandLen; i++) { + effectEmitterAlloc(NULL, source, i); + } + } else { + effectEmitterAlloc(NULL, source, 0); + } + } + } + } + } +} + +/** @brief Push the script's current position onto the head of the trail. */ +void effectTrailPush(EffectDrawScript *script) { + EffectDrawList *list = g_effectDrawList; + + list->trail[0].vx = script->unk290 << 16; + list->trail[0].vy = script->unk292 << 16; + list->trail[0].vz = script->unk294 << 16; + list->trail[5] = list->trail[0]; + list->trail[4] = list->trail[5]; + list->trail[3] = list->trail[4]; + list->trail[2] = list->trail[3]; + list->trail[1] = list->trail[2]; +} diff --git a/src/effect/lib/packet.c b/src/effect/lib/packet.c new file mode 100644 index 00000000..aade6b82 --- /dev/null +++ b/src/effect/lib/packet.c @@ -0,0 +1,48 @@ +/** + * @file packet.c + * @brief Build a mesh packet for a particle and link its copies into the OT. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/common.h" +#include "effect/lib/pose.h" +#include "effect/lib/emit.h" +#include "effect/lib/particle.h" +#include "effect/lib/packet.h" + +/** @brief Build one mesh packet for @p particle and link every copy into the OT. */ +void effectBuildMeshPacket(EffectParticle *particle, EffectPoseStep *step) { + EffectPrimBuild *prim; + + g_effectStackTop -= sizeof(EffectPrimBuild); + prim = (EffectPrimBuild *)g_effectStackTop; + prim->stream = particle->stream; + prim->flags = 0; + if (step->unk03A == 0) { + prim->flags = EFFECT_EMIT_TWO_SIDED | EFFECT_EMIT_G_TWO_SIDED; + } + if (particle->unk1CE != 0) { + prim->color = particle->color; + prim->flags |= EFFECT_EMIT_DEPTH_CUE | EFFECT_EMIT_G_DEPTH_CUE; + prim->depth = particle->unk1CE; + } + if (particle->copies == 1) { + + gte_SetRotMatrix(&particle->mtx); + gte_SetTransMatrix(&particle->mtx); + g_effectPrimCursor = effectEmitStream(prim, D_800FA5E8->ot, 2, g_effectPrimCursor); + } else { + s32 i; + + for (i = 0; i < particle->copies; i++) { + particle->mtx.t[0] = particle->offsets[i].vx; + particle->mtx.t[1] = particle->offsets[i].vy; + particle->mtx.t[2] = particle->offsets[i].vz; + gte_SetRotMatrix(&particle->mtx); + gte_SetTransMatrix(&particle->mtx); + g_effectPrimCursor = effectEmitStream(prim, D_800FA5E8->ot, 2, g_effectPrimCursor); + } + } + g_effectStackTop += sizeof(EffectPrimBuild); +} diff --git a/src/effect/lib/particlealloc.c b/src/effect/lib/particlealloc.c new file mode 100644 index 00000000..c9e8fc58 --- /dev/null +++ b/src/effect/lib/particlealloc.c @@ -0,0 +1,50 @@ +/** + * @file particlealloc.c + * @brief Allocate a particle from its fixed pool of slots. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/entity.h" +#include "effect/lib/list.h" +#include "effect/lib/particle.h" +#include "effect_params.h" +#include "effect/lib/particlealloc.h" + +/** + * @brief Allocate a particle from its pool and put it on the draw list. + * + * The search starts where the last one left off and gives up after one lap. + * + * @return The particle, or NULL when the pool is full. + */ +EffectParticle *effectParticleAlloc(EffectEmitter *owner, u8 source) { + EffectParticle *found = NULL; + s32 i = g_effectParticleCursor; + s32 n; + + for (n = 0; n < 0x8C; n++) { + if (g_effectParticleBank[i].inUse == 0) { + EffectParticle *particle = &g_effectParticleBank[i]; + + found = particle; + effectZeroWords(particle, sizeof(EffectParticle)); + particle->inUse = 1; + particle->unk1D6 = source; + particle->owner = owner; + particle->unk1D9 = owner->unk06B; + g_effectDrawList->live++; + effectListAppend(particle, 1); + break; + } + i++; + if (i >= EFFECT_PARTICLE_COUNT) { + i = 0; + } + } + i++; + if (i >= EFFECT_PARTICLE_COUNT) { + i = 0; + } + g_effectParticleCursor = i; + return found; +} diff --git a/src/effect/lib/particlestep.c b/src/effect/lib/particlestep.c new file mode 100644 index 00000000..0eafaccc --- /dev/null +++ b/src/effect/lib/particlestep.c @@ -0,0 +1,422 @@ +/** + * @file particlestep.c + * @brief Advance, age and pose the copies of a particle. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/common.h" +#include "effect/lib/rotz.h" +#include "effect/lib/heading.h" +#include "effect/lib/pose.h" +#include "effect/lib/vector.h" +#include "effect/lib/packet.h" +#include "effect/lib/particle.h" +#include "effect/lib/particlestep.h" + +/** @brief Run the late step on every particle whose source is in the right mode. */ +void effectParticleLateStep(void) { + EffectParticle *particle = g_effectDrawList->head; + + while (particle != NULL) { + if (particle->unk008 == 1) { + s32 idx = particle->unk1D6; + EffectPoseStep *step = g_effectDrawList->sources[idx]; + + if ((u32)(step->mode - 4) < 2 && particle->stream != NULL) { + effectBuildMeshPacket(particle, step); + } + } + particle = particle->next; + } +} + +/** @brief Advance every copy of @p particle: spin it, drift it and turn its pose. */ +void effectParticleAdvance(EffectParticle *particle, EffectPoseStep *step) { + VECTOR posed[4]; + VECTOR spinOffset; + VECTOR spun; + MATRIX m; + s32 i; + + particle->angle = particle->angleBase; + particle->angle.vx += step->unk0D8[particle->unk1CA]; + particle->angle.vy += step->unk0DC[particle->unk1CA]; + particle->angle.vz += step->unk0E0[particle->unk1CA]; + particle->angle.vx &= 0xFFF; + particle->angle.vy &= 0xFFF; + particle->angle.vz &= 0xFFF; + particle->lastPos = particle->pos[0]; + switch (step->unk027) { + case 0: + for (i = 0; i < particle->copies; i++) { + if (particle->spinRate != NULL || particle->spin != NULL) { + particle->spin[i] += particle->spinRate[i]; + if (particle->unk1DA != 0) { + effectFadeValue(particle->unk1DA, &particle->spin[i]); + } + spinOffset.vx = 0; + spinOffset.vy = -particle->spin[i]; + spinOffset.vz = 0; + ApplyMatrixLV(&particle->poses[i], &spinOffset, &spun); + particle->offset[i].vx += spun.vx; + particle->offset[i].vy += spun.vy; + particle->offset[i].vz += spun.vz; + } + particle->accel[i].vx += particle->jerk[i].vx; + particle->accel[i].vy += particle->jerk[i].vy; + particle->accel[i].vz += particle->jerk[i].vz; + particle->vel[i].vx += particle->accel[i].vx; + particle->vel[i].vy += particle->accel[i].vy; + particle->vel[i].vz += particle->accel[i].vz; + particle->vel[i].vy += particle->unk1DC; + if (particle->unk1DA != 0) { + effectFadeVector(particle->unk1DA, &particle->vel[i]); + } + particle->offset[i].vx += particle->vel[i].vx; + particle->offset[i].vy += particle->vel[i].vy; + particle->offset[i].vz += particle->vel[i].vz; + } + break; + case 1: + for (i = 0; i < particle->copies; i++) { + particle->offset[i].vx = step->unk13C[particle->unk1CA] << 16; + particle->offset[i].vy = step->unk140[particle->unk1CA] << 16; + particle->offset[i].vz = step->unk144[particle->unk1CA] << 16; + if (particle->owner != NULL) { + m = particle->owner->mtx; + } + effectMatrixIdentity(&m); + ApplyMatrixLV(&m, &particle->offset[i], &particle->offset[i]); + } + break; + } + if (step->unk026 == 1) { + EffectEmitter *owner = particle->owner; + + switch (step->unk01D) { + case 0: + effectMatrixIdentity(&m); + if (owner != NULL) { + m = owner->mtx; + } + if (particle->angle.vz != 0) { + effectMatrixRotZ(&m, particle->angle.vz); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&m, particle->angle.vx); + } + if (particle->angle.vy != 0) { + effectMatrixRotY(&m, particle->angle.vy); + } + break; + case 1: + effectMatrixIdentity(&m); + if (owner != NULL) { + m = owner->mtx; + } + if (particle->angle.vy != 0) { + effectMatrixRotY(&m, particle->angle.vy); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&m, particle->angle.vx); + } + if (particle->angle.vz != 0) { + effectMatrixRotZ(&m, particle->angle.vz); + } + break; + case 2: + TransposeMatrix(g_effectStackBase, &m); + if (particle->angle.vz != 0) { + effectMatrixRotZ(&m, particle->angle.vz); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&m, particle->angle.vx); + } + if (particle->angle.vy != 0) { + effectMatrixRotY(&m, particle->angle.vy); + } + break; + case 3: + TransposeMatrix(g_effectStackBase, &m); + if (particle->angle.vy != 0) { + effectMatrixRotY(&m, particle->angle.vy); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&m, particle->angle.vx); + } + if (particle->angle.vz != 0) { + effectMatrixRotZ(&m, particle->angle.vz); + } + break; + } + for (i = 0; i < particle->copies; i++) { + ApplyMatrixLV(&m, &particle->offset[i], &posed[i]); + } + } else { + for (i = 0; i < particle->copies; i++) { + posed[i] = particle->offset[i]; + } + } + switch (step->unk022) { + case 0: + for (i = 0; i < particle->copies; i++) { + particle->pos[i] = posed[i]; + } + break; + case 1: { + EffectEmitter *owner = particle->owner; + + if (owner != NULL) { + for (i = 0; i < particle->copies; i++) { + particle->pos[i] = owner->pos; + particle->pos[i].vx += posed[i].vx; + particle->pos[i].vy += posed[i].vy; + particle->pos[i].vz += posed[i].vz; + } + } + break; + } + } + if (step->unk01B == 1) { + for (i = 0; i < particle->copies; i++) { + particle->pos[i].vy = 0; + } + } +} + +/** @brief Age @p particle's hold on its current step and advance when it expires. */ +void effectParticleAgeHold(EffectParticle *particle, EffectPoseStep *step) { + switch (step->mode) { + case 0: + particle->unk1D0++; + if (particle->unk1D1 < particle->unk1D0) { + particle->unk1D0 = 0; + particle->unk1D2 = 1; + } + break; + case 2: + particle->unk1D0++; + if (particle->unk1D4 < particle->unk1D0) { + if (particle->unk1D5 != 0) { + particle->unk1D5--; + particle->unk1D0 = particle->unk1D3; + } + } + if (particle->unk1D0 > particle->unk1D1) { + particle->unk1D0 = 0; + particle->unk1D2 = 1; + } + break; + case 1: + particle->unk1D0++; + if (particle->unk1D1 < particle->unk1D0) { + particle->unk1D0 = 0; + } + break; + } +} + +/** @brief Build @p particle's pose: orient it, colour it, scale it and place it. */ +void effectParticlePose(EffectParticle *particle, EffectPoseStep *step) { + VECTOR scale; + /* The original reserved a second slot here and never read it. */ + VECTOR unused; + s32 i; + + if (step->mode == 4 || step->mode == 5) { + u8 *target = &step->targets[particle->unk1CA]; + + particle->stream = g_effectDrawList->targets[*target]; + if (step->mode == 5) { + /* Called with no arguments: particle and step are already in $a0/$a1. */ + ((void (*)())effectBlendPointSets)(); + } + } + switch (step->unk01B) { + case 0: + TransposeMatrix(g_effectStackBase, &particle->orient); + if (particle->angle.vz != 0) { + effectMatrixRotZ(&particle->orient, particle->angle.vz); + } + break; + case 3: { + EffectEmitter *owner = particle->owner; + + switch (step->unk01D) { + case 0: + if (owner != NULL) { + particle->orient = owner->mtx; + } else { + effectMatrixIdentity(&particle->orient); + } + if (particle->angle.vz != 0) { + effectMatrixRotZ(&particle->orient, particle->angle.vz); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&particle->orient, particle->angle.vx); + } + if (particle->angle.vy != 0) { + effectMatrixRotY(&particle->orient, particle->angle.vy); + } + break; + case 1: + if (owner != NULL) { + particle->orient = owner->mtx; + } else { + effectMatrixIdentity(&particle->orient); + } + if (particle->angle.vy != 0) { + effectMatrixRotY(&particle->orient, particle->angle.vy); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&particle->orient, particle->angle.vx); + } + if (particle->angle.vz != 0) { + effectMatrixRotZ(&particle->orient, particle->angle.vz); + } + break; + case 2: + TransposeMatrix(g_effectStackBase, &particle->orient); + if (particle->angle.vz != 0) { + effectMatrixRotZ(&particle->orient, particle->angle.vz); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&particle->orient, particle->angle.vx); + } + if (particle->angle.vy != 0) { + effectMatrixRotY(&particle->orient, particle->angle.vy); + } + break; + case 3: + TransposeMatrix(g_effectStackBase, &particle->orient); + if (particle->angle.vy != 0) { + effectMatrixRotY(&particle->orient, particle->angle.vy); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&particle->orient, particle->angle.vx); + } + if (particle->angle.vz != 0) { + effectMatrixRotZ(&particle->orient, particle->angle.vz); + } + break; + } + break; + } + case 1: + effectMatrixIdentity(&particle->orient); + effectMatrixRotX(&particle->orient, 0x400); + break; + case 2: { + s16 angle = effectMatrixHeading(g_effectStackBase); + + effectMatrixIdentity(&particle->orient); + if (angle != 0) { + effectMatrixRotY(&particle->orient, angle); + } + break; + } + case 4: { + EffectEmitter *owner = particle->owner; + + switch (step->unk01D) { + case 0: + if (owner != NULL) { + particle->orient = owner->mtx; + } else { + effectMatrixIdentity(&particle->orient); + } + if (particle->angle.vz != 0) { + effectMatrixRotZ(&particle->orient, particle->angle.vz); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&particle->orient, particle->angle.vx); + } + if (particle->angle.vy != 0) { + effectMatrixRotY(&particle->orient, particle->angle.vy); + } + break; + case 1: + if (owner != NULL) { + particle->orient = owner->mtx; + } else { + effectMatrixIdentity(&particle->orient); + } + if (particle->angle.vy != 0) { + effectMatrixRotY(&particle->orient, particle->angle.vy); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&particle->orient, particle->angle.vx); + } + if (particle->angle.vz != 0) { + effectMatrixRotZ(&particle->orient, particle->angle.vz); + } + break; + case 2: + TransposeMatrix(g_effectStackBase, &particle->orient); + if (particle->angle.vz != 0) { + effectMatrixRotZ(&particle->orient, particle->angle.vz); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&particle->orient, particle->angle.vx); + } + if (particle->angle.vy != 0) { + effectMatrixRotY(&particle->orient, particle->angle.vy); + } + break; + case 3: + TransposeMatrix(g_effectStackBase, &particle->orient); + if (particle->angle.vy != 0) { + effectMatrixRotY(&particle->orient, particle->angle.vy); + } + if (particle->angle.vx != 0) { + effectMatrixRotX(&particle->orient, particle->angle.vx); + } + if (particle->angle.vz != 0) { + effectMatrixRotZ(&particle->orient, particle->angle.vz); + } + break; + } + break; + } + } + if (step->mode == 4 || step->mode == 5) { + particle->color.r = step->unk0F0[particle->unk1CA]; + particle->color.g = step->unk0F4[particle->unk1CA]; + particle->color.b = step->unk0F8[particle->unk1CA]; + particle->unk1CE = step->unk0FC[particle->unk1CA]; + particle->scaleX = step->unk0E4[particle->unk1CA]; + particle->scaleY = step->unk0E8[particle->unk1CA]; + particle->scaleZ = step->unk0EC[particle->unk1CA]; + scale.vx = particle->scaleX; + scale.vy = particle->scaleY; + scale.vz = particle->scaleZ; + ScaleMatrix(&particle->orient, &scale); + } + if (particle->copies == 1) { + particle->orient.t[0] = particle->pos[0].vx / 0x10000; + particle->orient.t[1] = particle->pos[0].vy / 0x10000; + particle->orient.t[2] = particle->pos[0].vz / 0x10000; + gte_MulMatrix0(g_effectStackBase, &particle->orient, &particle->mtx); + gte_SetTransMatrix(g_effectStackBase); + gte_ldlv0(particle->orient.t); + gte_rt(); + gte_stlvnl(particle->mtx.t); + particle->mtx.t[2] += step->unk038; + } else { + for (i = 0; i < particle->copies; i++) { + particle->orient.t[0] = particle->pos[i].vx / 0x10000; + particle->orient.t[1] = particle->pos[i].vy / 0x10000; + particle->orient.t[2] = particle->pos[i].vz / 0x10000; + gte_MulMatrix0(g_effectStackBase, &particle->orient, &particle->mtx); + gte_SetTransMatrix(g_effectStackBase); + gte_ldlv0(particle->orient.t); + gte_rt(); + gte_stlvnl(particle->mtx.t); + particle->offsets[i].vx = particle->mtx.t[0]; + particle->offsets[i].vy = particle->mtx.t[1]; + particle->offsets[i].vz = particle->mtx.t[2]; + particle->offsets[i].vz += step->unk038; + } + } +} diff --git a/src/effect/lib/pose.c b/src/effect/lib/pose.c new file mode 100644 index 00000000..8c52f6b3 --- /dev/null +++ b/src/effect/lib/pose.c @@ -0,0 +1,483 @@ +/** + * @file pose.c + * @brief Walk a pose script and hand each posed part to a callback. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/fill.h" +#include "effect/lib/pose.h" + +/** + * @name Pose channel flags + * + * @ref EffectPosePart::flags carries one trio per channel: the channel is + * keyed, it also carries a velocity, it also carries an acceleration. The + * trios are the translation, the rotation, the scale and the colour, in the + * order @ref effectPoseWalk walks them; the top two bits are single channels + * of their own, the depth ramp and the vertex-animation cursor. + * @{ + */ +#define EFFECT_POSE_KEY0 0x1 +#define EFFECT_POSE_VEL0 0x2 +#define EFFECT_POSE_ACC0 0x4 +#define EFFECT_POSE_KEY1 0x8 +#define EFFECT_POSE_VEL1 0x10 +#define EFFECT_POSE_ACC1 0x20 +#define EFFECT_POSE_SCALE_KEY 0x40 +#define EFFECT_POSE_SCALE_VEL 0x80 +#define EFFECT_POSE_SCALE_ACC 0x100 +#define EFFECT_POSE_KEY3 0x200 +#define EFFECT_POSE_VEL3 0x400 +#define EFFECT_POSE_ACC3 0x800 +#define EFFECT_POSE_DEPTH 0x1000 +#define EFFECT_POSE_ANIM 0x2000 +/** @} */ + +/** @brief One animated part of a posed model; its flags size its pose slot. */ +typedef struct { + /* 0x00 */ s32 unk000; + /* 0x04 */ s32 flags; +} EffectPosePart; + +/** @brief The part list a posed model carries, each part at a byte offset. */ +typedef struct { + /* 0x00 */ s32 count; + /* 0x04 */ s32 unk004; + /* 0x08 */ s32 offsets[1]; +} EffectPoseParts; + +/** @brief Ceiling of the depth and vertex-animation ramps: @ref ONE in 16.16. */ +#define EFFECT_POSE_RAMP_MAX 0x10000000 + +static void effectPoseFindShorts(s32 key, s32 *table, s16 *out); +static void effectPoseFindWords(s32 key, s32 *table, s32 *out); +static s32 *effectPoseStepChannel(s32 flags, u8 *base, s32 **cursorPtr, + s32 *slot, s32 key, s16 *out); + +/** + * @brief Clear @p size bytes of pose state at @p out and seed it from @p model. + * + * Every part of the model takes a slot sized by its flags; the three-word + * scale slot starts at 1.0 rather than zero. + */ +void effectPoseInit(EffectPoseModel *model, s32 *out, s32 size) { + EffectPoseParts *parts; + EffectPosePart *part; + s32 *offset; + s32 *slot; + s32 count; + s32 flags; + s32 i; + + effectFillWords(out, 0, size >> 2); + out[0] = (s32)model; + slot = out + 2; + parts = (EffectPoseParts *)((u8 *)model + model->partsOffset); + count = parts->count; + offset = parts->offsets; + for (i = 0; i < count; i++) { + part = (EffectPosePart *)((u8 *)parts + *offset++); + flags = part->flags; + if (flags & EFFECT_POSE_ACC0) { + slot += 9; + } else if (flags & EFFECT_POSE_VEL0) { + slot += 6; + } else if (flags & EFFECT_POSE_KEY0) { + slot += 3; + } + if (flags & EFFECT_POSE_ACC1) { + slot += 9; + } else if (flags & EFFECT_POSE_VEL1) { + slot += 6; + } else if (flags & EFFECT_POSE_KEY1) { + slot += 3; + } + if (flags & (EFFECT_POSE_SCALE_KEY | EFFECT_POSE_SCALE_VEL | + EFFECT_POSE_SCALE_ACC)) { + slot[2] = ONE << 16; + slot[1] = ONE << 16; + slot[0] = ONE << 16; + } + if (flags & EFFECT_POSE_SCALE_ACC) { + slot += 9; + } else if (flags & EFFECT_POSE_SCALE_VEL) { + slot += 6; + } else if (flags & EFFECT_POSE_SCALE_KEY) { + slot += 3; + } + if (flags & EFFECT_POSE_ACC3) { + slot += 6; + } else if (flags & EFFECT_POSE_VEL3) { + slot += 4; + } else if (flags & EFFECT_POSE_KEY3) { + slot += 2; + } + if (flags & EFFECT_POSE_DEPTH) { + slot += 3; + } + if (flags & EFFECT_POSE_ANIM) { + slot += 4; + } + } +} + +/** + * @brief Look @p key up in a table of 12-byte entries and copy its three + * halfwords out. + * + * The entries are ordered by key and closed by -1; nothing is written when the + * key is missing. + */ +static void effectPoseFindShorts(s32 key, s32 *table, s16 *out) { + s32 k = *table; + + while (k < key) { + if (-1 == k) { + return; + } + table += 3; + k = *table; + } + if (k == key) { + table++; + *out++ = ((s16 *)table)[0]; + *out++ = ((s16 *)table)[1]; + *out = ((s16 *)table)[2]; + } +} + +/** + * @brief As @ref effectPoseFindShorts, for 16-byte entries holding three words. + */ +static void effectPoseFindWords(s32 key, s32 *table, s32 *out) { + s32 k = *table; + + while (k < key) { + if (-1 == k) { + return; + } + table += 4; + k = *table; + } + if (k == key) { + table++; + *out++ = *table++; + *out = table[0]; + out[1] = table[1]; + } +} + +/** + * @brief Step one channel of a pose and hand its current value to @p out. + * + * @param flags Bit 0 keyed, bit 1 velocity, bit 2 acceleration. + * @param base The keyed tables are at byte offsets from here. + * @param cursorPtr Walks the channel's table offsets; advanced past the ones read. + * @param slot The channel's state; position, then velocity, then acceleration. + * @param key Frame the keyed tables are looked up at. + * @param out Receives the channel's three components. + * @return The state past this channel. + * + * @note The gotos are the shape the original had: each arm falls into the next + * one's body, so the move and emit blocks exist once. Written as an + * if/else-if chain the compiler copies them instead (143 instructions + * against 124); written as three independent tests it reaches the emit + * block through a mask of 6 and 7 rather than 2 and 1. + */ +static s32 *effectPoseStepChannel(s32 flags, u8 *base, s32 **cursorPtr, + s32 *slot, s32 key, s16 *out) { + s32 *cursor = *cursorPtr; + s32 *vel; + s32 *acc; + s16 *p; + s32 v; + + if (flags & EFFECT_POSE_ACC0) { + vel = slot + 3; + acc = slot + 6; + vel[0] += acc[0]; + vel[1] += acc[1]; + vel[2] += acc[2]; + goto move; + } + if (flags & EFFECT_POSE_VEL0) { + vel = slot + 3; +move: + slot[0] += vel[0]; + slot[1] += vel[1]; + slot[2] += vel[2]; + p = (s16 *)slot; + goto emit; + } + if (flags & EFFECT_POSE_KEY0) { + p = (s16 *)slot; +emit: + v = p[1]; + out[0] = v; + v = p[3]; + out[1] = v; + v = p[5]; + out[2] = v; + } + if (flags & EFFECT_POSE_KEY0) { + effectPoseFindWords(key, (s32 *)(base + *cursor++), slot); + v = ((s16 *)slot)[1]; + slot++; + out[0] = v; + v = ((s16 *)slot)[1]; + slot++; + out[1] = v; + v = ((s16 *)slot)[1]; + slot++; + out[2] = v; + } else if (flags & (EFFECT_POSE_VEL0 | EFFECT_POSE_ACC0)) { + slot += 3; + } + if (flags & EFFECT_POSE_VEL0) { + effectPoseFindWords(key, (s32 *)(base + *cursor++), slot); + slot += 3; + } else if (flags & EFFECT_POSE_ACC0) { + slot += 3; + } + if (flags & EFFECT_POSE_ACC0) { + effectPoseFindWords(key, (s32 *)(base + *cursor++), slot); + slot += 3; + } + *cursorPtr = cursor; + return slot; +} + +/** + * @brief Walk one pose script and hand every part it poses to @p fn. + * + * The script opens with a part count and a frame count, then one byte offset + * per part. A part entry names the model part it drives and the channels it + * carries, and is followed by one keyed table per channel the flags claim. + * @p pose holds the model, the frame the script is on, and then the running + * state of every channel of every part -- the layout @ref effectPoseInit sizes. + * That state is one array of words, but a channel holds either words or pairs + * of halfwords, which is what every cast of @c slot below is reading. + * + * A channel that carries an acceleration steps its velocity by it, and one + * that carries a velocity steps its value; the keyed table is read after that, + * and overwrites the state only on a frame it holds. Channels the flags leave + * out keep whatever the zeroed pose gives them, scale excepted -- that starts + * at 1.0. + * + * @param pose The model and the frame the script is on. + * @param fn Called once per part, with the pose the script left. + * @param req Draw state handed straight to @p fn. + * @return The frames left after this one; 0 once the script has run out. + * + * @note The gotos are the shape the original had. The colour channel's three + * arms fall into each other -- acceleration into the velocity ramp, and + * that into the keyed arm's colour store -- so each body exists once; + * spelled as an if/else-if chain the compiler copies them instead, the + * same way it does in @ref effectPoseStepChannel. The two out of the + * table walks land on the code that keeps the stepped value: reaching it + * with a @c break costs an instruction, because the frame comparison the + * walk falls out to is then on the path. + */ +s32 effectPoseWalk(s32 *pose, EffectPartFn fn, EffectDrawRequest *req) { + EffectPartPose part; + s32 *cursor; + s32 *script; + s32 *offsets; + s32 *depthTab; + u8 *entry; + s32 *slot; + u16 *vel; + u16 *rgb; + u16 frame; + u16 *acc; + s32 count; + s32 frames; + u32 flags; + s32 depthKey; + s32 i; + s32 v; + s32 *animTab; + s32 animKey; + s32 value; + s32 limit; + + script = (s32 *)((u8 *)pose[0] + ((s32 *)pose[0])[1]); + frames = script[1]; + if (pose[1] >= frames) { + return 0; + } + offsets = script + 2; + slot = pose + 2; + count = script[0]; + for (i = 0; i < count; i++) { + effectFillWords((s32 *)&part, 0, sizeof(part) / sizeof(s32)); + entry = (u8 *)script + *offsets++; + part.index = i; + part.scale[2] = ONE; + part.scale[1] = ONE; + part.scale[0] = ONE; + cursor = (s32 *)entry; + part.unk004 = *cursor++; + part.mesh = (u8)part.unk004; + flags = *cursor++; + slot = effectPoseStepChannel(flags, entry, &cursor, slot, pose[1], + part.pos); + slot = effectPoseStepChannel(flags >> 3, entry, &cursor, slot, pose[1], + part.rot); + slot = effectPoseStepChannel(flags >> 6, entry, &cursor, slot, pose[1], + part.scale); + if (flags & EFFECT_POSE_ACC3) { + vel = (u16 *)slot + 4; + acc = (u16 *)slot + 8; + vel[0] += acc[0]; + vel[1] += acc[1]; + vel[2] += acc[2]; + goto ramp; + } + if (flags & EFFECT_POSE_VEL3) { + vel = (u16 *)slot + 4; +ramp: + rgb = (u16 *)slot; + v = rgb[0] + (s16)vel[0]; + if (v < 0) { + v = 0; + } else if (v > 0xFFFF) { + v = 0xFFFF; + } + rgb[0] = v; + v = rgb[1] + (s16)vel[1]; + if (v < 0) { + v = 0; + } else if (v > 0xFFFF) { + v = 0xFFFF; + } + rgb[1] = v; + v = rgb[2] + (s16)vel[2]; + if (v < 0) { + v = 0; + } else if (v > 0xFFFF) { + v = 0xFFFF; + } + rgb[2] = v; + goto emit; + } + if (flags & EFFECT_POSE_KEY3) { + rgb = (u16 *)slot; +emit: + part.colour.r = rgb[0] >> 8; + part.colour.g = rgb[1] >> 8; + part.colour.b = rgb[2] >> 8; + part.colour.cd = 0; + } + if (flags & EFFECT_POSE_KEY3) { + effectPoseFindShorts(pose[1], (s32 *)(entry + *cursor++), + (s16 *)slot); + part.colour.r = *(u16 *)slot >> 8; + slot = (s32 *)((u16 *)slot + 1); + part.colour.g = *(u16 *)slot >> 8; + slot = (s32 *)((u16 *)slot + 1); + part.colour.b = *(u16 *)slot >> 8; + part.colour.cd = 0; + slot++; + } else if (flags & (EFFECT_POSE_VEL3 | EFFECT_POSE_ACC3)) { + slot += 2; + } + if (flags & EFFECT_POSE_VEL3) { + effectPoseFindShorts(pose[1], (s32 *)(entry + *cursor++), + (s16 *)slot); + slot += 2; + } else if (flags & EFFECT_POSE_ACC3) { + slot += 2; + } + if (flags & EFFECT_POSE_ACC3) { + effectPoseFindShorts(pose[1], (s32 *)(entry + *cursor++), + (s16 *)slot); + slot += 2; + } + if (flags & EFFECT_POSE_DEPTH) { + slot[1] += slot[2]; + slot[0] += slot[1]; + if (slot[0] < 0) { + slot[0] = 0; + } else { + limit = EFFECT_POSE_RAMP_MAX; + if (slot[0] > limit) { + slot[0] = limit; + } + } + depthTab = (s32 *)(entry + *cursor++); + depthKey = *depthTab; + while (depthKey < pose[1]) { + if (depthKey == -1) { + goto noDepthKey; + } + depthTab += 4; + depthKey = *depthTab; + } + if (depthKey == pose[1]) { + depthTab++; + value = *depthTab++; + *slot++ = value; + part.depth = value >> 16; + *slot++ = *depthTab; + *slot++ = depthTab[1]; + } else { +noDepthKey: + value = ((s16 *)slot)[1]; + part.depth = value; + slot += 3; + } + } + if (flags & EFFECT_POSE_ANIM) { + slot[2] += slot[3]; + slot[1] += slot[2]; + if (slot[1] < 0) { + slot[1] = 0; + } else { + limit = EFFECT_POSE_RAMP_MAX; + if (slot[1] > limit) { + slot[1] = limit; + } + } + animTab = (s32 *)(entry + *cursor++); + animKey = *animTab; + while (animKey < pose[1]) { + if (animKey == -1) { + goto noAnimKey; + } + animTab += 5; + animKey = *animTab; + } + if (animKey == pose[1]) { + animTab++; + frame = *(u16 *)animTab; + *(u16 *)slot = frame; + part.frameA = frame; + slot = (s32 *)((u16 *)slot + 1); + animTab = (s32 *)((u16 *)animTab + 1); + frame = *(u16 *)animTab; + *(u16 *)slot = frame; + part.frameB = frame; + slot = (s32 *)((u16 *)slot + 1); + animTab = (s32 *)((u16 *)animTab + 1); + value = *animTab++; + *slot++ = value; + part.weight = value >> 16; + *slot++ = *animTab; + *slot++ = animTab[1]; + } else { +noAnimKey: + part.frameA = *(u16 *)slot; + slot = (s32 *)((u16 *)slot + 1); + part.frameB = *(u16 *)slot; + slot = (s32 *)((u16 *)slot + 1); + value = ((s16 *)slot)[1]; + part.weight = value; + slot += 3; + } + } + fn(pose, &part, req); + } + pose[1]++; + return frames - pose[1]; +} diff --git a/src/effect/lib/render.c b/src/effect/lib/render.c new file mode 100644 index 00000000..cbb0db14 --- /dev/null +++ b/src/effect/lib/render.c @@ -0,0 +1,484 @@ +/** + * @file render.c + * @brief The effect renderer: pose a model and draw its meshes. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libgpu.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/common.h" +#include "effect/lib/aim.h" +#include "effect/lib/render.h" + +static void effectRenderSetPose(EffectRender *render, BattleEffectSlot *slot); +static void effectRenderFaceCamera(EffectRender *render); +static void effectRenderMesh(EffectMesh *mesh, u32 *ot, s32 mode, + EffectRender *render); + +/** @brief Refresh the render matrices from @p pose and apply the offset. */ +static void effectRenderSetPose(EffectRender *render, BattleEffectSlot *slot) { + SVECTOR offset; + + render->unk014 = slot->mtx; + render->unk054 = render->unk014; + switch (render->unk0D2) { + case 0: + ApplyMatrixSV(&render->unk054, &render->unk0B4, &offset); + render->unk054.t[0] += offset.vx; + render->unk054.t[1] += offset.vy; + render->unk054.t[2] += offset.vz; + break; + case 1: + render->unk054.t[0] += render->unk0B4.vx; + render->unk054.t[1] += render->unk0B4.vy; + render->unk054.t[2] += render->unk0B4.vz; + break; + } +} + +/** @brief Compose each joint's matrix through the pose and the per-joint offset. */ +void effectRenderPoseJoints(EffectSkeletonRef *ref, EffectPose *pose) { + MATRIX *offset = g_effectJointOffsets; + EffectSkeleton *skeleton = ref->mesh->skeleton; + EffectJoint *joint = skeleton->joints; + s32 i; + + for (i = 0; i < skeleton->count; i++) { + CompMatrix(&pose->world, &joint->mtx, offset); + CompMatrix(&pose->view, &joint->mtx, &joint->mtx); + joint++; + offset++; + } +} + +/** @brief Rebase a table of absolute pointers into offsets from its own head. */ +void effectRebasePointers(s32 *table) { + s32 *base; + s32 count; + s32 i; + + base = table; + count = *table; + for (i = 0; i < count; i++) { + table++; + *table -= (s32)base; + } +} + +/** @brief Aim the render's model at the camera reference point. */ +static void effectRenderFaceCamera(EffectRender *render) { + EffectAimScratch *a = func_800B3698(sizeof(EffectAimScratch)); + s32 dx; + s32 dy; + s32 dz; + + a->pos.vx = render->unk054.t[0]; + a->pos.vy = render->unk054.t[1]; + a->pos.vz = render->unk054.t[2]; + dx = a->pos.vx - render->unk0DC; + a->delta.vx = dx; + dy = a->pos.vy - render->unk0DE; + a->delta.vy = dy; + dz = a->pos.vz - render->unk0E0; + a->delta.vz = dz; + a->distSq = dx * dx + dy * dy + dz * dz; + a->dist = SquareRoot0(a->distSq); + a->up.vx = 0; + a->up.vy = ONE; + a->up.vz = 0; + VectorNormalS(&a->delta, &a->dir); + effectMatrixAim(&a->rot, &a->dir, &a->up); + TransposeMatrix(&a->rot, &a->out); + a->out.t[0] = 0; + a->out.t[1] = 0; + a->out.t[2] = render->scale; + a->rot = render->unk014; + a->rot.t[0] = render->unk014.t[0] - render->unk054.t[0]; + a->rot.t[1] = render->unk014.t[1] - render->unk054.t[1]; + a->rot.t[2] = render->unk014.t[2] - render->unk054.t[2]; + gte_MulMatrix0(&a->out, &a->rot, &render->unk074); + gte_SetTransMatrix(&a->out); + gte_ldlv0(a->rot.t); + gte_mvmva(1, 0, 0, 0, 0); + gte_stlvnl(render->unk074.t); + func_800B36B8(sizeof(EffectAimScratch)); +} + +/** @brief Reset a render request to its default pose, white, and unit scale. */ +void effectRenderReset(EffectRender *render, void *pose, EffectRenderPart *part, + void *texture, BattleEffectSlot *source, s32 frame) { + render->unk00C = &D_800FA5F0; + render->unk000 = pose; + render->part = part; + render->unk010 = &g_effectPrimCursor; + render->unk0B4.vx = 0; + render->unk0B4.vy = 0; + render->unk0B4.vz = 0; + render->r = 0x80; + render->g = 0x80; + render->b = 0x80; + render->scale = 0x2000; + render->unk0D2 = 1; + render->unk0CE = 0; + render->unk0F2 = 0; + render->unk0A4.vx = 0; + render->unk0A4.vy = 0; + render->unk0A4.vz = 0; + render->unk0AC.vx = 0; + render->unk0AC.vy = 0; + render->unk0AC.vz = 0; + render->unk0BC.vx = 0; + render->unk0BC.vy = 0; + render->unk0BC.vz = 0; + render->slot = source; + render->unk0DA = frame; + part->unk018 = 0x140; + part->unk004 = texture; + part->unk014 = 0; + part->unk016 = 0; + part->unk01A = 0; + part->r = 0x80; + part->g = 0x80; + part->b = 0x80; + part->unk020 = -1; + part->unk024 = 0; +} + +/** @brief Point a render request at a texture page, CLUT and source rectangle. */ +void effectRenderSetTexture(EffectRender *render, SVECTOR *pos, s16 tx, s16 ty, + s16 clutX, s16 clutY, s16 w, s16 h) { + POLY_FT3 poly; + + render->unk0DC = pos->vx; + render->unk0DE = pos->vy; + render->unk0E0 = pos->vz; + setPolyFT3(&poly); + setSemiTrans(&poly, 1); + setShadeTex(&poly, 1); + poly.tpage = getTPage(1, 1, tx, ty); + poly.clut = getClut(clutX, clutY); + render->tpage = poly.tpage; + render->clut = poly.clut; + render->unk0E4 = w; + render->unk0E6 = h; + /* The u coordinate within the page, doubled for the 4bpp texture. */ + render->unk0E8 = ((s16)tx - (s16)(tx & ~0x3F)) * 2; + render->unk0EA = ty & 0xFF; +} + +/** + * @brief Draw one mesh: pose its vertices by joint, then emit its triangles and + * quads into the ordering table. + * + * Each part of the mesh is a command stream. Its first section lists vertex + * groups, each posed through one joint of the skeleton with the GTE's light + * matrix (the part's vertices are normals: @c mvmva against the loaded joint). + * The aligned tail holds a triangle list and a quad list. Every primitive is + * transformed twice: once with the view matrix, which gives the screen + * coordinates and depth for BOTH the shaded prim (whose UVs are the screen + * coordinates -- an environment map) and the textured prim; then with the + * light matrix, whose winding decides the back-face cull when the render asks + * for it. A primitive is also rejected when any projected vertex leaves the + * clip rectangle, in which case only the textured prim is emitted. + * + * The prim cursors come from and return to the render's two list heads; the + * scratch record lives in the battle scratchpad for the duration. + * + * @param mesh Skeleton and part table to draw. + * @param ot Ordering table the prims are linked into. + * @param mode Unused. + * @param render Render state: matrices, colour, texture page and clip rect. + */ +static void effectRenderMesh(EffectMesh *mesh, u32 *ot, s32 mode, + EffectRender *render) { + EffectMeshVertex *vbuf = render->part->unk004; + EffectJoint *joints = mesh->skeleton->joints; + u32 *parts = mesh->parts; + s32 count = parts[0]; + /* battle.bin keeps this list head as a word (see D_800FA5F0). */ + POLY_FT3 *ft3 = (POLY_FT3 *)render->unk00C[0]; + POLY_GT3 *gt3 = render->unk010[0]; + BattleEffectSlot *slot = render->slot; + EffectMeshScratch *s = func_800B3698(sizeof(EffectMeshScratch)); + s32 part; + POLY_FT4 *ft4; + POLY_GT4 *gt4; + u32 colour; + + s->visible = slot->unk07C; + s->unk0CE = render->unk0F4; + s->tpage = render->tpage; + s->clut = render->clut; + s->clipX0 = -(render->unk0E4 / 2); + s->clipX1 = render->unk0E4 / 2 - 1; + s->clipY0 = -(render->unk0E6 / 2); + s->clipY1 = render->unk0E6 / 2 - 1; + s->clipX0 += EFFECT_SCREEN_CX; + s->clipX1 += EFFECT_SCREEN_CX; + s->clipY0 += EFFECT_SCREEN_CY; + s->clipY1 += EFFECT_SCREEN_CY; + s->offX = render->unk0E8 + render->unk0E4 / 2 - EFFECT_SCREEN_CX; + s->offY = render->unk0EA + render->unk0E6 / 2 - EFFECT_SCREEN_CY; + /* The shaded prims are semi-transparent (code bit 2), the textured are not. */ + s->colour = *(u32 *)&render->r & EFFECT_PRIM_RGB; + s->unk0AC = s->colour | EFFECT_PRIM_CODE(0x3C | 0x02); + s->colour = s->colour | EFFECT_PRIM_CODE(0x34 | 0x02); + s->unk0B0 = slot->unk028 & EFFECT_PRIM_RGB; + s->unk0B4 = s->unk0B0 | EFFECT_PRIM_CODE(0x2C); + s->unk0B0 = s->unk0B0 | EFFECT_PRIM_CODE(0x24); + s->view = render->unk034; + s->light = render->unk074; + s->unk0A4 = render->unk0CE; + s->unk0A6 = render->unk0CC; + parts++; + gte_SetRotMatrix(&s->view); + gte_SetTransMatrix(&s->view); + for (part = 0; part < count; part++) { + EffectMeshVertex *vb = vbuf; + s16 *stream = (s16 *)((u8 *)mesh->parts + *parts++); + s32 groups; + s32 g; + s32 n; + s32 v; + s32 tris; + EffectMeshTri *tri; + MATRIX *jm; + EffectMeshQuad *quad; + s32 quads; + POLY_GT3 *gt; + POLY_FT3 *ft; + + if (!((s->visible >> part) & 1)) { + continue; + } + groups = *stream++; + for (g = 0; g < groups; g++) { + jm = &joints[*stream++].mtx; + gte_SetLightMatrix(jm); + gte_ldbkdir(jm->t[0], jm->t[1], jm->t[2]); + n = *stream++; + for (v = 0; v < n; v++) { + s->normal.vx = *stream++; + s->normal.vy = *stream++; + s->normal.vz = *stream++; + gte_ldv0(&s->normal); + gte_mvmva(1, 1, 0, 1, 0); + gte_stsv(&vb->pos); + vb++; + } + } + stream = (s16 *)(((u32)stream + 3) & ~3); + tris = *stream++; + quads = *stream++; + stream += 4; + tri = (EffectMeshTri *)stream; + vb = vbuf; + gt = gt3; + ft = ft3; + for (g = 0; g < tris; g++) { + s->idx0 = tri->idx0 & EFFECT_MESH_INDEX_MASK; + s->idx1 = tri->idx1 & EFFECT_MESH_INDEX_MASK; + s->idx2 = tri->idx2 & EFFECT_MESH_INDEX_MASK; + gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, &vb[s->idx2].pos); + gte_rtpt(); + gte_nclip(); + gte_stopz(&s->nclip); + if (s->nclip > 0) { + gte_avsz3(); + gte_stotz(&s->otz); + s->otz = (u32)s->otz >> 2; + gte_stsxy3_gt3(gt); + gte_stsxy3_ft3(ft); + gte_SetRotMatrix(&s->light); + gte_SetTransMatrix(&s->light); + gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, + &vb[s->idx2].pos); + gte_rtpt(); + s->reject = 0; + gte_nclip(); + gte_stopz(&s->nclip); + if (s->unk0CE == 1 && s->nclip <= 0) { + s->reject = 1; + } + if (!s->reject) { + gte_stsxy3c(s->sxy); + for (v = 0; v < 3; v++) { + if (s->sxy[v].vx < s->clipX0) { + s->reject = 1; + break; + } + if (s->clipX1 < s->sxy[v].vx) { + s->reject = 1; + break; + } + if (s->sxy[v].vy < s->clipY0) { + s->reject = 1; + break; + } + if (s->clipY1 < s->sxy[v].vy) { + s->reject = 1; + break; + } + s->sxy[v].vx += s->offX; + s->sxy[v].vy += s->offY; + } + } + if (!s->reject) { + gt->tag = EFFECT_PRIM_TAG(POLY_GT3); + gt->u0 = s->sxy[0].vx; + gt->v0 = s->sxy[0].vy; + gt->u1 = s->sxy[1].vx; + gt->v1 = s->sxy[1].vy; + gt->u2 = s->sxy[2].vx; + gt->v2 = s->sxy[2].vy; + gt->tpage = s->tpage; + gt->clut = s->clut; + /* Colour and code as one word; four byte stores do not match. */ + colour = s->colour; + *(u32 *)>->r2 = colour; + *(u32 *)>->r1 = colour; + *(u32 *)>->r0 = colour; + addPrim(&ot[s->otz], gt); + gt++; + } + ft->tag = EFFECT_PRIM_TAG(POLY_FT3); + *(u32 *)&ft->u0 = tri->uv0; + *(u32 *)&ft->u1 = *(u32 *)&tri->uv1; + *(u16 *)&ft->u2 = tri->uv2; + *(u32 *)&ft->r0 = s->unk0B0; + if (tri->tpage & EFFECT_MESH_TPAGE_ABE) { + ft->code |= 2; + } + addPrim(&ot[s->otz], ft); + ft++; + gte_SetRotMatrix(&s->view); + gte_SetTransMatrix(&s->view); + } + tri++; + } + quad = (EffectMeshQuad *)tri; + ft4 = (POLY_FT4 *)ft; + gt4 = (POLY_GT4 *)gt; + for (g = 0; g < quads; g++) { + s->idx0 = quad->idx0 & EFFECT_MESH_INDEX_MASK; + s->idx1 = quad->idx1 & EFFECT_MESH_INDEX_MASK; + s->idx2 = quad->idx2 & EFFECT_MESH_INDEX_MASK; + gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, &vb[s->idx2].pos); + gte_rtpt(); + gte_nclip(); + gte_stopz(&s->nclip); + if (s->nclip > 0) { + gte_stsxy3_gt3(gt4); + gte_stsxy3_ft3(ft4); + s->idx3 = quad->idx3 & EFFECT_MESH_INDEX_MASK; + gte_ldv0(&vb[s->idx3].pos); + gte_rtps(); + gte_stsxy(>4->x3); + gte_stsxy(&ft4->x3); + gte_avsz4(); + gte_stotz(&s->otz); + s->otz = (u32)s->otz >> 2; + gte_SetRotMatrix(&s->light); + gte_SetTransMatrix(&s->light); + gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, + &vb[s->idx2].pos); + gte_rtpt(); + s->reject = 0; + gte_nclip(); + gte_stopz(&s->nclip); + if (s->unk0CE == 1 && s->nclip <= 0) { + s->reject = 1; + } + if (!s->reject) { + gte_stsxy3c(s->sxy); + gte_ldv0(&vb[s->idx3].pos); + gte_rtps(); + gte_stsxy(&s->sxy[3]); + for (v = 0; v < 4; v++) { + if (s->sxy[v].vx < s->clipX0) { + s->reject = 1; + break; + } + if (s->clipX1 < s->sxy[v].vx) { + s->reject = 1; + break; + } + if (s->sxy[v].vy < s->clipY0) { + s->reject = 1; + break; + } + if (s->clipY1 < s->sxy[v].vy) { + s->reject = 1; + break; + } + s->sxy[v].vx += s->offX; + s->sxy[v].vy += s->offY; + } + } + if (!s->reject) { + gt4->tag = EFFECT_PRIM_TAG(POLY_GT4); + gt4->u0 = s->sxy[0].vx; + gt4->v0 = s->sxy[0].vy; + gt4->u1 = s->sxy[1].vx; + gt4->v1 = s->sxy[1].vy; + gt4->u2 = s->sxy[2].vx; + gt4->v2 = s->sxy[2].vy; + gt4->u3 = s->sxy[3].vx; + gt4->v3 = s->sxy[3].vy; + gt4->tpage = s->tpage; + gt4->clut = s->clut; + colour = s->unk0AC; + *(u32 *)>4->r3 = colour; + *(u32 *)>4->r2 = colour; + *(u32 *)>4->r1 = colour; + *(u32 *)>4->r0 = colour; + addPrim(&ot[s->otz], gt4); + gt4++; + } + ft4->tag = EFFECT_PRIM_TAG(POLY_FT4); + *(u32 *)&ft4->u0 = quad->uv0; + *(u32 *)&ft4->u1 = *(u32 *)&quad->uv1; + *(u16 *)&ft4->u2 = quad->uv2; + *(u16 *)&ft4->u3 = quad->uv3; + *(u32 *)&ft4->r0 = s->unk0B4; + if (quad->tpage & EFFECT_MESH_TPAGE_ABE) { + ft4->code |= 2; + } + addPrim(&ot[s->otz], ft4); + ft4++; + gte_SetRotMatrix(&s->view); + gte_SetTransMatrix(&s->view); + } + quad++; + } + ft3 = (POLY_FT3 *)ft4; + gt3 = (POLY_GT3 *)gt4; + } + render->unk00C[0] = (s32)ft3; + render->unk010[0] = gt3; + func_800B36B8(sizeof(EffectMeshScratch)); +} + +/** @brief Pose the effect's model and link its skeletons into the battle OT. */ +void effectRenderModel(EffectRender *render) { + BattleEffectSlot *slot = render->slot; + + effectRenderSetPose(render, slot); + if (render->unk0DA != 0xFF) { + func_800BC420(slot->unk060); + } else if (func_800BCA3C(slot->unk060, slot->unk06C) != 0) { + func_800BCF6C(slot->unk060, slot->unk06C, render->unk0D8); + } + CompMatrix(&D_800F02C8, &render->unk014, &render->unk034); + if (!(slot->flags & BATTLE_SLOT_FLAG_UNK20)) { + *render->unk00C = func_800BC060(slot, D_800FA5E8->unk4040, 0x10, *render->unk00C); + } + effectRenderFaceCamera(render); + effectRenderMesh(slot->mesh, D_800FA5E8->ot, 4, render); + if (slot->unk078 != NULL) { + effectRenderMesh(slot->unk078->mesh, D_800FA5E8->ot, 4, render); + } + if (render->unk0DA != 0xFF) { + func_800BC420(slot->unk060); + } +} diff --git a/src/effect/lib/rot.c b/src/effect/lib/rot.c new file mode 100644 index 00000000..2efe4010 --- /dev/null +++ b/src/effect/lib/rot.c @@ -0,0 +1,82 @@ +/** + * @file rot.c + * @brief Rotation matrices built from an angle. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/rot.h" + +static void effectMatrixSetRotX(s16 angle, MATRIX *m); +static void effectMatrixSetRotY(s16 angle, MATRIX *m); +static void effectMatrixSetRotZ(s16 angle, MATRIX *m); + +/** @brief Build the rotation of @p angle about X in @p m. */ +static void effectMatrixSetRotX(s16 angle, MATRIX *m) { + s32 *p = (s32 *)m; + s32 i; + s16 sin; + s16 cos; + + for (i = 5; i != 0; i--) { + *p++ = 0; + } + sin = rsin(angle); + cos = rcos(angle); + m->m[0][0] = ONE; + m->m[1][1] = cos; + m->m[2][1] = -sin; + m->m[1][2] = sin; + m->m[2][2] = cos; +} + +/** @brief Build the rotation of @p angle about Y in @p m. */ +static void effectMatrixSetRotY(s16 angle, MATRIX *m) { + s32 *p = (s32 *)m; + s32 i; + s16 sin; + s16 cos; + + for (i = 5; i != 0; i--) { + *p++ = 0; + } + sin = rsin(angle); + cos = rcos(angle); + m->m[2][0] = sin; + m->m[0][0] = cos; + m->m[1][1] = ONE; + m->m[0][2] = -sin; + m->m[2][2] = cos; +} + +/** @brief Build the rotation of @p angle about Z in @p m. */ +static void effectMatrixSetRotZ(s16 angle, MATRIX *m) { + s32 *p = (s32 *)m; + s32 i; + s16 sin; + s16 cos; + + for (i = 5; i != 0; i--) { + *p++ = 0; + } + sin = rsin(angle); + cos = rcos(angle); + m->m[1][0] = -sin; + m->m[0][0] = cos; + m->m[1][1] = cos; + m->m[0][1] = sin; + m->m[2][2] = ONE; +} + +/** @brief Build the Z, X then Y rotation of @p angles in @p out. */ +void effectMatrixSetRotZXY(SVECTOR *angles, MATRIX *out) { + MATRIX rz; + MATRIX rx; + MATRIX ry; + MATRIX zx; + + effectMatrixSetRotZ(angles->vz, &rz); + effectMatrixSetRotX(angles->vx, &rx); + MulMatrix0(&rz, &rx, &zx); + effectMatrixSetRotY(angles->vy, &ry); + MulMatrix0(&zx, &ry, out); +} diff --git a/src/effect/lib/rotz.c b/src/effect/lib/rotz.c new file mode 100644 index 00000000..0ccc97a4 --- /dev/null +++ b/src/effect/lib/rotz.c @@ -0,0 +1,27 @@ +/** + * @file rotz.c + * @brief Rotation about the Z axis. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/rotz.h" + +/** @brief Turn @p m about Z by @p angle. */ +void effectMatrixRotZ(MATRIX *m, s32 angle) { + EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); + + rot->sin = rsin(angle); + rot->cos = rsin((angle + 0x400) & 0xFFF); + rot->m.m[0][0] = rot->cos; + rot->m.m[0][1] = -rot->sin; + rot->m.m[0][2] = 0; + rot->m.m[1][0] = rot->sin; + rot->m.m[1][1] = rot->cos; + rot->m.m[1][2] = 0; + rot->m.m[2][0] = 0; + rot->m.m[2][1] = 0; + rot->m.m[2][2] = ONE; + gte_MulMatrix0(m, &rot->m, m); + func_800B36B8(sizeof(EffectRotScratch)); +} diff --git a/src/effect/lib/scatter.c b/src/effect/lib/scatter.c new file mode 100644 index 00000000..f9ee6e3f --- /dev/null +++ b/src/effect/lib/scatter.c @@ -0,0 +1,33 @@ +/** + * @file scatter.c + * @brief Push an entity to a random point. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libc.h" +#include "effect/lib/common.h" +#include "effect/lib/scatter.h" + +/** @brief Push @p entity to a random point within @p radius of where it is. */ +void effectScatter(EffectEntity *entity, s16 radius) { + MATRIX m; + SVECTOR angles; + SVECTOR in; + SVECTOR out; + + if (radius == 0) { + radius = 1; + } + angles.vy = rand() & 0xFFF; + angles.vx = rand() & 0xFFF; + effectMatrixIdentity(&m); + effectMatrixRotY(&m, angles.vy); + effectMatrixRotX(&m, angles.vx); + in.vx = 0; + in.vy = 0; + in.vz = rand() % radius; + ApplyMatrixSV(&m, &in, &out); + entity->pos.vx += out.vx; + entity->pos.vy += out.vy; + entity->pos.vz += out.vz; +} diff --git a/src/effect/lib/spawn.c b/src/effect/lib/spawn.c new file mode 100644 index 00000000..53a14bae --- /dev/null +++ b/src/effect/lib/spawn.c @@ -0,0 +1,505 @@ +/** + * @file spawn.c + * @brief Spawn particles off emitters and place emitters on the points their sources name. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libc.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/common.h" +#include "effect/lib/rotz.h" +#include "effect/lib/pose.h" +#include "effect/lib/vector.h" +#include "effect/lib/particlealloc.h" +#include "effect/lib/draw.h" +#include "effect/lib/list.h" +#include "effect/lib/particle.h" +#include "effect/lib/spawn.h" + +/** @brief The scratch a spawned particle's aim and speed are worked out in. */ +typedef struct { + /* 0x00 */ MATRIX m; + /* 0x20 */ SVECTOR dir; /**< Straight down, then turned by @c m. */ + /* 0x28 */ SVECTOR angle; /**< The turn @c m was built from. */ + /* 0x30 */ SVECTOR spin; /**< The turn the particle's own pose takes. */ + /* 0x38 */ VECTOR vel; + /* 0x48 */ s16 speedX; + /* 0x4A */ s16 speedY; + /* 0x4C */ s16 speedZ; + /* 0x4E */ s16 frame; /**< The mote's age, which indexes the tables. */ +} EffectSpawnScratch; /* 0x50 */ + +static void effectEmitterAge(EffectEmitter *node); +static void effectEmitterSpawn(EffectEmitter *mote, EffectPoseStep *step, s32 copies); +static void effectEmitterSpawnShare(EffectEmitter *node); + +/** @brief Step @p emitter's life and retire it when it runs out. */ +static void effectEmitterAge(EffectEmitter *emitter) { + emitter->life--; + if (emitter->life <= 0) { + effectListRemove((EffectParticle *)emitter); + emitter->inUse = 0; + g_effectDrawList->count--; + } +} + +/** @brief Spawn a particle off @p emitter and give each of its @p copies its own aim. */ +static void effectEmitterSpawn(EffectEmitter *emitter, EffectPoseStep *step, s32 copies) { + EffectSpawnScratch *s; + EffectParticle *particle; + s32 i; + + g_effectStackTop -= sizeof(EffectSpawnScratch); + s = (EffectSpawnScratch *)g_effectStackTop; + particle = effectParticleAlloc(emitter, emitter->unk06A); + particle->copies = copies; + s->frame = emitter->unk060; + if (step->mode < 3) { + particle->stream = g_effectDrawList->unk228[emitter->unk06A]; + particle->unk1D1 = effectEmitterCountStep(particle->stream); + if (step->mode == 2) { + particle->unk1D3 = step->unk017; + particle->unk1D4 = step->unk018; + particle->unk1D5 = effectRandBetween(step->unk019, step->unk01A); + } + } + particle->unk1DA = step->unk064; + for (i = 0; i < copies; i++) { + particle->pos[i] = emitter->pos; + if (step->unk022 == 0) { + particle->offset[i] = emitter->pos; + } + if (step->unk034 == 1) { + s->angle.vx = rand() & 0xFFF; + s->angle.vy = rand() & 0xFFF; + s->angle.vz = rand() & 0xFFF; + effectMatrixIdentity(&s->m); + if (s->angle.vz != 0) { + effectMatrixRotZ(&s->m, s->angle.vz); + } + if (s->angle.vx != 0) { + effectMatrixRotX(&s->m, s->angle.vx); + } + if (s->angle.vy != 0) { + effectMatrixRotY(&s->m, s->angle.vy); + } + if (step->unk033 == 0) { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + s->speedZ = step->unk0D4[s->frame]; + if (s->speedX != 0) { + s->speedX = rand() % s->speedX; + } + if (s->speedY != 0) { + s->speedY = rand() % s->speedY; + } + if (s->speedZ != 0) { + s->speedZ = rand() % s->speedZ; + } + } else { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + s->speedZ = step->unk0D4[s->frame]; + } + s->dir.vx = 0; + s->dir.vy = -0x1000; + s->dir.vz = 0; + gte_SetRotMatrix(&s->m); + gte_ldv0(&s->dir); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&s->dir); + s->vel.vx = s->dir.vx * s->speedX << 4; + s->vel.vy = s->dir.vy * s->speedY << 4; + s->vel.vz = s->dir.vz * s->speedZ << 4; + particle->offset[i].vx += s->vel.vx; + particle->offset[i].vy += s->vel.vy; + particle->offset[i].vz += s->vel.vz; + } else if (step->unk034 == 2) { + s->angle.vx = rand() & 0xFFF; + s->angle.vy = 0; + s->angle.vz = 0x400; + effectMatrixIdentity(&s->m); + if (s->angle.vz != 0) { + effectMatrixRotZ(&s->m, s->angle.vz); + } + if (s->angle.vx != 0) { + effectMatrixRotX(&s->m, s->angle.vx); + } + if (s->angle.vy != 0) { + effectMatrixRotY(&s->m, s->angle.vy); + } + if (step->unk033 == 0) { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + s->speedZ = step->unk0D4[s->frame]; + if (s->speedX != 0) { + s->speedX = rand() % s->speedX; + } + if (s->speedY != 0) { + s->speedY = rand() % s->speedY; + } + if (s->speedZ != 0) { + s->speedZ = rand() % s->speedZ; + } + } else if (step->unk033 != 1) { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + s->speedZ = step->unk0D4[s->frame]; + if (s->speedX != 0) { + s->speedX = rand() % s->speedX; + } + if (s->speedY != 0) { + s->speedY = rand() % s->speedY; + } + if (s->speedZ != 0) { + s->speedZ = rand() % s->speedZ; + } + } else { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + if (s->speedY != 0) { + s->speedY = rand() % s->speedY; + } + s->speedZ = step->unk0D4[s->frame]; + } + if (rand() & 1) { + s32 speed = s->speedY; + + s->speedY = -speed; + } + s->dir.vx = 0; + s->dir.vy = -0x1000; + s->dir.vz = 0; + gte_SetRotMatrix(&s->m); + gte_ldv0(&s->dir); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&s->dir); + s->vel.vx = s->dir.vx * s->speedX << 4; + s->vel.vy = s->speedY << 16; + s->vel.vz = s->dir.vz * s->speedZ << 4; + particle->offset[i].vx += s->vel.vx; + particle->offset[i].vy += s->vel.vy; + particle->offset[i].vz += s->vel.vz; + } + switch (step->unk021) { + case 0: + effectJitterSVector(&s->spin, &step->unk03C, &step->unk044); + break; + case 1: + s->spin = s->angle; + break; + } + effectMatrixIdentity(&particle->poses[i]); + if (s->spin.vz != 0) { + effectMatrixRotZ(&particle->poses[i], s->spin.vz); + } + if (s->spin.vx != 0) { + effectMatrixRotX(&particle->poses[i], s->spin.vx); + } + if (s->spin.vy != 0) { + effectMatrixRotY(&particle->poses[i], s->spin.vy); + } + gte_MulMatrix0(&emitter->mtx, &particle->poses[i], &particle->poses[i]); + if (step->unk027 == 0) { + particle->spin[i] = effectRandProduct(step->unk04C, step->unk050); + particle->spinRate[i] = effectRandProduct(step->unk054, step->unk058); + } + } + effectJitterSVector(&particle->angleBase, &step->unk148, &step->unk150); + if (step->kind == 1 || step->kind == 2) { + particle->unk1C8 = effectRandBetween(step->unk01F, step->unk020); + } + if (step->unk027 == 0) { + particle->unk1DC = effectRandProduct(step->unk05C, step->unk060); + for (i = 0; i < copies; i++) { + effectRandVector(&particle->vel[i], &step->unk068, &step->unk078); + effectRandVector(&particle->accel[i], &step->unk088, &step->unk098); + effectRandVector(&particle->jerk[i], &step->unk0A8, &step->unk0B8); + } + } + effectStepFireSlots(particle, step); + g_effectStackTop += sizeof(EffectSpawnScratch); +} + +/** @brief Emit this frame's share of @p emitter's work, in chunks its source sizes. */ +static void effectEmitterSpawnShare(EffectEmitter *emitter) { + EffectPoseStep *step = g_effectDrawList->sources[emitter->unk06A]; + s32 age = emitter->unk060; + s32 want; + s32 full; + s32 rest; + s32 i; + + if (age < 0x28) { + want = step->unk0C8[age]; + if (want != 0) { + full = want / step->unk036; + rest = want % step->unk036; + for (i = 0; i < full; i++) { + effectEmitterSpawn(emitter, step, step->unk036); + } + if (rest > 0) { + effectEmitterSpawn(emitter, step, rest); + } + } + } +} + +/** @brief Step one emitter: set it up once, run its kind, then age it. */ +void effectEmitterStep(EffectEmitter *emitter) { + if (emitter->unk062 == 0) { + /* Called with no argument: the mote is already in $a0 and stays there. */ + ((void (*)())effectEmitterSetup)(); + emitter->unk062++; + } + switch (emitter->kind) { + case 3: + effectEmitterOnOwner(emitter); + break; + case 4: + effectEmitterSlide(emitter); + break; + } + effectEmitterSpawnShare(emitter); + emitter->unk060++; + effectEmitterAge(emitter); +} + +/** @brief Put @p emitter on the strand point its source names, plus that source's offset. */ +void effectEmitterOnStrand(EffectEmitter *emitter) { + EffectPoseStep *step = g_effectDrawList->sources[emitter->unk06A]; + SVECTOR offset; + SVECTOR turned; + MATRIX m; + + switch (step->anchor) { + case 0: + emitter->pos = g_effectDrawList->strands[1][emitter->unk06B]; + break; + case 1: + emitter->pos = g_effectDrawList->strands[2][emitter->unk06B]; + break; + case 2: + emitter->pos = g_effectDrawList->strands[3][emitter->unk06B]; + break; + case 3: + emitter->pos = g_effectDrawList->strands[4][emitter->unk06B]; + break; + } + offset.vx = step->offsetX / 0x10000; + offset.vy = step->offsetY / 0x10000; + offset.vz = step->offsetZ / 0x10000; + m = emitter->mtx; + gte_SetRotMatrix(&m); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + emitter->pos.vx += turned.vx << 16; + emitter->pos.vy += turned.vy << 16; + emitter->pos.vz += turned.vz << 16; +} + +/** @brief Put @p emitter on the trail point its source names, plus that source's offset. */ +void effectEmitterOnTrail(EffectEmitter *emitter) { + EffectPoseStep *step = g_effectDrawList->sources[emitter->unk06A]; + SVECTOR offset; + SVECTOR turned; + + switch (step->anchor) { + case 0: + emitter->pos = g_effectDrawList->trail[2]; + break; + case 1: + emitter->pos = g_effectDrawList->trail[3]; + break; + case 2: + emitter->pos = g_effectDrawList->trail[4]; + break; + case 3: + emitter->pos = g_effectDrawList->trail[5]; + break; + } + offset.vx = step->offsetX / 0x10000; + offset.vy = step->offsetY / 0x10000; + offset.vz = step->offsetZ / 0x10000; + gte_SetRotMatrix(&emitter->mtx); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + emitter->pos.vx += turned.vx << 16; + emitter->pos.vy += turned.vy << 16; + emitter->pos.vz += turned.vz << 16; +} + +/** @brief Put @p emitter on the node that owns it, plus that source's turned offset. */ +void effectEmitterOnOwner(EffectEmitter *emitter) { + EffectPoseStep *step = g_effectDrawList->sources[emitter->unk06A]; + EffectParticle *owner = emitter->owner; + + if (owner != NULL) { + SVECTOR offset; + SVECTOR turned; + + if (step->midpoint == 1) { + emitter->pos.vx = (owner->pos[0].vx + owner->lastPos.vx) / 2; + emitter->pos.vy = (owner->pos[0].vy + owner->lastPos.vy) / 2; + emitter->pos.vz = (owner->pos[0].vz + owner->lastPos.vz) / 2; + } else { + emitter->pos = owner->pos[0]; + } + if (step->unk01C == 3) { + emitter->mtx = owner->orient; + offset.vx = step->offsetX / 0x10000; + offset.vy = step->offsetY / 0x10000; + offset.vz = step->offsetZ / 0x10000; + gte_SetRotMatrix(&emitter->mtx); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + emitter->pos.vx += turned.vx << 16; + emitter->pos.vy += turned.vy << 16; + emitter->pos.vz += turned.vz << 16; + } + } +} + +/** @brief Slide @p emitter between the two points its source names. */ +void effectEmitterSlide(EffectEmitter *emitter) { + EffectPoseStep *step = g_effectDrawList->sources[emitter->unk06A]; + u8 kind; + s32 index; + SVECTOR from; + SVECTOR to; + SVECTOR offset; + SVECTOR turned; + s32 t; + + kind = step->unk100[emitter->unk060]; + index = step->unk108[emitter->unk060]; + switch (kind) { + case 0: + switch (index) { + case 0: + from.vx = g_effectDrawList->strands[1][emitter->unk06B].vx / 0x10000; + from.vy = g_effectDrawList->strands[1][emitter->unk06B].vy / 0x10000; + from.vz = g_effectDrawList->strands[1][emitter->unk06B].vz / 0x10000; + break; + case 1: + from.vx = g_effectDrawList->strands[2][emitter->unk06B].vx / 0x10000; + from.vy = g_effectDrawList->strands[2][emitter->unk06B].vy / 0x10000; + from.vz = g_effectDrawList->strands[2][emitter->unk06B].vz / 0x10000; + break; + case 2: + from.vx = g_effectDrawList->strands[3][emitter->unk06B].vx / 0x10000; + from.vy = g_effectDrawList->strands[3][emitter->unk06B].vy / 0x10000; + from.vz = g_effectDrawList->strands[3][emitter->unk06B].vz / 0x10000; + break; + case 3: + from.vx = g_effectDrawList->strands[4][emitter->unk06B].vx / 0x10000; + from.vy = g_effectDrawList->strands[4][emitter->unk06B].vy / 0x10000; + from.vz = g_effectDrawList->strands[4][emitter->unk06B].vz / 0x10000; + break; + } + break; + case 1: + switch (index) { + case 0: + from.vx = g_effectDrawList->trail[2].vx / 0x10000; + from.vy = g_effectDrawList->trail[2].vy / 0x10000; + from.vz = g_effectDrawList->trail[2].vz / 0x10000; + break; + case 1: + from.vx = g_effectDrawList->trail[3].vx / 0x10000; + from.vy = g_effectDrawList->trail[3].vy / 0x10000; + from.vz = g_effectDrawList->trail[3].vz / 0x10000; + break; + case 2: + from.vx = g_effectDrawList->trail[4].vx / 0x10000; + from.vy = g_effectDrawList->trail[4].vy / 0x10000; + from.vz = g_effectDrawList->trail[4].vz / 0x10000; + break; + case 3: + from.vx = g_effectDrawList->trail[5].vx / 0x10000; + from.vy = g_effectDrawList->trail[5].vy / 0x10000; + from.vz = g_effectDrawList->trail[5].vz / 0x10000; + break; + } + break; + } + offset.vx = step->unk110[emitter->unk060]; + offset.vy = step->unk114[emitter->unk060]; + offset.vz = step->unk118[emitter->unk060]; + gte_SetRotMatrix(&emitter->mtx); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + from.vx += turned.vx; + from.vy += turned.vy; + from.vz += turned.vz; + kind = step->unk104[emitter->unk060]; + index = step->unk10C[emitter->unk060]; + switch (kind) { + case 0: + switch (index) { + case 0: + to.vx = g_effectDrawList->strands[1][emitter->unk06B].vx / 0x10000; + to.vy = g_effectDrawList->strands[1][emitter->unk06B].vy / 0x10000; + to.vz = g_effectDrawList->strands[1][emitter->unk06B].vz / 0x10000; + break; + case 1: + to.vx = g_effectDrawList->strands[2][emitter->unk06B].vx / 0x10000; + to.vy = g_effectDrawList->strands[2][emitter->unk06B].vy / 0x10000; + to.vz = g_effectDrawList->strands[2][emitter->unk06B].vz / 0x10000; + break; + case 2: + to.vx = g_effectDrawList->strands[3][emitter->unk06B].vx / 0x10000; + to.vy = g_effectDrawList->strands[3][emitter->unk06B].vy / 0x10000; + to.vz = g_effectDrawList->strands[3][emitter->unk06B].vz / 0x10000; + break; + case 3: + to.vx = g_effectDrawList->strands[4][emitter->unk06B].vx / 0x10000; + to.vy = g_effectDrawList->strands[4][emitter->unk06B].vy / 0x10000; + to.vz = g_effectDrawList->strands[4][emitter->unk06B].vz / 0x10000; + break; + } + break; + case 1: + switch (index) { + case 0: + to.vx = g_effectDrawList->trail[2].vx / 0x10000; + to.vy = g_effectDrawList->trail[2].vy / 0x10000; + to.vz = g_effectDrawList->trail[2].vz / 0x10000; + break; + case 1: + to.vx = g_effectDrawList->trail[3].vx / 0x10000; + to.vy = g_effectDrawList->trail[3].vy / 0x10000; + to.vz = g_effectDrawList->trail[3].vz / 0x10000; + break; + case 2: + to.vx = g_effectDrawList->trail[4].vx / 0x10000; + to.vy = g_effectDrawList->trail[4].vy / 0x10000; + to.vz = g_effectDrawList->trail[4].vz / 0x10000; + break; + case 3: + to.vx = g_effectDrawList->trail[5].vx / 0x10000; + to.vy = g_effectDrawList->trail[5].vy / 0x10000; + to.vz = g_effectDrawList->trail[5].vz / 0x10000; + break; + } + break; + } + offset.vx = step->unk11C[emitter->unk060]; + offset.vy = step->unk120[emitter->unk060]; + offset.vz = step->unk124[emitter->unk060]; + gte_SetRotMatrix(&emitter->mtx); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + to.vx += turned.vx; + to.vy += turned.vy; + to.vz += turned.vz; + t = step->unk128[emitter->unk060]; + emitter->pos.vx = (from.vx << 16) + ((to.vx - from.vx) * t << 4); + emitter->pos.vy = (from.vy << 16) + ((to.vy - from.vy) * t << 4); + emitter->pos.vz = (from.vz << 16) + ((to.vz - from.vz) * t << 4); +} diff --git a/src/effect/lib/sprite.c b/src/effect/lib/sprite.c new file mode 100644 index 00000000..1cab1d85 --- /dev/null +++ b/src/effect/lib/sprite.c @@ -0,0 +1,26 @@ +/** + * @file sprite.c + * @brief One sprite through the battle sprite packet. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/common.h" +#include "effect/lib/sprite.h" + +/** @brief Build this effect's primitive and link it into the battle display list. */ +void effectDrawSprite(EffectEntity *entity) { + s16 count; + BattleSpritePrim *prim; + + if (entity->flags & EFFECT_FLAG_DONE) { + return; + } + prim = func_800B3698(sizeof(BattleSpritePrim)); + func_800C96E4(&entity->pos, ONE, entity->unk054); + prim->anim = entity->unk04C; + count = entity->unk050; + prim->flags = 0; + prim->frame = count; + g_effectPrimCursor = func_800C9E10(prim, D_800FA5E8->ot, 2, g_effectPrimCursor); + func_800B36B8(sizeof(BattleSpritePrim)); +} diff --git a/src/effect/lib/step.c b/src/effect/lib/step.c new file mode 100644 index 00000000..1087220d --- /dev/null +++ b/src/effect/lib/step.c @@ -0,0 +1,18 @@ +/** + * @file step.c + * @brief Script counter stepping. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/step.h" + +/** @brief Advance the script's counter, clamping at its limit. */ +s32 effectStepCounter(EffectEntity *entity) { + entity->unk050++; + if (entity->unk052 < entity->unk050) { + entity->unk050 = entity->unk052; + entity->flags |= EFFECT_FLAG_DONE; + return 1; + } + return 0; +} diff --git a/src/effect/lib/strands.c b/src/effect/lib/strands.c new file mode 100644 index 00000000..e4d2659c --- /dev/null +++ b/src/effect/lib/strands.c @@ -0,0 +1,120 @@ +/** + * @file strands.c + * @brief Seed, push and aim the five strands. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/pose.h" +#include "effect/lib/drawscript.h" +#include "effect/lib/particle.h" +#include "effect/lib/strands.h" + +/** @brief Seed every point of all five strands from the list's seed point. */ +void effectStrandsSeed(void) { + s32 i; + + for (i = 0; i < g_effectDrawList->strandLen; i++) { + g_effectDrawList->strands[0][i] = g_effectDrawList->strandSeed; + g_effectDrawList->strands[1][i] = g_effectDrawList->strandSeed; + g_effectDrawList->strands[2][i] = g_effectDrawList->strandSeed; + g_effectDrawList->strands[3][i] = g_effectDrawList->strandSeed; + g_effectDrawList->strands[4][i] = g_effectDrawList->strandSeed; + } +} + +/** @brief Push @p node's position onto the head of every strand. */ +void effectStrandsPush(EffectDrawScript *script) { + s32 i; + + for (i = 0; i < g_effectDrawList->strandLen; i++) { + EffectDrawList *list = g_effectDrawList; + + list->strandHead.vx = script->unk288 << 16; + list->strandHead.vy = script->unk28A << 16; + list->strandHead.vz = script->unk28C << 16; + list->strands[4][i] = list->strandHead; + list->strands[3][i] = list->strands[4][i]; + list->strands[2][i] = list->strands[3][i]; + list->strands[1][i] = list->strands[2][i]; + list->strands[0][i] = list->strands[1][i]; + } +} + +/** + * @brief Aim every strand at its own model part, then seed them from the middle. + * + * @note @p script is not read; @ref effectDrawScriptStart passes the entity it has. + */ +void effectStrandsAim(EffectDrawScript *script) { + SVECTOR pt; + s32 i; + s16 n; + s32 minX; + s32 maxX; + s32 minZ; + s32 maxZ; + + for (i = 0; i < g_effectDrawList->strandLen; i++) { + BattleEffectSlot *slot = &D_800EF2D0[g_effectDrawList->parts[i]]; + + if (g_effectDrawList->unk024 != 2) { + func_800B3960(slot, 0xF1, 0, &pt); + } + pt.vy = slot->unk024; + g_effectDrawList->strands[0][i].vx = pt.vx << 16; + g_effectDrawList->strands[0][i].vy = pt.vy << 16; + g_effectDrawList->strands[0][i].vz = pt.vz << 16; + g_effectDrawList->strands[1][i].vx = g_effectDrawList->strands[0][i].vx; + g_effectDrawList->strands[1][i].vy = 0; + g_effectDrawList->strands[1][i].vz = g_effectDrawList->strands[0][i].vz; + if (g_effectDrawList->unk024 != 2) { + func_800B3960(slot, 0xF1, 0, &pt); + } + g_effectDrawList->strands[2][i].vx = pt.vx << 16; + g_effectDrawList->strands[2][i].vy = pt.vy << 16; + g_effectDrawList->strands[2][i].vz = pt.vz << 16; + if (g_effectDrawList->unk024 != 2) { + func_800B3960(slot, 0xF0, 0, &pt); + } + g_effectDrawList->strands[3][i].vx = pt.vx << 16; + g_effectDrawList->strands[3][i].vy = pt.vy << 16; + g_effectDrawList->strands[3][i].vz = pt.vz << 16; + g_effectDrawList->strands[4][i].vx = g_effectDrawList->strands[0][i].vx; + g_effectDrawList->strands[4][i].vy = slot->unk03C << 16; + g_effectDrawList->strands[4][i].vz = g_effectDrawList->strands[0][i].vz; + } + maxZ = 0; + minZ = 0; + maxX = 0; + minX = 0; + n = 0; + i = 0; + + for (; i < g_effectDrawList->strandLen; i++) { + s32 v; + + if (n == 0) { + maxX = g_effectDrawList->strands[0][i].vx; + maxZ = g_effectDrawList->strands[0][i].vz; + minX = maxX; + minZ = maxZ; + } else { + v = g_effectDrawList->strands[0][i].vx; + if (v < minX) { + minX = v; + } else if (maxX < v) { + maxX = v; + } + v = g_effectDrawList->strands[0][i].vz; + if (v < minZ) { + minZ = v; + } else if (maxZ < v) { + maxZ = v; + } + } + n++; + } + g_effectDrawList->strandSeed.vx = (minX + maxX) / 2; + g_effectDrawList->strandSeed.vy = 0; + g_effectDrawList->strandSeed.vz = (minZ + maxZ) / 2; +} diff --git a/src/effect/lib/tables.c b/src/effect/lib/tables.c new file mode 100644 index 00000000..474db456 --- /dev/null +++ b/src/effect/lib/tables.c @@ -0,0 +1,99 @@ +/** + * @file tables.c + * @brief Relocate a pose-script table set and start a child script on it. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/entity.h" +#include "effect/lib/pose.h" +#include "effect/lib/particle.h" +#include "effect/lib/tables.h" + +/** + * @brief Turn every offset in @p tables into an address. + * + * The tables are built with everything relative to their own start, so the + * first script to use them walks each of the four arrays -- and then every + * table inside each source -- adding that start. Each array is walked as + * words because the entries are offsets until this pass has run. + */ +void effectPoseTablesRelocate(EffectPoseTables *tables) { + s32 base = (s32)tables; + s32 *entry; + s32 i; + + entry = (s32 *)g_effectDrawList->sources; + for (i = 0; i < 16; i++) { + if (entry[i] != 0) { + entry[i] += base; + } + } + entry = (s32 *)g_effectDrawList->unk228; + for (i = 0; i < 16; i++) { + if (entry[i] != 0) { + entry[i] += base; + } + } + entry = (s32 *)g_effectDrawList->targets; + for (i = 0; i < 16; i++) { + if (entry[i] != 0) { + entry[i] += base; + } + } + entry = (s32 *)g_effectDrawList->frames; + for (i = 0; i < 16; i++) { + if (entry[i] != 0) { + entry[i] += base; + } + } + for (i = 0; i < 16; i++) { + EffectPoseStep *step = g_effectDrawList->sources[i]; + + if (step != NULL) { + step->unk0C8 += base; + step->unk0CC = (u16 *)((u8 *)step->unk0CC + base); + step->unk0D0 = (u16 *)((u8 *)step->unk0D0 + base); + step->unk0D4 = (u16 *)((u8 *)step->unk0D4 + base); + step->unk0D8 = (u16 *)((u8 *)step->unk0D8 + base); + step->unk0DC = (u16 *)((u8 *)step->unk0DC + base); + step->unk0E0 = (u16 *)((u8 *)step->unk0E0 + base); + step->unk0E4 = (u16 *)((u8 *)step->unk0E4 + base); + step->unk0E8 = (u16 *)((u8 *)step->unk0E8 + base); + step->unk0EC = (u16 *)((u8 *)step->unk0EC + base); + step->unk0F0 += base; + step->unk0F4 += base; + step->unk0F8 += base; + step->unk0FC = (u16 *)((u8 *)step->unk0FC + base); + step->unk100 += base; + step->unk104 += base; + step->unk108 += base; + step->unk10C += base; + step->unk110 = (u16 *)((u8 *)step->unk110 + base); + step->unk114 = (u16 *)((u8 *)step->unk114 + base); + step->unk118 = (u16 *)((u8 *)step->unk118 + base); + step->unk11C = (u16 *)((u8 *)step->unk11C + base); + step->unk120 = (u16 *)((u8 *)step->unk120 + base); + step->unk124 = (u16 *)((u8 *)step->unk124 + base); + step->unk128 = (s16 *)((u8 *)step->unk128 + base); + step->targets += base; + step->framesA += base; + step->framesB += base; + step->weights = (s16 *)((u8 *)step->weights + base); + step->unk13C = (s16 *)((u8 *)step->unk13C + base); + step->unk140 = (s16 *)((u8 *)step->unk140 + base); + step->unk144 = (s16 *)((u8 *)step->unk144 + base); + } + } +} + +/** @brief Start @p task as a child of @p owner and seed its script fields. */ +void effectStartChildScript(EffectEntity *owner, void *task, void *arg, s16 stopFrame, + s16 a, s16 b) { + EffectDrawScript *child = + effectSpawnTask(&g_effectChildPool, task, sizeof(EffectDrawScript), owner); + + child->tables = arg; + child->stopFrame = stopFrame; + child->unk29C = b; + child->unk29E = a; +} diff --git a/src/effect/lib/tint.c b/src/effect/lib/tint.c new file mode 100644 index 00000000..c65e16a1 --- /dev/null +++ b/src/effect/lib/tint.c @@ -0,0 +1,115 @@ +/** + * @file tint.c + * @brief Play one step of a screen tint as four quads over the frame. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libgpu.h" +#include "effect/lib/common.h" +#include "effect/lib/entity.h" +#include "effect/lib/tint.h" + +/** @brief Pack a screen corner the way the tint quads carry them. */ +#define EFFECT_TINT_XY(x, y) ((x) | ((y) << 16)) + +/** @brief Prim code of a tint quad: a semi-transparent flat quad. */ +#define EFFECT_TINT_CODE 0x2A + +/** + * @name Script records + * + * @ref effectSpawnTask hands back as many bytes as the script asks for, with an + * @ref EffectEntity at the front of them, so a script owns everything the + * engine leaves alone. These two lay their own state over the pose fields. + * @{ + */ + +/** @brief The full-screen quad the tint script draws, four to a frame. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; /**< The step's colour; its top byte is the prim code. */ + /* 0x08 */ u32 xy0; /**< X in the low half, Y in the high. */ + /* 0x0C */ u32 xy1; + /* 0x10 */ u32 xy2; + /* 0x14 */ u32 xy3; +} EffectTintQuad; /* 0x18 */ + +/** @} */ + +/** + * @brief Play one step of the screen tint and lay four quads over the frame. + * + * @return 2 once the script has stopped and its children have drained. + */ +s32 effectTintPlay(EffectTintScript *script) { + EffectTintStep step = script->steps[script->step]; + s32 draw = 1; + + if (step.op == 0xFF) { + script->flags |= EFFECT_FLAG_STOP; + draw = 0; + } else if (step.op == 0xFE) { + if (script->frame >= step.b) { + script->step++; + script->tint = script->steps[script->step]; + } + } else { + script->tint = script->steps[script->step]; + script->step++; + } + if (draw == 1) { + /* The step's four bytes are the quad's colour word, code byte and all. */ + u32 *ot = &D_800FA5E8->frontOT[2]; + EffectTintQuad *quad = g_effectPrimCursor; + DR_MODE *mode; + + quad->rgb = *(u32 *)&script->tint; + setlen(quad, 5); + setcode(quad, EFFECT_TINT_CODE); + quad->xy0 = EFFECT_TINT_XY(0, 0); + quad->xy1 = EFFECT_TINT_XY(0xA0, 0); + quad->xy2 = EFFECT_TINT_XY(0, 0x78); + quad->xy3 = EFFECT_TINT_XY(0xA0, 0x78); + AddPrim(ot, quad); + quad++; + quad->rgb = *(u32 *)&script->tint; + setlen(quad, 5); + setcode(quad, EFFECT_TINT_CODE); + quad->xy0 = EFFECT_TINT_XY(0xA0, 0); + quad->xy1 = EFFECT_TINT_XY(0x140, 0); + quad->xy2 = EFFECT_TINT_XY(0xA0, 0x78); + quad->xy3 = EFFECT_TINT_XY(0x140, 0x78); + AddPrim(ot, quad); + quad++; + quad->rgb = *(u32 *)&script->tint; + setlen(quad, 5); + setcode(quad, EFFECT_TINT_CODE); + quad->xy0 = EFFECT_TINT_XY(0, 0x78); + quad->xy1 = EFFECT_TINT_XY(0xA0, 0x78); + quad->xy2 = EFFECT_TINT_XY(0, 0xF0); + quad->xy3 = EFFECT_TINT_XY(0xA0, 0xF0); + AddPrim(ot, quad); + quad++; + quad->rgb = *(u32 *)&script->tint; + setlen(quad, 5); + setcode(quad, EFFECT_TINT_CODE); + quad->xy0 = EFFECT_TINT_XY(0xA0, 0x78); + quad->xy1 = EFFECT_TINT_XY(0x140, 0x78); + quad->xy2 = EFFECT_TINT_XY(0xA0, 0xF0); + quad->xy3 = EFFECT_TINT_XY(0x140, 0xF0); + AddPrim(ot, quad); + quad++; + mode = (DR_MODE *)quad; + SetDrawMode(mode, 0, 0, GetTPage(0, 1, 0x280, 0), NULL); + AddPrim(ot, mode); + g_effectPrimCursor = mode + 1; + } + script->frame++; + if (script->flags & EFFECT_FLAG_STOP) { + if (script->wait == 0) { + effectReleaseWait((EffectEntity *)script); + return 2; + } + } + return 0; +} diff --git a/src/effect/lib/trail.c b/src/effect/lib/trail.c new file mode 100644 index 00000000..d5b700f7 --- /dev/null +++ b/src/effect/lib/trail.c @@ -0,0 +1,41 @@ +/** + * @file trail.c + * @brief Aim the trail at the battle slot. + */ +#include "common.h" +#include "effect.h" +#include "effect/lib/pose.h" +#include "effect/lib/particle.h" +#include "effect/lib/trail.h" + +/** @brief Aim the trail at the battle slot: its two points and its height. */ +void effectTrailAim(void) { + BattleEffectSlot *slot = &D_800EF2D0[g_effectDrawList->slot]; + SVECTOR pt; + + if (g_effectDrawList->unk024 != 2) { + func_800B3960(slot, 0xF1, 0, &pt); + } + pt.vy = slot->unk024; + g_effectDrawList->trail[1].vx = pt.vx << 16; + g_effectDrawList->trail[1].vy = pt.vy << 16; + g_effectDrawList->trail[1].vz = pt.vz << 16; + g_effectDrawList->trail[2].vx = g_effectDrawList->trail[1].vx; + g_effectDrawList->trail[2].vy = 0; + g_effectDrawList->trail[2].vz = g_effectDrawList->trail[1].vz; + if (g_effectDrawList->unk024 != 2) { + func_800B3960(slot, 0xF1, 0, &pt); + } + g_effectDrawList->trail[3].vx = pt.vx << 16; + g_effectDrawList->trail[3].vy = pt.vy << 16; + g_effectDrawList->trail[3].vz = pt.vz << 16; + if (g_effectDrawList->unk024 != 2) { + func_800B3960(slot, 0xF0, 0, &pt); + } + g_effectDrawList->trail[4].vx = pt.vx << 16; + g_effectDrawList->trail[4].vy = pt.vy << 16; + g_effectDrawList->trail[4].vz = pt.vz << 16; + g_effectDrawList->trail[5].vx = g_effectDrawList->trail[1].vx; + g_effectDrawList->trail[5].vy = slot->unk03C << 16; + g_effectDrawList->trail[5].vz = g_effectDrawList->trail[1].vz; +} diff --git a/src/effect/lib/vector.c b/src/effect/lib/vector.c new file mode 100644 index 00000000..4a3e02d0 --- /dev/null +++ b/src/effect/lib/vector.c @@ -0,0 +1,220 @@ +/** + * @file vector.c + * @brief Random draws, angle jitter and vector helpers for the particle engine. + */ +#include "common.h" +#include "effect.h" +#include "psxsdk/libc.h" +#include "psxsdk/inline_c.h" +#include "effect/lib/common.h" +#include "effect/lib/rotz.h" +#include "effect/lib/pose.h" +#include "effect/lib/particle.h" +#include "effect/lib/vector.h" + +static s32 effectJitterAngle(s32 base, s32 spread); +static s32 effectJitterAngleS(s16 base, s16 spread); + +/** @brief A random value between @p lo and @p hi. */ +s32 effectRandBetween(s32 lo, s32 hi) { + s32 span; + s32 r; + + if (lo == hi) { + return lo; + } + span = hi - lo; + r = rand() % span; + if (span >= 0) { + r = r + lo; + } else { + r = lo - r; + } + return r; +} + +/** @brief A random value between @p lo and @p hi, from a product of two draws. */ +s32 effectRandProduct(s32 lo, s32 hi) { + s32 span; + s32 r; + + if (lo == hi) { + return lo; + } + span = hi - lo; + r = rand() * rand() % span; + if (span >= 0) { + r = r + lo; + } else { + r = lo - r; + } + return r; +} + +/** + * @brief Combine @p a and @p b component-wise through @ref effectRandProduct. + */ +void effectRandVector(VECTOR *out, VECTOR *a, VECTOR *b) { + out->vx = effectRandProduct(a->vx, b->vx); + out->vy = effectRandProduct(a->vy, b->vy); + out->vz = effectRandProduct(a->vz, b->vz); +} + +/** @brief Build @p m from @p angles, applying only the turns that are non-zero. */ +void effectMatrixFromAngles(VECTOR *angles, MATRIX *m) { + SVECTOR a; + + a.vx = angles->vx / 0x10000; + a.vy = angles->vy / 0x10000; + a.vz = angles->vz / 0x10000; + effectMatrixIdentity(m); + if (a.vz != 0) { + effectMatrixRotZ(m, a.vz); + } + if (a.vx != 0) { + effectMatrixRotX(m, a.vx); + } + if (a.vy != 0) { + effectMatrixRotY(m, a.vy); + } +} + +/** @brief Scatter @p base by up to half of @p spread, wrapped to one turn. */ +static s32 effectJitterAngle(s32 base, s32 spread) { + s32 half = spread / 2; + s32 v = effectRandProduct(-half, half) + base; + + if (v >= 0) { + while (v > 0xFFFFFFF) { + v -= 0x10000000; + } + } else { + while (v <= 0) { + v += 0x10000000; + } + } + return v; +} + +/** + * @brief Combine @p a and @p b component-wise through @ref effectJitterAngle. + */ +void effectJitterVector(VECTOR *out, VECTOR *a, VECTOR *b) { + out->vx = effectJitterAngle(a->vx, b->vx); + out->vy = effectJitterAngle(a->vy, b->vy); + out->vz = effectJitterAngle(a->vz, b->vz); +} + +/** @brief Scatter @p base by up to half of @p spread, wrapped to one turn. */ +static s32 effectJitterAngleS(s16 base, s16 spread) { + s16 half = spread / 2; + s32 v = effectRandBetween(-half, half) + base; + + if (v >= 0) { + while (v >= 0x1000) { + v -= 0x1000; + } + } else { + while (v <= 0) { + v += 0x1000; + } + } + return v; +} + +/** + * @brief Combine @p a and @p b component-wise through @ref effectJitterAngleS. + */ +void effectJitterSVector(SVECTOR *out, SVECTOR *a, SVECTOR *b) { + out->vx = effectJitterAngleS(a->vx, b->vx); + out->vy = effectJitterAngleS(a->vy, b->vy); + out->vz = effectJitterAngleS(a->vz, b->vz); +} + +/** @brief Copy the three words at @p src to @p dst. */ +void effectCopyVector(s32 *src, s32 *dst) { + /* The original reserved a slot here and never read it; dropping it moves + every local below and the frame no longer matches. */ + VECTOR unused; + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; +} + +/** + * @brief Fade @p value towards zero by @p t, a 16.16 fraction of it. + * + * @param t Clamped to the closed range 0 to 1.0. + * @param value Read and written in place. + */ +void effectFadeValue(s32 t, s32 *value) { + s32 v; + + if (t > 0x10000) { + t = 0x10000; + } + if (t < 0) { + t = 0; + } + v = *value; + *value = v - (v / 256 * t / 256); +} + +/** @brief Fade all three components of @p v towards zero by @p t. */ +void effectFadeVector(s32 t, VECTOR *v) { + /* The original reserved a slot here and never read it. */ + VECTOR unused; + s32 x; + s32 y; + s32 z; + + if (t > 0x10000) { + t = 0x10000; + } + if (t < 0) { + t = 0; + } + x = v->vx; + v->vx = x - (x / 256 * t / 256); + y = v->vy; + v->vy = y - (y / 256 * t / 256); + z = v->vz; + v->vz = z - (z / 256 * t / 256); +} + +/** @brief Blend this frame's two source point sets into the particle's target set. */ +void effectBlendPointSets(EffectParticle *particle, EffectPoseStep *step) { + u8 *dstId = &step->targets[particle->unk1CA]; + u8 *srcAId = &step->framesA[particle->unk1CA]; + u8 *srcBId = &step->framesB[particle->unk1CA]; + EffectPointSet *target = g_effectDrawList->targets[*dstId]; + EffectPointSet *a = g_effectDrawList->frames[*srcAId]; + EffectPointSet *b = g_effectDrawList->frames[*srcBId]; + + if (a != NULL && b != NULL) { + s32 *weight = func_800B3698(8); + SVECTOR *out = target->points; + SVECTOR *pa = a->points; + SVECTOR *pb = b->points; + s32 i = 0; + s32 t = step->weights[particle->unk1CA]; + s32 n = target->count; + + weight[1] = t; + weight[0] = ONE - t; + for (; i < n; i++) { + gte_lddp(weight[0]); + gte_ldsv(pa); + gte_gpf1(); + pa++; + gte_lddp(weight[1]); + gte_ldsv(pb); + gte_gpl1(); + pb++; + gte_stsv(out); + out++; + } + func_800B36B8(8); + } +} diff --git a/tools/objdiff/objdiff_generate.py b/tools/objdiff/objdiff_generate.py index d26d97df..4e7c4d89 100644 --- a/tools/objdiff/objdiff_generate.py +++ b/tools/objdiff/objdiff_generate.py @@ -8,6 +8,7 @@ import argparse import json import os +import re from pathlib import Path ROOT = Path(__file__).resolve().parent.parent.parent @@ -77,6 +78,22 @@ def effect_categories(): return [{"id": name, "name": f"{name}.bin"} for name in names] +def unit_name(ovl_name, rel_from_ovl): + """The object's path under the overlay's src_path, so a unit in a + subdirectory keeps it: effect/lib/entity.o is lib/entity, not entity.""" + src_path = f"src/{ovl_name}" + yaml_path = ROOT / "build" / "splat" / f"{ovl_name}.yaml" + if yaml_path.exists(): + m = re.search(r"^\s*src_path:\s*(\S+)", yaml_path.read_text(), re.M) + if m: + src_path = m.group(1) + stem = str(rel_from_ovl)[:-2] + prefix = src_path.rstrip("/") + "/" + if stem.startswith(prefix): + return stem[len(prefix):] + return Path(stem).name + + def find_units(): units = [] @@ -123,9 +140,8 @@ def find_units(): for o_file in sorted(src_root.rglob("*.o")): rel_from_ovl = o_file.relative_to(ovl_dir) expected_o = EXPECTED / ovl_dir.relative_to(ROOT) / rel_from_ovl - src_rel = str(rel_from_ovl).replace(".o", "") units.append({ - "name": f"ovl/{ovl_name}/{Path(src_rel).name}", + "name": f"ovl/{ovl_name}/{unit_name(ovl_name, rel_from_ovl)}", "target_path": target_path(expected_o, o_file), "base_path": str(o_file.relative_to(ROOT)), "metadata": {"progress_categories": [ovl_name]}, diff --git a/tools/progress.py b/tools/progress.py index c4da0870..94740112 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -25,6 +25,16 @@ def effect_overlays(): return [name for name in names if name != "all"] +def effect_sources(name): + """The C units an effect overlay links, from its ff8.yaml entry: its own + files and the library units it shares with the other effects.""" + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + with open(os.path.join(root, "config", "ff8.yaml")) as f: + entry = re.search(r"^- name: " + name + r"\n(.*?)(?=^- name: |\Z)", f.read(), re.M | re.S) + units = re.findall(r"^\s*- \[0x[0-9A-Fa-f]+, c, ([\w/]+)\]", entry.group(1), re.M) if entry else [name] + return [f"src/effect/{unit}.c" for unit in units] + + EFFECT_OVERLAYS = effect_overlays() CODE_OVERLAYS = [ "field_init", "intro", "field", @@ -42,7 +52,7 @@ def effect_overlays(): "battle": ["src/battle/*.c"], "tripletriad": ["src/tripletriad/*.c"], "world": ["src/world/*.c"], - **{name: [f"src/effect/{name}.c"] for name in EFFECT_OVERLAYS}, + **{name: effect_sources(name) for name in EFFECT_OVERLAYS}, **{name: [f"src/menu/{name}/*.c"] for name in MENU_OVERLAYS}, }