From f43b3de3d9c970d7532dd3eb906e64b4753ddb86 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 10 Dec 2024 06:49:15 -0500 Subject: [PATCH 01/34] Add name to dDemo_manager_c enabling logic based on specific demos --- src/ZeldaWindWaker/Main.ts | 2 +- src/ZeldaWindWaker/d_demo.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 9c2cb303e..a92bdb893 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -1039,7 +1039,7 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { if (!demoData) demoData = globals.modelCache.resCtrl.getStageResByName(ResType.Stb, "Stage", this.stbFilename); - if( demoData ) { globals.scnPlay.demo.create(demoData, this.offsetPos, this.rotY / 180.0 * Math.PI, this.startFrame); } + if( demoData ) { globals.scnPlay.demo.create(this.id, demoData, this.offsetPos, this.rotY / 180.0 * Math.PI, this.startFrame); } else { console.warn('Failed to load demo data:', this.stbFilename); } return this.globals.renderer; diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 67a3ba121..303b8a474 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -424,6 +424,7 @@ export class dDemo_manager_c { private frameNoMsg: number; private mode = EDemoMode.None; private curFile: ArrayBufferSlice | null; + private name: string | null = null; private parser: TParse; private system = new dDemo_system_c(this.globals); @@ -433,12 +434,14 @@ export class dDemo_manager_c { private globals: dGlobals ) { } + public getName() { return this.name; } public getFrame() { return this.frame; } public getFrameNoMsg() { return this.frameNoMsg; } public getMode() { return this.mode; } public getSystem() { return this.system; } - public create(data: ArrayBufferSlice, originPos?: vec3, rotY?: number, startFrame?: number): boolean { + public create(name: string, data: ArrayBufferSlice, originPos?: vec3, rotY?: number, startFrame?: number): boolean { + this.name = name; this.parser = new TParse(this.control); if (!this.parser.parse(data, 0)) { @@ -463,6 +466,7 @@ export class dDemo_manager_c { this.control.destroyObject_all(); this.system.remove(); this.curFile = null; + this.name = null; this.mode = 0; } From 99032808b6f837ff7f9c47676071213ed1114957 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 10 Dec 2024 06:53:48 -0500 Subject: [PATCH 02/34] Starting point for Placename implementation --- src/ZeldaWindWaker/Main.ts | 7 +++ src/ZeldaWindWaker/d_place_name.ts | 77 ++++++++++++++++++++++++++++++ src/ZeldaWindWaker/d_procname.ts | 1 + 3 files changed, 85 insertions(+) create mode 100644 src/ZeldaWindWaker/d_place_name.ts diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index a92bdb893..741849e81 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -42,6 +42,7 @@ import { WoodPacket } from './d_wood.js'; import { fopAcM_create, fopAcM_searchFromName, fopAc_ac_c } from './f_op_actor.js'; import { cPhs__Status, fGlobals, fopDw_Draw, fopScn, fpcCt_Handler, fpcLy_SetCurrentLayer, fpcM_Management, fpcPf__Register, fpcSCtRq_Request, fpc_pc__ProfileList } from './framework.js'; import { dDemo_manager_c, EDemoCamFlags, EDemoMode } from './d_demo.js'; +import { d_pn__RegisterConstructors, Placename, PlacenameState, updatePlaceName } from './d_place_name.js'; type SymbolData = { Filename: string, SymbolName: string, Data: ArrayBufferSlice }; type SymbolMapData = { SymbolData: SymbolData[] }; @@ -775,6 +776,8 @@ class d_s_play extends fopScn { public woodPacket: WoodPacket; public vrboxLoaded: boolean = false; + public placenameIndex: Placename; + public placenameState: PlacenameState; public override load(globals: dGlobals, userData: any): cPhs__Status { super.load(globals, userData); @@ -794,6 +797,9 @@ class d_s_play extends fopScn { public override execute(globals: dGlobals, deltaTimeFrames: number): void { this.demo.update(); + // From d_menu_window::dMs_placenameMove() + updatePlaceName(globals); + // From executeEvtManager() -> SpecialProcPackage() if (this.demo.getMode() === EDemoMode.Ended) { this.demo.remove(); @@ -884,6 +890,7 @@ class SceneDesc { dKy__RegisterConstructors(framework); dKyw__RegisterConstructors(framework); d_a__RegisterConstructors(framework); + d_pn__RegisterConstructors(framework); LegacyActor__RegisterFallbackConstructor(framework); const symbolMap = new SymbolMap(modelCache.getFileData(`extra.crg1_arc`)); diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts new file mode 100644 index 000000000..67458ef52 --- /dev/null +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -0,0 +1,77 @@ +import { GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; +import { ViewerRenderInput } from "../viewer.js"; +import { EDemoMode } from "./d_demo.js"; +import { dProcName_e } from "./d_procname.js"; +import { fopMsgM_create } from "./f_op_msg_mng.js"; +import { cPhs__Status, fGlobals, fpc_bs__Constructor, fpcPf__Register, fpcSCtRq_Request, leafdraw_class } from "./framework.js"; +import { dGlobals } from "./Main.js"; + +export const enum Placename { + OutsetIsland, + ForsakenFortress, +} + +export const enum PlacenameState { + Init, + Hidden, + Visible, +} + +export function updatePlaceName(globals: dGlobals) { + // From d_menu_window::dMs_placenameMove() + if(globals.scnPlay.demo.getMode() == EDemoMode.Playing) { + const frameNo = globals.scnPlay.demo.getFrameNoMsg(); + const demoName = globals.scnPlay.demo.getName(); + + if(demoName == 'awake') { + if (frameNo >= 200 && frameNo < 0x15e) { + globals.scnPlay.placenameIndex = Placename.OutsetIsland; + globals.scnPlay.placenameState = PlacenameState.Visible; + } else if(frameNo >= 0x15e) { + globals.scnPlay.placenameState = PlacenameState.Hidden; + } + } else if (demoName == 'majyuu_shinnyuu') { + if (frameNo >= 0xb54 && frameNo < 0xbea) { + globals.scnPlay.placenameIndex = Placename.ForsakenFortress; + globals.scnPlay.placenameState = PlacenameState.Visible; + } else if(frameNo >= 0xbea) { + globals.scnPlay.placenameState = PlacenameState.Hidden; + } + } + } + + // From d_meter::dMeter_placeNameMove + if (globals.scnPlay.placenameState == PlacenameState.Visible) { + fpcSCtRq_Request(globals.frameworkGlobals, null, dProcName_e.d_place_name, null); + } +} + + +export class d_place_name extends leafdraw_class { + public static PROCESS_NAME = dProcName_e.d_place_name; + + public override load(globals: dGlobals): cPhs__Status { + debugger; + return cPhs__Status.Complete; + } + + public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { + debugger; + } + + public override execute(globals: dGlobals, deltaTimeFrames: number): void { + debugger; + } +} + +interface constructor extends fpc_bs__Constructor { + PROCESS_NAME: dProcName_e; +} + +export function d_pn__RegisterConstructors(globals: fGlobals): void { + function R(constructor: constructor): void { + fpcPf__Register(globals, constructor.PROCESS_NAME, constructor); + } + + R(d_place_name); +} diff --git a/src/ZeldaWindWaker/d_procname.ts b/src/ZeldaWindWaker/d_procname.ts index b9149e263..45c68a8ca 100644 --- a/src/ZeldaWindWaker/d_procname.ts +++ b/src/ZeldaWindWaker/d_procname.ts @@ -32,4 +32,5 @@ export const enum dProcName_e { d_a_swhit0 = 0x01C9, d_kyeff = 0x01E4, d_kyeff2 = 0x01E5, + d_place_name = 0x01EE, }; From 2711dabf227cd6e8255f1667e018f7c7386754d1 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 11 Dec 2024 12:32:06 +0200 Subject: [PATCH 03/34] Fix duplicate d_place_name creations, and Outset Island bti location --- src/ZeldaWindWaker/d_place_name.ts | 35 ++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index 67458ef52..ee7f5fa7e 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -1,14 +1,18 @@ +import { BTI, BTIData } from "../Common/JSYSTEM/JUTTexture.js"; import { GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; import { ViewerRenderInput } from "../viewer.js"; import { EDemoMode } from "./d_demo.js"; import { dProcName_e } from "./d_procname.js"; -import { fopMsgM_create } from "./f_op_msg_mng.js"; +import { dComIfG_resLoad, ResType } from "./d_resorce.js"; import { cPhs__Status, fGlobals, fpc_bs__Constructor, fpcPf__Register, fpcSCtRq_Request, leafdraw_class } from "./framework.js"; import { dGlobals } from "./Main.js"; +let currentPlaceName: number | null = null; + export const enum Placename { OutsetIsland, ForsakenFortress, + DragonRoost, } export const enum PlacenameState { @@ -41,8 +45,15 @@ export function updatePlaceName(globals: dGlobals) { } // From d_meter::dMeter_placeNameMove - if (globals.scnPlay.placenameState == PlacenameState.Visible) { - fpcSCtRq_Request(globals.frameworkGlobals, null, dProcName_e.d_place_name, null); + if(!currentPlaceName) { + if (globals.scnPlay.placenameState == PlacenameState.Visible) { + fpcSCtRq_Request(globals.frameworkGlobals, null, dProcName_e.d_place_name, null); + currentPlaceName = globals.scnPlay.placenameIndex; + } + } else { + if (globals.scnPlay.placenameState == PlacenameState.Hidden) { + currentPlaceName = null; + } } } @@ -51,7 +62,23 @@ export class d_place_name extends leafdraw_class { public static PROCESS_NAME = dProcName_e.d_place_name; public override load(globals: dGlobals): cPhs__Status { - debugger; + let status = dComIfG_resLoad(globals, 'PName'); + if (status != cPhs__Status.Complete) + return status; + + // The Outset Island image lives inside the arc. All others are loose files in 'res/placename/' + let img: BTIData; + if( globals.scnPlay.placenameIndex === Placename.OutsetIsland ) { + img = globals.resCtrl.getObjectRes(ResType.Bti, `PName`, 0x07) + } else { + const filename = `placename/pn_0${globals.scnPlay.placenameIndex + 1}.bti`; // @TODO: Need to support 2 digit numbers + status = globals.modelCache.requestFileData(filename); + if (status !== cPhs__Status.Complete) + return status; + const imgData = globals.modelCache.getFileData(filename); + img = new BTIData(globals.context.device, globals.renderer.renderCache, BTI.parse(imgData, filename).texture); + } + return cPhs__Status.Complete; } From e3c106e41e28faba39c0639941b46f6e69078488 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 11 Dec 2024 15:37:26 +0200 Subject: [PATCH 04/34] JSystem: Add J2D.ts. Implemented loaders for SCRN, PIC1, and PAN1 --- src/Common/JSYSTEM/J2D.ts | 218 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 src/Common/JSYSTEM/J2D.ts diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts new file mode 100644 index 000000000..2440002c7 --- /dev/null +++ b/src/Common/JSYSTEM/J2D.ts @@ -0,0 +1,218 @@ +import ArrayBufferSlice from "../../ArrayBufferSlice.js"; +import { JSystemFileReaderHelper } from "./J3D/J3DLoader.js"; +import { align, assert, readString } from "../../util.js"; +import { Color, colorNewFromRGBA8 } from "../../Color.js"; + +export interface PaneBase { + parent: PaneBase | null +} + +//#region Helpers +interface ResRef { + type: number; + name: string; +} + +function parseResourceReference(dst: ResRef, buffer: ArrayBufferSlice, offset: number): number { + const dataView = buffer.createDataView(); + dst.type = dataView.getUint8(offset + 0); + const nameLen = dataView.getUint8(offset + 1); + dst.name = readString(buffer, offset + 2, nameLen); + + if (dst.type == 2 || dst.type == 3 || dst.type == 4) { + dst.name = ""; + } + + return nameLen + 2; +} +//#endregion + +//#region INF1 +export interface INF1 { + width: number; + height: number; + color: Color +} + +function readINF1Chunk(buffer: ArrayBufferSlice): INF1 { + const view = buffer.createDataView(); + const width = view.getUint16(8); + const height = view.getUint16(10); + const color = view.getUint32(12); + return {width, height, color: colorNewFromRGBA8(color)}; +} +//#endregion + +//#region J2DPicture +interface PIC1 extends PAN1 { + timg: ResRef; + tlut: ResRef; + binding: number; + flags: number; + colorBlack: Color; + colorWhite: Color; + colorCorner: Color; +} + +function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PaneBase | null): PIC1 { + const view = buffer.createDataView(); + + const pane = readPAN1Chunk(buffer, parent); + + let dataCount = view.getUint8(pane.offset + 0); + let offset = pane.offset + 1; + + const timg = { type: 0, name: "" }; + const tlut = { type: 0, name: "" }; + offset += parseResourceReference(timg, buffer, offset); + offset += parseResourceReference(tlut, buffer, offset); + const binding = view.getUint8(offset); + offset += 1; + dataCount -= 3; + + let flags = 0; + if (dataCount > 0) { + flags = view.getUint8(offset); + offset += 1; + dataCount -= 1; + } + + if (dataCount > 0) { + offset += 1; + dataCount -= 1; + } + + let colorBlack = 0x0; + if (dataCount > 0) { + colorBlack = view.getUint32(offset); + offset += 4; + dataCount -= 1; + } + + let colorWhite = 0xFFFFFFFF; + if (dataCount > 0) { + colorWhite = view.getUint32(offset); + offset += 4; + dataCount -= 1; + } + + let colorCorner = 0xFFFFFFFF; + if (dataCount > 0) { + colorCorner = view.getUint32(offset); + offset += 4; + dataCount -= 1; + } + + return {...pane, timg, tlut, binding, flags, colorBlack: colorNewFromRGBA8(colorBlack), + colorWhite: colorNewFromRGBA8(colorWhite), colorCorner: colorNewFromRGBA8(colorCorner) }; +} +//#endregion J2DPicture + +//#region J2Pane +interface PAN1 extends PaneBase { + visible: boolean; + tag: string; + x: number; + y: number; + w: number; + h: number; + rot: number; + basePos: number; + alpha: number; + inheritAlpha: boolean; + + offset: number; // For parsing only +} + +function readPAN1Chunk(buffer: ArrayBufferSlice, parent: PaneBase | null): PAN1 { + const view = buffer.createDataView(); + let offset = 8; + + let dataCount = view.getUint8(offset + 0); + + const visible = !!view.getUint8(offset + 1); + const tag = readString(buffer, offset + 4, 4); + const x = view.getInt16(offset + 8); + const y = view.getInt16(offset + 10); + const w = view.getInt16(offset + 12); + const h = view.getInt16(offset + 14); + dataCount -= 6; + offset += 16; + + let rot = 0; + if(dataCount > 0) { + rot = view.getUint16(offset); + offset += 2; + dataCount -= 1; + } + + let basePos = 0; + if(dataCount > 0) { + basePos = view.getUint8(offset); + offset += 1; + dataCount -= 1; + } + + let alpha = 0; + if(dataCount > 0) { + alpha = view.getUint8(offset); + offset += 1; + dataCount -= 1; + } + + let inheritAlpha = true; + if(dataCount > 0) { + inheritAlpha = !!view.getUint8(offset); + offset += 1; + dataCount -= 1; + } + + offset = align(offset, 4); + return { parent, visible, tag, x, y, w, h, rot, basePos, alpha, inheritAlpha, offset }; +} +//#endregion J2Pane + +//#region J2Screen +export interface SCRN { + inf1: INF1; + panes: PaneBase[]; +} + +export class BLO { + public static parse(buffer: ArrayBufferSlice): SCRN { + const j2d = new JSystemFileReaderHelper(buffer); + assert(j2d.magic === 'SCRNblo1'); + + const inf1 = readINF1Chunk(j2d.nextChunk('INF1')) + const panes: PaneBase[] = []; + + let parentStack: (PaneBase | null)[] = [null]; + let shouldContinue = true; + while(shouldContinue) { + const magic = readString(buffer, j2d.offs, 4); + const chunkSize = j2d.view.getUint32(j2d.offs + 4); + + switch(magic) { + // Panel Types + case 'PAN1': panes.push(readPAN1Chunk(j2d.nextChunk('PAN1'), parentStack[parentStack.length - 1])); break; + case 'PIC1': panes.push(readPIC1Chunk(j2d.nextChunk('PIC1'), parentStack[parentStack.length - 1])); break; + // case 'WIN1': readWIN1Chunk(j2d.nextChunk('WIN1')); break; + // case 'TBX1': readTBX1Chunk(j2d.nextChunk('TBX1')); break; + + // Hierarchy + case 'EXT1': shouldContinue = false; break; + case 'BGN1': j2d.offs += chunkSize; parentStack.push(panes[panes.length - 1]); break; + case 'END1': j2d.offs += chunkSize; parentStack.pop(); break; + + default: + console.warn('Unsupported SCRN block:', magic); + j2d.offs += chunkSize; + break; + } + } + + return { inf1, panes }; + } +} + +//#endregion J2Screen \ No newline at end of file From 1564c36a43262f0d0dcf908ef436e11b546f2e14 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 11 Dec 2024 15:38:04 +0200 Subject: [PATCH 05/34] Wind Waker: Add automatic parsing of BLO (2D UI) data types --- src/ZeldaWindWaker/d_resorce.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_resorce.ts b/src/ZeldaWindWaker/d_resorce.ts index b5e79059b..e5f158e6f 100644 --- a/src/ZeldaWindWaker/d_resorce.ts +++ b/src/ZeldaWindWaker/d_resorce.ts @@ -13,6 +13,7 @@ import { dGlobals } from "./Main.js"; import { cPhs__Status } from "./framework.js"; import { cBgD_t } from "./d_bg.js"; import { NamedArrayBufferSlice } from "../DataFetcher.js"; +import { BLO, SCRN } from "../Common/JSYSTEM/J2D.js"; export interface DZSChunkHeader { type: string; @@ -43,7 +44,7 @@ function parseDZSHeaders(buffer: ArrayBufferSlice): DZS { } export const enum ResType { - Model, Bmt, Bck, Bpk, Brk, Btp, Btk, Bti, Dzb, Dzs, Bva, Stb, Raw, + Model, Bmt, Bck, Bpk, Brk, Btp, Btk, Bti, Dzb, Dzs, Bva, Blo, Stb, Raw, } export type ResAssetType = @@ -58,6 +59,7 @@ export type ResAssetType = T extends ResType.Dzb ? cBgD_t : T extends ResType.Dzs ? DZS : T extends ResType.Bva ? VAF1 : + T extends ResType.Blo ? SCRN : T extends ResType.Stb ? NamedArrayBufferSlice : T extends ResType.Raw ? NamedArrayBufferSlice : unknown; @@ -162,6 +164,8 @@ export class dRes_info_c { resEntry.res = BTK.parse(file.buffer) as ResAssetType; } else if (resType === ResType.Bva) { resEntry.res = BVA.parse(file.buffer) as ResAssetType; + } else if (resType === ResType.Blo) { + resEntry.res = BLO.parse(file.buffer) as ResAssetType; } else if (resType === ResType.Dzs) { resEntry.res = parseDZSHeaders(file.buffer) as ResAssetType; } else if (resType === ResType.Raw || resType === ResType.Stb) { From 5e19797b574a97c250eefff4403d86dd0298b567 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 11 Dec 2024 22:17:50 +0200 Subject: [PATCH 06/34] J2D: Adding starting point of J2DPane and J2DPicture These can (hackily) be used to render a super basic pane --- src/Common/JSYSTEM/J2D.ts | 320 ++++++++++++++++++++++++++++++-------- 1 file changed, 253 insertions(+), 67 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 2440002c7..92cb375e2 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -2,10 +2,25 @@ import ArrayBufferSlice from "../../ArrayBufferSlice.js"; import { JSystemFileReaderHelper } from "./J3D/J3DLoader.js"; import { align, assert, readString } from "../../util.js"; import { Color, colorNewFromRGBA8 } from "../../Color.js"; - -export interface PaneBase { - parent: PaneBase | null -} +import { GfxRenderInst, GfxRenderInstManager } from "../../gfx/render/GfxRenderInstManager.js"; +import * as GX_Material from '../../gx/gx_material.js'; +import * as GX from '../../gx/gx_enum.js'; +import { DrawParams, fillSceneParamsData, GXMaterialHelperGfx, MaterialParams, SceneParams, ub_SceneParamsBufferSize } from "../../gx/gx_render.js"; +import { GfxClipSpaceNearZ, GfxDevice } from "../../gfx/platform/GfxPlatform.js"; +import { computeModelMatrixT, projectionMatrixForCuboid } from "../../MathHelpers.js"; +import { projectionMatrixConvertClipSpaceNearZ } from "../../gfx/helpers/ProjectionHelpers.js"; +import { TSDraw } from "../../SuperMarioGalaxy/DDraw.js"; +import { BTIData } from "./JUTTexture.js"; +import { GXMaterialBuilder } from "../../gx/GXMaterialBuilder.js"; +import { mat4, vec2, vec4 } from "gl-matrix"; +import { GfxRenderCache } from "../../gfx/render/GfxRenderCache.js"; + +//#region Scratch +const materialParams = new MaterialParams(); +const drawParams = new DrawParams(); + +const scratchVec4a = vec4.create(); +//#endregion //#region Helpers interface ResRef { @@ -54,12 +69,12 @@ interface PIC1 extends PAN1 { colorCorner: Color; } -function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PaneBase | null): PIC1 { +function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PIC1 { const view = buffer.createDataView(); const pane = readPAN1Chunk(buffer, parent); - let dataCount = view.getUint8(pane.offset + 0); + const dataCount = view.getUint8(pane.offset + 0); let offset = pane.offset + 1; const timg = { type: 0, name: "" }; @@ -68,40 +83,16 @@ function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PaneBase | null): PIC1 offset += parseResourceReference(tlut, buffer, offset); const binding = view.getUint8(offset); offset += 1; - dataCount -= 3; let flags = 0; - if (dataCount > 0) { - flags = view.getUint8(offset); - offset += 1; - dataCount -= 1; - } - - if (dataCount > 0) { - offset += 1; - dataCount -= 1; - } - let colorBlack = 0x0; - if (dataCount > 0) { - colorBlack = view.getUint32(offset); - offset += 4; - dataCount -= 1; - } - let colorWhite = 0xFFFFFFFF; - if (dataCount > 0) { - colorWhite = view.getUint32(offset); - offset += 4; - dataCount -= 1; - } - let colorCorner = 0xFFFFFFFF; - if (dataCount > 0) { - colorCorner = view.getUint32(offset); - offset += 4; - dataCount -= 1; - } + + if( dataCount >= 4) { flags = view.getUint8(offset + 0); } + if( dataCount >= 6) { colorBlack = view.getUint32(offset + 2); } + if( dataCount >= 7) { colorWhite = view.getUint32(offset + 6); } + if( dataCount >= 8) { colorCorner = view.getUint32(offset + 10); } return {...pane, timg, tlut, binding, flags, colorBlack: colorNewFromRGBA8(colorBlack), colorWhite: colorNewFromRGBA8(colorWhite), colorCorner: colorNewFromRGBA8(colorCorner) }; @@ -109,7 +100,10 @@ function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PaneBase | null): PIC1 //#endregion J2DPicture //#region J2Pane -interface PAN1 extends PaneBase { +interface PAN1 { + parent: PAN1 | null; + type: string; + children: PAN1[]; visible: boolean; tag: string; x: number; @@ -124,11 +118,12 @@ interface PAN1 extends PaneBase { offset: number; // For parsing only } -function readPAN1Chunk(buffer: ArrayBufferSlice, parent: PaneBase | null): PAN1 { +function readPAN1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PAN1 { const view = buffer.createDataView(); + const type = readString(buffer, 0, 4); let offset = 8; - let dataCount = view.getUint8(offset + 0); + const dataCount = view.getUint8(offset + 0); const visible = !!view.getUint8(offset + 1); const tag = readString(buffer, offset + 4, 4); @@ -136,46 +131,27 @@ function readPAN1Chunk(buffer: ArrayBufferSlice, parent: PaneBase | null): PAN1 const y = view.getInt16(offset + 10); const w = view.getInt16(offset + 12); const h = view.getInt16(offset + 14); - dataCount -= 6; offset += 16; let rot = 0; - if(dataCount > 0) { - rot = view.getUint16(offset); - offset += 2; - dataCount -= 1; - } - let basePos = 0; - if(dataCount > 0) { - basePos = view.getUint8(offset); - offset += 1; - dataCount -= 1; - } - let alpha = 0; - if(dataCount > 0) { - alpha = view.getUint8(offset); - offset += 1; - dataCount -= 1; - } - let inheritAlpha = true; - if(dataCount > 0) { - inheritAlpha = !!view.getUint8(offset); - offset += 1; - dataCount -= 1; - } + + if(dataCount >= 7) { rot = view.getUint16(offset); offset += 2; } + if(dataCount >= 8) { basePos = view.getUint8(offset); offset += 1; } + if(dataCount >= 9) { alpha = view.getUint8(offset); offset += 1; } + if(dataCount >= 10) { inheritAlpha = !!view.getUint8(offset); offset += 1; } offset = align(offset, 4); - return { parent, visible, tag, x, y, w, h, rot, basePos, alpha, inheritAlpha, offset }; + return { parent, type, visible, tag, x, y, w, h, rot, basePos, alpha, inheritAlpha, offset, children: [] }; } //#endregion J2Pane //#region J2Screen export interface SCRN { inf1: INF1; - panes: PaneBase[]; + panes: PAN1[]; } export class BLO { @@ -184,9 +160,9 @@ export class BLO { assert(j2d.magic === 'SCRNblo1'); const inf1 = readINF1Chunk(j2d.nextChunk('INF1')) - const panes: PaneBase[] = []; + const panes: PAN1[] = []; - let parentStack: (PaneBase | null)[] = [null]; + let parentStack: (PAN1 | null)[] = [null]; let shouldContinue = true; while(shouldContinue) { const magic = readString(buffer, j2d.offs, 4); @@ -211,8 +187,218 @@ export class BLO { } } + // Generate 'children' lists for each pane + for( const pane of panes ) { + if( pane.parent ) { + pane.parent.children.push(pane); + } + } + return { inf1, panes }; } } -//#endregion J2Screen \ No newline at end of file +//#endregion J2Screen +// TODO: Move and reorganize +export class J2DGrafContext { + sceneParams = new SceneParams(); + + constructor(device: GfxDevice) { + projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 0, 1, 0, 1); + const clipSpaceNearZ = device.queryVendorInfo().clipSpaceNearZ; + projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne); + } + + public setOnRenderInst(renderInst: GfxRenderInst) { + const sceneParamsOffs = renderInst.allocateUniformBuffer(GX_Material.GX_Program.ub_SceneParams, ub_SceneParamsBufferSize); + fillSceneParamsData(renderInst.mapUniformBufferF32(GX_Material.GX_Program.ub_SceneParams), sceneParamsOffs, this.sceneParams); + } +} + +//#region J2DPane +export class J2DPane { + public children: J2DPane[] = []; // @TODO: Make private, provide search mechanism + private parent: J2DPane | null = null; + + public drawMtx = mat4.create(); + public drawAlpha = 1.0; + public drawPos = vec2.create(); + public drawRange = vec2.create(); + + constructor(public data: PAN1, cache: GfxRenderCache, parent: J2DPane | null = null ) { + this.parent = parent; + for (const pane of data.children) { + switch (pane.type) { + case 'PAN1': this.children.push(new J2DPane(pane, cache, this)); break; + case 'PIC1': this.children.push(new J2DPicture(pane, cache, this)); break; + // case 'WIN1': this.children.push(new J2DWindow(pane)); break; + // case 'TBX1': this.children.push(new J2DTextbox(pane)); break; + default: console.warn('Unsupported J2D type:', pane.type); break; + } + } + } + + // NOTE: Overwritten by child classes + public drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx: J2DGrafContext) {} + + public draw(ctx: J2DGrafContext, renderInstManager: GfxRenderInstManager, offsetX: number = 0, offsetY: number = 0, clip: boolean = true): void { + const boundsValid = this.data.w > 0 && this.data.h > 0; + + if(this.data.visible && boundsValid) { + // Src data is in GameCube pixels (640x480, origin bottom left), convert to screen coordinates [0-1] origin upper left. + vec2.set(this.drawPos, this.data.x / 640, 1.0 - this.data.y / 480); + vec2.set(this.drawRange, this.data.w / 480 /* TODO: Multiply by aspect */ , this.data.h / 480); + + if(this.parent) { + // TODO: Handle parents offsets and alpha + this.makeMatrix(); + } else { + this.drawPos[0] += offsetX; + this.drawPos[1] += offsetY; + this.makeMatrix(); + this.drawAlpha = this.data.alpha; + } + + if(this.drawRange[0] > 0 && this.drawRange[1] > 0) { + this.drawSelf(renderInstManager, offsetX, offsetY, ctx); + for (const pane of this.children) { + pane.draw(ctx, renderInstManager, offsetX, offsetY, clip); + } + } + } + + // JSUTree * pParent = mPaneTree.getParent(); + // J2DPane * pParentPane = NULL; + // if (pParent != NULL) + // pParentPane = pParent->getObject(); + + // if (mVisible && mBounds.isValid()) { + // mScreenBounds = mBounds; + // mDrawBounds = mBounds; + + // if (pParentPane != NULL) { + // JGeometry::TBox2 screenBounds = pParentPane->mScreenBounds; + // mScreenBounds.addPos(screenBounds.i.x, screenBounds.i.y); + // MTXConcat(pParentPane->mDrawMtx, mMtx, mDrawMtx); + + // if (clip) { + // JGeometry::TBox2 screenBounds = pParentPane->mScreenBounds; + // mDrawBounds.addPos(screenBounds.i.x, screenBounds.i.y); + // mDrawBounds.intersect(pParentPane->mDrawBounds); + // } + + // mDrawAlpha = mAlpha; + // if (mInheritAlpha) + // mDrawAlpha = (mAlpha * pParentPane->mDrawAlpha) / 0xFF; + // } else { + // mScreenBounds.addPos(x, y); + // makeMatrix(mBounds.i.x + x, mBounds.i.y + y); + // MTXCopy(mMtx, mDrawMtx); + // mDrawBounds.set(mScreenBounds); + // mDrawAlpha = mAlpha; + // } + + // JGeometry::TBox2 clipBounds(0.0f, 0.0f, 0.0f, 0.0f); + // if (clip) + // ((J2DOrthoGraph*)pCtx)->scissorBounds(&clipBounds, &mDrawBounds); + + // if (mDrawBounds.isValid() || !clip) { + // ctx.place(mScreenBounds); + // if (clip) { + // ctx.scissor(clipBounds); + // ctx.setScissor(); + // } + // GXSetCullMode((GXCullMode)mCullMode); + // drawSelf(x, y, &ctx.mPosMtx); + + // for (JSUTreeIterator iter = mPaneTree.getFirstChild(); iter != mPaneTree.getEndChild(); ++iter) + // iter->draw(0.0f, 0.0f, pCtx, clip); + // } + // } + } + + public destroy(device: GfxDevice): void { + // TODO: Destroy children + } + + private makeMatrix() { + if (this.data.rot != 0) { + debugger; // Untested + // TODO: + // MTXTrans(stack1, -mBasePosition.x, -mBasePosition.y, 0.0f); + // f32 rot = mRotationAxis == ROTATE_Z ? -mRotation : mRotation; + // MTXRotDeg(stack2, mRotationAxis, rot); + // MTXTrans(stack3, mBasePosition.x + x, mBasePosition.y + y, 0.0f); + // MTXConcat(stack2, stack1, mMtx); + // MTXConcat(stack3, mMtx, mMtx); + } else { + computeModelMatrixT(this.drawMtx, this.drawPos[0], this.drawPos[1], -1 ); + mat4.scale(this.drawMtx, this.drawMtx, [this.drawRange[0], this.drawRange[1], 1]); + } + } +} +//#endregion + +//#region J2DPicture +export class J2DPicture extends J2DPane { + private sdraw = new TSDraw(); // TODO: Time to move TSDraw out of Mario Galaxy? + private materialHelper: GXMaterialHelperGfx; + public tex: BTIData; // TODO: Make private + + constructor(data: PAN1, cache: GfxRenderCache, parent: J2DPane | null ) { + super(data, cache, parent); + + this.sdraw.setVtxDesc(GX.Attr.POS, true); + this.sdraw.setVtxDesc(GX.Attr.TEX0, true); + + const size = 1; + this.sdraw.beginDraw(cache); + this.sdraw.begin(GX.Command.DRAW_QUADS, 4); + this.sdraw.position3f32(0, 0, 0); + this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 1); + this.sdraw.position3f32(0, size, 0); + this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 0); + this.sdraw.position3f32(size, size, 0); + this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 0); + this.sdraw.position3f32(size, 0, 0); + this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 1); + this.sdraw.end(); + this.sdraw.endDraw(cache); + + const mb = new GXMaterialBuilder('J2DPane'); + mb.setTexCoordGen(GX.TexCoordID.TEXCOORD0, GX.TexGenType.MTX2x4, GX.TexGenSrc.TEX0, GX.TexGenMatrix.IDENTITY); + mb.setTevOrder(0, GX.TexCoordID.TEXCOORD0, GX.TexMapID.TEXMAP0, GX.RasColorChannelID.COLOR_ZERO); + mb.setTevColorIn(0, GX.CC.ZERO, GX.CC.ZERO, GX.CC.ZERO, GX.CC.TEXC); + mb.setTevColorOp(0, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, false, GX.Register.PREV); + mb.setTevAlphaIn(0, GX.CA.ZERO, GX.CA.ZERO, GX.CA.ZERO, GX.CA.TEXA); + mb.setTevAlphaOp(0, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, false, GX.Register.PREV); + mb.setZMode(true, GX.CompareType.LEQUAL, false); + mb.setBlendMode(GX.BlendMode.BLEND, GX.BlendFactor.SRCALPHA, GX.BlendFactor.INVSRCALPHA); + mb.setUsePnMtxIdx(false); + this.materialHelper = new GXMaterialHelperGfx(mb.finish()); + } + + public override drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx2D: J2DGrafContext): void { + renderInstManager.pushTemplate(); + const renderInst = renderInstManager.newRenderInst(); + + ctx2D.setOnRenderInst(renderInst); + this.sdraw.setOnRenderInst(renderInst); + this.materialHelper.setOnRenderInst(renderInstManager.gfxRenderCache, renderInst); + + this.tex.fillTextureMapping(materialParams.m_TextureMapping[0]); + this.materialHelper.allocateMaterialParamsDataOnInst(renderInst, materialParams); + renderInst.setSamplerBindingsFromTextureMappings(materialParams.m_TextureMapping); + + mat4.copy(drawParams.u_PosMtx[0], this.drawMtx); + this.materialHelper.allocateDrawParamsDataOnInst(renderInst, drawParams); + + renderInstManager.submitRenderInst(renderInst); + renderInstManager.popTemplate(); + } + + public override destroy(device: GfxDevice): void { + this.sdraw.destroy(device); + } +} +//#endregion \ No newline at end of file From 15a9f3a7955527300dbe011bf3d27ec7a82ebb1c Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 11 Dec 2024 22:24:26 +0200 Subject: [PATCH 07/34] Hackily render place names. A good starting point. --- src/Common/JSYSTEM/J2D.ts | 3 +-- src/ZeldaWindWaker/d_place_name.ts | 32 ++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 92cb375e2..812404879 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -251,11 +251,10 @@ export class J2DPane { if(this.parent) { // TODO: Handle parents offsets and alpha - this.makeMatrix(); } else { this.drawPos[0] += offsetX; this.drawPos[1] += offsetY; - this.makeMatrix(); + // this.makeMatrix(); this.drawAlpha = this.data.alpha; } diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index ee7f5fa7e..e8e1ec750 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -1,3 +1,5 @@ +import { mat4, vec3 } from "gl-matrix"; +import { J2DGrafContext, J2DPane, J2DPicture, SCRN } from "../Common/JSYSTEM/J2D.js"; import { BTI, BTIData } from "../Common/JSYSTEM/JUTTexture.js"; import { GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; import { ViewerRenderInput } from "../viewer.js"; @@ -6,6 +8,7 @@ import { dProcName_e } from "./d_procname.js"; import { dComIfG_resLoad, ResType } from "./d_resorce.js"; import { cPhs__Status, fGlobals, fpc_bs__Constructor, fpcPf__Register, fpcSCtRq_Request, leafdraw_class } from "./framework.js"; import { dGlobals } from "./Main.js"; +import { MtxTrans } from "./m_do_mtx.js"; let currentPlaceName: number | null = null; @@ -60,12 +63,17 @@ export function updatePlaceName(globals: dGlobals) { export class d_place_name extends leafdraw_class { public static PROCESS_NAME = dProcName_e.d_place_name; + private pane: J2DPane; + private ctx2D: J2DGrafContext; public override load(globals: dGlobals): cPhs__Status { let status = dComIfG_resLoad(globals, 'PName'); if (status != cPhs__Status.Complete) return status; + const screen = globals.resCtrl.getObjectRes(ResType.Blo, `PName`, 0x04) + this.ctx2D = new J2DGrafContext(globals.renderer.device); + // The Outset Island image lives inside the arc. All others are loose files in 'res/placename/' let img: BTIData; if( globals.scnPlay.placenameIndex === Placename.OutsetIsland ) { @@ -78,16 +86,36 @@ export class d_place_name extends leafdraw_class { const imgData = globals.modelCache.getFileData(filename); img = new BTIData(globals.context.device, globals.renderer.renderCache, BTI.parse(imgData, filename).texture); } + + this.pane = new J2DPane(screen.panes[0], globals.renderer.renderCache); + this.pane.children[0].data.visible = false; + this.pane.children[1].data.visible = false; + const pic = this.pane.children[2] as J2DPicture; + pic.tex = img; return cPhs__Status.Complete; } public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { - debugger; + const pane = this.pane.children[2]; + + let x = (pane.data.x / 640); + let y = 1.0 - (pane.data.y / 480); + + let h = (pane.data.h / 480); + let w = h * (pane.data.w / pane.data.h) / globals.camera.aspect; + + // @TODO: Remove. Do this in J2D + MtxTrans([x, y, -1], false, pane.drawMtx); + mat4.scale(pane.drawMtx, pane.drawMtx, [w, h, 1]); + + this.pane.draw(this.ctx2D, renderInstManager); } public override execute(globals: dGlobals, deltaTimeFrames: number): void { - debugger; + + // this.positionM(this.test.modelMatrix, 0.5, 0.5, -1.0, .5, .1); + // this.test.modelMatrix } } From d3035dc11bf8e636ffe787c6024a2de861daa878 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 09:44:53 +0200 Subject: [PATCH 08/34] J2D: Child panes correctly inherit their parents translation --- src/Common/JSYSTEM/J2D.ts | 82 +++++++++------------------------------ 1 file changed, 19 insertions(+), 63 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 812404879..5791acae9 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -20,6 +20,7 @@ const materialParams = new MaterialParams(); const drawParams = new DrawParams(); const scratchVec4a = vec4.create(); +const scratchMat = mat4.create(); //#endregion //#region Helpers @@ -204,7 +205,7 @@ export class J2DGrafContext { sceneParams = new SceneParams(); constructor(device: GfxDevice) { - projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 0, 1, 0, 1); + projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 0, 1, -1, 1); const clipSpaceNearZ = device.queryVendorInfo().clipSpaceNearZ; projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne); } @@ -245,17 +246,22 @@ export class J2DPane { const boundsValid = this.data.w > 0 && this.data.h > 0; if(this.data.visible && boundsValid) { - // Src data is in GameCube pixels (640x480, origin bottom left), convert to screen coordinates [0-1] origin upper left. - vec2.set(this.drawPos, this.data.x / 640, 1.0 - this.data.y / 480); + // Src data is in GameCube pixels (640x480), convert to normalized screen coordinates [0-1]. + vec2.set(this.drawPos, this.data.x / 640, this.data.y / 480); vec2.set(this.drawRange, this.data.w / 480 /* TODO: Multiply by aspect */ , this.data.h / 480); + this.drawAlpha = this.data.alpha / 0xFF; if(this.parent) { - // TODO: Handle parents offsets and alpha + this.makeMatrix(); + mat4.mul(this.drawMtx, this.parent.drawMtx, this.drawMtx ); + if(this.data.inheritAlpha) { + this.drawAlpha *= this.parent.drawAlpha; + } } else { + // Offsets only affect the root pane this.drawPos[0] += offsetX; this.drawPos[1] += offsetY; - // this.makeMatrix(); - this.drawAlpha = this.data.alpha; + this.makeMatrix(); } if(this.drawRange[0] > 0 && this.drawRange[1] > 0) { @@ -265,55 +271,6 @@ export class J2DPane { } } } - - // JSUTree * pParent = mPaneTree.getParent(); - // J2DPane * pParentPane = NULL; - // if (pParent != NULL) - // pParentPane = pParent->getObject(); - - // if (mVisible && mBounds.isValid()) { - // mScreenBounds = mBounds; - // mDrawBounds = mBounds; - - // if (pParentPane != NULL) { - // JGeometry::TBox2 screenBounds = pParentPane->mScreenBounds; - // mScreenBounds.addPos(screenBounds.i.x, screenBounds.i.y); - // MTXConcat(pParentPane->mDrawMtx, mMtx, mDrawMtx); - - // if (clip) { - // JGeometry::TBox2 screenBounds = pParentPane->mScreenBounds; - // mDrawBounds.addPos(screenBounds.i.x, screenBounds.i.y); - // mDrawBounds.intersect(pParentPane->mDrawBounds); - // } - - // mDrawAlpha = mAlpha; - // if (mInheritAlpha) - // mDrawAlpha = (mAlpha * pParentPane->mDrawAlpha) / 0xFF; - // } else { - // mScreenBounds.addPos(x, y); - // makeMatrix(mBounds.i.x + x, mBounds.i.y + y); - // MTXCopy(mMtx, mDrawMtx); - // mDrawBounds.set(mScreenBounds); - // mDrawAlpha = mAlpha; - // } - - // JGeometry::TBox2 clipBounds(0.0f, 0.0f, 0.0f, 0.0f); - // if (clip) - // ((J2DOrthoGraph*)pCtx)->scissorBounds(&clipBounds, &mDrawBounds); - - // if (mDrawBounds.isValid() || !clip) { - // ctx.place(mScreenBounds); - // if (clip) { - // ctx.scissor(clipBounds); - // ctx.setScissor(); - // } - // GXSetCullMode((GXCullMode)mCullMode); - // drawSelf(x, y, &ctx.mPosMtx); - - // for (JSUTreeIterator iter = mPaneTree.getFirstChild(); iter != mPaneTree.getEndChild(); ++iter) - // iter->draw(0.0f, 0.0f, pCtx, clip); - // } - // } } public destroy(device: GfxDevice): void { @@ -331,8 +288,7 @@ export class J2DPane { // MTXConcat(stack2, stack1, mMtx); // MTXConcat(stack3, mMtx, mMtx); } else { - computeModelMatrixT(this.drawMtx, this.drawPos[0], this.drawPos[1], -1 ); - mat4.scale(this.drawMtx, this.drawMtx, [this.drawRange[0], this.drawRange[1], 1]); + computeModelMatrixT(this.drawMtx, this.drawPos[0], this.drawPos[1], 0 ); } } } @@ -350,16 +306,15 @@ export class J2DPicture extends J2DPane { this.sdraw.setVtxDesc(GX.Attr.POS, true); this.sdraw.setVtxDesc(GX.Attr.TEX0, true); - const size = 1; this.sdraw.beginDraw(cache); this.sdraw.begin(GX.Command.DRAW_QUADS, 4); this.sdraw.position3f32(0, 0, 0); this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 1); - this.sdraw.position3f32(0, size, 0); + this.sdraw.position3f32(0, 1, 0); this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 0); - this.sdraw.position3f32(size, size, 0); + this.sdraw.position3f32(1, 1, 0); this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 0); - this.sdraw.position3f32(size, 0, 0); + this.sdraw.position3f32(1, 0, 0); this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 1); this.sdraw.end(); this.sdraw.endDraw(cache); @@ -388,8 +343,9 @@ export class J2DPicture extends J2DPane { this.tex.fillTextureMapping(materialParams.m_TextureMapping[0]); this.materialHelper.allocateMaterialParamsDataOnInst(renderInst, materialParams); renderInst.setSamplerBindingsFromTextureMappings(materialParams.m_TextureMapping); - - mat4.copy(drawParams.u_PosMtx[0], this.drawMtx); + + const scale = mat4.fromScaling(scratchMat, [this.drawRange[0], this.drawRange[1], 1]) + mat4.mul(drawParams.u_PosMtx[0], this.drawMtx, scale); this.materialHelper.allocateDrawParamsDataOnInst(renderInst, drawParams); renderInstManager.submitRenderInst(renderInst); From 44c4af232fa9136e2da53367a60c247a3f4fbc54 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 10:09:51 +0200 Subject: [PATCH 09/34] J2D: Handle UV binding --- src/Common/JSYSTEM/J2D.ts | 63 ++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 5791acae9..76f5f3135 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -43,6 +43,20 @@ function parseResourceReference(dst: ResRef, buffer: ArrayBufferSlice, offset: n } //#endregion +//#region Enums +/** + * If set, the UVs for a quad will be pinned (bound) to the quad edge. If not set, the UVs will be clipped by the quad. + * For instance, if the texture is 200 pixels wide, but the quad is 100 pixels wide and Right is not set, the texture + * will be clipped by half. If both Left and Right are set, the texture will be squashed to fit within the quad. + */ +enum J2DUVBinding { + Bottom = (1 << 0), + Top = (1 << 1), + Right = (1 << 2), + Left = (1 << 3), +}; +//#endregion + //#region INF1 export interface INF1 { width: number; @@ -63,7 +77,7 @@ function readINF1Chunk(buffer: ArrayBufferSlice): INF1 { interface PIC1 extends PAN1 { timg: ResRef; tlut: ResRef; - binding: number; + uvBinding: number; flags: number; colorBlack: Color; colorWhite: Color; @@ -95,7 +109,7 @@ function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PIC1 { if( dataCount >= 7) { colorWhite = view.getUint32(offset + 6); } if( dataCount >= 8) { colorCorner = view.getUint32(offset + 10); } - return {...pane, timg, tlut, binding, flags, colorBlack: colorNewFromRGBA8(colorBlack), + return {...pane, timg, tlut, uvBinding: binding, flags, colorBlack: colorNewFromRGBA8(colorBlack), colorWhite: colorNewFromRGBA8(colorWhite), colorCorner: colorNewFromRGBA8(colorCorner) }; } //#endregion J2DPicture @@ -205,7 +219,7 @@ export class J2DGrafContext { sceneParams = new SceneParams(); constructor(device: GfxDevice) { - projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 0, 1, -1, 1); + projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 0, 1, -1, 0); const clipSpaceNearZ = device.queryVendorInfo().clipSpaceNearZ; projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne); } @@ -224,7 +238,7 @@ export class J2DPane { public drawMtx = mat4.create(); public drawAlpha = 1.0; public drawPos = vec2.create(); - public drawRange = vec2.create(); + public drawDimensions = vec2.create(); constructor(public data: PAN1, cache: GfxRenderCache, parent: J2DPane | null = null ) { this.parent = parent; @@ -248,7 +262,7 @@ export class J2DPane { if(this.data.visible && boundsValid) { // Src data is in GameCube pixels (640x480), convert to normalized screen coordinates [0-1]. vec2.set(this.drawPos, this.data.x / 640, this.data.y / 480); - vec2.set(this.drawRange, this.data.w / 480 /* TODO: Multiply by aspect */ , this.data.h / 480); + vec2.set(this.drawDimensions, this.data.w / 480 /* TODO: Multiply by aspect */ , this.data.h / 480); this.drawAlpha = this.data.alpha / 0xFF; if(this.parent) { @@ -264,7 +278,7 @@ export class J2DPane { this.makeMatrix(); } - if(this.drawRange[0] > 0 && this.drawRange[1] > 0) { + if(this.drawDimensions[0] > 0 && this.drawDimensions[1] > 0) { this.drawSelf(renderInstManager, offsetX, offsetY, ctx); for (const pane of this.children) { pane.draw(ctx, renderInstManager, offsetX, offsetY, clip); @@ -274,7 +288,9 @@ export class J2DPane { } public destroy(device: GfxDevice): void { - // TODO: Destroy children + for (const pane of this.children) { + pane.destroy(device); + } } private makeMatrix() { @@ -299,6 +315,7 @@ export class J2DPicture extends J2DPane { private sdraw = new TSDraw(); // TODO: Time to move TSDraw out of Mario Galaxy? private materialHelper: GXMaterialHelperGfx; public tex: BTIData; // TODO: Make private + public override data: PIC1; constructor(data: PAN1, cache: GfxRenderCache, parent: J2DPane | null ) { super(data, cache, parent); @@ -333,6 +350,36 @@ export class J2DPicture extends J2DPane { } public override drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx2D: J2DGrafContext): void { + let u0, t1, u1, t2; + + const texDimensions = [this.tex.btiTexture.width, this.tex.btiTexture.height]; + const bindLeft = this.data.uvBinding & J2DUVBinding.Left; + const bindRight = this.data.uvBinding & J2DUVBinding.Right; + const bindTop = this.data.uvBinding & J2DUVBinding.Top; + const bindBottom = this.data.uvBinding & J2DUVBinding.Bottom; + + if (bindLeft) { + u0 = 0.0; + u1 = bindRight ? 1.0 : (this.drawDimensions[0] / texDimensions[0]); + } else if (bindRight) { + u0 = 1.0 - (this.drawDimensions[0] / texDimensions[0]); + u1 = 1.0; + } else { + u0 = 0.5 - (this.drawDimensions[0] / texDimensions[0]) / 2.0; + u1 = 0.5 + (this.drawDimensions[0] / texDimensions[0]) / 2.0; + } + + if (bindTop) { + t1 = 0.0; + t1 = bindBottom ? 1.0 : (this.drawDimensions[1] / texDimensions[1]); + } else if (bindBottom) { + t1 = 1.0 - (this.drawDimensions[1] / texDimensions[1]); + t1 = 1.0; + } else { + t1 = 0.5 - (this.drawDimensions[1] / texDimensions[1]) / 2.0; + t1 = 0.5 + (this.drawDimensions[1] / texDimensions[1]) / 2.0; + } + renderInstManager.pushTemplate(); const renderInst = renderInstManager.newRenderInst(); @@ -344,7 +391,7 @@ export class J2DPicture extends J2DPane { this.materialHelper.allocateMaterialParamsDataOnInst(renderInst, materialParams); renderInst.setSamplerBindingsFromTextureMappings(materialParams.m_TextureMapping); - const scale = mat4.fromScaling(scratchMat, [this.drawRange[0], this.drawRange[1], 1]) + const scale = mat4.fromScaling(scratchMat, [this.drawDimensions[0], this.drawDimensions[1], 1]) mat4.mul(drawParams.u_PosMtx[0], this.drawMtx, scale); this.materialHelper.allocateDrawParamsDataOnInst(renderInst, drawParams); From a6eb5ea37f2a62ca5691336193b9454bfc776ba8 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 11:02:38 +0200 Subject: [PATCH 10/34] J2DPicture: GX modes now consistent with original --- src/Common/JSYSTEM/J2D.ts | 67 ++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 76f5f3135..e38d8c510 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -79,9 +79,9 @@ interface PIC1 extends PAN1 { tlut: ResRef; uvBinding: number; flags: number; - colorBlack: Color; - colorWhite: Color; - colorCorner: Color; + colorBlack: number; + colorWhite: number; + colorCorners: number[]; } function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PIC1 { @@ -102,15 +102,17 @@ function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PIC1 { let flags = 0; let colorBlack = 0x0; let colorWhite = 0xFFFFFFFF; - let colorCorner = 0xFFFFFFFF; + let colorCorners = [0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF]; if( dataCount >= 4) { flags = view.getUint8(offset + 0); } if( dataCount >= 6) { colorBlack = view.getUint32(offset + 2); } if( dataCount >= 7) { colorWhite = view.getUint32(offset + 6); } - if( dataCount >= 8) { colorCorner = view.getUint32(offset + 10); } + if( dataCount >= 8) { colorCorners[0] = view.getUint32(offset + 10); } + if( dataCount >= 9) { colorCorners[1] = view.getUint32(offset + 10); } + if( dataCount >= 10) { colorCorners[2] = view.getUint32(offset + 10); } + if( dataCount >= 11) { colorCorners[3] = view.getUint32(offset + 10); } - return {...pane, timg, tlut, uvBinding: binding, flags, colorBlack: colorNewFromRGBA8(colorBlack), - colorWhite: colorNewFromRGBA8(colorWhite), colorCorner: colorNewFromRGBA8(colorCorner) }; + return {...pane, timg, tlut, uvBinding: binding, flags, colorBlack, colorWhite, colorCorners }; } //#endregion J2DPicture @@ -251,6 +253,9 @@ export class J2DPane { default: console.warn('Unsupported J2D type:', pane.type); break; } } + + if(this.data.basePos != 0) { console.warn('Untested J2D feature'); } + if(this.data.rot != 0) { console.warn('Untested J2D feature'); } } // NOTE: Overwritten by child classes @@ -312,41 +317,73 @@ export class J2DPane { //#region J2DPicture export class J2DPicture extends J2DPane { + public override data: PIC1; + private sdraw = new TSDraw(); // TODO: Time to move TSDraw out of Mario Galaxy? private materialHelper: GXMaterialHelperGfx; public tex: BTIData; // TODO: Make private - public override data: PIC1; constructor(data: PAN1, cache: GfxRenderCache, parent: J2DPane | null ) { super(data, cache, parent); this.sdraw.setVtxDesc(GX.Attr.POS, true); this.sdraw.setVtxDesc(GX.Attr.TEX0, true); + this.sdraw.setVtxDesc(GX.Attr.CLR0, true); + + const colors = this.data.colorCorners.map(c => colorNewFromRGBA8(this.data.colorCorners[0])); + colors.forEach(c => c.a = 0.5); this.sdraw.beginDraw(cache); this.sdraw.begin(GX.Command.DRAW_QUADS, 4); this.sdraw.position3f32(0, 0, 0); + this.sdraw.color4color(GX.Attr.CLR0, colors[0] ); this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 1); this.sdraw.position3f32(0, 1, 0); + this.sdraw.color4color(GX.Attr.CLR0, colors[1] ); this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 0); this.sdraw.position3f32(1, 1, 0); + this.sdraw.color4color(GX.Attr.CLR0, colors[3] ); this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 0); this.sdraw.position3f32(1, 0, 0); + this.sdraw.color4color(GX.Attr.CLR0, colors[2] ); this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 1); this.sdraw.end(); this.sdraw.endDraw(cache); const mb = new GXMaterialBuilder('J2DPane'); - mb.setTexCoordGen(GX.TexCoordID.TEXCOORD0, GX.TexGenType.MTX2x4, GX.TexGenSrc.TEX0, GX.TexGenMatrix.IDENTITY); mb.setTevOrder(0, GX.TexCoordID.TEXCOORD0, GX.TexMapID.TEXMAP0, GX.RasColorChannelID.COLOR_ZERO); - mb.setTevColorIn(0, GX.CC.ZERO, GX.CC.ZERO, GX.CC.ZERO, GX.CC.TEXC); - mb.setTevColorOp(0, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, false, GX.Register.PREV); - mb.setTevAlphaIn(0, GX.CA.ZERO, GX.CA.ZERO, GX.CA.ZERO, GX.CA.TEXA); - mb.setTevAlphaOp(0, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, false, GX.Register.PREV); - mb.setZMode(true, GX.CompareType.LEQUAL, false); - mb.setBlendMode(GX.BlendMode.BLEND, GX.BlendFactor.SRCALPHA, GX.BlendFactor.INVSRCALPHA); + mb.setTevColorIn(0, GX.CC.TEXC, GX.CC.ZERO, GX.CC.ZERO, GX.CC.ZERO); + + // Assume alpha is enabled. This is byte 1 on a JUTTexture, but noclip doesn't read it + mb.setTevAlphaIn(0, GX.CA.TEXA, GX.CA.ZERO, GX.CA.ZERO, GX.CA.ZERO); + + mb.setTevColorOp(0, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + mb.setTevAlphaOp(0, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + + // TODO: + // GXSetTevKColor(GX_KCOLOR0, mBlendKonstColor); + // GXSetTevKColor(GX_KCOLOR2, mBlendKonstAlpha); + + // TODO: Why isn't this using the alpha from the vertex colors? + if( true /* Alpha */) { + mb.setTevOrder(1, GX.TexCoordID.TEXCOORD_NULL, GX.TexMapID.TEXMAP_NULL, GX.RasColorChannelID.COLOR0A0); + mb.setTevColorIn(1, GX.CC.CPREV, GX.CC.RASC, GX.CC.ZERO, GX.CC.ZERO); + mb.setTevAlphaIn(1, GX.CA.APREV, GX.CA.RASA, GX.CA.ZERO, GX.CA.ZERO); + mb.setTevColorOp(1, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + mb.setTevAlphaOp(1, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + } + + mb.setTexCoordGen(GX.TexCoordID.TEXCOORD0, GX.TexGenType.MTX2x4, GX.TexGenSrc.TEX0, GX.TexGenMatrix.IDENTITY); + mb.setZMode(false, GX.CompareType.ALWAYS, false); + mb.setBlendMode(GX.BlendMode.BLEND, GX.BlendFactor.SRCALPHA, GX.BlendFactor.INVSRCALPHA, GX.LogicOp.SET); mb.setUsePnMtxIdx(false); this.materialHelper = new GXMaterialHelperGfx(mb.finish()); + + if(this.data.tlut.type != 0) { console.warn('Untested J2D feature'); } + if(this.data.flags != 0) { console.warn('Untested J2D feature'); } + if(this.data.colorBlack != 0 || this.data.colorWhite != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } + if(this.data.colorCorners[0] != 0xFFFFFFFF || this.data.colorCorners[1] != 0xFFFFFFFF + || this.data.colorCorners[2] != 0xFFFFFFFF || this.data.colorCorners[3] != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } } public override drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx2D: J2DGrafContext): void { From 1c487cea341bdefe6b89ebe8975c0ed42e84f398 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 12:59:09 +0200 Subject: [PATCH 11/34] Fix place name constantly being created when at place 0 (Outset Island) --- src/ZeldaWindWaker/d_place_name.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index e8e1ec750..43506a7f1 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -48,7 +48,7 @@ export function updatePlaceName(globals: dGlobals) { } // From d_meter::dMeter_placeNameMove - if(!currentPlaceName) { + if(currentPlaceName === null) { if (globals.scnPlay.placenameState == PlacenameState.Visible) { fpcSCtRq_Request(globals.frameworkGlobals, null, dProcName_e.d_place_name, null); currentPlaceName = globals.scnPlay.placenameIndex; From 6901b779d149836289b623688f7bee2b44d67c4f Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 13:38:11 +0200 Subject: [PATCH 12/34] J2DPicture: Alpha is now computed correctly as texApha * vertAlpha * drawAlpha --- src/Common/JSYSTEM/J2D.ts | 45 ++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index e38d8c510..d5a3b8674 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -5,7 +5,7 @@ import { Color, colorNewFromRGBA8 } from "../../Color.js"; import { GfxRenderInst, GfxRenderInstManager } from "../../gfx/render/GfxRenderInstManager.js"; import * as GX_Material from '../../gx/gx_material.js'; import * as GX from '../../gx/gx_enum.js'; -import { DrawParams, fillSceneParamsData, GXMaterialHelperGfx, MaterialParams, SceneParams, ub_SceneParamsBufferSize } from "../../gx/gx_render.js"; +import { ColorKind, DrawParams, fillSceneParamsData, GXMaterialHelperGfx, MaterialParams, SceneParams, ub_SceneParamsBufferSize } from "../../gx/gx_render.js"; import { GfxClipSpaceNearZ, GfxDevice } from "../../gfx/platform/GfxPlatform.js"; import { computeModelMatrixT, projectionMatrixForCuboid } from "../../MathHelpers.js"; import { projectionMatrixConvertClipSpaceNearZ } from "../../gfx/helpers/ProjectionHelpers.js"; @@ -152,7 +152,7 @@ function readPAN1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PAN1 { let rot = 0; let basePos = 0; - let alpha = 0; + let alpha = 0xFF; let inheritAlpha = true; if(dataCount >= 7) { rot = view.getUint16(offset); offset += 2; } @@ -330,48 +330,38 @@ export class J2DPicture extends J2DPane { this.sdraw.setVtxDesc(GX.Attr.TEX0, true); this.sdraw.setVtxDesc(GX.Attr.CLR0, true); - const colors = this.data.colorCorners.map(c => colorNewFromRGBA8(this.data.colorCorners[0])); - colors.forEach(c => c.a = 0.5); - this.sdraw.beginDraw(cache); this.sdraw.begin(GX.Command.DRAW_QUADS, 4); this.sdraw.position3f32(0, 0, 0); - this.sdraw.color4color(GX.Attr.CLR0, colors[0] ); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[0]) ); this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 1); this.sdraw.position3f32(0, 1, 0); - this.sdraw.color4color(GX.Attr.CLR0, colors[1] ); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[1]) ); this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 0); this.sdraw.position3f32(1, 1, 0); - this.sdraw.color4color(GX.Attr.CLR0, colors[3] ); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[3]) ); this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 0); this.sdraw.position3f32(1, 0, 0); - this.sdraw.color4color(GX.Attr.CLR0, colors[2] ); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[2]) ); this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 1); this.sdraw.end(); this.sdraw.endDraw(cache); const mb = new GXMaterialBuilder('J2DPane'); - mb.setTevOrder(0, GX.TexCoordID.TEXCOORD0, GX.TexMapID.TEXMAP0, GX.RasColorChannelID.COLOR_ZERO); - mb.setTevColorIn(0, GX.CC.TEXC, GX.CC.ZERO, GX.CC.ZERO, GX.CC.ZERO); - // Assume alpha is enabled. This is byte 1 on a JUTTexture, but noclip doesn't read it - mb.setTevAlphaIn(0, GX.CA.TEXA, GX.CA.ZERO, GX.CA.ZERO, GX.CA.ZERO); - + mb.setChanCtrl(GX.ColorChannelID.COLOR0A0, false, GX.ColorSrc.REG, GX.ColorSrc.VTX, 0, GX.DiffuseFunction.NONE, GX.AttenuationFunction.NONE); + // 0: Multiply tex and vertex colors and alpha + mb.setTevOrder(0, GX.TexCoordID.TEXCOORD0, GX.TexMapID.TEXMAP0, GX.RasColorChannelID.COLOR0A0); + mb.setTevColorIn(0, GX.CC.ZERO, GX.CC.TEXC, GX.CC.RASC, GX.CC.ZERO); + mb.setTevAlphaIn(0, GX.CA.ZERO, GX.CA.TEXA, GX.CA.RASA, GX.CA.ZERO); mb.setTevColorOp(0, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); mb.setTevAlphaOp(0, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); - - // TODO: - // GXSetTevKColor(GX_KCOLOR0, mBlendKonstColor); - // GXSetTevKColor(GX_KCOLOR2, mBlendKonstAlpha); - - // TODO: Why isn't this using the alpha from the vertex colors? - if( true /* Alpha */) { - mb.setTevOrder(1, GX.TexCoordID.TEXCOORD_NULL, GX.TexMapID.TEXMAP_NULL, GX.RasColorChannelID.COLOR0A0); - mb.setTevColorIn(1, GX.CC.CPREV, GX.CC.RASC, GX.CC.ZERO, GX.CC.ZERO); - mb.setTevAlphaIn(1, GX.CA.APREV, GX.CA.RASA, GX.CA.ZERO, GX.CA.ZERO); - mb.setTevColorOp(1, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); - mb.setTevAlphaOp(1, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); - } + // 1: Multiply result alpha by dynamic alpha (this.drawAlpha) + mb.setTevOrder(1, GX.TexCoordID.TEXCOORD_NULL, GX.TexMapID.TEXMAP_NULL, GX.RasColorChannelID.COLOR_ZERO); + mb.setTevColorIn(1, GX.CC.CPREV, GX.CC.ZERO, GX.CC.ZERO, GX.CC.ZERO); + mb.setTevAlphaIn(1, GX.CA.ZERO, GX.CA.APREV, GX.CA.A0, GX.CA.ZERO); + mb.setTevColorOp(1, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + mb.setTevAlphaOp(1, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); mb.setTexCoordGen(GX.TexCoordID.TEXCOORD0, GX.TexGenType.MTX2x4, GX.TexGenSrc.TEX0, GX.TexGenMatrix.IDENTITY); mb.setZMode(false, GX.CompareType.ALWAYS, false); @@ -424,6 +414,7 @@ export class J2DPicture extends J2DPane { this.sdraw.setOnRenderInst(renderInst); this.materialHelper.setOnRenderInst(renderInstManager.gfxRenderCache, renderInst); + materialParams.u_Color[ColorKind.C0].a = this.drawAlpha; this.tex.fillTextureMapping(materialParams.m_TextureMapping[0]); this.materialHelper.allocateMaterialParamsDataOnInst(renderInst, materialParams); renderInst.setSamplerBindingsFromTextureMappings(materialParams.m_TextureMapping); From 88964c15d329b05116c6a17f4cd6651076c141ff Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 13:39:05 +0200 Subject: [PATCH 13/34] J2D: Formatting --- src/Common/JSYSTEM/J2D.ts | 90 +++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index d5a3b8674..93ace7006 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -69,7 +69,7 @@ function readINF1Chunk(buffer: ArrayBufferSlice): INF1 { const width = view.getUint16(8); const height = view.getUint16(10); const color = view.getUint32(12); - return {width, height, color: colorNewFromRGBA8(color)}; + return { width, height, color: colorNewFromRGBA8(color) }; } //#endregion @@ -86,7 +86,7 @@ interface PIC1 extends PAN1 { function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PIC1 { const view = buffer.createDataView(); - + const pane = readPAN1Chunk(buffer, parent); const dataCount = view.getUint8(pane.offset + 0); @@ -104,15 +104,15 @@ function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PIC1 { let colorWhite = 0xFFFFFFFF; let colorCorners = [0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF]; - if( dataCount >= 4) { flags = view.getUint8(offset + 0); } - if( dataCount >= 6) { colorBlack = view.getUint32(offset + 2); } - if( dataCount >= 7) { colorWhite = view.getUint32(offset + 6); } - if( dataCount >= 8) { colorCorners[0] = view.getUint32(offset + 10); } - if( dataCount >= 9) { colorCorners[1] = view.getUint32(offset + 10); } - if( dataCount >= 10) { colorCorners[2] = view.getUint32(offset + 10); } - if( dataCount >= 11) { colorCorners[3] = view.getUint32(offset + 10); } + if (dataCount >= 4) { flags = view.getUint8(offset + 0); } + if (dataCount >= 6) { colorBlack = view.getUint32(offset + 2); } + if (dataCount >= 7) { colorWhite = view.getUint32(offset + 6); } + if (dataCount >= 8) { colorCorners[0] = view.getUint32(offset + 10); } + if (dataCount >= 9) { colorCorners[1] = view.getUint32(offset + 10); } + if (dataCount >= 10) { colorCorners[2] = view.getUint32(offset + 10); } + if (dataCount >= 11) { colorCorners[3] = view.getUint32(offset + 10); } - return {...pane, timg, tlut, uvBinding: binding, flags, colorBlack, colorWhite, colorCorners }; + return { ...pane, timg, tlut, uvBinding: binding, flags, colorBlack, colorWhite, colorCorners }; } //#endregion J2DPicture @@ -131,7 +131,7 @@ interface PAN1 { basePos: number; alpha: number; inheritAlpha: boolean; - + offset: number; // For parsing only } @@ -155,10 +155,10 @@ function readPAN1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PAN1 { let alpha = 0xFF; let inheritAlpha = true; - if(dataCount >= 7) { rot = view.getUint16(offset); offset += 2; } - if(dataCount >= 8) { basePos = view.getUint8(offset); offset += 1; } - if(dataCount >= 9) { alpha = view.getUint8(offset); offset += 1; } - if(dataCount >= 10) { inheritAlpha = !!view.getUint8(offset); offset += 1; } + if (dataCount >= 7) { rot = view.getUint16(offset); offset += 2; } + if (dataCount >= 8) { basePos = view.getUint8(offset); offset += 1; } + if (dataCount >= 9) { alpha = view.getUint8(offset); offset += 1; } + if (dataCount >= 10) { inheritAlpha = !!view.getUint8(offset); offset += 1; } offset = align(offset, 4); return { parent, type, visible, tag, x, y, w, h, rot, basePos, alpha, inheritAlpha, offset, children: [] }; @@ -175,17 +175,17 @@ export class BLO { public static parse(buffer: ArrayBufferSlice): SCRN { const j2d = new JSystemFileReaderHelper(buffer); assert(j2d.magic === 'SCRNblo1'); - + const inf1 = readINF1Chunk(j2d.nextChunk('INF1')) const panes: PAN1[] = []; let parentStack: (PAN1 | null)[] = [null]; let shouldContinue = true; - while(shouldContinue) { + while (shouldContinue) { const magic = readString(buffer, j2d.offs, 4); const chunkSize = j2d.view.getUint32(j2d.offs + 4); - switch(magic) { + switch (magic) { // Panel Types case 'PAN1': panes.push(readPAN1Chunk(j2d.nextChunk('PAN1'), parentStack[parentStack.length - 1])); break; case 'PIC1': panes.push(readPIC1Chunk(j2d.nextChunk('PIC1'), parentStack[parentStack.length - 1])); break; @@ -205,8 +205,8 @@ export class BLO { } // Generate 'children' lists for each pane - for( const pane of panes ) { - if( pane.parent ) { + for (const pane of panes) { + if (pane.parent) { pane.parent.children.push(pane); } } @@ -242,7 +242,7 @@ export class J2DPane { public drawPos = vec2.create(); public drawDimensions = vec2.create(); - constructor(public data: PAN1, cache: GfxRenderCache, parent: J2DPane | null = null ) { + constructor(public data: PAN1, cache: GfxRenderCache, parent: J2DPane | null = null) { this.parent = parent; for (const pane of data.children) { switch (pane.type) { @@ -254,26 +254,26 @@ export class J2DPane { } } - if(this.data.basePos != 0) { console.warn('Untested J2D feature'); } - if(this.data.rot != 0) { console.warn('Untested J2D feature'); } + if (this.data.basePos != 0) { console.warn('Untested J2D feature'); } + if (this.data.rot != 0) { console.warn('Untested J2D feature'); } } // NOTE: Overwritten by child classes - public drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx: J2DGrafContext) {} + public drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx: J2DGrafContext) { } public draw(ctx: J2DGrafContext, renderInstManager: GfxRenderInstManager, offsetX: number = 0, offsetY: number = 0, clip: boolean = true): void { const boundsValid = this.data.w > 0 && this.data.h > 0; - if(this.data.visible && boundsValid) { + if (this.data.visible && boundsValid) { // Src data is in GameCube pixels (640x480), convert to normalized screen coordinates [0-1]. vec2.set(this.drawPos, this.data.x / 640, this.data.y / 480); - vec2.set(this.drawDimensions, this.data.w / 480 /* TODO: Multiply by aspect */ , this.data.h / 480); + vec2.set(this.drawDimensions, this.data.w / 480 /* TODO: Multiply by aspect */, this.data.h / 480); this.drawAlpha = this.data.alpha / 0xFF; - if(this.parent) { + if (this.parent) { this.makeMatrix(); - mat4.mul(this.drawMtx, this.parent.drawMtx, this.drawMtx ); - if(this.data.inheritAlpha) { + mat4.mul(this.drawMtx, this.parent.drawMtx, this.drawMtx); + if (this.data.inheritAlpha) { this.drawAlpha *= this.parent.drawAlpha; } } else { @@ -283,7 +283,7 @@ export class J2DPane { this.makeMatrix(); } - if(this.drawDimensions[0] > 0 && this.drawDimensions[1] > 0) { + if (this.drawDimensions[0] > 0 && this.drawDimensions[1] > 0) { this.drawSelf(renderInstManager, offsetX, offsetY, ctx); for (const pane of this.children) { pane.draw(ctx, renderInstManager, offsetX, offsetY, clip); @@ -309,7 +309,7 @@ export class J2DPane { // MTXConcat(stack2, stack1, mMtx); // MTXConcat(stack3, mMtx, mMtx); } else { - computeModelMatrixT(this.drawMtx, this.drawPos[0], this.drawPos[1], 0 ); + computeModelMatrixT(this.drawMtx, this.drawPos[0], this.drawPos[1], 0); } } } @@ -323,9 +323,9 @@ export class J2DPicture extends J2DPane { private materialHelper: GXMaterialHelperGfx; public tex: BTIData; // TODO: Make private - constructor(data: PAN1, cache: GfxRenderCache, parent: J2DPane | null ) { + constructor(data: PAN1, cache: GfxRenderCache, parent: J2DPane | null) { super(data, cache, parent); - + this.sdraw.setVtxDesc(GX.Attr.POS, true); this.sdraw.setVtxDesc(GX.Attr.TEX0, true); this.sdraw.setVtxDesc(GX.Attr.CLR0, true); @@ -333,16 +333,16 @@ export class J2DPicture extends J2DPane { this.sdraw.beginDraw(cache); this.sdraw.begin(GX.Command.DRAW_QUADS, 4); this.sdraw.position3f32(0, 0, 0); - this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[0]) ); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[0])); this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 1); this.sdraw.position3f32(0, 1, 0); - this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[1]) ); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[1])); this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 0); this.sdraw.position3f32(1, 1, 0); - this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[3]) ); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[3])); this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 0); this.sdraw.position3f32(1, 0, 0); - this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[2]) ); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[2])); this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 1); this.sdraw.end(); this.sdraw.endDraw(cache); @@ -369,22 +369,22 @@ export class J2DPicture extends J2DPane { mb.setUsePnMtxIdx(false); this.materialHelper = new GXMaterialHelperGfx(mb.finish()); - if(this.data.tlut.type != 0) { console.warn('Untested J2D feature'); } - if(this.data.flags != 0) { console.warn('Untested J2D feature'); } - if(this.data.colorBlack != 0 || this.data.colorWhite != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } - if(this.data.colorCorners[0] != 0xFFFFFFFF || this.data.colorCorners[1] != 0xFFFFFFFF + if (this.data.tlut.type != 0) { console.warn('Untested J2D feature'); } + if (this.data.flags != 0) { console.warn('Untested J2D feature'); } + if (this.data.colorBlack != 0 || this.data.colorWhite != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } + if (this.data.colorCorners[0] != 0xFFFFFFFF || this.data.colorCorners[1] != 0xFFFFFFFF || this.data.colorCorners[2] != 0xFFFFFFFF || this.data.colorCorners[3] != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } } public override drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx2D: J2DGrafContext): void { let u0, t1, u1, t2; - - const texDimensions = [this.tex.btiTexture.width, this.tex.btiTexture.height]; + + const texDimensions = [this.tex.btiTexture.width, this.tex.btiTexture.height]; const bindLeft = this.data.uvBinding & J2DUVBinding.Left; const bindRight = this.data.uvBinding & J2DUVBinding.Right; const bindTop = this.data.uvBinding & J2DUVBinding.Top; const bindBottom = this.data.uvBinding & J2DUVBinding.Bottom; - + if (bindLeft) { u0 = 0.0; u1 = bindRight ? 1.0 : (this.drawDimensions[0] / texDimensions[0]); @@ -418,7 +418,7 @@ export class J2DPicture extends J2DPane { this.tex.fillTextureMapping(materialParams.m_TextureMapping[0]); this.materialHelper.allocateMaterialParamsDataOnInst(renderInst, materialParams); renderInst.setSamplerBindingsFromTextureMappings(materialParams.m_TextureMapping); - + const scale = mat4.fromScaling(scratchMat, [this.drawDimensions[0], this.drawDimensions[1], 1]) mat4.mul(drawParams.u_PosMtx[0], this.drawMtx, scale); this.materialHelper.allocateDrawParamsDataOnInst(renderInst, drawParams); From 5e5feea942375b3956f01189b004c0887b5f6a64 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 14:22:46 +0200 Subject: [PATCH 14/34] Added `fopMsgM_` functionality to framework.ts, similar to `fopKyM_` This is used by the message system, of which d_place_name is a part. In the future, I'll also use this for message dialogs during demos. --- src/ZeldaWindWaker/d_place_name.ts | 4 +-- src/ZeldaWindWaker/framework.ts | 43 +++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index 43506a7f1..be48a3a0e 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -6,7 +6,7 @@ import { ViewerRenderInput } from "../viewer.js"; import { EDemoMode } from "./d_demo.js"; import { dProcName_e } from "./d_procname.js"; import { dComIfG_resLoad, ResType } from "./d_resorce.js"; -import { cPhs__Status, fGlobals, fpc_bs__Constructor, fpcPf__Register, fpcSCtRq_Request, leafdraw_class } from "./framework.js"; +import { cPhs__Status, fGlobals, fopMsgM_Delete, fpc_bs__Constructor, fpcPf__Register, fpcSCtRq_Request, leafdraw_class, msg_class } from "./framework.js"; import { dGlobals } from "./Main.js"; import { MtxTrans } from "./m_do_mtx.js"; @@ -61,7 +61,7 @@ export function updatePlaceName(globals: dGlobals) { } -export class d_place_name extends leafdraw_class { +export class d_place_name extends msg_class { public static PROCESS_NAME = dProcName_e.d_place_name; private pane: J2DPane; private ctx2D: J2DGrafContext; diff --git a/src/ZeldaWindWaker/framework.ts b/src/ZeldaWindWaker/framework.ts index 79dbf6342..cf2cc9502 100644 --- a/src/ZeldaWindWaker/framework.ts +++ b/src/ZeldaWindWaker/framework.ts @@ -1,9 +1,10 @@ -import { vec3 } from "gl-matrix"; +import { ReadonlyVec3, vec3 } from "gl-matrix"; import ArrayBufferSlice from "../ArrayBufferSlice.js"; import { GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; import { arrayRemove, assert, assertExists, nArray } from "../util.js"; import { ViewerRenderInput } from "../viewer.js"; +import { fopAc_ac_c } from "./f_op_actor.js"; export type fpc_pc__ProfileList = { Profiles: ArrayBufferSlice[] }; @@ -492,4 +493,44 @@ export function fopKyM_Delete(globals: fGlobals, ky: kankyo_class): void { fpcDt_Delete(globals, ky); } //#endregion + +//#region fopMsgM +export class msg_class extends leafdraw_class { + public actor: fopAc_ac_c | null; + public pos = vec3.create(); + public msgNo: number; + + private loadInit: boolean = false; + + public override load(globals: GlobalUserData, prm: fopMsg_prm_class | null): cPhs__Status { + if (!this.loadInit) { + this.loadInit = true; + + if (prm !== null) { + if (prm.pos !== null) + vec3.copy(this.pos, prm.pos); + this.actor = prm.actor; + this.msgNo = prm.msgNo; + } + } + return cPhs__Status.Next; + } +}; + +export interface fopMsg_prm_class { + actor: fopAc_ac_c | null; + pos: ReadonlyVec3 | null; + msgNo: number; +} + +export function fopMsgM_Delete(globals: fGlobals, msg: leafdraw_class) { + fpcDt_Delete(globals, msg); +} + +export function fopMsgM_create(globals: fGlobals, pcName: number): number | null { + // Create on current layer. + const prm: fopMsg_prm_class = { actor: null, pos: null, msgNo: 0 }; + return fpcSCtRq_Request(globals, null, pcName, prm); +} +//#endregion //#endregion From 3d096f90c91fe4fa00e2d4e1ebff2c8fe0279c3c Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 14:24:15 +0200 Subject: [PATCH 15/34] Place names now fade in and out --- src/ZeldaWindWaker/d_place_name.ts | 35 +++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index be48a3a0e..f8d145b8b 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -31,7 +31,7 @@ export function updatePlaceName(globals: dGlobals) { const demoName = globals.scnPlay.demo.getName(); if(demoName == 'awake') { - if (frameNo >= 200 && frameNo < 0x15e) { + if (frameNo >= 200 && frameNo < 350) { globals.scnPlay.placenameIndex = Placename.OutsetIsland; globals.scnPlay.placenameState = PlacenameState.Visible; } else if(frameNo >= 0x15e) { @@ -65,6 +65,7 @@ export class d_place_name extends msg_class { public static PROCESS_NAME = dProcName_e.d_place_name; private pane: J2DPane; private ctx2D: J2DGrafContext; + private animFrame: number = 0; public override load(globals: dGlobals): cPhs__Status { let status = dComIfG_resLoad(globals, 'PName'); @@ -113,9 +114,36 @@ export class d_place_name extends msg_class { } public override execute(globals: dGlobals, deltaTimeFrames: number): void { + if (globals.scnPlay.placenameState == PlacenameState.Visible) { + this.openAnime(deltaTimeFrames); + } else if (globals.scnPlay.placenameState == PlacenameState.Hidden) { + if (this.closeAnime(deltaTimeFrames)) + fopMsgM_Delete(globals.frameworkGlobals, this); + } + } + + private openAnime(deltaTimeFrames: number) { + if(this.animFrame < 10) { + this.animFrame += deltaTimeFrames; - // this.positionM(this.test.modelMatrix, 0.5, 0.5, -1.0, .5, .1); - // this.test.modelMatrix + const pct = (this.animFrame / 10) + const alpha = pct * pct; + + this.pane.data.alpha = alpha * 0xFF; + } + } + + private closeAnime(deltaTimeFrames: number) { + if(this.animFrame > 0) { + this.animFrame -= deltaTimeFrames; + + const pct = (this.animFrame / 10) + const alpha = pct * pct; + + this.pane.data.alpha = alpha * 0xFF; + } + + return this.animFrame <= 0; } } @@ -130,3 +158,4 @@ export function d_pn__RegisterConstructors(globals: fGlobals): void { R(d_place_name); } + From 48c210b3450ed6bb3e88baf04c9ce8260fbfb9be Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 14:47:16 +0200 Subject: [PATCH 16/34] J2DPicture: Clean up UVBinding and texture setting --- src/Common/JSYSTEM/J2D.ts | 152 ++++++++++++++++------------- src/ZeldaWindWaker/d_place_name.ts | 2 +- 2 files changed, 84 insertions(+), 70 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 93ace7006..83fe23689 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -321,31 +321,106 @@ export class J2DPicture extends J2DPane { private sdraw = new TSDraw(); // TODO: Time to move TSDraw out of Mario Galaxy? private materialHelper: GXMaterialHelperGfx; - public tex: BTIData; // TODO: Make private + private tex: BTIData; - constructor(data: PAN1, cache: GfxRenderCache, parent: J2DPane | null) { + constructor(data: PAN1, private cache: GfxRenderCache, parent: J2DPane | null) { super(data, cache, parent); + // @TODO: If the type > 4, load the image on construction + if (this.data.timg.type != 0 && this.data.timg.type != 2) { console.warn('Untested J2D feature'); } + + if (this.data.tlut.type != 0) { console.warn('Untested J2D feature'); } + if (this.data.uvBinding != 15) { console.warn('Untested J2D feature'); } + if (this.data.flags != 0) { console.warn('Untested J2D feature'); } + if (this.data.colorBlack != 0 || this.data.colorWhite != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } + if (this.data.colorCorners[0] != 0xFFFFFFFF || this.data.colorCorners[1] != 0xFFFFFFFF + || this.data.colorCorners[2] != 0xFFFFFFFF || this.data.colorCorners[3] != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } + } + + public setTexture(tex: BTIData) { + this.tex = tex; + this.prepare(); + } + + public override drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx2D: J2DGrafContext): void { + if(!this.tex) { return; } + + renderInstManager.pushTemplate(); + const renderInst = renderInstManager.newRenderInst(); + + ctx2D.setOnRenderInst(renderInst); + this.sdraw.setOnRenderInst(renderInst); + this.materialHelper.setOnRenderInst(renderInstManager.gfxRenderCache, renderInst); + + materialParams.u_Color[ColorKind.C0].a = this.drawAlpha; + this.tex.fillTextureMapping(materialParams.m_TextureMapping[0]); + this.materialHelper.allocateMaterialParamsDataOnInst(renderInst, materialParams); + renderInst.setSamplerBindingsFromTextureMappings(materialParams.m_TextureMapping); + + const scale = mat4.fromScaling(scratchMat, [this.drawDimensions[0], this.drawDimensions[1], 1]) + mat4.mul(drawParams.u_PosMtx[0], this.drawMtx, scale); + this.materialHelper.allocateDrawParamsDataOnInst(renderInst, drawParams); + + renderInstManager.submitRenderInst(renderInst); + renderInstManager.popTemplate(); + } + + public override destroy(device: GfxDevice): void { + this.sdraw.destroy(device); + } + + private prepare() { + assert(!!this.tex); + + let u0, v0, v1, u1; + const texDimensions = [this.tex.btiTexture.width, this.tex.btiTexture.height]; + const bindLeft = this.data.uvBinding & J2DUVBinding.Left; + const bindRight = this.data.uvBinding & J2DUVBinding.Right; + const bindTop = this.data.uvBinding & J2DUVBinding.Top; + const bindBottom = this.data.uvBinding & J2DUVBinding.Bottom; + + if (bindLeft) { + u0 = 0.0; + u1 = bindRight ? 1.0 : (this.drawDimensions[0] / texDimensions[0]); + } else if (bindRight) { + u0 = 1.0 - (this.drawDimensions[0] / texDimensions[0]); + u1 = 1.0; + } else { + u0 = 0.5 - (this.drawDimensions[0] / texDimensions[0]) / 2.0; + u1 = 0.5 + (this.drawDimensions[0] / texDimensions[0]) / 2.0; + } + + if (bindTop) { + v0 = 0.0; + v1 = bindBottom ? 1.0 : (this.drawDimensions[1] / texDimensions[1]); + } else if (bindBottom) { + v0 = 1.0 - (this.drawDimensions[1] / texDimensions[1]); + v1 = 1.0; + } else { + v0 = 0.5 - (this.drawDimensions[1] / texDimensions[1]) / 2.0; + v1 = 0.5 + (this.drawDimensions[1] / texDimensions[1]) / 2.0; + } + this.sdraw.setVtxDesc(GX.Attr.POS, true); this.sdraw.setVtxDesc(GX.Attr.TEX0, true); this.sdraw.setVtxDesc(GX.Attr.CLR0, true); - this.sdraw.beginDraw(cache); + this.sdraw.beginDraw(this.cache); this.sdraw.begin(GX.Command.DRAW_QUADS, 4); this.sdraw.position3f32(0, 0, 0); this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[0])); - this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 1); + this.sdraw.texCoord2f32(GX.Attr.TEX0, u0, v1); this.sdraw.position3f32(0, 1, 0); this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[1])); - this.sdraw.texCoord2f32(GX.Attr.TEX0, 0, 0); + this.sdraw.texCoord2f32(GX.Attr.TEX0, u0, v0); this.sdraw.position3f32(1, 1, 0); this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[3])); - this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 0); + this.sdraw.texCoord2f32(GX.Attr.TEX0, u1, v0); this.sdraw.position3f32(1, 0, 0); this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[2])); - this.sdraw.texCoord2f32(GX.Attr.TEX0, 1, 1); + this.sdraw.texCoord2f32(GX.Attr.TEX0, u1, v1); this.sdraw.end(); - this.sdraw.endDraw(cache); + this.sdraw.endDraw(this.cache); const mb = new GXMaterialBuilder('J2DPane'); // Assume alpha is enabled. This is byte 1 on a JUTTexture, but noclip doesn't read it @@ -368,67 +443,6 @@ export class J2DPicture extends J2DPane { mb.setBlendMode(GX.BlendMode.BLEND, GX.BlendFactor.SRCALPHA, GX.BlendFactor.INVSRCALPHA, GX.LogicOp.SET); mb.setUsePnMtxIdx(false); this.materialHelper = new GXMaterialHelperGfx(mb.finish()); - - if (this.data.tlut.type != 0) { console.warn('Untested J2D feature'); } - if (this.data.flags != 0) { console.warn('Untested J2D feature'); } - if (this.data.colorBlack != 0 || this.data.colorWhite != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } - if (this.data.colorCorners[0] != 0xFFFFFFFF || this.data.colorCorners[1] != 0xFFFFFFFF - || this.data.colorCorners[2] != 0xFFFFFFFF || this.data.colorCorners[3] != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } - } - - public override drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx2D: J2DGrafContext): void { - let u0, t1, u1, t2; - - const texDimensions = [this.tex.btiTexture.width, this.tex.btiTexture.height]; - const bindLeft = this.data.uvBinding & J2DUVBinding.Left; - const bindRight = this.data.uvBinding & J2DUVBinding.Right; - const bindTop = this.data.uvBinding & J2DUVBinding.Top; - const bindBottom = this.data.uvBinding & J2DUVBinding.Bottom; - - if (bindLeft) { - u0 = 0.0; - u1 = bindRight ? 1.0 : (this.drawDimensions[0] / texDimensions[0]); - } else if (bindRight) { - u0 = 1.0 - (this.drawDimensions[0] / texDimensions[0]); - u1 = 1.0; - } else { - u0 = 0.5 - (this.drawDimensions[0] / texDimensions[0]) / 2.0; - u1 = 0.5 + (this.drawDimensions[0] / texDimensions[0]) / 2.0; - } - - if (bindTop) { - t1 = 0.0; - t1 = bindBottom ? 1.0 : (this.drawDimensions[1] / texDimensions[1]); - } else if (bindBottom) { - t1 = 1.0 - (this.drawDimensions[1] / texDimensions[1]); - t1 = 1.0; - } else { - t1 = 0.5 - (this.drawDimensions[1] / texDimensions[1]) / 2.0; - t1 = 0.5 + (this.drawDimensions[1] / texDimensions[1]) / 2.0; - } - - renderInstManager.pushTemplate(); - const renderInst = renderInstManager.newRenderInst(); - - ctx2D.setOnRenderInst(renderInst); - this.sdraw.setOnRenderInst(renderInst); - this.materialHelper.setOnRenderInst(renderInstManager.gfxRenderCache, renderInst); - - materialParams.u_Color[ColorKind.C0].a = this.drawAlpha; - this.tex.fillTextureMapping(materialParams.m_TextureMapping[0]); - this.materialHelper.allocateMaterialParamsDataOnInst(renderInst, materialParams); - renderInst.setSamplerBindingsFromTextureMappings(materialParams.m_TextureMapping); - - const scale = mat4.fromScaling(scratchMat, [this.drawDimensions[0], this.drawDimensions[1], 1]) - mat4.mul(drawParams.u_PosMtx[0], this.drawMtx, scale); - this.materialHelper.allocateDrawParamsDataOnInst(renderInst, drawParams); - - renderInstManager.submitRenderInst(renderInst); - renderInstManager.popTemplate(); - } - - public override destroy(device: GfxDevice): void { - this.sdraw.destroy(device); } } //#endregion \ No newline at end of file diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index f8d145b8b..5ec0589cb 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -92,7 +92,7 @@ export class d_place_name extends msg_class { this.pane.children[0].data.visible = false; this.pane.children[1].data.visible = false; const pic = this.pane.children[2] as J2DPicture; - pic.tex = img; + pic.setTexture(img); return cPhs__Status.Complete; } From 03e2d6d0955e22b03b2f53a92aea02cf6b4e39cf Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 14:57:27 +0200 Subject: [PATCH 17/34] J2D: Handle Y-coordinate flip in ortho matrix. Gamecube origin is top-left, ours is bottom left. Place names are now in the correct positions --- src/Common/JSYSTEM/J2D.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 83fe23689..c8932b692 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -221,7 +221,8 @@ export class J2DGrafContext { sceneParams = new SceneParams(); constructor(device: GfxDevice) { - projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 0, 1, -1, 0); + // @NOTE: The y axis is inverted by this ortho matrix. Gamecube origin is top-left, ours is bottom left. + projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 1, 0, -1, 0); const clipSpaceNearZ = device.queryVendorInfo().clipSpaceNearZ; projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne); } @@ -267,7 +268,7 @@ export class J2DPane { if (this.data.visible && boundsValid) { // Src data is in GameCube pixels (640x480), convert to normalized screen coordinates [0-1]. vec2.set(this.drawPos, this.data.x / 640, this.data.y / 480); - vec2.set(this.drawDimensions, this.data.w / 480 /* TODO: Multiply by aspect */, this.data.h / 480); + vec2.set(this.drawDimensions, this.data.w / 480, this.data.h / 480); this.drawAlpha = this.data.alpha / 0xFF; if (this.parent) { @@ -405,15 +406,16 @@ export class J2DPicture extends J2DPane { this.sdraw.setVtxDesc(GX.Attr.TEX0, true); this.sdraw.setVtxDesc(GX.Attr.CLR0, true); + // @NOTE: Y positions are inverted, to account for the projection matrix Y-flip. Gamecube origin is top-left, ours is bottom left. this.sdraw.beginDraw(this.cache); this.sdraw.begin(GX.Command.DRAW_QUADS, 4); this.sdraw.position3f32(0, 0, 0); this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[0])); this.sdraw.texCoord2f32(GX.Attr.TEX0, u0, v1); - this.sdraw.position3f32(0, 1, 0); + this.sdraw.position3f32(0, -1, 0); this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[1])); this.sdraw.texCoord2f32(GX.Attr.TEX0, u0, v0); - this.sdraw.position3f32(1, 1, 0); + this.sdraw.position3f32(1, -1, 0); this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[3])); this.sdraw.texCoord2f32(GX.Attr.TEX0, u1, v0); this.sdraw.position3f32(1, 0, 0); From 848cbb50f8b8b6e44fee26f75b067a520b352a84 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 15:00:21 +0200 Subject: [PATCH 18/34] Use === and !=== instead of == and != --- src/Common/JSYSTEM/J2D.ts | 26 +++++++++++++------------- src/ZeldaWindWaker/d_place_name.ts | 16 ++++++++-------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index c8932b692..bd492dc2c 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -35,7 +35,7 @@ function parseResourceReference(dst: ResRef, buffer: ArrayBufferSlice, offset: n const nameLen = dataView.getUint8(offset + 1); dst.name = readString(buffer, offset + 2, nameLen); - if (dst.type == 2 || dst.type == 3 || dst.type == 4) { + if (dst.type === 2 || dst.type === 3 || dst.type === 4) { dst.name = ""; } @@ -255,8 +255,8 @@ export class J2DPane { } } - if (this.data.basePos != 0) { console.warn('Untested J2D feature'); } - if (this.data.rot != 0) { console.warn('Untested J2D feature'); } + if (this.data.basePos !== 0) { console.warn('Untested J2D feature'); } + if (this.data.rot !== 0) { console.warn('Untested J2D feature'); } } // NOTE: Overwritten by child classes @@ -300,11 +300,11 @@ export class J2DPane { } private makeMatrix() { - if (this.data.rot != 0) { + if (this.data.rot !== 0) { debugger; // Untested // TODO: // MTXTrans(stack1, -mBasePosition.x, -mBasePosition.y, 0.0f); - // f32 rot = mRotationAxis == ROTATE_Z ? -mRotation : mRotation; + // f32 rot = mRotationAxis === ROTATE_Z ? -mRotation : mRotation; // MTXRotDeg(stack2, mRotationAxis, rot); // MTXTrans(stack3, mBasePosition.x + x, mBasePosition.y + y, 0.0f); // MTXConcat(stack2, stack1, mMtx); @@ -327,14 +327,14 @@ export class J2DPicture extends J2DPane { constructor(data: PAN1, private cache: GfxRenderCache, parent: J2DPane | null) { super(data, cache, parent); // @TODO: If the type > 4, load the image on construction - if (this.data.timg.type != 0 && this.data.timg.type != 2) { console.warn('Untested J2D feature'); } - - if (this.data.tlut.type != 0) { console.warn('Untested J2D feature'); } - if (this.data.uvBinding != 15) { console.warn('Untested J2D feature'); } - if (this.data.flags != 0) { console.warn('Untested J2D feature'); } - if (this.data.colorBlack != 0 || this.data.colorWhite != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } - if (this.data.colorCorners[0] != 0xFFFFFFFF || this.data.colorCorners[1] != 0xFFFFFFFF - || this.data.colorCorners[2] != 0xFFFFFFFF || this.data.colorCorners[3] != 0xFFFFFFFF) { console.warn('Untested J2D feature'); } + if (this.data.timg.type !== 0 && this.data.timg.type !== 2) { console.warn('Untested J2D feature'); } + + if (this.data.tlut.type !== 0) { console.warn('Untested J2D feature'); } + if (this.data.uvBinding !== 15) { console.warn('Untested J2D feature'); } + if (this.data.flags !== 0) { console.warn('Untested J2D feature'); } + if (this.data.colorBlack !== 0 || this.data.colorWhite !== 0xFFFFFFFF) { console.warn('Untested J2D feature'); } + if (this.data.colorCorners[0] !== 0xFFFFFFFF || this.data.colorCorners[1] !== 0xFFFFFFFF + || this.data.colorCorners[2] !== 0xFFFFFFFF || this.data.colorCorners[3] !== 0xFFFFFFFF) { console.warn('Untested J2D feature'); } } public setTexture(tex: BTIData) { diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index 5ec0589cb..3b2a87b9f 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -26,18 +26,18 @@ export const enum PlacenameState { export function updatePlaceName(globals: dGlobals) { // From d_menu_window::dMs_placenameMove() - if(globals.scnPlay.demo.getMode() == EDemoMode.Playing) { + if(globals.scnPlay.demo.getMode() === EDemoMode.Playing) { const frameNo = globals.scnPlay.demo.getFrameNoMsg(); const demoName = globals.scnPlay.demo.getName(); - if(demoName == 'awake') { + if(demoName === 'awake') { if (frameNo >= 200 && frameNo < 350) { globals.scnPlay.placenameIndex = Placename.OutsetIsland; globals.scnPlay.placenameState = PlacenameState.Visible; } else if(frameNo >= 0x15e) { globals.scnPlay.placenameState = PlacenameState.Hidden; } - } else if (demoName == 'majyuu_shinnyuu') { + } else if (demoName === 'majyuu_shinnyuu') { if (frameNo >= 0xb54 && frameNo < 0xbea) { globals.scnPlay.placenameIndex = Placename.ForsakenFortress; globals.scnPlay.placenameState = PlacenameState.Visible; @@ -49,12 +49,12 @@ export function updatePlaceName(globals: dGlobals) { // From d_meter::dMeter_placeNameMove if(currentPlaceName === null) { - if (globals.scnPlay.placenameState == PlacenameState.Visible) { + if (globals.scnPlay.placenameState === PlacenameState.Visible) { fpcSCtRq_Request(globals.frameworkGlobals, null, dProcName_e.d_place_name, null); currentPlaceName = globals.scnPlay.placenameIndex; } } else { - if (globals.scnPlay.placenameState == PlacenameState.Hidden) { + if (globals.scnPlay.placenameState === PlacenameState.Hidden) { currentPlaceName = null; } } @@ -69,7 +69,7 @@ export class d_place_name extends msg_class { public override load(globals: dGlobals): cPhs__Status { let status = dComIfG_resLoad(globals, 'PName'); - if (status != cPhs__Status.Complete) + if (status !== cPhs__Status.Complete) return status; const screen = globals.resCtrl.getObjectRes(ResType.Blo, `PName`, 0x04) @@ -114,9 +114,9 @@ export class d_place_name extends msg_class { } public override execute(globals: dGlobals, deltaTimeFrames: number): void { - if (globals.scnPlay.placenameState == PlacenameState.Visible) { + if (globals.scnPlay.placenameState === PlacenameState.Visible) { this.openAnime(deltaTimeFrames); - } else if (globals.scnPlay.placenameState == PlacenameState.Hidden) { + } else if (globals.scnPlay.placenameState === PlacenameState.Hidden) { if (this.closeAnime(deltaTimeFrames)) fopMsgM_Delete(globals.frameworkGlobals, this); } From 9b13303d0065a5b80d1474c035c14ef5b2d2075e Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 15:08:36 +0200 Subject: [PATCH 19/34] J2D: Handle non 4:3 aspect ratios --- src/Common/JSYSTEM/J2D.ts | 15 +++++++++------ src/ZeldaWindWaker/d_place_name.ts | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index bd492dc2c..b34341c09 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -14,6 +14,7 @@ import { BTIData } from "./JUTTexture.js"; import { GXMaterialBuilder } from "../../gx/GXMaterialBuilder.js"; import { mat4, vec2, vec4 } from "gl-matrix"; import { GfxRenderCache } from "../../gfx/render/GfxRenderCache.js"; +import { ViewerRenderInput } from "../../viewer.js"; //#region Scratch const materialParams = new MaterialParams(); @@ -260,15 +261,17 @@ export class J2DPane { } // NOTE: Overwritten by child classes - public drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx: J2DGrafContext) { } + public drawSelf(renderInstManager: GfxRenderInstManager, viewerRenderInput: ViewerRenderInput, ctx2D: J2DGrafContext, offsetX: number, offsetY: number) { } - public draw(ctx: J2DGrafContext, renderInstManager: GfxRenderInstManager, offsetX: number = 0, offsetY: number = 0, clip: boolean = true): void { + public draw(renderInstManager: GfxRenderInstManager, viewerRenderInput: ViewerRenderInput, ctx2D: J2DGrafContext, offsetX: number = 0, offsetY: number = 0, clip: boolean = true): void { const boundsValid = this.data.w > 0 && this.data.h > 0; if (this.data.visible && boundsValid) { // Src data is in GameCube pixels (640x480), convert to normalized screen coordinates [0-1]. + // To support dynamic aspect ratios, we keep the original screenspace height and the original aspect ratio. + // So changing the window width will not cause 2D elements to scale, but changing the window height will. vec2.set(this.drawPos, this.data.x / 640, this.data.y / 480); - vec2.set(this.drawDimensions, this.data.w / 480, this.data.h / 480); + vec2.set(this.drawDimensions, this.data.w / (480 * viewerRenderInput.camera.aspect), this.data.h / 480); this.drawAlpha = this.data.alpha / 0xFF; if (this.parent) { @@ -285,9 +288,9 @@ export class J2DPane { } if (this.drawDimensions[0] > 0 && this.drawDimensions[1] > 0) { - this.drawSelf(renderInstManager, offsetX, offsetY, ctx); + this.drawSelf(renderInstManager, viewerRenderInput, ctx2D, offsetX, offsetY); for (const pane of this.children) { - pane.draw(ctx, renderInstManager, offsetX, offsetY, clip); + pane.draw(renderInstManager, viewerRenderInput, ctx2D, offsetX, offsetY, clip); } } } @@ -342,7 +345,7 @@ export class J2DPicture extends J2DPane { this.prepare(); } - public override drawSelf(renderInstManager: GfxRenderInstManager, offsetX: number, offsetY: number, ctx2D: J2DGrafContext): void { + public override drawSelf(renderInstManager: GfxRenderInstManager, viewerRenderInput: ViewerRenderInput, ctx2D: J2DGrafContext, offsetX: number, offsetY: number): void { if(!this.tex) { return; } renderInstManager.pushTemplate(); diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index 3b2a87b9f..f0b6e80bf 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -110,7 +110,7 @@ export class d_place_name extends msg_class { MtxTrans([x, y, -1], false, pane.drawMtx); mat4.scale(pane.drawMtx, pane.drawMtx, [w, h, 1]); - this.pane.draw(this.ctx2D, renderInstManager); + this.pane.draw(renderInstManager, viewerInput, this.ctx2D ); } public override execute(globals: dGlobals, deltaTimeFrames: number): void { From cad71e24865c281a5a6da15118728cfe87b1f299 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 15:39:46 +0200 Subject: [PATCH 20/34] Formatting --- src/Common/JSYSTEM/J2D.ts | 4 ++-- src/ZeldaWindWaker/d_place_name.ts | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index b34341c09..3386b9bc4 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -333,7 +333,7 @@ export class J2DPicture extends J2DPane { if (this.data.timg.type !== 0 && this.data.timg.type !== 2) { console.warn('Untested J2D feature'); } if (this.data.tlut.type !== 0) { console.warn('Untested J2D feature'); } - if (this.data.uvBinding !== 15) { console.warn('Untested J2D feature'); } + if (this.data.uvBinding !== 15) { console.warn('Untested J2D feature'); } if (this.data.flags !== 0) { console.warn('Untested J2D feature'); } if (this.data.colorBlack !== 0 || this.data.colorWhite !== 0xFFFFFFFF) { console.warn('Untested J2D feature'); } if (this.data.colorCorners[0] !== 0xFFFFFFFF || this.data.colorCorners[1] !== 0xFFFFFFFF @@ -346,7 +346,7 @@ export class J2DPicture extends J2DPane { } public override drawSelf(renderInstManager: GfxRenderInstManager, viewerRenderInput: ViewerRenderInput, ctx2D: J2DGrafContext, offsetX: number, offsetY: number): void { - if(!this.tex) { return; } + if (!this.tex) { return; } renderInstManager.pushTemplate(); const renderInst = renderInstManager.newRenderInst(); diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index f0b6e80bf..df5172e75 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -16,39 +16,39 @@ export const enum Placename { OutsetIsland, ForsakenFortress, DragonRoost, -} +} export const enum PlacenameState { Init, Hidden, Visible, -} +} export function updatePlaceName(globals: dGlobals) { // From d_menu_window::dMs_placenameMove() - if(globals.scnPlay.demo.getMode() === EDemoMode.Playing) { + if (globals.scnPlay.demo.getMode() === EDemoMode.Playing) { const frameNo = globals.scnPlay.demo.getFrameNoMsg(); const demoName = globals.scnPlay.demo.getName(); - if(demoName === 'awake') { + if (demoName === 'awake') { if (frameNo >= 200 && frameNo < 350) { globals.scnPlay.placenameIndex = Placename.OutsetIsland; globals.scnPlay.placenameState = PlacenameState.Visible; - } else if(frameNo >= 0x15e) { + } else if (frameNo >= 0x15e) { globals.scnPlay.placenameState = PlacenameState.Hidden; } } else if (demoName === 'majyuu_shinnyuu') { if (frameNo >= 0xb54 && frameNo < 0xbea) { globals.scnPlay.placenameIndex = Placename.ForsakenFortress; globals.scnPlay.placenameState = PlacenameState.Visible; - } else if(frameNo >= 0xbea) { + } else if (frameNo >= 0xbea) { globals.scnPlay.placenameState = PlacenameState.Hidden; } } } // From d_meter::dMeter_placeNameMove - if(currentPlaceName === null) { + if (currentPlaceName === null) { if (globals.scnPlay.placenameState === PlacenameState.Visible) { fpcSCtRq_Request(globals.frameworkGlobals, null, dProcName_e.d_place_name, null); currentPlaceName = globals.scnPlay.placenameIndex; @@ -77,7 +77,7 @@ export class d_place_name extends msg_class { // The Outset Island image lives inside the arc. All others are loose files in 'res/placename/' let img: BTIData; - if( globals.scnPlay.placenameIndex === Placename.OutsetIsland ) { + if (globals.scnPlay.placenameIndex === Placename.OutsetIsland) { img = globals.resCtrl.getObjectRes(ResType.Bti, `PName`, 0x07) } else { const filename = `placename/pn_0${globals.scnPlay.placenameIndex + 1}.bti`; // @TODO: Need to support 2 digit numbers @@ -93,13 +93,13 @@ export class d_place_name extends msg_class { this.pane.children[1].data.visible = false; const pic = this.pane.children[2] as J2DPicture; pic.setTexture(img); - + return cPhs__Status.Complete; } public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { const pane = this.pane.children[2]; - + let x = (pane.data.x / 640); let y = 1.0 - (pane.data.y / 480); @@ -110,7 +110,7 @@ export class d_place_name extends msg_class { MtxTrans([x, y, -1], false, pane.drawMtx); mat4.scale(pane.drawMtx, pane.drawMtx, [w, h, 1]); - this.pane.draw(renderInstManager, viewerInput, this.ctx2D ); + this.pane.draw(renderInstManager, viewerInput, this.ctx2D); } public override execute(globals: dGlobals, deltaTimeFrames: number): void { @@ -123,7 +123,7 @@ export class d_place_name extends msg_class { } private openAnime(deltaTimeFrames: number) { - if(this.animFrame < 10) { + if (this.animFrame < 10) { this.animFrame += deltaTimeFrames; const pct = (this.animFrame / 10) @@ -134,7 +134,7 @@ export class d_place_name extends msg_class { } private closeAnime(deltaTimeFrames: number) { - if(this.animFrame > 0) { + if (this.animFrame > 0) { this.animFrame -= deltaTimeFrames; const pct = (this.animFrame / 10) From 498aa780ffa38854db5e305ee427b186d59ebcf0 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 15:40:15 +0200 Subject: [PATCH 21/34] Render place names into the UI render list so they appear on top --- src/ZeldaWindWaker/d_place_name.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index df5172e75..4c0c22587 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -110,6 +110,7 @@ export class d_place_name extends msg_class { MtxTrans([x, y, -1], false, pane.drawMtx); mat4.scale(pane.drawMtx, pane.drawMtx, [w, h, 1]); + renderInstManager.setCurrentList(globals.dlst.ui[0]); this.pane.draw(renderInstManager, viewerInput, this.ctx2D); } From 163fe5c6336bb6ecf172ceb8fadecfe6aed1c341 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 15:44:29 +0200 Subject: [PATCH 22/34] Fix bug where alpha could exceed 0xFF and wrap back to near 0 --- src/ZeldaWindWaker/d_place_name.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index 4c0c22587..f0c5ea12e 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -127,7 +127,7 @@ export class d_place_name extends msg_class { if (this.animFrame < 10) { this.animFrame += deltaTimeFrames; - const pct = (this.animFrame / 10) + const pct = Math.min(this.animFrame / 10, 1.0) const alpha = pct * pct; this.pane.data.alpha = alpha * 0xFF; @@ -138,7 +138,7 @@ export class d_place_name extends msg_class { if (this.animFrame > 0) { this.animFrame -= deltaTimeFrames; - const pct = (this.animFrame / 10) + const pct = Math.min(this.animFrame / 10, 1.0) const alpha = pct * pct; this.pane.data.alpha = alpha * 0xFF; From 2ac88b9155a357dd1db14653a98ee682eab17a97 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 15:50:01 +0200 Subject: [PATCH 23/34] J2D: Reorganizing and cleanup --- src/Common/JSYSTEM/J2D.ts | 55 ++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 3386b9bc4..77d1619bb 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -16,15 +16,11 @@ import { mat4, vec2, vec4 } from "gl-matrix"; import { GfxRenderCache } from "../../gfx/render/GfxRenderCache.js"; import { ViewerRenderInput } from "../../viewer.js"; -//#region Scratch const materialParams = new MaterialParams(); const drawParams = new DrawParams(); -const scratchVec4a = vec4.create(); const scratchMat = mat4.create(); -//#endregion -//#region Helpers interface ResRef { type: number; name: string; @@ -42,9 +38,7 @@ function parseResourceReference(dst: ResRef, buffer: ArrayBufferSlice, offset: n return nameLen + 2; } -//#endregion -//#region Enums /** * If set, the UVs for a quad will be pinned (bound) to the quad edge. If not set, the UVs will be clipped by the quad. * For instance, if the texture is 200 pixels wide, but the quad is 100 pixels wide and Right is not set, the texture @@ -56,9 +50,26 @@ enum J2DUVBinding { Right = (1 << 2), Left = (1 << 3), }; -//#endregion -//#region INF1 + +// TODO: Move and reorganize +export class J2DGrafContext { + sceneParams = new SceneParams(); + + constructor(device: GfxDevice) { + // @NOTE: The y axis is inverted by this ortho matrix. Gamecube origin is top-left, ours is bottom left. + projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 1, 0, -1, 0); + const clipSpaceNearZ = device.queryVendorInfo().clipSpaceNearZ; + projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne); + } + + public setOnRenderInst(renderInst: GfxRenderInst) { + const sceneParamsOffs = renderInst.allocateUniformBuffer(GX_Material.GX_Program.ub_SceneParams, ub_SceneParamsBufferSize); + fillSceneParamsData(renderInst.mapUniformBufferF32(GX_Material.GX_Program.ub_SceneParams), sceneParamsOffs, this.sceneParams); + } +} + +//#region Loading/INF1 export interface INF1 { width: number; height: number; @@ -74,7 +85,7 @@ function readINF1Chunk(buffer: ArrayBufferSlice): INF1 { } //#endregion -//#region J2DPicture +//#region Loading/J2DPicture interface PIC1 extends PAN1 { timg: ResRef; tlut: ResRef; @@ -115,9 +126,9 @@ function readPIC1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PIC1 { return { ...pane, timg, tlut, uvBinding: binding, flags, colorBlack, colorWhite, colorCorners }; } -//#endregion J2DPicture +//#endregion Loading/J2DPicture -//#region J2Pane +//#region Loading/J2Pane interface PAN1 { parent: PAN1 | null; type: string; @@ -164,9 +175,9 @@ function readPAN1Chunk(buffer: ArrayBufferSlice, parent: PAN1 | null): PAN1 { offset = align(offset, 4); return { parent, type, visible, tag, x, y, w, h, rot, basePos, alpha, inheritAlpha, offset, children: [] }; } -//#endregion J2Pane +//#endregion Loading/J2Pane -//#region J2Screen +//#region Loading/J2Screen export interface SCRN { inf1: INF1; panes: PAN1[]; @@ -217,22 +228,6 @@ export class BLO { } //#endregion J2Screen -// TODO: Move and reorganize -export class J2DGrafContext { - sceneParams = new SceneParams(); - - constructor(device: GfxDevice) { - // @NOTE: The y axis is inverted by this ortho matrix. Gamecube origin is top-left, ours is bottom left. - projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 1, 0, -1, 0); - const clipSpaceNearZ = device.queryVendorInfo().clipSpaceNearZ; - projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne); - } - - public setOnRenderInst(renderInst: GfxRenderInst) { - const sceneParamsOffs = renderInst.allocateUniformBuffer(GX_Material.GX_Program.ub_SceneParams, ub_SceneParamsBufferSize); - fillSceneParamsData(renderInst.mapUniformBufferF32(GX_Material.GX_Program.ub_SceneParams), sceneParamsOffs, this.sceneParams); - } -} //#region J2DPane export class J2DPane { @@ -329,7 +324,7 @@ export class J2DPicture extends J2DPane { constructor(data: PAN1, private cache: GfxRenderCache, parent: J2DPane | null) { super(data, cache, parent); - // @TODO: If the type > 4, load the image on construction + // @TODO: If type > 4, load the image on construction if (this.data.timg.type !== 0 && this.data.timg.type !== 2) { console.warn('Untested J2D feature'); } if (this.data.tlut.type !== 0) { console.warn('Untested J2D feature'); } From 1278d8a9d206e8c761944142fbf418eaac6d9492 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 15:52:15 +0200 Subject: [PATCH 24/34] PlaceName: Remove old code, cleanup --- src/ZeldaWindWaker/d_place_name.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index f0c5ea12e..a5f976e9e 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -25,6 +25,8 @@ export const enum PlacenameState { } export function updatePlaceName(globals: dGlobals) { + // TODO: Initiate other place names manually + // From d_menu_window::dMs_placenameMove() if (globals.scnPlay.demo.getMode() === EDemoMode.Playing) { const frameNo = globals.scnPlay.demo.getFrameNoMsg(); @@ -98,18 +100,6 @@ export class d_place_name extends msg_class { } public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { - const pane = this.pane.children[2]; - - let x = (pane.data.x / 640); - let y = 1.0 - (pane.data.y / 480); - - let h = (pane.data.h / 480); - let w = h * (pane.data.w / pane.data.h) / globals.camera.aspect; - - // @TODO: Remove. Do this in J2D - MtxTrans([x, y, -1], false, pane.drawMtx); - mat4.scale(pane.drawMtx, pane.drawMtx, [w, h, 1]); - renderInstManager.setCurrentList(globals.dlst.ui[0]); this.pane.draw(renderInstManager, viewerInput, this.ctx2D); } From a6fbf24efdeb4a7c90f16fbf5a4dc48159200f48 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 16:02:44 +0200 Subject: [PATCH 25/34] J2D: Implement J2DScreen --- src/Common/JSYSTEM/J2D.ts | 14 +++++++++++++- src/ZeldaWindWaker/d_place_name.ts | 18 +++++++++--------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 77d1619bb..a83f188ee 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -227,7 +227,7 @@ export class BLO { } } -//#endregion J2Screen +//#endregion Loading/J2Screen //#region J2DPane export class J2DPane { @@ -445,4 +445,16 @@ export class J2DPicture extends J2DPane { this.materialHelper = new GXMaterialHelperGfx(mb.finish()); } } +//#endregion + +//#region J2DScreen +export class J2DScreen extends J2DPane { + public color: Color + + constructor(data: SCRN, cache: GfxRenderCache) { + super(data.panes[0], cache, null); + this.color = data.inf1.color; + } +} + //#endregion \ No newline at end of file diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index a5f976e9e..d8b2241f7 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -1,5 +1,5 @@ import { mat4, vec3 } from "gl-matrix"; -import { J2DGrafContext, J2DPane, J2DPicture, SCRN } from "../Common/JSYSTEM/J2D.js"; +import { J2DGrafContext, J2DPane, J2DPicture, J2DScreen, SCRN } from "../Common/JSYSTEM/J2D.js"; import { BTI, BTIData } from "../Common/JSYSTEM/JUTTexture.js"; import { GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; import { ViewerRenderInput } from "../viewer.js"; @@ -65,7 +65,7 @@ export function updatePlaceName(globals: dGlobals) { export class d_place_name extends msg_class { public static PROCESS_NAME = dProcName_e.d_place_name; - private pane: J2DPane; + private screen: J2DScreen; private ctx2D: J2DGrafContext; private animFrame: number = 0; @@ -90,10 +90,10 @@ export class d_place_name extends msg_class { img = new BTIData(globals.context.device, globals.renderer.renderCache, BTI.parse(imgData, filename).texture); } - this.pane = new J2DPane(screen.panes[0], globals.renderer.renderCache); - this.pane.children[0].data.visible = false; - this.pane.children[1].data.visible = false; - const pic = this.pane.children[2] as J2DPicture; + this.screen = new J2DScreen(screen, globals.renderer.renderCache); + this.screen.children[0].data.visible = false; + this.screen.children[1].data.visible = false; + const pic = this.screen.children[2] as J2DPicture; pic.setTexture(img); return cPhs__Status.Complete; @@ -101,7 +101,7 @@ export class d_place_name extends msg_class { public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { renderInstManager.setCurrentList(globals.dlst.ui[0]); - this.pane.draw(renderInstManager, viewerInput, this.ctx2D); + this.screen.draw(renderInstManager, viewerInput, this.ctx2D); } public override execute(globals: dGlobals, deltaTimeFrames: number): void { @@ -120,7 +120,7 @@ export class d_place_name extends msg_class { const pct = Math.min(this.animFrame / 10, 1.0) const alpha = pct * pct; - this.pane.data.alpha = alpha * 0xFF; + this.screen.data.alpha = alpha * 0xFF; } } @@ -131,7 +131,7 @@ export class d_place_name extends msg_class { const pct = Math.min(this.animFrame / 10, 1.0) const alpha = pct * pct; - this.pane.data.alpha = alpha * 0xFF; + this.screen.data.alpha = alpha * 0xFF; } return this.animFrame <= 0; From fcd760ff2db0cfab781fc7e5be327137b34ed73d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 16:24:45 +0200 Subject: [PATCH 26/34] PlaceName: Fill out Placename enum, support 2 digit ids --- src/ZeldaWindWaker/d_place_name.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index d8b2241f7..74a94d222 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -16,6 +16,22 @@ export const enum Placename { OutsetIsland, ForsakenFortress, DragonRoost, + ForestHaven, + GreatfishIsland, + WindfallIsland, + TowerOfTheGods, + KingdomOfHyrule, + GaleIsle, + HeadstoneIsle, + FireMountain, + IceRingIsle, + FairyAtoll, + DragonRoostCavern, + ForbiddenWoods, + TowerOfTheGods2, + EarthTemple, + WindTemple, + GanonsTower, } export const enum PlacenameState { @@ -82,7 +98,8 @@ export class d_place_name extends msg_class { if (globals.scnPlay.placenameIndex === Placename.OutsetIsland) { img = globals.resCtrl.getObjectRes(ResType.Bti, `PName`, 0x07) } else { - const filename = `placename/pn_0${globals.scnPlay.placenameIndex + 1}.bti`; // @TODO: Need to support 2 digit numbers + const placenameId = (globals.scnPlay.placenameIndex + 1); + const filename = `placename/pn_${placenameId.toString().padStart(2, "0")}.bti`; status = globals.modelCache.requestFileData(filename); if (status !== cPhs__Status.Complete) return status; From 7924a318506957aa2a085a6ed1e6d66584f9965c Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 12 Dec 2024 16:42:31 +0200 Subject: [PATCH 27/34] Cleanup unused imports, add missing private designator --- src/Common/JSYSTEM/J2D.ts | 2 +- src/ZeldaWindWaker/d_place_name.ts | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index a83f188ee..7b6961226 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -54,7 +54,7 @@ enum J2DUVBinding { // TODO: Move and reorganize export class J2DGrafContext { - sceneParams = new SceneParams(); + private sceneParams = new SceneParams(); constructor(device: GfxDevice) { // @NOTE: The y axis is inverted by this ortho matrix. Gamecube origin is top-left, ours is bottom left. diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index 74a94d222..9c92a57fc 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -1,14 +1,12 @@ -import { mat4, vec3 } from "gl-matrix"; -import { J2DGrafContext, J2DPane, J2DPicture, J2DScreen, SCRN } from "../Common/JSYSTEM/J2D.js"; +import { J2DGrafContext, J2DPicture, J2DScreen } from "../Common/JSYSTEM/J2D.js"; import { BTI, BTIData } from "../Common/JSYSTEM/JUTTexture.js"; import { GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; import { ViewerRenderInput } from "../viewer.js"; import { EDemoMode } from "./d_demo.js"; import { dProcName_e } from "./d_procname.js"; import { dComIfG_resLoad, ResType } from "./d_resorce.js"; -import { cPhs__Status, fGlobals, fopMsgM_Delete, fpc_bs__Constructor, fpcPf__Register, fpcSCtRq_Request, leafdraw_class, msg_class } from "./framework.js"; +import { cPhs__Status, fGlobals, fopMsgM_Delete, fpc_bs__Constructor, fpcPf__Register, fpcSCtRq_Request, msg_class } from "./framework.js"; import { dGlobals } from "./Main.js"; -import { MtxTrans } from "./m_do_mtx.js"; let currentPlaceName: number | null = null; From f77f4b14e795fa26923308144ae7af5bc80e645d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 15 Dec 2024 17:47:23 +0200 Subject: [PATCH 28/34] Update `J2Pane.drawSelf()` comment to be more clear --- src/Common/JSYSTEM/J2D.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 7b6961226..60d6e59e1 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -255,7 +255,7 @@ export class J2DPane { if (this.data.rot !== 0) { console.warn('Untested J2D feature'); } } - // NOTE: Overwritten by child classes + // NOTE: Overwritten by child classes which actually do some rendering, such as J2DPicture public drawSelf(renderInstManager: GfxRenderInstManager, viewerRenderInput: ViewerRenderInput, ctx2D: J2DGrafContext, offsetX: number, offsetY: number) { } public draw(renderInstManager: GfxRenderInstManager, viewerRenderInput: ViewerRenderInput, ctx2D: J2DGrafContext, offsetX: number = 0, offsetY: number = 0, clip: boolean = true): void { From 0722dd26079584157607242a6a34768d5c0cc8dc Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 15 Dec 2024 17:50:21 +0200 Subject: [PATCH 29/34] Rename `updatePlaceName` to `dPn__update()` --- src/ZeldaWindWaker/Main.ts | 4 ++-- src/ZeldaWindWaker/d_place_name.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 741849e81..c60b8fdcf 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -42,7 +42,7 @@ import { WoodPacket } from './d_wood.js'; import { fopAcM_create, fopAcM_searchFromName, fopAc_ac_c } from './f_op_actor.js'; import { cPhs__Status, fGlobals, fopDw_Draw, fopScn, fpcCt_Handler, fpcLy_SetCurrentLayer, fpcM_Management, fpcPf__Register, fpcSCtRq_Request, fpc_pc__ProfileList } from './framework.js'; import { dDemo_manager_c, EDemoCamFlags, EDemoMode } from './d_demo.js'; -import { d_pn__RegisterConstructors, Placename, PlacenameState, updatePlaceName } from './d_place_name.js'; +import { d_pn__RegisterConstructors, Placename, PlacenameState, dPn__update } from './d_place_name.js'; type SymbolData = { Filename: string, SymbolName: string, Data: ArrayBufferSlice }; type SymbolMapData = { SymbolData: SymbolData[] }; @@ -798,7 +798,7 @@ class d_s_play extends fopScn { this.demo.update(); // From d_menu_window::dMs_placenameMove() - updatePlaceName(globals); + dPn__update(globals); // From executeEvtManager() -> SpecialProcPackage() if (this.demo.getMode() === EDemoMode.Ended) { diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index 9c92a57fc..f93e22f80 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -38,7 +38,7 @@ export const enum PlacenameState { Visible, } -export function updatePlaceName(globals: dGlobals) { +export function dPn__update(globals: dGlobals) { // TODO: Initiate other place names manually // From d_menu_window::dMs_placenameMove() From af7ed887770870ef8d627f2c59c4d32dcb9b72f6 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 15 Dec 2024 17:55:08 +0200 Subject: [PATCH 30/34] Rename `J2D.ts` to `J2Dv1.ts` and add comment explaining v1 vs v2 --- src/Common/JSYSTEM/{J2D.ts => J2Dv1.ts} | 3 +++ src/ZeldaWindWaker/d_place_name.ts | 2 +- src/ZeldaWindWaker/d_resorce.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) rename src/Common/JSYSTEM/{J2D.ts => J2Dv1.ts} (99%) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2Dv1.ts similarity index 99% rename from src/Common/JSYSTEM/J2D.ts rename to src/Common/JSYSTEM/J2Dv1.ts index 60d6e59e1..aab7e09d1 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2Dv1.ts @@ -1,3 +1,6 @@ +// Nintendo 2D rendering, version 1. Used by Wind Waker. +// Twilight Princess (and likely newer titles), use J2D version 2. + import ArrayBufferSlice from "../../ArrayBufferSlice.js"; import { JSystemFileReaderHelper } from "./J3D/J3DLoader.js"; import { align, assert, readString } from "../../util.js"; diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index f93e22f80..92102767b 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -1,4 +1,4 @@ -import { J2DGrafContext, J2DPicture, J2DScreen } from "../Common/JSYSTEM/J2D.js"; +import { J2DGrafContext, J2DPicture, J2DScreen } from "../Common/JSYSTEM/J2Dv1.js"; import { BTI, BTIData } from "../Common/JSYSTEM/JUTTexture.js"; import { GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; import { ViewerRenderInput } from "../viewer.js"; diff --git a/src/ZeldaWindWaker/d_resorce.ts b/src/ZeldaWindWaker/d_resorce.ts index e5f158e6f..c0bf272b2 100644 --- a/src/ZeldaWindWaker/d_resorce.ts +++ b/src/ZeldaWindWaker/d_resorce.ts @@ -13,7 +13,7 @@ import { dGlobals } from "./Main.js"; import { cPhs__Status } from "./framework.js"; import { cBgD_t } from "./d_bg.js"; import { NamedArrayBufferSlice } from "../DataFetcher.js"; -import { BLO, SCRN } from "../Common/JSYSTEM/J2D.js"; +import { BLO, SCRN } from "../Common/JSYSTEM/J2Dv1.js"; export interface DZSChunkHeader { type: string; From f55e4f37b6b705b79fe92a92bcd9cf19e2f6d7b6 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 15 Dec 2024 17:56:04 +0200 Subject: [PATCH 31/34] J2D: Add `| null` to optional member Co-authored-by: Jasper St. Pierre --- src/Common/JSYSTEM/J2D.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/JSYSTEM/J2D.ts b/src/Common/JSYSTEM/J2D.ts index 7b6961226..d08378c29 100644 --- a/src/Common/JSYSTEM/J2D.ts +++ b/src/Common/JSYSTEM/J2D.ts @@ -320,7 +320,7 @@ export class J2DPicture extends J2DPane { private sdraw = new TSDraw(); // TODO: Time to move TSDraw out of Mario Galaxy? private materialHelper: GXMaterialHelperGfx; - private tex: BTIData; + private tex: BTIData | null = null; constructor(data: PAN1, private cache: GfxRenderCache, parent: J2DPane | null) { super(data, cache, parent); From d6cc8751ea91a94baabcb3f4881d74f1ae18a215 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 16 Dec 2024 16:29:17 +0200 Subject: [PATCH 32/34] J2D: Fix ortho convention to match original, fix J2DPicture vert data to work with new ortho matrix Thanks to Jasper for setting me straight on where the Y inversion was coming from (J2DOrthoGraph::setPort()) --- src/Common/JSYSTEM/J2Dv1.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Common/JSYSTEM/J2Dv1.ts b/src/Common/JSYSTEM/J2Dv1.ts index bd434eff8..3d75be9af 100644 --- a/src/Common/JSYSTEM/J2Dv1.ts +++ b/src/Common/JSYSTEM/J2Dv1.ts @@ -60,7 +60,7 @@ export class J2DGrafContext { private sceneParams = new SceneParams(); constructor(device: GfxDevice) { - // @NOTE: The y axis is inverted by this ortho matrix. Gamecube origin is top-left, ours is bottom left. + // NOTE: Bottom is 1, Top is 0, to match the J2D convention projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 1, 0, -1, 0); const clipSpaceNearZ = device.queryVendorInfo().clipSpaceNearZ; projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne); @@ -407,21 +407,20 @@ export class J2DPicture extends J2DPane { this.sdraw.setVtxDesc(GX.Attr.TEX0, true); this.sdraw.setVtxDesc(GX.Attr.CLR0, true); - // @NOTE: Y positions are inverted, to account for the projection matrix Y-flip. Gamecube origin is top-left, ours is bottom left. this.sdraw.beginDraw(this.cache); this.sdraw.begin(GX.Command.DRAW_QUADS, 4); this.sdraw.position3f32(0, 0, 0); this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[0])); - this.sdraw.texCoord2f32(GX.Attr.TEX0, u0, v1); - this.sdraw.position3f32(0, -1, 0); - this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[1])); - this.sdraw.texCoord2f32(GX.Attr.TEX0, u0, v0); - this.sdraw.position3f32(1, -1, 0); - this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[3])); - this.sdraw.texCoord2f32(GX.Attr.TEX0, u1, v0); + this.sdraw.texCoord2f32(GX.Attr.TEX0, u0, v0); // 0 this.sdraw.position3f32(1, 0, 0); this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[2])); - this.sdraw.texCoord2f32(GX.Attr.TEX0, u1, v1); + this.sdraw.texCoord2f32(GX.Attr.TEX0, u1, v0); // 1 + this.sdraw.position3f32(1, 1, 0); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[3])); + this.sdraw.texCoord2f32(GX.Attr.TEX0, u1, v1); // 0 + this.sdraw.position3f32(0, 1, 0); + this.sdraw.color4color(GX.Attr.CLR0, colorNewFromRGBA8(this.data.colorCorners[1])); + this.sdraw.texCoord2f32(GX.Attr.TEX0, u0, v1); // 0 this.sdraw.end(); this.sdraw.endDraw(this.cache); From 90ea8aea62287179bc7f1d86d1be861163659921 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 16 Dec 2024 17:04:43 +0200 Subject: [PATCH 33/34] J2D: Setup J2DGrafContext's ortho matrix similar to original (640x480) --- src/Common/JSYSTEM/J2Dv1.ts | 25 +++++++++++++++++++------ src/ZeldaWindWaker/d_place_name.ts | 6 ++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Common/JSYSTEM/J2Dv1.ts b/src/Common/JSYSTEM/J2Dv1.ts index 3d75be9af..36509d2e0 100644 --- a/src/Common/JSYSTEM/J2Dv1.ts +++ b/src/Common/JSYSTEM/J2Dv1.ts @@ -58,10 +58,12 @@ enum J2DUVBinding { // TODO: Move and reorganize export class J2DGrafContext { private sceneParams = new SceneParams(); + public aspectRatio: number; - constructor(device: GfxDevice) { - // NOTE: Bottom is 1, Top is 0, to match the J2D convention - projectionMatrixForCuboid(this.sceneParams.u_Projection, 0, 1, 1, 0, -1, 0); + constructor(device: GfxDevice, x: number, y: number, w: number, h: number, far: number, near: number) { + this.aspectRatio = w / h; + // NOTE: Y axis is inverted here (bottom = height), to match the original J2D convention + projectionMatrixForCuboid(this.sceneParams.u_Projection, x, w, h, y, near, far); const clipSpaceNearZ = device.queryVendorInfo().clipSpaceNearZ; projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne); } @@ -265,11 +267,10 @@ export class J2DPane { const boundsValid = this.data.w > 0 && this.data.h > 0; if (this.data.visible && boundsValid) { - // Src data is in GameCube pixels (640x480), convert to normalized screen coordinates [0-1]. // To support dynamic aspect ratios, we keep the original screenspace height and the original aspect ratio. // So changing the window width will not cause 2D elements to scale, but changing the window height will. - vec2.set(this.drawPos, this.data.x / 640, this.data.y / 480); - vec2.set(this.drawDimensions, this.data.w / (480 * viewerRenderInput.camera.aspect), this.data.h / 480); + vec2.set(this.drawPos, this.data.x, this.data.y); + vec2.set(this.drawDimensions, this.data.w * (ctx2D.aspectRatio / viewerRenderInput.camera.aspect), this.data.h); this.drawAlpha = this.data.alpha / 0xFF; if (this.parent) { @@ -452,11 +453,23 @@ export class J2DPicture extends J2DPane { //#region J2DScreen export class J2DScreen extends J2DPane { public color: Color + private static defaultCtx: J2DGrafContext; constructor(data: SCRN, cache: GfxRenderCache) { super(data.panes[0], cache, null); this.color = data.inf1.color; } + + override draw(renderInstManager: GfxRenderInstManager, viewerRenderInput: ViewerRenderInput, ctx2D: J2DGrafContext | null, offsetX?: number, offsetY?: number): void { + if (ctx2D !== null) { + super.draw(renderInstManager, viewerRenderInput, ctx2D, offsetX, offsetY); + } else { + if(!J2DScreen.defaultCtx) { + J2DScreen.defaultCtx = new J2DGrafContext(renderInstManager.gfxRenderCache.device, 0.0, 0.0, 640.0, 480.0, -1.0, 0.0); + } + super.draw(renderInstManager, viewerRenderInput, J2DScreen.defaultCtx, offsetX, offsetY); + } + } } //#endregion \ No newline at end of file diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index 92102767b..b54e05900 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -80,7 +80,6 @@ export function dPn__update(globals: dGlobals) { export class d_place_name extends msg_class { public static PROCESS_NAME = dProcName_e.d_place_name; private screen: J2DScreen; - private ctx2D: J2DGrafContext; private animFrame: number = 0; public override load(globals: dGlobals): cPhs__Status { @@ -88,8 +87,7 @@ export class d_place_name extends msg_class { if (status !== cPhs__Status.Complete) return status; - const screen = globals.resCtrl.getObjectRes(ResType.Blo, `PName`, 0x04) - this.ctx2D = new J2DGrafContext(globals.renderer.device); + const screen = globals.resCtrl.getObjectRes(ResType.Blo, `PName`, 0x04); // The Outset Island image lives inside the arc. All others are loose files in 'res/placename/' let img: BTIData; @@ -116,7 +114,7 @@ export class d_place_name extends msg_class { public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { renderInstManager.setCurrentList(globals.dlst.ui[0]); - this.screen.draw(renderInstManager, viewerInput, this.ctx2D); + this.screen.draw(renderInstManager, viewerInput, null); } public override execute(globals: dGlobals, deltaTimeFrames: number): void { From bb53e8cf79ed10351d2def139dc0bdb9d34caddd Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 17 Dec 2024 19:58:04 +0200 Subject: [PATCH 34/34] J2DScreen: Don't use static members to avoid leaking memory between scenes --- src/Common/JSYSTEM/J2Dv1.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Common/JSYSTEM/J2Dv1.ts b/src/Common/JSYSTEM/J2Dv1.ts index 36509d2e0..38c1e953c 100644 --- a/src/Common/JSYSTEM/J2Dv1.ts +++ b/src/Common/JSYSTEM/J2Dv1.ts @@ -453,10 +453,11 @@ export class J2DPicture extends J2DPane { //#region J2DScreen export class J2DScreen extends J2DPane { public color: Color - private static defaultCtx: J2DGrafContext; + private defaultCtx: J2DGrafContext; constructor(data: SCRN, cache: GfxRenderCache) { super(data.panes[0], cache, null); + this.defaultCtx = new J2DGrafContext(cache.device, 0.0, 0.0, 640.0, 480.0, -1.0, 0.0); this.color = data.inf1.color; } @@ -464,10 +465,7 @@ export class J2DScreen extends J2DPane { if (ctx2D !== null) { super.draw(renderInstManager, viewerRenderInput, ctx2D, offsetX, offsetY); } else { - if(!J2DScreen.defaultCtx) { - J2DScreen.defaultCtx = new J2DGrafContext(renderInstManager.gfxRenderCache.device, 0.0, 0.0, 640.0, 480.0, -1.0, 0.0); - } - super.draw(renderInstManager, viewerRenderInput, J2DScreen.defaultCtx, offsetX, offsetY); + super.draw(renderInstManager, viewerRenderInput, this.defaultCtx, offsetX, offsetY); } } }