diff --git a/src/Common/JSYSTEM/J2Dv1.ts b/src/Common/JSYSTEM/J2Dv1.ts index 5f8d2276f..1b6687f75 100644 --- a/src/Common/JSYSTEM/J2Dv1.ts +++ b/src/Common/JSYSTEM/J2Dv1.ts @@ -15,7 +15,7 @@ import { projectionMatrixConvertClipSpaceNearZ } from "../../gfx/helpers/Project import { TSDraw } from "../../SuperMarioGalaxy/DDraw.js"; import { BTIData } from "./JUTTexture.js"; import { GXMaterialBuilder } from "../../gx/GXMaterialBuilder.js"; -import { mat4, vec2 } from "gl-matrix"; +import { mat4, vec2, vec4 } from "gl-matrix"; import { GfxRenderCache } from "../../gfx/render/GfxRenderCache.js"; const materialParams = new MaterialParams(); @@ -54,6 +54,18 @@ function parseResourceReference(buffer: ArrayBufferSlice, offset: number, resTyp return { refType, resType, resName, arcName, _nextOffset: nextOffset }; } +/** + * When rendering a J2D element that was originally designed for 4:3 in a wider aspect ratio, the screenspace Y + * positions are maintained but the X positions must be adjusted. Often we simply want to keep the elements centered, + * but occasionally we want to anchor them to the left or right side of the screen (e.g. Wind Waker place names). + * See also: J2DGrafContext.setPort() and dPlaceName.load() + */ +export enum J2DAnchorPos { + Left, + Center, + Right +} + /** * 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 @@ -67,21 +79,45 @@ const enum J2DUVBinding { }; export class J2DGrafContext { - private sceneParams = new SceneParams(); - public aspectRatio: number; - - constructor(device: GfxDevice, x: number, y: number, private w: number, private 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); + private clipSpaceNearZ: GfxClipSpaceNearZ; + public sceneParams = new SceneParams(); + public viewport = vec4.create(); + public ortho = vec4.create(); + + public near: number; + public far: number; + + // Used to preserve the aspect ratio of assets that were designed for 640x480, but being rendered at a different resolution. + public aspectRatioCorrection: number = 1.0; + + constructor(device: GfxDevice, vpX: number, vpY: number, vpWidth: number, vpHeight: number, near: number, far: number) { + this.clipSpaceNearZ = device.queryVendorInfo().clipSpaceNearZ; + vec4.set(this.viewport, vpX, vpY, vpWidth, vpHeight); + vec4.set(this.ortho, 0, 0, vpWidth, vpHeight); + this.near = far; + this.far = far; + } + + public setOrtho(left: number, top: number, right: number, bottom: number, far: number, near: number) { + vec4.set(this.ortho, left, right, bottom, top); + this.near = near; + this.far = far; } - public setupView(backbufferWidth: number, backbufferHeight: number): void { - const screenAspect = backbufferWidth / backbufferHeight; - const grafAspect = this.w / this.h; - this.aspectRatio = grafAspect / screenAspect; + public setPort(backbufferWidth: number, backbufferHeight: number) { + // 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. + const dstAspect = backbufferWidth / backbufferHeight; + const srcAspect = this.viewport[2] / this.viewport[3]; + this.aspectRatioCorrection = dstAspect / srcAspect; + + const left = this.ortho[0] * this.aspectRatioCorrection; + const right = this.ortho[1] * this.aspectRatioCorrection; + const bottom = this.ortho[2] + 0.5; + const top = this.ortho[3]; + + projectionMatrixForCuboid(this.sceneParams.u_Projection, left, right, bottom, top, this.near, this.far); + projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, this.clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne); } public setOnRenderInst(renderInst: GfxRenderInst): void { @@ -210,9 +246,9 @@ export class BLO { const panes: PAN1[] = []; const screen: SCRN = { - parent: null, type: 'SCRN', children: [], visible: true, - x: 0, y: 0, w: inf1.width, h: inf1.height, color: inf1.color, rot: 0, tag: '', basePos: 0, - alpha: inf1.color.a, inheritAlpha: false, offset: 0, + parent: null, type: 'SCRN', children: [], visible: true, + x: 0, y: 0, w: inf1.width, h: inf1.height, color: inf1.color, rot: 0, tag: '', basePos: 0, + alpha: 0xFF, inheritAlpha: true, offset: 0, }; let parentStack: (PAN1 | null)[] = [screen]; @@ -257,10 +293,10 @@ 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 drawDimensions = vec2.create(); + protected drawMtx = mat4.create(); + protected drawAlpha = 1.0; + protected drawPos = vec2.create(); + protected drawDimensions = vec2.create(); constructor(public data: PAN1, cache: GfxRenderCache, parent: J2DPane | null = null) { this.parent = parent; @@ -288,6 +324,14 @@ export class J2DPane { this.data.visible = false; } + public setAlpha(alpha: number) { + this.data.alpha = alpha * 0xFF; + } + + public getAlpha(alpha: number) { + this.data.alpha = alpha / 0xFF; + } + // NOTE: Overwritten by child classes which actually do some rendering, such as J2DPicture public drawSelf(renderInstManager: GfxRenderInstManager, ctx2D: J2DGrafContext, offsetX: number, offsetY: number) { } @@ -296,10 +340,8 @@ export class J2DPane { const boundsValid = this.data.w > 0 && this.data.h > 0; if (this.data.visible && boundsValid) { - // 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, this.data.y); - vec2.set(this.drawDimensions, this.data.w * ctx2D.aspectRatio, this.data.h); + vec2.set(this.drawDimensions, this.data.w, this.data.h); this.drawAlpha = this.data.alpha / 0xFF; if (this.parent) { @@ -321,7 +363,7 @@ export class J2DPane { } } } - + public search(tag: string): J2DPane | null { if (this.data.tag === tag) return this; @@ -376,8 +418,6 @@ export class J2DPicture extends J2DPane { if (this.data.uvBinding !== 15) { console.warn('Untested J2D feature'); } if (this.data.flags !== 0) { console.warn('Untested J2D feature'); } - if (!colorEqual(this.data.colorBlack, TransparentBlack) || !colorEqual(this.data.colorWhite, White)) - console.warn(`Untested J2D feature colorBlack ${this.data.colorBlack}, colorWhite ${this.data.colorWhite}`); if (!colorEqual(this.data.colorCorners[0], White) || !colorEqual(this.data.colorCorners[1], White) || !colorEqual(this.data.colorCorners[2], White) || !colorEqual(this.data.colorCorners[3], White)) console.warn(`Untested J2D feature colorCorners ${this.data.colorCorners}`); } @@ -398,7 +438,10 @@ export class J2DPicture extends J2DPane { this.sdraw.setOnRenderInst(renderInst); this.materialHelper.setOnRenderInst(renderInstManager.gfxRenderCache, renderInst); - materialParams.u_Color[ColorKind.C0].a = this.drawAlpha; + materialParams.u_Color[ColorKind.C0] = this.data.colorBlack; + materialParams.u_Color[ColorKind.C1] = this.data.colorWhite; + materialParams.u_Color[ColorKind.C2].a = this.drawAlpha; + this.tex.fillTextureMapping(materialParams.m_TextureMapping[0]); this.materialHelper.allocateMaterialParamsDataOnInst(renderInst, materialParams); renderInst.setSamplerBindingsFromTextureMappings(materialParams.m_TextureMapping); @@ -479,20 +522,36 @@ export class J2DPicture extends J2DPane { this.sdraw.endDraw(this.cache); const mb = new GXMaterialBuilder('J2DPane'); + let tevStage = 0; + // Assume alpha is enabled. This is byte 1 on a JUTTexture, but noclip doesn't read it 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); - // 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.setTevOrder(tevStage, GX.TexCoordID.TEXCOORD0, GX.TexMapID.TEXMAP0, GX.RasColorChannelID.COLOR0A0); + mb.setTevColorIn(tevStage, GX.CC.ZERO, GX.CC.TEXC, GX.CC.RASC, GX.CC.ZERO); + mb.setTevAlphaIn(tevStage, GX.CA.ZERO, GX.CA.TEXA, GX.CA.RASA, GX.CA.ZERO); + mb.setTevColorOp(tevStage, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + mb.setTevAlphaOp(tevStage, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + tevStage += 1; + + // 1: Lerp between the Black and White colors based on previous stage result + if (!colorEqual(this.data.colorBlack, TransparentBlack) || !colorEqual(this.data.colorWhite, White)) { + mb.setTevOrder(tevStage, GX.TexCoordID.TEXCOORD_NULL, GX.TexMapID.TEXMAP_NULL, GX.RasColorChannelID.COLOR_ZERO); + mb.setTevColorIn(tevStage, GX.CC.C0, GX.CC.C1, GX.CC.CPREV, GX.CC.ZERO); + mb.setTevAlphaIn(tevStage, GX.CA.A0, GX.CA.A1, GX.CA.APREV, GX.CA.ZERO); + mb.setTevColorOp(tevStage, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + mb.setTevAlphaOp(tevStage, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + tevStage += 1; + } + + // 2: Multiply result alpha by dynamic alpha (this.drawAlpha) + mb.setTevOrder(tevStage, GX.TexCoordID.TEXCOORD_NULL, GX.TexMapID.TEXMAP_NULL, GX.RasColorChannelID.COLOR_ZERO); + mb.setTevColorIn(tevStage, GX.CC.CPREV, GX.CC.ZERO, GX.CC.ZERO, GX.CC.ZERO); + mb.setTevAlphaIn(tevStage, GX.CA.ZERO, GX.CA.APREV, GX.CA.A2, GX.CA.ZERO); + mb.setTevColorOp(tevStage, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + mb.setTevAlphaOp(tevStage, GX.TevOp.ADD, GX.TevBias.ZERO, GX.TevScale.SCALE_1, true, GX.Register.PREV); + tevStage += 1; mb.setTexCoordGen(GX.TexCoordID.TEXCOORD0, GX.TexGenType.MTX2x4, GX.TexGenSrc.TEX0, GX.TexGenMatrix.IDENTITY); mb.setZMode(false, GX.CompareType.ALWAYS, false); @@ -503,7 +562,7 @@ export class J2DPicture extends J2DPane { protected override resolveReferences(resolver: ResourceResolver): void { const timg = this.data.timg; - if (timg.refType > 1) { + if (timg.refType > 1) { if (timg.refType !== 2) console.warn(`Untested J2D feature refType ${timg.refType}`); this.tex = resolver(JUTResType.TIMG, timg.resName); @@ -519,14 +578,22 @@ export class J2DPicture extends J2DPane { //#region J2DScreen export class J2DScreen extends J2DPane { public color: Color; + public anchorPos: J2DAnchorPos; - constructor(data: SCRN, cache: GfxRenderCache, resolver: ResourceResolver) { + constructor(data: SCRN, cache: GfxRenderCache, resolver: ResourceResolver, anchorPos: J2DAnchorPos) { super(data, cache, null); this.color = data.color; + this.anchorPos = anchorPos; this.resolveReferences(resolver); } public override draw(renderInstManager: GfxRenderInstManager, ctx2D: J2DGrafContext, offsetX?: number, offsetY?: number): void { + switch (this.anchorPos) { + case J2DAnchorPos.Left: this.data.x = 0; break; + case J2DAnchorPos.Center: this.data.x = (ctx2D.aspectRatioCorrection - 1.0) * ctx2D.viewport[2] * 0.5; break; + case J2DAnchorPos.Right: this.data.x = (ctx2D.aspectRatioCorrection - 1.0) * ctx2D.viewport[2]; break; + } + super.draw(renderInstManager, ctx2D, offsetX, offsetY); } diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index ce51ff087..4dd77efd8 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -499,6 +499,10 @@ export class WindWakerRenderer implements Viewer.SceneGfx { const dlst = this.globals.dlst; dlst.peekZ.beginFrame(device); + // From mDoGph_Painter, + this.globals.scnPlay.currentGrafPort.setOrtho(-9.0, -21.0, 650.0, 503.0, 100000.0, -100000.0); + this.globals.scnPlay.currentGrafPort.setPort(viewerInput.backbufferWidth, viewerInput.backbufferHeight); + this.executeDrawAll(device, viewerInput); const renderInstManager = this.renderHelper.renderInstManager; @@ -543,6 +547,8 @@ export class WindWakerRenderer implements Viewer.SceneGfx { this.executeList(passRenderer, dlst.effect[EffectDrawGroup.Main]); this.executeList(passRenderer, dlst.wetherEffect); this.executeListSet(passRenderer, dlst.ui); + + this.executeListSet(passRenderer, dlst.ui2D); }); }); @@ -724,7 +730,7 @@ class d_s_play extends fopScn { public placenameIndex: Placename; public placenameState: PlacenameState; - public orthoGraf2D: J2DGrafContext; + public currentGrafPort: J2DGrafContext; public override load(globals: dGlobals, userData: any): cPhs__Status { super.load(globals, userData); @@ -736,7 +742,7 @@ class d_s_play extends fopScn { this.grassPacket = new GrassPacket(globals); this.woodPacket = new WoodPacket(globals); - this.orthoGraf2D = new J2DGrafContext(globals.modelCache.device, 0.0, 0.0, 608.0, 448.0, -1.0, 0.0); + this.currentGrafPort = new J2DGrafContext(globals.modelCache.device, 0.0, 0.0, 640.0, 480.0, -1.0, 1.0); globals.scnPlay = this; @@ -758,8 +764,6 @@ class d_s_play extends fopScn { public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: Viewer.ViewerRenderInput): void { super.draw(globals, renderInstManager, viewerInput); - this.orthoGraf2D.setupView(viewerInput.backbufferWidth, viewerInput.backbufferHeight); - // Magma/Grass/Trees/Bushes/Flowers const frameCount = viewerInput.time / 1000.0 * 30; @@ -936,9 +940,9 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { public override stageDir: string, public override name: string, public override roomList: number[], - public stbFilename: string, + public stbFilename: string, public layer: number, - public offsetPos?:vec3, + public offsetPos?: vec3, public rotY: number = 0, public startCode?: number, public eventFlags?: number, @@ -962,7 +966,7 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { globals.scnPlay.demo.remove(); // TODO: Don't render until the camera has been placed for this demo. The cuts are jarring. - + // noclip modification: This normally happens on room load. Do it here instead so that we don't waste time // loading .arcs for cutscenes that aren't going to be played const lbnk = globals.roomCtrl.status[this.roomList[0]].data.lbnk; @@ -983,15 +987,22 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { await globals.modelCache.waitForLoad(); // Most cutscenes expect the Link actor to be loaded - if(!fopAcM_searchFromName(globals, 'Link', 0, 0)) { + if (!fopAcM_searchFromName(globals, 'Link', 0, 0)) { fopAcM_create(globals.frameworkGlobals, dProcName_e.d_a_py_lk, 0, null, globals.mStayNo, null, null, 0xFF, -1); } + // From dStage_playerInit + if (this.stbFilename == 'title.stb') { + fopAcM_create(globals.frameworkGlobals, dProcName_e.d_a_title, 0, null, globals.mStayNo, null, null, 0xFF, -1); + } + // noclip modification: ensure all the actors are created before we load the cutscene - await new Promise(resolve => { (function waitForActors(){ - if (globals.frameworkGlobals.ctQueue.length === 0) return resolve(null); - setTimeout(waitForActors, 30); - })(); }); + await new Promise(resolve => { + (function waitForActors() { + if (globals.frameworkGlobals.ctQueue.length === 0) return resolve(null); + setTimeout(waitForActors, 30); + })(); + }); // @TODO: Set noclip layer visiblity based on this.layer diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index f789f63d6..180b12df7 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -1,6 +1,6 @@ import { ReadonlyMat4, ReadonlyVec3, mat4, quat, vec2, vec3 } from "gl-matrix"; -import { TransparentBlack, colorCopy, colorFromRGBA8, colorNewCopy, colorNewFromRGBA8 } from "../Color.js"; +import { TransparentBlack, White, colorCopy, colorFromRGBA8, colorNewCopy, colorNewFromRGBA8 } from "../Color.js"; import { calcANK1JointAnimationTransform } from "../Common/JSYSTEM/J3D/J3DGraphAnimator.js"; import { J3DModelData, J3DModelInstance, buildEnvMtx } from "../Common/JSYSTEM/J3D/J3DGraphBase.js"; import { JointTransformInfo, LoopMode, TRK1, TTK1 } from "../Common/JSYSTEM/J3D/J3DLoader.js"; @@ -38,6 +38,7 @@ import { fopAcIt_JudgeByID, fopAcM_create, fopAcM_prm_class, fopAc_ac_c } from " import { cPhs__Status, fGlobals, fpcPf__Register, fpcSCtRq_Request, fpc_bs__Constructor } from "./framework.js"; import { mDoExt_McaMorf, mDoExt_bckAnm, mDoExt_brkAnm, mDoExt_btkAnm, mDoExt_btpAnm, mDoExt_modelEntryDL, mDoExt_modelUpdateDL } from "./m_do_ext.js"; import { MtxPosition, MtxTrans, calc_mtx, mDoMtx_XYZrotM, mDoMtx_XrotM, mDoMtx_YrotM, mDoMtx_YrotS, mDoMtx_ZXYrotM, mDoMtx_ZrotM, mDoMtx_ZrotS, quatM } from "./m_do_mtx.js"; +import { J2DAnchorPos, J2DGrafContext, J2DPane, J2DScreen } from "../Common/JSYSTEM/J2Dv1.js"; // Framework'd actors @@ -5753,6 +5754,271 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { } } +const enum TitlePane { + MainTitle, + JapanSubtitle, + PressStart, + Nintendo, + Effect1, + Effect2, +} + +class d_a_title extends fopAc_ac_c { + public static PROCESS_NAME = dProcName_e.d_a_title; + public static arcName = 'TlogoE' // Tlogo, TlogoE, TlogoE[0-9] + + private modelShip: J3DModelInstance; + private modelSubtitle: J3DModelInstance; + private modelSubtitleShimmer: J3DModelInstance; + private bckShip = new mDoExt_bckAnm(); + private bpkShip = new mDoExt_brkAnm(); + private btkSubtitle = new mDoExt_btkAnm(); + private btkShimmer = new mDoExt_btkAnm(); + private screen: J2DScreen; + private panes: J2DPane[] = []; + + private anmFrameCounter = 0 + private delayFrameCounter = 120; + private shipFrameCounter = -50; + private blinkFrameCounter = 0; + private shimmerFrameCounter = (cM_rndF(120) + 10 + 130.0); + private enterMode = 0; + private shipOffsetX: number = 0; + + public override subload(globals: dGlobals): cPhs__Status { + const status = dComIfG_resLoad(globals, d_a_title.arcName); + if (status !== cPhs__Status.Complete) + return status; + + this.proc_init2D(globals); + this.proc_init3D(globals); + + return cPhs__Status.Next; + } + + public override execute(globals: dGlobals, deltaTimeFrames: number): void { + if (this.delayFrameCounter > 0) { + this.delayFrameCounter -= deltaTimeFrames; + + if (this.delayFrameCounter == 0) { + // TODO: mDoAud_seStart(JA_SE_TITLE_WIND); + } + } else { + this.calc_2d_alpha(deltaTimeFrames); + } + + if (this.enterMode == 2) { + this.enterMode = 3; + } else if (this.enterMode == 3) { + this.shipFrameCounter += deltaTimeFrames; + } + + this.bckShip.play(deltaTimeFrames); + this.set_mtx(); + } + + public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { + let oldViewMtx = globals.camera.viewFromWorldMatrix; + let oldProjMtx = globals.camera.clipFromViewMatrix; + + // From mDoGph_Painter(). Set up new view and ortho proj matrices. + // TODO: This should be set by the Opa2D draw list + const orthoCtx = globals.scnPlay.currentGrafPort; + mat4.fromTranslation(scratchMat4a, [orthoCtx.aspectRatioCorrection * 320, 240, 1000]); + mDoMtx_ZrotM(scratchMat4a, -0x8000); + globals.camera.viewFromWorldMatrix = scratchMat4a; + globals.camera.clipFromViewMatrix = orthoCtx.sceneParams.u_Projection; + mat4.mul(globals.camera.clipFromWorldMatrix, globals.camera.clipFromViewMatrix, globals.camera.viewFromWorldMatrix); + globals.camera.frustum.updateClipFrustum(globals.camera.clipFromWorldMatrix, globals.camera.clipSpaceNearZ); + const template = renderInstManager.pushTemplate(); + orthoCtx.setOnRenderInst(template); + + // TODO: This should be a global immediate light set by the Opa2D draw list + const light = this.modelShip.getGXLightReference(0); + light.Position = [-35000.0, 0.0, -30000.0]; + light.Direction = [0, 0, 0]; + light.Color = White; + + { + this.model_draw(globals, renderInstManager); + + renderInstManager.setCurrentList(globals.dlst.ui2D[0]); + this.screen.draw(renderInstManager, orthoCtx); + } + + // TODO: This should be set by the Opa2D draw list + globals.camera.viewFromWorldMatrix = oldViewMtx; + globals.camera.clipFromViewMatrix = oldProjMtx; + mat4.mul(globals.camera.clipFromWorldMatrix, globals.camera.clipFromViewMatrix, globals.camera.viewFromWorldMatrix); + globals.camera.frustum.updateClipFrustum(globals.camera.clipFromWorldMatrix, globals.camera.clipSpaceNearZ); + renderInstManager.popTemplate(); + } + + private proc_init2D(globals: dGlobals) { + const screenData = globals.resCtrl.getObjectResByName(ResType.Blo, d_a_title.arcName, "title_logo_e.blo"); + assert(screenData !== null); + this.screen = new J2DScreen(screenData, globals.renderer.renderCache, globals.resCtrl.getResResolver(d_a_title.arcName), J2DAnchorPos.Center); + this.screen.color = White; + + this.panes[TitlePane.MainTitle] = this.screen.search('zeld')!; + this.panes[TitlePane.JapanSubtitle] = this.screen.search('zelj')!; + this.panes[TitlePane.PressStart] = this.screen.search('pres')!; + this.panes[TitlePane.Nintendo] = this.screen.search('nint')!; + this.panes[4] = this.screen.search('eft1')!; + this.panes[5] = this.screen.search('eft2')!; + + for (let pane of this.panes) { + pane.setAlpha(0.0); + } + } + + private proc_init3D(globals: dGlobals) { + const modelDataShip = globals.resCtrl.getObjectRes(ResType.Model, d_a_title.arcName, 0xD); + this.modelShip = new J3DModelInstance(modelDataShip); + + const modelDataSub = globals.resCtrl.getObjectRes(ResType.Model, d_a_title.arcName, 0xC); + this.modelSubtitle = new J3DModelInstance(modelDataSub); + + const modelDataKirari = globals.resCtrl.getObjectRes(ResType.Model, d_a_title.arcName, 0xB); + this.modelSubtitleShimmer = new J3DModelInstance(modelDataKirari); + + const bckDataShip = globals.resCtrl.getObjectRes(ResType.Bck, d_a_title.arcName, 0x8); + this.bckShip.init(modelDataShip, bckDataShip, true, LoopMode.Repeat, 1.0, 0, -1, false); + + const bpkDataShip = globals.resCtrl.getObjectRes(ResType.Bpk, d_a_title.arcName, 0x10); + this.bpkShip.init(modelDataShip, bpkDataShip, true, LoopMode.Repeat, 1.0, 0, -1, false); + + this.bpkShip.frameCtrl.setFrame(0.0); + this.bpkShip.frameCtrl.setRate(1.0); + + const btkDataSub = globals.resCtrl.getObjectRes(ResType.Btk, d_a_title.arcName, 0x14); + this.btkSubtitle.init(modelDataSub, btkDataSub, true, LoopMode.Once, 1.0, 0, -1, false); + + const btkDataShimmer = globals.resCtrl.getObjectRes(ResType.Btk, d_a_title.arcName, 0x13); + this.btkShimmer.init(modelDataKirari, btkDataShimmer, true, LoopMode.Once, 1.0, 0, -1, false); + + this.set_mtx(); + } + + private model_draw(globals: dGlobals, renderInstManager: GfxRenderInstManager) { + + if (this.btkSubtitle.frameCtrl.getFrame() != 0.0) { + this.btkShimmer.entry(this.modelSubtitleShimmer) + mDoExt_modelUpdateDL(globals, this.modelSubtitleShimmer, renderInstManager, globals.dlst.ui); + + this.btkSubtitle.entry(this.modelSubtitle); + mDoExt_modelUpdateDL(globals, this.modelSubtitle, renderInstManager, globals.dlst.ui); + } + + if (this.bpkShip.frameCtrl.getFrame() != 0.0) { + this.bckShip.entry(this.modelShip); + this.bpkShip.entry(this.modelShip); + mDoExt_modelUpdateDL(globals, this.modelShip, renderInstManager, globals.dlst.ui); + } + } + + private set_mtx() { + vec3.set(this.modelShip.baseScale, 0.9, 0.9, 0.9); + mat4.fromTranslation(this.modelShip.modelMatrix, [this.shipOffsetX, 0, 1000]); + mDoMtx_ZXYrotM(this.modelShip.modelMatrix, [0, 0x4000, 0]); + + vec3.set(this.modelSubtitle.baseScale, 1.0, 1.0, 1.0); + vec3.set(this.modelSubtitleShimmer.baseScale, 1.0, 1.0, 1.0); + + mat4.fromTranslation(this.modelSubtitle.modelMatrix, [-57.0, -3.0, -10000.0]); + mDoMtx_ZXYrotM(this.modelSubtitle.modelMatrix, [0, -0x8000, 0]); + + mat4.fromTranslation(this.modelSubtitleShimmer.modelMatrix, [-57.0, -3.0, -10010.0]); + mDoMtx_ZXYrotM(this.modelSubtitleShimmer.modelMatrix, [0, -0x8000, 0]); + } + + private calc_2d_alpha(deltaTimeFrames: number) { + this.anmFrameCounter += deltaTimeFrames; + if (this.anmFrameCounter >= 200 && this.enterMode == 0) { + this.enterMode = 1; + } + + if (this.enterMode == 0) { + if (this.shipFrameCounter < 0) { + this.shipFrameCounter += deltaTimeFrames; + } + + // TODO: Emitters + + if (this.anmFrameCounter <= 30) { + this.panes[TitlePane.MainTitle].setAlpha(0.0); + } else if (this.anmFrameCounter <= 80) { + this.panes[TitlePane.MainTitle].setAlpha((this.anmFrameCounter - 30) / 50.0); + } else { + this.panes[TitlePane.MainTitle].setAlpha(1.0); + } + + // TODO: Viewable japanese version + this.panes[TitlePane.JapanSubtitle].setAlpha(0.0); + + // TODO: Emitters + + if (this.anmFrameCounter >= 80) { + this.btkSubtitle.play(deltaTimeFrames); + } + + if (this.anmFrameCounter <= 150) { + this.panes[TitlePane.Nintendo].setAlpha(0.0); + } else if (this.anmFrameCounter <= 170) { + this.panes[TitlePane.Nintendo].setAlpha((this.anmFrameCounter - 150) / 20.0); + } else { + this.panes[TitlePane.Nintendo].setAlpha(1.0); + } + + if (this.anmFrameCounter <= 160) { + this.panes[TitlePane.PressStart].setAlpha(0.0); + } else if (this.anmFrameCounter <= 180) { + this.panes[TitlePane.PressStart].setAlpha((this.anmFrameCounter - 160) / 20.0); + } else { + this.panes[TitlePane.PressStart].setAlpha(1.0); + } + } else { + // TODO: Emitters + + this.panes[TitlePane.MainTitle].setAlpha(1.0); + this.panes[TitlePane.JapanSubtitle].setAlpha(0.0); + + this.btkSubtitle.frameCtrl.setFrame(this.btkSubtitle.frameCtrl.endFrame); + this.panes[TitlePane.Nintendo].setAlpha(1.0); + if (this.blinkFrameCounter >= 100) { + this.blinkFrameCounter = 0; + } else { + this.blinkFrameCounter += deltaTimeFrames; + } + + if (this.blinkFrameCounter >= 50) { + this.panes[TitlePane.PressStart].setAlpha((this.blinkFrameCounter - 50) / 50.0); + } else { + this.panes[TitlePane.PressStart].setAlpha((50 - this.blinkFrameCounter) / 50.0); + } + } + + if (this.shimmerFrameCounter <= 0) { + const finished = this.btkShimmer.play(deltaTimeFrames); + if (finished) { + this.btkShimmer.frameCtrl.setFrame(0.0); + this.btkShimmer.frameCtrl.setRate(1.0); + this.shimmerFrameCounter = cM_rndF(120) + 10; + } + } else { + this.shimmerFrameCounter -= deltaTimeFrames; + } + + if (this.shipFrameCounter <= 0) { + this.shipOffsetX = (this.shipFrameCounter * this.shipFrameCounter) * -0.1; + this.bpkShip.frameCtrl.setFrame(100.0 + (this.shipFrameCounter * 2)); + } else { + this.shipOffsetX = (this.shipFrameCounter * this.shipFrameCounter) * 0.1; + this.bpkShip.frameCtrl.setFrame(100.0 - (this.shipFrameCounter * 2)); + } + } +} + interface constructor extends fpc_bs__Constructor { PROCESS_NAME: dProcName_e; } @@ -5787,4 +6053,5 @@ export function d_a__RegisterConstructors(globals: fGlobals): void { R(d_a_npc_ls1); R(d_a_npc_zl1); R(d_a_py_lk); + R(d_a_title); } diff --git a/src/ZeldaWindWaker/d_camera.ts b/src/ZeldaWindWaker/d_camera.ts index 996eb1857..6bee01c6f 100644 --- a/src/ZeldaWindWaker/d_camera.ts +++ b/src/ZeldaWindWaker/d_camera.ts @@ -125,7 +125,7 @@ export class dCamera_c extends leafdraw_class { if (demoCam.flags & EDemoCamFlags.HasNearZ) { this.near = demoCam.projNear; } if (demoCam.flags & EDemoCamFlags.HasFarZ) { this.far = demoCam.projFar; } - this.cameraMode = CameraMode.Cinematic; + this.cameraMode = (globals.scnPlay.demo.getName() == 'title') ? CameraMode.Default : CameraMode.Cinematic; globals.sceneContext.inputManager.isMouseEnabled = false; } else { this.cameraMode = CameraMode.Default; diff --git a/src/ZeldaWindWaker/d_drawlist.ts b/src/ZeldaWindWaker/d_drawlist.ts index 6aabf1530..1f625413f 100644 --- a/src/ZeldaWindWaker/d_drawlist.ts +++ b/src/ZeldaWindWaker/d_drawlist.ts @@ -198,6 +198,13 @@ export class dDlst_list_c { new GfxRenderInstList(gfxRenderInstCompareNone, GfxRenderInstExecutionOrder.Backwards), new GfxRenderInstList(gfxRenderInstCompareNone, GfxRenderInstExecutionOrder.Backwards), ]; + + // These correspond to 2DOpa and 2DXlu + public ui2D: dDlst_list_Set = [ + new GfxRenderInstList(gfxRenderInstCompareNone, GfxRenderInstExecutionOrder.Forwards), + new GfxRenderInstList(gfxRenderInstCompareNone, GfxRenderInstExecutionOrder.Forwards) + ]; + public alphaModel = new GfxRenderInstList(gfxRenderInstCompareNone, GfxRenderInstExecutionOrder.Forwards); public peekZ = new PeekZManager(128); public alphaModel0: dDlst_alphaModel_c; diff --git a/src/ZeldaWindWaker/d_place_name.ts b/src/ZeldaWindWaker/d_place_name.ts index d323cfc8e..ad79f4ccf 100644 --- a/src/ZeldaWindWaker/d_place_name.ts +++ b/src/ZeldaWindWaker/d_place_name.ts @@ -1,4 +1,4 @@ -import { J2DPicture, J2DScreen } from "../Common/JSYSTEM/J2Dv1.js"; +import { J2DAnchorPos, J2DPicture, J2DScreen } from "../Common/JSYSTEM/J2Dv1.js"; import { BTI, BTIData } from "../Common/JSYSTEM/JUTTexture.js"; import { GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; import { assertExists } from "../util.js"; @@ -89,7 +89,7 @@ export class d_place_name extends msg_class { return status; const screenData = globals.resCtrl.getObjectRes(ResType.Blo, `PName`, 0x04); - this.screen = new J2DScreen(screenData, globals.renderer.renderCache, globals.resCtrl.getResResolver('PName')); + this.screen = new J2DScreen(screenData, globals.renderer.renderCache, globals.resCtrl.getResResolver('PName'), J2DAnchorPos.Left); this.screen.search('blc1')!.hide(); this.screen.search('blc2')!.hide(); @@ -114,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, globals.scnPlay.orthoGraf2D); + this.screen.draw(renderInstManager, globals.scnPlay.currentGrafPort); } public override execute(globals: dGlobals, deltaTimeFrames: number): void { diff --git a/src/ZeldaWindWaker/d_procname.ts b/src/ZeldaWindWaker/d_procname.ts index b42b80846..5ea34b9cb 100644 --- a/src/ZeldaWindWaker/d_procname.ts +++ b/src/ZeldaWindWaker/d_procname.ts @@ -29,6 +29,7 @@ export const enum dProcName_e { d_a_vrbox = 0x01BA, d_a_vrbox2 = 0x01BB, d_a_bg = 0x01BC, + d_a_title = 0x01C1, d_a_swhit0 = 0x01C9, d_camera = 0x01E2, d_kyeff = 0x01E4,