From c153a82e9cb594dd5de28c806cb2bb150ef3f156 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 31 Oct 2024 19:45:31 -0600 Subject: [PATCH 001/102] Add STB as a loadable ResType --- src/ZeldaWindWaker/d_resorce.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/d_resorce.ts b/src/ZeldaWindWaker/d_resorce.ts index 89b74ae0f..d34a29b05 100644 --- a/src/ZeldaWindWaker/d_resorce.ts +++ b/src/ZeldaWindWaker/d_resorce.ts @@ -43,7 +43,7 @@ function parseDZSHeaders(buffer: ArrayBufferSlice): DZS { } export const enum ResType { - Model, Bmt, Bck, Bpk, Brk, Btp, Btk, Bti, Dzb, Dzs, Bva, Raw, + Model, Bmt, Bck, Bpk, Brk, Btp, Btk, Bti, Dzb, Dzs, Bva, Stb, Raw, } export type ResAssetType = @@ -58,6 +58,7 @@ export type ResAssetType = T extends ResType.Dzb ? cBgD_t : T extends ResType.Dzs ? DZS : T extends ResType.Bva ? VAF1 : + T extends ResType.Stb ? NamedArrayBufferSlice : T extends ResType.Raw ? NamedArrayBufferSlice : unknown; @@ -149,7 +150,7 @@ export class dRes_info_c { resEntry.res = BVA.parse(file.buffer) as ResAssetType; } else if (resType === ResType.Dzs) { resEntry.res = parseDZSHeaders(file.buffer) as ResAssetType; - } else if (resType === ResType.Raw) { + } else if (resType === ResType.Raw || resType === ResType.Stb) { resEntry.res = file.buffer as ResAssetType; } else { throw "whoops"; From 999971e4b87507798fcdcd3f42a9b907aa67a907 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 31 Oct 2024 19:46:20 -0600 Subject: [PATCH 002/102] Workable starting point for JStudio, STB parsing, and ZWW Demo handling Nothing is fully working yet, but all of the main classes are in place --- src/Common/JSYSTEM/JStudio.ts | 753 ++++++++++++++++++++++++++++++++++ src/ZeldaWindWaker/d_demo.ts | 208 ++++++++++ 2 files changed, 961 insertions(+) create mode 100644 src/Common/JSYSTEM/JStudio.ts create mode 100644 src/ZeldaWindWaker/d_demo.ts diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts new file mode 100644 index 000000000..b01a58410 --- /dev/null +++ b/src/Common/JSYSTEM/JStudio.ts @@ -0,0 +1,753 @@ +// Nintendo's cutscene framework. Seems very over-engineered. + +import { ReadonlyVec3, vec3 } from "gl-matrix"; +import ArrayBufferSlice from "../../ArrayBufferSlice"; +import { align, assert, nArray, readString } from "../../util.js"; +import { JSystemFileReaderHelper } from "./J3D/J3DLoader.js"; + +const scratchVec3a = vec3.create(); +const scratchVec3b = vec3.create(); +const scratchVec3c = vec3.create(); +const scratchVec3d = vec3.create(); + +//---------------------------------------------------------------------------------------------------------------------- +// Stage Objects +// These are created an managed by the game. Each Stage Object has a corresponding STB Object, connected by an Adaptor. +// The STB objects are manipulated by Sequences from the STB file each frame, and update the Stage Object via Adaptor. +//---------------------------------------------------------------------------------------------------------------------- +export namespace JStage { + export enum TEObject { + ACTOR_UNK = 0x0, + UNK1 = 0x1, + ACTOR = 0x2, + CAMERA = 0x3, + AMBIENT = 0x4, + LIGHT = 0x5, + FOG = 0x6, + }; + + export class TObject { + + } +} + +//---------------------------------------------------------------------------------------------------------------------- +// System +// The main interface between a game and JStudio. Provides a method of finding or creating objects that will then be +// modified by a cutscene. Each game should override JSGFindObject() to supply or create objects for manipulation. +//---------------------------------------------------------------------------------------------------------------------- +export interface TSystem { + JSGFindObject(objId: string, objType: JStage.TEObject): JStage.TObject | undefined; +} + +//---------------------------------------------------------------------------------------------------------------------- +// TVariableValue +// Manages a single float, which will be updated each frame. This float can be updated using a variety of operations: +// - Immediate(x): Set to single value. On Update(y), set the value to a single number. +// - Time(x): Increase over time. On Update(y), set the value to x * y * mAge. +// - FuncVal(x): Set to the output of a functor. See FVB for details. +// +// Normally, after update() the value can be retrieved from mValue(). Alternatively, if setOutput() is called that +// functor will be called during update(). +//---------------------------------------------------------------------------------------------------------------------- +class TVariableValue { + public mValue: number; + public mAge: number; // In frames + + // struct TOutput { + // virtual void operator()(f32, JStudio::TAdaptor*) const = 0; + // virtual ~TOutput() = 0; + // }; // Size: 0x04 + + // struct TOutput_none_ : public TOutput { + // ~TOutput_none_(); + // void operator()(f32, JStudio::TAdaptor*) const; + // }; + + // void update(f64, JStudio::TAdaptor*); + // static void update_immediate_(JStudio::TVariableValue*, f64); + // static void update_time_(JStudio::TVariableValue*, f64); + // static void update_functionValue_(JStudio::TVariableValue*, f64); + // TVariableValue() + // : field_0x4(0) + // , field_0x8(NULL) + // , pOutput_(&soOutput_none_) + // { + // } + + // void setValue_immediate(f32 value) { + // field_0x8 = &update_immediate_; + // field_0x4 = 0; + // field_0xc.val = value; + // } + + // void setValue_none() { + // field_0x8 = NULL; + // } + + // void setValue_time(f32 value) { + // field_0x8 = &update_time_; + // field_0x4 = 0; + // field_0xc.val = value; + // } + + // void setValue_functionValue(TFunctionValue* value) { + // field_0x8 = &update_functionValue_; + // field_0x4 = 0; + // field_0xc.fv = value; + // } + + // f32 getValue() const { return mValue; } + + // template + // T getValue_clamp() const { + // f32 val = mValue; + // if (val <= std::numeric_limits::min()) { + // return std::numeric_limits::min(); + // } else if (val >= std::numeric_limits::max()) { + // return std::numeric_limits::max(); + // } + // return val; + // } + // u8 getValue_uint8() const { return getValue_clamp(); } + + // void forward(u32 param_0) { + // if (std::numeric_limits::max() - field_0x4 <= param_0) { + // field_0x4 = std::numeric_limits::max(); + // } else { + // field_0x4 += param_0; + // } + // } + + // inline void setOutput(const TOutput* output) { + // pOutput_ = (output != NULL ? (TOutput*)output : (TOutput*)&soOutput_none_); + // } + + // static TOutput_none_ soOutput_none_; + + // /* 0x00 */ f32 mValue; + // /* 0x04 */ u32 field_0x4; + // /* 0x08 */ void (*field_0x8)(TVariableValue*, double); + // /* 0x0C */ union { + // TFunctionValue* fv; + // f32 val; + // } field_0xc; + // /* 0x10 */ TOutput* pOutput_; +} + +//---------------------------------------------------------------------------------------------------------------------- +// TAdaptor +// Connects the STBObject to a Game Object. Manages tracks of TVariableValues, updates their values on the Game object. +//---------------------------------------------------------------------------------------------------------------------- +const enum TEOperationData { + NONE = 0, + VOID = 1, // Disable updates for this track. + IMMEDIATE = 2, // Set the value on this track with an immediate value. + TIME = 3, // Ramp the track's value based by a given velocity, starting at 0. + FUNCVALUE_NAME = 0x10, // Unused? + FUNCVALUE_INDEX = 0x12 // Make the track use a function value object for the value. +}; + +abstract class TAdaptor { + constructor( + protected mCount: number, + protected mVariableValues = nArray(mCount, i => new TVariableValue() ), + ) { } + + abstract adaptor_do_prepare(): void; + abstract adaptor_do_begin(): void; + abstract adaptor_do_end(): void; + abstract adaptor_do_update(frameCount: number): void; + + adaptor_setVariableValue(obj: STBObject, trackIdx: number, dataOp: TEOperationData, data: number, dataSize: number) { + const varval = this.mVariableValues[trackIdx]; + const control = obj.mControl; + + switch (dataOp) { + // case TEOperationData.VOID: varval.setValue_none(); + // case TEOperationData.IMMEDIATE: varval.setValue_immediate(data); + // case TEOperationData.TIME: varval.setValue_time(data); + // case TEOperationData.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValue(data, dataSize)) //@TODO: Not support ATM because we're passing in data as a number instead of DataView (this is a string) + // case TEOperationData.FUNCVALUE_INDEX: varval.setValue_functionValue(control.getFunctionValue_index(data)); + default: + console.debug('Unsupported dataOp: ', dataOp); + return; + } + } + +} + +// struct TSetVariableValue_immediate { +// inline TSetVariableValue_immediate(u32 p1, f32 p2) +// : field_0x0(p1) +// , field_0x4(p2) +// { +// } + +// u32 field_0x0; +// f32 field_0x4; +// }; +// typedef void (*setVarFunc)(JStudio::TAdaptor*, JStudio::TObject*, u32, void const*, u32); +// virtual ~TAdaptor() = 0; +// virtual void adaptor_do_prepare(const JStudio::TObject*); +// virtual void adaptor_do_begin(const JStudio::TObject*); +// virtual void adaptor_do_end(const JStudio::TObject*); +// virtual void adaptor_do_update(const JStudio::TObject*, u32); +// virtual void adaptor_do_data(const JStudio::TObject*, void const*, u32, void const*, u32); + +// void adaptor_setVariableValue_n(JStudio::TObject*, u32 const*, u32, JStudio::data::TEOperationData, void const*, u32); +// void adaptor_setVariableValue_immediate(JStudio::TAdaptor::TSetVariableValue_immediate const*); +// void adaptor_setVariableValue_Vec(u32 const*, Vec const&); +// void adaptor_getVariableValue_Vec(Vec*, u32 const*) const; +// void adaptor_setVariableValue_GXColor(u32 const*, GXColor const&); +// void adaptor_getVariableValue_GXColor(GXColor*, u32 const*) const; +// void adaptor_updateVariableValue(JStudio::TObject*, u32); + + +// TVariableValue* adaptor_referVariableValue(u32 param_0) { +// return &mVariableValues[param_0]; +// } + +// void adaptor_setVariableValue_immediate(u32 param_0, f32 param_1) { +// adaptor_referVariableValue(param_0)->setValue_immediate(param_1); +// } + +// const TVariableValue* adaptor_getVariableValue(u32 param_0) const { +// return &mVariableValues[param_0]; +// } + +//---------------------------------------------------------------------------------------------------------------------- +// STB Objects +// Created at parse time, and controlled by Sequences from the STB file. Connects to Game objects via an Adaptor. +// Each frame the STB data is marched (see do_paragraph) to update one or more properties of the Object via its Adaptor. +//---------------------------------------------------------------------------------------------------------------------- +abstract class STBObject { + public mControl: TControl; + public mAdaptor: TAdaptor; + + private mId: string; + private mType: string; + private mFlags: number; + private mIsSequence: boolean = false; + private mSuspendFrames: number = 0; + private mData: Reader; + private pSequence: number; + private pSequence_next: number; + private mWait: number = 0; + private mStatus: TEStatus = TEStatus.STILL; + + constructor(control: TControl, blockObj: TBlockObject, adaptor: TAdaptor) { + this.mControl = control; + this.mAdaptor = adaptor; + + this.mId = blockObj.id; + this.mType = blockObj.type; + this.mFlags = blockObj.flag; + this.mData = blockObj.data; + this.pSequence = 0; + this.pSequence_next = 0xC + align(blockObj.id.length + 1, 4); + } + + // These are intended to be overridden by subclasses + abstract do_paragraph(view: DataView, dataSize: number, dataOffset: number, param: number): void; + do_begin() { } + do_end() { } + + // Done updating this frame. Compute our variable data (i.e. interpolate) and forward to the game object. + do_wait(flags: number) { + // adaptor->adaptor_updateVariableValue(this, param_0); // @TODO: + this.mAdaptor.adaptor_do_update(flags); + } + // do_data(void const*, u32, void const*, u32) {} + + isSuspended(): boolean { + return this.mSuspendFrames > 0; + } + + forward(frameCount: number) { + let hasWaited = false; + while (true) { + // Top bit of mFlags makes this object immediately inactive, restarting any existing sequence + if (this.mFlags & 0x8000) { + if (this.mStatus != TEStatus.INACTIVE) { + this.mStatus = TEStatus.INACTIVE; + if (this.mIsSequence) { + this.do_end(); + } + } + return 1; + } + + if (this.mStatus == TEStatus.INACTIVE) { + assert(this.mIsSequence); + this.do_begin(); + this.mStatus = TEStatus.WAIT; + } + + if ((this.mControl && this.mControl.mIsSuspended) || this.isSuspended()) { + if (this.mIsSequence) { + assert((this.mStatus == TEStatus.WAIT) || (this.mStatus == TEStatus.SUSPEND)); + this.mStatus = TEStatus.SUSPEND; + this.do_wait(frameCount); + } + return 1; + } + + while (true) { + this.pSequence = this.pSequence_next; + + // If there is nothing left in the sequence, end it + if (!this.pSequence) { + if (this.mIsSequence) { + assert(this.mStatus != TEStatus.STILL); + if (!hasWaited) { + this.do_wait(0); + } + this.mIsSequence = false; + this.mStatus = TEStatus.END; + this.do_end(); + } + return 0; + } + + // If we're not currently running a sequence, start it + if (!this.mIsSequence) { + assert(this.mStatus == TEStatus.STILL); + this.mIsSequence = true; + this.do_begin(); + } + + this.mStatus = TEStatus.WAIT; + + if (this.mWait == 0) { + this.process_sequence(); + if (this.mWait == 0) { + break; + } + } + assert(this.mWait > 0); + + hasWaited = true; + if (frameCount >= this.mWait) { + const wait = this.mWait; + frameCount -= this.mWait; + this.mWait = 0; + this.do_wait(wait); + } else { + this.mWait -= frameCount; + this.do_wait(frameCount); + return 1; + } + } + } + } + + private process_sequence() { + const view = this.mData.view; + let byteIdx = this.pSequence; + + let cmd = view.getUint8(byteIdx); + let param = view.getUint32(byteIdx) & 0xFFFFFF; + + let next = 0; + if (cmd != 0) { + if (cmd <= 0x7f) { + next = byteIdx + 4; + } else { + next = byteIdx + 4 + param; + } + } + + this.pSequence_next = next; + + switch (cmd) { + case ESequenceCmd.End: + break; + + case ESequenceCmd.Wait: + this.mWait = param; + break; + + case ESequenceCmd.Paragraph: + byteIdx += 4; + while (byteIdx < this.pSequence_next) { + const para = parseParagraph(view, byteIdx); + if (para.dataSize <= 0xff) { + console.debug('Unsupported paragraph feature: ', para.params); + // process_paragraph_reserved_(para.type, para.content, para.param); + } else { + this.do_paragraph(view, para.dataSize, para.dataOffset, para.params); + } + byteIdx = para.nextOffset; + } + + break; + + default: + console.debug('Unhandled sequence cmd: ', cmd); + byteIdx += 4; + break; + } + } +} + +//---------------------------------------------------------------------------------------------------------------------- +// Camera +//---------------------------------------------------------------------------------------------------------------------- +const enum Camera_Cmd { + SET_EYE_X_POS = 0x0015, + SET_EYE_Y_POS = 0x0016, + SET_EYE_Z_POS = 0x0017, + SET_EYE_POS = 0x0018, + SET_TARGET_X_POS = 0x0019, + SET_TARGET_Y_POS = 0x001A, + SET_TARGET_Z_POS = 0x001B, + SET_TARGET_POS = 0x001C, + SET_UNK_0026 = 0x0026, + SET_UNK_0027 = 0x0027, + SET_DIST_NEAR = 0x0028, + SET_DIST_FAR = 0x0029, + SET_DIST_NEAR_FAR = 0x002A, +} + +const enum Camera_Track { + EYE_X_POS = 0x00, + EYE_Y_POS = 0x01, + EYE_Z_POS = 0x02, + TARGET_X_POS = 0x03, + TARGET_Y_POS = 0x04, + TARGET_Z_POS = 0x05, + UNK_0026 = 0x06, + UNK_0027 = 0x07, + DIST_NEAR = 0x08, + DIST_FAR = 0x09, + CAMERA_TRACKS_MAX = 0x0A, +} + +export abstract class TCamera extends JStage.TObject { + JSGFGetType() { return JStage.TEObject.CAMERA; } + // JSGGetProjectionType() { return true; } + // JSGSetProjectionType(JStage:: TECameraProjection) { } + JSGGetProjectionNear() { return 0.0; } + JSGSetProjectionNear(near: number) { } + JSGGetProjectionFar() { return Number.MAX_VALUE; } + JSGSetProjectionFar(far: number) { } + JSGGetProjectionFovy() { return 0.0 }; + JSGSetProjectionFovy(fovy: number) { }; + JSGGetProjectionAspect() { return 0.0 }; + JSGSetProjectionAspect(aspect: number) { }; + JSGGetProjectionField() { return 0.0 }; + JSGSetProjectionField(field: number) { }; + // JSGGetViewType() { return true; }; + // JSGSetViewType(JStage:: TECameraView) { } + JSGGetViewPosition(dst: vec3) { vec3.zero(dst); } + JSGSetViewPosition(v: ReadonlyVec3) { } + JSGGetViewUpVector(dst: vec3) { vec3.zero(dst); } + JSGSetViewUpVector(v: ReadonlyVec3) { } + JSGGetViewTargetPosition(dst: vec3) { vec3.zero(dst); } + JSGSetViewTargetPosition(v: ReadonlyVec3) { } + JSGGetViewRoll() { return 0.0 }; + JSGSetViewRoll(roll: number) { }; +} + +class TCameraAdaptor extends TAdaptor { + constructor( + private mStageCam: TCamera + ) { super(11); } + + adaptor_do_prepare(): void { + + } + + adaptor_do_begin(): void { + + } + + adaptor_do_end(): void { + + } + + adaptor_do_update(frameCount: number): void { + + } +} + +class TCameraObject extends STBObject { + constructor( + control: TControl, + blockObj: TBlockObject, + stageObj: JStage.TObject, + ) { super(control, blockObj, new TCameraAdaptor(stageObj as TCamera)) } + + override do_paragraph(view: DataView, dataSize: number, dataOffset: number, param: number): void { + const updateType = param & 0x1F; + const cmdType = param >> 5; + + const data = updateType == TEOperationData.FUNCVALUE_INDEX ? + view.getUint32(dataOffset) : view.getFloat32(dataOffset); + + // switch (cmdType) { + // // Eye position + // case Camera_Cmd.SET_EYE_X_POS: + // mTracks[Camera_Track.EYE_X_POS].AddKey(data, curFrame, updateType); + // break; + // case Camera_Cmd.SET_EYE_Y_POS: + // mTracks[Camera_Track.EYE_Y_POS].AddKey(data, curFrame, updateType); + // break; + // case Camera_Cmd.SET_EYE_Z_POS: + // mTracks[Camera_Track.EYE_Z_POS].AddKey(data, curFrame, updateType); + // break; + // case Camera_Cmd.SET_EYE_POS: + // mTracks[Camera_Track.EYE_X_POS].AddKey(data, curFrame, updateType); + // mTracks[Camera_Track.EYE_Y_POS].AddKey(data, curFrame, updateType); + // mTracks[Camera_Track.EYE_Z_POS].AddKey(data, curFrame, updateType); + // break; + + // // Target position + // case Camera_Cmd.SET_TARGET_X_POS: + // mTracks[Camera_Track.TARGET_X_POS].AddKey(data, curFrame, updateType); + // break; + // case Camera_Cmd.Camera_Cmd.SET_TARGET_Y_POS: + // mTracks[Camera_Track.TARGET_Y_POS].AddKey(data, curFrame, updateType); + // break; + // case Camera_Cmd.SET_TARGET_Z_POS: + // mTracks[Camera_Track.TARGET_Z_POS].AddKey(data, curFrame, updateType); + // break; + // case Camera_Cmd.SET_TARGET_POS: + // mTracks[Camera_Track.TARGET_X_POS].AddKey(data, curFrame, updateType); + // mTracks[Camera_Track.TARGET_Y_POS].AddKey(data, curFrame, updateType); + // mTracks[Camera_Track.TARGET_Z_POS].AddKey(data, curFrame, updateType); + // break; + + // // ? + // case Camera_Cmd.SET_UNK_0026: + // mTracks[Camera_Track.UNK_0026].AddKey(data, curFrame, updateType); + // break; + // case Camera_Cmd.SET_UNK_0027: + // mTracks[Camera_Track.UNK_0027].AddKey(data, curFrame, updateType); + // break; + + // // Near/far distance + // case Camera_Cmd.SET_DIST_NEAR: + // mTracks[Camera_Track.DIST_NEAR].AddKey(data, curFrame, updateType); + // break; + // case Camera_Cmd.SET_DIST_FAR: + // mTracks[Camera_Track.DIST_FAR].AddKey(data, curFrame, updateType); + // break; + // case Camera_Cmd.SET_DIST_NEAR_FAR: + // mTracks[Camera_Track.DIST_NEAR].AddKey(data, curFrame, updateType); + // mTracks[Camera_Track.DIST_FAR].AddKey(data, curFrame, updateType); + // break; + + // default: + // console.debug('Unsupported TCamera update: ', cmdType, ' ', updateType); + // break; + // } + } +} + +//---------------------------------------------------------------------------------------------------------------------- +// STB Parsing +//---------------------------------------------------------------------------------------------------------------------- +enum ESequenceCmd { + End = 0, + SetFlag = 1, + Wait = 2, + Skip = 3, + Suspend = 4, + Paragraph = 0x80, +} + +enum TEStatus { + STILL = 0, + END = 1 << 0, + WAIT = 1 << 1, + SUSPEND = 1 << 2, + INACTIVE = 1 << 3, +} + +// The runtime object that stores interpolated data each frame. Written to by a TAdapter. By default, the base object +// is empty and can get or set no data. JSystem expects each game to provide implementations of the TObject interface +// which are provided by a JStage:TSystem override +export abstract class TBlockObject { + /* 0x0 */ size: number; + /* 0x4 */ type: string; // char[4] JMSG, JSND, JACT, ... + /* 0x8 */ flag: number; + /* 0xA id_size: number* + /* 0xC */ id: string; + /* 0xC + align(id_size, 4) */ data: Reader; +} + +// The "loader" which manages a key-value store of parameters for each object. Each frame these value are interpolated +// and written to a corresponding TObject, where they can be read by the game. +class TAdapter { + +} + +class TParagraph { + dataSize: number; + params: number; + dataOffset: number; + nextOffset: number; +} + +function parseParagraph(view: DataView, byteIdx: number): TParagraph { + // The top bit of the paragraph determines if the type and size are 16 bit (if set), or 32 (if not set) + let dataSize = view.getUint16(byteIdx); + let params; + let offset; + + if ((dataSize & 0x8000) == 0) { + // 16 bit data + params = view.getUint16(byteIdx + 2); + offset = 4; + } else { + // 32 bit data + dataSize = view.getUint32(byteIdx + 0) & ~0x80000000; + params = view.getUint32(byteIdx + 4); + offset = 8; + } + + if (dataSize == 0) { + return { dataSize, params, dataOffset: 0, nextOffset: byteIdx + offset }; + } else { + return { dataSize, params, dataOffset: byteIdx + offset, nextOffset: byteIdx + offset + align(dataSize, 4) }; + } +} + +class Reader { + buffer: ArrayBufferSlice; + view: DataView; + offset: number; + + constructor(buffer: ArrayBufferSlice, offset: number) { + this.buffer = buffer.subarray(offset); + this.view = this.buffer.createDataView(); + this.offset = 0; + } +} + +export class TControl { + private mSystem: TSystem; + private mObjects: STBObject[] = []; + public mIsSuspended = false; + + constructor(system: TSystem) { + this.mSystem = system; + } + + public forward(flags: number): number { + for (let obj of this.mObjects) { + const res = obj.forward(flags); + // @TODO: Set status + } + return 1; + } + + // Really this is a Factory method + public createObject(blockObj: TBlockObject): STBObject | undefined { + let objConstructor; + let objType: JStage.TEObject; + switch (blockObj.type) { + case 'JCMR': objConstructor = TCameraObject; objType = JStage.TEObject.CAMERA; break; + case 'JACT': + case 'JABL': + case 'JLIT': + case 'JFOG': + default: + return undefined; + } + + const stageObj = this.mSystem.JSGFindObject(blockObj.id, objType); + if (!stageObj) { + return undefined; + } + + const obj = new objConstructor(this, blockObj, stageObj); + this.mObjects.push(obj); + return obj; + } +} + +export class TParse { + constructor( + private mControl: TControl + ) { } + + private parseFVB(data: ArrayBufferSlice) { + + } + + // Parse an entire scene's worth of object sequences at once + private parseBlockObject(file: Reader, flags: number) { + const blockObj: TBlockObject = { + size: file.view.getUint32(0), + type: readString(file.buffer, file.offset + 4, 4), + flag: file.view.getUint16(8), + id: readString(file.buffer, 12, file.view.getUint16(10)), + data: file + } + + const blockTypeNum = file.view.getInt32(4); + if (blockTypeNum == -1) { + console.debug('Unhandled STB block type: -1'); + } + + if (flags & 0x10) { + console.debug('Unhandled flag during parseBlockObject: 0x10'); + return true; + } + + if (flags & 0x20) { + console.debug('Unhandled flag during parseBlockObject: 0x20'); + return true; + } + + // Create the object, using overloaded functions from the game + const obj = this.mControl.createObject(blockObj); + if (!obj) { + if (flags & 0x40) { + console.debug('Unhandled flag during parseBlockObject: 0x40'); + return true; + } + console.debug('Unhandled STB block type: ', blockObj.type); + return false; + } + + return true; + } + + // Parse all the TBlocks from an STB file. Blocks can either contain STBObjects, or FVB (function value) data. + // All objects will be created, they can be modified by using TControl. + public parse(data: ArrayBufferSlice, flags: number) { + const file = new JSystemFileReaderHelper(data); + + // Parse the THeader + let byteOrder = file.view.getUint16(0x04); + let version = file.view.getUint16(0x06); + let targetVersion = file.view.getUint16(0x1E); + assert(file.magic === 'STB'); + assert(version >= 1 && version <= 3); // As of Wind Waker, only versions 1-3 supported. TP seems to support <7, but untested. + assert(targetVersion >= 2 && targetVersion <= 3); // As of Wind Waker, only version 2-3 is supported + assert(byteOrder == 0xFEFF); + + let byteIdx = file.offs; + let blockCount = file.view.getUint32(12); + + for (let i = 0; i < blockCount; i++) { + const blockSize = file.view.getUint32(byteIdx + 0); + const blockType = readString(file.buffer, byteIdx + 4, 4); + const blockData = data.slice(byteIdx + 8, byteIdx + blockSize); + + if (blockType == 'JFVB') { + this.parseFVB(blockData) + } else { + this.parseBlockObject(new Reader(file.buffer, byteIdx), flags); + } + + byteIdx += blockSize; + } + + return true; + } +} \ No newline at end of file diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts new file mode 100644 index 000000000..bca399303 --- /dev/null +++ b/src/ZeldaWindWaker/d_demo.ts @@ -0,0 +1,208 @@ +import { ReadonlyVec3, vec3 } from "gl-matrix"; +import ArrayBufferSlice from "../ArrayBufferSlice"; +import { TParse, JStage, TSystem, TControl, TCamera } from "../Common/JSYSTEM/JStudio.js"; +import { getMatrixAxisY } from "../MathHelpers.js"; +import { dGlobals } from "./Main"; + +class dDemo_camera_c extends TCamera { + mFlags: number; + mProjNear: number; + mProjFar: number; + mFovy: number; + mAspect: number; + mViewPosition: vec3; + mUpVector: vec3; + mTargetPosition: vec3; + mRoll: number; + + globals: dGlobals; + + override JSGGetProjectionNear(): number { + const camera = this.globals.camera; + if (!camera) + return 0.0; + return camera.near; + } + + override JSGSetProjectionNear(v: number) { + this.mProjNear = v; + this.mFlags |= 0x01; + } + + override JSGGetProjectionFar(): number { + const camera = this.globals.camera; + if (!camera) + return 1.0; + return camera.far; + } + + + override JSGSetProjectionFar(v: number): void { + this.mProjFar = v; + this.mFlags |= 0x02; + } + + + override JSGGetProjectionFovy(): number { + const camera = this.globals.camera; + if (!camera) + return 60.0; + return camera.fovY; + } + + + override JSGSetProjectionFovy(v: number): void { + this.mFovy = v; + this.mFlags |= 0x04; + } + + + override JSGGetProjectionAspect() { + const camera = this.globals.camera; + if (!camera) + return 1.3333; + return camera.aspect; + } + + + override JSGSetProjectionAspect(v: number) { + this.mAspect = v; + this.mFlags |= 0x08; + } + + + override JSGGetViewPosition(dst: vec3) { + vec3.copy(dst, this.globals.cameraPosition); + } + + + override JSGSetViewPosition(v: ReadonlyVec3) { + vec3.copy(this.mViewPosition, v); + this.mFlags |= 0x10; + } + + + override JSGGetViewUpVector(dst: vec3) { + const camera = this.globals.camera; + if (!camera) + vec3.set(dst, 0, 1, 0); + getMatrixAxisY(dst, camera.viewMatrix); // @TODO: Double check that this is correct + } + + + override JSGSetViewUpVector(v: ReadonlyVec3) { + vec3.copy(this.mUpVector, v); + this.mFlags |= 0x20; + } + + + override JSGGetViewTargetPosition(dst: vec3) { + const camera = this.globals.camera; + if (!camera) + vec3.set(dst, 0, 0, 0); + console.debug('JSGGetViewTargetPosition called. This is not yet working'); + vec3.set(dst, 0, 0, 0); + } + + + override JSGSetViewTargetPosition(v: ReadonlyVec3) { + vec3.copy(this.mTargetPosition, v); + this.mFlags |= 0x40; + } + + + override JSGGetViewRoll() { + const camera = this.globals.camera; + if (!camera) + return 0.0; + return 0.0; // @TODO + } + + + override JSGSetViewRoll(v: number) { + this.mRoll = v; + this.mFlags |= 0x80; + } +} + +class dDemo_system_c implements TSystem { + private mpActiveCamera: dDemo_camera_c; + // private mpActors: dDemo_actor_c[]; + // private mpAmbient: dDemo_ambient_c; + // private mpLight: dDemo_light_c[]; + // private mpFog: dDemo_fog_c; + + public JSGFindObject(objId: string, objType: JStage.TEObject): JStage.TObject | undefined { + switch (objType) { + case JStage.TEObject.CAMERA: + if (this.mpActiveCamera) return this.mpActiveCamera; + else return new dDemo_camera_c(); + + case JStage.TEObject.ACTOR: + case JStage.TEObject.ACTOR_UNK: + case JStage.TEObject.AMBIENT: + case JStage.TEObject.LIGHT: + case JStage.TEObject.FOG: + default: + console.debug('[JSGFindObject] Unhandled type: ', objType); + return undefined; + } + } +} + +export class dDemo_manager_c { + private mFrame: number; + private mFrameNoMsg: number; + private mMode: number; + private mCurFile: ArrayBufferSlice; + + private mParser: TParse; + private mSystem = new dDemo_system_c(); + private mControl: TControl = new TControl(this.mSystem); + + public create(data: ArrayBufferSlice, originPos: vec3, rotY: number): boolean { + this.mParser = new TParse(this.mControl); + + if(!this.mParser.parse(data, 0)) { + console.error('Failed to parse demo data'); + return false; + } + + this.mControl.forward(0); + // if (originPos == NULL) { + // mControl->transform_enable(false); + // } else { + // mControl->transform_enable(true); + // mControl->transform_setOrigin(*originPos, rotY); + // } + + this.mFrame = 0; + this.mFrameNoMsg = 0; + this.mCurFile = data; + this.mMode = 1; + + return true; + } + + public remove() { + // mControl->destroyObject_all(); + // mDemoObj.remove(); + // this.mCurFile = NULL; + // this.mMode = 0; + } + + public update(): boolean { + // if (!this.mCurFile) { + // return false; + // } + // if (this.mControl->forward(1)) { + // this.mFrame++; + // if (!this.mControl->isSuspended()) { + // this.mFrameNoMsg++; + // } + // } else { + // this.mMode = 2; + // } + return true; + } +} \ No newline at end of file From af5aa9023ca1abc8dc977646c370144f563fc591 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 1 Nov 2024 09:38:58 -0600 Subject: [PATCH 003/102] JStudio WIP updates * Mostly implemented TVariableValue * Mostly implemented TAdaptor * Filled out some missing enum values related to TCamera * Void, Immediate, and Time TVariableValue updates are supported * Implemented JStudio::TObject base class Next TODOs: * Implement TFunctionValue * Implement FVB parsing --- src/Common/JSYSTEM/JStudio.ts | 387 +++++++++++++++++++++------------- 1 file changed, 242 insertions(+), 145 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index b01a58410..39932c3ff 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -1,9 +1,11 @@ // Nintendo's cutscene framework. Seems very over-engineered. -import { ReadonlyVec3, vec3 } from "gl-matrix"; +import { mat4, ReadonlyVec3, vec3 } from "gl-matrix"; import ArrayBufferSlice from "../../ArrayBufferSlice"; import { align, assert, nArray, readString } from "../../util.js"; import { JSystemFileReaderHelper } from "./J3D/J3DLoader.js"; +import { GfxColor } from "../../gfx/platform/GfxPlatform"; +import { clamp } from "../../MathHelpers.js"; const scratchVec3a = vec3.create(); const scratchVec3b = vec3.create(); @@ -25,9 +27,25 @@ export namespace JStage { LIGHT = 0x5, FOG = 0x6, }; - - export class TObject { + export abstract class TObject { + JSGFDisableFlag(flag: number): void { this.JSGSetFlag(this.JSGGetFlag() & ~flag); } + JSGFEnableFlag(flag: number): void { this.JSGSetFlag(this.JSGGetFlag() | flag); } + + abstract JSGFGetType(): number; + JSGGetName(): boolean { return false; } // TODO: What's the point of this? + JSGGetFlag(): number { return 0; } + JSGSetFlag(flag: number): void { } + JSGGetData(unk0: number, data: Object, unk1: number): boolean { return false; } + JSGSetData(unk0: number, data: Object, unk1: number): void { } + JSGGetParent(parentDst: JStage.TObject, unk: { x: number }): void { } + JSGSetParent(parent: JStage.TObject, unk: number): void { } + JSGSetRelation(related: boolean, obj: JStage.TObject, unk: number): void { } + JSGFindNodeID(id: string): number { return -1; } + JSGGetNodeTransformation(unk: number, mtx: mat4): number { + mat4.identity(mtx); + return 0; + } } } @@ -40,10 +58,17 @@ export interface TSystem { JSGFindObject(objId: string, objType: JStage.TEObject): JStage.TObject | undefined; } +//---------------------------------------------------------------------------------------------------------------------- +// Function Values +//---------------------------------------------------------------------------------------------------------------------- +class TFunctionValue { + //@TODO +} + //---------------------------------------------------------------------------------------------------------------------- // TVariableValue // Manages a single float, which will be updated each frame. This float can be updated using a variety of operations: -// - Immediate(x): Set to single value. On Update(y), set the value to a single number. +// - Immediate(x): Set to single value. On Update(y), set the value to a single number then do nothing on future frames. // - Time(x): Increase over time. On Update(y), set the value to x * y * mAge. // - FuncVal(x): Set to the output of a functor. See FVB for details. // @@ -51,8 +76,77 @@ export interface TSystem { // functor will be called during update(). //---------------------------------------------------------------------------------------------------------------------- class TVariableValue { - public mValue: number; - public mAge: number; // In frames + private mValue: number; + private mAge: number; // In frames + private mUpdateFunc?: (varval: TVariableValue, x: number) => void; + private mUpdateParam: number | TFunctionValue; + // @TODO: TOutput + + getValue() { return this.mValue; } + getValueU8() { return clamp(this.mValue, 0, 255); } + + forward(frameCount: number) { + if (Number.MAX_VALUE - this.mAge <= frameCount) { + this.mAge = Number.MAX_VALUE; + } else { + this.mAge += frameCount; + } + } + + update(secondsPerFrame: number, adaptor: TAdaptor): void { + if (this.mUpdateFunc) { + this.mUpdateFunc(this, secondsPerFrame); + // (* pOutput_)(mValue, param_1); // @TODO: Output + } + } + + //-------------------- + // Update functions + // Each frame, one of these (or nothing) will be called to update the value of each TVariableValue. + //-------------------- + private static update_immediate(varval: TVariableValue, secondsPerFrame: number): void { + varval.mValue = (varval.mUpdateParam as number); + varval.mUpdateFunc = undefined; + } + + private static update_time(varval: TVariableValue, secondsPerFrame: number): void { + varval.mValue = (varval.mUpdateParam as number) * (varval.mAge * secondsPerFrame); + } + + private static update_functionValue(varval: TVariableValue, secondsPerFrame: number): void { + // @TODO: + debugger; + } + + //-------------------- + // Set Update functions + // Modify the function that will be called each Update() + //-------------------- + public setValue_none() { + this.mUpdateFunc = undefined; + } + + // Value will be set only on next update + setValue_immediate(v: number): void { + this.mUpdateFunc = TVariableValue.update_immediate; + this.mAge = 0; + this.mUpdateParam = v; + } + + // Value will be set to (mAge * v * x) each frame + setValue_time(v: number): void { + this.mUpdateFunc = TVariableValue.update_time; + this.mAge = 0; + this.mUpdateParam = v; + } + + // Value will be the result of a Function Value each frame + setValue_functionValue(v: TFunctionValue): void { + this.mUpdateFunc = TVariableValue.update_functionValue; + this.mAge = 0; + this.mUpdateParam = v; + } + // struct TOutput { // virtual void operator()(f32, JStudio::TAdaptor*) const = 0; @@ -64,10 +158,6 @@ class TVariableValue { // void operator()(f32, JStudio::TAdaptor*) const; // }; - // void update(f64, JStudio::TAdaptor*); - // static void update_immediate_(JStudio::TVariableValue*, f64); - // static void update_time_(JStudio::TVariableValue*, f64); - // static void update_functionValue_(JStudio::TVariableValue*, f64); // TVariableValue() // : field_0x4(0) // , field_0x8(NULL) @@ -75,28 +165,6 @@ class TVariableValue { // { // } - // void setValue_immediate(f32 value) { - // field_0x8 = &update_immediate_; - // field_0x4 = 0; - // field_0xc.val = value; - // } - - // void setValue_none() { - // field_0x8 = NULL; - // } - - // void setValue_time(f32 value) { - // field_0x8 = &update_time_; - // field_0x4 = 0; - // field_0xc.val = value; - // } - - // void setValue_functionValue(TFunctionValue* value) { - // field_0x8 = &update_functionValue_; - // field_0x4 = 0; - // field_0xc.fv = value; - // } - // f32 getValue() const { return mValue; } // template @@ -151,30 +219,71 @@ const enum TEOperationData { abstract class TAdaptor { constructor( protected mCount: number, - protected mVariableValues = nArray(mCount, i => new TVariableValue() ), + protected mVariableValues = nArray(mCount, i => new TVariableValue()), ) { } abstract adaptor_do_prepare(): void; abstract adaptor_do_begin(): void; abstract adaptor_do_end(): void; abstract adaptor_do_update(frameCount: number): void; + abstract adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void; - adaptor_setVariableValue(obj: STBObject, trackIdx: number, dataOp: TEOperationData, data: number, dataSize: number) { - const varval = this.mVariableValues[trackIdx]; + // Set a single VariableValue update function, with the option of using FuncVals + adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: TEOperationData, data: number | string) { + const varval = this.mVariableValues[keyIdx]; const control = obj.mControl; switch (dataOp) { - // case TEOperationData.VOID: varval.setValue_none(); - // case TEOperationData.IMMEDIATE: varval.setValue_immediate(data); - // case TEOperationData.TIME: varval.setValue_time(data); - // case TEOperationData.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValue(data, dataSize)) //@TODO: Not support ATM because we're passing in data as a number instead of DataView (this is a string) + case TEOperationData.VOID: varval.setValue_none(); + case TEOperationData.IMMEDIATE: varval.setValue_immediate(data as number); + case TEOperationData.TIME: varval.setValue_time(data as number); + // case TEOperationData.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValue(data, dataSize)) // case TEOperationData.FUNCVALUE_INDEX: varval.setValue_functionValue(control.getFunctionValue_index(data)); default: console.debug('Unsupported dataOp: ', dataOp); + debugger; return; } } + // Immediately set 3 consecutive VariableValue update functions from a single vec3 + adaptor_setVariableValue_Vec(startKeyIdx: number, data: vec3) { + this.mVariableValues[startKeyIdx + 0].setValue_immediate(data[0]); + this.mVariableValues[startKeyIdx + 1].setValue_immediate(data[1]); + this.mVariableValues[startKeyIdx + 2].setValue_immediate(data[2]); + } + + // Get the current value of 3 consecutive VariableValues, as a vector. E.g. Camera position. + adaptor_getVariableValue_Vec(dst: vec3, startKeyIdx: number) { + dst[0] = this.mVariableValues[startKeyIdx + 0].getValue(); + dst[1] = this.mVariableValues[startKeyIdx + 1].getValue(); + dst[2] = this.mVariableValues[startKeyIdx + 2].getValue(); + } + + // Immediately set 4 consecutive VariableValue update functions from a single GXColor (4 bytes) + adaptor_setVariableValue_GXColor(startKeyIdx: number, data: GfxColor) { + debugger; // @TODO: Confirm that all uses of this always have consecutive keyIdxs. JStudio remaps them. + this.mVariableValues[startKeyIdx + 0].setValue_immediate(data.r); + this.mVariableValues[startKeyIdx + 1].setValue_immediate(data.g); + this.mVariableValues[startKeyIdx + 2].setValue_immediate(data.b); + this.mVariableValues[startKeyIdx + 4].setValue_immediate(data.a); + } + + // Get the current value of 4 consecutive VariableValues, as a GXColor. E.g. Fog color. + adaptor_getVariableValue_GXColor(dst: GfxColor, startKeyIdx: number) { + dst.r = this.mVariableValues[startKeyIdx + 0].getValue(); + dst.g = this.mVariableValues[startKeyIdx + 1].getValue(); + dst.b = this.mVariableValues[startKeyIdx + 2].getValue(); + dst.a = this.mVariableValues[startKeyIdx + 2].getValue(); + } + + adaptor_updateVariableValue(obj: STBObject, frameCount: number) { + const control = obj.mControl; + for (let vv of this.mVariableValues) { + vv.forward(frameCount); + vv.update(control.mSecondsPerFrame, this); + } + } } // struct TSetVariableValue_immediate { @@ -188,20 +297,6 @@ abstract class TAdaptor { // f32 field_0x4; // }; // typedef void (*setVarFunc)(JStudio::TAdaptor*, JStudio::TObject*, u32, void const*, u32); -// virtual ~TAdaptor() = 0; -// virtual void adaptor_do_prepare(const JStudio::TObject*); -// virtual void adaptor_do_begin(const JStudio::TObject*); -// virtual void adaptor_do_end(const JStudio::TObject*); -// virtual void adaptor_do_update(const JStudio::TObject*, u32); -// virtual void adaptor_do_data(const JStudio::TObject*, void const*, u32, void const*, u32); - -// void adaptor_setVariableValue_n(JStudio::TObject*, u32 const*, u32, JStudio::data::TEOperationData, void const*, u32); -// void adaptor_setVariableValue_immediate(JStudio::TAdaptor::TSetVariableValue_immediate const*); -// void adaptor_setVariableValue_Vec(u32 const*, Vec const&); -// void adaptor_getVariableValue_Vec(Vec*, u32 const*) const; -// void adaptor_setVariableValue_GXColor(u32 const*, GXColor const&); -// void adaptor_getVariableValue_GXColor(GXColor*, u32 const*) const; -// void adaptor_updateVariableValue(JStudio::TObject*, u32); // TVariableValue* adaptor_referVariableValue(u32 param_0) { @@ -238,7 +333,7 @@ abstract class STBObject { constructor(control: TControl, blockObj: TBlockObject, adaptor: TAdaptor) { this.mControl = control; - this.mAdaptor = adaptor; + this.mAdaptor = adaptor; this.mId = blockObj.id; this.mType = blockObj.type; @@ -249,14 +344,14 @@ abstract class STBObject { } // These are intended to be overridden by subclasses - abstract do_paragraph(view: DataView, dataSize: number, dataOffset: number, param: number): void; + abstract do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void; do_begin() { } do_end() { } - // Done updating this frame. Compute our variable data (i.e. interpolate) and forward to the game object. - do_wait(flags: number) { - // adaptor->adaptor_updateVariableValue(this, param_0); // @TODO: - this.mAdaptor.adaptor_do_update(flags); + // Done updating this frame. Compute our variable data (i.e. interpolate) and send to the game object. + do_wait(frameCount: number) { + this.mAdaptor.adaptor_updateVariableValue(this, frameCount); + this.mAdaptor.adaptor_do_update(frameCount); } // do_data(void const*, u32, void const*, u32) {} @@ -372,11 +467,11 @@ abstract class STBObject { byteIdx += 4; while (byteIdx < this.pSequence_next) { const para = parseParagraph(view, byteIdx); - if (para.dataSize <= 0xff) { - console.debug('Unsupported paragraph feature: ', para.params); + if (para.type <= 0xff) { + console.debug('Unsupported paragraph feature: ', para.type); // process_paragraph_reserved_(para.type, para.content, para.param); } else { - this.do_paragraph(view, para.dataSize, para.dataOffset, para.params); + this.do_paragraph(this.mData, para.dataSize, para.dataOffset, para.type); } byteIdx = para.nextOffset; } @@ -403,8 +498,8 @@ const enum Camera_Cmd { SET_TARGET_Y_POS = 0x001A, SET_TARGET_Z_POS = 0x001B, SET_TARGET_POS = 0x001C, - SET_UNK_0026 = 0x0026, - SET_UNK_0027 = 0x0027, + SET_PROJ_FOVY = 0x0026, + SET_VIEW_ROLL = 0x0027, SET_DIST_NEAR = 0x0028, SET_DIST_FAR = 0x0029, SET_DIST_NEAR_FAR = 0x002A, @@ -417,8 +512,8 @@ const enum Camera_Track { TARGET_X_POS = 0x03, TARGET_Y_POS = 0x04, TARGET_Z_POS = 0x05, - UNK_0026 = 0x06, - UNK_0027 = 0x07, + PROJ_FOVY = 0x06, + VIEW_ROLL = 0x07, DIST_NEAR = 0x08, DIST_FAR = 0x09, CAMERA_TRACKS_MAX = 0x0A, @@ -456,93 +551,99 @@ class TCameraAdaptor extends TAdaptor { ) { super(11); } adaptor_do_prepare(): void { - + // @TODO: Setup output functions } adaptor_do_begin(): void { - + // @TODO: } adaptor_do_end(): void { - + this.mStageCam.JSGFDisableFlag(1); } adaptor_do_update(frameCount: number): void { + // @TODO: + } + + adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void { + // This is not used by TWW. Untested. + debugger; + } + + // Custom adaptor functions. These can be called from within TCameraObject::do_paragraph() + + adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, unk0: number): void { + debugger; + } + + adaptor_do_PARENT_NODE(dataOp: TEOperationData, data: number | string, unk0: number): void { + debugger; + } + adaptor_do_PARENT_ENABLE(dataOp: TEOperationData, data: number | string, unk0: number): void { + debugger; } } class TCameraObject extends STBObject { constructor( - control: TControl, - blockObj: TBlockObject, + control: TControl, + blockObj: TBlockObject, stageObj: JStage.TObject, ) { super(control, blockObj, new TCameraAdaptor(stageObj as TCamera)) } - override do_paragraph(view: DataView, dataSize: number, dataOffset: number, param: number): void { - const updateType = param & 0x1F; + override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { + const dataOp = (param & 0x1F) as TEOperationData; const cmdType = param >> 5; - const data = updateType == TEOperationData.FUNCVALUE_INDEX ? - view.getUint32(dataOffset) : view.getFloat32(dataOffset); - - // switch (cmdType) { - // // Eye position - // case Camera_Cmd.SET_EYE_X_POS: - // mTracks[Camera_Track.EYE_X_POS].AddKey(data, curFrame, updateType); - // break; - // case Camera_Cmd.SET_EYE_Y_POS: - // mTracks[Camera_Track.EYE_Y_POS].AddKey(data, curFrame, updateType); - // break; - // case Camera_Cmd.SET_EYE_Z_POS: - // mTracks[Camera_Track.EYE_Z_POS].AddKey(data, curFrame, updateType); - // break; - // case Camera_Cmd.SET_EYE_POS: - // mTracks[Camera_Track.EYE_X_POS].AddKey(data, curFrame, updateType); - // mTracks[Camera_Track.EYE_Y_POS].AddKey(data, curFrame, updateType); - // mTracks[Camera_Track.EYE_Z_POS].AddKey(data, curFrame, updateType); - // break; - - // // Target position - // case Camera_Cmd.SET_TARGET_X_POS: - // mTracks[Camera_Track.TARGET_X_POS].AddKey(data, curFrame, updateType); - // break; - // case Camera_Cmd.Camera_Cmd.SET_TARGET_Y_POS: - // mTracks[Camera_Track.TARGET_Y_POS].AddKey(data, curFrame, updateType); - // break; - // case Camera_Cmd.SET_TARGET_Z_POS: - // mTracks[Camera_Track.TARGET_Z_POS].AddKey(data, curFrame, updateType); - // break; - // case Camera_Cmd.SET_TARGET_POS: - // mTracks[Camera_Track.TARGET_X_POS].AddKey(data, curFrame, updateType); - // mTracks[Camera_Track.TARGET_Y_POS].AddKey(data, curFrame, updateType); - // mTracks[Camera_Track.TARGET_Z_POS].AddKey(data, curFrame, updateType); - // break; - - // // ? - // case Camera_Cmd.SET_UNK_0026: - // mTracks[Camera_Track.UNK_0026].AddKey(data, curFrame, updateType); - // break; - // case Camera_Cmd.SET_UNK_0027: - // mTracks[Camera_Track.UNK_0027].AddKey(data, curFrame, updateType); - // break; - - // // Near/far distance - // case Camera_Cmd.SET_DIST_NEAR: - // mTracks[Camera_Track.DIST_NEAR].AddKey(data, curFrame, updateType); - // break; - // case Camera_Cmd.SET_DIST_FAR: - // mTracks[Camera_Track.DIST_FAR].AddKey(data, curFrame, updateType); - // break; - // case Camera_Cmd.SET_DIST_NEAR_FAR: - // mTracks[Camera_Track.DIST_NEAR].AddKey(data, curFrame, updateType); - // mTracks[Camera_Track.DIST_FAR].AddKey(data, curFrame, updateType); - // break; - - // default: - // console.debug('Unsupported TCamera update: ', cmdType, ' ', updateType); - // break; - // } + let keyCount = 1; + let keyIdx; + let data; + + // Parse the data here instead of deferring to adaptor_setVariableValue, so we don't have to pass along `file`. + switch (dataOp) { + case TEOperationData.FUNCVALUE_INDEX: data = file.view.getUint32(dataOffset); break; + case TEOperationData.FUNCVALUE_NAME: + data = readString(file.buffer, dataOffset, dataSize); + console.warn('FUNCVALUE_NAME found! Remove this comment after testing'); + debugger; + break; + default: data = file.view.getFloat32(dataOffset); + } + + switch (cmdType) { + // Eye position + case Camera_Cmd.SET_EYE_X_POS: keyIdx = Camera_Track.EYE_X_POS; break; + case Camera_Cmd.SET_EYE_Z_POS: keyIdx = Camera_Track.EYE_Y_POS; break; + case Camera_Cmd.SET_EYE_Y_POS: keyIdx = Camera_Track.EYE_Z_POS; break; + case Camera_Cmd.SET_EYE_POS: keyCount = 3; keyIdx = Camera_Track.EYE_X_POS; break; + break; + + // Target position + case Camera_Cmd.SET_TARGET_X_POS: keyIdx = Camera_Track.TARGET_X_POS; break; + case Camera_Cmd.SET_TARGET_Y_POS: keyIdx = Camera_Track.TARGET_Y_POS; break; + case Camera_Cmd.SET_TARGET_Z_POS: keyIdx = Camera_Track.TARGET_Z_POS; break; + case Camera_Cmd.SET_EYE_POS: keyCount = 3; keyIdx = Camera_Track.TARGET_X_POS; break; + + // Camera params + case Camera_Cmd.SET_PROJ_FOVY: keyIdx = Camera_Track.PROJ_FOVY; break; + case Camera_Cmd.SET_VIEW_ROLL: keyIdx = Camera_Track.VIEW_ROLL; break; + + // Near/far distance + case Camera_Cmd.SET_DIST_NEAR: keyIdx = Camera_Track.DIST_NEAR; break; + case Camera_Cmd.SET_DIST_FAR: keyIdx = Camera_Track.DIST_FAR; break; + case Camera_Cmd.SET_EYE_POS: keyCount = 2; keyIdx = Camera_Track.DIST_NEAR; break; + + default: + console.debug('Unsupported TCamera update: ', cmdType, ' ', dataOp); + debugger; + return; + } + + for (let i = 0; i < keyCount; i++) { + this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, data); + } } } @@ -578,15 +679,9 @@ export abstract class TBlockObject { /* 0xC + align(id_size, 4) */ data: Reader; } -// The "loader" which manages a key-value store of parameters for each object. Each frame these value are interpolated -// and written to a corresponding TObject, where they can be read by the game. -class TAdapter { - -} - class TParagraph { + type: number; dataSize: number; - params: number; dataOffset: number; nextOffset: number; } @@ -594,24 +689,24 @@ class TParagraph { function parseParagraph(view: DataView, byteIdx: number): TParagraph { // The top bit of the paragraph determines if the type and size are 16 bit (if set), or 32 (if not set) let dataSize = view.getUint16(byteIdx); - let params; + let type; let offset; if ((dataSize & 0x8000) == 0) { // 16 bit data - params = view.getUint16(byteIdx + 2); + type = view.getUint16(byteIdx + 2); offset = 4; } else { // 32 bit data dataSize = view.getUint32(byteIdx + 0) & ~0x80000000; - params = view.getUint32(byteIdx + 4); + type = view.getUint32(byteIdx + 4); offset = 8; } if (dataSize == 0) { - return { dataSize, params, dataOffset: 0, nextOffset: byteIdx + offset }; + return { dataSize, type, dataOffset: 0, nextOffset: byteIdx + offset }; } else { - return { dataSize, params, dataOffset: byteIdx + offset, nextOffset: byteIdx + offset + align(dataSize, 4) }; + return { dataSize, type, dataOffset: byteIdx + offset, nextOffset: byteIdx + offset + align(dataSize, 4) }; } } @@ -630,10 +725,12 @@ class Reader { export class TControl { private mSystem: TSystem; private mObjects: STBObject[] = []; + public mSecondsPerFrame: number; public mIsSuspended = false; constructor(system: TSystem) { this.mSystem = system; + this.mSecondsPerFrame = 1 / 30.0; // @TODO: Allow the game to change this? } public forward(flags: number): number { From 58aa53251fced48344fdca132ecd15f431282649 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 1 Nov 2024 20:00:30 -0600 Subject: [PATCH 004/102] dDemo lifecycle improvements * Uncommented update() and remove() * Fixed up some errors preventing Camera data updates --- src/Common/JSYSTEM/JStudio.ts | 6 ++++ src/ZeldaWindWaker/d_demo.ts | 60 +++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 39932c3ff..2d5000c0f 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -733,6 +733,8 @@ export class TControl { this.mSecondsPerFrame = 1 / 30.0; // @TODO: Allow the game to change this? } + public isSuspended() { return this.mIsSuspended; } + public forward(flags: number): number { for (let obj of this.mObjects) { const res = obj.forward(flags); @@ -764,6 +766,10 @@ export class TControl { this.mObjects.push(obj); return obj; } + + public destroyObject_all() { + this.mObjects = []; + } } export class TParse { diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index bca399303..3b0796e21 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -5,15 +5,15 @@ import { getMatrixAxisY } from "../MathHelpers.js"; import { dGlobals } from "./Main"; class dDemo_camera_c extends TCamera { - mFlags: number; - mProjNear: number; - mProjFar: number; - mFovy: number; - mAspect: number; - mViewPosition: vec3; - mUpVector: vec3; - mTargetPosition: vec3; - mRoll: number; + mFlags: number = 0; + mProjNear: number = 0; + mProjFar: number = 0; + mFovy: number = 0; + mAspect: number = 0; + mViewPosition: vec3 = vec3.create(); + mUpVector: vec3 = vec3.create(); + mTargetPosition: vec3 = vec3.create(); + mRoll: number = 0; globals: dGlobals; @@ -126,7 +126,7 @@ class dDemo_camera_c extends TCamera { } class dDemo_system_c implements TSystem { - private mpActiveCamera: dDemo_camera_c; + private mpActiveCamera?: dDemo_camera_c; // private mpActors: dDemo_actor_c[]; // private mpAmbient: dDemo_ambient_c; // private mpLight: dDemo_light_c[]; @@ -136,8 +136,7 @@ class dDemo_system_c implements TSystem { switch (objType) { case JStage.TEObject.CAMERA: if (this.mpActiveCamera) return this.mpActiveCamera; - else return new dDemo_camera_c(); - + else return this.mpActiveCamera = new dDemo_camera_c(); case JStage.TEObject.ACTOR: case JStage.TEObject.ACTOR_UNK: case JStage.TEObject.AMBIENT: @@ -148,13 +147,17 @@ class dDemo_system_c implements TSystem { return undefined; } } + + public remove() { + this.mpActiveCamera = undefined; + } } export class dDemo_manager_c { private mFrame: number; private mFrameNoMsg: number; private mMode: number; - private mCurFile: ArrayBufferSlice; + private mCurFile?: ArrayBufferSlice; private mParser: TParse; private mSystem = new dDemo_system_c(); @@ -169,6 +172,7 @@ export class dDemo_manager_c { } this.mControl.forward(0); + // @TODO: // if (originPos == NULL) { // mControl->transform_enable(false); // } else { @@ -185,24 +189,24 @@ export class dDemo_manager_c { } public remove() { - // mControl->destroyObject_all(); - // mDemoObj.remove(); - // this.mCurFile = NULL; - // this.mMode = 0; + this.mControl.destroyObject_all(); + this.mSystem.remove(); + this.mCurFile = undefined; + this.mMode = 0; } public update(): boolean { - // if (!this.mCurFile) { - // return false; - // } - // if (this.mControl->forward(1)) { - // this.mFrame++; - // if (!this.mControl->isSuspended()) { - // this.mFrameNoMsg++; - // } - // } else { - // this.mMode = 2; - // } + if (!this.mCurFile) { + return false; + } + if (this.mControl.forward(1)) { + this.mFrame++; + if (!this.mControl.isSuspended()) { + this.mFrameNoMsg++; + } + } else { + this.mMode = 2; + } return true; } } \ No newline at end of file From 7a91c1d1ea397a69a235e37efb87665bbea40ab6 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 2 Nov 2024 11:27:24 -0600 Subject: [PATCH 005/102] FVB Parsing starting point Implemented most of the skeleton of FVB, although still missing implementations for most of the various function variable implementations. --- src/Common/JSYSTEM/JStudio.ts | 581 +++++++++++++++++++++++++++++----- 1 file changed, 507 insertions(+), 74 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 2d5000c0f..b9d60faa9 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -6,6 +6,8 @@ import { align, assert, nArray, readString } from "../../util.js"; import { JSystemFileReaderHelper } from "./J3D/J3DLoader.js"; import { GfxColor } from "../../gfx/platform/GfxPlatform"; import { clamp } from "../../MathHelpers.js"; +import { Endianness } from "../../endian.js"; +import { ArtObjectData } from "../../Fez/ArtObjectData"; const scratchVec3a = vec3.create(); const scratchVec3b = vec3.create(); @@ -58,13 +60,6 @@ export interface TSystem { JSGFindObject(objId: string, objType: JStage.TEObject): JStage.TObject | undefined; } -//---------------------------------------------------------------------------------------------------------------------- -// Function Values -//---------------------------------------------------------------------------------------------------------------------- -class TFunctionValue { - //@TODO -} - //---------------------------------------------------------------------------------------------------------------------- // TVariableValue // Manages a single float, which will be updated each frame. This float can be updated using a variety of operations: @@ -79,7 +74,7 @@ class TVariableValue { private mValue: number; private mAge: number; // In frames private mUpdateFunc?: (varval: TVariableValue, x: number) => void; - private mUpdateParam: number | TFunctionValue; + private mUpdateParam: number | FVB.TFunctionValue | undefined; // @TODO: TOutput getValue() { return this.mValue; } @@ -114,14 +109,15 @@ class TVariableValue { } private static update_functionValue(varval: TVariableValue, secondsPerFrame: number): void { - // @TODO: - debugger; + const t = varval.mAge * secondsPerFrame; + varval.mValue = (varval.mUpdateParam as FVB.TFunctionValue).getValue(t); } //-------------------- // Set Update functions // Modify the function that will be called each Update() //-------------------- + // @TODO: Shorten these names public setValue_none() { this.mUpdateFunc = undefined; } @@ -141,7 +137,7 @@ class TVariableValue { } // Value will be the result of a Function Value each frame - setValue_functionValue(v: TFunctionValue): void { + setValue_functionValue(v?: FVB.TFunctionValue): void { this.mUpdateFunc = TVariableValue.update_functionValue; this.mAge = 0; this.mUpdateParam = v; @@ -234,11 +230,11 @@ abstract class TAdaptor { const control = obj.mControl; switch (dataOp) { - case TEOperationData.VOID: varval.setValue_none(); - case TEOperationData.IMMEDIATE: varval.setValue_immediate(data as number); - case TEOperationData.TIME: varval.setValue_time(data as number); - // case TEOperationData.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValue(data, dataSize)) - // case TEOperationData.FUNCVALUE_INDEX: varval.setValue_functionValue(control.getFunctionValue_index(data)); + case TEOperationData.VOID: varval.setValue_none(); break; + case TEOperationData.IMMEDIATE: varval.setValue_immediate(data as number); break; + case TEOperationData.TIME: varval.setValue_time(data as number); break; + case TEOperationData.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValueByName(data as string)); break; + case TEOperationData.FUNCVALUE_INDEX: varval.setValue_functionValue(control.getFunctionValueByIdx(data as number)); break; default: console.debug('Unsupported dataOp: ', dataOp); debugger; @@ -466,7 +462,7 @@ abstract class STBObject { case ESequenceCmd.Paragraph: byteIdx += 4; while (byteIdx < this.pSequence_next) { - const para = parseParagraph(view, byteIdx); + const para = TParagraph.parse(view, byteIdx); if (para.type <= 0xff) { console.debug('Unsupported paragraph feature: ', para.type); // process_paragraph_reserved_(para.type, para.content, para.param); @@ -624,7 +620,7 @@ class TCameraObject extends STBObject { case Camera_Cmd.SET_TARGET_X_POS: keyIdx = Camera_Track.TARGET_X_POS; break; case Camera_Cmd.SET_TARGET_Y_POS: keyIdx = Camera_Track.TARGET_Y_POS; break; case Camera_Cmd.SET_TARGET_Z_POS: keyIdx = Camera_Track.TARGET_Z_POS; break; - case Camera_Cmd.SET_EYE_POS: keyCount = 3; keyIdx = Camera_Track.TARGET_X_POS; break; + case Camera_Cmd.SET_TARGET_POS: keyCount = 3; keyIdx = Camera_Track.TARGET_X_POS; break; // Camera params case Camera_Cmd.SET_PROJ_FOVY: keyIdx = Camera_Track.PROJ_FOVY; break; @@ -647,6 +643,486 @@ class TCameraObject extends STBObject { } } + +//---------------------------------------------------------------------------------------------------------------------- +// Parsing helpers +//---------------------------------------------------------------------------------------------------------------------- +// @TODO: Rename +class Reader { + buffer: ArrayBufferSlice; + view: DataView; + offset: number; + + constructor(buffer: ArrayBufferSlice, offset: number) { + this.buffer = buffer.subarray(offset); + this.view = this.buffer.createDataView(); + this.offset = 0; + } +} + +class TParagraph { + type: number; + dataSize: number; + dataOffset: number; + nextOffset: number; + + static parse(view: DataView, byteIdx: number): TParagraph { + // The top bit of the paragraph determines if the type and size are 16 bit (if set), or 32 (if not set) + let dataSize = view.getUint16(byteIdx); + let type; + let offset; + + if ((dataSize & 0x8000) == 0) { + // 16 bit data + type = view.getUint16(byteIdx + 2); + offset = 4; + } else { + // 32 bit data + dataSize = view.getUint32(byteIdx + 0) & ~0x80000000; + type = view.getUint32(byteIdx + 4); + offset = 8; + } + + if (dataSize == 0) { + return { dataSize, type, dataOffset: 0, nextOffset: byteIdx + offset }; + } else { + return { dataSize, type, dataOffset: byteIdx + offset, nextOffset: byteIdx + offset + align(dataSize, 4) }; + } + } +} + +//---------------------------------------------------------------------------------------------------------------------- +// FVB (Function Value Binary) Parsing +// Although embedded in the STB file, the FVB section is treated and parsed like a separate file +//---------------------------------------------------------------------------------------------------------------------- +namespace FVB { + enum EFuncValType { + None = 0, + Composite = 1, + Constant = 2, + Transition = 3, + List = 4, + ListParameter = 5, + Hermite = 6, + }; + + enum EPrepareOp { + None = 0x00, + Data = 0x01, + ObjSetByName = 0x10, + ObjSetByIdx = 0x11, + RangeSet = 0x12, + RangeProgress = 0x13, + RangeAdjust = 0x14, + RangeOutside = 0x15, + InterpSet = 0x16, + }; + + class TBlock { + size: number; + type: number; + id: string; + dataOffset: number; + }; + + export abstract class TFunctionValue { + protected range?: Attribute.Range; + protected refer?: Attribute.Refer; + protected interpolate?: Attribute.Interpolate; + + abstract getType(): EFuncValType; + abstract prepare(): void; + abstract getValue(arg: number): number; + + getAttrRange() { return this.range; } + getAttrRefer() { return this.refer; } + getAttrInterpolate() { return this.interpolate; } + + // static ExtrapolateParameter toFunction_outside(int); + + // static ExtrapolateParameter toFunction(TFunctionValue::TEOutside outside) { + // return toFunction_outside(outside); + // } + } + + export abstract class TObject { + public funcVal: TFunctionValue; + public id: string; + + constructor(block: TBlock) { + this.id = block.id; + } + + abstract prepare_data(para: TParagraph, control: TControl, file: Reader): void; + + prepare(block: TBlock, mControl: TControl, file: Reader) { + const blockNext = file.offset + block.size; + file.offset = blockNext; + + let pOffset = block.dataOffset; + while (pOffset < blockNext) { + const para = TParagraph.parse(file.view, pOffset); + switch (para.type) { + case EPrepareOp.None: + this.funcVal.prepare(); + assert(para.nextOffset == blockNext); + return; + + case EPrepareOp.Data: + this.prepare_data(para, mControl, file); + break; + + case EPrepareOp.RangeSet: + assert(para.dataSize == 8); + const range = this.funcVal.getAttrRange(); + assert(!!range, 'FVB Paragraph assumes TObject has range attribute, but it does not'); + const begin = file.view.getFloat32(para.dataOffset + 0); + const end = file.view.getFloat32(para.dataOffset + 4); + range.set(begin, end); + break; + + case EPrepareOp.InterpSet: + assert(para.dataSize == 4); + const interp = this.funcVal.getAttrInterpolate(); + assert(!!interp, 'FVB Paragraph assumes TObject has interpolate attribute, but it does not'); + const interpType = file.view.getUint32(para.dataOffset + 0); + interp.set(interpType); + break; + + case EPrepareOp.ObjSetByName: + case EPrepareOp.ObjSetByIdx: + + case EPrepareOp.RangeProgress: + case EPrepareOp.RangeAdjust: + case EPrepareOp.RangeOutside: + + default: + console.warn('Unhandled FVB PrepareOp: ', para.type); + debugger; + } + pOffset = para.nextOffset; + } + + assert(pOffset == blockNext); + this.funcVal.prepare(); + } + } + + export class TControl { + public mObjects: TObject[] = []; + + // Really this is a fvb::TFactory method + public createObject(block: TBlock): TObject | undefined { + switch (block.type) { + // case EFuncValType.Composite: + // return new TObject_composite(block); + // case EFuncValType.Constant: + // return new TObject_constant(block); + // case EFuncValType.Transition: + // return new TObject_transition(block); + // case EFuncValType.List: + // return new TObject_list(block); + case EFuncValType.ListParameter: + return new TObject_ListParameter(block); + // case EFuncValType.Hermite: + // return new TObject_hermite(block); + default: + console.warn('Unknown FVB type: ', block.type); + return undefined; + } + } + + public destroyObject_all() { + this.mObjects = []; + } + } + + export class TParse { + constructor( + private mControl: TControl + ) { } + + private parseBlock(file: Reader, flags: number): boolean { + const idLen = file.view.getUint16(file.offset + 6); + const block: TBlock = { + size: file.view.getUint32(file.offset + 0), + type: file.view.getUint16(file.offset + 4), + id: readString(file.buffer, file.offset + 8, idLen), + dataOffset: file.offset + align(8 + idLen, 4), + } + + console.log('Parsing Block:', block.id, block.size, block.type); + + const obj = this.mControl.createObject(block); + if (!obj) { return false; } + + obj.prepare(block, this.mControl, file); + this.mControl.mObjects.push(obj); + + return true; + } + + public parse(data: ArrayBufferSlice, flags: number) { + const view = data.createDataView(); + let fourCC = readString(data, 0, 4); + let byteOrder = view.getUint16(0x04); + let version = view.getUint16(0x06); + let blockCount = view.getUint32(0x0C); + assert(fourCC === 'FVB'); + assert(byteOrder == 0xFEFF); + assert(version >= 2 && version <= 256); // As of Wind Waker + + const blockReader = new Reader(data, 16); + for (let i = 0; i < blockCount; i++) { + this.parseBlock(blockReader, flags); + } + } + } + + //---------------------------------------------------------------------------------------------------------------------- + // FV Attributes + //---------------------------------------------------------------------------------------------------------------------- + enum EInterpolateType { + None = 0, + Linear = 1, + Plateau = 2, + BSpline = 3 + } + namespace Attribute { + export class Range { + private begin: number = 0; + private end: number = 0; + private diff: number = 0; + + private progress: number = 0; + private adjust: number = 0; + + prepare() { + // Progress updated here + } + + set(begin: number, end: number) { + this.begin = begin; + this.end = end; + this.diff = end - begin; + assert(this.diff >= 0); + } + + getParameter(time: number, startTime: number, endTime: number): number { + // @NOTE: Does not currently support, Progress, Adjust, or Outside modifications. These can only be set + // in an FVB paragraph, so attempt to set them will be caught in FVB.TObject.prepare(). + return time; + } + } + + export class Refer { + } + + export class Interpolate { + private type = EInterpolateType.None; + prepare() { } + set(type: EInterpolateType) { this.type = type; } + get() { return this.type; } + + static Linear(t: number, t0: number, v0: number, t1: number, v1: number) { + return v0 + ((v1 - v0) * (t - t0)) / (t1 - t0); + } + + static BSpline_Nonuniform(t: number, controlPoints: Float64Array, knotVector: Float64Array) { + const knot0 = knotVector[0]; + const knot1 = knotVector[1]; + const knot2 = knotVector[2]; + const knot3 = knotVector[3]; + const knot4 = knotVector[4]; + const knot5 = knotVector[5]; + const diff0 = t - knot0; + const diff1 = t - knot1; + const diff2 = t - knot2; + const diff3 = knot3 - t; + const diff4 = knot4 - t; + const diff5 = knot5 - t; + const inverseDeltaKnot32 = 1 / (knot3 - knot2); + const blendFactor3 = (diff3 * inverseDeltaKnot32) / (knot3 - knot1); + const blendFactor2 = (diff2 * inverseDeltaKnot32) / (knot4 - knot2); + const blendFactor1 = (diff3 * blendFactor3) / (knot3 - knot0); + const blendFactor4 = ((diff1 * blendFactor3) + (diff4 * blendFactor2)) / (knot4 - knot1); + const blendFactor5 = (diff2 * blendFactor2) / (knot5 - knot2); + const term1 = diff3 * blendFactor1; + const term2 = (diff0 * blendFactor1) + (diff4 * blendFactor4); + const term3 = (diff1 * blendFactor4) + (diff5 * blendFactor5); + const term4 = diff2 * blendFactor5; + + return (term1 * controlPoints[0]) + (term2 * controlPoints[1]) + (term3 * controlPoints[2]) + (term4 * controlPoints[3]); + } + } + } + + //---------------------------------------------------------------------------------------------------------------------- + // FunctionValues + //---------------------------------------------------------------------------------------------------------------------- + class FunctionValue_ListParameter extends TFunctionValue { + protected override range = new Attribute.Range(); + protected override interpolate = new Attribute.Interpolate(); + + // Each key contains 2 floats, a time and value + private keyCount: number = 0; + private keys: Float32Array; + private curKeyIdx: number; + private interpFunc: (t: number) => number; + + prepare(): void { + this.range.prepare(); + this.interpolate.prepare(); + + const interp = this.interpolate.get(); + switch (interp) { + case EInterpolateType.None: + debugger; break; // Untested. Remove after confirmed working. + case EInterpolateType.Linear: + debugger; break; // Untested. Remove after confirmed working. + case EInterpolateType.Plateau: + debugger; break; // Untested. Remove after confirmed working. + case EInterpolateType.BSpline: + if (this.keyCount > 2) { this.interpFunc = this.interpolateBSpline; } + else { + this.interpFunc = this.interpolateLinear; + debugger; // Untested. Remove after confirmed working. + } + break; + + default: + console.warn('Invalid EInterp value', interp); + } + } + + set_data(values: Float32Array) { + this.keys = values; + this.keyCount = values.length / 2; + this.curKeyIdx = 0; + } + + getType() { return EFuncValType.ListParameter; } + getStartTime() { return this.keys[0]; } + getEndTime(): number { return this.keys[this.keys.length - 2]; } + + // Interpolate between our keyframes, given the current time + getValue(timeSec: number): number { + // Remap (if requested) the time to our range + const t = this.range.getParameter(timeSec, this.getStartTime(), this.getEndTime()); + + // Update our current key. If the current time is between keys, select the later one. + this.curKeyIdx = this.keys.findIndex((k, i) => (i % 2) == 0 && k >= t) / 2; + + if (this.curKeyIdx == 0) { // Time is at or before the start, return the first key + return this.keys[this.curKeyIdx * 2 + 1]; + } else if (this.curKeyIdx == -1) { // Time is at or after the end, return the last key + this.curKeyIdx = this.keyCount - 1; + return this.keys[this.curKeyIdx * 2 + 1]; + } + + return this.interpFunc(t); + } + + interpolateBSpline(t: number): number { + const controlPoints = new Float64Array(4); + const knotVector = new Float64Array(6); + controlPoints[1] = this.keys[this.curKeyIdx - 1]; + controlPoints[2] = this.keys[this.curKeyIdx + 1]; + knotVector[2] = this.keys[this.curKeyIdx + -2]; + knotVector[3] = this.keys[this.curKeyIdx + 0]; + + const keysBefore = this.curKeyIdx; + const keysAfter = this.keyCount - this.curKeyIdx; + + switch (keysBefore) { + case 2: + controlPoints[0] = 2.0 * controlPoints[1] - controlPoints[2]; + controlPoints[3] = this.keys[this.curKeyIdx + 3]; + knotVector[4] = this.keys[this.curKeyIdx + 2]; + knotVector[1] = 2.0 * knotVector[2] - knotVector[3]; + knotVector[0] = 2.0 * knotVector[2] - knotVector[4]; + switch (keysAfter) { + case 2: + case 4: + knotVector[5] = 2.0 * knotVector[4] - knotVector[3]; + break; + default: + knotVector[5] = this.keys[this.curKeyIdx + 4]; + break; + } + break; + case 4: + controlPoints[0] = this.keys[this.curKeyIdx + -3]; + knotVector[1] = this.keys[this.curKeyIdx + -4]; + knotVector[0] = 2.0 * knotVector[1] - knotVector[2]; + switch (keysAfter) { + case 2: + controlPoints[3] = 2.0 * controlPoints[2] - controlPoints[1]; + knotVector[4] = 2.0 * knotVector[3] - knotVector[2]; + knotVector[5] = 2.0 * knotVector[3] - knotVector[1]; + break; + case 4: + controlPoints[3] = this.keys[this.curKeyIdx + 3]; + knotVector[4] = this.keys[this.curKeyIdx + 2]; + knotVector[5] = 2.0 * knotVector[4] - knotVector[3]; + break; + default: + controlPoints[3] = this.keys[this.curKeyIdx + 3]; + knotVector[4] = this.keys[this.curKeyIdx + 2]; + knotVector[5] = this.keys[this.curKeyIdx + 4]; + } + break; + default: + controlPoints[0] = this.keys[this.curKeyIdx + -3]; + knotVector[1] = this.keys[this.curKeyIdx + -4]; + knotVector[0] = this.keys[this.curKeyIdx + -6]; + switch (keysAfter) { + case 2: + controlPoints[3] = 2.0 * controlPoints[2] - controlPoints[1]; + knotVector[4] = 2.0 * knotVector[3] - knotVector[2]; + knotVector[5] = 2.0 * knotVector[3] - knotVector[1]; + break; + case 4: + controlPoints[3] = this.keys[this.curKeyIdx + 3]; + knotVector[4] = this.keys[this.curKeyIdx + 2]; + knotVector[5] = 2.0 * knotVector[4] - knotVector[3]; + break; + default: + controlPoints[3] = this.keys[this.curKeyIdx + 3]; + knotVector[4] = this.keys[this.curKeyIdx + 2]; + knotVector[5] = this.keys[this.curKeyIdx + 4]; + break; + } + break; + } + + return Attribute.Interpolate.BSpline_Nonuniform(t, controlPoints, knotVector); + } + + interpolateLinear(t: number) { + const ks = this.keys; + const c = this.curKeyIdx; + return Attribute.Interpolate.Linear(t, ks[c - 2], ks[c - 1], ks[c + 0], ks[c + 1]); + } + } + + //---------------------------------------------------------------------------------------------------------------------- + // FVB Objects + // Manages a FunctionValue. + //---------------------------------------------------------------------------------------------------------------------- + class TObject_ListParameter extends FVB.TObject { + override funcVal = new FunctionValue_ListParameter; + + override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + assert(para.dataSize >= 8); + // Each Key contains 2 floats, a time and value + const keyCount = file.view.getUint32(para.dataOffset + 0); + const keys = file.buffer.createTypedArray(Float32Array, para.dataOffset + 4, keyCount * 2, Endianness.BIG_ENDIAN); + this.funcVal.set_data(keys); + } + } +} + //---------------------------------------------------------------------------------------------------------------------- // STB Parsing //---------------------------------------------------------------------------------------------------------------------- @@ -679,55 +1155,15 @@ export abstract class TBlockObject { /* 0xC + align(id_size, 4) */ data: Reader; } -class TParagraph { - type: number; - dataSize: number; - dataOffset: number; - nextOffset: number; -} - -function parseParagraph(view: DataView, byteIdx: number): TParagraph { - // The top bit of the paragraph determines if the type and size are 16 bit (if set), or 32 (if not set) - let dataSize = view.getUint16(byteIdx); - let type; - let offset; - - if ((dataSize & 0x8000) == 0) { - // 16 bit data - type = view.getUint16(byteIdx + 2); - offset = 4; - } else { - // 32 bit data - dataSize = view.getUint32(byteIdx + 0) & ~0x80000000; - type = view.getUint32(byteIdx + 4); - offset = 8; - } - - if (dataSize == 0) { - return { dataSize, type, dataOffset: 0, nextOffset: byteIdx + offset }; - } else { - return { dataSize, type, dataOffset: byteIdx + offset, nextOffset: byteIdx + offset + align(dataSize, 4) }; - } -} - -class Reader { - buffer: ArrayBufferSlice; - view: DataView; - offset: number; - - constructor(buffer: ArrayBufferSlice, offset: number) { - this.buffer = buffer.subarray(offset); - this.view = this.buffer.createDataView(); - this.offset = 0; - } -} - +// This combines JStudio::TControl and JStudio::stb::TControl into a single class, for simplicity. export class TControl { private mSystem: TSystem; - private mObjects: STBObject[] = []; + public mFvbControl = new FVB.TControl(); public mSecondsPerFrame: number; public mIsSuspended = false; + private mObjects: STBObject[] = []; + constructor(system: TSystem) { this.mSystem = system; this.mSecondsPerFrame = 1 / 30.0; // @TODO: Allow the game to change this? @@ -743,7 +1179,10 @@ export class TControl { return 1; } - // Really this is a Factory method + public getFunctionValueByIdx(idx: number) { return this.mFvbControl.mObjects[idx]?.funcVal; } + public getFunctionValueByName(name: string) { return this.mFvbControl.mObjects.find(v => v.id == name)?.funcVal; } + + // Really this is a stb::TFactory method public createObject(blockObj: TBlockObject): STBObject | undefined { let objConstructor; let objType: JStage.TEObject; @@ -769,18 +1208,16 @@ export class TControl { public destroyObject_all() { this.mObjects = []; + this.mFvbControl.destroyObject_all(); } } export class TParse { constructor( - private mControl: TControl + private mControl: TControl, + private mFvbParse = new FVB.TParse(mControl.mFvbControl) ) { } - private parseFVB(data: ArrayBufferSlice) { - - } - // Parse an entire scene's worth of object sequences at once private parseBlockObject(file: Reader, flags: number) { const blockObj: TBlockObject = { @@ -806,7 +1243,6 @@ export class TParse { return true; } - // Create the object, using overloaded functions from the game const obj = this.mControl.createObject(blockObj); if (!obj) { if (flags & 0x40) { @@ -835,15 +1271,12 @@ export class TParse { assert(byteOrder == 0xFEFF); let byteIdx = file.offs; - let blockCount = file.view.getUint32(12); - - for (let i = 0; i < blockCount; i++) { + for (let i = 0; i < file.numChunks; i++) { const blockSize = file.view.getUint32(byteIdx + 0); const blockType = readString(file.buffer, byteIdx + 4, 4); - const blockData = data.slice(byteIdx + 8, byteIdx + blockSize); if (blockType == 'JFVB') { - this.parseFVB(blockData) + this.mFvbParse.parse(file.buffer.subarray(byteIdx + 8, blockSize - 8), flags) } else { this.parseBlockObject(new Reader(file.buffer, byteIdx), flags); } From 568abdd266722139e6c29e4609a6c7bab534a986 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 2 Nov 2024 13:27:55 -0600 Subject: [PATCH 006/102] Finished implementing TCameraAdaptor. Values are being fed all the way through to the Demo camera, and they seem reasonable. - Fixed some issues in the BSpline interpolation implementation --- src/Common/JSYSTEM/JStudio.ts | 158 +++++++++++++++------------------- 1 file changed, 69 insertions(+), 89 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index b9d60faa9..4e115e7a5 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -75,7 +75,7 @@ class TVariableValue { private mAge: number; // In frames private mUpdateFunc?: (varval: TVariableValue, x: number) => void; private mUpdateParam: number | FVB.TFunctionValue | undefined; - // @TODO: TOutput + private mOutputFunc?: (val: number, adaptor: TAdaptor) => void; getValue() { return this.mValue; } getValueU8() { return clamp(this.mValue, 0, 255); } @@ -91,7 +91,7 @@ class TVariableValue { update(secondsPerFrame: number, adaptor: TAdaptor): void { if (this.mUpdateFunc) { this.mUpdateFunc(this, secondsPerFrame); - // (* pOutput_)(mValue, param_1); // @TODO: Output + if (this.mOutputFunc) this.mOutputFunc(this.mValue, adaptor); } } @@ -143,62 +143,15 @@ class TVariableValue { this.mUpdateParam = v; } - - // struct TOutput { - // virtual void operator()(f32, JStudio::TAdaptor*) const = 0; - // virtual ~TOutput() = 0; - // }; // Size: 0x04 - - // struct TOutput_none_ : public TOutput { - // ~TOutput_none_(); - // void operator()(f32, JStudio::TAdaptor*) const; - // }; - - // TVariableValue() - // : field_0x4(0) - // , field_0x8(NULL) - // , pOutput_(&soOutput_none_) - // { - // } - - // f32 getValue() const { return mValue; } - - // template - // T getValue_clamp() const { - // f32 val = mValue; - // if (val <= std::numeric_limits::min()) { - // return std::numeric_limits::min(); - // } else if (val >= std::numeric_limits::max()) { - // return std::numeric_limits::max(); - // } - // return val; - // } - // u8 getValue_uint8() const { return getValue_clamp(); } - - // void forward(u32 param_0) { - // if (std::numeric_limits::max() - field_0x4 <= param_0) { - // field_0x4 = std::numeric_limits::max(); - // } else { - // field_0x4 += param_0; - // } - // } - - // inline void setOutput(const TOutput* output) { - // pOutput_ = (output != NULL ? (TOutput*)output : (TOutput*)&soOutput_none_); - // } - - // static TOutput_none_ soOutput_none_; - - // /* 0x00 */ f32 mValue; - // /* 0x04 */ u32 field_0x4; - // /* 0x08 */ void (*field_0x8)(TVariableValue*, double); - // /* 0x0C */ union { - // TFunctionValue* fv; - // f32 val; - // } field_0xc; - // /* 0x10 */ TOutput* pOutput_; + //-------------------- + // Set Output + //-------------------- + setOutput(outputFunc?: (val: number, adaptor: TAdaptor) => void) { + this.mOutputFunc = outputFunc; + } } + //---------------------------------------------------------------------------------------------------------------------- // TAdaptor // Connects the STBObject to a Game Object. Manages tracks of TVariableValues, updates their values on the Game object. @@ -485,6 +438,7 @@ abstract class STBObject { //---------------------------------------------------------------------------------------------------------------------- // Camera //---------------------------------------------------------------------------------------------------------------------- +// TODO: Rename these Enums const enum Camera_Cmd { SET_EYE_X_POS = 0x0015, SET_EYE_Y_POS = 0x0016, @@ -547,11 +501,26 @@ class TCameraAdaptor extends TAdaptor { ) { super(11); } adaptor_do_prepare(): void { - // @TODO: Setup output functions + this.mVariableValues[6].setOutput(this.mStageCam.JSGSetProjectionFovy); + this.mVariableValues[7].setOutput(this.mStageCam.JSGSetViewRoll); + this.mVariableValues[8].setOutput(this.mStageCam.JSGSetProjectionNear); + this.mVariableValues[9].setOutput(this.mStageCam.JSGSetProjectionFar); } adaptor_do_begin(): void { - // @TODO: + const camPos = scratchVec3a; + const targetPos = scratchVec3a; + this.mStageCam.JSGGetViewPosition(camPos); + this.mStageCam.JSGGetViewTargetPosition(targetPos); + + // @TODO: Transform positions + + this.adaptor_setVariableValue_Vec(Camera_Track.EYE_X_POS, camPos); + this.adaptor_setVariableValue_Vec(Camera_Track.TARGET_X_POS, targetPos); + this.mVariableValues[6].setValue_immediate(this.mStageCam.JSGGetProjectionFovy()); + this.mVariableValues[7].setValue_immediate(this.mStageCam.JSGGetViewRoll()); + this.mVariableValues[8].setValue_immediate(this.mStageCam.JSGGetProjectionNear()); + this.mVariableValues[9].setValue_immediate(this.mStageCam.JSGGetProjectionFar()); } adaptor_do_end(): void { @@ -559,7 +528,16 @@ class TCameraAdaptor extends TAdaptor { } adaptor_do_update(frameCount: number): void { - // @TODO: + const camPos = scratchVec3a; + const targetPos = scratchVec3a; + + this.adaptor_getVariableValue_Vec(camPos, Camera_Track.EYE_X_POS); + this.adaptor_getVariableValue_Vec(targetPos, Camera_Track.TARGET_X_POS); + + // @TODO: Transform + + this.mStageCam.JSGSetViewPosition(camPos); + this.mStageCam.JSGSetViewTargetPosition(targetPos); } adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void { @@ -638,7 +616,7 @@ class TCameraObject extends STBObject { } for (let i = 0; i < keyCount; i++) { - this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, data); + this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, (data as number) + i); } } } @@ -1023,74 +1001,75 @@ namespace FVB { return this.interpFunc(t); } + // @TODO: Better way of accessing 2-word keys interpolateBSpline(t: number): number { const controlPoints = new Float64Array(4); const knotVector = new Float64Array(6); - controlPoints[1] = this.keys[this.curKeyIdx - 1]; - controlPoints[2] = this.keys[this.curKeyIdx + 1]; - knotVector[2] = this.keys[this.curKeyIdx + -2]; - knotVector[3] = this.keys[this.curKeyIdx + 0]; + controlPoints[1] = this.keys[this.curKeyIdx * 2 - 1]; + controlPoints[2] = this.keys[this.curKeyIdx * 2 + 1]; + knotVector[2] = this.keys[this.curKeyIdx * 2 + -2]; + knotVector[3] = this.keys[this.curKeyIdx * 2 + 0]; const keysBefore = this.curKeyIdx; const keysAfter = this.keyCount - this.curKeyIdx; switch (keysBefore) { - case 2: + case 1: controlPoints[0] = 2.0 * controlPoints[1] - controlPoints[2]; - controlPoints[3] = this.keys[this.curKeyIdx + 3]; - knotVector[4] = this.keys[this.curKeyIdx + 2]; + controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; + knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; knotVector[1] = 2.0 * knotVector[2] - knotVector[3]; knotVector[0] = 2.0 * knotVector[2] - knotVector[4]; switch (keysAfter) { + case 1: case 2: - case 4: knotVector[5] = 2.0 * knotVector[4] - knotVector[3]; break; default: - knotVector[5] = this.keys[this.curKeyIdx + 4]; + knotVector[5] = this.keys[this.curKeyIdx * 2 + 4]; break; } break; - case 4: - controlPoints[0] = this.keys[this.curKeyIdx + -3]; - knotVector[1] = this.keys[this.curKeyIdx + -4]; + case 2: + controlPoints[0] = this.keys[this.curKeyIdx * 2 + -3]; + knotVector[1] = this.keys[this.curKeyIdx * 2 + -4]; knotVector[0] = 2.0 * knotVector[1] - knotVector[2]; switch (keysAfter) { - case 2: + case 1: controlPoints[3] = 2.0 * controlPoints[2] - controlPoints[1]; knotVector[4] = 2.0 * knotVector[3] - knotVector[2]; knotVector[5] = 2.0 * knotVector[3] - knotVector[1]; break; - case 4: - controlPoints[3] = this.keys[this.curKeyIdx + 3]; - knotVector[4] = this.keys[this.curKeyIdx + 2]; + case 2: + controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; + knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; knotVector[5] = 2.0 * knotVector[4] - knotVector[3]; break; default: - controlPoints[3] = this.keys[this.curKeyIdx + 3]; - knotVector[4] = this.keys[this.curKeyIdx + 2]; - knotVector[5] = this.keys[this.curKeyIdx + 4]; + controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; + knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; + knotVector[5] = this.keys[this.curKeyIdx * 2 + 4]; } break; default: - controlPoints[0] = this.keys[this.curKeyIdx + -3]; - knotVector[1] = this.keys[this.curKeyIdx + -4]; - knotVector[0] = this.keys[this.curKeyIdx + -6]; + controlPoints[0] = this.keys[this.curKeyIdx * 2 + -3]; + knotVector[1] = this.keys[this.curKeyIdx * 2 + -4]; + knotVector[0] = this.keys[this.curKeyIdx * 2 + -6]; switch (keysAfter) { - case 2: + case 1: controlPoints[3] = 2.0 * controlPoints[2] - controlPoints[1]; knotVector[4] = 2.0 * knotVector[3] - knotVector[2]; knotVector[5] = 2.0 * knotVector[3] - knotVector[1]; break; - case 4: - controlPoints[3] = this.keys[this.curKeyIdx + 3]; - knotVector[4] = this.keys[this.curKeyIdx + 2]; + case 2: + controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; + knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; knotVector[5] = 2.0 * knotVector[4] - knotVector[3]; break; default: - controlPoints[3] = this.keys[this.curKeyIdx + 3]; - knotVector[4] = this.keys[this.curKeyIdx + 2]; - knotVector[5] = this.keys[this.curKeyIdx + 4]; + controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; + knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; + knotVector[5] = this.keys[this.curKeyIdx * 2 + 4]; break; } break; @@ -1202,6 +1181,7 @@ export class TControl { } const obj = new objConstructor(this, blockObj, stageObj); + obj.mAdaptor.adaptor_do_prepare(); this.mObjects.push(obj); return obj; } From 51d52ed1766d8abc0a11d457175452aa60c1acf9 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 2 Nov 2024 15:05:20 -0600 Subject: [PATCH 007/102] Added transformations to TControl. Demos can now have an origin and rotation. Confirmed that the camera position (both eye and target) are working as expected with demo51.stb (the Wind Waker opening cutscene). Woo! --- src/Common/JSYSTEM/JStudio.ts | 57 ++++++++++++++++++++++++----------- src/ZeldaWindWaker/d_demo.ts | 30 ++++++++++-------- 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 4e115e7a5..4957f698b 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -171,11 +171,11 @@ abstract class TAdaptor { protected mVariableValues = nArray(mCount, i => new TVariableValue()), ) { } - abstract adaptor_do_prepare(): void; - abstract adaptor_do_begin(): void; - abstract adaptor_do_end(): void; - abstract adaptor_do_update(frameCount: number): void; - abstract adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void; + abstract adaptor_do_prepare(obj: STBObject): void; + abstract adaptor_do_begin(obj: STBObject): void; + abstract adaptor_do_end(obj: STBObject): void; + abstract adaptor_do_update(obj: STBObject, frameCount: number): void; + abstract adaptor_do_data(obj: STBObject, unk0: Object, unk1: number, unk2: Object, unk3: number): void; // Set a single VariableValue update function, with the option of using FuncVals adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: TEOperationData, data: number | string) { @@ -294,13 +294,13 @@ abstract class STBObject { // These are intended to be overridden by subclasses abstract do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void; - do_begin() { } - do_end() { } + do_begin() { this.mAdaptor.adaptor_do_begin(this); } + do_end() { this.mAdaptor.adaptor_do_end(this); } // Done updating this frame. Compute our variable data (i.e. interpolate) and send to the game object. do_wait(frameCount: number) { this.mAdaptor.adaptor_updateVariableValue(this, frameCount); - this.mAdaptor.adaptor_do_update(frameCount); + this.mAdaptor.adaptor_do_update(this, frameCount); } // do_data(void const*, u32, void const*, u32) {} @@ -500,20 +500,21 @@ class TCameraAdaptor extends TAdaptor { private mStageCam: TCamera ) { super(11); } - adaptor_do_prepare(): void { + adaptor_do_prepare(obj: STBObject): void { this.mVariableValues[6].setOutput(this.mStageCam.JSGSetProjectionFovy); this.mVariableValues[7].setOutput(this.mStageCam.JSGSetViewRoll); this.mVariableValues[8].setOutput(this.mStageCam.JSGSetProjectionNear); this.mVariableValues[9].setOutput(this.mStageCam.JSGSetProjectionFar); } - adaptor_do_begin(): void { + adaptor_do_begin(obj: STBObject): void { const camPos = scratchVec3a; - const targetPos = scratchVec3a; + const targetPos = scratchVec3b; this.mStageCam.JSGGetViewPosition(camPos); this.mStageCam.JSGGetViewTargetPosition(targetPos); - // @TODO: Transform positions + vec3.transformMat4(camPos, camPos, obj.mControl.getTransformOnGet()); + vec3.transformMat4(targetPos, targetPos, obj.mControl.getTransformOnGet()); this.adaptor_setVariableValue_Vec(Camera_Track.EYE_X_POS, camPos); this.adaptor_setVariableValue_Vec(Camera_Track.TARGET_X_POS, targetPos); @@ -523,18 +524,19 @@ class TCameraAdaptor extends TAdaptor { this.mVariableValues[9].setValue_immediate(this.mStageCam.JSGGetProjectionFar()); } - adaptor_do_end(): void { + adaptor_do_end(obj: STBObject): void { this.mStageCam.JSGFDisableFlag(1); } - adaptor_do_update(frameCount: number): void { + adaptor_do_update(obj: STBObject, frameCount: number): void { const camPos = scratchVec3a; - const targetPos = scratchVec3a; + const targetPos = scratchVec3b; this.adaptor_getVariableValue_Vec(camPos, Camera_Track.EYE_X_POS); this.adaptor_getVariableValue_Vec(targetPos, Camera_Track.TARGET_X_POS); - // @TODO: Transform + vec3.transformMat4(camPos, camPos, obj.mControl.getTransformOnSet()); + vec3.transformMat4(targetPos, targetPos, obj.mControl.getTransformOnSet()); this.mStageCam.JSGSetViewPosition(camPos); this.mStageCam.JSGSetViewTargetPosition(targetPos); @@ -1141,6 +1143,11 @@ export class TControl { public mSecondsPerFrame: number; public mIsSuspended = false; + private mTransformOrigin?: vec3; + private mTransformRotY?: number; + private mTransformOnGetMtx = mat4.create(); + private mTransformOnSetMtx = mat4.create(); + private mObjects: STBObject[] = []; constructor(system: TSystem) { @@ -1150,6 +1157,22 @@ export class TControl { public isSuspended() { return this.mIsSuspended; } + public isTransformEnabled() { return !!this.mTransformOrigin; } + public getTransformOnSet() { return this.mTransformOnSetMtx; } + public getTransformOnGet() { return this.mTransformOnGetMtx; } + public transformSetOrigin(originPos: vec3, rotY: number) { + this.mTransformOrigin = originPos; + this.mTransformRotY = rotY; + + // The "OnGet" matrix transforms from world space into demo space + mat4.fromYRotation(this.mTransformOnGetMtx, -rotY); + mat4.translate(this.mTransformOnGetMtx, this.mTransformOnGetMtx, vec3.negate(scratchVec3a, originPos)); + + // The "OnSet" matrix is the inverse + mat4.fromTranslation(this.mTransformOnSetMtx, originPos); + mat4.rotateY(this.mTransformOnSetMtx, this.mTransformOnSetMtx, rotY); + } + public forward(flags: number): number { for (let obj of this.mObjects) { const res = obj.forward(flags); @@ -1181,7 +1204,7 @@ export class TControl { } const obj = new objConstructor(this, blockObj, stageObj); - obj.mAdaptor.adaptor_do_prepare(); + obj.mAdaptor.adaptor_do_prepare(obj); this.mObjects.push(obj); return obj; } diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 3b0796e21..e3cbc14bf 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -15,7 +15,9 @@ class dDemo_camera_c extends TCamera { mTargetPosition: vec3 = vec3.create(); mRoll: number = 0; - globals: dGlobals; + constructor( + private globals: dGlobals + ) { super() } override JSGGetProjectionNear(): number { const camera = this.globals.camera; @@ -126,17 +128,21 @@ class dDemo_camera_c extends TCamera { } class dDemo_system_c implements TSystem { - private mpActiveCamera?: dDemo_camera_c; + public mpActiveCamera?: dDemo_camera_c; // private mpActors: dDemo_actor_c[]; // private mpAmbient: dDemo_ambient_c; // private mpLight: dDemo_light_c[]; // private mpFog: dDemo_fog_c; + constructor( + private globals: dGlobals + ) {} + public JSGFindObject(objId: string, objType: JStage.TEObject): JStage.TObject | undefined { switch (objType) { case JStage.TEObject.CAMERA: if (this.mpActiveCamera) return this.mpActiveCamera; - else return this.mpActiveCamera = new dDemo_camera_c(); + else return this.mpActiveCamera = new dDemo_camera_c(this.globals); case JStage.TEObject.ACTOR: case JStage.TEObject.ACTOR_UNK: case JStage.TEObject.AMBIENT: @@ -160,10 +166,14 @@ export class dDemo_manager_c { private mCurFile?: ArrayBufferSlice; private mParser: TParse; - private mSystem = new dDemo_system_c(); + private mSystem = new dDemo_system_c(this.globals); private mControl: TControl = new TControl(this.mSystem); - public create(data: ArrayBufferSlice, originPos: vec3, rotY: number): boolean { + constructor( + private globals: dGlobals + ) {} + + public create(data: ArrayBufferSlice, originPos?: vec3, rotY?: number): boolean { this.mParser = new TParse(this.mControl); if(!this.mParser.parse(data, 0)) { @@ -172,13 +182,9 @@ export class dDemo_manager_c { } this.mControl.forward(0); - // @TODO: - // if (originPos == NULL) { - // mControl->transform_enable(false); - // } else { - // mControl->transform_enable(true); - // mControl->transform_setOrigin(*originPos, rotY); - // } + if (originPos) { + this.mControl.transformSetOrigin(originPos, rotY || 0); + } this.mFrame = 0; this.mFrameNoMsg = 0; From 281aeacabbf40ddd6b157a44f35ed40d18580004 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 3 Nov 2024 08:10:09 -0700 Subject: [PATCH 008/102] Small cleanup --- src/Common/JSYSTEM/JStudio.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 4957f698b..2f212951b 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -1,4 +1,4 @@ -// Nintendo's cutscene framework. Seems very over-engineered. +// Nintendo's cutscene framework. Seems very over-engineered. Data is stored in a STB (Studio Binary) file. import { mat4, ReadonlyVec3, vec3 } from "gl-matrix"; import ArrayBufferSlice from "../../ArrayBufferSlice"; @@ -7,12 +7,9 @@ import { JSystemFileReaderHelper } from "./J3D/J3DLoader.js"; import { GfxColor } from "../../gfx/platform/GfxPlatform"; import { clamp } from "../../MathHelpers.js"; import { Endianness } from "../../endian.js"; -import { ArtObjectData } from "../../Fez/ArtObjectData"; const scratchVec3a = vec3.create(); const scratchVec3b = vec3.create(); -const scratchVec3c = vec3.create(); -const scratchVec3d = vec3.create(); //---------------------------------------------------------------------------------------------------------------------- // Stage Objects From b5494b6feb68583b35e03692ba510f9941f53a61 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 3 Nov 2024 08:09:09 -0700 Subject: [PATCH 009/102] Fix a bug in FunctionValue_ListParameter::getValue() that would produce NaNs at the end of the sequence I was incorrectly checking for an invalid curKeyIdx (because of the `/ 2`) which was skipping the end-of-list check and attempting to interpolate off the end of the array. --- src/Common/JSYSTEM/JStudio.ts | 45 +++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 2f212951b..0fa94673b 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -269,13 +269,13 @@ abstract class STBObject { private mId: string; private mType: string; private mFlags: number; + private mStatus: TEStatus = TEStatus.STILL; private mIsSequence: boolean = false; private mSuspendFrames: number = 0; private mData: Reader; private pSequence: number; private pSequence_next: number; private mWait: number = 0; - private mStatus: TEStatus = TEStatus.STILL; constructor(control: TControl, blockObj: TBlockObject, adaptor: TAdaptor) { this.mControl = control; @@ -301,11 +301,12 @@ abstract class STBObject { } // do_data(void const*, u32, void const*, u32) {} + getStatus() { return this.mStatus; } isSuspended(): boolean { return this.mSuspendFrames > 0; } - forward(frameCount: number) { + forward(frameCount: number): boolean { let hasWaited = false; while (true) { // Top bit of mFlags makes this object immediately inactive, restarting any existing sequence @@ -316,7 +317,7 @@ abstract class STBObject { this.do_end(); } } - return 1; + return true; } if (this.mStatus == TEStatus.INACTIVE) { @@ -331,7 +332,7 @@ abstract class STBObject { this.mStatus = TEStatus.SUSPEND; this.do_wait(frameCount); } - return 1; + return true; } while (true) { @@ -348,7 +349,7 @@ abstract class STBObject { this.mStatus = TEStatus.END; this.do_end(); } - return 0; + return false; } // If we're not currently running a sequence, start it @@ -377,7 +378,7 @@ abstract class STBObject { } else { this.mWait -= frameCount; this.do_wait(frameCount); - return 1; + return true; } } } @@ -992,12 +993,18 @@ namespace FVB { if (this.curKeyIdx == 0) { // Time is at or before the start, return the first key return this.keys[this.curKeyIdx * 2 + 1]; - } else if (this.curKeyIdx == -1) { // Time is at or after the end, return the last key + } else if (this.curKeyIdx < 0) { // Time is at or after the end, return the last key this.curKeyIdx = this.keyCount - 1; return this.keys[this.curKeyIdx * 2 + 1]; } - return this.interpFunc(t); + const value = this.interpFunc(t); + if (isNaN(value)) { + console.warn('NaN generated by FunctionValue'); + debugger; + } + + return value; } // @TODO: Better way of accessing 2-word keys @@ -1145,6 +1152,7 @@ export class TControl { private mTransformOnGetMtx = mat4.create(); private mTransformOnSetMtx = mat4.create(); + private mStatus: TEStatus = TEStatus.STILL; private mObjects: STBObject[] = []; constructor(system: TSystem) { @@ -1157,8 +1165,8 @@ export class TControl { public isTransformEnabled() { return !!this.mTransformOrigin; } public getTransformOnSet() { return this.mTransformOnSetMtx; } public getTransformOnGet() { return this.mTransformOnGetMtx; } - public transformSetOrigin(originPos: vec3, rotY: number) { - this.mTransformOrigin = originPos; + public transformSetOrigin(originPos: vec3, rotY: number) { + this.mTransformOrigin = originPos; this.mTransformRotY = rotY; // The "OnGet" matrix transforms from world space into demo space @@ -1170,12 +1178,22 @@ export class TControl { mat4.rotateY(this.mTransformOnSetMtx, this.mTransformOnSetMtx, rotY); } - public forward(flags: number): number { + public forward(flags: number): boolean { + let shouldContinue = false; + let andStatus = 0xFF; + let orStatus = 0; + for (let obj of this.mObjects) { const res = obj.forward(flags); - // @TODO: Set status + shouldContinue ||= res; + + const objStatus = obj.getStatus(); + andStatus &= objStatus; + orStatus |= objStatus; } - return 1; + + this.mStatus = (andStatus | (orStatus << 0x10)); + return shouldContinue; } public getFunctionValueByIdx(idx: number) { return this.mFvbControl.mObjects[idx]?.funcVal; } @@ -1231,6 +1249,7 @@ export class TParse { const blockTypeNum = file.view.getInt32(4); if (blockTypeNum == -1) { console.debug('Unhandled STB block type: -1'); + debugger; // Remove after implementing and testing } if (flags & 0x10) { From 4e982a5fc32747c1b0f064cba2cdb1c9c54a0107 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 3 Nov 2024 08:27:48 -0700 Subject: [PATCH 010/102] Demo timing changes. Now respects NoClip pause, and matches the original timing --- src/ZeldaWindWaker/d_demo.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index e3cbc14bf..465fbd260 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -205,7 +205,10 @@ export class dDemo_manager_c { if (!this.mCurFile) { return false; } - if (this.mControl.forward(1)) { + + const dtFrames = this.globals.context.viewerInput.deltaTime / 1000.0 * 30; + + if (this.mControl.forward(dtFrames)) { this.mFrame++; if (!this.mControl.isSuspended()) { this.mFrameNoMsg++; From d3c3b07b40810d63287fa0e284f077cf545dd1a0 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 3 Nov 2024 09:42:12 -0700 Subject: [PATCH 011/102] Hook up dDemo_manager_c in d_s_play It currently overrides the camera parameters in d_s_play.execute(), but this is not correct. I need to find the proper place/method for this. --- src/ZeldaWindWaker/Main.ts | 18 ++++++++++++++++++ src/ZeldaWindWaker/d_demo.ts | 17 ++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 738258759..2cbe9aba8 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -39,6 +39,7 @@ import { dPa_control_c } from './d_particle.js'; import { ResType, dRes_control_c } from './d_resorce.js'; import { dStage_dt_c_roomLoader, dStage_dt_c_roomReLoader, dStage_dt_c_stageInitLoader, dStage_dt_c_stageLoader, dStage_roomStatus_c, dStage_stageDt_c } from './d_stage.js'; import { cPhs__Status, fGlobals, fopAcM_create, fopAc_ac_c, fopDw_Draw, fopScn, fpcCt_Handler, fpcLy_SetCurrentLayer, fpcM_Management, fpcPf__Register, fpcSCtRq_Request, fpc__ProcessName, fpc_pc__ProfileList } from './framework.js'; +import { dDemo_manager_c, EDemoMode } from './d_demo.js'; type SymbolData = { Filename: string, SymbolName: string, Data: ArrayBufferSlice }; type SymbolMapData = { SymbolData: SymbolData[] }; @@ -736,6 +737,7 @@ export const pathBase = `ZeldaWindWaker`; class d_s_play extends fopScn { public bgS = new dBgS(); + public demo: dDemo_manager_c; public flowerPacket: FlowerPacket; public treePacket: TreePacket; @@ -747,6 +749,8 @@ class d_s_play extends fopScn { public override load(globals: dGlobals, userData: any): cPhs__Status { super.load(globals, userData); + this.demo = new dDemo_manager_c(globals); + this.treePacket = new TreePacket(globals); this.flowerPacket = new FlowerPacket(globals); this.grassPacket = new GrassPacket(globals); @@ -757,6 +761,20 @@ class d_s_play extends fopScn { return cPhs__Status.Complete; } + public override execute(globals: dGlobals, deltaTimeFrames: number): void { + this.demo.update(); + + // @TODO: Determine the correct place for this + if (this.demo.getMode() == EDemoMode.Playing) { + const viewPos = this.demo.getSystem().mpActiveCamera?.mViewPosition; + const targetPos = this.demo.getSystem().mpActiveCamera?.mTargetPosition; + if (viewPos) { + mat4.targetTo(globals.camera.worldMatrix, viewPos, targetPos, [0, 1, 0]); + globals.camera.worldMatrixUpdated(); + } + } + } + public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: Viewer.ViewerRenderInput): void { super.draw(globals, renderInstManager, viewerInput); diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 465fbd260..b9b819b8a 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -4,6 +4,12 @@ import { TParse, JStage, TSystem, TControl, TCamera } from "../Common/JSYSTEM/JS import { getMatrixAxisY } from "../MathHelpers.js"; import { dGlobals } from "./Main"; +export enum EDemoMode { + None, + Playing, + Ended +} + class dDemo_camera_c extends TCamera { mFlags: number = 0; mProjNear: number = 0; @@ -162,7 +168,7 @@ class dDemo_system_c implements TSystem { export class dDemo_manager_c { private mFrame: number; private mFrameNoMsg: number; - private mMode: number; + private mMode = EDemoMode.None; private mCurFile?: ArrayBufferSlice; private mParser: TParse; @@ -173,6 +179,11 @@ export class dDemo_manager_c { private globals: dGlobals ) {} + getFrame() { return this.mFrame; } + getFrameNoMsg() { return this.mFrameNoMsg; } + getMode() { return this.mMode; } + getSystem() { return this.mSystem; } + public create(data: ArrayBufferSlice, originPos?: vec3, rotY?: number): boolean { this.mParser = new TParse(this.mControl); @@ -189,7 +200,7 @@ export class dDemo_manager_c { this.mFrame = 0; this.mFrameNoMsg = 0; this.mCurFile = data; - this.mMode = 1; + this.mMode = EDemoMode.Playing; return true; } @@ -214,7 +225,7 @@ export class dDemo_manager_c { this.mFrameNoMsg++; } } else { - this.mMode = 2; + this.mMode = EDemoMode.Ended; } return true; } From 3cbaba12eb61f35e313882bb510c902b14224686 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 3 Nov 2024 09:43:21 -0700 Subject: [PATCH 012/102] Hackily hook up cutscene loading in CreateScene. "Opening Cutscene" is now a selectable Scene. --- src/ZeldaWindWaker/Main.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 2cbe9aba8..472044d3c 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -813,7 +813,7 @@ class d_s_play extends fopScn { class SceneDesc { public id: string; - public constructor(public stageDir: string, public name: string, public roomList: number[] = [0]) { + public constructor(public stageDir: string, public name: string, public roomList: number[] = [0], public cutscene?: string) { this.id = stageDir; // Garbage hack. @@ -933,6 +933,15 @@ class SceneDesc { dStage_dt_c_roomReLoader(globals, globals.roomStatus[roomNo], dzr); } + // @TODO: Improve and move to the correct place + if (this.cutscene) { + modelCache.fetchObjectData(this.cutscene); + await modelCache.waitForLoad(); + const stbData = modelCache.resCtrl.getObjectRes(ResType.Stb, this.cutscene, 0x3); + globals.scnPlay.demo.create(stbData, [-220000, 0, 320000], Math.PI); + console.log(stbData); + } + return renderer; } } @@ -960,6 +969,7 @@ const sceneDescs = [ new SceneDesc("Obshop", "Beedle's Shop", [1]), "Outset Island", + new SceneDesc("sea", "Opening Cutscene", [44], 'Demo51'), new SceneDesc("sea", "Outset Island", [44]), new SceneDesc("LinkRM", "Link's House"), new SceneDesc("LinkUG", "Under Link's House"), From 88fb0024d043d787cb88cc1a8cff51f13d54645c Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 3 Nov 2024 10:34:29 -0700 Subject: [PATCH 013/102] Slightly less hacky way of loading cutscenes Adjacent to the array of SceneDescs there is now an array of DemoDescs. Contains the name, arc file, stb index, origin, and rotation. All the data needed to load and play the demo, except for the scene it is expected to be in. --- src/ZeldaWindWaker/Main.ts | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 472044d3c..814655f49 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -764,7 +764,8 @@ class d_s_play extends fopScn { public override execute(globals: dGlobals, deltaTimeFrames: number): void { this.demo.update(); - // @TODO: Determine the correct place for this + // TODO: Determine the correct place for this + // dCamera_c::Store() sets the camera params if the demo camera is active if (this.demo.getMode() == EDemoMode.Playing) { const viewPos = this.demo.getSystem().mpActiveCamera?.mViewPosition; const targetPos = this.demo.getSystem().mpActiveCamera?.mTargetPosition; @@ -813,7 +814,7 @@ class d_s_play extends fopScn { class SceneDesc { public id: string; - public constructor(public stageDir: string, public name: string, public roomList: number[] = [0], public cutscene?: string) { + public constructor(public stageDir: string, public name: string, public roomList: number[] = [0], public demo?: DemoDesc) { this.id = stageDir; // Garbage hack. @@ -933,19 +934,33 @@ class SceneDesc { dStage_dt_c_roomReLoader(globals, globals.roomStatus[roomNo], dzr); } - // @TODO: Improve and move to the correct place - if (this.cutscene) { - modelCache.fetchObjectData(this.cutscene); - await modelCache.waitForLoad(); - const stbData = modelCache.resCtrl.getObjectRes(ResType.Stb, this.cutscene, 0x3); - globals.scnPlay.demo.create(stbData, [-220000, 0, 320000], Math.PI); - console.log(stbData); - } + // TODO: Improve and move to the correct place + if( this.demo) + this.demo.load(globals, modelCache); return renderer; } } +class DemoDesc { + public id: string; + + public constructor(public arcName: string, public name: string, private stbIndex: number, private originPos?: vec3, private rotY?: number ) { + this.id = arcName; + } + + async load(globals: dGlobals, modelCache: ModelCache) { + modelCache.fetchObjectData(this.arcName); + await modelCache.waitForLoad(); + const stbData = modelCache.resCtrl.getObjectRes(ResType.Stb, this.arcName, this.stbIndex); + globals.scnPlay.demo.create(stbData, this.originPos, this.rotY); + } +} + +const demoDescs = { + Demo51: new DemoDesc("Demo51", "Start Screen", 0x03, [-220000, 0, 320000], Math.PI), +} + // Location names taken from CryZe's Debug Menu. // https://github.com/CryZe/WindWakerDebugMenu/blob/master/src/warp_menu/consts.rs const sceneDescs = [ @@ -969,7 +984,7 @@ const sceneDescs = [ new SceneDesc("Obshop", "Beedle's Shop", [1]), "Outset Island", - new SceneDesc("sea", "Opening Cutscene", [44], 'Demo51'), + new SceneDesc("sea", "Opening Cutscene", [44], demoDescs.Demo51), new SceneDesc("sea", "Outset Island", [44]), new SceneDesc("LinkRM", "Link's House"), new SceneDesc("LinkUG", "Under Link's House"), From a288138ce24e773f76495938539093e785c7c55a Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 3 Nov 2024 10:53:02 -0700 Subject: [PATCH 014/102] Allow the FPSCamera to take control when a demo is paused Resuming the demo will snap the camera back to the demo camera --- src/ZeldaWindWaker/Main.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 814655f49..2f9c45f76 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -764,9 +764,13 @@ class d_s_play extends fopScn { public override execute(globals: dGlobals, deltaTimeFrames: number): void { this.demo.update(); + // noclip modification: if we're paused, allow noclip camera + const isPaused = globals.context.viewerInput.deltaTime === 0; + // TODO: Determine the correct place for this // dCamera_c::Store() sets the camera params if the demo camera is active - if (this.demo.getMode() == EDemoMode.Playing) { + if (this.demo.getMode() == EDemoMode.Playing && !isPaused) { + const viewPos = this.demo.getSystem().mpActiveCamera?.mViewPosition; const targetPos = this.demo.getSystem().mpActiveCamera?.mTargetPosition; if (viewPos) { From bbbfe341137ab60bb50e39750b373e756090124e Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 3 Nov 2024 11:47:05 -0700 Subject: [PATCH 015/102] Remove the demo camera when done. Only override camera params which have been set on the demo cam. --- src/ZeldaWindWaker/Main.ts | 39 ++++++++++++++++++++++++++---------- src/ZeldaWindWaker/d_demo.ts | 27 +++++++++++++++++-------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 2f9c45f76..935253e20 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -17,7 +17,7 @@ import { J3DModelInstance } from '../Common/JSYSTEM/J3D/J3DGraphBase.js'; import * as JPA from '../Common/JSYSTEM/JPA.js'; import { BTIData } from '../Common/JSYSTEM/JUTTexture.js'; import { dfRange } from '../DebugFloaters.js'; -import { getMatrixAxisZ, range } from '../MathHelpers.js'; +import { getMatrixAxisY, getMatrixAxisZ, range } from '../MathHelpers.js'; import { SceneContext } from '../SceneBase.js'; import { TextureMapping } from '../TextureHolder.js'; import { setBackbufferDescSimple, standardFullClearRenderPassDescriptor } from '../gfx/helpers/RenderGraphHelpers.js'; @@ -39,7 +39,7 @@ import { dPa_control_c } from './d_particle.js'; import { ResType, dRes_control_c } from './d_resorce.js'; import { dStage_dt_c_roomLoader, dStage_dt_c_roomReLoader, dStage_dt_c_stageInitLoader, dStage_dt_c_stageLoader, dStage_roomStatus_c, dStage_stageDt_c } from './d_stage.js'; import { cPhs__Status, fGlobals, fopAcM_create, fopAc_ac_c, fopDw_Draw, fopScn, fpcCt_Handler, fpcLy_SetCurrentLayer, fpcM_Management, fpcPf__Register, fpcSCtRq_Request, fpc__ProcessName, fpc_pc__ProfileList } from './framework.js'; -import { dDemo_manager_c, EDemoMode } from './d_demo.js'; +import { dDemo_manager_c, EDemoCamFlags, EDemoMode } from './d_demo.js'; type SymbolData = { Filename: string, SymbolName: string, Data: ArrayBufferSlice }; type SymbolMapData = { SymbolData: SymbolData[] }; @@ -318,6 +318,8 @@ const enum EffectDrawGroup { } const scratchMatrix = mat4.create(); +const scratchVec3a = vec3.create(); +const scratchVec3b = vec3.create(); export class WindWakerRenderer implements Viewer.SceneGfx { private mainColorDesc = new GfxrRenderTargetDescription(GfxFormat.U8_RGBA_RT); private mainDepthDesc = new GfxrRenderTargetDescription(GfxFormat.D32F); @@ -764,19 +766,34 @@ class d_s_play extends fopScn { public override execute(globals: dGlobals, deltaTimeFrames: number): void { this.demo.update(); - // noclip modification: if we're paused, allow noclip camera + // From executeEvtManager() -> SpecialProcPackage() + if (globals.scnPlay.demo.getMode() == EDemoMode.Ended) { + globals.scnPlay.demo.remove(); + } + + // noclip modification: if we're paused, allow noclip camera control const isPaused = globals.context.viewerInput.deltaTime === 0; // TODO: Determine the correct place for this // dCamera_c::Store() sets the camera params if the demo camera is active - if (this.demo.getMode() == EDemoMode.Playing && !isPaused) { - - const viewPos = this.demo.getSystem().mpActiveCamera?.mViewPosition; - const targetPos = this.demo.getSystem().mpActiveCamera?.mTargetPosition; - if (viewPos) { - mat4.targetTo(globals.camera.worldMatrix, viewPos, targetPos, [0, 1, 0]); - globals.camera.worldMatrixUpdated(); - } + const demoCam = this.demo.getSystem().mpActiveCamera; + if (demoCam && !isPaused) { + let viewPos = globals.cameraPosition; + let targetPos = vec3.add(scratchVec3a, globals.cameraPosition, globals.cameraFwd); + let upVec = vec3.set(scratchVec3b, 0, 1, 0); + + if(demoCam.mFlags & EDemoCamFlags.HasTargetPos) { targetPos = demoCam.mTargetPosition; } + if(demoCam.mFlags & EDemoCamFlags.HasEyePos) { viewPos = demoCam.mViewPosition; } + if(demoCam.mFlags & EDemoCamFlags.HasUpVec) { upVec = demoCam.mUpVector; } + mat4.targetTo(globals.camera.worldMatrix, viewPos, targetPos, upVec); + + if(demoCam.mFlags & EDemoCamFlags.HasFovY) { globals.camera.fovY = demoCam.mFovy; } + if(demoCam.mFlags & EDemoCamFlags.HasRoll) { debugger; /* Untested. Remove once confirmed working */ } + if(demoCam.mFlags & EDemoCamFlags.HasAspect) { debugger; /* Untested. Remove once confirmed working */ } + if(demoCam.mFlags & EDemoCamFlags.HasNearZ) { globals.camera.near = demoCam.mProjNear; debugger; /* Untested. Remove once confirmed working */ } + if(demoCam.mFlags & EDemoCamFlags.HasFarZ) { globals.camera.far = demoCam.mProjFar; debugger; /* Untested. Remove once confirmed working */ } + + globals.camera.worldMatrixUpdated(); } } diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index b9b819b8a..bb5d71673 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -10,6 +10,17 @@ export enum EDemoMode { Ended } +export enum EDemoCamFlags { + HasNearZ = 1 << 0, + HasFarZ = 1 << 1, + HasFovY = 1 << 2, + HasAspect = 1 << 3, + HasEyePos = 1 << 4, + HasUpVec = 1 << 5, + HasTargetPos = 1 << 6, + HasRoll = 1 << 7, +} + class dDemo_camera_c extends TCamera { mFlags: number = 0; mProjNear: number = 0; @@ -34,7 +45,7 @@ class dDemo_camera_c extends TCamera { override JSGSetProjectionNear(v: number) { this.mProjNear = v; - this.mFlags |= 0x01; + this.mFlags |= EDemoCamFlags.HasNearZ; } override JSGGetProjectionFar(): number { @@ -47,7 +58,7 @@ class dDemo_camera_c extends TCamera { override JSGSetProjectionFar(v: number): void { this.mProjFar = v; - this.mFlags |= 0x02; + this.mFlags |= EDemoCamFlags.HasFarZ; } @@ -61,7 +72,7 @@ class dDemo_camera_c extends TCamera { override JSGSetProjectionFovy(v: number): void { this.mFovy = v; - this.mFlags |= 0x04; + this.mFlags |= EDemoCamFlags.HasFovY; } @@ -75,7 +86,7 @@ class dDemo_camera_c extends TCamera { override JSGSetProjectionAspect(v: number) { this.mAspect = v; - this.mFlags |= 0x08; + this.mFlags |= EDemoCamFlags.HasAspect; } @@ -86,7 +97,7 @@ class dDemo_camera_c extends TCamera { override JSGSetViewPosition(v: ReadonlyVec3) { vec3.copy(this.mViewPosition, v); - this.mFlags |= 0x10; + this.mFlags |= EDemoCamFlags.HasEyePos; } @@ -100,7 +111,7 @@ class dDemo_camera_c extends TCamera { override JSGSetViewUpVector(v: ReadonlyVec3) { vec3.copy(this.mUpVector, v); - this.mFlags |= 0x20; + this.mFlags |= EDemoCamFlags.HasUpVec; } @@ -115,7 +126,7 @@ class dDemo_camera_c extends TCamera { override JSGSetViewTargetPosition(v: ReadonlyVec3) { vec3.copy(this.mTargetPosition, v); - this.mFlags |= 0x40; + this.mFlags |= EDemoCamFlags.HasTargetPos; } @@ -129,7 +140,7 @@ class dDemo_camera_c extends TCamera { override JSGSetViewRoll(v: number) { this.mRoll = v; - this.mFlags |= 0x80; + this.mFlags |= EDemoCamFlags.HasRoll; } } From c775333e56d104b28154778fa4c4ba548d6669ce Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 4 Nov 2024 21:30:12 -0700 Subject: [PATCH 016/102] Move roomStatus into the new dStage_roomControl_c class Also made dStage_roomStatus_c a member of dStage_roomStatus_c, instead of its parent class. This is closer to the structure of the game, which will make it easier to add roomControl features in upcoming commits --- src/ZeldaWindWaker/Grass.ts | 10 +++++----- src/ZeldaWindWaker/Main.ts | 19 +++++++------------ src/ZeldaWindWaker/d_a.ts | 8 ++++---- src/ZeldaWindWaker/d_kankyo.ts | 2 +- src/ZeldaWindWaker/d_kankyo_wether.ts | 8 ++++---- src/ZeldaWindWaker/d_stage.ts | 18 +++++++++++++++--- src/ZeldaWindWaker/d_wood.ts | 4 ++-- 7 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/ZeldaWindWaker/Grass.ts b/src/ZeldaWindWaker/Grass.ts index 0112116d8..bc53774b5 100644 --- a/src/ZeldaWindWaker/Grass.ts +++ b/src/ZeldaWindWaker/Grass.ts @@ -123,8 +123,8 @@ function checkGroundY(globals: dGlobals, roomIdx: number, pos: vec3) { } function setColorFromRoomNo(globals: dGlobals, materialParams: MaterialParams, roomNo: number): void { - colorCopy(materialParams.u_Color[ColorKind.C0], globals.roomStatus[roomNo].tevStr.colorC0); - colorCopy(materialParams.u_Color[ColorKind.C1], globals.roomStatus[roomNo].tevStr.colorK0); + colorCopy(materialParams.u_Color[ColorKind.C0], globals.roomCtrl.status[roomNo].tevStr.colorC0); + colorCopy(materialParams.u_Color[ColorKind.C1], globals.roomCtrl.status[roomNo].tevStr.colorK0); } function distanceCull(camPos: ReadonlyVec3, objPos: ReadonlyVec3, maxDist = 20000) { @@ -397,7 +397,7 @@ export class FlowerPacket { } private drawRoom(globals: dGlobals, roomIdx: number, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { - if (!globals.roomStatus[roomIdx].visible) + if (!globals.roomCtrl.status[roomIdx].visible) return; if (this.rooms[roomIdx].length === 0) @@ -719,7 +719,7 @@ export class TreePacket { } private drawRoom(globals: dGlobals, roomIdx: number, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput, device: GfxDevice) { - if (!globals.roomStatus[roomIdx].visible) + if (!globals.roomCtrl.status[roomIdx].visible) return; const room = this.rooms[roomIdx]; @@ -1016,7 +1016,7 @@ export class GrassPacket { } private drawRoom(globals: dGlobals, roomIdx: number, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { - if (!globals.roomStatus[roomIdx].visible) + if (!globals.roomCtrl.status[roomIdx].visible) return; const room = this.rooms[roomIdx]; diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 935253e20..1a8807da4 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -33,11 +33,11 @@ import { dDlst_2DStatic_c, d_a__RegisterConstructors } from './d_a.js'; import { d_a_sea } from './d_a_sea.js'; import { dBgS } from './d_bg.js'; import { dDlst_list_Set, dDlst_list_c } from './d_drawlist.js'; -import { dKankyo_create, dKy__RegisterConstructors, dKy_setLight, dKy_tevstr_init, dScnKy_env_light_c } from './d_kankyo.js'; +import { dKankyo_create, dKy__RegisterConstructors, dKy_setLight, dScnKy_env_light_c } from './d_kankyo.js'; import { dKyw__RegisterConstructors } from './d_kankyo_wether.js'; import { dPa_control_c } from './d_particle.js'; import { ResType, dRes_control_c } from './d_resorce.js'; -import { dStage_dt_c_roomLoader, dStage_dt_c_roomReLoader, dStage_dt_c_stageInitLoader, dStage_dt_c_stageLoader, dStage_roomStatus_c, dStage_stageDt_c } from './d_stage.js'; +import { dStage_dt_c_roomLoader, dStage_dt_c_roomReLoader, dStage_dt_c_stageInitLoader, dStage_dt_c_stageLoader, dStage_roomControl_c, dStage_roomStatus_c, dStage_stageDt_c } from './d_stage.js'; import { cPhs__Status, fGlobals, fopAcM_create, fopAc_ac_c, fopDw_Draw, fopScn, fpcCt_Handler, fpcLy_SetCurrentLayer, fpcM_Management, fpcPf__Register, fpcSCtRq_Request, fpc__ProcessName, fpc_pc__ProfileList } from './framework.js'; import { dDemo_manager_c, EDemoCamFlags, EDemoMode } from './d_demo.js'; @@ -126,7 +126,7 @@ export class dGlobals { // This is tucked away somewhere in dComInfoPlay public stageName: string; public dStage_dt = new dStage_stageDt_c(); - public roomStatus: dStage_roomStatus_c[] = nArray(64, () => new dStage_roomStatus_c()); + public roomCtrl = new dStage_roomControl_c(); public particleCtrl: dPa_control_c; public scnPlay: d_s_play; @@ -160,11 +160,6 @@ export class dGlobals { this.relNameTable = createRelNameTable(extraSymbolData); this.objectNameTable = createActorTable(extraSymbolData); - for (let i = 0; i < this.roomStatus.length; i++) { - this.roomStatus[i].roomNo = i; - dKy_tevstr_init(this.roomStatus[i].tevStr, i); - } - this.quadStatic = new dDlst_2DStatic_c(modelCache.device, modelCache.cache); this.dlst = new dDlst_list_c(modelCache.device, modelCache.cache, extraSymbolData); @@ -411,7 +406,7 @@ export class WindWakerRenderer implements Viewer.SceneGfx { if (ac.roomNo === -1) return null; - return this.globals.roomStatus[ac.roomNo]; + return this.globals.roomCtrl.status[ac.roomNo]; } private getSingleRoomVisible(): number { @@ -941,7 +936,7 @@ class SceneDesc { const roomNo = Math.abs(this.roomList[i]); const visible = this.roomList[i] >= 0; - const roomStatus = globals.roomStatus[i]; + const roomStatus = globals.roomCtrl.status[i]; roomStatus.visible = visible; renderer.rooms.push(new WindWakerRoom(roomNo, roomStatus)); @@ -951,8 +946,8 @@ class SceneDesc { fopAcM_create(framework, fpc__ProcessName.d_a_bg, roomNo, null, roomNo, null, null, 0xFF, -1); const dzr = assertExists(resCtrl.getStageResByName(ResType.Dzs, `Room${roomNo}`, `room.dzr`)); - dStage_dt_c_roomLoader(globals, globals.roomStatus[roomNo], dzr); - dStage_dt_c_roomReLoader(globals, globals.roomStatus[roomNo], dzr); + dStage_dt_c_roomLoader(globals, globals.roomCtrl.status[roomNo].data, dzr); + dStage_dt_c_roomReLoader(globals, globals.roomCtrl.status[roomNo].data, dzr); } // TODO: Improve and move to the correct place diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 436b32e5e..51390272a 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -475,7 +475,7 @@ class d_a_bg extends fopAc_ac_c { mat4.copy(this.bgModel[i]!.modelMatrix, calc_mtx); } - dKy_tevstr_init(globals.roomStatus[roomNo].tevStr, roomNo, -1); + dKy_tevstr_init(globals.roomCtrl.status[roomNo].tevStr, roomNo, -1); return cPhs__Status.Next; } @@ -507,7 +507,7 @@ class d_a_bg extends fopAc_ac_c { } const roomNo = this.parameters; - settingTevStruct(globals, LightType.BG0, null, globals.roomStatus[roomNo].tevStr); + settingTevStruct(globals, LightType.BG0, null, globals.roomCtrl.status[roomNo].tevStr); } public override delete(globals: dGlobals): void { @@ -608,7 +608,7 @@ class d_a_vrbox extends fopAc_ac_c { return; let skyboxOffsY = 0; - const fili = globals.roomStatus[globals.mStayNo].fili; + const fili = globals.roomCtrl.status[globals.mStayNo].data.fili; if (fili !== null) skyboxOffsY = fili.skyboxY; @@ -725,7 +725,7 @@ class d_a_vrbox2 extends fopAc_ac_c { return; let skyboxOffsY = 0; - const fili = globals.roomStatus[globals.mStayNo].fili; + const fili = globals.roomCtrl.status[globals.mStayNo].data.fili; if (fili !== null) skyboxOffsY = fili.skyboxY; diff --git a/src/ZeldaWindWaker/d_kankyo.ts b/src/ZeldaWindWaker/d_kankyo.ts index 9ab640388..08fad9e05 100644 --- a/src/ZeldaWindWaker/d_kankyo.ts +++ b/src/ZeldaWindWaker/d_kankyo.ts @@ -650,7 +650,7 @@ export function setLightTevColorType(globals: dGlobals, modelInstance: J3DModelI function SetBaseLight(globals: dGlobals): void { const envLight = globals.g_env_light; - const lgtv = globals.roomStatus[globals.mStayNo].lgtv; + const lgtv = globals.roomCtrl.status[globals.mStayNo].data.lgtv; if (lgtv !== null) { vec3.copy(envLight.baseLight.pos, lgtv.pos); colorFromRGBA(envLight.baseLight.color, 0.0, 0.0, 0.0, 0.0); diff --git a/src/ZeldaWindWaker/d_kankyo_wether.ts b/src/ZeldaWindWaker/d_kankyo_wether.ts index 68d224c52..1654b7111 100644 --- a/src/ZeldaWindWaker/d_kankyo_wether.ts +++ b/src/ZeldaWindWaker/d_kankyo_wether.ts @@ -2001,7 +2001,7 @@ function wether_move_windline(globals: dGlobals, deltaTimeFrames: number): void const envLight = globals.g_env_light; let windlineCount = 0; - const fili = globals.roomStatus[globals.mStayNo].fili; + const fili = globals.roomCtrl.status[globals.mStayNo].data.fili; if (fili !== null && !!(fili.param & 0x100000) && globals.stageName !== 'GTower') { windlineCount = (10.0 * dKyw_get_wind_pow(envLight)) | 0; } @@ -2397,7 +2397,7 @@ function wether_move_wave(globals: dGlobals, deltaTimeFrames: number): void { // TODO(jstpierre): #TACT_WIND. Overwrite with tact wind. LinkRM / Orichh / Ojhous2 / Omasao / Onobuta } - const fili = globals.roomStatus[globals.mStayNo].fili; + const fili = globals.roomCtrl.status[globals.mStayNo].data.fili; let skyboxY = 0.0; if (fili !== null) skyboxY = fili.skyboxY; @@ -2524,7 +2524,7 @@ function vrkumo_move(globals: dGlobals, deltaTimeFrames: number): void { } { - const fili = globals.roomStatus[globals.mStayNo].fili; + const fili = globals.roomCtrl.status[globals.mStayNo].data.fili; let skyboxY = 0.0; if (fili !== null) skyboxY = fili.skyboxY; @@ -2697,7 +2697,7 @@ export function dKyw_wind_set(globals: dGlobals): void { targetWindPower = envLight.customWindPower; } else { let windPowerFlag = 0; - const fili = globals.roomStatus[globals.mStayNo].fili; + const fili = globals.roomCtrl.status[globals.mStayNo].data.fili; if (fili !== null) windPowerFlag = dStage_FileList_dt_GlobalWindLevel(fili); diff --git a/src/ZeldaWindWaker/d_stage.ts b/src/ZeldaWindWaker/d_stage.ts index 1b5ea3c67..6dae1ebbe 100644 --- a/src/ZeldaWindWaker/d_stage.ts +++ b/src/ZeldaWindWaker/d_stage.ts @@ -3,7 +3,7 @@ import { Color, White, colorNewCopy, colorFromRGBA8, colorNewFromRGBA8 } from ". import { DZS } from "./d_resorce.js"; import ArrayBufferSlice from "../ArrayBufferSlice.js"; import { nArray, assert, readString } from "../util.js"; -import { dKy_tevstr_c } from "./d_kankyo.js"; +import { dKy_tevstr_c, dKy_tevstr_init } from "./d_kankyo.js"; import { vec3 } from "gl-matrix"; import { Endianness } from "../endian.js"; import { dGlobals } from "./Main.js"; @@ -520,11 +520,23 @@ export class dStage_roomDt_c extends dStage_dt { public lgtv: stage_lightvec_info_class | null = null; } -export class dStage_roomStatus_c extends dStage_roomDt_c { +export class dStage_roomStatus_c { + public data = new dStage_roomDt_c(); public tevStr = new dKy_tevstr_c(); public visible = true; } +export class dStage_roomControl_c { + public status: dStage_roomStatus_c[] = nArray(64, () => new dStage_roomStatus_c()); + + constructor() { + for (let i = 0; i < this.status.length; i++) { + this.status[i].data.roomNo = i; + dKy_tevstr_init(this.status[i].tevStr, i); + } + } +} + function dStage_filiInfoInit(globals: dGlobals, dt: dStage_roomDt_c, buffer: ArrayBufferSlice, count: number): void { if (count !== 0) { assert(count === 1); @@ -581,5 +593,5 @@ export function dStage_dt_c_roomReLoader(globals: dGlobals, dt: dStage_roomDt_c, //#endregion export function dPath_GetRoomPath(globals: dGlobals, idx: number, roomNo: number): dPath { - return globals.roomStatus[roomNo].rpat[idx]; + return globals.roomCtrl.status[roomNo].data.rpat[idx]; } diff --git a/src/ZeldaWindWaker/d_wood.ts b/src/ZeldaWindWaker/d_wood.ts index 6abb93c76..29c409fd4 100644 --- a/src/ZeldaWindWaker/d_wood.ts +++ b/src/ZeldaWindWaker/d_wood.ts @@ -756,8 +756,8 @@ export class WoodPacket implements J3DPacket { for (let r = 0; r < kRoomCount; r++) { // Set the room color and fog params - colorCopy(materialParams.u_Color[ColorKind.C0], globals.roomStatus[r].tevStr.colorC0); - colorCopy(materialParams.u_Color[ColorKind.C1], globals.roomStatus[r].tevStr.colorK0); + colorCopy(materialParams.u_Color[ColorKind.C0], globals.roomCtrl.status[r].tevStr.colorC0); + colorCopy(materialParams.u_Color[ColorKind.C1], globals.roomCtrl.status[r].tevStr.colorK0); dKy_GxFog_set(globals.g_env_light, materialParams.u_FogBlock, viewerInput.camera); for (let unit of this.unit[r]) { From bad3ec0d37ba3dd074d8a2268cf0ddfa75c0ec9f Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 4 Nov 2024 22:31:00 -0700 Subject: [PATCH 017/102] Fetch a Stage's Demo archive when it is loaded The LBNK section of the Room info contains a u8 for each of the 12 layers. This int is the index of the Demo arc that the stage expects to be loaded for that layer (or 0xFF for no arc). For instance, Outset Island (Room 44 of 'sea') specifies 0x02 in LBNK[0]. It expects Objects/Demo02.arc to be loaded. Demo events will then load demos using filenames that map into that arc. In the game, this occurs just after dStage_dt_c_roomLoader() is called, during phase 2 of dScnRoom_Create(). --- src/ZeldaWindWaker/Main.ts | 25 +++++++++++++++++++++++++ src/ZeldaWindWaker/d_stage.ts | 7 +++++++ 2 files changed, 32 insertions(+) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 1a8807da4..0bb5ac47f 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -926,6 +926,8 @@ class SceneDesc { // dStage_dt_c_stageLoader() // dMap_c::create() + globals.roomCtrl.demoArcName = undefined; + const vrbox = resCtrl.getStageResByName(ResType.Model, `Stage`, `vr_sky.bdl`); if (vrbox !== null) { fpcSCtRq_Request(framework, null, fpc__ProcessName.d_a_vrbox, null); @@ -947,6 +949,29 @@ class SceneDesc { const dzr = assertExists(resCtrl.getStageResByName(ResType.Dzs, `Room${roomNo}`, `room.dzr`)); dStage_dt_c_roomLoader(globals, globals.roomCtrl.status[roomNo].data, dzr); + + if (!globals.roomCtrl.demoArcName) { + const lbnk = globals.roomCtrl.status[roomNo].data.lbnk; + if (lbnk) { + // This system can load a different demo file based on the active layer when the room is loaded. + // It should use dComIfG_play_c::getLayerNo(roomNo) to select the current layer. + // For now, just use layer 0 + if(lbnk[1] != 0xFF) console.warn('Stage contains a demo in non-zero layer. May be worth investigating.'); + const layerNo = 0; + + const bank = lbnk[layerNo]; + if (bank != 0xFF) { + assert(bank >= 0 && bank < 100); + globals.roomCtrl.demoArcName = `Demo${bank.toString().padStart(2, '0')}`; + console.debug(`Loading stage demo file: ${globals.roomCtrl.demoArcName}`); + modelCache.fetchObjectData(globals.roomCtrl.demoArcName).catch(e => { + // @TODO: Better error handling. This does not prevent a debugger break. + console.log(`Failed to load stage demo file: ${globals.roomCtrl.demoArcName}`, e); + }) + } + } + } + dStage_dt_c_roomReLoader(globals, globals.roomCtrl.status[roomNo].data, dzr); } diff --git a/src/ZeldaWindWaker/d_stage.ts b/src/ZeldaWindWaker/d_stage.ts index 6dae1ebbe..95f6210f2 100644 --- a/src/ZeldaWindWaker/d_stage.ts +++ b/src/ZeldaWindWaker/d_stage.ts @@ -518,6 +518,7 @@ export function dStage_dt_c_stageLoader(globals: dGlobals, dt: dStage_stageDt_c, export class dStage_roomDt_c extends dStage_dt { public fili: dStage_FileList_dt_c | null = null; public lgtv: stage_lightvec_info_class | null = null; + public lbnk: Uint8Array; } export class dStage_roomStatus_c { @@ -528,6 +529,7 @@ export class dStage_roomStatus_c { export class dStage_roomControl_c { public status: dStage_roomStatus_c[] = nArray(64, () => new dStage_roomStatus_c()); + public demoArcName?: string; constructor() { for (let i = 0; i < this.status.length; i++) { @@ -558,6 +560,10 @@ function dStage_lgtvInfoInit(globals: dGlobals, dt: dStage_roomDt_c, buffer: Arr } } +function dStage_lbnkInfoInit(globals: dGlobals, dt: dStage_roomDt_c, buffer: ArrayBufferSlice, count: number ): void { + dt.lbnk = buffer.createTypedArray(Uint8Array, 0, count); +} + function dStage_roomTresureInit(globals: dGlobals, dt: dStage_roomDt_c, buffer: ArrayBufferSlice, count: number, fileData: ArrayBufferSlice, layer: number): void { // dt.tres = ...; dStage_actorInit(globals, dt, buffer, count, fileData, layer); @@ -574,6 +580,7 @@ export function dStage_dt_c_roomLoader(globals: dGlobals, dt: dStage_roomDt_c, d 'LGTV': dStage_lgtvInfoInit, 'RPPN': dStage_rppnInfoInit, 'RPAT': dStage_rpatInfoInit, + 'LBNK': dStage_lbnkInfoInit, }); } From bb7eaf4eca6cff16bb2348bf56144b98d2f81a66 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 4 Nov 2024 22:38:02 -0700 Subject: [PATCH 018/102] Add dRes_control_c.getObjectResByName() --- src/ZeldaWindWaker/d_resorce.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ZeldaWindWaker/d_resorce.ts b/src/ZeldaWindWaker/d_resorce.ts index d34a29b05..f6f878125 100644 --- a/src/ZeldaWindWaker/d_resorce.ts +++ b/src/ZeldaWindWaker/d_resorce.ts @@ -89,6 +89,10 @@ export class dRes_control_c { return this.getResByIndex(resType, arcName, resIndex, this.resObj); } + public getObjectResByName(resType: T, arcName: string, resName: string): ResAssetType | null { + return this.getResByName(resType, arcName, resName, this.resObj); + } + public getResByName(resType: T, arcName: string, resName: string, resList: dRes_info_c[]): ResAssetType | null { const resInfo = assertExists(this.findResInfo(arcName, resList)); return resInfo.getResByName(resType, resName); From a4839b9f2ef3c2f4ce3ee499b724925642ef2a88 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 4 Nov 2024 22:39:33 -0700 Subject: [PATCH 019/102] DemoDesc now contains the filename property, which Demo Events use to access STBs from the stage's demo arc --- src/ZeldaWindWaker/Main.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 0bb5ac47f..ff969ab44 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -986,20 +986,21 @@ class SceneDesc { class DemoDesc { public id: string; - public constructor(public arcName: string, public name: string, private stbIndex: number, private originPos?: vec3, private rotY?: number ) { - this.id = arcName; + public constructor(public filename: string, public name: string, private stbIndex: number, private originPos?: vec3, private rotY?: number ) { + this.id = filename; } async load(globals: dGlobals, modelCache: ModelCache) { - modelCache.fetchObjectData(this.arcName); await modelCache.waitForLoad(); - const stbData = modelCache.resCtrl.getObjectRes(ResType.Stb, this.arcName, this.stbIndex); - globals.scnPlay.demo.create(stbData, this.originPos, this.rotY); + if(globals.roomCtrl.demoArcName) { + const stbData = modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName!, this.filename); + if( stbData ) { globals.scnPlay.demo.create(stbData, this.originPos, this.rotY); } + } } } const demoDescs = { - Demo51: new DemoDesc("Demo51", "Start Screen", 0x03, [-220000, 0, 320000], Math.PI), + Demo51: new DemoDesc("title.stb", "Start Screen", 0x03, [-220000, 0, 320000], Math.PI), } // Location names taken from CryZe's Debug Menu. @@ -1025,7 +1026,7 @@ const sceneDescs = [ new SceneDesc("Obshop", "Beedle's Shop", [1]), "Outset Island", - new SceneDesc("sea", "Opening Cutscene", [44], demoDescs.Demo51), + new SceneDesc("sea_T", "Opening Cutscene", [44], demoDescs.Demo51), new SceneDesc("sea", "Outset Island", [44]), new SceneDesc("LinkRM", "Link's House"), new SceneDesc("LinkUG", "Under Link's House"), From 9f51dfd58fbbc37316610a74f1bfbaf9e9a2a1cc Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 4 Nov 2024 23:34:22 -0700 Subject: [PATCH 020/102] Imported the list of all demo events (with all their parameters) in Wind Waker --- src/ZeldaWindWaker/Main.ts | 85 +++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index ff969ab44..860748a35 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -956,6 +956,7 @@ class SceneDesc { // This system can load a different demo file based on the active layer when the room is loaded. // It should use dComIfG_play_c::getLayerNo(roomNo) to select the current layer. // For now, just use layer 0 + // TODO: Use the first set layer if(lbnk[1] != 0xFF) console.warn('Stage contains a demo in non-zero layer. May be worth investigating.'); const layerNo = 0; @@ -984,24 +985,87 @@ class SceneDesc { } class DemoDesc { - public id: string; - - public constructor(public filename: string, public name: string, private stbIndex: number, private originPos?: vec3, private rotY?: number ) { - this.id = filename; - } + public constructor( + public stage: string, + public name: string, + public stbFilename: string, + public offsetPos?:vec3, + public rotY?: number, + public roomNo?: number, + public layer?: number, + public startCode?: number, + public eventFlags?: number, + ) {} async load(globals: dGlobals, modelCache: ModelCache) { await modelCache.waitForLoad(); if(globals.roomCtrl.demoArcName) { - const stbData = modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName!, this.filename); - if( stbData ) { globals.scnPlay.demo.create(stbData, this.originPos, this.rotY); } + const stbData = modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName!, this.stbFilename); + if( stbData ) { globals.scnPlay.demo.create(stbData, this.offsetPos, this.rotY); } } } } -const demoDescs = { - Demo51: new DemoDesc("title.stb", "Start Screen", 0x03, [-220000, 0, 320000], Math.PI), -} +const demoDescs = [ + new DemoDesc("ADMumi", "warp_in", "warp_in.stb", [0.0, 0.0, 0.0], 0.0, 0, 8, 200, 0), + new DemoDesc("ADMumi", "warphole", "warphole.stb", [0.0, 0.0, 0.0], 0.0, 0, -1, 219, 0), + new DemoDesc("ADMumi", "runaway_majuto", "runaway_majuto.stb", [0, 0, 0], 0, 0, -1, 0, 0), + new DemoDesc("ADMumi", "towerd", "towerd.stb", [-50179.0, -1000.0, 7070.0], 90.0, 26, -1, 0, 0), + new DemoDesc("ADMumi", "towerf", "towerf.stb", [-50179.0, -1000.0, 7070.0], 90.0, 26, -1, 0, 0), + new DemoDesc("ADMumi", "towern", "towern.stb", [-50179.0, -1000.0, 7070.0], 90.0, 26, -1, 0, 0), + new DemoDesc("A_mori", "MEET_TETORA", "meet_tetra.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("A_nami", "COUNTER_DEMO", "counter.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("Adanmae", "demo41", "howling.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 0, 0), + new DemoDesc("Atorizk", "demo10", "dragontale.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("DmSpot0", "SARAWARE", "kaizoku_zelda_fly.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("E3ROOP", "VOYAGE", "voyage.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("E3ROOP", "COUNTER_DEMO", "counter.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("ENDumi", "ending", "ending.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 0, 0), + new DemoDesc("Edaichi", "dance_zola", "dance_zola.stb", [0.0, 0.0, 0.0], 0, 0, -1, 226, 0), + new DemoDesc("Ekaze", "dance_kokiri", "dance_kokiri.stb", [0.0, 0.0, 0.0], 0, 0, -1, 229, 0), + new DemoDesc("GTower", "g2before", "g2before.stb", [0.0, 0.0, 0.0], 0.0, 0, 1, 0, 0), + new DemoDesc("GTower", "endhr", "endhr.stb", [0.0, 0.0, 0.0], 0.0, 0, -1, 0, 0), + new DemoDesc("GanonK", "kugutu_ganon", "kugutu_ganon.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 0, 0), + new DemoDesc("GanonK", "to_roof", "to_roof.stb", [0.0, 0.0, 0.0], 0.0, 0, -1, 4, 0), + new DemoDesc("Hyroom", "rebirth_hyral", "rebirth_hyral.stb", [0.0, -2100.0, 3910.0], 0.0, 0, 10, 249, 0), + new DemoDesc("Hyrule", "warp_out", "warp_out.stb", [0, 0, 0], 0, 0, 0, 201, 0), + new DemoDesc("Hyrule", "seal", "seal.stb", [0.0, 0.0, 0.0], 0, 0, 0, 0, 0), + new DemoDesc("LinkRM", "TALE_DEMO", "tale.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("LinkRM", "TALE_DEMO2", "tale_2.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("LinkRM", "get_shield", "get_shield.stb", [0, 0, 0], 0, 0, 9, 201, 0), + new DemoDesc("LinkRM", "tale_2", "tale_2.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("M2ganon", "attack_ganon", "attack_ganon.stb", [2000.0, 11780.0, -8000.0], 0.0, 0, 11, 244, 0), + new DemoDesc("M2tower", "rescue", "rescue.stb", [3214.0, 3939.0, -3011.0], 57.5, 0, 3, 20, 0), + new DemoDesc("M_DaiB", "pray_zola", "pray_zola.stb", [0, 0, 0], 0, 45, -1, 229, 0), + new DemoDesc("MajyuE", "majyuu_shinnyuu", "maju_shinnyu.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("Mjtower", "FIND_SISTER", "find_sister.stb", [4889.0, 0.0, -2635.0], 57.5, 0, 0, 0, 0), + new DemoDesc("Obombh", "bombshop", "bombshop.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 200, 0), + new DemoDesc("Ocean", "COUNTER_DEMO", "counter.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("Omori", "getperl_deku", "getperl_deku.stb", [0, 0, 0], 0, 0, -1, 214, 0), + new DemoDesc("Omori", "meet_deku", "meet_deku.stb", [0, 0, 0], 0, 0, -1, 213, 0), + new DemoDesc("Otkura", "awake_kokiri", "awake_kokiri.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("Pjavdou", "getperl_jab", "getperl_jab.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 0, 0), + new DemoDesc("VrTest", "COUNTER_DEMO", "counter.stb", [0, 0, 0], 0, 0, 0, 0, 0), + new DemoDesc("kazeB", "pray_kokiri", "pray_kokiri.stb", [0.0, 300.0, 0.0], 0.0, 4, -1, 232, 0), + new DemoDesc("kenroom", "awake_zelda", "awake_zelda.stb", [0.0, 0.0, 0.0], 0.0, 0, -1, 2, 0), + new DemoDesc("kenroom", "master_sword", "master_sword.stb", [-124.0, -3223.0, -7823.0], 180.0, 0, 8, 248, 0), + new DemoDesc("kenroom", "swing_sword", "swing_sword.stb", [-124.0, -3223.0, -7823.0], 180.0, 0, 6, 249, 0), + new DemoDesc("sea", "awake", "awake.stb", [-220000.0, 0.0, 320000.0], 0.0, 0, 0, 0, 0), + new DemoDesc("sea", "departure_DEMO", "departure.stb", [-200000.0, 0.0, 320000.0], 0.0, 0, -1, 204, 0), + new DemoDesc("sea", "fairy", "fairy.stb", [-180000.0, 740.0, -199937.0], 25.0, 9, -1, 2, 0), + new DemoDesc("sea", "fairy_flag_on", "fairy_flag_on.stb", [-180000.0, 740.0, -199937.0], 25.0, 9, -1, 2, 0), + new DemoDesc("sea", "awake_zola", "awake_zola.stb", [200000.0, 0.0, -200000.0], 0, 13, -1, 227, 0), + new DemoDesc("sea", "getperl_komori", "getperl_komori.stb", [200000.0, 0.0, -200000.0], 0, 0, 0, 0, 0), + new DemoDesc("sea", "MEETSHISHIOH", "meetshishioh.stb", [0.0, 0.0, -200000.0], 0, 11, 0, 128, 0), + new DemoDesc("sea", "STOLENSISTER", "stolensister.stb", [0.0, 0.0, 20000.0], 0, 0, 0, 0, 0), + new DemoDesc("sea", "zelda_fly", "kaizoku_zelda_fly.stb", [-200000.0, 0.0, 320000.0], 180.0, 0, 0, 0, 0), + new DemoDesc("sea_E", "awake", "awake.stb", [-220000.0, 0.0, 320000.0], 0.0, 0, 0, 0, 0), + new DemoDesc("sea_E", "epilogue", "epilogue.stb", [-200000.0, 0.0, 320000.0], 0.0, 44, -1, 0, 0), + new DemoDesc("sea_T", "awake", "awake.stb", [-220000.0, 0.0, 320000.0], 0.0, 0, 0, 0, 0), + new DemoDesc("sea_T", "title", "title.stb", [-220000.0, 0.0, 320000.0], 180.0, 44, -1, 0, 0), + new DemoDesc("sea_T", "fly_demo", "kaizoku_zelda_fly.stb", [-200000.0, 0.0, 320000.0], 180.0, 0, 0, 0, 0), + new DemoDesc("sea_T", "departure_DEMO", "departure.stb", [-200000.0, 0.0, 320000.0], 0.0, 0, 0, 0, 0), +] // Location names taken from CryZe's Debug Menu. // https://github.com/CryZe/WindWakerDebugMenu/blob/master/src/warp_menu/consts.rs @@ -1026,7 +1090,6 @@ const sceneDescs = [ new SceneDesc("Obshop", "Beedle's Shop", [1]), "Outset Island", - new SceneDesc("sea_T", "Opening Cutscene", [44], demoDescs.Demo51), new SceneDesc("sea", "Outset Island", [44]), new SceneDesc("LinkRM", "Link's House"), new SceneDesc("LinkUG", "Under Link's House"), From 7442f4cdd8b75289576127171fb7a0c3f731f411 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 5 Nov 2024 00:12:22 -0700 Subject: [PATCH 021/102] If a Stage has demos, add a new UI panel. Click on one in the list will play it. --- src/ZeldaWindWaker/Main.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 860748a35..789943f26 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -323,6 +323,7 @@ export class WindWakerRenderer implements Viewer.SceneGfx { public renderHelper: GXRenderHelperGfx; public rooms: WindWakerRoom[] = []; + public demos: DemoDesc[] = [] public extraTextures: ZWWExtraTextures; public renderCache: GfxRenderCache; @@ -366,6 +367,16 @@ export class WindWakerRenderer implements Viewer.SceneGfx { roomsPanel.setTitle(UI.LAYER_ICON, 'Rooms'); roomsPanel.setLayers(this.rooms); + const demosPanel = new UI.Panel(); + demosPanel.customHeaderBackgroundColor = UI.COOL_BLUE_COLOR; + demosPanel.setTitle(UI.LAYER_ICON, 'Cutscenes'); + const demoSelect = new UI.SingleSelect(); + demoSelect.setStrings(this.demos.map(d => d.name)); + demoSelect.onselectionchange = (idx: number) => { + this.demos[idx].load(this.globals) + }; + demosPanel.contents.appendChild(demoSelect.elem); + const renderHacksPanel = new UI.Panel(); renderHacksPanel.customHeaderBackgroundColor = UI.COOL_BLUE_COLOR; renderHacksPanel.setTitle(UI.RENDER_HACKS_ICON, 'Render Hacks'); @@ -396,7 +407,9 @@ export class WindWakerRenderer implements Viewer.SceneGfx { renderHacksPanel.contents.appendChild(wireframe.elem); } - return [roomsPanel, scenarioPanel, renderHacksPanel]; + const panels = [roomsPanel, scenarioPanel, renderHacksPanel]; + if( this.demos.length > 0 ) { panels.push(demosPanel); } + return panels; } // For people to play around with. @@ -969,6 +982,8 @@ class SceneDesc { // @TODO: Better error handling. This does not prevent a debugger break. console.log(`Failed to load stage demo file: ${globals.roomCtrl.demoArcName}`, e); }) + + globals.renderer.demos = demoDescs.filter(d => d.stage == globals.stageName); } } } @@ -976,10 +991,6 @@ class SceneDesc { dStage_dt_c_roomReLoader(globals, globals.roomCtrl.status[roomNo].data, dzr); } - // TODO: Improve and move to the correct place - if( this.demo) - this.demo.load(globals, modelCache); - return renderer; } } @@ -997,10 +1008,11 @@ class DemoDesc { public eventFlags?: number, ) {} - async load(globals: dGlobals, modelCache: ModelCache) { - await modelCache.waitForLoad(); + async load(globals: dGlobals) { + await globals.modelCache.waitForLoad(); if(globals.roomCtrl.demoArcName) { - const stbData = modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName!, this.stbFilename); + const stbData = globals.modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName!, this.stbFilename); + // @TODO: Search the stage arc as well. See dEvDtStaff_c::specialProcPackage() if( stbData ) { globals.scnPlay.demo.create(stbData, this.offsetPos, this.rotY); } } } From 443302e6021bab159047cebd1b08b5480c133817 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 5 Nov 2024 00:14:15 -0700 Subject: [PATCH 022/102] Fix demo rotation. The imported data is in degrees, we expect radians --- src/ZeldaWindWaker/Main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 789943f26..ea35feec2 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -1001,7 +1001,7 @@ class DemoDesc { public name: string, public stbFilename: string, public offsetPos?:vec3, - public rotY?: number, + public rotY: number = 0, public roomNo?: number, public layer?: number, public startCode?: number, @@ -1013,7 +1013,7 @@ class DemoDesc { if(globals.roomCtrl.demoArcName) { const stbData = globals.modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName!, this.stbFilename); // @TODO: Search the stage arc as well. See dEvDtStaff_c::specialProcPackage() - if( stbData ) { globals.scnPlay.demo.create(stbData, this.offsetPos, this.rotY); } + if( stbData ) { globals.scnPlay.demo.create(stbData, this.offsetPos, this.rotY / 180.0 * Math.PI); } } } } From 76f8a684b5e8bb4be0572e415eeab08f21f26b60 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 5 Nov 2024 00:14:34 -0700 Subject: [PATCH 023/102] Add "Title Screen" stage to the Outset Island section --- src/ZeldaWindWaker/Main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index ea35feec2..03c141594 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -1102,6 +1102,7 @@ const sceneDescs = [ new SceneDesc("Obshop", "Beedle's Shop", [1]), "Outset Island", + new SceneDesc("sea_T", "Title Screen", [44]), new SceneDesc("sea", "Outset Island", [44]), new SceneDesc("LinkRM", "Link's House"), new SceneDesc("LinkUG", "Under Link's House"), From 8f3cab6454ace70cc526cca4f10703e5bc2b2156 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 6 Nov 2024 18:06:28 -0700 Subject: [PATCH 024/102] Added correct room and layer information for all demos They were extracted from the game using a modified version of the excellent wwrando/wwlib/stage_searcher.py script from LagoLunatic. Most of this data comes from the PLAY action of the PACKAGE actor in an event from a Stage's event_list.dat. This action has properties for the room and layer that these cutscenes are designed for. HOWEVER, this data is often missing. It has been reconstructed by cross-referencing each Room's lbnk section (which points to a Demo*.arc file for each layer), the .stb files contained in each of those Objects/Demo*.arc files, and the FileName attribute from the event action. --- src/ZeldaWindWaker/Main.ts | 118 +++++++++++++++++----------------- src/ZeldaWindWaker/d_stage.ts | 2 +- 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 03c141594..ce6dcc4ca 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -1000,10 +1000,10 @@ class DemoDesc { public stage: string, public name: string, public stbFilename: string, + public roomNo: number, + public layer: number, public offsetPos?:vec3, public rotY: number = 0, - public roomNo?: number, - public layer?: number, public startCode?: number, public eventFlags?: number, ) {} @@ -1018,65 +1018,63 @@ class DemoDesc { } } +// Extracted from the game using a modified version of the excellent wwrando/wwlib/stage_searcher.py script from LagoLunatic +// Most of this data comes from the PLAY action of the PACKAGE actor in an event from a Stage's event_list.dat. This +// action has properties for the room and layer that these cutscenes are designed for. HOWEVER, this data is often missing. +// It has been reconstructed by cross-referencing each Room's lbnk section (which points to a Demo*.arc file for each layer), +// the .stb files contained in each of those Objects/Demo*.arc files, and the FileName attribute from the event action. const demoDescs = [ - new DemoDesc("ADMumi", "warp_in", "warp_in.stb", [0.0, 0.0, 0.0], 0.0, 0, 8, 200, 0), - new DemoDesc("ADMumi", "warphole", "warphole.stb", [0.0, 0.0, 0.0], 0.0, 0, -1, 219, 0), - new DemoDesc("ADMumi", "runaway_majuto", "runaway_majuto.stb", [0, 0, 0], 0, 0, -1, 0, 0), - new DemoDesc("ADMumi", "towerd", "towerd.stb", [-50179.0, -1000.0, 7070.0], 90.0, 26, -1, 0, 0), - new DemoDesc("ADMumi", "towerf", "towerf.stb", [-50179.0, -1000.0, 7070.0], 90.0, 26, -1, 0, 0), - new DemoDesc("ADMumi", "towern", "towern.stb", [-50179.0, -1000.0, 7070.0], 90.0, 26, -1, 0, 0), - new DemoDesc("A_mori", "MEET_TETORA", "meet_tetra.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("A_nami", "COUNTER_DEMO", "counter.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("Adanmae", "demo41", "howling.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 0, 0), - new DemoDesc("Atorizk", "demo10", "dragontale.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("DmSpot0", "SARAWARE", "kaizoku_zelda_fly.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("E3ROOP", "VOYAGE", "voyage.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("E3ROOP", "COUNTER_DEMO", "counter.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("ENDumi", "ending", "ending.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 0, 0), - new DemoDesc("Edaichi", "dance_zola", "dance_zola.stb", [0.0, 0.0, 0.0], 0, 0, -1, 226, 0), - new DemoDesc("Ekaze", "dance_kokiri", "dance_kokiri.stb", [0.0, 0.0, 0.0], 0, 0, -1, 229, 0), - new DemoDesc("GTower", "g2before", "g2before.stb", [0.0, 0.0, 0.0], 0.0, 0, 1, 0, 0), - new DemoDesc("GTower", "endhr", "endhr.stb", [0.0, 0.0, 0.0], 0.0, 0, -1, 0, 0), - new DemoDesc("GanonK", "kugutu_ganon", "kugutu_ganon.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 0, 0), - new DemoDesc("GanonK", "to_roof", "to_roof.stb", [0.0, 0.0, 0.0], 0.0, 0, -1, 4, 0), - new DemoDesc("Hyroom", "rebirth_hyral", "rebirth_hyral.stb", [0.0, -2100.0, 3910.0], 0.0, 0, 10, 249, 0), - new DemoDesc("Hyrule", "warp_out", "warp_out.stb", [0, 0, 0], 0, 0, 0, 201, 0), - new DemoDesc("Hyrule", "seal", "seal.stb", [0.0, 0.0, 0.0], 0, 0, 0, 0, 0), - new DemoDesc("LinkRM", "TALE_DEMO", "tale.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("LinkRM", "TALE_DEMO2", "tale_2.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("LinkRM", "get_shield", "get_shield.stb", [0, 0, 0], 0, 0, 9, 201, 0), - new DemoDesc("LinkRM", "tale_2", "tale_2.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("M2ganon", "attack_ganon", "attack_ganon.stb", [2000.0, 11780.0, -8000.0], 0.0, 0, 11, 244, 0), - new DemoDesc("M2tower", "rescue", "rescue.stb", [3214.0, 3939.0, -3011.0], 57.5, 0, 3, 20, 0), - new DemoDesc("M_DaiB", "pray_zola", "pray_zola.stb", [0, 0, 0], 0, 45, -1, 229, 0), - new DemoDesc("MajyuE", "majyuu_shinnyuu", "maju_shinnyu.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("Mjtower", "FIND_SISTER", "find_sister.stb", [4889.0, 0.0, -2635.0], 57.5, 0, 0, 0, 0), - new DemoDesc("Obombh", "bombshop", "bombshop.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 200, 0), - new DemoDesc("Ocean", "COUNTER_DEMO", "counter.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("Omori", "getperl_deku", "getperl_deku.stb", [0, 0, 0], 0, 0, -1, 214, 0), - new DemoDesc("Omori", "meet_deku", "meet_deku.stb", [0, 0, 0], 0, 0, -1, 213, 0), - new DemoDesc("Otkura", "awake_kokiri", "awake_kokiri.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("Pjavdou", "getperl_jab", "getperl_jab.stb", [0.0, 0.0, 0.0], 0.0, 0, 0, 0, 0), - new DemoDesc("VrTest", "COUNTER_DEMO", "counter.stb", [0, 0, 0], 0, 0, 0, 0, 0), - new DemoDesc("kazeB", "pray_kokiri", "pray_kokiri.stb", [0.0, 300.0, 0.0], 0.0, 4, -1, 232, 0), - new DemoDesc("kenroom", "awake_zelda", "awake_zelda.stb", [0.0, 0.0, 0.0], 0.0, 0, -1, 2, 0), - new DemoDesc("kenroom", "master_sword", "master_sword.stb", [-124.0, -3223.0, -7823.0], 180.0, 0, 8, 248, 0), - new DemoDesc("kenroom", "swing_sword", "swing_sword.stb", [-124.0, -3223.0, -7823.0], 180.0, 0, 6, 249, 0), - new DemoDesc("sea", "awake", "awake.stb", [-220000.0, 0.0, 320000.0], 0.0, 0, 0, 0, 0), - new DemoDesc("sea", "departure_DEMO", "departure.stb", [-200000.0, 0.0, 320000.0], 0.0, 0, -1, 204, 0), - new DemoDesc("sea", "fairy", "fairy.stb", [-180000.0, 740.0, -199937.0], 25.0, 9, -1, 2, 0), - new DemoDesc("sea", "fairy_flag_on", "fairy_flag_on.stb", [-180000.0, 740.0, -199937.0], 25.0, 9, -1, 2, 0), - new DemoDesc("sea", "awake_zola", "awake_zola.stb", [200000.0, 0.0, -200000.0], 0, 13, -1, 227, 0), - new DemoDesc("sea", "getperl_komori", "getperl_komori.stb", [200000.0, 0.0, -200000.0], 0, 0, 0, 0, 0), - new DemoDesc("sea", "MEETSHISHIOH", "meetshishioh.stb", [0.0, 0.0, -200000.0], 0, 11, 0, 128, 0), - new DemoDesc("sea", "STOLENSISTER", "stolensister.stb", [0.0, 0.0, 20000.0], 0, 0, 0, 0, 0), - new DemoDesc("sea", "zelda_fly", "kaizoku_zelda_fly.stb", [-200000.0, 0.0, 320000.0], 180.0, 0, 0, 0, 0), - new DemoDesc("sea_E", "awake", "awake.stb", [-220000.0, 0.0, 320000.0], 0.0, 0, 0, 0, 0), - new DemoDesc("sea_E", "epilogue", "epilogue.stb", [-200000.0, 0.0, 320000.0], 0.0, 44, -1, 0, 0), - new DemoDesc("sea_T", "awake", "awake.stb", [-220000.0, 0.0, 320000.0], 0.0, 0, 0, 0, 0), - new DemoDesc("sea_T", "title", "title.stb", [-220000.0, 0.0, 320000.0], 180.0, 44, -1, 0, 0), - new DemoDesc("sea_T", "fly_demo", "kaizoku_zelda_fly.stb", [-200000.0, 0.0, 320000.0], 180.0, 0, 0, 0, 0), - new DemoDesc("sea_T", "departure_DEMO", "departure.stb", [-200000.0, 0.0, 320000.0], 0.0, 0, 0, 0, 0), + new DemoDesc("ADMumi", "warp_in.stb", "warp_in.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 200, 0), + new DemoDesc("ADMumi", "warphole.stb", "warphole.stb", 0, 10, [0.0, 0.0, 0.0], 0.0, 219, 0), + new DemoDesc("ADMumi", "runaway_majuto.stb", "runaway_majuto.stb", 0, 11, [0, 0, 0], 0, 0, 0), + new DemoDesc("ADMumi", "towerd.stb", "towerd.stb", 0, 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), + new DemoDesc("ADMumi", "towerf.stb", "towerf.stb", 0, 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), + new DemoDesc("ADMumi", "towern.stb", "towern.stb", 0, 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), + new DemoDesc("A_mori", "meet_tetra.stb", "meet_tetra.stb", 0, 0, [0, 0, 0], 0, 0, 0), + new DemoDesc("Adanmae", "howling.stb", "howling.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("Atorizk", "dragontale.stb", "dragontale.stb", 0, 0, [0, 0, 0], 0, 0, 0), + new DemoDesc("ENDumi", "ending.stb", "ending.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("Edaichi", "dance_zola.stb", "dance_zola.stb", 0, 8, [0.0, 0.0, 0.0], 0, 226, 0), + new DemoDesc("Ekaze", "dance_kokiri.stb", "dance_kokiri.stb", 0, 8, [0.0, 0.0, 0.0], 0, 229, 0), + new DemoDesc("GTower", "g2before.stb", "g2before.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("GTower", "endhr.stb", "endhr.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("GanonK", "kugutu_ganon.stb", "kugutu_ganon.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("GanonK", "to_roof.stb", "to_roof.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 4, 0), + new DemoDesc("Hyroom", "rebirth_hyral.stb", "rebirth_hyral.stb", 0, 8, [0.0, -2100.0, 3910.0], 0.0, 249, 0), + new DemoDesc("Hyrule", "warp_out.stb", "warp_out.stb", 0, 8, [0, 0, 0], 0, 201, 0), + new DemoDesc("Hyrule", "seal.stb", "seal.stb", 0, 4, [0.0, 0.0, 0.0], 0, 0, 0), + new DemoDesc("LinkRM", "tale.stb", "tale.stb", 0, 8, [0, 0, 0], 0, 0, 0), + new DemoDesc("LinkRM", "tale_2.stb", "tale_2.stb", 0, 8, [0, 0, 0], 0, 0, 0), + new DemoDesc("LinkRM", "get_shield.stb", "get_shield.stb", 0, 9, [0, 0, 0], 0, 201, 0), + new DemoDesc("LinkRM", "tale_2.stb", "tale_2.stb", 0, 8, [0, 0, 0], 0, 0, 0), + new DemoDesc("M2ganon", "attack_ganon.stb", "attack_ganon.stb", 0, 1, [2000.0, 11780.0, -8000.0], 0.0, 244, 0), + new DemoDesc("M2tower", "rescue.stb", "rescue.stb", 0, 1, [3214.0, 3939.0, -3011.0], 57.5, 20, 0), + new DemoDesc("M_DaiB", "pray_zola.stb", "pray_zola.stb", 0, 8, [0, 0, 0], 0, 229, 0), + new DemoDesc("MajyuE", "maju_shinnyu.stb", "maju_shinnyu.stb", 0, 0, [0, 0, 0], 0, 0, 0), + new DemoDesc("Mjtower", "find_sister.stb", "find_sister.stb", 0, 0, [4889.0, 0.0, -2635.0], 57.5, 0, 0), + new DemoDesc("Obombh", "bombshop.stb", "bombshop.stb", 0, 0, [0.0, 0.0, 0.0], 0.0, 200, 0), + new DemoDesc("Ocean", "counter.stb", "counter.stb", -1, 0, [0, 0, 0], 0, 0, 0), + new DemoDesc("Omori", "getperl_deku.stb", "getperl_deku.stb", 0, 9, [0, 0, 0], 0, 214, 0), + new DemoDesc("Omori", "meet_deku.stb", "meet_deku.stb", 0, 8, [0, 0, 0], 0, 213, 0), + new DemoDesc("Otkura", "awake_kokiri.stb", "awake_kokiri.stb", 0, 8, [0, 0, 0], 0, 0, 0), + new DemoDesc("Pjavdou", "getperl_jab.stb", "getperl_jab.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("kazeB", "pray_kokiri.stb", "pray_kokiri.stb", 0, 8, [0.0, 300.0, 0.0], 0.0, 232, 0), + new DemoDesc("kenroom", "awake_zelda.stb", "awake_zelda.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 2, 0), + new DemoDesc("kenroom", "master_sword.stb", "master_sword.stb", 0, 0, [-124.0, -3223.0, -7823.0], 180.0, 248, 0), + new DemoDesc("kenroom", "swing_sword.stb", "swing_sword.stb", 0, 10, [-124.0, -3223.0, -7823.0], 180.0, 249, 0), + new DemoDesc("sea", "awake.stb", "awake.stb", 44, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea", "departure.stb", "departure.stb", 44, 10, [-200000.0, 0.0, 320000.0], 0.0, 204, 0), + new DemoDesc("sea", "fairy.stb", "fairy.stb", 9, 8, [-180000.0, 740.0, -199937.0], 25.0, 2, 0), + new DemoDesc("sea", "fairy_flag_on.stb", "fairy_flag_on.stb", 9, 8, [-180000.0, 740.0, -199937.0], 25.0, 2, 0), + new DemoDesc("sea", "awake_zola.stb", "awake_zola.stb", 13, 8, [200000.0, 0.0, -200000.0], 0, 227, 0), + new DemoDesc("sea", "getperl_komori.stb", "getperl_komori.stb", 13, 9, [200000.0, 0.0, -200000.0], 0, 0, 0), + new DemoDesc("sea", "meetshishioh.stb", "meetshishioh.stb", 11, 8, [0.0, 0.0, -200000.0], 0, 128, 0), + new DemoDesc("sea", "stolensister.stb", "stolensister.stb", 44, 9, [0.0, 0.0, 20000.0], 0, 0, 0), + new DemoDesc("sea", "kaizoku_zelda_fly.stb", "kaizoku_zelda_fly.stb", 44, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), + new DemoDesc("sea_T", "awake.stb", "awake.stb", 255, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea_T", "title.stb", "title.stb", 44, 0, [-220000.0, 0.0, 320000.0], 180.0, 0, 0), + new DemoDesc("sea_T", "kaizoku_zelda_fly.stb", "kaizoku_zelda_fly.stb", 255, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), + new DemoDesc("sea_T", "departure.stb", "departure.stb", 255, 0, [-200000.0, 0.0, 320000.0], 0.0, 0, 0), ] // Location names taken from CryZe's Debug Menu. diff --git a/src/ZeldaWindWaker/d_stage.ts b/src/ZeldaWindWaker/d_stage.ts index 95f6210f2..590e914d3 100644 --- a/src/ZeldaWindWaker/d_stage.ts +++ b/src/ZeldaWindWaker/d_stage.ts @@ -518,7 +518,7 @@ export function dStage_dt_c_stageLoader(globals: dGlobals, dt: dStage_stageDt_c, export class dStage_roomDt_c extends dStage_dt { public fili: dStage_FileList_dt_c | null = null; public lgtv: stage_lightvec_info_class | null = null; - public lbnk: Uint8Array; + public lbnk: Uint8Array | null = null; } export class dStage_roomStatus_c { From f67129257e888445758b5ac4c755a60ac41f773a Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 6 Nov 2024 18:13:10 -0700 Subject: [PATCH 025/102] Defer loading of Demo arcs until a cutscene is played This also adds support for playing cutscenes in Scenes with multiple rooms loaded (e.g. Great Sea) --- src/ZeldaWindWaker/Main.ts | 67 ++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index ce6dcc4ca..6befd9c64 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -962,35 +962,13 @@ class SceneDesc { const dzr = assertExists(resCtrl.getStageResByName(ResType.Dzs, `Room${roomNo}`, `room.dzr`)); dStage_dt_c_roomLoader(globals, globals.roomCtrl.status[roomNo].data, dzr); - - if (!globals.roomCtrl.demoArcName) { - const lbnk = globals.roomCtrl.status[roomNo].data.lbnk; - if (lbnk) { - // This system can load a different demo file based on the active layer when the room is loaded. - // It should use dComIfG_play_c::getLayerNo(roomNo) to select the current layer. - // For now, just use layer 0 - // TODO: Use the first set layer - if(lbnk[1] != 0xFF) console.warn('Stage contains a demo in non-zero layer. May be worth investigating.'); - const layerNo = 0; - - const bank = lbnk[layerNo]; - if (bank != 0xFF) { - assert(bank >= 0 && bank < 100); - globals.roomCtrl.demoArcName = `Demo${bank.toString().padStart(2, '0')}`; - console.debug(`Loading stage demo file: ${globals.roomCtrl.demoArcName}`); - modelCache.fetchObjectData(globals.roomCtrl.demoArcName).catch(e => { - // @TODO: Better error handling. This does not prevent a debugger break. - console.log(`Failed to load stage demo file: ${globals.roomCtrl.demoArcName}`, e); - }) - - globals.renderer.demos = demoDescs.filter(d => d.stage == globals.stageName); - } - } - } - dStage_dt_c_roomReLoader(globals, globals.roomCtrl.status[roomNo].data, dzr); } + // Build a list of all the demos (cutscenes) that are playable in this scene + globals.renderer.demos = demoDescs.filter(d => + d.stage == globals.stageName && (d.roomNo == -1 || this.roomList.includes(d.roomNo))); + return renderer; } } @@ -1009,20 +987,39 @@ class DemoDesc { ) {} async load(globals: dGlobals) { - await globals.modelCache.waitForLoad(); + // 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.roomNo]?.data.lbnk; + if (lbnk) { + const bank = lbnk[this.layer]; + if (bank != 0xFF) { + assert(bank >= 0 && bank < 100); + globals.roomCtrl.demoArcName = `Demo${bank.toString().padStart(2, '0')}`; + console.debug(`Loading stage demo file: ${globals.roomCtrl.demoArcName}`); + + globals.modelCache.fetchObjectData(globals.roomCtrl.demoArcName).catch(e => { + // @TODO: Better error handling. This does not prevent a debugger break. + console.log(`Failed to load stage demo file: ${globals.roomCtrl.demoArcName}`, e); + }) + } + } + if(globals.roomCtrl.demoArcName) { - const stbData = globals.modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName!, this.stbFilename); - // @TODO: Search the stage arc as well. See dEvDtStaff_c::specialProcPackage() - if( stbData ) { globals.scnPlay.demo.create(stbData, this.offsetPos, this.rotY / 180.0 * Math.PI); } + await globals.modelCache.waitForLoad(); + + // @TODO: Set noclip layer visiblity based on this.layer + // @TODO: Test counter.stb + // @TODO: Fix switching cutscenes while one is playing + + let demoData = globals.modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName, this.stbFilename); + if (demoData == null) + 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); } + else { console.warn('Failed to load demo data:', this.stbFilename); } } } } -// Extracted from the game using a modified version of the excellent wwrando/wwlib/stage_searcher.py script from LagoLunatic -// Most of this data comes from the PLAY action of the PACKAGE actor in an event from a Stage's event_list.dat. This -// action has properties for the room and layer that these cutscenes are designed for. HOWEVER, this data is often missing. -// It has been reconstructed by cross-referencing each Room's lbnk section (which points to a Demo*.arc file for each layer), -// the .stb files contained in each of those Objects/Demo*.arc files, and the FileName attribute from the event action. const demoDescs = [ new DemoDesc("ADMumi", "warp_in.stb", "warp_in.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 200, 0), new DemoDesc("ADMumi", "warphole.stb", "warphole.stb", 0, 10, [0.0, 0.0, 0.0], 0.0, 219, 0), From d808ae5c9de03036cd9031c77028a160183a39cc Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 07:14:24 -0700 Subject: [PATCH 026/102] Tested counter.stb, the only demo expected to be a Stage resource That file is not present in Stage/Ocean/Stage.arc, it must a be a leftover. I've re-arranged the logic for loading demos to match the game and support the fallback-to-Stage behavior in case I can find counter.stb somewhere later --- src/ZeldaWindWaker/Main.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 6befd9c64..5d5fe0e5b 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -1004,19 +1004,19 @@ class DemoDesc { } } - if(globals.roomCtrl.demoArcName) { - await globals.modelCache.waitForLoad(); - - // @TODO: Set noclip layer visiblity based on this.layer - // @TODO: Test counter.stb - // @TODO: Fix switching cutscenes while one is playing - - let demoData = globals.modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName, this.stbFilename); - if (demoData == null) - 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); } - else { console.warn('Failed to load demo data:', this.stbFilename); } - } + await globals.modelCache.waitForLoad(); + + // @TODO: Set noclip layer visiblity based on this.layer + // @TODO: Fix switching cutscenes while one is playing + + let demoData; + if(globals.roomCtrl.demoArcName) + demoData = globals.modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName, this.stbFilename); + 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); } + else { console.warn('Failed to load demo data:', this.stbFilename); } } } @@ -1050,7 +1050,6 @@ const demoDescs = [ new DemoDesc("MajyuE", "maju_shinnyu.stb", "maju_shinnyu.stb", 0, 0, [0, 0, 0], 0, 0, 0), new DemoDesc("Mjtower", "find_sister.stb", "find_sister.stb", 0, 0, [4889.0, 0.0, -2635.0], 57.5, 0, 0), new DemoDesc("Obombh", "bombshop.stb", "bombshop.stb", 0, 0, [0.0, 0.0, 0.0], 0.0, 200, 0), - new DemoDesc("Ocean", "counter.stb", "counter.stb", -1, 0, [0, 0, 0], 0, 0, 0), new DemoDesc("Omori", "getperl_deku.stb", "getperl_deku.stb", 0, 9, [0, 0, 0], 0, 214, 0), new DemoDesc("Omori", "meet_deku.stb", "meet_deku.stb", 0, 8, [0, 0, 0], 0, 213, 0), new DemoDesc("Otkura", "awake_kokiri.stb", "awake_kokiri.stb", 0, 8, [0, 0, 0], 0, 0, 0), @@ -1068,10 +1067,15 @@ const demoDescs = [ new DemoDesc("sea", "meetshishioh.stb", "meetshishioh.stb", 11, 8, [0.0, 0.0, -200000.0], 0, 128, 0), new DemoDesc("sea", "stolensister.stb", "stolensister.stb", 44, 9, [0.0, 0.0, 20000.0], 0, 0, 0), new DemoDesc("sea", "kaizoku_zelda_fly.stb", "kaizoku_zelda_fly.stb", 44, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), - new DemoDesc("sea_T", "awake.stb", "awake.stb", 255, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), new DemoDesc("sea_T", "title.stb", "title.stb", 44, 0, [-220000.0, 0.0, 320000.0], 180.0, 0, 0), + + // These are present in the sea_T event_list.dat, but not in the room's lbnk. They are only playable from "sea". + new DemoDesc("sea_T", "awake.stb", "awake.stb", 255, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), new DemoDesc("sea_T", "kaizoku_zelda_fly.stb", "kaizoku_zelda_fly.stb", 255, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), new DemoDesc("sea_T", "departure.stb", "departure.stb", 255, 0, [-200000.0, 0.0, 320000.0], 0.0, 0, 0), + + // The game expects this STB file to be in Stage/Ocean/Stage.arc, but it is not. Must be a leftover. + new DemoDesc("Ocean", "counter.stb", "counter.stb", -1, 0, [0, 0, 0], 0, 0, 0), ] // Location names taken from CryZe's Debug Menu. From f8795fc741065f2c0068e8f95a821b8966d44e08 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 07:44:38 -0700 Subject: [PATCH 027/102] Fix starting a cutscene while another is playing Now we just call demo.remove() when a cutscene is triggered. The game has no mechanism for this, it just assumes no demo is playing when one is started (seems fair) --- src/ZeldaWindWaker/Main.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 5d5fe0e5b..cf99475ec 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -987,6 +987,8 @@ class DemoDesc { ) {} async load(globals: dGlobals) { + globals.scnPlay.demo.remove(); + // 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.roomNo]?.data.lbnk; @@ -1001,13 +1003,12 @@ class DemoDesc { // @TODO: Better error handling. This does not prevent a debugger break. console.log(`Failed to load stage demo file: ${globals.roomCtrl.demoArcName}`, e); }) + + await globals.modelCache.waitForLoad(); } } - await globals.modelCache.waitForLoad(); - // @TODO: Set noclip layer visiblity based on this.layer - // @TODO: Fix switching cutscenes while one is playing let demoData; if(globals.roomCtrl.demoArcName) From aa79c6f3299ffb12a7fbfe8def24c4b20f699d84 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 09:14:21 -0700 Subject: [PATCH 028/102] Added support for STB control sequences, used suspend demos while waiting for user input But for now, we just suspend for one frame and then continue. - Added the "Control" object to TControl - This can be controlled by the STB file and is used to set the suspend state when waiting for user input (e.g. when a message is being displayed) - Parser now handles -1 object type (control) - Added suspend support --- src/Common/JSYSTEM/JStudio.ts | 98 +++++++++++++++++++++++++---------- src/ZeldaWindWaker/d_demo.ts | 13 +++-- 2 files changed, 80 insertions(+), 31 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 0fa94673b..2c3778da7 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -277,33 +277,44 @@ abstract class STBObject { private pSequence_next: number; private mWait: number = 0; - constructor(control: TControl, blockObj: TBlockObject, adaptor: TAdaptor) { + constructor(control: TControl, blockObj?: TBlockObject, adaptor?: TAdaptor) { this.mControl = control; - this.mAdaptor = adaptor; - this.mId = blockObj.id; - this.mType = blockObj.type; - this.mFlags = blockObj.flag; - this.mData = blockObj.data; - this.pSequence = 0; - this.pSequence_next = 0xC + align(blockObj.id.length + 1, 4); + if (blockObj && adaptor) { + this.mAdaptor = adaptor; + + this.mId = blockObj.id; + this.mType = blockObj.type; + this.mFlags = blockObj.flag; + this.mData = blockObj.data; + this.pSequence = 0; + this.pSequence_next = 0xC + align(blockObj.id.length + 1, 4); + } } // These are intended to be overridden by subclasses abstract do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void; - do_begin() { this.mAdaptor.adaptor_do_begin(this); } - do_end() { this.mAdaptor.adaptor_do_end(this); } + do_begin() { if (this.mAdaptor) this.mAdaptor.adaptor_do_begin(this); } + do_end() { if (this.mAdaptor) this.mAdaptor.adaptor_do_end(this); } // Done updating this frame. Compute our variable data (i.e. interpolate) and send to the game object. do_wait(frameCount: number) { - this.mAdaptor.adaptor_updateVariableValue(this, frameCount); - this.mAdaptor.adaptor_do_update(this, frameCount); + if (this.mAdaptor) this.mAdaptor.adaptor_updateVariableValue(this, frameCount); + if (this.mAdaptor) this.mAdaptor.adaptor_do_update(this, frameCount); } // do_data(void const*, u32, void const*, u32) {} getStatus() { return this.mStatus; } - isSuspended(): boolean { - return this.mSuspendFrames > 0; + getSuspendFrames(): number { return this.mSuspendFrames; } + isSuspended(): boolean { return this.mSuspendFrames > 0; } + setSuspend(frameCount: number) { this.mSuspendFrames = frameCount; } + + reset(blockObj: TBlockObject) { + this.pSequence = 0; + this.mStatus = TEStatus.STILL; + this.pSequence_next = 0xC + align(blockObj.id.length + 1, 4); + this.mData = blockObj.data; + this.mWait = 0; } forward(frameCount: number): boolean { @@ -326,7 +337,7 @@ abstract class STBObject { this.mStatus = TEStatus.WAIT; } - if ((this.mControl && this.mControl.mIsSuspended) || this.isSuspended()) { + if ((this.mControl && this.mControl.isSuspended()) || this.isSuspended()) { if (this.mIsSequence) { assert((this.mStatus == TEStatus.WAIT) || (this.mStatus == TEStatus.SUSPEND)); this.mStatus = TEStatus.SUSPEND; @@ -405,17 +416,30 @@ abstract class STBObject { switch (cmd) { case ESequenceCmd.End: break; + + case ESequenceCmd.SetFlag: + debugger; // Untested. Remove after confirmed working. + break; case ESequenceCmd.Wait: this.mWait = param; break; + case ESequenceCmd.Skip: + debugger; // Untested. Remove after confirmed working. + break; + + case ESequenceCmd.Suspend: + this.mSuspendFrames += param; + break; + case ESequenceCmd.Paragraph: byteIdx += 4; while (byteIdx < this.pSequence_next) { const para = TParagraph.parse(view, byteIdx); if (para.type <= 0xff) { console.debug('Unsupported paragraph feature: ', para.type); + debugger; // process_paragraph_reserved_(para.type, para.content, para.param); } else { this.do_paragraph(this.mData, para.dataSize, para.dataOffset, para.type); @@ -426,13 +450,22 @@ abstract class STBObject { break; default: - console.debug('Unhandled sequence cmd: ', cmd); + console.debug('Unsupported sequence cmd: ', cmd); + debugger; byteIdx += 4; break; } } } +class TControlObject extends STBObject { + constructor(control: TControl) { + super(control) + } + + override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { } +} + //---------------------------------------------------------------------------------------------------------------------- // Camera //---------------------------------------------------------------------------------------------------------------------- @@ -957,7 +990,8 @@ namespace FVB { case EInterpolateType.None: debugger; break; // Untested. Remove after confirmed working. case EInterpolateType.Linear: - debugger; break; // Untested. Remove after confirmed working. + this.interpFunc = this.interpolateLinear; + debugger; break; // Untested. Remove after confirmed working. case EInterpolateType.Plateau: debugger; break; // Untested. Remove after confirmed working. case EInterpolateType.BSpline: @@ -1111,6 +1145,8 @@ namespace FVB { //---------------------------------------------------------------------------------------------------------------------- // STB Parsing //---------------------------------------------------------------------------------------------------------------------- +const BLOCK_TYPE_CONTROL = "ÿÿÿÿ"; // -1 represented as a fourcc + enum ESequenceCmd { End = 0, SetFlag = 1, @@ -1145,7 +1181,7 @@ export class TControl { private mSystem: TSystem; public mFvbControl = new FVB.TControl(); public mSecondsPerFrame: number; - public mIsSuspended = false; + private mSuspendFrames: number; private mTransformOrigin?: vec3; private mTransformRotY?: number; @@ -1155,12 +1191,16 @@ export class TControl { private mStatus: TEStatus = TEStatus.STILL; private mObjects: STBObject[] = []; + // A special object that the STB file can use to suspend the demo (such as while waiting for player input) + private mControlObject = new TControlObject(this); + constructor(system: TSystem) { this.mSystem = system; this.mSecondsPerFrame = 1 / 30.0; // @TODO: Allow the game to change this? } - public isSuspended() { return this.mIsSuspended; } + public isSuspended() { return this.mSuspendFrames > 0; } + public setSuspend(frameCount: number) { return this.mControlObject.setSuspend( frameCount ); } public isTransformEnabled() { return !!this.mTransformOrigin; } public getTransformOnSet() { return this.mTransformOnSetMtx; } @@ -1178,13 +1218,20 @@ export class TControl { mat4.rotateY(this.mTransformOnSetMtx, this.mTransformOnSetMtx, rotY); } - public forward(flags: number): boolean { - let shouldContinue = false; + public setControlObject(obj: TBlockObject) { + this.mControlObject.reset(obj); + } + + public forward(frameCount: number): boolean { + ; let andStatus = 0xFF; let orStatus = 0; + this.mSuspendFrames = this.mControlObject.getSuspendFrames(); + let shouldContinue = this.mControlObject.forward(frameCount); + for (let obj of this.mObjects) { - const res = obj.forward(flags); + const res = obj.forward(frameCount); shouldContinue ||= res; const objStatus = obj.getStatus(); @@ -1246,10 +1293,9 @@ export class TParse { data: file } - const blockTypeNum = file.view.getInt32(4); - if (blockTypeNum == -1) { - console.debug('Unhandled STB block type: -1'); - debugger; // Remove after implementing and testing + if (blockObj.type == BLOCK_TYPE_CONTROL) { + this.mControl.setControlObject(blockObj); + return true; } if (flags & 0x10) { diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index bb5d71673..34c958be3 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -5,7 +5,7 @@ import { getMatrixAxisY } from "../MathHelpers.js"; import { dGlobals } from "./Main"; export enum EDemoMode { - None, + None, Playing, Ended } @@ -153,7 +153,7 @@ class dDemo_system_c implements TSystem { constructor( private globals: dGlobals - ) {} + ) { } public JSGFindObject(objId: string, objType: JStage.TEObject): JStage.TObject | undefined { switch (objType) { @@ -188,7 +188,7 @@ export class dDemo_manager_c { constructor( private globals: dGlobals - ) {} + ) { } getFrame() { return this.mFrame; } getFrameNoMsg() { return this.mFrameNoMsg; } @@ -198,7 +198,7 @@ export class dDemo_manager_c { public create(data: ArrayBufferSlice, originPos?: vec3, rotY?: number): boolean { this.mParser = new TParse(this.mControl); - if(!this.mParser.parse(data, 0)) { + if (!this.mParser.parse(data, 0)) { console.error('Failed to parse demo data'); return false; } @@ -227,9 +227,12 @@ export class dDemo_manager_c { if (!this.mCurFile) { return false; } - + const dtFrames = this.globals.context.viewerInput.deltaTime / 1000.0 * 30; + // noclip modification: If a demo is suspended (waiting for the user to interact with a message), just resume + if (this.mControl.isSuspended()) { this.mControl.setSuspend(0); } + if (this.mControl.forward(dtFrames)) { this.mFrame++; if (!this.mControl.isSuspended()) { From fb1c7dfa172a50f43045fccb06c399b238a519a3 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 21:30:39 -0700 Subject: [PATCH 029/102] JStudio: Implement FunctionValue_Constant --- src/Common/JSYSTEM/JStudio.ts | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 2c3778da7..a879a093b 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -827,8 +827,8 @@ namespace FVB { switch (block.type) { // case EFuncValType.Composite: // return new TObject_composite(block); - // case EFuncValType.Constant: - // return new TObject_constant(block); + case EFuncValType.Constant: + return new TObject_Constant(block); // case EFuncValType.Transition: // return new TObject_transition(block); // case EFuncValType.List: @@ -971,6 +971,18 @@ namespace FVB { //---------------------------------------------------------------------------------------------------------------------- // FunctionValues //---------------------------------------------------------------------------------------------------------------------- + class FunctionValue_Constant extends TFunctionValue { + private value: number = 0; + + getType() { return EFuncValType.Constant; } + prepare() {} + setData(value: number) { this.value = value; } + getValue(timeSec: number) { + debugger; // Untested. Remove once confirmed working + return this.value; + } + } + class FunctionValue_ListParameter extends TFunctionValue { protected override range = new Attribute.Range(); protected override interpolate = new Attribute.Interpolate(); @@ -1007,7 +1019,7 @@ namespace FVB { } } - set_data(values: Float32Array) { + setData(values: Float32Array) { this.keys = values; this.keyCount = values.length / 2; this.curKeyIdx = 0; @@ -1129,6 +1141,16 @@ namespace FVB { // FVB Objects // Manages a FunctionValue. //---------------------------------------------------------------------------------------------------------------------- + class TObject_Constant extends FVB.TObject { + override funcVal = new FunctionValue_Constant; + + override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + assert(para.dataSize == 4); + const value = file.view.getFloat32(para.dataOffset); + this.funcVal.setData(value); + } + } + class TObject_ListParameter extends FVB.TObject { override funcVal = new FunctionValue_ListParameter; @@ -1137,7 +1159,7 @@ namespace FVB { // Each Key contains 2 floats, a time and value const keyCount = file.view.getUint32(para.dataOffset + 0); const keys = file.buffer.createTypedArray(Float32Array, para.dataOffset + 4, keyCount * 2, Endianness.BIG_ENDIAN); - this.funcVal.set_data(keys); + this.funcVal.setData(keys); } } } From 9424e851d076a5b02447a6f2921d624bed11ee1f Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 21:37:13 -0700 Subject: [PATCH 030/102] JStudio: Implemented interpolateNone Put `debugger` statements in place to catch first usages of None and Plateau interpolation so that they can be confirmed working --- src/Common/JSYSTEM/JStudio.ts | 40 +++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index a879a093b..01b82339d 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -416,7 +416,7 @@ abstract class STBObject { switch (cmd) { case ESequenceCmd.End: break; - + case ESequenceCmd.SetFlag: debugger; // Untested. Remove after confirmed working. break; @@ -975,12 +975,12 @@ namespace FVB { private value: number = 0; getType() { return EFuncValType.Constant; } - prepare() {} + prepare() { } setData(value: number) { this.value = value; } - getValue(timeSec: number) { + getValue(timeSec: number) { debugger; // Untested. Remove once confirmed working - return this.value; - } + return this.value; + } } class FunctionValue_ListParameter extends TFunctionValue { @@ -999,19 +999,12 @@ namespace FVB { const interp = this.interpolate.get(); switch (interp) { - case EInterpolateType.None: - debugger; break; // Untested. Remove after confirmed working. - case EInterpolateType.Linear: - this.interpFunc = this.interpolateLinear; - debugger; break; // Untested. Remove after confirmed working. - case EInterpolateType.Plateau: - debugger; break; // Untested. Remove after confirmed working. + case EInterpolateType.None: this.interpFunc = this.interpolateNone; + case EInterpolateType.Linear: this.interpFunc = this.interpolateLinear; + case EInterpolateType.Plateau: this.interpFunc = this.interpolatePlateau; case EInterpolateType.BSpline: if (this.keyCount > 2) { this.interpFunc = this.interpolateBSpline; } - else { - this.interpFunc = this.interpolateLinear; - debugger; // Untested. Remove after confirmed working. - } + else { this.interpFunc = this.interpolateLinear; } break; default: @@ -1130,11 +1123,22 @@ namespace FVB { return Attribute.Interpolate.BSpline_Nonuniform(t, controlPoints, knotVector); } + interpolateNone(t: number) { + debugger; // Untested. Remove after confirmed working. + return this.keys[this.curKeyIdx]; + } + interpolateLinear(t: number) { const ks = this.keys; const c = this.curKeyIdx; return Attribute.Interpolate.Linear(t, ks[c - 2], ks[c - 1], ks[c + 0], ks[c + 1]); } + + interpolatePlateau(t: number) { + console.error('Plateau interpolation not yet implemented') + debugger; // Untested. Remove after confirmed working. + return this.interpolateNone(t); + } } //---------------------------------------------------------------------------------------------------------------------- @@ -1150,7 +1154,7 @@ namespace FVB { this.funcVal.setData(value); } } - + class TObject_ListParameter extends FVB.TObject { override funcVal = new FunctionValue_ListParameter; @@ -1222,7 +1226,7 @@ export class TControl { } public isSuspended() { return this.mSuspendFrames > 0; } - public setSuspend(frameCount: number) { return this.mControlObject.setSuspend( frameCount ); } + public setSuspend(frameCount: number) { return this.mControlObject.setSuspend(frameCount); } public isTransformEnabled() { return !!this.mTransformOrigin; } public getTransformOnSet() { return this.mTransformOnSetMtx; } From 65feeb5c2abb487f49c002e50214214f342a396b Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 22:25:16 -0700 Subject: [PATCH 031/102] Implement (untested) FunctionValue_Composite --- src/Common/JSYSTEM/JStudio.ts | 123 ++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 6 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 01b82339d..1045a9ba7 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -766,7 +766,7 @@ namespace FVB { abstract prepare_data(para: TParagraph, control: TControl, file: Reader): void; - prepare(block: TBlock, mControl: TControl, file: Reader) { + prepare(block: TBlock, pControl: TControl, file: Reader) { const blockNext = file.offset + block.size; file.offset = blockNext; @@ -780,13 +780,13 @@ namespace FVB { return; case EPrepareOp.Data: - this.prepare_data(para, mControl, file); + this.prepare_data(para, pControl, file); break; case EPrepareOp.RangeSet: assert(para.dataSize == 8); const range = this.funcVal.getAttrRange(); - assert(!!range, 'FVB Paragraph assumes TObject has range attribute, but it does not'); + assert(!!range, 'FVB Paragraph assumes FuncVal has range attribute, but it does not'); const begin = file.view.getFloat32(para.dataOffset + 0); const end = file.view.getFloat32(para.dataOffset + 4); range.set(begin, end); @@ -795,7 +795,7 @@ namespace FVB { case EPrepareOp.InterpSet: assert(para.dataSize == 4); const interp = this.funcVal.getAttrInterpolate(); - assert(!!interp, 'FVB Paragraph assumes TObject has interpolate attribute, but it does not'); + assert(!!interp, 'FVB Paragraph assumes FuncVal has interpolate attribute, but it does not'); const interpType = file.view.getUint32(para.dataOffset + 0); interp.set(interpType); break; @@ -825,8 +825,8 @@ namespace FVB { // Really this is a fvb::TFactory method public createObject(block: TBlock): TObject | undefined { switch (block.type) { - // case EFuncValType.Composite: - // return new TObject_composite(block); + case EFuncValType.Composite: + return new TObject_Composite(block); case EFuncValType.Constant: return new TObject_Constant(block); // case EFuncValType.Transition: @@ -839,6 +839,7 @@ namespace FVB { // return new TObject_hermite(block); default: console.warn('Unknown FVB type: ', block.type); + debugger; return undefined; } } @@ -927,6 +928,7 @@ namespace FVB { } export class Refer { + public fvs: TFunctionValue[] = []; } export class Interpolate { @@ -1009,6 +1011,7 @@ namespace FVB { default: console.warn('Invalid EInterp value', interp); + debugger; } } @@ -1141,6 +1144,73 @@ namespace FVB { } } + class FunctionValue_Composite extends TFunctionValue { + protected override refer = new Attribute.Refer(); + + override prepare(): void { } + override getType(): EFuncValType { return EFuncValType.Composite; } + setData(func: (ref: TFunctionValue[], dataVal: number, t: number) => number, dataVal: number) { + this.func = func; + this.dataVal = dataVal; + } + + getValue(timeSec: number): number { + return this.func(this.refer.fvs, this.dataVal, timeSec); + } + + public static composite_raw(fvs: TFunctionValue[], dataVal: number, timeSec: number): number { + debugger; // Untested. Remove once confirmed working + if (fvs.length == 0) { return 0.0; } + return fvs[dataVal].getValue(timeSec); + } + + public static composite_index(fvs: TFunctionValue[], dataVal: number, timeSec: number): number { + debugger; // Untested. Remove once confirmed working + return 0.0; + } + + public static composite_parameter(fvs: TFunctionValue[], dataVal: number, timeSec: number): number { + debugger; // Untested. Remove once confirmed working + let val = timeSec - dataVal; + for (let fv of fvs) { val = fv.getValue(timeSec); } + return val; + } + + public static composite_add(fvs: TFunctionValue[], dataVal: number, timeSec: number): number { + debugger; // Untested. Remove once confirmed working + let val = dataVal; + for (let fv of fvs) { val += fv.getValue(timeSec); } + return val; + } + + public static composite_subtract(fvs: TFunctionValue[], dataVal: number, timeSec: number): number { + debugger; // Untested. Remove once confirmed working + if (fvs.length == 0) { return 0.0; } + let val = fvs[0].getValue(timeSec); + for (let fv of fvs.slice(1)) { val -= fv.getValue(timeSec); } + return val - dataVal; + } + + public static composite_multiply(fvs: TFunctionValue[], dataVal: number, timeSec: number): number { + debugger; // Untested. Remove once confirmed working + let val = dataVal; + for (let fv of fvs) { val *= fv.getValue(timeSec); } + return val; + } + + public static composite_divide(fvs: TFunctionValue[], dataVal: number, timeSec: number): number { + debugger; // Untested. Remove once confirmed working + if (fvs.length == 0) { return 0.0; } + let val = fvs[0].getValue(timeSec); + for (let fv of fvs.slice(1)) { val /= fv.getValue(timeSec); } + return val / dataVal; + } + + + private func: (ref: TFunctionValue[], dataVal: number, t: number) => number; + private dataVal: number; + } + //---------------------------------------------------------------------------------------------------------------------- // FVB Objects // Manages a FunctionValue. @@ -1166,6 +1236,47 @@ namespace FVB { this.funcVal.setData(keys); } } + + enum TECompositeOp { + NONE, + RAW, + IDX, + PARAM, + ADD, + SUB, + MUL, + DIV, + ENUM_SIZE, + } + + class TObject_Composite extends FVB.TObject { + override funcVal = new FunctionValue_Composite; + + override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + assert(para.dataSize >= 8); + + const compositeOp = file.view.getUint32(para.dataOffset + 0); + const floatData = file.view.getFloat32(para.dataOffset + 4); + const uintData = file.view.getUint32(para.dataOffset + 4); + + let fvData: number; + let fvFunc: (ref: TFunctionValue[], data: number, t: number) => number; + switch (compositeOp) { + case TECompositeOp.RAW: fvData = uintData; fvFunc = FunctionValue_Composite.composite_raw; break; + case TECompositeOp.IDX: fvData = uintData; fvFunc = FunctionValue_Composite.composite_index; break; + case TECompositeOp.PARAM: fvData = floatData; fvFunc = FunctionValue_Composite.composite_parameter; break; + case TECompositeOp.ADD: fvData = floatData; fvFunc = FunctionValue_Composite.composite_add; break; + case TECompositeOp.SUB: fvData = floatData; fvFunc = FunctionValue_Composite.composite_subtract; break; + case TECompositeOp.MUL: fvData = floatData; fvFunc = FunctionValue_Composite.composite_multiply; break; + case TECompositeOp.DIV: fvData = floatData; fvFunc = FunctionValue_Composite.composite_divide; break; + default: + console.warn('Unsupported CompositeOp:', compositeOp); + return; + } + + this.funcVal.setData(fvFunc, fvData) + } + } } //---------------------------------------------------------------------------------------------------------------------- From fe7f6d41e4771affbfc2e95b94500c85fac6aa3d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 22:26:13 -0700 Subject: [PATCH 032/102] Implement EPrepareOp.ObjSetByIdx (tested) and ObjSetByName (untested) --- src/Common/JSYSTEM/JStudio.ts | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 1045a9ba7..61a04a1d6 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -792,6 +792,36 @@ namespace FVB { range.set(begin, end); break; + case EPrepareOp.ObjSetByName: { + debugger; // Untested. Remove after confirmed working. + assert(para.dataSize >= 4); + const refer = this.funcVal.getAttrRefer(); + assert(!!refer, 'FVB Paragraph assumes FuncVal has refer attribute, but it does not'); + const objCount = file.view.getUint32(para.dataOffset + 0); + for (let i = 0; i < objCount; i++) { + const idSize = file.view.getUint32(para.dataOffset + 4 + i * 8 + 0); + const id = readString(file.buffer, para.dataOffset + 4 + i * 8 + 4, idSize); + const obj = pControl.mObjects.find(o => o.id == id); + assert(!!obj); + refer.fvs.push(obj.funcVal); + } + break; + } + + case EPrepareOp.ObjSetByIdx: { + assert(para.dataSize >= 4); + const refer = this.funcVal.getAttrRefer(); + assert(!!refer, 'FVB Paragraph assumes FuncVal has refer attribute, but it does not'); + const objCount = file.view.getUint32(para.dataOffset + 0); + for (let i = 0; i < objCount; i++) { + const idx = file.view.getUint32(para.dataOffset + 4 + i * 4); + const obj = pControl.mObjects[idx]; + assert(!!obj); + refer.fvs.push(obj.funcVal); + } + break; + } + case EPrepareOp.InterpSet: assert(para.dataSize == 4); const interp = this.funcVal.getAttrInterpolate(); @@ -800,9 +830,6 @@ namespace FVB { interp.set(interpType); break; - case EPrepareOp.ObjSetByName: - case EPrepareOp.ObjSetByIdx: - case EPrepareOp.RangeProgress: case EPrepareOp.RangeAdjust: case EPrepareOp.RangeOutside: From 3e1a67e187e49691ef520f8061c124c1a4d7882d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 22:36:36 -0700 Subject: [PATCH 033/102] Reorganized and named some cutscenes --- src/ZeldaWindWaker/Main.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index cf99475ec..9a170a953 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -1022,12 +1022,19 @@ class DemoDesc { } const demoDescs = [ + new DemoDesc("sea", "Awaken", "awake.stb", 44, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea", "Stolen Sister", "stolensister.stb", 44, 9, [0.0, 0.0, 20000.0], 0, 0, 0), + new DemoDesc("sea", "Departure", "departure.stb", 44, 10, [-200000.0, 0.0, 320000.0], 0.0, 204, 0), + new DemoDesc("sea", "Pirate Zelda Fly", "kaizoku_zelda_fly.stb", 44, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), + new DemoDesc("sea_T", "Title Screen", "title.stb", 44, 0, [-220000.0, 0.0, 320000.0], 180.0, 0, 0), + new DemoDesc("ADMumi", "warp_in.stb", "warp_in.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 200, 0), new DemoDesc("ADMumi", "warphole.stb", "warphole.stb", 0, 10, [0.0, 0.0, 0.0], 0.0, 219, 0), new DemoDesc("ADMumi", "runaway_majuto.stb", "runaway_majuto.stb", 0, 11, [0, 0, 0], 0, 0, 0), new DemoDesc("ADMumi", "towerd.stb", "towerd.stb", 0, 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), new DemoDesc("ADMumi", "towerf.stb", "towerf.stb", 0, 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), new DemoDesc("ADMumi", "towern.stb", "towern.stb", 0, 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), + new DemoDesc("A_mori", "meet_tetra.stb", "meet_tetra.stb", 0, 0, [0, 0, 0], 0, 0, 0), new DemoDesc("Adanmae", "howling.stb", "howling.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), new DemoDesc("Atorizk", "dragontale.stb", "dragontale.stb", 0, 0, [0, 0, 0], 0, 0, 0), @@ -1056,24 +1063,23 @@ const demoDescs = [ new DemoDesc("Otkura", "awake_kokiri.stb", "awake_kokiri.stb", 0, 8, [0, 0, 0], 0, 0, 0), new DemoDesc("Pjavdou", "getperl_jab.stb", "getperl_jab.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), new DemoDesc("kazeB", "pray_kokiri.stb", "pray_kokiri.stb", 0, 8, [0.0, 300.0, 0.0], 0.0, 232, 0), + new DemoDesc("kenroom", "awake_zelda.stb", "awake_zelda.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 2, 0), new DemoDesc("kenroom", "master_sword.stb", "master_sword.stb", 0, 0, [-124.0, -3223.0, -7823.0], 180.0, 248, 0), new DemoDesc("kenroom", "swing_sword.stb", "swing_sword.stb", 0, 10, [-124.0, -3223.0, -7823.0], 180.0, 249, 0), - new DemoDesc("sea", "awake.stb", "awake.stb", 44, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), - new DemoDesc("sea", "departure.stb", "departure.stb", 44, 10, [-200000.0, 0.0, 320000.0], 0.0, 204, 0), - new DemoDesc("sea", "fairy.stb", "fairy.stb", 9, 8, [-180000.0, 740.0, -199937.0], 25.0, 2, 0), + + new DemoDesc("sea", "Fairy", "fairy.stb", 9, 8, [-180000.0, 740.0, -199937.0], 25.0, 2, 0), new DemoDesc("sea", "fairy_flag_on.stb", "fairy_flag_on.stb", 9, 8, [-180000.0, 740.0, -199937.0], 25.0, 2, 0), - new DemoDesc("sea", "awake_zola.stb", "awake_zola.stb", 13, 8, [200000.0, 0.0, -200000.0], 0, 227, 0), - new DemoDesc("sea", "getperl_komori.stb", "getperl_komori.stb", 13, 9, [200000.0, 0.0, -200000.0], 0, 0, 0), + + new DemoDesc("sea", "Zola Awakens", "awake_zola.stb", 13, 8, [200000.0, 0.0, -200000.0], 0, 227, 0), + new DemoDesc("sea", "Get Komori Pearl", "getperl_komori.stb", 13, 9, [200000.0, 0.0, -200000.0], 0, 0, 0), + new DemoDesc("sea", "meetshishioh.stb", "meetshishioh.stb", 11, 8, [0.0, 0.0, -200000.0], 0, 128, 0), - new DemoDesc("sea", "stolensister.stb", "stolensister.stb", 44, 9, [0.0, 0.0, 20000.0], 0, 0, 0), - new DemoDesc("sea", "kaizoku_zelda_fly.stb", "kaizoku_zelda_fly.stb", 44, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), - new DemoDesc("sea_T", "title.stb", "title.stb", 44, 0, [-220000.0, 0.0, 320000.0], 180.0, 0, 0), - + // These are present in the sea_T event_list.dat, but not in the room's lbnk. They are only playable from "sea". - new DemoDesc("sea_T", "awake.stb", "awake.stb", 255, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), - new DemoDesc("sea_T", "kaizoku_zelda_fly.stb", "kaizoku_zelda_fly.stb", 255, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), - new DemoDesc("sea_T", "departure.stb", "departure.stb", 255, 0, [-200000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea_T", "Awaken", "awake.stb", 255, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea_T", "Departure", "departure.stb", 255, 0, [-200000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea_T", "PirateZeldaFly", "kaizoku_zelda_fly.stb", 255, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), // The game expects this STB file to be in Stage/Ocean/Stage.arc, but it is not. Must be a leftover. new DemoDesc("Ocean", "counter.stb", "counter.stb", -1, 0, [0, 0, 0], 0, 0, 0), From b89f0aa4f3a2f526a0d98d45ff00baa084a54f45 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 22:43:13 -0700 Subject: [PATCH 034/102] Reorganized FunctionValues to be grouped with their corresponding TObject implementations --- src/Common/JSYSTEM/JStudio.ts | 141 ++++++++++++++++++---------------- 1 file changed, 73 insertions(+), 68 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 61a04a1d6..9ae962a10 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -998,8 +998,19 @@ namespace FVB { } //---------------------------------------------------------------------------------------------------------------------- - // FunctionValues + // FunctionValue: Constant + // Simply return a constant value every frame //---------------------------------------------------------------------------------------------------------------------- + class TObject_Constant extends FVB.TObject { + override funcVal = new FunctionValue_Constant; + + override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + assert(para.dataSize == 4); + const value = file.view.getFloat32(para.dataOffset); + this.funcVal.setData(value); + } + } + class FunctionValue_Constant extends TFunctionValue { private value: number = 0; @@ -1012,6 +1023,21 @@ namespace FVB { } } + //---------------------------------------------------------------------------------------------------------------------- + // FunctionValue: ListParameter + // Interpolate between a list of values using a specific interpolation function [None, Linear, Plateau, BSpline] + //---------------------------------------------------------------------------------------------------------------------- + class TObject_ListParameter extends FVB.TObject { + override funcVal = new FunctionValue_ListParameter; + + override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + assert(para.dataSize >= 8); + // Each Key contains 2 floats, a time and value + const keyCount = file.view.getUint32(para.dataOffset + 0); + const keys = file.buffer.createTypedArray(Float32Array, para.dataOffset + 4, keyCount * 2, Endianness.BIG_ENDIAN); + this.funcVal.setData(keys); + } + } class FunctionValue_ListParameter extends TFunctionValue { protected override range = new Attribute.Range(); protected override interpolate = new Attribute.Interpolate(); @@ -1171,6 +1197,52 @@ namespace FVB { } } + //---------------------------------------------------------------------------------------------------------------------- + // FunctionValue: Composite + // Perform a simple operation to combine some number of other FunctionValues, returning the result. + // For example, we can using the ADD ECompositeOp we can return the sum of two ListParameter FunctionValues. + //---------------------------------------------------------------------------------------------------------------------- + enum TECompositeOp { + NONE, + RAW, + IDX, + PARAM, + ADD, + SUB, + MUL, + DIV, + ENUM_SIZE, + } + + class TObject_Composite extends FVB.TObject { + override funcVal = new FunctionValue_Composite; + + override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + assert(para.dataSize >= 8); + + const compositeOp = file.view.getUint32(para.dataOffset + 0); + const floatData = file.view.getFloat32(para.dataOffset + 4); + const uintData = file.view.getUint32(para.dataOffset + 4); + + let fvData: number; + let fvFunc: (ref: TFunctionValue[], data: number, t: number) => number; + switch (compositeOp) { + case TECompositeOp.RAW: fvData = uintData; fvFunc = FunctionValue_Composite.composite_raw; break; + case TECompositeOp.IDX: fvData = uintData; fvFunc = FunctionValue_Composite.composite_index; break; + case TECompositeOp.PARAM: fvData = floatData; fvFunc = FunctionValue_Composite.composite_parameter; break; + case TECompositeOp.ADD: fvData = floatData; fvFunc = FunctionValue_Composite.composite_add; break; + case TECompositeOp.SUB: fvData = floatData; fvFunc = FunctionValue_Composite.composite_subtract; break; + case TECompositeOp.MUL: fvData = floatData; fvFunc = FunctionValue_Composite.composite_multiply; break; + case TECompositeOp.DIV: fvData = floatData; fvFunc = FunctionValue_Composite.composite_divide; break; + default: + console.warn('Unsupported CompositeOp:', compositeOp); + return; + } + + this.funcVal.setData(fvFunc, fvData) + } + } + class FunctionValue_Composite extends TFunctionValue { protected override refer = new Attribute.Refer(); @@ -1237,73 +1309,6 @@ namespace FVB { private func: (ref: TFunctionValue[], dataVal: number, t: number) => number; private dataVal: number; } - - //---------------------------------------------------------------------------------------------------------------------- - // FVB Objects - // Manages a FunctionValue. - //---------------------------------------------------------------------------------------------------------------------- - class TObject_Constant extends FVB.TObject { - override funcVal = new FunctionValue_Constant; - - override prepare_data(para: TParagraph, control: TControl, file: Reader): void { - assert(para.dataSize == 4); - const value = file.view.getFloat32(para.dataOffset); - this.funcVal.setData(value); - } - } - - class TObject_ListParameter extends FVB.TObject { - override funcVal = new FunctionValue_ListParameter; - - override prepare_data(para: TParagraph, control: TControl, file: Reader): void { - assert(para.dataSize >= 8); - // Each Key contains 2 floats, a time and value - const keyCount = file.view.getUint32(para.dataOffset + 0); - const keys = file.buffer.createTypedArray(Float32Array, para.dataOffset + 4, keyCount * 2, Endianness.BIG_ENDIAN); - this.funcVal.setData(keys); - } - } - - enum TECompositeOp { - NONE, - RAW, - IDX, - PARAM, - ADD, - SUB, - MUL, - DIV, - ENUM_SIZE, - } - - class TObject_Composite extends FVB.TObject { - override funcVal = new FunctionValue_Composite; - - override prepare_data(para: TParagraph, control: TControl, file: Reader): void { - assert(para.dataSize >= 8); - - const compositeOp = file.view.getUint32(para.dataOffset + 0); - const floatData = file.view.getFloat32(para.dataOffset + 4); - const uintData = file.view.getUint32(para.dataOffset + 4); - - let fvData: number; - let fvFunc: (ref: TFunctionValue[], data: number, t: number) => number; - switch (compositeOp) { - case TECompositeOp.RAW: fvData = uintData; fvFunc = FunctionValue_Composite.composite_raw; break; - case TECompositeOp.IDX: fvData = uintData; fvFunc = FunctionValue_Composite.composite_index; break; - case TECompositeOp.PARAM: fvData = floatData; fvFunc = FunctionValue_Composite.composite_parameter; break; - case TECompositeOp.ADD: fvData = floatData; fvFunc = FunctionValue_Composite.composite_add; break; - case TECompositeOp.SUB: fvData = floatData; fvFunc = FunctionValue_Composite.composite_subtract; break; - case TECompositeOp.MUL: fvData = floatData; fvFunc = FunctionValue_Composite.composite_multiply; break; - case TECompositeOp.DIV: fvData = floatData; fvFunc = FunctionValue_Composite.composite_divide; break; - default: - console.warn('Unsupported CompositeOp:', compositeOp); - return; - } - - this.funcVal.setData(fvFunc, fvData) - } - } } //---------------------------------------------------------------------------------------------------------------------- From ee823e9937099313309c0febaa3baa4780985ee4 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 7 Nov 2024 23:28:01 -0700 Subject: [PATCH 035/102] Implemented FunctionValue_Hermite (untested) --- src/Common/JSYSTEM/JStudio.ts | 94 ++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 9ae962a10..ad12216b7 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -862,8 +862,8 @@ namespace FVB { // return new TObject_list(block); case EFuncValType.ListParameter: return new TObject_ListParameter(block); - // case EFuncValType.Hermite: - // return new TObject_hermite(block); + case EFuncValType.Hermite: + return new TObject_Hermite(block); default: console.warn('Unknown FVB type: ', block.type); debugger; @@ -994,6 +994,23 @@ namespace FVB { return (term1 * controlPoints[0]) + (term2 * controlPoints[1]) + (term3 * controlPoints[2]) + (term4 * controlPoints[3]); } + + static Hermite(c0: number, c1: number, x: number, c2: number, x2: number, c3: number, x3: number) { + let a: number; + let b: number; + let c: number; + let d: number; + + a = c0 - c1; + b = a * (1.0 / (x2 - c1)); // (a - b) * 1.0 / (c - d) + c = b - 1.0; // 1.0 + d = (3.0 + -2.0 * b) * (b * b); // 3.0 - 2.0 * b + const cab = (c * a * b); + const coeffx3 = cab * x3; + const cca = (c * c * a); + const coeffc2 = cca * c2; + return ((1.0 - d) * x + (d * c3)) + coeffc2 + coeffx3; + } } } @@ -1309,6 +1326,79 @@ namespace FVB { private func: (ref: TFunctionValue[], dataVal: number, t: number) => number; private dataVal: number; } + + //---------------------------------------------------------------------------------------------------------------------- + // FunctionValue: Hermite + // Use hermite interpolation to compute a value from a list + //---------------------------------------------------------------------------------------------------------------------- + class TObject_Hermite extends FVB.TObject { + override funcVal = new FunctionValue_Hermite; + + override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + assert(para.dataSize >= 8); + + const keyCount = file.view.getUint32(para.dataOffset + 0) & 0xFFFFFFF; + const stride = file.view.getUint32(para.dataOffset + 0) >> 0x1C; + + // Each Key contains `stride` floats, a time and value + const keys = file.buffer.createTypedArray(Float32Array, para.dataOffset + 4, keyCount * stride, Endianness.BIG_ENDIAN); + this.funcVal.setData(keys, stride); + } + } + + class FunctionValue_Hermite extends TFunctionValue { + protected override range = new Attribute.Range(); + + // Each key contains `stride` floats, a time and values + private keyCount: number = 0; + private keys: Float32Array; + private curKeyIdx: number; + private stride: number; + + prepare(): void { this.range.prepare(); } + + setData(values: Float32Array, stride: number) { + assert(stride == 3 || stride == 4); + this.keys = values; + this.keyCount = values.length / this.stride; + this.curKeyIdx = 0; + this.stride = stride + } + + getType() { return EFuncValType.ListParameter; } + getStartTime() { return this.keys[0]; } + getEndTime(): number { return this.keys[(this.keyCount - 1) * this.stride]; } + + getValue(timeSec: number): number { + debugger; // Untested. Remove after confirmed working. + + // Remap (if requested) the time to our range + const t = this.range.getParameter(timeSec, this.getStartTime(), this.getEndTime()); + + // Update our current key. If the current time is between keys, select the later one. + this.curKeyIdx = this.keys.findIndex((k, i) => (i % this.stride) == 0 && k >= t) / this.stride; + + if (this.curKeyIdx == 0) { // Time is at or before the start, return the first key + return this.keys[this.curKeyIdx * this.stride + 1]; + } else if (this.curKeyIdx < 0) { // Time is at or after the end, return the last key + this.curKeyIdx = this.keyCount - 1; + return this.keys[this.curKeyIdx * this.stride + 1]; + } + + const ks = this.keys; + const c = this.curKeyIdx; + const l = c - this.stride; + const value = Attribute.Interpolate.Hermite( + t, ks[l + 0], ks[l + 1], ks[l + this.stride - 1], ks[c + 0], ks[c + 1], ks[c + 2]); + + if (isNaN(value)) { + console.warn('NaN generated by FunctionValue'); + debugger; + } + + return value; + } + } } //---------------------------------------------------------------------------------------------------------------------- From 22649ff04d413b0e74b323dd42cf2301581886a3 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 9 Nov 2024 08:09:32 -0700 Subject: [PATCH 036/102] Added shimmed Actor-related STB types --- src/Common/JSYSTEM/JStudio.ts | 102 +++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 26 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index ad12216b7..98c447ba4 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -232,31 +232,6 @@ abstract class TAdaptor { } } -// struct TSetVariableValue_immediate { -// inline TSetVariableValue_immediate(u32 p1, f32 p2) -// : field_0x0(p1) -// , field_0x4(p2) -// { -// } - -// u32 field_0x0; -// f32 field_0x4; -// }; -// typedef void (*setVarFunc)(JStudio::TAdaptor*, JStudio::TObject*, u32, void const*, u32); - - -// TVariableValue* adaptor_referVariableValue(u32 param_0) { -// return &mVariableValues[param_0]; -// } - -// void adaptor_setVariableValue_immediate(u32 param_0, f32 param_1) { -// adaptor_referVariableValue(param_0)->setValue_immediate(param_1); -// } - -// const TVariableValue* adaptor_getVariableValue(u32 param_0) const { -// return &mVariableValues[param_0]; -// } - //---------------------------------------------------------------------------------------------------------------------- // STB Objects // Created at parse time, and controlled by Sequences from the STB file. Connects to Game objects via an Adaptor. @@ -466,6 +441,81 @@ class TControlObject extends STBObject { override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { } } + +//---------------------------------------------------------------------------------------------------------------------- +// Actor +//---------------------------------------------------------------------------------------------------------------------- +export abstract class TActor extends JStage.TObject { + JSGFGetType() { return JStage.TEObject.ACTOR; } + JSGGetTranslation(dst: vec3) { } + JSGSetTranslation(src: ReadonlyVec3) { } + JSGGetScaling(dst: vec3) { } + JSGSetScaling(src: ReadonlyVec3) { } + JSGGetRotation(dst: vec3) { } + JSGSetRotation(src: ReadonlyVec3) { } + JSGGetShape(): number { return -1; } + JSGSetShape(x: number): void { } + JSGGetAnimation(): number { return -1; } + JSGSetAnimation(x: number): void { } + JSGGetAnimationFrame(): number { return 0.0; } + JSGSetAnimationFrame(x: number): void { } + JSGGetAnimationFrameMax(): number { return 0.0; } + JSGGetAnimationTransition(): number { return 0.0; } + JSGSetAnimationTransition(x: number): void { } + JSGGetTextureAnimation(): number { return -1; } + JSGSetTextureAnimation(x: number): void { } + JSGGetTextureAnimationFrame(): number { return 0.0; } + JSGSetTextureAnimationFrame(x: number): void { } + JSGGetTextureAnimationFrameMax(): number { return 0.0; } +} + +class TActorAdaptor extends TAdaptor { + // /* 0x12C */ JStage::TObject* m12C; + // /* 0x130 */ u32 m130; + // /* 0x134 */ JStage::TObject* m134; + // /* 0x138 */ u32 m138; + // /* 0x13C */ u32 m13C; + // /* 0x140 */ u32 m140; + + constructor( + private mObject: TActor, + ) { super(14); } + + adaptor_do_prepare(obj: STBObject): void { debugger; } + adaptor_do_begin(obj: STBObject): void { debugger; } + adaptor_do_end(obj: STBObject): void { debugger; } + adaptor_do_update(obj: STBObject, frameCount: number): void { debugger; } + adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void { debugger; } + adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_PARENT_NODE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_PARENT_ENABLE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_RELATION(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_RELATION_NODE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_RELATION_ENABLE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_SHAPE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_ANIMATION(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_TEXTURE_ANIMATION(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } + + setJSG_ID_(actorFunc: (x: number) => void, dataOp: TEOperationData, data: number) { + debugger; + }; +} + +class TActorObject extends STBObject { + constructor( + control: TControl, + blockObj: TBlockObject, + stageObj: JStage.TObject, + ) { super(control, blockObj, new TActorAdaptor(stageObj as TActor)) } + + override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { + debugger; + } +} + + //---------------------------------------------------------------------------------------------------------------------- // Camera //---------------------------------------------------------------------------------------------------------------------- @@ -1511,7 +1561,7 @@ export class TControl { let objType: JStage.TEObject; switch (blockObj.type) { case 'JCMR': objConstructor = TCameraObject; objType = JStage.TEObject.CAMERA; break; - case 'JACT': + case 'JACT': objConstructor = TActorObject; objType = JStage.TEObject.ACTOR; break; case 'JABL': case 'JLIT': case 'JFOG': From 161bf2b5db3c1c1b815ddbaaab6b90c355acfd0e Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 9 Nov 2024 10:37:05 -0700 Subject: [PATCH 037/102] Framework: Added fopAcM_searchFromName This searches all processes based on the name. For the test, it uses base_process_class::mProfName, which is the index used to access the f_pc_profiles table. Currently in noclip we only store the pcName that comes out of the profile. So far, pcName and profName always seem to match, but the game frequently accesses both. I'm now storing profName and added an assert if they ever differ. I think this will make it clearer in the future if there is another function that access profName. --- src/ZeldaWindWaker/framework.ts | 42 ++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/ZeldaWindWaker/framework.ts b/src/ZeldaWindWaker/framework.ts index 05a227733..56250e21b 100644 --- a/src/ZeldaWindWaker/framework.ts +++ b/src/ZeldaWindWaker/framework.ts @@ -8,6 +8,7 @@ import { dKy_tevstr_c, dKy_tevstr_init } from "./d_kankyo.js"; import { AABB } from "../Geometry.js"; import { Camera, computeScreenSpaceProjectionFromWorldSpaceAABB, computeScreenSpaceProjectionFromWorldSpaceSphere, ScreenSpaceProjection } from "../Camera.js"; import { transformVec3Mat4w1 } from "../MathHelpers.js"; +import { dGlobals } from "./Main.js"; export const enum fpc__ProcessName { d_s_play = 0x0007, @@ -155,7 +156,7 @@ function fpcDt_Delete(globals: fGlobals, pc: base_process_class): void { //#region cPhs, fpcCt, fpcSCtRq export interface fpc_bs__Constructor { - new(globalUserData: fGlobals, pcId: number, profile: DataView): base_process_class; + new(globalUserData: fGlobals, pcName: number, pcId: number, profile: DataView): base_process_class; } class standard_create_request_class { @@ -167,7 +168,7 @@ class standard_create_request_class { ]); public process: base_process_class | null = null; - constructor(public layer: layer_class, public pcId: number, public konstructor: fpc_bs__Constructor, public profileBinary: ArrayBufferSlice, public userData: T) { + constructor(public layer: layer_class, public pcName: number, public pcId: number, public konstructor: fpc_bs__Constructor, public profileBinary: ArrayBufferSlice, public userData: T) { } // fpcSCtRq_Handler @@ -181,7 +182,7 @@ class standard_create_request_class { private CreateProcess(globals: fGlobals, globalUserData: GlobalUserData, userData: this): cPhs__Status { const self = userData; - self.process = new self.konstructor(globals, self.pcId, self.profileBinary.createDataView()); + self.process = new self.konstructor(globals, self.pcName, self.pcId, self.profileBinary.createDataView()); return cPhs__Status.Next; } @@ -251,7 +252,7 @@ export function fpcSCtRq_Request(globals: fGlobals, ly: layer_class | null, p const binary = fpcPf_Get__ProfileBinary(globals, pcName); const pcId = fpcBs_MakeOfId(globals); - const rq = new standard_create_request_class(ly, pcId, constructor, binary, userData); + const rq = new standard_create_request_class(ly, pcName, pcId, constructor, binary, userData); fpcCtRq_ToCreateQ(globals, rq); return pcId; } @@ -338,13 +339,16 @@ export class base_process_class { public ly: layer_class; public pi = new process_priority_class(); - constructor(globals: fGlobals, public processId: number, profile: DataView) { + constructor(globals: fGlobals, public profName: number, public processId: number, profile: DataView) { // fpcBs_Create this.pi.layerID = profile.getUint32(0x00); this.pi.listID = profile.getUint16(0x04); this.pi.listIndex = profile.getUint16(0x06); this.processName = profile.getUint16(0x08); this.parameters = profile.getUint32(0x18); + + // The game stores these separately, and frequently accesses both. Lets see if they ever differ. + assert(profName == this.processName); } // In the original game, construction is inside "create". Here, we split it into construction and "load". @@ -392,8 +396,8 @@ class process_node_class extends base_process_class { public visible: boolean = true; public roomVisible: boolean = true; - constructor(globals: fGlobals, pcId: number, profile: DataView) { - super(globals, pcId, profile); + constructor(globals: fGlobals, pcName: number, pcId: number, profile: DataView) { + super(globals, pcName, pcId, profile); this.layer = new layer_class(globals.lyNextID++); } @@ -409,8 +413,8 @@ class leafdraw_class extends base_process_class { public visible: boolean = true; public roomVisible: boolean = true; - constructor(globals: fGlobals, pcId: number, profile: DataView) { - super(globals, pcId, profile); + constructor(globals: fGlobals, pcName: number, pcId: number, profile: DataView) { + super(globals, pcName, pcId, profile); this.drawPriority = profile.getUint16(0x20); } @@ -494,8 +498,8 @@ export class fopAc_ac_c extends leafdraw_class { private loadInit: boolean = false; - constructor(globals: fGlobals, pcId: number, profile: DataView) { - super(globals, pcId, profile); + constructor(globals: fGlobals, pcName: number, pcId: number, profile: DataView) { + super(globals, pcName, pcId, profile); // Initialize our culling information from the profile... const cullType = profile.getUint8(0x2D); @@ -648,6 +652,22 @@ export function fopAcIt_JudgeByID(globals: fGlobal } return null; } + +export function fopAcM_searchFromName(globals: dGlobals, procName: string, paramMask: number, param: number): fopAc_ac_c | null { + const objName = globals.dStage_searchName(procName); + if (!objName) { return null; } + + for (let i = 0; i < globals.frameworkGlobals.lnQueue.length; i++) { + const act = globals.frameworkGlobals.lnQueue[i] as fopAc_ac_c; + if (act.profName == objName.pcName + && objName.subtype == act.subtype + && (paramMask == 0 || param == (act.parameters & paramMask)) + ) + return act; + } + + return null; +} //#endregion //#region fopKy From 18654d09289c83ce2aec920afd45334fdda97aca Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 9 Nov 2024 11:02:02 -0700 Subject: [PATCH 038/102] Demo: Implemented JSGFindObject for Actors --- src/ZeldaWindWaker/Main.ts | 2 +- src/ZeldaWindWaker/d_demo.ts | 34 ++++++++++++++++++++++++++++++--- src/ZeldaWindWaker/framework.ts | 1 + 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 9a170a953..e009936db 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -784,7 +784,7 @@ class d_s_play extends fopScn { // TODO: Determine the correct place for this // dCamera_c::Store() sets the camera params if the demo camera is active - const demoCam = this.demo.getSystem().mpActiveCamera; + const demoCam = this.demo.getSystem().getCamera(); if (demoCam && !isPaused) { let viewPos = globals.cameraPosition; let targetPos = vec3.add(scratchVec3a, globals.cameraPosition, globals.cameraFwd); diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 34c958be3..21e25ba8d 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -3,6 +3,7 @@ import ArrayBufferSlice from "../ArrayBufferSlice"; import { TParse, JStage, TSystem, TControl, TCamera } from "../Common/JSYSTEM/JStudio.js"; import { getMatrixAxisY } from "../MathHelpers.js"; import { dGlobals } from "./Main"; +import { fopAc_ac_c, fopAcM_searchFromName } from "./framework.js"; export enum EDemoMode { None, @@ -144,9 +145,15 @@ class dDemo_camera_c extends TCamera { } } +class dDemo_actor_c extends TCamera { + constructor(actor: fopAc_ac_c) { + super(); + } +} + class dDemo_system_c implements TSystem { - public mpActiveCamera?: dDemo_camera_c; - // private mpActors: dDemo_actor_c[]; + private mpActiveCamera?: dDemo_camera_c; + private mpActors: dDemo_actor_c[] = []; // private mpAmbient: dDemo_ambient_c; // private mpLight: dDemo_light_c[]; // private mpFog: dDemo_fog_c; @@ -155,13 +162,30 @@ class dDemo_system_c implements TSystem { private globals: dGlobals ) { } - public JSGFindObject(objId: string, objType: JStage.TEObject): JStage.TObject | undefined { + public JSGFindObject(objName: string, objType: JStage.TEObject): JStage.TObject | undefined { switch (objType) { case JStage.TEObject.CAMERA: if (this.mpActiveCamera) return this.mpActiveCamera; else return this.mpActiveCamera = new dDemo_camera_c(this.globals); + case JStage.TEObject.ACTOR: case JStage.TEObject.ACTOR_UNK: + debugger; + let actor = fopAcM_searchFromName(this.globals, objName, 0, 0); + if (!actor) { + if (objType == JStage.TEObject.ACTOR && objName == "d_act") { + debugger; // Untested. Unimplemented + actor = {} as fopAc_ac_c; + } else { + return undefined; + } + } + if (!this.mpActors[actor.demoActorID]) { + actor.demoActorID = this.mpActors.length; + this.mpActors[actor.demoActorID] = new dDemo_actor_c(actor); + }; + return this.mpActors[actor.demoActorID]; + case JStage.TEObject.AMBIENT: case JStage.TEObject.LIGHT: case JStage.TEObject.FOG: @@ -171,8 +195,12 @@ class dDemo_system_c implements TSystem { } } + public getCamera() { return this.mpActiveCamera; } + public getActor(actorID: number) { return this.mpActors[actorID]; } + public remove() { this.mpActiveCamera = undefined; + this.mpActors = []; } } diff --git a/src/ZeldaWindWaker/framework.ts b/src/ZeldaWindWaker/framework.ts index 56250e21b..2ad00a22f 100644 --- a/src/ZeldaWindWaker/framework.ts +++ b/src/ZeldaWindWaker/framework.ts @@ -489,6 +489,7 @@ export class fopAc_ac_c extends leafdraw_class { public subtype: number = 0xFF; public roomNo: number = -1; public tevStr = new dKy_tevstr_c(); + public demoActorID: number = -1; protected cullSizeBox: AABB | null = null; protected cullSizeSphere: vec4 | null = null; protected cullMtx: mat4 | null = null; From f83ca2f1975071ba33db38644a03405168b151a4 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 9 Nov 2024 11:45:36 -0700 Subject: [PATCH 039/102] JStudio: Starting point for TActorAdaptor implementation Prepare, Begin, and End implemented but untested --- src/Common/JSYSTEM/JStudio.ts | 64 ++++++++++++++++++++++++++++++++--- src/ZeldaWindWaker/d_demo.ts | 5 ++- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 98c447ba4..2f758f0df 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -10,6 +10,7 @@ import { Endianness } from "../../endian.js"; const scratchVec3a = vec3.create(); const scratchVec3b = vec3.create(); +const scratchVec3c = vec3.create(); //---------------------------------------------------------------------------------------------------------------------- // Stage Objects @@ -445,6 +446,22 @@ class TControlObject extends STBObject { //---------------------------------------------------------------------------------------------------------------------- // Actor //---------------------------------------------------------------------------------------------------------------------- +const enum Actor_ValueIdx { + ANIM_FRAME = 0, + ANIM_TRANSITION = 1, + ANIM_TEX_FRAME = 2, + + POS_X, + POS_Y, + POS_Z, + ROT_X, + ROT_Y, + ROT_Z, + SCALE_X, + SCALE_Y, + SCALE_Z, +} + export abstract class TActor extends JStage.TObject { JSGFGetType() { return JStage.TEObject.ACTOR; } JSGGetTranslation(dst: vec3) { } @@ -481,9 +498,46 @@ class TActorAdaptor extends TAdaptor { private mObject: TActor, ) { super(14); } - adaptor_do_prepare(obj: STBObject): void { debugger; } - adaptor_do_begin(obj: STBObject): void { debugger; } - adaptor_do_end(obj: STBObject): void { debugger; } + adaptor_do_prepare(obj: STBObject): void { + this.mVariableValues[1].setOutput(this.mObject.JSGSetAnimationTransition); + + // @TODO: + // TVVOutput_ANIMATION_FRAME_(0, 317, &JStage::TActor::JSGSetAnimationFrame, &JStage::TActor::JSGGetAnimationFrame, &JStage::TActor::JSGGetAnimationFrameMax), + // TVVOutput_ANIMATION_FRAME_(2, 321, &JStage::TActor::JSGSetTextureAnimationFrame, &JStage::TActor::JSGGetTextureAnimationFrame, &JStage::TActor::JSGGetTextureAnimationFrameMax), + } + + adaptor_do_begin(obj: STBObject): void { + this.mObject.JSGFEnableFlag(1); + + const pos = scratchVec3a; + const rot = scratchVec3b; + const scale = scratchVec3c; + this.mObject.JSGGetTranslation(pos); + this.mObject.JSGGetRotation(rot); + this.mObject.JSGGetScaling(scale); + + if (obj.mControl.isTransformEnabled()) { + vec3.transformMat4(pos, pos, obj.mControl.getTransformOnGet()); + rot[1] -= obj.mControl.mTransformRotY!; // @TODO: Check this shouldn't be negated + } + + this.adaptor_setVariableValue_Vec(Actor_ValueIdx.POS_X, pos); + this.adaptor_setVariableValue_Vec(Actor_ValueIdx.ROT_X, rot); + this.adaptor_setVariableValue_Vec(Actor_ValueIdx.SCALE_X, scale); + + // @TODO: + // for (const TVVOutputObject* output = saoVVOutput_; output->mValueIndex != -1; output++) { + // mVariableValues[output->mValueIndex].setValue_immediate((this.mObject.*(output->mGetter))()); + // } + // for (const TVVOutput_ANIMATION_FRAME_* output = saoVVOutput_ANIMATION_FRAME_; output->mValueIndex != -1; output++) { + // mVariableValues[output->mValueIndex].setValue_immediate((this.mObject.*(output->mGetter))()); + // } + } + + adaptor_do_end(obj: STBObject): void { + this.mObject.JSGFDisableFlag(1); + } + adaptor_do_update(obj: STBObject, frameCount: number): void { debugger; } adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void { debugger; } adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } @@ -1492,8 +1546,8 @@ export class TControl { public mSecondsPerFrame: number; private mSuspendFrames: number; - private mTransformOrigin?: vec3; - private mTransformRotY?: number; + public mTransformOrigin?: vec3; + public mTransformRotY?: number; private mTransformOnGetMtx = mat4.create(); private mTransformOnSetMtx = mat4.create(); diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 21e25ba8d..fa74463ad 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -1,6 +1,6 @@ import { ReadonlyVec3, vec3 } from "gl-matrix"; import ArrayBufferSlice from "../ArrayBufferSlice"; -import { TParse, JStage, TSystem, TControl, TCamera } from "../Common/JSYSTEM/JStudio.js"; +import { TParse, JStage, TSystem, TControl, TCamera, TActor } from "../Common/JSYSTEM/JStudio.js"; import { getMatrixAxisY } from "../MathHelpers.js"; import { dGlobals } from "./Main"; import { fopAc_ac_c, fopAcM_searchFromName } from "./framework.js"; @@ -145,7 +145,7 @@ class dDemo_camera_c extends TCamera { } } -class dDemo_actor_c extends TCamera { +class dDemo_actor_c extends TActor { constructor(actor: fopAc_ac_c) { super(); } @@ -170,7 +170,6 @@ class dDemo_system_c implements TSystem { case JStage.TEObject.ACTOR: case JStage.TEObject.ACTOR_UNK: - debugger; let actor = fopAcM_searchFromName(this.globals, objName, 0, 0); if (!actor) { if (objType == JStage.TEObject.ACTOR && objName == "d_act") { From 40a70cb69443c142fb5374e28e06dd6961454037 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 9 Nov 2024 14:03:44 -0700 Subject: [PATCH 040/102] Implemented most TActorAdaptor functions --- src/Common/JSYSTEM/JStudio.ts | 261 +++++++++++++++++++++++++++------- src/ZeldaWindWaker/d_demo.ts | 2 +- 2 files changed, 211 insertions(+), 52 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 2f758f0df..d2f461099 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -19,7 +19,7 @@ const scratchVec3c = vec3.create(); //---------------------------------------------------------------------------------------------------------------------- export namespace JStage { export enum TEObject { - ACTOR_UNK = 0x0, + PREEXISTING_ACTOR = 0x0, UNK1 = 0x1, ACTOR = 0x2, CAMERA = 0x3, @@ -39,7 +39,7 @@ export namespace JStage { JSGGetData(unk0: number, data: Object, unk1: number): boolean { return false; } JSGSetData(unk0: number, data: Object, unk1: number): void { } JSGGetParent(parentDst: JStage.TObject, unk: { x: number }): void { } - JSGSetParent(parent: JStage.TObject, unk: number): void { } + JSGSetParent(parent: JStage.TObject | null, unk: number): void { } JSGSetRelation(related: boolean, obj: JStage.TObject, unk: number): void { } JSGFindNodeID(id: string): number { return -1; } JSGGetNodeTransformation(unk: number, mtx: mat4): number { @@ -154,19 +154,22 @@ class TVariableValue { // TAdaptor // Connects the STBObject to a Game Object. Manages tracks of TVariableValues, updates their values on the Game object. //---------------------------------------------------------------------------------------------------------------------- +// @TODO: Cleanup enum names const enum TEOperationData { NONE = 0, VOID = 1, // Disable updates for this track. IMMEDIATE = 2, // Set the value on this track with an immediate value. TIME = 3, // Ramp the track's value based by a given velocity, starting at 0. FUNCVALUE_NAME = 0x10, // Unused? - FUNCVALUE_INDEX = 0x12 // Make the track use a function value object for the value. + FUNCVALUE_INDEX = 0x12, // Make the track use a function value object for the value. + OBJECT_NAME = 0x18, // TODO + OBJECT_INDEX = 0x19, // Set the value directly on the JStage object (e.g. an actor), don't store in the adaptor }; abstract class TAdaptor { constructor( - protected mCount: number, - protected mVariableValues = nArray(mCount, i => new TVariableValue()), + public mCount: number, + public mVariableValues = nArray(mCount, i => new TVariableValue()), ) { } abstract adaptor_do_prepare(obj: STBObject): void; @@ -446,20 +449,23 @@ class TControlObject extends STBObject { //---------------------------------------------------------------------------------------------------------------------- // Actor //---------------------------------------------------------------------------------------------------------------------- -const enum Actor_ValueIdx { +const enum Actor_ValIdx { ANIM_FRAME = 0, ANIM_TRANSITION = 1, ANIM_TEX_FRAME = 2, - POS_X, - POS_Y, - POS_Z, - ROT_X, - ROT_Y, - ROT_Z, - SCALE_X, - SCALE_Y, - SCALE_Z, + POS_X = 3, + POS_Y = 4, + POS_Z = 5, + ROT_X = 6, + ROT_Y = 7, + ROT_Z = 8, + SCALE_X = 9, + SCALE_Y = 10, + SCALE_Z = 11, + + PARENT = 12, + RELATION = 13, } export abstract class TActor extends JStage.TObject { @@ -487,28 +493,29 @@ export abstract class TActor extends JStage.TObject { } class TActorAdaptor extends TAdaptor { - // /* 0x12C */ JStage::TObject* m12C; - // /* 0x130 */ u32 m130; - // /* 0x134 */ JStage::TObject* m134; - // /* 0x138 */ u32 m138; - // /* 0x13C */ u32 m13C; - // /* 0x140 */ u32 m140; + public parent?: JStage.TObject; + public parentNodeID: number; + public relation?: JStage.TObject; + public relationNodeID: number; + public animMode: number; // @TODO: Enum + public animTexMode: number; // @TODO: Enum constructor( - private mObject: TActor, + private mSystem: TSystem, + public mObject: TActor, ) { super(14); } adaptor_do_prepare(obj: STBObject): void { - this.mVariableValues[1].setOutput(this.mObject.JSGSetAnimationTransition); + this.mVariableValues[Actor_ValIdx.ANIM_TRANSITION].setOutput(this.mObject.JSGSetAnimationTransition); // @TODO: - // TVVOutput_ANIMATION_FRAME_(0, 317, &JStage::TActor::JSGSetAnimationFrame, &JStage::TActor::JSGGetAnimationFrame, &JStage::TActor::JSGGetAnimationFrameMax), - // TVVOutput_ANIMATION_FRAME_(2, 321, &JStage::TActor::JSGSetTextureAnimationFrame, &JStage::TActor::JSGGetTextureAnimationFrame, &JStage::TActor::JSGGetTextureAnimationFrameMax), + // TVVOutput_ANIMATION_FRAME_(Actor_ValIdx.ANIM_FRAME, 317, &JStage::TActor::JSGSetAnimationFrame, &JStage::TActor::JSGGetAnimationFrame, &JStage::TActor::JSGGetAnimationFrameMax), + // TVVOutput_ANIMATION_FRAME_(Actor_ValIdx.ANIM_TEX_FRAME, 321, &JStage::TActor::JSGSetTextureAnimationFrame, &JStage::TActor::JSGGetTextureAnimationFrame, &JStage::TActor::JSGGetTextureAnimationFrameMax), } adaptor_do_begin(obj: STBObject): void { this.mObject.JSGFEnableFlag(1); - + const pos = scratchVec3a; const rot = scratchVec3b; const scale = scratchVec3c; @@ -520,11 +527,13 @@ class TActorAdaptor extends TAdaptor { vec3.transformMat4(pos, pos, obj.mControl.getTransformOnGet()); rot[1] -= obj.mControl.mTransformRotY!; // @TODO: Check this shouldn't be negated } - - this.adaptor_setVariableValue_Vec(Actor_ValueIdx.POS_X, pos); - this.adaptor_setVariableValue_Vec(Actor_ValueIdx.ROT_X, rot); - this.adaptor_setVariableValue_Vec(Actor_ValueIdx.SCALE_X, scale); - + + this.adaptor_setVariableValue_Vec(Actor_ValIdx.POS_X, pos); + this.adaptor_setVariableValue_Vec(Actor_ValIdx.ROT_X, rot); + this.adaptor_setVariableValue_Vec(Actor_ValIdx.SCALE_X, scale); + + this.mVariableValues[Actor_ValIdx.ANIM_TRANSITION].setValue_immediate(this.mObject.JSGGetAnimationTransition()); + // @TODO: // for (const TVVOutputObject* output = saoVVOutput_; output->mValueIndex != -1; output++) { // mVariableValues[output->mValueIndex].setValue_immediate((this.mObject.*(output->mGetter))()); @@ -534,38 +543,188 @@ class TActorAdaptor extends TAdaptor { // } } - adaptor_do_end(obj: STBObject): void { + adaptor_do_end(obj: STBObject): void { this.mObject.JSGFDisableFlag(1); } - adaptor_do_update(obj: STBObject, frameCount: number): void { debugger; } - adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void { debugger; } - adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_PARENT_NODE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_PARENT_ENABLE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_RELATION(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_RELATION_NODE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_RELATION_ENABLE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_SHAPE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_ANIMATION(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_TEXTURE_ANIMATION(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } - - setJSG_ID_(actorFunc: (x: number) => void, dataOp: TEOperationData, data: number) { + adaptor_do_update(obj: STBObject, frameCount: number): void { debugger; - }; + } + + adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void { + debugger; + } + + adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, dataSize: number): void { + assert(dataOp == TEOperationData.OBJECT_NAME); + this.parent = this.mSystem.JSGFindObject(data as string, JStage.TEObject.PREEXISTING_ACTOR); + } + + adaptor_do_PARENT_NODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + debugger; + switch (dataOp) { + case TEOperationData.OBJECT_NAME: + if (this.parent) + this.parentNodeID = this.parent.JSGFindNodeID(data as string); + break; + case TEOperationData.OBJECT_INDEX: + this.parentNodeID = data as number; + break; + default: assert(false); + } + } + + adaptor_do_PARENT_ENABLE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + assert(dataOp == TEOperationData.IMMEDIATE); + if (!!data) { this.mObject.JSGSetParent(this.parent!, this.parentNodeID); } + else { this.mObject.JSGSetParent(null, 0xFFFFFFFF); } + } + + adaptor_do_RELATION(dataOp: TEOperationData, data: number | string, dataSize: number): void { + assert(dataOp == TEOperationData.OBJECT_NAME); + this.relation = this.mSystem.JSGFindObject(data as string, JStage.TEObject.PREEXISTING_ACTOR); + } + + adaptor_do_RELATION_NODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + debugger; + switch (dataOp) { + case TEOperationData.OBJECT_NAME: + if (this.relation) + this.relationNodeID = this.relation.JSGFindNodeID(data as string); + break; + case TEOperationData.OBJECT_INDEX: + this.relationNodeID = data as number; + break; + default: assert(false); + } + } + + adaptor_do_RELATION_ENABLE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + assert(dataOp == TEOperationData.IMMEDIATE); + this.mObject.JSGSetRelation(!!data, this.relation!, this.relationNodeID); + } + + adaptor_do_SHAPE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + assert(dataOp == TEOperationData.OBJECT_INDEX); + this.mObject.JSGSetShape(data as number); + } + + adaptor_do_ANIMATION(dataOp: TEOperationData, data: number | string, dataSize: number): void { + assert(dataOp == TEOperationData.OBJECT_INDEX); + this.mObject.JSGSetAnimation(data as number); + } + + adaptor_do_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + assert(dataOp == TEOperationData.IMMEDIATE); + this.animMode = data as number; + } + + adaptor_do_TEXTURE_ANIMATION(dataOp: TEOperationData, data: number | string, dataSize: number): void { + assert(dataOp == TEOperationData.OBJECT_INDEX); + this.mObject.JSGSetTextureAnimation(data as number); + } + + adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + assert(dataOp == TEOperationData.IMMEDIATE); + this.animTexMode = data as number; + } } class TActorObject extends STBObject { + override mAdaptor: TActorAdaptor; + constructor( control: TControl, blockObj: TBlockObject, stageObj: JStage.TObject, - ) { super(control, blockObj, new TActorAdaptor(stageObj as TActor)) } + ) { super(control, blockObj, new TActorAdaptor(control.mSystem, stageObj as TActor)) } override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { - debugger; + const dataOp = (param & 0x1F) as TEOperationData; + const cmdType = param >> 5; + + let keyCount = 1; + let keyIdx; + let data; + + // Parse the data here instead of deferring to adaptor_setVariableValue, so we don't have to pass along `file`. + switch (dataOp) { + case TEOperationData.FUNCVALUE_INDEX: data = file.view.getUint32(dataOffset); break; + case TEOperationData.FUNCVALUE_NAME: + data = readString(file.buffer, dataOffset, dataSize); + console.warn('FUNCVALUE_NAME found! Remove this comment after testing'); + debugger; + break; + default: data = file.view.getFloat32(dataOffset); + } + + switch (cmdType) { + // Pos + case 0x09: keyIdx = Actor_ValIdx.POS_X; break; + case 0x0a: keyIdx = Actor_ValIdx.POS_Y; break; + case 0x0b: keyIdx = Actor_ValIdx.POS_Z; break; + case 0x0c: keyCount = 3; keyIdx = Actor_ValIdx.POS_X; break; + + // Rot + case 0x0d: keyIdx = Actor_ValIdx.ROT_X; break; + case 0x0e: keyIdx = Actor_ValIdx.ROT_Y; break; + case 0x0f: keyIdx = Actor_ValIdx.ROT_Z; break; + case 0x10: keyCount = 3; keyIdx = Actor_ValIdx.ROT_X; break; + + // Scale + case 0x11: keyIdx = Actor_ValIdx.SCALE_X; break; + case 0x12: keyIdx = Actor_ValIdx.SCALE_Y; break; + case 0x13: keyIdx = Actor_ValIdx.SCALE_Z; break; + case 0x14: keyCount = 3; keyIdx = Actor_ValIdx.SCALE_X; break; + + case 0x3b: keyIdx = Actor_ValIdx.ANIM_FRAME; break; + case 0x4b: keyIdx = Actor_ValIdx.ANIM_TRANSITION; break; + + case 0x39: debugger; this.mAdaptor.adaptor_do_SHAPE(dataOp, data, dataSize); return; + case 0x3a: debugger; this.mAdaptor.adaptor_do_ANIMATION(dataOp, data, dataSize); return; + case 0x43: debugger; this.mAdaptor.adaptor_do_ANIMATION_MODE(dataOp, data, dataSize); return; + case 0x4c: debugger; this.mAdaptor.adaptor_do_TEXTURE_ANIMATION(dataOp, data, dataSize); return; + case 0x4e: debugger; this.mAdaptor.adaptor_do_TEXTURE_ANIMATION_MODE(dataOp, data, dataSize); return; + + case 0x30: debugger; this.mAdaptor.adaptor_do_PARENT(dataOp, data, dataSize); return; + case 0x31: debugger; this.mAdaptor.adaptor_do_PARENT_NODE(dataOp, data, dataSize); return; + case 0x32: + debugger; + keyIdx = Actor_ValIdx.PARENT; + if ((dataOp < 0x13) && (dataOp > 0x0F)) { + debugger; + this.mAdaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); + this.mAdaptor.mVariableValues[keyIdx].setOutput((enabled, adaptor) => { + (adaptor as TActorAdaptor).adaptor_do_PARENT_ENABLE(dataOp, enabled, dataSize) + }); + } + this.mAdaptor.adaptor_do_PARENT_ENABLE(dataOp, data, dataSize); + break; + + case 0x33: debugger; this.mAdaptor.adaptor_do_RELATION(dataOp, data, dataSize); return; + case 0x34: debugger; this.mAdaptor.adaptor_do_RELATION_NODE(dataOp, data, dataSize); return; + case 0x35: + debugger; + keyIdx = Actor_ValIdx.RELATION; + if ((dataOp < 0x13) && (dataOp > 0x0F)) { + debugger; + this.mAdaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); + this.mAdaptor.mVariableValues[keyIdx].setOutput((enabled, adaptor) => { + (adaptor as TActorAdaptor).adaptor_do_RELATION_ENABLE(dataOp, enabled, dataSize) + }); + } + this.mAdaptor.adaptor_do_RELATION_ENABLE(dataOp, data, dataSize); + break; + + default: + console.debug('Unsupported TActor update: ', cmdType, ' ', dataOp); + debugger; + return; + } + + for (let i = 0; i < keyCount; i++) { + this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, (data as number) + i); + } } } @@ -1541,7 +1700,7 @@ export abstract class TBlockObject { // This combines JStudio::TControl and JStudio::stb::TControl into a single class, for simplicity. export class TControl { - private mSystem: TSystem; + public mSystem: TSystem; public mFvbControl = new FVB.TControl(); public mSecondsPerFrame: number; private mSuspendFrames: number; diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index fa74463ad..0f2f43d5e 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -169,7 +169,7 @@ class dDemo_system_c implements TSystem { else return this.mpActiveCamera = new dDemo_camera_c(this.globals); case JStage.TEObject.ACTOR: - case JStage.TEObject.ACTOR_UNK: + case JStage.TEObject.PREEXISTING_ACTOR: let actor = fopAcM_searchFromName(this.globals, objName, 0, 0); if (!actor) { if (objType == JStage.TEObject.ACTOR && objName == "d_act") { From dcf8641b8fcd685772537d22c632456989fb76db Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 9 Nov 2024 14:42:38 -0700 Subject: [PATCH 041/102] Implemented dDemo_actor_c getter/setter functions --- src/Common/JSYSTEM/JStudio.ts | 50 +++++++++---------- src/ZeldaWindWaker/d_demo.ts | 93 ++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 29 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index d2f461099..02b4a17d9 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -37,7 +37,7 @@ export namespace JStage { JSGGetFlag(): number { return 0; } JSGSetFlag(flag: number): void { } JSGGetData(unk0: number, data: Object, unk1: number): boolean { return false; } - JSGSetData(unk0: number, data: Object, unk1: number): void { } + JSGSetData(dataSize: number, data: DataView, unk1: number): void { } JSGGetParent(parentDst: JStage.TObject, unk: { x: number }): void { } JSGSetParent(parent: JStage.TObject | null, unk: number): void { } JSGSetRelation(related: boolean, obj: JStage.TObject, unk: number): void { } @@ -166,6 +166,24 @@ const enum TEOperationData { OBJECT_INDEX = 0x19, // Set the value directly on the JStage object (e.g. an actor), don't store in the adaptor }; +// Parse data from a DataView as either a number or a string, based on the dataOp +function readData(dataOp: TEOperationData, dataOffset: number, dataSize: number, file: Reader): number | string { + switch (dataOp) { + case TEOperationData.IMMEDIATE: + case TEOperationData.TIME: + case TEOperationData.FUNCVALUE_INDEX: + case TEOperationData.OBJECT_INDEX: + return file.view.getUint32(dataOffset); + + case TEOperationData.FUNCVALUE_NAME: + case TEOperationData.OBJECT_NAME: + return readString(file.buffer, dataOffset, dataSize); + + default: + assert(false, 'Unsupported data operation'); + } +} + abstract class TAdaptor { constructor( public mCount: number, @@ -645,18 +663,7 @@ class TActorObject extends STBObject { let keyCount = 1; let keyIdx; - let data; - - // Parse the data here instead of deferring to adaptor_setVariableValue, so we don't have to pass along `file`. - switch (dataOp) { - case TEOperationData.FUNCVALUE_INDEX: data = file.view.getUint32(dataOffset); break; - case TEOperationData.FUNCVALUE_NAME: - data = readString(file.buffer, dataOffset, dataSize); - console.warn('FUNCVALUE_NAME found! Remove this comment after testing'); - debugger; - break; - default: data = file.view.getFloat32(dataOffset); - } + let data = readData(dataOp, dataOffset, dataSize, file); switch (cmdType) { // Pos @@ -680,9 +687,9 @@ class TActorObject extends STBObject { case 0x3b: keyIdx = Actor_ValIdx.ANIM_FRAME; break; case 0x4b: keyIdx = Actor_ValIdx.ANIM_TRANSITION; break; - case 0x39: debugger; this.mAdaptor.adaptor_do_SHAPE(dataOp, data, dataSize); return; + case 0x39: this.mAdaptor.adaptor_do_SHAPE(dataOp, data, dataSize); return; case 0x3a: debugger; this.mAdaptor.adaptor_do_ANIMATION(dataOp, data, dataSize); return; - case 0x43: debugger; this.mAdaptor.adaptor_do_ANIMATION_MODE(dataOp, data, dataSize); return; + case 0x43: this.mAdaptor.adaptor_do_ANIMATION_MODE(dataOp, data, dataSize); return; case 0x4c: debugger; this.mAdaptor.adaptor_do_TEXTURE_ANIMATION(dataOp, data, dataSize); return; case 0x4e: debugger; this.mAdaptor.adaptor_do_TEXTURE_ANIMATION_MODE(dataOp, data, dataSize); return; @@ -869,18 +876,7 @@ class TCameraObject extends STBObject { let keyCount = 1; let keyIdx; - let data; - - // Parse the data here instead of deferring to adaptor_setVariableValue, so we don't have to pass along `file`. - switch (dataOp) { - case TEOperationData.FUNCVALUE_INDEX: data = file.view.getUint32(dataOffset); break; - case TEOperationData.FUNCVALUE_NAME: - data = readString(file.buffer, dataOffset, dataSize); - console.warn('FUNCVALUE_NAME found! Remove this comment after testing'); - debugger; - break; - default: data = file.view.getFloat32(dataOffset); - } + let data = readData(dataOp, dataOffset, dataSize, file); switch (cmdType) { // Eye position diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 0f2f43d5e..a5f4b29b0 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -1,9 +1,10 @@ -import { ReadonlyVec3, vec3 } from "gl-matrix"; +import { mat4, ReadonlyVec3, vec3 } from "gl-matrix"; import ArrayBufferSlice from "../ArrayBufferSlice"; import { TParse, JStage, TSystem, TControl, TCamera, TActor } from "../Common/JSYSTEM/JStudio.js"; -import { getMatrixAxisY } from "../MathHelpers.js"; +import { getMatrixAxisY, MathConstants } from "../MathHelpers.js"; import { dGlobals } from "./Main"; import { fopAc_ac_c, fopAcM_searchFromName } from "./framework.js"; +import { J3DModelInstance } from "../Common/JSYSTEM/J3D/J3DGraphBase"; export enum EDemoMode { None, @@ -146,9 +147,97 @@ class dDemo_camera_c extends TCamera { } class dDemo_actor_c extends TActor { + mFlags: number; + mTranslation = vec3.create(); + mScaling = vec3.create(); + mRotation = vec3.create(); + mShapeId: number; + mNextBckId: number; + mAnimationFrame: number; + mAnimationTransition: number; + mAnimationFrameMax: number; + mTexAnimation: number; + mTexAnimationFrame: number; + mTexAnimationFrameMax: number; + mModel: J3DModelInstance; + stbDataSize: number; + stbData: DataView; + stbDataUnk: number; + mActorPcId: number; + mBckId: number; + mBtpId: number; + mBtkId: number; + mBrkId: number; + constructor(actor: fopAc_ac_c) { super(); } + + override JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { + debugger; // I think this may be one of the shapeInstanceState matrices instead + mat4.copy( mtx, this.mModel.modelMatrix); + return 1; + } + + override JSGGetAnimationFrameMax() { return this.mAnimationFrameMax; } + override JSGGetTextureAnimationFrameMax() { return this.mTexAnimationFrameMax; } + + override JSGGetTranslation(dst: vec3) { vec3.copy( dst, this.mTranslation); } + override JSGGetScaling(dst: vec3) { vec3.copy( dst, this.mScaling ); } + override JSGGetRotation(dst: vec3) { debugger; vec3.scale(dst, this.mRotation, MathConstants.RAD_TO_DEG); } + + override JSGSetData(dataSize: number, data: DataView, unk1: number): void { + this.stbDataSize = dataSize; + this.stbData = data; + this.stbDataUnk = unk1; + this.mFlags |= 0x01; + } + + override JSGSetTranslation(src: ReadonlyVec3) { + vec3.copy(this.mTranslation, src); + this.mFlags |= 0x02; + } + + override JSGSetScaling(src: ReadonlyVec3) { + vec3.copy(this.mScaling, src); + this.mFlags |= 0x04; + } + + override JSGSetRotation(src: ReadonlyVec3) { + vec3.scale(this.mRotation, src, MathConstants.DEG_TO_RAD); + this.mFlags |= 0x08; + } + + override JSGSetShape(id: number): void { + this.mShapeId = id; + this.mFlags |= 0x10; + } + + override JSGSetAnimation(id: number): void { + this.mNextBckId = id; + this.mAnimationFrameMax = 3.402823e+38; + this.mFlags |= 0x20; + } + + override JSGSetAnimationFrame(x: number): void { + this.mAnimationFrame = x; + this.mFlags |= 0x40; + } + + override JSGSetAnimationTransition(x: number): void { + this.mAnimationTransition = x; + this.mFlags |= 0x40; + } + + override JSGSetTextureAnimation(id: number): void { + this.mTexAnimation = id; + this.mFlags |= 0x80; + } + + override JSGSetTextureAnimationFrame(x: number): void { + this.mTexAnimationFrame = x; + this.mFlags |= 0x100; + } } class dDemo_system_c implements TSystem { From ab3ee10221a7a89ad514965af21dc292f11cdf56 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 9 Nov 2024 16:13:44 -0700 Subject: [PATCH 042/102] Implement process_paragraph_reserved_() and do_data() --- src/Common/JSYSTEM/JStudio.ts | 50 ++++++++++++++++++++++++----------- src/ZeldaWindWaker/d_demo.ts | 9 +++---- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 02b4a17d9..328508fd2 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -37,12 +37,12 @@ export namespace JStage { JSGGetFlag(): number { return 0; } JSGSetFlag(flag: number): void { } JSGGetData(unk0: number, data: Object, unk1: number): boolean { return false; } - JSGSetData(dataSize: number, data: DataView, unk1: number): void { } + JSGSetData(id: number, data: DataView): void { } JSGGetParent(parentDst: JStage.TObject, unk: { x: number }): void { } JSGSetParent(parent: JStage.TObject | null, unk: number): void { } JSGSetRelation(related: boolean, obj: JStage.TObject, unk: number): void { } JSGFindNodeID(id: string): number { return -1; } - JSGGetNodeTransformation(unk: number, mtx: mat4): number { + JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { mat4.identity(mtx); return 0; } @@ -171,15 +171,15 @@ function readData(dataOp: TEOperationData, dataOffset: number, dataSize: number, switch (dataOp) { case TEOperationData.IMMEDIATE: case TEOperationData.TIME: - case TEOperationData.FUNCVALUE_INDEX: - case TEOperationData.OBJECT_INDEX: + case TEOperationData.FUNCVALUE_INDEX: + case TEOperationData.OBJECT_INDEX: return file.view.getUint32(dataOffset); - case TEOperationData.FUNCVALUE_NAME: - case TEOperationData.OBJECT_NAME: + case TEOperationData.FUNCVALUE_NAME: + case TEOperationData.OBJECT_NAME: return readString(file.buffer, dataOffset, dataSize); - default: + default: assert(false, 'Unsupported data operation'); } } @@ -194,7 +194,7 @@ abstract class TAdaptor { abstract adaptor_do_begin(obj: STBObject): void; abstract adaptor_do_end(obj: STBObject): void; abstract adaptor_do_update(obj: STBObject, frameCount: number): void; - abstract adaptor_do_data(obj: STBObject, unk0: Object, unk1: number, unk2: Object, unk3: number): void; + abstract adaptor_do_data(obj: STBObject, id: number, data: DataView): void; // Set a single VariableValue update function, with the option of using FuncVals adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: TEOperationData, data: number | string) { @@ -299,7 +299,7 @@ abstract class STBObject { if (this.mAdaptor) this.mAdaptor.adaptor_updateVariableValue(this, frameCount); if (this.mAdaptor) this.mAdaptor.adaptor_do_update(this, frameCount); } - // do_data(void const*, u32, void const*, u32) {} + do_data(id: number, data: DataView) { if (this.mAdaptor) this.mAdaptor.adaptor_do_data(this, id, data); } getStatus() { return this.mStatus; } getSuspendFrames(): number { return this.mSuspendFrames; } @@ -435,9 +435,7 @@ abstract class STBObject { while (byteIdx < this.pSequence_next) { const para = TParagraph.parse(view, byteIdx); if (para.type <= 0xff) { - console.debug('Unsupported paragraph feature: ', para.type); - debugger; - // process_paragraph_reserved_(para.type, para.content, para.param); + this.process_paragraph_reserved_(this.mData, para.dataSize, para.dataOffset, para.type); } else { this.do_paragraph(this.mData, para.dataSize, para.dataOffset, para.type); } @@ -453,6 +451,28 @@ abstract class STBObject { break; } } + + private process_paragraph_reserved_(file: Reader, dataSize: number, dataOffset: number, param: number): void { + switch (param) { + case 0x1: debugger; break; + case 0x2: debugger; break; + case 0x3: debugger; break; + case 0x80: debugger; break; + case 0x81: + debugger; + const idSize = file.view.getUint16(dataOffset + 2); + assert( idSize == 4 ); + const id = file.view.getUint32(dataOffset + 4); + const contentOffset = dataOffset + 4 + align(idSize, 4); + const contentSize = dataSize - (contentOffset - dataOffset); + const content = file.buffer.createDataView(contentOffset, contentSize); + this.do_data(id, content); + break; + + case 0x82: + break; + } + } } class TControlObject extends STBObject { @@ -569,8 +589,8 @@ class TActorAdaptor extends TAdaptor { debugger; } - adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void { - debugger; + adaptor_do_data(obj: STBObject, id: number, data: DataView): void { + this.mObject.JSGSetData(id, data); } adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, dataSize: number): void { @@ -843,7 +863,7 @@ class TCameraAdaptor extends TAdaptor { this.mStageCam.JSGSetViewTargetPosition(targetPos); } - adaptor_do_data(unk0: Object, unk1: number, unk2: Object, unk3: number): void { + adaptor_do_data(obj: STBObject, id: number, data: DataView): void { // This is not used by TWW. Untested. debugger; } diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index a5f4b29b0..be2711a0c 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -160,9 +160,8 @@ class dDemo_actor_c extends TActor { mTexAnimationFrame: number; mTexAnimationFrameMax: number; mModel: J3DModelInstance; - stbDataSize: number; + stbDataId: number; stbData: DataView; - stbDataUnk: number; mActorPcId: number; mBckId: number; mBtpId: number; @@ -186,10 +185,10 @@ class dDemo_actor_c extends TActor { override JSGGetScaling(dst: vec3) { vec3.copy( dst, this.mScaling ); } override JSGGetRotation(dst: vec3) { debugger; vec3.scale(dst, this.mRotation, MathConstants.RAD_TO_DEG); } - override JSGSetData(dataSize: number, data: DataView, unk1: number): void { - this.stbDataSize = dataSize; + override JSGSetData(id: number, data: DataView): void { + debugger; + this.stbDataId = id; this.stbData = data; - this.stbDataUnk = unk1; this.mFlags |= 0x01; } From 524896a2e508fb213e2f0c41e41323d46974a9ce Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 07:21:28 -0700 Subject: [PATCH 043/102] Implemented (untested) TActorAdaptor::adaptor_do_update() --- src/Common/JSYSTEM/JStudio.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 328508fd2..6ef454ac0 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -586,7 +586,21 @@ class TActorAdaptor extends TAdaptor { } adaptor_do_update(obj: STBObject, frameCount: number): void { - debugger; + const pos = scratchVec3a; + const rot = scratchVec3b; + const scale = scratchVec3c; + this.adaptor_getVariableValue_Vec(pos, Actor_ValIdx.POS_X); + this.adaptor_getVariableValue_Vec(rot, Actor_ValIdx.ROT_X); + this.adaptor_getVariableValue_Vec(scale, Actor_ValIdx.SCALE_X); + + if( obj.mControl.isTransformEnabled()) { + vec3.transformMat4(pos, pos, obj.mControl.getTransformOnSet()); + rot[1] += obj.mControl.mTransformRotY!; // @TODO: Check this shouldn't be negated + } + + this.mObject.JSGSetTranslation(pos); + this.mObject.JSGSetRotation(rot); + this.mObject.JSGSetScaling(scale); } adaptor_do_data(obj: STBObject, id: number, data: DataView): void { From 908fd32489e0b6c490bc2ea52b45bee168ab7640 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 09:22:59 -0700 Subject: [PATCH 044/102] JStudio: Fix bug in linear interpolation data access --- src/Common/JSYSTEM/JStudio.ts | 48 ++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 6ef454ac0..695ef1899 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -1414,12 +1414,14 @@ namespace FVB { // @TODO: Better way of accessing 2-word keys interpolateBSpline(t: number): number { + const c = this.curKeyIdx * 2; + const controlPoints = new Float64Array(4); const knotVector = new Float64Array(6); - controlPoints[1] = this.keys[this.curKeyIdx * 2 - 1]; - controlPoints[2] = this.keys[this.curKeyIdx * 2 + 1]; - knotVector[2] = this.keys[this.curKeyIdx * 2 + -2]; - knotVector[3] = this.keys[this.curKeyIdx * 2 + 0]; + controlPoints[1] = this.keys[c - 1]; + controlPoints[2] = this.keys[c + 1]; + knotVector[2] = this.keys[c + -2]; + knotVector[3] = this.keys[c + 0]; const keysBefore = this.curKeyIdx; const keysAfter = this.keyCount - this.curKeyIdx; @@ -1427,8 +1429,8 @@ namespace FVB { switch (keysBefore) { case 1: controlPoints[0] = 2.0 * controlPoints[1] - controlPoints[2]; - controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; - knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; + controlPoints[3] = this.keys[c + 3]; + knotVector[4] = this.keys[c + 2]; knotVector[1] = 2.0 * knotVector[2] - knotVector[3]; knotVector[0] = 2.0 * knotVector[2] - knotVector[4]; switch (keysAfter) { @@ -1437,13 +1439,13 @@ namespace FVB { knotVector[5] = 2.0 * knotVector[4] - knotVector[3]; break; default: - knotVector[5] = this.keys[this.curKeyIdx * 2 + 4]; + knotVector[5] = this.keys[c + 4]; break; } break; case 2: - controlPoints[0] = this.keys[this.curKeyIdx * 2 + -3]; - knotVector[1] = this.keys[this.curKeyIdx * 2 + -4]; + controlPoints[0] = this.keys[c + -3]; + knotVector[1] = this.keys[c + -4]; knotVector[0] = 2.0 * knotVector[1] - knotVector[2]; switch (keysAfter) { case 1: @@ -1452,20 +1454,20 @@ namespace FVB { knotVector[5] = 2.0 * knotVector[3] - knotVector[1]; break; case 2: - controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; - knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; + controlPoints[3] = this.keys[c + 3]; + knotVector[4] = this.keys[c + 2]; knotVector[5] = 2.0 * knotVector[4] - knotVector[3]; break; default: - controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; - knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; - knotVector[5] = this.keys[this.curKeyIdx * 2 + 4]; + controlPoints[3] = this.keys[c + 3]; + knotVector[4] = this.keys[c + 2]; + knotVector[5] = this.keys[c + 4]; } break; default: - controlPoints[0] = this.keys[this.curKeyIdx * 2 + -3]; - knotVector[1] = this.keys[this.curKeyIdx * 2 + -4]; - knotVector[0] = this.keys[this.curKeyIdx * 2 + -6]; + controlPoints[0] = this.keys[c + -3]; + knotVector[1] = this.keys[c + -4]; + knotVector[0] = this.keys[c + -6]; switch (keysAfter) { case 1: controlPoints[3] = 2.0 * controlPoints[2] - controlPoints[1]; @@ -1473,14 +1475,14 @@ namespace FVB { knotVector[5] = 2.0 * knotVector[3] - knotVector[1]; break; case 2: - controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; - knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; + controlPoints[3] = this.keys[c + 3]; + knotVector[4] = this.keys[c + 2]; knotVector[5] = 2.0 * knotVector[4] - knotVector[3]; break; default: - controlPoints[3] = this.keys[this.curKeyIdx * 2 + 3]; - knotVector[4] = this.keys[this.curKeyIdx * 2 + 2]; - knotVector[5] = this.keys[this.curKeyIdx * 2 + 4]; + controlPoints[3] = this.keys[c + 3]; + knotVector[4] = this.keys[c + 2]; + knotVector[5] = this.keys[c + 4]; break; } break; @@ -1496,7 +1498,7 @@ namespace FVB { interpolateLinear(t: number) { const ks = this.keys; - const c = this.curKeyIdx; + const c = this.curKeyIdx * 2; return Attribute.Interpolate.Linear(t, ks[c - 2], ks[c - 1], ks[c + 0], ks[c + 1]); } From 65c9aafb0e4c5ca0bdd672f58411e39217c0f87d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 10:57:49 -0700 Subject: [PATCH 045/102] Add dRes_control_c::getObjectResByID() --- src/ZeldaWindWaker/d_resorce.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ZeldaWindWaker/d_resorce.ts b/src/ZeldaWindWaker/d_resorce.ts index f6f878125..b5e79059b 100644 --- a/src/ZeldaWindWaker/d_resorce.ts +++ b/src/ZeldaWindWaker/d_resorce.ts @@ -93,6 +93,11 @@ export class dRes_control_c { return this.getResByName(resType, arcName, resName, this.resObj); } + public getObjectIDRes(resType: T, arcName: string, resID: number): ResAssetType { + resID &= 0x0000FFFF; // Consider resID as a short + return this.getResByID(resType, arcName, resID, this.resObj); + } + public getResByName(resType: T, arcName: string, resName: string, resList: dRes_info_c[]): ResAssetType | null { const resInfo = assertExists(this.findResInfo(arcName, resList)); return resInfo.getResByName(resType, resName); @@ -103,6 +108,11 @@ export class dRes_control_c { return resInfo.getResByIndex(resType, resIndex); } + public getResByID(resType: T, arcName: string, resID: number, resList: dRes_info_c[]): ResAssetType { + const resInfo = assertExists(this.findResInfo(arcName, resList)); + return resInfo.getResByID(resType, resID); + } + public mountRes(device: GfxDevice, cache: GfxRenderCache, arcName: string, archive: JKRArchive, resList: dRes_info_c[]): void { if (this.findResInfo(arcName, resList) !== null) return; From 1e41295188efb9e3d7c01f1c019c8c600683fd00 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 17:56:26 -0700 Subject: [PATCH 046/102] Fix bug in setMorf that failed to support a morfFrames value of 0 This matches the original behavior --- src/ZeldaWindWaker/m_do_ext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/m_do_ext.ts b/src/ZeldaWindWaker/m_do_ext.ts index 53d790f66..19e930792 100644 --- a/src/ZeldaWindWaker/m_do_ext.ts +++ b/src/ZeldaWindWaker/m_do_ext.ts @@ -188,7 +188,7 @@ export class mDoExt_McaMorf implements JointMatrixCalc { } public setMorf(morfFrames: number): void { - if (this.prevMorf < 0.0 || morfFrames < 0.0) { + if (this.prevMorf < 0.0 || morfFrames <= 0.0) { this.curMorf = 1.0; } else { this.curMorf = 0.0; From 94b1d82845380adc54201cb338b39049419eaa62 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 17:56:59 -0700 Subject: [PATCH 047/102] JStudio: Confirm that adaptor_do_ANIMATION is working as expected --- src/Common/JSYSTEM/JStudio.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 695ef1899..bae3bf687 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -459,7 +459,6 @@ abstract class STBObject { case 0x3: debugger; break; case 0x80: debugger; break; case 0x81: - debugger; const idSize = file.view.getUint16(dataOffset + 2); assert( idSize == 4 ); const id = file.view.getUint32(dataOffset + 4); @@ -722,7 +721,7 @@ class TActorObject extends STBObject { case 0x4b: keyIdx = Actor_ValIdx.ANIM_TRANSITION; break; case 0x39: this.mAdaptor.adaptor_do_SHAPE(dataOp, data, dataSize); return; - case 0x3a: debugger; this.mAdaptor.adaptor_do_ANIMATION(dataOp, data, dataSize); return; + case 0x3a: this.mAdaptor.adaptor_do_ANIMATION(dataOp, data, dataSize); return; case 0x43: this.mAdaptor.adaptor_do_ANIMATION_MODE(dataOp, data, dataSize); return; case 0x4c: debugger; this.mAdaptor.adaptor_do_TEXTURE_ANIMATION(dataOp, data, dataSize); return; case 0x4e: debugger; this.mAdaptor.adaptor_do_TEXTURE_ANIMATION_MODE(dataOp, data, dataSize); return; From 6f24e8d10cee5af4114b15cabba9ae6d1787b5d2 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 18:07:39 -0700 Subject: [PATCH 048/102] d_demo: Added support for specifying a startFrame when playing demos To help with debugging --- src/ZeldaWindWaker/d_demo.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index be2711a0c..12335c6d3 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -174,21 +174,20 @@ class dDemo_actor_c extends TActor { override JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { debugger; // I think this may be one of the shapeInstanceState matrices instead - mat4.copy( mtx, this.mModel.modelMatrix); + mat4.copy(mtx, this.mModel.modelMatrix); return 1; } override JSGGetAnimationFrameMax() { return this.mAnimationFrameMax; } override JSGGetTextureAnimationFrameMax() { return this.mTexAnimationFrameMax; } - override JSGGetTranslation(dst: vec3) { vec3.copy( dst, this.mTranslation); } - override JSGGetScaling(dst: vec3) { vec3.copy( dst, this.mScaling ); } - override JSGGetRotation(dst: vec3) { debugger; vec3.scale(dst, this.mRotation, MathConstants.RAD_TO_DEG); } + override JSGGetTranslation(dst: vec3) { vec3.copy(dst, this.mTranslation); } + override JSGGetScaling(dst: vec3) { vec3.copy(dst, this.mScaling); } + override JSGGetRotation(dst: vec3) { vec3.scale(dst, this.mRotation, MathConstants.RAD_TO_DEG); } override JSGSetData(id: number, data: DataView): void { - debugger; this.stbDataId = id; - this.stbData = data; + this.stbData = data; // @TODO: Check that data makes sense this.mFlags |= 0x01; } @@ -264,6 +263,7 @@ class dDemo_system_c implements TSystem { debugger; // Untested. Unimplemented actor = {} as fopAc_ac_c; } else { + console.warn('Demo failed to find actor', objName); return undefined; } } @@ -310,7 +310,7 @@ export class dDemo_manager_c { getMode() { return this.mMode; } getSystem() { return this.mSystem; } - public create(data: ArrayBufferSlice, originPos?: vec3, rotY?: number): boolean { + public create(data: ArrayBufferSlice, originPos?: vec3, rotY?: number, startFrame?: number): boolean { this.mParser = new TParse(this.mControl); if (!this.mParser.parse(data, 0)) { @@ -318,7 +318,7 @@ export class dDemo_manager_c { return false; } - this.mControl.forward(0); + this.mControl.forward(startFrame || 0); if (originPos) { this.mControl.transformSetOrigin(originPos, rotY || 0); } From 17777ff65ea8aa18b88d65a1d428f6cc0a2f4667 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 18:07:58 -0700 Subject: [PATCH 049/102] d_demo: Added checkEnable and getMorfParam --- src/ZeldaWindWaker/d_demo.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 12335c6d3..1b1a1bcc9 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -171,7 +171,33 @@ class dDemo_actor_c extends TActor { constructor(actor: fopAc_ac_c) { super(); } - + + checkEnable(mask: number) { + return this.mFlags & mask; + } + + getMorfParam() { + // Doesn't have anim properties + if ((this.mFlags & 0x40) == 0) { + // Has STB data + if ((this.mFlags & 1) == 0) { + return 0.0; + } else { + switch(this.stbDataId) { + // @TODO: Double check this, somehow + case 6: return this.stbData.getInt8(15); + case 5: return this.stbData.getInt8(11); + case 4: return this.stbData.getInt8(6); + case 2: return this.stbData.getInt8(7); + case 1: return this.stbData.getInt8(2); + default: return 0.0; + } + } + } else { + return this.mAnimationTransition; + } + } + override JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { debugger; // I think this may be one of the shapeInstanceState matrices instead mat4.copy(mtx, this.mModel.modelMatrix); From ab81cde4fe03e80145404e6c6db56877fea9d0c8 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 18:09:46 -0700 Subject: [PATCH 050/102] d_demo: Fix frame counts These were assuming a frame increment of one each frame --- src/ZeldaWindWaker/d_demo.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 1b1a1bcc9..2189c485a 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -375,9 +375,9 @@ export class dDemo_manager_c { if (this.mControl.isSuspended()) { this.mControl.setSuspend(0); } if (this.mControl.forward(dtFrames)) { - this.mFrame++; + this.mFrame += dtFrames; if (!this.mControl.isSuspended()) { - this.mFrameNoMsg++; + this.mFrameNoMsg += dtFrames; } } else { this.mMode = EDemoMode.Ended; From 09c05f4b33110777e55e99a67979d0d6ca4c726d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 18:15:57 -0700 Subject: [PATCH 051/102] d_demo: Added dDemo_setDemoData Mostly untested --- src/ZeldaWindWaker/d_demo.ts | 64 ++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 2189c485a..1fb061dbc 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -5,6 +5,10 @@ import { getMatrixAxisY, MathConstants } from "../MathHelpers.js"; import { dGlobals } from "./Main"; import { fopAc_ac_c, fopAcM_searchFromName } from "./framework.js"; import { J3DModelInstance } from "../Common/JSYSTEM/J3D/J3DGraphBase"; +import { mDoExt_McaMorf } from "./m_do_ext"; +import { assert } from "../util.js"; +import { ResType } from "./d_resorce.js"; +import { LoopMode } from "../Common/JSYSTEM/J3D/J3DLoader"; export enum EDemoMode { None, @@ -384,4 +388,64 @@ export class dDemo_manager_c { } return true; } +} + +/** + * Called by Actor update functions to update their data from the demo version of the actor. + */ +export function dDemo_setDemoData(globals: dGlobals, dtFrames: number, actor: fopAc_ac_c, flagMask: number, + morf?: mDoExt_McaMorf, arcName?: string, soundCount?: number, soundIdxs?: number[], soundMaterialID?: number, reverb?: number) { + const demoActor = globals.scnPlay.demo.getSystem().getActor(actor.demoActorID); + if (!demoActor) + return false; + + const enable = demoActor.checkEnable(flagMask); + if (enable & 2) { + // actor.current.pos = demoActor.mTranslation; + // actor.old.pos = actor.current.pos; + vec3.copy(actor.pos, demoActor.mTranslation); + } + if (enable & 8) { + // actor.shape_angle = demoActor.mRotation; + // actor.current.angle = actor.shape_angle; + vec3.copy(actor.rot, demoActor.mRotation); + } + if (enable & 4) { + actor.scale = demoActor.mScaling; + } + + if (!morf) + return true; + + demoActor.mModel = morf.model; + + if ((enable & 0x20)) { + const bckID = demoActor.mNextBckId; + if (bckID & 0x10000) + arcName = globals.roomCtrl.demoArcName; + assert(!!arcName); + demoActor.mBckId = bckID; + + const i_key = globals.resCtrl.getObjectIDRes(ResType.Bck, arcName, bckID); + assert(!!i_key); + + // void* i_sound = dDemo_getJaiPointer(a_name, bck, soundCount, soundIdxs); + morf.setAnm(i_key, -1 as LoopMode, demoActor.getMorfParam(), 1.0, 0.0, -1.0); + demoActor.mAnimationFrameMax = morf.frameCtrl.endFrame; + + if (enable & 0x40) { + debugger; + if (demoActor.mAnimationFrame > 1.0) { + morf.frameCtrl.setFrame(demoActor.mAnimationFrame - 1.0); + morf.play(dtFrames); + } else { + morf.frameCtrl.setFrame(demoActor.mAnimationFrame); + } + } else { + morf.play(dtFrames); + console.log(morf.frameCtrl.currentTimeInFrames); + } + } + + return true; } \ No newline at end of file From a7cd2d30a7284e0682dd93a36c029e52dc378430 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 18:17:50 -0700 Subject: [PATCH 052/102] Add optional start frame to DemoDesc --- src/ZeldaWindWaker/Main.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index e009936db..241db1a86 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -984,6 +984,7 @@ class DemoDesc { public rotY: number = 0, public startCode?: number, public eventFlags?: number, + public startFrame?: number, // noclip modification for easier debugging ) {} async load(globals: dGlobals) { @@ -1016,13 +1017,13 @@ class DemoDesc { 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); } + if( demoData ) { globals.scnPlay.demo.create(demoData, this.offsetPos, this.rotY / 180.0 * Math.PI, this.startFrame); } else { console.warn('Failed to load demo data:', this.stbFilename); } } } const demoDescs = [ - new DemoDesc("sea", "Awaken", "awake.stb", 44, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea", "Awaken", "awake.stb", 44, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0, 525), new DemoDesc("sea", "Stolen Sister", "stolensister.stb", 44, 9, [0.0, 0.0, 20000.0], 0, 0, 0), new DemoDesc("sea", "Departure", "departure.stb", 44, 10, [-200000.0, 0.0, 320000.0], 0.0, 204, 0), new DemoDesc("sea", "Pirate Zelda Fly", "kaizoku_zelda_fly.stb", 44, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), From 34d84bd5d4eab5b8758e64ca0c8a049b3a339c34 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 21:31:38 -0700 Subject: [PATCH 053/102] d_demo: Fix bug in dDemo_setDemoData() which cause played animations to reset every frame --- src/ZeldaWindWaker/d_demo.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 1fb061dbc..fe09534db 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -187,7 +187,7 @@ class dDemo_actor_c extends TActor { if ((this.mFlags & 1) == 0) { return 0.0; } else { - switch(this.stbDataId) { + switch (this.stbDataId) { // @TODO: Double check this, somehow case 6: return this.stbData.getInt8(15); case 5: return this.stbData.getInt8(11); @@ -419,7 +419,7 @@ export function dDemo_setDemoData(globals: dGlobals, dtFrames: number, actor: fo demoActor.mModel = morf.model; - if ((enable & 0x20)) { + if ((enable & 0x20) && (demoActor.mNextBckId != demoActor.mBckId)) { const bckID = demoActor.mNextBckId; if (bckID & 0x10000) arcName = globals.roomCtrl.demoArcName; @@ -432,19 +432,18 @@ export function dDemo_setDemoData(globals: dGlobals, dtFrames: number, actor: fo // void* i_sound = dDemo_getJaiPointer(a_name, bck, soundCount, soundIdxs); morf.setAnm(i_key, -1 as LoopMode, demoActor.getMorfParam(), 1.0, 0.0, -1.0); demoActor.mAnimationFrameMax = morf.frameCtrl.endFrame; + } - if (enable & 0x40) { - debugger; - if (demoActor.mAnimationFrame > 1.0) { - morf.frameCtrl.setFrame(demoActor.mAnimationFrame - 1.0); - morf.play(dtFrames); - } else { - morf.frameCtrl.setFrame(demoActor.mAnimationFrame); - } - } else { + if (enable & 0x40) { + debugger; + if (demoActor.mAnimationFrame > 1.0) { + morf.frameCtrl.setFrame(demoActor.mAnimationFrame - 1.0); morf.play(dtFrames); - console.log(morf.frameCtrl.currentTimeInFrames); + } else { + morf.frameCtrl.setFrame(demoActor.mAnimationFrame); } + } else { + morf.play(dtFrames); } return true; From b0ea1730524d58d84f19c76b1a5018e8735a8e0c Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 22:45:08 -0700 Subject: [PATCH 054/102] JStudio: Add toggleable logging for actor sequences If enabled, every action performed by do_paragraph is logged. This is essentially the stream of all actions performed on an object (actor, camera, etc) as the demo progresses. Very useful for debugging. --- src/Common/JSYSTEM/JStudio.ts | 60 ++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index bae3bf687..a4b11365a 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -166,6 +166,19 @@ const enum TEOperationData { OBJECT_INDEX = 0x19, // Set the value directly on the JStage object (e.g. an actor), don't store in the adaptor }; +function dataOpToString(enumValue: TEOperationData) { + switch (enumValue) { + case TEOperationData.NONE: return "None" + case TEOperationData.VOID: return "Void" + case TEOperationData.IMMEDIATE: return "Immediate" + case TEOperationData.TIME: return "Time" + case TEOperationData.FUNCVALUE_NAME: return "FuncVal" + case TEOperationData.FUNCVALUE_INDEX: return "FuncVal" + case TEOperationData.OBJECT_NAME: return "Obj" + case TEOperationData.OBJECT_INDEX: return "Obj" + } +} + // Parse data from a DataView as either a number or a string, based on the dataOp function readData(dataOp: TEOperationData, dataOffset: number, dataSize: number, file: Reader): number | string { switch (dataOp) { @@ -460,7 +473,7 @@ abstract class STBObject { case 0x80: debugger; break; case 0x81: const idSize = file.view.getUint16(dataOffset + 2); - assert( idSize == 4 ); + assert(idSize == 4); const id = file.view.getUint32(dataOffset + 4); const contentOffset = dataOffset + 4 + align(idSize, 4); const contentSize = dataSize - (contentOffset - dataOffset); @@ -505,6 +518,25 @@ const enum Actor_ValIdx { RELATION = 13, } +function keyToString(enumValue: Actor_ValIdx, count: number) { + switch (enumValue) { + case Actor_ValIdx.ANIM_FRAME: return "ANIM_FRAME" + case Actor_ValIdx.ANIM_TRANSITION: return "ANIM_TRANSITION" + case Actor_ValIdx.ANIM_TEX_FRAME: return "ANIM_TEX_FRAME" + case Actor_ValIdx.POS_X: return count == 3 ? 'POS' : 'POS_X'; + case Actor_ValIdx.POS_Y: return "POS_Y" + case Actor_ValIdx.POS_Z: return "POS_Z" + case Actor_ValIdx.ROT_X: return count == 3 ? 'ROT' : 'ROT_X'; + case Actor_ValIdx.ROT_Y: return "ROT_Y" + case Actor_ValIdx.ROT_Z: return "ROT_Z" + case Actor_ValIdx.SCALE_X: return count == 3 ? 'SCALE' : 'SCALE_X'; + case Actor_ValIdx.SCALE_Y: return "SCALE_Y" + case Actor_ValIdx.SCALE_Z: return "SCALE_Z" + case Actor_ValIdx.PARENT: return "PARENT" + case Actor_ValIdx.RELATION: return "RELATION" + } +} + export abstract class TActor extends JStage.TObject { JSGFGetType() { return JStage.TEObject.ACTOR; } JSGGetTranslation(dst: vec3) { } @@ -537,6 +569,8 @@ class TActorAdaptor extends TAdaptor { public animMode: number; // @TODO: Enum public animTexMode: number; // @TODO: Enum + public enableLog = true; + constructor( private mSystem: TSystem, public mObject: TActor, @@ -592,7 +626,7 @@ class TActorAdaptor extends TAdaptor { this.adaptor_getVariableValue_Vec(rot, Actor_ValIdx.ROT_X); this.adaptor_getVariableValue_Vec(scale, Actor_ValIdx.SCALE_X); - if( obj.mControl.isTransformEnabled()) { + if (obj.mControl.isTransformEnabled()) { vec3.transformMat4(pos, pos, obj.mControl.getTransformOnSet()); rot[1] += obj.mControl.mTransformRotY!; // @TODO: Check this shouldn't be negated } @@ -603,16 +637,19 @@ class TActorAdaptor extends TAdaptor { } adaptor_do_data(obj: STBObject, id: number, data: DataView): void { + if (this.enableLog) console.debug('SetData: (id)', id); this.mObject.JSGSetData(id, data); } adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_NAME); + if (this.enableLog) console.debug('SetParent:', data); this.parent = this.mSystem.JSGFindObject(data as string, JStage.TEObject.PREEXISTING_ACTOR); } adaptor_do_PARENT_NODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { debugger; + if (this.enableLog) console.debug('SetParentNode:', data); switch (dataOp) { case TEOperationData.OBJECT_NAME: if (this.parent) @@ -627,17 +664,20 @@ class TActorAdaptor extends TAdaptor { adaptor_do_PARENT_ENABLE(dataOp: TEOperationData, data: number | string, dataSize: number): void { assert(dataOp == TEOperationData.IMMEDIATE); + if (this.enableLog) console.debug('SetParentEnable:', data); if (!!data) { this.mObject.JSGSetParent(this.parent!, this.parentNodeID); } else { this.mObject.JSGSetParent(null, 0xFFFFFFFF); } } adaptor_do_RELATION(dataOp: TEOperationData, data: number | string, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_NAME); + if (this.enableLog) console.debug('SetRelation:', data); this.relation = this.mSystem.JSGFindObject(data as string, JStage.TEObject.PREEXISTING_ACTOR); } adaptor_do_RELATION_NODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { debugger; + if (this.enableLog) console.debug('SetRelationNode:', data); switch (dataOp) { case TEOperationData.OBJECT_NAME: if (this.relation) @@ -652,31 +692,37 @@ class TActorAdaptor extends TAdaptor { adaptor_do_RELATION_ENABLE(dataOp: TEOperationData, data: number | string, dataSize: number): void { assert(dataOp == TEOperationData.IMMEDIATE); + if (this.enableLog) console.debug('SetRelationEnable:', data); this.mObject.JSGSetRelation(!!data, this.relation!, this.relationNodeID); } adaptor_do_SHAPE(dataOp: TEOperationData, data: number | string, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_INDEX); + if (this.enableLog) console.debug('SetShape: ', data as number); this.mObject.JSGSetShape(data as number); } adaptor_do_ANIMATION(dataOp: TEOperationData, data: number | string, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_INDEX); + if (this.enableLog) console.debug(`SetAnimation: ${(data as number) & 0xFFFF} (${(data as number) >> 4 & 0x01})`); this.mObject.JSGSetAnimation(data as number); } adaptor_do_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { assert(dataOp == TEOperationData.IMMEDIATE); + if (this.enableLog) console.debug('SetAnimationMode: ', data as number); this.animMode = data as number; } adaptor_do_TEXTURE_ANIMATION(dataOp: TEOperationData, data: number | string, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_INDEX); + if (this.enableLog) console.debug('SetTexAnim:', data); this.mObject.JSGSetTextureAnimation(data as number); } adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { assert(dataOp == TEOperationData.IMMEDIATE); + if (this.enableLog) console.debug('SetTexAnimMode:', data); this.animTexMode = data as number; } } @@ -762,8 +808,14 @@ class TActorObject extends STBObject { return; } + let keyData = []; for (let i = 0; i < keyCount; i++) { - this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, (data as number) + i); + keyData[i] = readData(dataOp, dataOffset + i * 4, dataSize, file); + this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); + } + + if (this.mAdaptor.enableLog) { + console.debug(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${keyData}]`); } } } @@ -1182,8 +1234,6 @@ namespace FVB { dataOffset: file.offset + align(8 + idLen, 4), } - console.log('Parsing Block:', block.id, block.size, block.type); - const obj = this.mControl.createObject(block); if (!obj) { return false; } From c6f7a6c0527ad17f7af24cf47a12936f94d533be Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 22:45:53 -0700 Subject: [PATCH 055/102] JStudio: Fix bug where Immediate and Time values were ints instead of floats This fixes the actor rotation issue in Awake --- src/Common/JSYSTEM/JStudio.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index a4b11365a..b53115b57 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -184,6 +184,8 @@ function readData(dataOp: TEOperationData, dataOffset: number, dataSize: number, switch (dataOp) { case TEOperationData.IMMEDIATE: case TEOperationData.TIME: + return file.view.getFloat32(dataOffset); + case TEOperationData.FUNCVALUE_INDEX: case TEOperationData.OBJECT_INDEX: return file.view.getUint32(dataOffset); From 6f82a59befcb485928408701a8537915f75547ad Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 10 Nov 2024 23:59:21 -0700 Subject: [PATCH 056/102] JStudio: Allow adaptor_do_*() functions to read data as an int or float --- src/Common/JSYSTEM/JStudio.ts | 91 ++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index b53115b57..89d17113d 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -122,6 +122,7 @@ class TVariableValue { // Value will be set only on next update setValue_immediate(v: number): void { + assert(v !== undefined); this.mUpdateFunc = TVariableValue.update_immediate; this.mAge = 0; this.mUpdateParam = v; @@ -129,6 +130,7 @@ class TVariableValue { // Value will be set to (mAge * v * x) each frame setValue_time(v: number): void { + assert(v !== undefined); this.mUpdateFunc = TVariableValue.update_time; this.mAge = 0; this.mUpdateParam = v; @@ -136,6 +138,7 @@ class TVariableValue { // Value will be the result of a Function Value each frame setValue_functionValue(v?: FVB.TFunctionValue): void { + assert(v !== undefined); this.mUpdateFunc = TVariableValue.update_functionValue; this.mAge = 0; this.mUpdateParam = v; @@ -166,6 +169,12 @@ const enum TEOperationData { OBJECT_INDEX = 0x19, // Set the value directly on the JStage object (e.g. an actor), don't store in the adaptor }; +class DataVal { + asInt?: number; + asFloat?: number; + asStr?: string; +} + function dataOpToString(enumValue: TEOperationData) { switch (enumValue) { case TEOperationData.NONE: return "None" @@ -180,19 +189,19 @@ function dataOpToString(enumValue: TEOperationData) { } // Parse data from a DataView as either a number or a string, based on the dataOp -function readData(dataOp: TEOperationData, dataOffset: number, dataSize: number, file: Reader): number | string { +function readData(dataOp: TEOperationData, dataOffset: number, dataSize: number, file: Reader): DataVal { switch (dataOp) { case TEOperationData.IMMEDIATE: case TEOperationData.TIME: - return file.view.getFloat32(dataOffset); + return { asInt: file.view.getUint32(dataOffset), asFloat: file.view.getFloat32(dataOffset) }; case TEOperationData.FUNCVALUE_INDEX: case TEOperationData.OBJECT_INDEX: - return file.view.getUint32(dataOffset); + return { asInt: file.view.getUint32(dataOffset) }; case TEOperationData.FUNCVALUE_NAME: case TEOperationData.OBJECT_NAME: - return readString(file.buffer, dataOffset, dataSize); + return { asStr: readString(file.buffer, dataOffset, dataSize) }; default: assert(false, 'Unsupported data operation'); @@ -212,16 +221,16 @@ abstract class TAdaptor { abstract adaptor_do_data(obj: STBObject, id: number, data: DataView): void; // Set a single VariableValue update function, with the option of using FuncVals - adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: TEOperationData, data: number | string) { + adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: TEOperationData, data: DataVal) { const varval = this.mVariableValues[keyIdx]; const control = obj.mControl; switch (dataOp) { case TEOperationData.VOID: varval.setValue_none(); break; - case TEOperationData.IMMEDIATE: varval.setValue_immediate(data as number); break; - case TEOperationData.TIME: varval.setValue_time(data as number); break; - case TEOperationData.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValueByName(data as string)); break; - case TEOperationData.FUNCVALUE_INDEX: varval.setValue_functionValue(control.getFunctionValueByIdx(data as number)); break; + case TEOperationData.IMMEDIATE: varval.setValue_immediate(data.asFloat!); break; + case TEOperationData.TIME: varval.setValue_time(data.asFloat!); break; + case TEOperationData.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValueByName(data.asStr!)); break; + case TEOperationData.FUNCVALUE_INDEX: varval.setValue_functionValue(control.getFunctionValueByIdx(data.asInt!)); break; default: console.debug('Unsupported dataOp: ', dataOp); debugger; @@ -643,89 +652,89 @@ class TActorAdaptor extends TAdaptor { this.mObject.JSGSetData(id, data); } - adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_PARENT(dataOp: TEOperationData, data: DataVal, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_NAME); - if (this.enableLog) console.debug('SetParent:', data); - this.parent = this.mSystem.JSGFindObject(data as string, JStage.TEObject.PREEXISTING_ACTOR); + if (this.enableLog) console.debug('SetParent:', data.asStr); + this.parent = this.mSystem.JSGFindObject(data.asStr!, JStage.TEObject.PREEXISTING_ACTOR); } - adaptor_do_PARENT_NODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_PARENT_NODE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { debugger; if (this.enableLog) console.debug('SetParentNode:', data); switch (dataOp) { case TEOperationData.OBJECT_NAME: if (this.parent) - this.parentNodeID = this.parent.JSGFindNodeID(data as string); + this.parentNodeID = this.parent.JSGFindNodeID(data.asStr!); break; case TEOperationData.OBJECT_INDEX: - this.parentNodeID = data as number; + this.parentNodeID = data.asInt!; break; default: assert(false); } } - adaptor_do_PARENT_ENABLE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_PARENT_ENABLE(dataOp: TEOperationData, data: number, dataSize: number): void { assert(dataOp == TEOperationData.IMMEDIATE); if (this.enableLog) console.debug('SetParentEnable:', data); - if (!!data) { this.mObject.JSGSetParent(this.parent!, this.parentNodeID); } + if (data) { this.mObject.JSGSetParent(this.parent!, this.parentNodeID); } else { this.mObject.JSGSetParent(null, 0xFFFFFFFF); } } - adaptor_do_RELATION(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_RELATION(dataOp: TEOperationData, data: DataVal, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_NAME); - if (this.enableLog) console.debug('SetRelation:', data); - this.relation = this.mSystem.JSGFindObject(data as string, JStage.TEObject.PREEXISTING_ACTOR); + if (this.enableLog) console.debug('SetRelation:', data.asStr!); + this.relation = this.mSystem.JSGFindObject(data.asStr!, JStage.TEObject.PREEXISTING_ACTOR); } - adaptor_do_RELATION_NODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_RELATION_NODE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { debugger; if (this.enableLog) console.debug('SetRelationNode:', data); switch (dataOp) { case TEOperationData.OBJECT_NAME: if (this.relation) - this.relationNodeID = this.relation.JSGFindNodeID(data as string); + this.relationNodeID = this.relation.JSGFindNodeID(data.asStr!); break; case TEOperationData.OBJECT_INDEX: - this.relationNodeID = data as number; + this.relationNodeID = data.asInt!; break; default: assert(false); } } - adaptor_do_RELATION_ENABLE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_RELATION_ENABLE(dataOp: TEOperationData, data: number, dataSize: number): void { assert(dataOp == TEOperationData.IMMEDIATE); if (this.enableLog) console.debug('SetRelationEnable:', data); this.mObject.JSGSetRelation(!!data, this.relation!, this.relationNodeID); } - adaptor_do_SHAPE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_SHAPE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_INDEX); - if (this.enableLog) console.debug('SetShape: ', data as number); - this.mObject.JSGSetShape(data as number); + if (this.enableLog) console.debug('SetShape: ', data.asInt!); + this.mObject.JSGSetShape(data.asInt!); } - adaptor_do_ANIMATION(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_ANIMATION(dataOp: TEOperationData, data: DataVal, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_INDEX); - if (this.enableLog) console.debug(`SetAnimation: ${(data as number) & 0xFFFF} (${(data as number) >> 4 & 0x01})`); - this.mObject.JSGSetAnimation(data as number); + if (this.enableLog) console.debug(`SetAnimation: ${(data.asInt!) & 0xFFFF} (${(data.asInt!) >> 4 & 0x01})`); + this.mObject.JSGSetAnimation(data.asInt!); } - adaptor_do_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_ANIMATION_MODE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { assert(dataOp == TEOperationData.IMMEDIATE); - if (this.enableLog) console.debug('SetAnimationMode: ', data as number); - this.animMode = data as number; + if (this.enableLog) console.debug('SetAnimationMode: ', data.asInt!); + this.animMode = data.asInt!; } - adaptor_do_TEXTURE_ANIMATION(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_TEXTURE_ANIMATION(dataOp: TEOperationData, data: DataVal, dataSize: number): void { assert(dataOp == TEOperationData.OBJECT_INDEX); if (this.enableLog) console.debug('SetTexAnim:', data); - this.mObject.JSGSetTextureAnimation(data as number); + this.mObject.JSGSetTextureAnimation(data.asInt!); } - adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: TEOperationData, data: number | string, dataSize: number): void { + adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { assert(dataOp == TEOperationData.IMMEDIATE); if (this.enableLog) console.debug('SetTexAnimMode:', data); - this.animTexMode = data as number; + this.animTexMode = data.asInt!; } } @@ -786,7 +795,7 @@ class TActorObject extends STBObject { (adaptor as TActorAdaptor).adaptor_do_PARENT_ENABLE(dataOp, enabled, dataSize) }); } - this.mAdaptor.adaptor_do_PARENT_ENABLE(dataOp, data, dataSize); + this.mAdaptor.adaptor_do_PARENT_ENABLE(dataOp, data.asInt!, dataSize); break; case 0x33: debugger; this.mAdaptor.adaptor_do_RELATION(dataOp, data, dataSize); return; @@ -801,7 +810,7 @@ class TActorObject extends STBObject { (adaptor as TActorAdaptor).adaptor_do_RELATION_ENABLE(dataOp, enabled, dataSize) }); } - this.mAdaptor.adaptor_do_RELATION_ENABLE(dataOp, data, dataSize); + this.mAdaptor.adaptor_do_RELATION_ENABLE(dataOp, data.asInt!, dataSize); break; default: @@ -994,8 +1003,10 @@ class TCameraObject extends STBObject { return; } + let keyData = [] for (let i = 0; i < keyCount; i++) { - this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, (data as number) + i); + keyData[i] = readData(dataOp, dataOffset + i * 4, dataSize, file); + this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } } } From 904f6b08a4507089ad0195c81fc7efb1bea88823 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 00:21:27 -0700 Subject: [PATCH 057/102] JStudio: Improve logging output --- src/Common/JSYSTEM/JStudio.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 89d17113d..0d8d0b683 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -826,7 +826,18 @@ class TActorObject extends STBObject { } if (this.mAdaptor.enableLog) { - console.debug(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${keyData}]`); + const vals = keyData.map(d => { + switch (dataOp) { + case TEOperationData.FUNCVALUE_INDEX: + case TEOperationData.OBJECT_INDEX: + return d.asInt; + case TEOperationData.FUNCVALUE_NAME: + case TEOperationData.OBJECT_NAME: + return d.asStr; + default: + return d.asFloat; + }} ); + console.debug(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${vals}]`); } } } From 738bd32e862b5a51a66725333450bf458f0faaf9 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 01:22:37 -0700 Subject: [PATCH 058/102] JStudio: Fix camera roll and fov being swapped --- src/Common/JSYSTEM/JStudio.ts | 74 +++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 0d8d0b683..43b89c275 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -188,6 +188,21 @@ function dataOpToString(enumValue: TEOperationData) { } } +function logKeyAction(keyData: DataVal[], dataOp: number ) { + const vals = keyData.map(d => { + switch (dataOp) { + case TEOperationData.FUNCVALUE_INDEX: + case TEOperationData.OBJECT_INDEX: + return d.asInt; + case TEOperationData.FUNCVALUE_NAME: + case TEOperationData.OBJECT_NAME: + return d.asStr; + default: + return d.asFloat; + }} ); + return vals; +} + // Parse data from a DataView as either a number or a string, based on the dataOp function readData(dataOp: TEOperationData, dataOffset: number, dataSize: number, file: Reader): DataVal { switch (dataOp) { @@ -825,24 +840,12 @@ class TActorObject extends STBObject { this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - if (this.mAdaptor.enableLog) { - const vals = keyData.map(d => { - switch (dataOp) { - case TEOperationData.FUNCVALUE_INDEX: - case TEOperationData.OBJECT_INDEX: - return d.asInt; - case TEOperationData.FUNCVALUE_NAME: - case TEOperationData.OBJECT_NAME: - return d.asStr; - default: - return d.asFloat; - }} ); - console.debug(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${vals}]`); + if (this.mAdaptor.enableLog) { + console.debug(`[Act] Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); } } } - //---------------------------------------------------------------------------------------------------------------------- // Camera //---------------------------------------------------------------------------------------------------------------------- @@ -856,8 +859,8 @@ const enum Camera_Cmd { SET_TARGET_Y_POS = 0x001A, SET_TARGET_Z_POS = 0x001B, SET_TARGET_POS = 0x001C, - SET_PROJ_FOVY = 0x0026, - SET_VIEW_ROLL = 0x0027, + SET_PROJ_FOVY = 0x0027, + SET_VIEW_ROLL = 0x0026, SET_DIST_NEAR = 0x0028, SET_DIST_FAR = 0x0029, SET_DIST_NEAR_FAR = 0x002A, @@ -877,6 +880,22 @@ const enum Camera_Track { CAMERA_TRACKS_MAX = 0x0A, } +function camKeyToString(enumValue: Camera_Track, count: number) { + switch (enumValue) { + case Camera_Track.EYE_X_POS: return "EYE_X_POS"; + case Camera_Track.EYE_Y_POS: return "EYE_Y_POS"; + case Camera_Track.EYE_Z_POS: return "EYE_Z_POS"; + case Camera_Track.TARGET_X_POS: return "TARGET_X_POS"; + case Camera_Track.TARGET_Y_POS: return "TARGET_Y_POS"; + case Camera_Track.TARGET_Z_POS: return "TARGET_Z_POS"; + case Camera_Track.PROJ_FOVY: return "PROJ_FOVY"; + case Camera_Track.VIEW_ROLL: return "VIEW_ROLL"; + case Camera_Track.DIST_NEAR: return "DIST_NEAR"; + case Camera_Track.DIST_FAR: return "DIST_FAR"; + case Camera_Track.CAMERA_TRACKS_MAX: return "CAMERA_TRACKS_MAX"; + } +} + export abstract class TCamera extends JStage.TObject { JSGFGetType() { return JStage.TEObject.CAMERA; } // JSGGetProjectionType() { return true; } @@ -909,10 +928,10 @@ class TCameraAdaptor extends TAdaptor { ) { super(11); } adaptor_do_prepare(obj: STBObject): void { - this.mVariableValues[6].setOutput(this.mStageCam.JSGSetProjectionFovy); - this.mVariableValues[7].setOutput(this.mStageCam.JSGSetViewRoll); - this.mVariableValues[8].setOutput(this.mStageCam.JSGSetProjectionNear); - this.mVariableValues[9].setOutput(this.mStageCam.JSGSetProjectionFar); + this.mVariableValues[Camera_Track.PROJ_FOVY].setOutput(this.mStageCam.JSGSetProjectionFovy.bind(this.mStageCam)); + this.mVariableValues[Camera_Track.VIEW_ROLL].setOutput(this.mStageCam.JSGSetViewRoll.bind(this.mStageCam)); + this.mVariableValues[Camera_Track.DIST_NEAR].setOutput(this.mStageCam.JSGSetProjectionNear.bind(this.mStageCam)); + this.mVariableValues[Camera_Track.DIST_FAR].setOutput(this.mStageCam.JSGSetProjectionFar.bind(this.mStageCam)); } adaptor_do_begin(obj: STBObject): void { @@ -926,10 +945,10 @@ class TCameraAdaptor extends TAdaptor { this.adaptor_setVariableValue_Vec(Camera_Track.EYE_X_POS, camPos); this.adaptor_setVariableValue_Vec(Camera_Track.TARGET_X_POS, targetPos); - this.mVariableValues[6].setValue_immediate(this.mStageCam.JSGGetProjectionFovy()); - this.mVariableValues[7].setValue_immediate(this.mStageCam.JSGGetViewRoll()); - this.mVariableValues[8].setValue_immediate(this.mStageCam.JSGGetProjectionNear()); - this.mVariableValues[9].setValue_immediate(this.mStageCam.JSGGetProjectionFar()); + this.mVariableValues[Camera_Track.PROJ_FOVY].setValue_immediate(this.mStageCam.JSGGetProjectionFovy()); + this.mVariableValues[Camera_Track.VIEW_ROLL].setValue_immediate(this.mStageCam.JSGGetViewRoll()); + this.mVariableValues[Camera_Track.DIST_NEAR].setValue_immediate(this.mStageCam.JSGGetProjectionNear()); + this.mVariableValues[Camera_Track.DIST_FAR].setValue_immediate(this.mStageCam.JSGGetProjectionFar()); } adaptor_do_end(obj: STBObject): void { @@ -956,7 +975,6 @@ class TCameraAdaptor extends TAdaptor { } // Custom adaptor functions. These can be called from within TCameraObject::do_paragraph() - adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, unk0: number): void { debugger; } @@ -971,6 +989,8 @@ class TCameraAdaptor extends TAdaptor { } class TCameraObject extends STBObject { + private enableLog = true; + constructor( control: TControl, blockObj: TBlockObject, @@ -1019,6 +1039,10 @@ class TCameraObject extends STBObject { keyData[i] = readData(dataOp, dataOffset + i * 4, dataSize, file); this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } + + if (this.enableLog) { + console.debug(`[Cam] Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); + } } } From 0fb528fa2f76d6a46e015fbd160826e25113c27c Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 01:25:58 -0700 Subject: [PATCH 059/102] Move demo camera assignment and added support for FovY, Near, and Far --- src/ZeldaWindWaker/Main.ts | 53 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 241db1a86..bab98261d 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -17,7 +17,7 @@ import { J3DModelInstance } from '../Common/JSYSTEM/J3D/J3DGraphBase.js'; import * as JPA from '../Common/JSYSTEM/JPA.js'; import { BTIData } from '../Common/JSYSTEM/JUTTexture.js'; import { dfRange } from '../DebugFloaters.js'; -import { getMatrixAxisY, getMatrixAxisZ, range } from '../MathHelpers.js'; +import { getMatrixAxisY, getMatrixAxisZ, MathConstants, range } from '../MathHelpers.js'; import { SceneContext } from '../SceneBase.js'; import { TextureMapping } from '../TextureHolder.js'; import { setBackbufferDescSimple, standardFullClearRenderPassDescriptor } from '../gfx/helpers/RenderGraphHelpers.js'; @@ -481,6 +481,32 @@ export class WindWakerRenderer implements Viewer.SceneGfx { viewerInput.camera.setClipPlanes(nearPlane, farPlane); + // noclip modification: if we're paused, allow noclip camera control during demos + const isPaused = viewerInput.deltaTime === 0; + + // TODO: Determine the correct place for this + // dCamera_c::Store() sets the camera params if the demo camera is active + const demoCam = this.globals.scnPlay.demo.getSystem().getCamera(); + if (demoCam && !isPaused) { + let viewPos = this.globals.cameraPosition; + let targetPos = vec3.add(scratchVec3a, this.globals.cameraPosition, this.globals.cameraFwd); + let upVec = vec3.set(scratchVec3b, 0, 1, 0); + + if(demoCam.mFlags & EDemoCamFlags.HasTargetPos) { targetPos = demoCam.mTargetPosition; } + if(demoCam.mFlags & EDemoCamFlags.HasEyePos) { viewPos = demoCam.mViewPosition; } + if(demoCam.mFlags & EDemoCamFlags.HasUpVec) { upVec = demoCam.mUpVector; } + mat4.targetTo(viewerInput.camera.worldMatrix, viewPos, targetPos, upVec); + + if(demoCam.mFlags & EDemoCamFlags.HasFovY) { viewerInput.camera.fovY = demoCam.mFovy * MathConstants.DEG_TO_RAD; } + if(demoCam.mFlags & EDemoCamFlags.HasRoll) { if(demoCam.mRoll > 0) debugger; /* Untested. Remove once confirmed working */ } + if(demoCam.mFlags & EDemoCamFlags.HasAspect) { debugger; /* Untested. Remove once confirmed working */ } + if(demoCam.mFlags & EDemoCamFlags.HasNearZ) { viewerInput.camera.near = demoCam.mProjNear; } + if(demoCam.mFlags & EDemoCamFlags.HasFarZ) { viewerInput.camera.far = demoCam.mProjFar; } + + viewerInput.camera.setClipPlanes(viewerInput.camera.near, viewerInput.camera.far); + viewerInput.camera.worldMatrixUpdated(); + } + this.globals.camera = viewerInput.camera; // Not sure exactly where this is ordered... @@ -778,31 +804,6 @@ class d_s_play extends fopScn { if (globals.scnPlay.demo.getMode() == EDemoMode.Ended) { globals.scnPlay.demo.remove(); } - - // noclip modification: if we're paused, allow noclip camera control - const isPaused = globals.context.viewerInput.deltaTime === 0; - - // TODO: Determine the correct place for this - // dCamera_c::Store() sets the camera params if the demo camera is active - const demoCam = this.demo.getSystem().getCamera(); - if (demoCam && !isPaused) { - let viewPos = globals.cameraPosition; - let targetPos = vec3.add(scratchVec3a, globals.cameraPosition, globals.cameraFwd); - let upVec = vec3.set(scratchVec3b, 0, 1, 0); - - if(demoCam.mFlags & EDemoCamFlags.HasTargetPos) { targetPos = demoCam.mTargetPosition; } - if(demoCam.mFlags & EDemoCamFlags.HasEyePos) { viewPos = demoCam.mViewPosition; } - if(demoCam.mFlags & EDemoCamFlags.HasUpVec) { upVec = demoCam.mUpVector; } - mat4.targetTo(globals.camera.worldMatrix, viewPos, targetPos, upVec); - - if(demoCam.mFlags & EDemoCamFlags.HasFovY) { globals.camera.fovY = demoCam.mFovy; } - if(demoCam.mFlags & EDemoCamFlags.HasRoll) { debugger; /* Untested. Remove once confirmed working */ } - if(demoCam.mFlags & EDemoCamFlags.HasAspect) { debugger; /* Untested. Remove once confirmed working */ } - if(demoCam.mFlags & EDemoCamFlags.HasNearZ) { globals.camera.near = demoCam.mProjNear; debugger; /* Untested. Remove once confirmed working */ } - if(demoCam.mFlags & EDemoCamFlags.HasFarZ) { globals.camera.far = demoCam.mProjFar; debugger; /* Untested. Remove once confirmed working */ } - - globals.camera.worldMatrixUpdated(); - } } public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: Viewer.ViewerRenderInput): void { From 42d77484b5c4e803450579028894995e8df8c88b Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 20:39:28 -0700 Subject: [PATCH 060/102] JStudio: Wire up Actor anim_frame, anim_transition, and texAnim_frame properties These go through a filtering function before being set on the game object, so that STB has a chance to control the looping, reversing, clamping of animations --- src/Common/JSYSTEM/JStudio.ts | 62 +++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 43b89c275..e240f6640 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -33,7 +33,7 @@ export namespace JStage { JSGFEnableFlag(flag: number): void { this.JSGSetFlag(this.JSGGetFlag() | flag); } abstract JSGFGetType(): number; - JSGGetName(): boolean { return false; } // TODO: What's the point of this? + JSGGetName(): boolean { return false; } JSGGetFlag(): number { return 0; } JSGSetFlag(flag: number): void { } JSGGetData(unk0: number, data: Object, unk1: number): boolean { return false; } @@ -592,8 +592,8 @@ class TActorAdaptor extends TAdaptor { public parentNodeID: number; public relation?: JStage.TObject; public relationNodeID: number; - public animMode: number; // @TODO: Enum - public animTexMode: number; // @TODO: Enum + public animMode: number; // See computeAnimFrame() + public animTexMode: number; // See computeAnimFrame() public enableLog = true; @@ -602,12 +602,30 @@ class TActorAdaptor extends TAdaptor { public mObject: TActor, ) { super(14); } + private static computeAnimFrame(animMode: number, maxFrame: number, frame: number) { + const outsideType = animMode; + const reverse = animMode >> 8; + + if( reverse ) { frame = maxFrame - frame; } + if (maxFrame > 0.0) { + const func = FVB.TFunctionValue.toFunction_outside(outsideType); + frame = func(frame, maxFrame); + } + return frame; + } + adaptor_do_prepare(obj: STBObject): void { - this.mVariableValues[Actor_ValIdx.ANIM_TRANSITION].setOutput(this.mObject.JSGSetAnimationTransition); + this.mVariableValues[Actor_ValIdx.ANIM_TRANSITION].setOutput(this.mObject.JSGSetAnimationTransition.bind(this.mObject)); + + this.mVariableValues[Actor_ValIdx.ANIM_FRAME].setOutput((frame: number, adaptor: TAdaptor) => { + frame = TActorAdaptor.computeAnimFrame(this.animMode, this.mObject.JSGGetAnimationFrameMax(), frame); + this.mObject.JSGSetAnimationFrame(frame); + }); - // @TODO: - // TVVOutput_ANIMATION_FRAME_(Actor_ValIdx.ANIM_FRAME, 317, &JStage::TActor::JSGSetAnimationFrame, &JStage::TActor::JSGGetAnimationFrame, &JStage::TActor::JSGGetAnimationFrameMax), - // TVVOutput_ANIMATION_FRAME_(Actor_ValIdx.ANIM_TEX_FRAME, 321, &JStage::TActor::JSGSetTextureAnimationFrame, &JStage::TActor::JSGGetTextureAnimationFrame, &JStage::TActor::JSGGetTextureAnimationFrameMax), + this.mVariableValues[Actor_ValIdx.ANIM_TEX_FRAME].setOutput((frame: number, adaptor: TAdaptor) => { + frame = TActorAdaptor.computeAnimFrame(this.animTexMode, this.mObject.JSGGetTextureAnimationFrameMax(), frame); + this.mObject.JSGSetTextureAnimationFrame(frame); + }); } adaptor_do_begin(obj: STBObject): void { @@ -622,7 +640,7 @@ class TActorAdaptor extends TAdaptor { if (obj.mControl.isTransformEnabled()) { vec3.transformMat4(pos, pos, obj.mControl.getTransformOnGet()); - rot[1] -= obj.mControl.mTransformRotY!; // @TODO: Check this shouldn't be negated + rot[1] -= obj.mControl.mTransformRotY!; } this.adaptor_setVariableValue_Vec(Actor_ValIdx.POS_X, pos); @@ -630,14 +648,8 @@ class TActorAdaptor extends TAdaptor { this.adaptor_setVariableValue_Vec(Actor_ValIdx.SCALE_X, scale); this.mVariableValues[Actor_ValIdx.ANIM_TRANSITION].setValue_immediate(this.mObject.JSGGetAnimationTransition()); - - // @TODO: - // for (const TVVOutputObject* output = saoVVOutput_; output->mValueIndex != -1; output++) { - // mVariableValues[output->mValueIndex].setValue_immediate((this.mObject.*(output->mGetter))()); - // } - // for (const TVVOutput_ANIMATION_FRAME_* output = saoVVOutput_ANIMATION_FRAME_; output->mValueIndex != -1; output++) { - // mVariableValues[output->mValueIndex].setValue_immediate((this.mObject.*(output->mGetter))()); - // } + this.mVariableValues[Actor_ValIdx.ANIM_FRAME].setValue_immediate(this.mObject.JSGGetAnimationFrame()); + this.mVariableValues[Actor_ValIdx.ANIM_FRAME].setValue_immediate(this.mObject.JSGGetTextureAnimationFrame()); } adaptor_do_end(obj: STBObject): void { @@ -654,7 +666,7 @@ class TActorAdaptor extends TAdaptor { if (obj.mControl.isTransformEnabled()) { vec3.transformMat4(pos, pos, obj.mControl.getTransformOnSet()); - rot[1] += obj.mControl.mTransformRotY!; // @TODO: Check this shouldn't be negated + rot[1] += obj.mControl.mTransformRotY!; } this.mObject.JSGSetTranslation(pos); @@ -1121,6 +1133,13 @@ namespace FVB { InterpSet = 0x16, }; + enum EExtrapolationType { + Raw, + Repeat, + Turn, + Clamp + } + class TBlock { size: number; type: number; @@ -1141,7 +1160,14 @@ namespace FVB { getAttrRefer() { return this.refer; } getAttrInterpolate() { return this.interpolate; } - // static ExtrapolateParameter toFunction_outside(int); + static toFunction_outside(type: EExtrapolationType): (frame: number, maxFrame: number ) => number { + switch(type ) { + case EExtrapolationType.Raw: return (f, m) => f; + case EExtrapolationType.Repeat: return (f, m) => { f = f % m; return f < 0 ? f + m : f; } + case EExtrapolationType.Turn: return (f, m) => { f %= (2*m); if (f < 0) f += m; return f > m ? 2*m - f : f }; + case EExtrapolationType.Clamp: return (f, m) => clamp(f, 0.0, m); + } + } // static ExtrapolateParameter toFunction(TFunctionValue::TEOutside outside) { // return toFunction_outside(outside); From fcf862d71733e43787a80f2395f29eaaa9490c01 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 20:54:14 -0700 Subject: [PATCH 061/102] d_demo: Adjust animation frame step to account for non-1 dt --- src/ZeldaWindWaker/d_demo.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index fe09534db..255d58d9b 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -435,9 +435,8 @@ export function dDemo_setDemoData(globals: dGlobals, dtFrames: number, actor: fo } if (enable & 0x40) { - debugger; - if (demoActor.mAnimationFrame > 1.0) { - morf.frameCtrl.setFrame(demoActor.mAnimationFrame - 1.0); + if (demoActor.mAnimationFrame > dtFrames) { + morf.frameCtrl.setFrame(demoActor.mAnimationFrame - dtFrames); morf.play(dtFrames); } else { morf.frameCtrl.setFrame(demoActor.mAnimationFrame); From b5a2de1714d3dd7dd800b0f8e8b5f372a76e03ee Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 20:57:59 -0700 Subject: [PATCH 062/102] JStudio: Renamed TEOperationData to EDataOp --- src/Common/JSYSTEM/JStudio.ts | 126 +++++++++++++++++----------------- 1 file changed, 62 insertions(+), 64 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index e240f6640..ca5e3726a 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -115,7 +115,6 @@ class TVariableValue { // Set Update functions // Modify the function that will be called each Update() //-------------------- - // @TODO: Shorten these names public setValue_none() { this.mUpdateFunc = undefined; } @@ -157,16 +156,15 @@ class TVariableValue { // TAdaptor // Connects the STBObject to a Game Object. Manages tracks of TVariableValues, updates their values on the Game object. //---------------------------------------------------------------------------------------------------------------------- -// @TODO: Cleanup enum names -const enum TEOperationData { +const enum EDataOp { NONE = 0, VOID = 1, // Disable updates for this track. - IMMEDIATE = 2, // Set the value on this track with an immediate value. - TIME = 3, // Ramp the track's value based by a given velocity, starting at 0. - FUNCVALUE_NAME = 0x10, // Unused? - FUNCVALUE_INDEX = 0x12, // Make the track use a function value object for the value. - OBJECT_NAME = 0x18, // TODO - OBJECT_INDEX = 0x19, // Set the value directly on the JStage object (e.g. an actor), don't store in the adaptor + IMMEDIATE = 2, // Set the value on this track to an immediate value. + TIME = 3, // The value increases each frame by a given velocity, starting at 0. + FUNCVALUE_NAME = 0x10, // Evaluate a FunctionValue each frame and use the result + FUNCVALUE_INDEX = 0x12, // Same as FUNCVALUE_NAME but by FunctionValue index + OBJECT_NAME = 0x18, // Set the value directly on the JStage object (e.g. an actor), don't store in the adaptor + OBJECT_INDEX = 0x19, // Same as OBJECT_NAME, but by object index }; class DataVal { @@ -175,27 +173,27 @@ class DataVal { asStr?: string; } -function dataOpToString(enumValue: TEOperationData) { +function dataOpToString(enumValue: EDataOp) { switch (enumValue) { - case TEOperationData.NONE: return "None" - case TEOperationData.VOID: return "Void" - case TEOperationData.IMMEDIATE: return "Immediate" - case TEOperationData.TIME: return "Time" - case TEOperationData.FUNCVALUE_NAME: return "FuncVal" - case TEOperationData.FUNCVALUE_INDEX: return "FuncVal" - case TEOperationData.OBJECT_NAME: return "Obj" - case TEOperationData.OBJECT_INDEX: return "Obj" + case EDataOp.NONE: return "None" + case EDataOp.VOID: return "Void" + case EDataOp.IMMEDIATE: return "Immediate" + case EDataOp.TIME: return "Time" + case EDataOp.FUNCVALUE_NAME: return "FuncVal" + case EDataOp.FUNCVALUE_INDEX: return "FuncVal" + case EDataOp.OBJECT_NAME: return "Obj" + case EDataOp.OBJECT_INDEX: return "Obj" } } function logKeyAction(keyData: DataVal[], dataOp: number ) { const vals = keyData.map(d => { switch (dataOp) { - case TEOperationData.FUNCVALUE_INDEX: - case TEOperationData.OBJECT_INDEX: + case EDataOp.FUNCVALUE_INDEX: + case EDataOp.OBJECT_INDEX: return d.asInt; - case TEOperationData.FUNCVALUE_NAME: - case TEOperationData.OBJECT_NAME: + case EDataOp.FUNCVALUE_NAME: + case EDataOp.OBJECT_NAME: return d.asStr; default: return d.asFloat; @@ -204,18 +202,18 @@ function logKeyAction(keyData: DataVal[], dataOp: number ) { } // Parse data from a DataView as either a number or a string, based on the dataOp -function readData(dataOp: TEOperationData, dataOffset: number, dataSize: number, file: Reader): DataVal { +function readData(dataOp: EDataOp, dataOffset: number, dataSize: number, file: Reader): DataVal { switch (dataOp) { - case TEOperationData.IMMEDIATE: - case TEOperationData.TIME: + case EDataOp.IMMEDIATE: + case EDataOp.TIME: return { asInt: file.view.getUint32(dataOffset), asFloat: file.view.getFloat32(dataOffset) }; - case TEOperationData.FUNCVALUE_INDEX: - case TEOperationData.OBJECT_INDEX: + case EDataOp.FUNCVALUE_INDEX: + case EDataOp.OBJECT_INDEX: return { asInt: file.view.getUint32(dataOffset) }; - case TEOperationData.FUNCVALUE_NAME: - case TEOperationData.OBJECT_NAME: + case EDataOp.FUNCVALUE_NAME: + case EDataOp.OBJECT_NAME: return { asStr: readString(file.buffer, dataOffset, dataSize) }; default: @@ -236,16 +234,16 @@ abstract class TAdaptor { abstract adaptor_do_data(obj: STBObject, id: number, data: DataView): void; // Set a single VariableValue update function, with the option of using FuncVals - adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: TEOperationData, data: DataVal) { + adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: EDataOp, data: DataVal) { const varval = this.mVariableValues[keyIdx]; const control = obj.mControl; switch (dataOp) { - case TEOperationData.VOID: varval.setValue_none(); break; - case TEOperationData.IMMEDIATE: varval.setValue_immediate(data.asFloat!); break; - case TEOperationData.TIME: varval.setValue_time(data.asFloat!); break; - case TEOperationData.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValueByName(data.asStr!)); break; - case TEOperationData.FUNCVALUE_INDEX: varval.setValue_functionValue(control.getFunctionValueByIdx(data.asInt!)); break; + case EDataOp.VOID: varval.setValue_none(); break; + case EDataOp.IMMEDIATE: varval.setValue_immediate(data.asFloat!); break; + case EDataOp.TIME: varval.setValue_time(data.asFloat!); break; + case EDataOp.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValueByName(data.asStr!)); break; + case EDataOp.FUNCVALUE_INDEX: varval.setValue_functionValue(control.getFunctionValueByIdx(data.asInt!)); break; default: console.debug('Unsupported dataOp: ', dataOp); debugger; @@ -679,87 +677,87 @@ class TActorAdaptor extends TAdaptor { this.mObject.JSGSetData(id, data); } - adaptor_do_PARENT(dataOp: TEOperationData, data: DataVal, dataSize: number): void { - assert(dataOp == TEOperationData.OBJECT_NAME); + adaptor_do_PARENT(dataOp: EDataOp, data: DataVal, dataSize: number): void { + assert(dataOp == EDataOp.OBJECT_NAME); if (this.enableLog) console.debug('SetParent:', data.asStr); this.parent = this.mSystem.JSGFindObject(data.asStr!, JStage.TEObject.PREEXISTING_ACTOR); } - adaptor_do_PARENT_NODE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { + adaptor_do_PARENT_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { debugger; if (this.enableLog) console.debug('SetParentNode:', data); switch (dataOp) { - case TEOperationData.OBJECT_NAME: + case EDataOp.OBJECT_NAME: if (this.parent) this.parentNodeID = this.parent.JSGFindNodeID(data.asStr!); break; - case TEOperationData.OBJECT_INDEX: + case EDataOp.OBJECT_INDEX: this.parentNodeID = data.asInt!; break; default: assert(false); } } - adaptor_do_PARENT_ENABLE(dataOp: TEOperationData, data: number, dataSize: number): void { - assert(dataOp == TEOperationData.IMMEDIATE); + adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { + assert(dataOp == EDataOp.IMMEDIATE); if (this.enableLog) console.debug('SetParentEnable:', data); if (data) { this.mObject.JSGSetParent(this.parent!, this.parentNodeID); } else { this.mObject.JSGSetParent(null, 0xFFFFFFFF); } } - adaptor_do_RELATION(dataOp: TEOperationData, data: DataVal, dataSize: number): void { - assert(dataOp == TEOperationData.OBJECT_NAME); + adaptor_do_RELATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { + assert(dataOp == EDataOp.OBJECT_NAME); if (this.enableLog) console.debug('SetRelation:', data.asStr!); this.relation = this.mSystem.JSGFindObject(data.asStr!, JStage.TEObject.PREEXISTING_ACTOR); } - adaptor_do_RELATION_NODE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { + adaptor_do_RELATION_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { debugger; if (this.enableLog) console.debug('SetRelationNode:', data); switch (dataOp) { - case TEOperationData.OBJECT_NAME: + case EDataOp.OBJECT_NAME: if (this.relation) this.relationNodeID = this.relation.JSGFindNodeID(data.asStr!); break; - case TEOperationData.OBJECT_INDEX: + case EDataOp.OBJECT_INDEX: this.relationNodeID = data.asInt!; break; default: assert(false); } } - adaptor_do_RELATION_ENABLE(dataOp: TEOperationData, data: number, dataSize: number): void { - assert(dataOp == TEOperationData.IMMEDIATE); + adaptor_do_RELATION_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { + assert(dataOp == EDataOp.IMMEDIATE); if (this.enableLog) console.debug('SetRelationEnable:', data); this.mObject.JSGSetRelation(!!data, this.relation!, this.relationNodeID); } - adaptor_do_SHAPE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { - assert(dataOp == TEOperationData.OBJECT_INDEX); + adaptor_do_SHAPE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + assert(dataOp == EDataOp.OBJECT_INDEX); if (this.enableLog) console.debug('SetShape: ', data.asInt!); this.mObject.JSGSetShape(data.asInt!); } - adaptor_do_ANIMATION(dataOp: TEOperationData, data: DataVal, dataSize: number): void { - assert(dataOp == TEOperationData.OBJECT_INDEX); + adaptor_do_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { + assert(dataOp == EDataOp.OBJECT_INDEX); if (this.enableLog) console.debug(`SetAnimation: ${(data.asInt!) & 0xFFFF} (${(data.asInt!) >> 4 & 0x01})`); this.mObject.JSGSetAnimation(data.asInt!); } - adaptor_do_ANIMATION_MODE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { - assert(dataOp == TEOperationData.IMMEDIATE); + adaptor_do_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + assert(dataOp == EDataOp.IMMEDIATE); if (this.enableLog) console.debug('SetAnimationMode: ', data.asInt!); this.animMode = data.asInt!; } - adaptor_do_TEXTURE_ANIMATION(dataOp: TEOperationData, data: DataVal, dataSize: number): void { - assert(dataOp == TEOperationData.OBJECT_INDEX); + adaptor_do_TEXTURE_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { + assert(dataOp == EDataOp.OBJECT_INDEX); if (this.enableLog) console.debug('SetTexAnim:', data); this.mObject.JSGSetTextureAnimation(data.asInt!); } - adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: TEOperationData, data: DataVal, dataSize: number): void { - assert(dataOp == TEOperationData.IMMEDIATE); + adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + assert(dataOp == EDataOp.IMMEDIATE); if (this.enableLog) console.debug('SetTexAnimMode:', data); this.animTexMode = data.asInt!; } @@ -775,7 +773,7 @@ class TActorObject extends STBObject { ) { super(control, blockObj, new TActorAdaptor(control.mSystem, stageObj as TActor)) } override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { - const dataOp = (param & 0x1F) as TEOperationData; + const dataOp = (param & 0x1F) as EDataOp; const cmdType = param >> 5; let keyCount = 1; @@ -987,15 +985,15 @@ class TCameraAdaptor extends TAdaptor { } // Custom adaptor functions. These can be called from within TCameraObject::do_paragraph() - adaptor_do_PARENT(dataOp: TEOperationData, data: number | string, unk0: number): void { + adaptor_do_PARENT(dataOp: EDataOp, data: number | string, unk0: number): void { debugger; } - adaptor_do_PARENT_NODE(dataOp: TEOperationData, data: number | string, unk0: number): void { + adaptor_do_PARENT_NODE(dataOp: EDataOp, data: number | string, unk0: number): void { debugger; } - adaptor_do_PARENT_ENABLE(dataOp: TEOperationData, data: number | string, unk0: number): void { + adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number | string, unk0: number): void { debugger; } } @@ -1010,7 +1008,7 @@ class TCameraObject extends STBObject { ) { super(control, blockObj, new TCameraAdaptor(stageObj as TCamera)) } override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { - const dataOp = (param & 0x1F) as TEOperationData; + const dataOp = (param & 0x1F) as EDataOp; const cmdType = param >> 5; let keyCount = 1; From 208c465da0a2e2e0ceae040108ae671731bb2ae1 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 21:04:48 -0700 Subject: [PATCH 063/102] Demo actors now store their name, and report it via JSGGetName for logging --- src/Common/JSYSTEM/JStudio.ts | 4 ++-- src/ZeldaWindWaker/d_demo.ts | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index ca5e3726a..2b0965dd2 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -33,7 +33,7 @@ export namespace JStage { JSGFEnableFlag(flag: number): void { this.JSGSetFlag(this.JSGGetFlag() | flag); } abstract JSGFGetType(): number; - JSGGetName(): boolean { return false; } + JSGGetName(): string | undefined { return undefined; } JSGGetFlag(): number { return 0; } JSGSetFlag(flag: number): void { } JSGGetData(unk0: number, data: Object, unk1: number): boolean { return false; } @@ -851,7 +851,7 @@ class TActorObject extends STBObject { } if (this.mAdaptor.enableLog) { - console.debug(`[Act] Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); + console.debug(`[${this.mAdaptor.mObject.JSGGetName()}] Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); } } } diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 255d58d9b..a9b878bb0 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -151,6 +151,7 @@ class dDemo_camera_c extends TCamera { } class dDemo_actor_c extends TActor { + mName: string; mFlags: number; mTranslation = vec3.create(); mScaling = vec3.create(); @@ -202,6 +203,8 @@ class dDemo_actor_c extends TActor { } } + override JSGGetName() { return this.mName; } + override JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { debugger; // I think this may be one of the shapeInstanceState matrices instead mat4.copy(mtx, this.mModel.modelMatrix); @@ -300,6 +303,7 @@ class dDemo_system_c implements TSystem { if (!this.mpActors[actor.demoActorID]) { actor.demoActorID = this.mpActors.length; this.mpActors[actor.demoActorID] = new dDemo_actor_c(actor); + this.mpActors[actor.demoActorID].mName = objName; }; return this.mpActors[actor.demoActorID]; From 1453a61001d26ee215932a18608ce7ef697e1dad Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 21:18:20 -0700 Subject: [PATCH 064/102] JStudio: A more general approavh to logging. Adaptor now has a logging function with prepends its object's name. --- src/Common/JSYSTEM/JStudio.ts | 73 +++++++++++++++++------------------ src/ZeldaWindWaker/d_demo.ts | 4 +- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 2b0965dd2..f815c8d69 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -222,9 +222,12 @@ function readData(dataOp: EDataOp, dataOffset: number, dataSize: number, file: R } abstract class TAdaptor { + public mObject: JStage.TObject; + constructor( public mCount: number, public mVariableValues = nArray(mCount, i => new TVariableValue()), + public mEnableLogging = true, ) { } abstract adaptor_do_prepare(obj: STBObject): void; @@ -289,6 +292,10 @@ abstract class TAdaptor { vv.update(control.mSecondsPerFrame, this); } } + + log(msg: string) { + if( this.mEnableLogging ) { console.debug(`[${this.mObject.JSGGetName()}] ${msg}`); } + } } //---------------------------------------------------------------------------------------------------------------------- @@ -593,11 +600,9 @@ class TActorAdaptor extends TAdaptor { public animMode: number; // See computeAnimFrame() public animTexMode: number; // See computeAnimFrame() - public enableLog = true; - constructor( private mSystem: TSystem, - public mObject: TActor, + public override mObject: TActor, ) { super(14); } private static computeAnimFrame(animMode: number, maxFrame: number, frame: number) { @@ -673,19 +678,19 @@ class TActorAdaptor extends TAdaptor { } adaptor_do_data(obj: STBObject, id: number, data: DataView): void { - if (this.enableLog) console.debug('SetData: (id)', id); + this.log(`SetData: ${id}`); this.mObject.JSGSetData(id, data); } adaptor_do_PARENT(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.OBJECT_NAME); - if (this.enableLog) console.debug('SetParent:', data.asStr); + this.log(`SetParent: ${data.asStr}`); this.parent = this.mSystem.JSGFindObject(data.asStr!, JStage.TEObject.PREEXISTING_ACTOR); } adaptor_do_PARENT_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { debugger; - if (this.enableLog) console.debug('SetParentNode:', data); + this.log(`SetParentNode: ${data}`); switch (dataOp) { case EDataOp.OBJECT_NAME: if (this.parent) @@ -700,20 +705,20 @@ class TActorAdaptor extends TAdaptor { adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { assert(dataOp == EDataOp.IMMEDIATE); - if (this.enableLog) console.debug('SetParentEnable:', data); + this.log(`SetParentEnable: ${data}`); if (data) { this.mObject.JSGSetParent(this.parent!, this.parentNodeID); } else { this.mObject.JSGSetParent(null, 0xFFFFFFFF); } } adaptor_do_RELATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.OBJECT_NAME); - if (this.enableLog) console.debug('SetRelation:', data.asStr!); + this.log(`SetRelation: ${data.asStr!}`); this.relation = this.mSystem.JSGFindObject(data.asStr!, JStage.TEObject.PREEXISTING_ACTOR); } adaptor_do_RELATION_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { debugger; - if (this.enableLog) console.debug('SetRelationNode:', data); + this.log(`SetRelationNode: ${data}`); switch (dataOp) { case EDataOp.OBJECT_NAME: if (this.relation) @@ -728,37 +733,37 @@ class TActorAdaptor extends TAdaptor { adaptor_do_RELATION_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { assert(dataOp == EDataOp.IMMEDIATE); - if (this.enableLog) console.debug('SetRelationEnable:', data); + this.log(`SetRelationEnable: ${data}`); this.mObject.JSGSetRelation(!!data, this.relation!, this.relationNodeID); } adaptor_do_SHAPE(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.OBJECT_INDEX); - if (this.enableLog) console.debug('SetShape: ', data.asInt!); + this.log(`SetShape: ${data.asInt!}`); this.mObject.JSGSetShape(data.asInt!); } adaptor_do_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.OBJECT_INDEX); - if (this.enableLog) console.debug(`SetAnimation: ${(data.asInt!) & 0xFFFF} (${(data.asInt!) >> 4 & 0x01})`); + this.log(`SetAnimation: ${(data.asInt!) & 0xFFFF} (${(data.asInt!) >> 4 & 0x01})`); this.mObject.JSGSetAnimation(data.asInt!); } adaptor_do_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.IMMEDIATE); - if (this.enableLog) console.debug('SetAnimationMode: ', data.asInt!); + this.log(`SetAnimationMode: ${data.asInt!}`); this.animMode = data.asInt!; } adaptor_do_TEXTURE_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.OBJECT_INDEX); - if (this.enableLog) console.debug('SetTexAnim:', data); + this.log(`SetTexAnim: ${data}`); this.mObject.JSGSetTextureAnimation(data.asInt!); } adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.IMMEDIATE); - if (this.enableLog) console.debug('SetTexAnimMode:', data); + this.log(`SetTexAnimMode: ${data}`); this.animTexMode = data.asInt!; } } @@ -850,9 +855,7 @@ class TActorObject extends STBObject { this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - if (this.mAdaptor.enableLog) { - console.debug(`[${this.mAdaptor.mObject.JSGGetName()}] Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); - } + this.mAdaptor.log(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); } } @@ -934,35 +937,35 @@ export abstract class TCamera extends JStage.TObject { class TCameraAdaptor extends TAdaptor { constructor( - private mStageCam: TCamera + override mObject: TCamera ) { super(11); } adaptor_do_prepare(obj: STBObject): void { - this.mVariableValues[Camera_Track.PROJ_FOVY].setOutput(this.mStageCam.JSGSetProjectionFovy.bind(this.mStageCam)); - this.mVariableValues[Camera_Track.VIEW_ROLL].setOutput(this.mStageCam.JSGSetViewRoll.bind(this.mStageCam)); - this.mVariableValues[Camera_Track.DIST_NEAR].setOutput(this.mStageCam.JSGSetProjectionNear.bind(this.mStageCam)); - this.mVariableValues[Camera_Track.DIST_FAR].setOutput(this.mStageCam.JSGSetProjectionFar.bind(this.mStageCam)); + this.mVariableValues[Camera_Track.PROJ_FOVY].setOutput(this.mObject.JSGSetProjectionFovy.bind(this.mObject)); + this.mVariableValues[Camera_Track.VIEW_ROLL].setOutput(this.mObject.JSGSetViewRoll.bind(this.mObject)); + this.mVariableValues[Camera_Track.DIST_NEAR].setOutput(this.mObject.JSGSetProjectionNear.bind(this.mObject)); + this.mVariableValues[Camera_Track.DIST_FAR].setOutput(this.mObject.JSGSetProjectionFar.bind(this.mObject)); } adaptor_do_begin(obj: STBObject): void { const camPos = scratchVec3a; const targetPos = scratchVec3b; - this.mStageCam.JSGGetViewPosition(camPos); - this.mStageCam.JSGGetViewTargetPosition(targetPos); + this.mObject.JSGGetViewPosition(camPos); + this.mObject.JSGGetViewTargetPosition(targetPos); vec3.transformMat4(camPos, camPos, obj.mControl.getTransformOnGet()); vec3.transformMat4(targetPos, targetPos, obj.mControl.getTransformOnGet()); this.adaptor_setVariableValue_Vec(Camera_Track.EYE_X_POS, camPos); this.adaptor_setVariableValue_Vec(Camera_Track.TARGET_X_POS, targetPos); - this.mVariableValues[Camera_Track.PROJ_FOVY].setValue_immediate(this.mStageCam.JSGGetProjectionFovy()); - this.mVariableValues[Camera_Track.VIEW_ROLL].setValue_immediate(this.mStageCam.JSGGetViewRoll()); - this.mVariableValues[Camera_Track.DIST_NEAR].setValue_immediate(this.mStageCam.JSGGetProjectionNear()); - this.mVariableValues[Camera_Track.DIST_FAR].setValue_immediate(this.mStageCam.JSGGetProjectionFar()); + this.mVariableValues[Camera_Track.PROJ_FOVY].setValue_immediate(this.mObject.JSGGetProjectionFovy()); + this.mVariableValues[Camera_Track.VIEW_ROLL].setValue_immediate(this.mObject.JSGGetViewRoll()); + this.mVariableValues[Camera_Track.DIST_NEAR].setValue_immediate(this.mObject.JSGGetProjectionNear()); + this.mVariableValues[Camera_Track.DIST_FAR].setValue_immediate(this.mObject.JSGGetProjectionFar()); } adaptor_do_end(obj: STBObject): void { - this.mStageCam.JSGFDisableFlag(1); + this.mObject.JSGFDisableFlag(1); } adaptor_do_update(obj: STBObject, frameCount: number): void { @@ -975,8 +978,8 @@ class TCameraAdaptor extends TAdaptor { vec3.transformMat4(camPos, camPos, obj.mControl.getTransformOnSet()); vec3.transformMat4(targetPos, targetPos, obj.mControl.getTransformOnSet()); - this.mStageCam.JSGSetViewPosition(camPos); - this.mStageCam.JSGSetViewTargetPosition(targetPos); + this.mObject.JSGSetViewPosition(camPos); + this.mObject.JSGSetViewTargetPosition(targetPos); } adaptor_do_data(obj: STBObject, id: number, data: DataView): void { @@ -999,8 +1002,6 @@ class TCameraAdaptor extends TAdaptor { } class TCameraObject extends STBObject { - private enableLog = true; - constructor( control: TControl, blockObj: TBlockObject, @@ -1050,9 +1051,7 @@ class TCameraObject extends STBObject { this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - if (this.enableLog) { - console.debug(`[Cam] Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); - } + this.mAdaptor.log(`Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); } } diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index a9b878bb0..2985643b0 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -42,6 +42,8 @@ class dDemo_camera_c extends TCamera { private globals: dGlobals ) { super() } + override JSGGetName() { return 'Cam'; } + override JSGGetProjectionNear(): number { const camera = this.globals.camera; if (!camera) @@ -204,7 +206,7 @@ class dDemo_actor_c extends TActor { } override JSGGetName() { return this.mName; } - + override JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { debugger; // I think this may be one of the shapeInstanceState matrices instead mat4.copy(mtx, this.mModel.modelMatrix); From 1e3a7641409b2d12322a6af35d60278ca93076f5 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 21:25:04 -0700 Subject: [PATCH 065/102] JStudio: Rename debug logging function --- src/Common/JSYSTEM/JStudio.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index f815c8d69..5faa524f3 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -186,7 +186,7 @@ function dataOpToString(enumValue: EDataOp) { } } -function logKeyAction(keyData: DataVal[], dataOp: number ) { +function dataToValue(keyData: DataVal[], dataOp: number ) { const vals = keyData.map(d => { switch (dataOp) { case EDataOp.FUNCVALUE_INDEX: @@ -855,7 +855,7 @@ class TActorObject extends STBObject { this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - this.mAdaptor.log(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); + this.mAdaptor.log(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp )}]`); } } @@ -1014,7 +1014,6 @@ class TCameraObject extends STBObject { let keyCount = 1; let keyIdx; - let data = readData(dataOp, dataOffset, dataSize, file); switch (cmdType) { // Eye position @@ -1051,7 +1050,7 @@ class TCameraObject extends STBObject { this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - this.mAdaptor.log(`Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${logKeyAction(keyData, dataOp )}]`); + this.mAdaptor.log(`Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp )}]`); } } From 8c5f9c26d85ab7cfdc6ccb5bf4fe77c9fe3acf03 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 21:50:50 -0700 Subject: [PATCH 066/102] JStudio: Unify formatting of all enums - All enum names start with E (e.g. EDataOp) following TWW decomp style - All enum values are camel case, not snake case (e.g. PosX) --- src/Common/JSYSTEM/JStudio.ts | 457 ++++++++++++++++------------------ src/ZeldaWindWaker/d_demo.ts | 16 +- 2 files changed, 227 insertions(+), 246 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 5faa524f3..1794cef5e 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -18,14 +18,14 @@ const scratchVec3c = vec3.create(); // The STB objects are manipulated by Sequences from the STB file each frame, and update the Stage Object via Adaptor. //---------------------------------------------------------------------------------------------------------------------- export namespace JStage { - export enum TEObject { - PREEXISTING_ACTOR = 0x0, - UNK1 = 0x1, - ACTOR = 0x2, - CAMERA = 0x3, - AMBIENT = 0x4, - LIGHT = 0x5, - FOG = 0x6, + export enum EObject { + PreExistingActor = 0x0, + Unk1 = 0x1, + Actor = 0x2, + Camera = 0x3, + Ambient = 0x4, + Light = 0x5, + Fog = 0x6, }; export abstract class TObject { @@ -55,7 +55,7 @@ export namespace JStage { // modified by a cutscene. Each game should override JSGFindObject() to supply or create objects for manipulation. //---------------------------------------------------------------------------------------------------------------------- export interface TSystem { - JSGFindObject(objId: string, objType: JStage.TEObject): JStage.TObject | undefined; + JSGFindObject(objId: string, objType: JStage.EObject): JStage.TObject | undefined; } //---------------------------------------------------------------------------------------------------------------------- @@ -157,14 +157,14 @@ class TVariableValue { // Connects the STBObject to a Game Object. Manages tracks of TVariableValues, updates their values on the Game object. //---------------------------------------------------------------------------------------------------------------------- const enum EDataOp { - NONE = 0, - VOID = 1, // Disable updates for this track. - IMMEDIATE = 2, // Set the value on this track to an immediate value. - TIME = 3, // The value increases each frame by a given velocity, starting at 0. - FUNCVALUE_NAME = 0x10, // Evaluate a FunctionValue each frame and use the result - FUNCVALUE_INDEX = 0x12, // Same as FUNCVALUE_NAME but by FunctionValue index - OBJECT_NAME = 0x18, // Set the value directly on the JStage object (e.g. an actor), don't store in the adaptor - OBJECT_INDEX = 0x19, // Same as OBJECT_NAME, but by object index + None = 0, + Void = 1, // Disable updates for this track. + Immediate = 2, // Set the value on this track to an immediate value. + Time = 3, // The value increases each frame by a given velocity, starting at 0. + FuncValName = 0x10, // Evaluate a FunctionValue each frame and use the result + FuncValIdx = 0x12, // Same as FuncValName but by FunctionValue index + ObjectName = 0x18, // Set the value directly on the JStage object (e.g. an actor), don't store in the adaptor + ObjectIdx = 0x19, // Same as ObjectName, but by object index }; class DataVal { @@ -175,45 +175,46 @@ class DataVal { function dataOpToString(enumValue: EDataOp) { switch (enumValue) { - case EDataOp.NONE: return "None" - case EDataOp.VOID: return "Void" - case EDataOp.IMMEDIATE: return "Immediate" - case EDataOp.TIME: return "Time" - case EDataOp.FUNCVALUE_NAME: return "FuncVal" - case EDataOp.FUNCVALUE_INDEX: return "FuncVal" - case EDataOp.OBJECT_NAME: return "Obj" - case EDataOp.OBJECT_INDEX: return "Obj" + case EDataOp.None: return "None" + case EDataOp.Void: return "Void" + case EDataOp.Immediate: return "Immediate" + case EDataOp.Time: return "Time" + case EDataOp.FuncValName: return "FuncVal" + case EDataOp.FuncValIdx: return "FuncVal" + case EDataOp.ObjectName: return "Obj" + case EDataOp.ObjectIdx: return "Obj" } } -function dataToValue(keyData: DataVal[], dataOp: number ) { +function dataToValue(keyData: DataVal[], dataOp: number) { const vals = keyData.map(d => { switch (dataOp) { - case EDataOp.FUNCVALUE_INDEX: - case EDataOp.OBJECT_INDEX: + case EDataOp.FuncValIdx: + case EDataOp.ObjectIdx: return d.asInt; - case EDataOp.FUNCVALUE_NAME: - case EDataOp.OBJECT_NAME: + case EDataOp.FuncValName: + case EDataOp.ObjectName: return d.asStr; - default: + default: return d.asFloat; - }} ); + } + }); return vals; } // Parse data from a DataView as either a number or a string, based on the dataOp function readData(dataOp: EDataOp, dataOffset: number, dataSize: number, file: Reader): DataVal { switch (dataOp) { - case EDataOp.IMMEDIATE: - case EDataOp.TIME: + case EDataOp.Immediate: + case EDataOp.Time: return { asInt: file.view.getUint32(dataOffset), asFloat: file.view.getFloat32(dataOffset) }; - case EDataOp.FUNCVALUE_INDEX: - case EDataOp.OBJECT_INDEX: + case EDataOp.FuncValIdx: + case EDataOp.ObjectIdx: return { asInt: file.view.getUint32(dataOffset) }; - case EDataOp.FUNCVALUE_NAME: - case EDataOp.OBJECT_NAME: + case EDataOp.FuncValName: + case EDataOp.ObjectName: return { asStr: readString(file.buffer, dataOffset, dataSize) }; default: @@ -242,11 +243,11 @@ abstract class TAdaptor { const control = obj.mControl; switch (dataOp) { - case EDataOp.VOID: varval.setValue_none(); break; - case EDataOp.IMMEDIATE: varval.setValue_immediate(data.asFloat!); break; - case EDataOp.TIME: varval.setValue_time(data.asFloat!); break; - case EDataOp.FUNCVALUE_NAME: varval.setValue_functionValue(control.getFunctionValueByName(data.asStr!)); break; - case EDataOp.FUNCVALUE_INDEX: varval.setValue_functionValue(control.getFunctionValueByIdx(data.asInt!)); break; + case EDataOp.Void: varval.setValue_none(); break; + case EDataOp.Immediate: varval.setValue_immediate(data.asFloat!); break; + case EDataOp.Time: varval.setValue_time(data.asFloat!); break; + case EDataOp.FuncValName: varval.setValue_functionValue(control.getFunctionValueByName(data.asStr!)); break; + case EDataOp.FuncValIdx: varval.setValue_functionValue(control.getFunctionValueByIdx(data.asInt!)); break; default: console.debug('Unsupported dataOp: ', dataOp); debugger; @@ -294,7 +295,7 @@ abstract class TAdaptor { } log(msg: string) { - if( this.mEnableLogging ) { console.debug(`[${this.mObject.JSGGetName()}] ${msg}`); } + if (this.mEnableLogging) { console.debug(`[${this.mObject.JSGGetName()}] ${msg}`); } } } @@ -310,7 +311,7 @@ abstract class STBObject { private mId: string; private mType: string; private mFlags: number; - private mStatus: TEStatus = TEStatus.STILL; + private mStatus: EStatus = EStatus.Still; private mIsSequence: boolean = false; private mSuspendFrames: number = 0; private mData: Reader; @@ -352,7 +353,7 @@ abstract class STBObject { reset(blockObj: TBlockObject) { this.pSequence = 0; - this.mStatus = TEStatus.STILL; + this.mStatus = EStatus.Still; this.pSequence_next = 0xC + align(blockObj.id.length + 1, 4); this.mData = blockObj.data; this.mWait = 0; @@ -363,8 +364,8 @@ abstract class STBObject { while (true) { // Top bit of mFlags makes this object immediately inactive, restarting any existing sequence if (this.mFlags & 0x8000) { - if (this.mStatus != TEStatus.INACTIVE) { - this.mStatus = TEStatus.INACTIVE; + if (this.mStatus != EStatus.Inactive) { + this.mStatus = EStatus.Inactive; if (this.mIsSequence) { this.do_end(); } @@ -372,16 +373,16 @@ abstract class STBObject { return true; } - if (this.mStatus == TEStatus.INACTIVE) { + if (this.mStatus == EStatus.Inactive) { assert(this.mIsSequence); this.do_begin(); - this.mStatus = TEStatus.WAIT; + this.mStatus = EStatus.Wait; } if ((this.mControl && this.mControl.isSuspended()) || this.isSuspended()) { if (this.mIsSequence) { - assert((this.mStatus == TEStatus.WAIT) || (this.mStatus == TEStatus.SUSPEND)); - this.mStatus = TEStatus.SUSPEND; + assert((this.mStatus == EStatus.Wait) || (this.mStatus == EStatus.Suspend)); + this.mStatus = EStatus.Suspend; this.do_wait(frameCount); } return true; @@ -393,12 +394,12 @@ abstract class STBObject { // If there is nothing left in the sequence, end it if (!this.pSequence) { if (this.mIsSequence) { - assert(this.mStatus != TEStatus.STILL); + assert(this.mStatus != EStatus.Still); if (!hasWaited) { this.do_wait(0); } this.mIsSequence = false; - this.mStatus = TEStatus.END; + this.mStatus = EStatus.End; this.do_end(); } return false; @@ -406,12 +407,12 @@ abstract class STBObject { // If we're not currently running a sequence, start it if (!this.mIsSequence) { - assert(this.mStatus == TEStatus.STILL); + assert(this.mStatus == EStatus.Still); this.mIsSequence = true; this.do_begin(); } - this.mStatus = TEStatus.WAIT; + this.mStatus = EStatus.Wait; if (this.mWait == 0) { this.process_sequence(); @@ -530,46 +531,46 @@ class TControlObject extends STBObject { //---------------------------------------------------------------------------------------------------------------------- // Actor //---------------------------------------------------------------------------------------------------------------------- -const enum Actor_ValIdx { - ANIM_FRAME = 0, - ANIM_TRANSITION = 1, - ANIM_TEX_FRAME = 2, - - POS_X = 3, - POS_Y = 4, - POS_Z = 5, - ROT_X = 6, - ROT_Y = 7, - ROT_Z = 8, - SCALE_X = 9, - SCALE_Y = 10, - SCALE_Z = 11, - - PARENT = 12, - RELATION = 13, +const enum EActorTrack { + AnimFrame = 0, + AnimTransition = 1, + TexAnimFrame = 2, + + PosX = 3, + PosY = 4, + PosZ = 5, + RotX = 6, + RotY = 7, + RotZ = 8, + ScaleX = 9, + ScaleY = 10, + ScaleZ = 11, + + Parent = 12, + Relation = 13, } -function keyToString(enumValue: Actor_ValIdx, count: number) { +function keyToString(enumValue: EActorTrack, count: number) { switch (enumValue) { - case Actor_ValIdx.ANIM_FRAME: return "ANIM_FRAME" - case Actor_ValIdx.ANIM_TRANSITION: return "ANIM_TRANSITION" - case Actor_ValIdx.ANIM_TEX_FRAME: return "ANIM_TEX_FRAME" - case Actor_ValIdx.POS_X: return count == 3 ? 'POS' : 'POS_X'; - case Actor_ValIdx.POS_Y: return "POS_Y" - case Actor_ValIdx.POS_Z: return "POS_Z" - case Actor_ValIdx.ROT_X: return count == 3 ? 'ROT' : 'ROT_X'; - case Actor_ValIdx.ROT_Y: return "ROT_Y" - case Actor_ValIdx.ROT_Z: return "ROT_Z" - case Actor_ValIdx.SCALE_X: return count == 3 ? 'SCALE' : 'SCALE_X'; - case Actor_ValIdx.SCALE_Y: return "SCALE_Y" - case Actor_ValIdx.SCALE_Z: return "SCALE_Z" - case Actor_ValIdx.PARENT: return "PARENT" - case Actor_ValIdx.RELATION: return "RELATION" + case EActorTrack.AnimFrame: return "AnimFrame" + case EActorTrack.AnimTransition: return "AnimTransition" + case EActorTrack.TexAnimFrame: return "TexAnimFrame" + case EActorTrack.PosX: return count == 3 ? 'POS' : 'PosX'; + case EActorTrack.PosY: return "PosY" + case EActorTrack.PosZ: return "PosZ" + case EActorTrack.RotX: return count == 3 ? 'ROT' : 'RotX'; + case EActorTrack.RotY: return "RotY" + case EActorTrack.RotZ: return "RotZ" + case EActorTrack.ScaleX: return count == 3 ? 'SCALE' : 'ScaleX'; + case EActorTrack.ScaleY: return "ScaleY" + case EActorTrack.ScaleZ: return "ScaleZ" + case EActorTrack.Parent: return "Parent" + case EActorTrack.Relation: return "Relation" } } export abstract class TActor extends JStage.TObject { - JSGFGetType() { return JStage.TEObject.ACTOR; } + JSGFGetType() { return JStage.EObject.Actor; } JSGGetTranslation(dst: vec3) { } JSGSetTranslation(src: ReadonlyVec3) { } JSGGetScaling(dst: vec3) { } @@ -606,10 +607,10 @@ class TActorAdaptor extends TAdaptor { ) { super(14); } private static computeAnimFrame(animMode: number, maxFrame: number, frame: number) { - const outsideType = animMode; + const outsideType = animMode; const reverse = animMode >> 8; - if( reverse ) { frame = maxFrame - frame; } + if (reverse) { frame = maxFrame - frame; } if (maxFrame > 0.0) { const func = FVB.TFunctionValue.toFunction_outside(outsideType); frame = func(frame, maxFrame); @@ -618,14 +619,14 @@ class TActorAdaptor extends TAdaptor { } adaptor_do_prepare(obj: STBObject): void { - this.mVariableValues[Actor_ValIdx.ANIM_TRANSITION].setOutput(this.mObject.JSGSetAnimationTransition.bind(this.mObject)); + this.mVariableValues[EActorTrack.AnimTransition].setOutput(this.mObject.JSGSetAnimationTransition.bind(this.mObject)); - this.mVariableValues[Actor_ValIdx.ANIM_FRAME].setOutput((frame: number, adaptor: TAdaptor) => { + this.mVariableValues[EActorTrack.AnimFrame].setOutput((frame: number, adaptor: TAdaptor) => { frame = TActorAdaptor.computeAnimFrame(this.animMode, this.mObject.JSGGetAnimationFrameMax(), frame); this.mObject.JSGSetAnimationFrame(frame); }); - this.mVariableValues[Actor_ValIdx.ANIM_TEX_FRAME].setOutput((frame: number, adaptor: TAdaptor) => { + this.mVariableValues[EActorTrack.TexAnimFrame].setOutput((frame: number, adaptor: TAdaptor) => { frame = TActorAdaptor.computeAnimFrame(this.animTexMode, this.mObject.JSGGetTextureAnimationFrameMax(), frame); this.mObject.JSGSetTextureAnimationFrame(frame); }); @@ -646,13 +647,13 @@ class TActorAdaptor extends TAdaptor { rot[1] -= obj.mControl.mTransformRotY!; } - this.adaptor_setVariableValue_Vec(Actor_ValIdx.POS_X, pos); - this.adaptor_setVariableValue_Vec(Actor_ValIdx.ROT_X, rot); - this.adaptor_setVariableValue_Vec(Actor_ValIdx.SCALE_X, scale); + this.adaptor_setVariableValue_Vec(EActorTrack.PosX, pos); + this.adaptor_setVariableValue_Vec(EActorTrack.RotX, rot); + this.adaptor_setVariableValue_Vec(EActorTrack.ScaleX, scale); - this.mVariableValues[Actor_ValIdx.ANIM_TRANSITION].setValue_immediate(this.mObject.JSGGetAnimationTransition()); - this.mVariableValues[Actor_ValIdx.ANIM_FRAME].setValue_immediate(this.mObject.JSGGetAnimationFrame()); - this.mVariableValues[Actor_ValIdx.ANIM_FRAME].setValue_immediate(this.mObject.JSGGetTextureAnimationFrame()); + this.mVariableValues[EActorTrack.AnimTransition].setValue_immediate(this.mObject.JSGGetAnimationTransition()); + this.mVariableValues[EActorTrack.AnimFrame].setValue_immediate(this.mObject.JSGGetAnimationFrame()); + this.mVariableValues[EActorTrack.AnimFrame].setValue_immediate(this.mObject.JSGGetTextureAnimationFrame()); } adaptor_do_end(obj: STBObject): void { @@ -663,9 +664,9 @@ class TActorAdaptor extends TAdaptor { const pos = scratchVec3a; const rot = scratchVec3b; const scale = scratchVec3c; - this.adaptor_getVariableValue_Vec(pos, Actor_ValIdx.POS_X); - this.adaptor_getVariableValue_Vec(rot, Actor_ValIdx.ROT_X); - this.adaptor_getVariableValue_Vec(scale, Actor_ValIdx.SCALE_X); + this.adaptor_getVariableValue_Vec(pos, EActorTrack.PosX); + this.adaptor_getVariableValue_Vec(rot, EActorTrack.RotX); + this.adaptor_getVariableValue_Vec(scale, EActorTrack.ScaleX); if (obj.mControl.isTransformEnabled()) { vec3.transformMat4(pos, pos, obj.mControl.getTransformOnSet()); @@ -683,20 +684,20 @@ class TActorAdaptor extends TAdaptor { } adaptor_do_PARENT(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.OBJECT_NAME); + assert(dataOp == EDataOp.ObjectName); this.log(`SetParent: ${data.asStr}`); - this.parent = this.mSystem.JSGFindObject(data.asStr!, JStage.TEObject.PREEXISTING_ACTOR); + this.parent = this.mSystem.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); } adaptor_do_PARENT_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { debugger; this.log(`SetParentNode: ${data}`); switch (dataOp) { - case EDataOp.OBJECT_NAME: + case EDataOp.ObjectName: if (this.parent) this.parentNodeID = this.parent.JSGFindNodeID(data.asStr!); break; - case EDataOp.OBJECT_INDEX: + case EDataOp.ObjectIdx: this.parentNodeID = data.asInt!; break; default: assert(false); @@ -704,27 +705,27 @@ class TActorAdaptor extends TAdaptor { } adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { - assert(dataOp == EDataOp.IMMEDIATE); + assert(dataOp == EDataOp.Immediate); this.log(`SetParentEnable: ${data}`); if (data) { this.mObject.JSGSetParent(this.parent!, this.parentNodeID); } else { this.mObject.JSGSetParent(null, 0xFFFFFFFF); } } adaptor_do_RELATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.OBJECT_NAME); + assert(dataOp == EDataOp.ObjectName); this.log(`SetRelation: ${data.asStr!}`); - this.relation = this.mSystem.JSGFindObject(data.asStr!, JStage.TEObject.PREEXISTING_ACTOR); + this.relation = this.mSystem.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); } adaptor_do_RELATION_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { debugger; this.log(`SetRelationNode: ${data}`); switch (dataOp) { - case EDataOp.OBJECT_NAME: + case EDataOp.ObjectName: if (this.relation) this.relationNodeID = this.relation.JSGFindNodeID(data.asStr!); break; - case EDataOp.OBJECT_INDEX: + case EDataOp.ObjectIdx: this.relationNodeID = data.asInt!; break; default: assert(false); @@ -732,37 +733,37 @@ class TActorAdaptor extends TAdaptor { } adaptor_do_RELATION_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { - assert(dataOp == EDataOp.IMMEDIATE); + assert(dataOp == EDataOp.Immediate); this.log(`SetRelationEnable: ${data}`); this.mObject.JSGSetRelation(!!data, this.relation!, this.relationNodeID); } adaptor_do_SHAPE(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.OBJECT_INDEX); + assert(dataOp == EDataOp.ObjectIdx); this.log(`SetShape: ${data.asInt!}`); this.mObject.JSGSetShape(data.asInt!); } adaptor_do_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.OBJECT_INDEX); + assert(dataOp == EDataOp.ObjectIdx); this.log(`SetAnimation: ${(data.asInt!) & 0xFFFF} (${(data.asInt!) >> 4 & 0x01})`); this.mObject.JSGSetAnimation(data.asInt!); } adaptor_do_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.IMMEDIATE); + assert(dataOp == EDataOp.Immediate); this.log(`SetAnimationMode: ${data.asInt!}`); this.animMode = data.asInt!; } adaptor_do_TEXTURE_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.OBJECT_INDEX); + assert(dataOp == EDataOp.ObjectIdx); this.log(`SetTexAnim: ${data}`); this.mObject.JSGSetTextureAnimation(data.asInt!); } adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.IMMEDIATE); + assert(dataOp == EDataOp.Immediate); this.log(`SetTexAnimMode: ${data}`); this.animTexMode = data.asInt!; } @@ -787,25 +788,25 @@ class TActorObject extends STBObject { switch (cmdType) { // Pos - case 0x09: keyIdx = Actor_ValIdx.POS_X; break; - case 0x0a: keyIdx = Actor_ValIdx.POS_Y; break; - case 0x0b: keyIdx = Actor_ValIdx.POS_Z; break; - case 0x0c: keyCount = 3; keyIdx = Actor_ValIdx.POS_X; break; + case 0x09: keyIdx = EActorTrack.PosX; break; + case 0x0a: keyIdx = EActorTrack.PosY; break; + case 0x0b: keyIdx = EActorTrack.PosZ; break; + case 0x0c: keyCount = 3; keyIdx = EActorTrack.PosX; break; // Rot - case 0x0d: keyIdx = Actor_ValIdx.ROT_X; break; - case 0x0e: keyIdx = Actor_ValIdx.ROT_Y; break; - case 0x0f: keyIdx = Actor_ValIdx.ROT_Z; break; - case 0x10: keyCount = 3; keyIdx = Actor_ValIdx.ROT_X; break; + case 0x0d: keyIdx = EActorTrack.RotX; break; + case 0x0e: keyIdx = EActorTrack.RotY; break; + case 0x0f: keyIdx = EActorTrack.RotZ; break; + case 0x10: keyCount = 3; keyIdx = EActorTrack.RotX; break; // Scale - case 0x11: keyIdx = Actor_ValIdx.SCALE_X; break; - case 0x12: keyIdx = Actor_ValIdx.SCALE_Y; break; - case 0x13: keyIdx = Actor_ValIdx.SCALE_Z; break; - case 0x14: keyCount = 3; keyIdx = Actor_ValIdx.SCALE_X; break; + case 0x11: keyIdx = EActorTrack.ScaleX; break; + case 0x12: keyIdx = EActorTrack.ScaleY; break; + case 0x13: keyIdx = EActorTrack.ScaleZ; break; + case 0x14: keyCount = 3; keyIdx = EActorTrack.ScaleX; break; - case 0x3b: keyIdx = Actor_ValIdx.ANIM_FRAME; break; - case 0x4b: keyIdx = Actor_ValIdx.ANIM_TRANSITION; break; + case 0x3b: keyIdx = EActorTrack.AnimFrame; break; + case 0x4b: keyIdx = EActorTrack.AnimTransition; break; case 0x39: this.mAdaptor.adaptor_do_SHAPE(dataOp, data, dataSize); return; case 0x3a: this.mAdaptor.adaptor_do_ANIMATION(dataOp, data, dataSize); return; @@ -817,7 +818,7 @@ class TActorObject extends STBObject { case 0x31: debugger; this.mAdaptor.adaptor_do_PARENT_NODE(dataOp, data, dataSize); return; case 0x32: debugger; - keyIdx = Actor_ValIdx.PARENT; + keyIdx = EActorTrack.Parent; if ((dataOp < 0x13) && (dataOp > 0x0F)) { debugger; this.mAdaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); @@ -832,7 +833,7 @@ class TActorObject extends STBObject { case 0x34: debugger; this.mAdaptor.adaptor_do_RELATION_NODE(dataOp, data, dataSize); return; case 0x35: debugger; - keyIdx = Actor_ValIdx.RELATION; + keyIdx = EActorTrack.Relation; if ((dataOp < 0x13) && (dataOp > 0x0F)) { debugger; this.mAdaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); @@ -855,64 +856,45 @@ class TActorObject extends STBObject { this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - this.mAdaptor.log(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp )}]`); + this.mAdaptor.log(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp)}]`); } } //---------------------------------------------------------------------------------------------------------------------- // Camera //---------------------------------------------------------------------------------------------------------------------- -// TODO: Rename these Enums -const enum Camera_Cmd { - SET_EYE_X_POS = 0x0015, - SET_EYE_Y_POS = 0x0016, - SET_EYE_Z_POS = 0x0017, - SET_EYE_POS = 0x0018, - SET_TARGET_X_POS = 0x0019, - SET_TARGET_Y_POS = 0x001A, - SET_TARGET_Z_POS = 0x001B, - SET_TARGET_POS = 0x001C, - SET_PROJ_FOVY = 0x0027, - SET_VIEW_ROLL = 0x0026, - SET_DIST_NEAR = 0x0028, - SET_DIST_FAR = 0x0029, - SET_DIST_NEAR_FAR = 0x002A, -} - -const enum Camera_Track { - EYE_X_POS = 0x00, - EYE_Y_POS = 0x01, - EYE_Z_POS = 0x02, - TARGET_X_POS = 0x03, - TARGET_Y_POS = 0x04, - TARGET_Z_POS = 0x05, - PROJ_FOVY = 0x06, - VIEW_ROLL = 0x07, - DIST_NEAR = 0x08, - DIST_FAR = 0x09, - CAMERA_TRACKS_MAX = 0x0A, +const enum ECameraTrack { + PosX = 0x00, + PosY = 0x01, + PosZ = 0x02, + TargetX = 0x03, + TargetY = 0x04, + TargetZ = 0x05, + FovY = 0x06, + Roll = 0x07, + DistNear = 0x08, + DistFar = 0x09, } -function camKeyToString(enumValue: Camera_Track, count: number) { +function camKeyToString(enumValue: ECameraTrack, count: number) { switch (enumValue) { - case Camera_Track.EYE_X_POS: return "EYE_X_POS"; - case Camera_Track.EYE_Y_POS: return "EYE_Y_POS"; - case Camera_Track.EYE_Z_POS: return "EYE_Z_POS"; - case Camera_Track.TARGET_X_POS: return "TARGET_X_POS"; - case Camera_Track.TARGET_Y_POS: return "TARGET_Y_POS"; - case Camera_Track.TARGET_Z_POS: return "TARGET_Z_POS"; - case Camera_Track.PROJ_FOVY: return "PROJ_FOVY"; - case Camera_Track.VIEW_ROLL: return "VIEW_ROLL"; - case Camera_Track.DIST_NEAR: return "DIST_NEAR"; - case Camera_Track.DIST_FAR: return "DIST_FAR"; - case Camera_Track.CAMERA_TRACKS_MAX: return "CAMERA_TRACKS_MAX"; + case ECameraTrack.PosX: return "PosX"; + case ECameraTrack.PosY: return "PosY"; + case ECameraTrack.PosZ: return "PosZ"; + case ECameraTrack.TargetX: return "TargetX"; + case ECameraTrack.TargetY: return "TargetY"; + case ECameraTrack.TargetZ: return "TargetZ"; + case ECameraTrack.FovY: return "FovY"; + case ECameraTrack.Roll: return "Roll"; + case ECameraTrack.DistNear: return "DistNear"; + case ECameraTrack.DistFar: return "DistFar"; } } export abstract class TCamera extends JStage.TObject { - JSGFGetType() { return JStage.TEObject.CAMERA; } - // JSGGetProjectionType() { return true; } - // JSGSetProjectionType(JStage:: TECameraProjection) { } + JSGFGetType() { return JStage.EObject.Camera; } + JSGGetProjectionType() { return true; } + JSGSetProjectionType(type: number) { } JSGGetProjectionNear() { return 0.0; } JSGSetProjectionNear(near: number) { } JSGGetProjectionFar() { return Number.MAX_VALUE; } @@ -923,8 +905,8 @@ export abstract class TCamera extends JStage.TObject { JSGSetProjectionAspect(aspect: number) { }; JSGGetProjectionField() { return 0.0 }; JSGSetProjectionField(field: number) { }; - // JSGGetViewType() { return true; }; - // JSGSetViewType(JStage:: TECameraView) { } + JSGGetViewType() { return true; }; + JSGSetViewType(type: number) { } JSGGetViewPosition(dst: vec3) { vec3.zero(dst); } JSGSetViewPosition(v: ReadonlyVec3) { } JSGGetViewUpVector(dst: vec3) { vec3.zero(dst); } @@ -941,10 +923,10 @@ class TCameraAdaptor extends TAdaptor { ) { super(11); } adaptor_do_prepare(obj: STBObject): void { - this.mVariableValues[Camera_Track.PROJ_FOVY].setOutput(this.mObject.JSGSetProjectionFovy.bind(this.mObject)); - this.mVariableValues[Camera_Track.VIEW_ROLL].setOutput(this.mObject.JSGSetViewRoll.bind(this.mObject)); - this.mVariableValues[Camera_Track.DIST_NEAR].setOutput(this.mObject.JSGSetProjectionNear.bind(this.mObject)); - this.mVariableValues[Camera_Track.DIST_FAR].setOutput(this.mObject.JSGSetProjectionFar.bind(this.mObject)); + this.mVariableValues[ECameraTrack.FovY].setOutput(this.mObject.JSGSetProjectionFovy.bind(this.mObject)); + this.mVariableValues[ECameraTrack.Roll].setOutput(this.mObject.JSGSetViewRoll.bind(this.mObject)); + this.mVariableValues[ECameraTrack.DistNear].setOutput(this.mObject.JSGSetProjectionNear.bind(this.mObject)); + this.mVariableValues[ECameraTrack.DistFar].setOutput(this.mObject.JSGSetProjectionFar.bind(this.mObject)); } adaptor_do_begin(obj: STBObject): void { @@ -956,12 +938,12 @@ class TCameraAdaptor extends TAdaptor { vec3.transformMat4(camPos, camPos, obj.mControl.getTransformOnGet()); vec3.transformMat4(targetPos, targetPos, obj.mControl.getTransformOnGet()); - this.adaptor_setVariableValue_Vec(Camera_Track.EYE_X_POS, camPos); - this.adaptor_setVariableValue_Vec(Camera_Track.TARGET_X_POS, targetPos); - this.mVariableValues[Camera_Track.PROJ_FOVY].setValue_immediate(this.mObject.JSGGetProjectionFovy()); - this.mVariableValues[Camera_Track.VIEW_ROLL].setValue_immediate(this.mObject.JSGGetViewRoll()); - this.mVariableValues[Camera_Track.DIST_NEAR].setValue_immediate(this.mObject.JSGGetProjectionNear()); - this.mVariableValues[Camera_Track.DIST_FAR].setValue_immediate(this.mObject.JSGGetProjectionFar()); + this.adaptor_setVariableValue_Vec(ECameraTrack.PosX, camPos); + this.adaptor_setVariableValue_Vec(ECameraTrack.TargetX, targetPos); + this.mVariableValues[ECameraTrack.FovY].setValue_immediate(this.mObject.JSGGetProjectionFovy()); + this.mVariableValues[ECameraTrack.Roll].setValue_immediate(this.mObject.JSGGetViewRoll()); + this.mVariableValues[ECameraTrack.DistNear].setValue_immediate(this.mObject.JSGGetProjectionNear()); + this.mVariableValues[ECameraTrack.DistFar].setValue_immediate(this.mObject.JSGGetProjectionFar()); } adaptor_do_end(obj: STBObject): void { @@ -972,8 +954,8 @@ class TCameraAdaptor extends TAdaptor { const camPos = scratchVec3a; const targetPos = scratchVec3b; - this.adaptor_getVariableValue_Vec(camPos, Camera_Track.EYE_X_POS); - this.adaptor_getVariableValue_Vec(targetPos, Camera_Track.TARGET_X_POS); + this.adaptor_getVariableValue_Vec(camPos, ECameraTrack.PosX); + this.adaptor_getVariableValue_Vec(targetPos, ECameraTrack.TargetX); vec3.transformMat4(camPos, camPos, obj.mControl.getTransformOnSet()); vec3.transformMat4(targetPos, targetPos, obj.mControl.getTransformOnSet()); @@ -1017,26 +999,26 @@ class TCameraObject extends STBObject { switch (cmdType) { // Eye position - case Camera_Cmd.SET_EYE_X_POS: keyIdx = Camera_Track.EYE_X_POS; break; - case Camera_Cmd.SET_EYE_Z_POS: keyIdx = Camera_Track.EYE_Y_POS; break; - case Camera_Cmd.SET_EYE_Y_POS: keyIdx = Camera_Track.EYE_Z_POS; break; - case Camera_Cmd.SET_EYE_POS: keyCount = 3; keyIdx = Camera_Track.EYE_X_POS; break; + case 0x15: keyIdx = ECameraTrack.PosX; break; + case 0x16: keyIdx = ECameraTrack.PosY; break; + case 0x17: keyIdx = ECameraTrack.PosZ; break; + case 0x18: keyCount = 3; keyIdx = ECameraTrack.PosX; break; break; // Target position - case Camera_Cmd.SET_TARGET_X_POS: keyIdx = Camera_Track.TARGET_X_POS; break; - case Camera_Cmd.SET_TARGET_Y_POS: keyIdx = Camera_Track.TARGET_Y_POS; break; - case Camera_Cmd.SET_TARGET_Z_POS: keyIdx = Camera_Track.TARGET_Z_POS; break; - case Camera_Cmd.SET_TARGET_POS: keyCount = 3; keyIdx = Camera_Track.TARGET_X_POS; break; + case 0x19: keyIdx = ECameraTrack.TargetX; break; + case 0x1A: keyIdx = ECameraTrack.TargetY; break; + case 0x1B: keyIdx = ECameraTrack.TargetZ; break; + case 0x1C: keyCount = 3; keyIdx = ECameraTrack.TargetX; break; // Camera params - case Camera_Cmd.SET_PROJ_FOVY: keyIdx = Camera_Track.PROJ_FOVY; break; - case Camera_Cmd.SET_VIEW_ROLL: keyIdx = Camera_Track.VIEW_ROLL; break; + case 0x26: keyIdx = ECameraTrack.Roll; break; + case 0x27: keyIdx = ECameraTrack.FovY; break; // Near/far distance - case Camera_Cmd.SET_DIST_NEAR: keyIdx = Camera_Track.DIST_NEAR; break; - case Camera_Cmd.SET_DIST_FAR: keyIdx = Camera_Track.DIST_FAR; break; - case Camera_Cmd.SET_EYE_POS: keyCount = 2; keyIdx = Camera_Track.DIST_NEAR; break; + case 0x28: keyIdx = ECameraTrack.DistNear; break; + case 0x29: keyIdx = ECameraTrack.DistFar; break; + case 0x2A: keyCount = 2; keyIdx = ECameraTrack.DistNear; break; default: console.debug('Unsupported TCamera update: ', cmdType, ' ', dataOp); @@ -1049,8 +1031,8 @@ class TCameraObject extends STBObject { keyData[i] = readData(dataOp, dataOffset + i * 4, dataSize, file); this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - - this.mAdaptor.log(`Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp )}]`); + + this.mAdaptor.log(`Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp)}]`); } } @@ -1156,11 +1138,11 @@ namespace FVB { getAttrRefer() { return this.refer; } getAttrInterpolate() { return this.interpolate; } - static toFunction_outside(type: EExtrapolationType): (frame: number, maxFrame: number ) => number { - switch(type ) { + static toFunction_outside(type: EExtrapolationType): (frame: number, maxFrame: number) => number { + switch (type) { case EExtrapolationType.Raw: return (f, m) => f; case EExtrapolationType.Repeat: return (f, m) => { f = f % m; return f < 0 ? f + m : f; } - case EExtrapolationType.Turn: return (f, m) => { f %= (2*m); if (f < 0) f += m; return f > m ? 2*m - f : f }; + case EExtrapolationType.Turn: return (f, m) => { f %= (2 * m); if (f < 0) f += m; return f > m ? 2 * m - f : f }; case EExtrapolationType.Clamp: return (f, m) => clamp(f, 0.0, m); } } @@ -1633,16 +1615,15 @@ namespace FVB { // Perform a simple operation to combine some number of other FunctionValues, returning the result. // For example, we can using the ADD ECompositeOp we can return the sum of two ListParameter FunctionValues. //---------------------------------------------------------------------------------------------------------------------- - enum TECompositeOp { - NONE, - RAW, - IDX, - PARAM, - ADD, - SUB, - MUL, - DIV, - ENUM_SIZE, + enum ECompositeOp { + None, + Raw, + Idx, + Parm, + Add, + Sub, + Mul, + Div, } class TObject_Composite extends FVB.TObject { @@ -1658,13 +1639,13 @@ namespace FVB { let fvData: number; let fvFunc: (ref: TFunctionValue[], data: number, t: number) => number; switch (compositeOp) { - case TECompositeOp.RAW: fvData = uintData; fvFunc = FunctionValue_Composite.composite_raw; break; - case TECompositeOp.IDX: fvData = uintData; fvFunc = FunctionValue_Composite.composite_index; break; - case TECompositeOp.PARAM: fvData = floatData; fvFunc = FunctionValue_Composite.composite_parameter; break; - case TECompositeOp.ADD: fvData = floatData; fvFunc = FunctionValue_Composite.composite_add; break; - case TECompositeOp.SUB: fvData = floatData; fvFunc = FunctionValue_Composite.composite_subtract; break; - case TECompositeOp.MUL: fvData = floatData; fvFunc = FunctionValue_Composite.composite_multiply; break; - case TECompositeOp.DIV: fvData = floatData; fvFunc = FunctionValue_Composite.composite_divide; break; + case ECompositeOp.Raw: fvData = uintData; fvFunc = FunctionValue_Composite.composite_raw; break; + case ECompositeOp.Idx: fvData = uintData; fvFunc = FunctionValue_Composite.composite_index; break; + case ECompositeOp.Parm: fvData = floatData; fvFunc = FunctionValue_Composite.composite_parameter; break; + case ECompositeOp.Add: fvData = floatData; fvFunc = FunctionValue_Composite.composite_add; break; + case ECompositeOp.Sub: fvData = floatData; fvFunc = FunctionValue_Composite.composite_subtract; break; + case ECompositeOp.Mul: fvData = floatData; fvFunc = FunctionValue_Composite.composite_multiply; break; + case ECompositeOp.Div: fvData = floatData; fvFunc = FunctionValue_Composite.composite_divide; break; default: console.warn('Unsupported CompositeOp:', compositeOp); return; @@ -1829,12 +1810,12 @@ enum ESequenceCmd { Paragraph = 0x80, } -enum TEStatus { - STILL = 0, - END = 1 << 0, - WAIT = 1 << 1, - SUSPEND = 1 << 2, - INACTIVE = 1 << 3, +enum EStatus { + Still = 0, + End = 1 << 0, + Wait = 1 << 1, + Suspend = 1 << 2, + Inactive = 1 << 3, } // The runtime object that stores interpolated data each frame. Written to by a TAdapter. By default, the base object @@ -1861,7 +1842,7 @@ export class TControl { private mTransformOnGetMtx = mat4.create(); private mTransformOnSetMtx = mat4.create(); - private mStatus: TEStatus = TEStatus.STILL; + private mStatus: EStatus = EStatus.Still; private mObjects: STBObject[] = []; // A special object that the STB file can use to suspend the demo (such as while waiting for player input) @@ -1922,10 +1903,10 @@ export class TControl { // Really this is a stb::TFactory method public createObject(blockObj: TBlockObject): STBObject | undefined { let objConstructor; - let objType: JStage.TEObject; + let objType: JStage.EObject; switch (blockObj.type) { - case 'JCMR': objConstructor = TCameraObject; objType = JStage.TEObject.CAMERA; break; - case 'JACT': objConstructor = TActorObject; objType = JStage.TEObject.ACTOR; break; + case 'JCMR': objConstructor = TCameraObject; objType = JStage.EObject.Camera; break; + case 'JACT': objConstructor = TActorObject; objType = JStage.EObject.Actor; break; case 'JABL': case 'JLIT': case 'JFOG': diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 2985643b0..5130a1cc5 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -284,17 +284,17 @@ class dDemo_system_c implements TSystem { private globals: dGlobals ) { } - public JSGFindObject(objName: string, objType: JStage.TEObject): JStage.TObject | undefined { + public JSGFindObject(objName: string, objType: JStage.EObject): JStage.TObject | undefined { switch (objType) { - case JStage.TEObject.CAMERA: + case JStage.EObject.Camera: if (this.mpActiveCamera) return this.mpActiveCamera; else return this.mpActiveCamera = new dDemo_camera_c(this.globals); - case JStage.TEObject.ACTOR: - case JStage.TEObject.PREEXISTING_ACTOR: + case JStage.EObject.Actor: + case JStage.EObject.PreExistingActor: let actor = fopAcM_searchFromName(this.globals, objName, 0, 0); if (!actor) { - if (objType == JStage.TEObject.ACTOR && objName == "d_act") { + if (objType == JStage.EObject.Actor && objName == "d_act") { debugger; // Untested. Unimplemented actor = {} as fopAc_ac_c; } else { @@ -309,9 +309,9 @@ class dDemo_system_c implements TSystem { }; return this.mpActors[actor.demoActorID]; - case JStage.TEObject.AMBIENT: - case JStage.TEObject.LIGHT: - case JStage.TEObject.FOG: + case JStage.EObject.Ambient: + case JStage.EObject.Light: + case JStage.EObject.Fog: default: console.debug('[JSGFindObject] Unhandled type: ', objType); return undefined; From 0a5d120f36ec717fd757ac62ba1abec1896b7a60 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 23:19:19 -0700 Subject: [PATCH 067/102] d_demo: Implement JSGGetViewTargetPosition() --- src/ZeldaWindWaker/d_demo.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 5130a1cc5..cc51a831d 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -127,8 +127,7 @@ class dDemo_camera_c extends TCamera { const camera = this.globals.camera; if (!camera) vec3.set(dst, 0, 0, 0); - console.debug('JSGGetViewTargetPosition called. This is not yet working'); - vec3.set(dst, 0, 0, 0); + vec3.add(dst, this.globals.cameraPosition, this.globals.cameraFwd); } From 79193cb4b0cf0191b87d5c0d6a78e5f45ea1c5b0 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 11 Nov 2024 23:19:42 -0700 Subject: [PATCH 068/102] JStudio: Remove some vestigial TODOs --- src/Common/JSYSTEM/JStudio.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 1794cef5e..dc62fee72 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -1040,7 +1040,6 @@ class TCameraObject extends STBObject { //---------------------------------------------------------------------------------------------------------------------- // Parsing helpers //---------------------------------------------------------------------------------------------------------------------- -// @TODO: Rename class Reader { buffer: ArrayBufferSlice; view: DataView; @@ -1229,7 +1228,6 @@ namespace FVB { case EPrepareOp.RangeProgress: case EPrepareOp.RangeAdjust: case EPrepareOp.RangeOutside: - default: console.warn('Unhandled FVB PrepareOp: ', para.type); debugger; @@ -1513,7 +1511,6 @@ namespace FVB { return value; } - // @TODO: Better way of accessing 2-word keys interpolateBSpline(t: number): number { const c = this.curKeyIdx * 2; @@ -1834,7 +1831,7 @@ export abstract class TBlockObject { export class TControl { public mSystem: TSystem; public mFvbControl = new FVB.TControl(); - public mSecondsPerFrame: number; + public mSecondsPerFrame: number = 1 / 30.0; private mSuspendFrames: number; public mTransformOrigin?: vec3; @@ -1850,7 +1847,6 @@ export class TControl { constructor(system: TSystem) { this.mSystem = system; - this.mSecondsPerFrame = 1 / 30.0; // @TODO: Allow the game to change this? } public isSuspended() { return this.mSuspendFrames > 0; } From 573f44f4b68e975d5da8da3f262601393dea3d99 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 12 Nov 2024 19:40:42 -0700 Subject: [PATCH 069/102] d_demo: Convert STB rotations into short radians This is expected by functions like mDoMtx_ZXYrotM --- src/ZeldaWindWaker/SComponent.ts | 8 ++++++++ src/ZeldaWindWaker/d_demo.ts | 11 +++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/SComponent.ts b/src/ZeldaWindWaker/SComponent.ts index 55cb84c33..966efd464 100644 --- a/src/ZeldaWindWaker/SComponent.ts +++ b/src/ZeldaWindWaker/SComponent.ts @@ -113,6 +113,14 @@ export function cM_rndFX(max: number): number { return 2.0 * (max * (Math.random() - 0.5)); } +export function cM_deg2s(deg: number): number { + return deg * 182.04445; +} + +export function cM_sht2d(rad: number) { + return rad * 0.005493164; +} + export function cM_atan2s(y: number, x: number): number { return cM__Rad2Short(Math.atan2(y, x)); } diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index cc51a831d..e1341c15c 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -9,6 +9,7 @@ import { mDoExt_McaMorf } from "./m_do_ext"; import { assert } from "../util.js"; import { ResType } from "./d_resorce.js"; import { LoopMode } from "../Common/JSYSTEM/J3D/J3DLoader"; +import { cM_deg2s, cM_sht2d } from "./SComponent.js"; export enum EDemoMode { None, @@ -217,7 +218,11 @@ class dDemo_actor_c extends TActor { override JSGGetTranslation(dst: vec3) { vec3.copy(dst, this.mTranslation); } override JSGGetScaling(dst: vec3) { vec3.copy(dst, this.mScaling); } - override JSGGetRotation(dst: vec3) { vec3.scale(dst, this.mRotation, MathConstants.RAD_TO_DEG); } + override JSGGetRotation(dst: vec3) { + dst[0] = cM_sht2d(this.mRotation[0]); + dst[1] = cM_sht2d(this.mRotation[1]); + dst[2] = cM_sht2d(this.mRotation[2]); + } override JSGSetData(id: number, data: DataView): void { this.stbDataId = id; @@ -236,7 +241,9 @@ class dDemo_actor_c extends TActor { } override JSGSetRotation(src: ReadonlyVec3) { - vec3.scale(this.mRotation, src, MathConstants.DEG_TO_RAD); + this.mRotation[0] = cM_deg2s(src[0]); + this.mRotation[1] = cM_deg2s(src[1]); + this.mRotation[2] = cM_deg2s(src[2]); this.mFlags |= 0x08; } From 953f23c00fd1e179d4e737cff7fa46725ef80744 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 12 Nov 2024 19:42:31 -0700 Subject: [PATCH 070/102] New actor: d_a_npc_ls1 (Aryll, Little Sister). Moved out of LegacyActor the body renders, but not yet the hands --- src/ZeldaWindWaker/LegacyActor.ts | 7 - src/ZeldaWindWaker/d_a.ts | 268 +++++++++++++++++++++++++++--- src/ZeldaWindWaker/framework.ts | 1 + 3 files changed, 246 insertions(+), 30 deletions(-) diff --git a/src/ZeldaWindWaker/LegacyActor.ts b/src/ZeldaWindWaker/LegacyActor.ts index 5dda4d26a..616d67781 100644 --- a/src/ZeldaWindWaker/LegacyActor.ts +++ b/src/ZeldaWindWaker/LegacyActor.ts @@ -262,13 +262,6 @@ function spawnLegacyActor(globals: dGlobals, legacy: d_a_noclip_legacy, actor: f m.bindTTK1(parseBTK(rarc, `btk/gmjwp00.btk`)); }); // NPCs - // Aryll - else if (actorName === 'Ls' || actorName === 'Ls1') fetchArchive(`Ls`).then((rarc) => { - const m = buildModel(rarc, `bdlm/ls.bdl`); - buildChildModel(rarc, `bdl/lshand.bdl`).setParentJoint(m, `handL`); - buildChildModel(rarc, `bdl/lshand.bdl`).setParentJoint(m, `handR`); - m.bindANK1(parseBCK(rarc, `bcks/ls_wait01.bck`)); - }); // Beedle else if (actorName === 'Bs1') fetchArchive(`Bs`).then((rarc) => { const m = buildModel(rarc, `bdlm/bs.bdl`); diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 51390272a..3dd7cfa06 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -19,7 +19,7 @@ import { TevDefaultSwapTables } from "../gx/gx_material.js"; import { ColorKind, DrawParams, GXMaterialHelperGfx, MaterialParams } from "../gx/gx_render.js"; import { arrayRemove, assert, assertExists, nArray } from "../util.js"; import { ViewerRenderInput } from "../viewer.js"; -import { cLib_addCalc, cLib_addCalc2, cLib_addCalcAngleRad2, cLib_addCalcAngleS, cLib_addCalcAngleS2, cLib_addCalcPosXZ2, cLib_chasePosXZ, cLib_distanceSqXZ, cLib_distanceXZ, cLib_targetAngleX, cLib_targetAngleY, cM__Short2Rad, cM_atan2s, cM_rndF, cM_rndFX } from "./SComponent.js"; +import { cLib_addCalc, cLib_addCalc2, cLib_addCalcAngleRad2, cLib_addCalcAngleS, cLib_addCalcAngleS2, cLib_addCalcPosXZ2, cLib_chasePosXZ, cLib_distanceSqXZ, cLib_distanceXZ, cLib_targetAngleX, cLib_targetAngleY, cM__Rad2Short, cM__Short2Rad, cM_atan2s, cM_rndF, cM_rndFX } from "./SComponent.js"; import { dLib_getWaterY, dLib_waveInit, dLib_waveRot, dLib_wave_c, d_a_sea } from "./d_a_sea.js"; import { cBgW_Flags, dBgS_GndChk, dBgW } from "./d_bg.js"; import { PeekZResult } from "./d_dlst_peekZ.js"; @@ -29,10 +29,11 @@ import { dPa_splashEcallBack, dPa_trackEcallBack, dPa_waveEcallBack } from "./d_ import { ResType, dComIfG_resLoad } from "./d_resorce.js"; import { dPath, dPath_GetRoomPath, dPath__Point, dStage_Multi_c, dStage_stagInfo_GetSTType } from "./d_stage.js"; import { cPhs__Status, fGlobals, fopAcIt_JudgeByID, fopAcM_create, fopAcM_prm_class, fopAc_ac_c, fpcPf__Register, fpcSCtRq_Request, fpc__ProcessName, fpc_bs__Constructor } from "./framework.js"; -import { mDoExt_McaMorf, mDoExt_bckAnm, mDoExt_brkAnm, mDoExt_btkAnm, mDoExt_modelEntryDL, mDoExt_modelUpdateDL, mDoLib_project } from "./m_do_ext.js"; +import { mDoExt_McaMorf, mDoExt_bckAnm, mDoExt_brkAnm, mDoExt_btkAnm, mDoExt_btpAnm, mDoExt_modelEntryDL, mDoExt_modelUpdateDL, mDoLib_project } 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 { dGlobals } from "./Main.js"; import { dDlst_alphaModel__Type } from "./d_drawlist.js"; +import { dDemo_setDemoData } from "./d_demo.js"; // Framework'd actors @@ -54,7 +55,7 @@ class d_a_grass extends fopAc_ac_c { { group: 5, count: 7 }, { group: 6, count: 5 }, ]; - + static kSpawnOffsets: vec3[][] = [ [ [0, 0, 0], @@ -163,7 +164,7 @@ class d_a_grass extends fopAc_ac_c { const pos = vec3.add(scratchVec3a, offset, this.pos); globals.scnPlay.grassPacket.newData(pos, this.roomNo, itemIdx); } - break; + break; case FoliageType.Tree: const rotation = mat4.fromYRotation(scratchMat4a, this.rot[1] / 0x7FFF * Math.PI); @@ -173,7 +174,7 @@ class d_a_grass extends fopAc_ac_c { const pos = vec3.add(scratchVec3b, offset, this.pos); globals.scnPlay.treePacket.newData(pos, 0, this.roomNo); } - break; + break; case FoliageType.WhiteFlower: case FoliageType.PinkFlower: @@ -185,7 +186,7 @@ class d_a_grass extends fopAc_ac_c { const pos = vec3.add(scratchVec3a, offset, this.pos); globals.scnPlay.flowerPacket.newData(globals, pos, isPink, this.roomNo, itemIdx); } - break; + break; default: console.warn('Unknown grass actor type'); } @@ -426,10 +427,10 @@ class d_a_bg extends fopAc_ac_c { const roomNo = this.parameters; const arcName = `Room` + roomNo; - const modelName = ['model.bmd', 'model1.bmd', 'model2.bmd', 'model3.bmd']; + const modelName = ['model.bmd', 'model1.bmd', 'model2.bmd', 'model3.bmd']; const modelName2 = ['model.bdl', 'model1.bdl', 'model2.bdl', 'model3.bdl']; - const btkName = ['model.btk', 'model1.btk', 'model2.btk', 'model3.btk']; - const brkName = ['model.brk', 'model1.brk', 'model2.brk', 'model3.brk']; + const btkName = ['model.btk', 'model1.btk', 'model2.btk', 'model3.btk']; + const brkName = ['model.brk', 'model1.brk', 'model2.brk', 'model3.brk']; // createHeap for (let i = 0; i < this.numBg; i++) { @@ -1151,7 +1152,7 @@ class d_a_obj_lpalm extends fopAc_ac_c { this.animDir[i] = cLib_addCalcAngleRad2(this.animDir[i], cM__Short2Rad(animDirTarget), cM__Short2Rad(0x04), cM__Short2Rad(0x20)); // Rock back and forth. - this.animWave[i] += cM__Short2Rad((windPow * 0x800) + cM_rndFX(0x80)) *deltaTimeFrames; + this.animWave[i] += cM__Short2Rad((windPow * 0x800) + cM_rndFX(0x80)) * deltaTimeFrames; const wave = Math.sin(this.animWave[i]); vec3.set(scratchVec3a, wave, 0, wave); @@ -2443,7 +2444,7 @@ class d_a_tori_flag extends fopAc_ac_c { const toonTex = resCtrl.getObjectRes(ResType.Bti, d_a_tori_flag.arcNameCloth, 0x03); const flagTex = resCtrl.getObjectRes(ResType.Bti, d_a_tori_flag.arcName, 0x07); this.cloth = new dCloth_packet_c(toonTex, flagTex, 5, 5, 210.0, 105.0, this.clothTevStr); - + vec3.copy(this.windvec, dKyw_get_wind_vec(globals.g_env_light)); this.set_mtx(); @@ -2593,9 +2594,9 @@ class d_a_majuu_flag extends fopAc_ac_c { for (let i = 0; i < this.pointCount; i++) { const dst = this.posArr[0][i]; - const x = posData[i*3+0]; - const y = posData[i*3+1]; - const z = posData[i*3+2]; + const x = posData[i * 3 + 0]; + const y = posData[i * 3 + 1]; + const z = posData[i * 3 + 2]; vec3.set(dst, x, y, z); if (!this.isPointFixed(i)) { @@ -2872,7 +2873,7 @@ class d_a_majuu_flag extends fopAc_ac_c { private majuu_flag_move(globals: dGlobals, deltaTimeFrames: number): void { this.wave += this.waveSpeed * deltaTimeFrames; - const windSpeed = lerp(this.windSpeed1, this.windSpeed2, Math.sin(cM__Short2Rad(this.wave)) * 0.5 + 0.5); + const windSpeed = lerp(this.windSpeed1, this.windSpeed2, Math.sin(cM__Short2Rad(this.wave)) * 0.5 + 0.5); const windpow = dKyw_get_wind_pow(globals.g_env_light); vec3.set(scratchVec3a, 0, 0, windSpeed * windpow * 2.0); @@ -2990,9 +2991,9 @@ class d_a_kamome extends fopAc_ac_c { if (status !== cPhs__Status.Complete) return status; - this.type = (this.parameters >>> 0x00) & 0xFF; - this.ko_count = (this.parameters >>> 0x08) & 0xFF; - this.path_arg = (this.parameters >>> 0x10) & 0xFF; + this.type = (this.parameters >>> 0x00) & 0xFF; + this.ko_count = (this.parameters >>> 0x08) & 0xFF; + this.path_arg = (this.parameters >>> 0x10) & 0xFF; this.switch_arg = (this.parameters >>> 0x18) & 0xFF; // createHeap @@ -4195,7 +4196,7 @@ class d_a_obj_flame extends fopAc_ac_c { this.extraScaleY = 1.0; const scale_xz = [1.0, 1.0, 1.0, 0.5][this.type]; - this.scaleY = [1.0, 0.815, 1.0, 0.5][this.type]; + this.scaleY = [1.0, 0.815, 1.0, 0.5][this.type]; this.scale[0] *= scale_xz; this.scale[1] *= this.extraScaleY * this.scaleY; this.scale[2] *= scale_xz; @@ -4204,17 +4205,17 @@ class d_a_obj_flame extends fopAc_ac_c { this.useSimpleEm = [true, false, false, false][this.type]; if (!this.useSimpleEm) { - const em01ScaleXZ = [-1, 13.0/3.0, 7.5, 0.5][this.type]; - const em01ScaleY = [-1, 2.716, 7.5, 0.5][this.type]; + const em01ScaleXZ = [-1, 13.0 / 3.0, 7.5, 0.5][this.type]; + const em01ScaleY = [-1, 2.716, 7.5, 0.5][this.type]; assert(em01ScaleXZ >= 0.0 && em01ScaleY >= 0.0); this.em01Scale = vec3.fromValues(em01ScaleXZ, this.extraScaleY * em01ScaleY, em01ScaleXZ); - const em2Scale = [-1, 0.866666, 1.0, 0.5][this.type]; + const em2Scale = [-1, 0.866666, 1.0, 0.5][this.type]; assert(em2Scale >= 0.0); this.em2Scale = vec3.fromValues(em2Scale, em2Scale, em2Scale); } - this.eyePosY = [1.0, 10.0/3.0, 7.5, 0.5][this.type]; + this.eyePosY = [1.0, 10.0 / 3.0, 7.5, 0.5][this.type]; this.bubblesParticleID = [0x805C, 0x808A, 0x808A, 0x805C][this.type]; // create_mode_init @@ -4711,6 +4712,226 @@ export class d_a_ff extends fopAc_ac_c { } } +class fopNpc_npc_c extends fopAc_ac_c { + protected morf: mDoExt_McaMorf; +}; + +class d_a_npc_ls1 extends fopNpc_npc_c { + public static PROCESS_NAME = fpc__ProcessName.d_a_npc_ls1; + private type: number; + private state = 0; + private animStopped: boolean; + private animTime: number; + private arcName = 'Ls'; + + private handModel: J3DModelInstance; + private btkAnim = new mDoExt_btkAnm(); + private btpAnim = new mDoExt_btpAnm(); + private btkFrame: number; + private btpFrame: number; + + private actionFunc: () => void; + + public override subload(globals: dGlobals): cPhs__Status { + const success = this.decideType(this.parameters); + if (!success) { return cPhs__Status.Error; } + + const status = dComIfG_resLoad(globals, this.arcName); + if (status !== cPhs__Status.Complete) + return status; + + this.createModels(globals); + + this.cullMtx = this.morf.model.modelMatrix; + this.setCullSizeBox(-50.0, -20.0, -50.0, 50.0, 140.0, 50.0); + + this.createInit(); + + return cPhs__Status.Next; + } + + public override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { + super.draw(globals, renderInstManager, viewerInput); + + settingTevStruct(globals, LightType.Actor, this.pos, this.tevStr); + setLightTevColorType(globals, this.morf.model, this.tevStr, viewerInput.camera); + setLightTevColorType(globals, this.handModel, this.tevStr, viewerInput.camera); + + // this.btkAnim.entry(this.morf.model, this.btkFrame); + // this.btpAnim.entry(this.morf.model, this.btpFrame); + + this.morf.entryDL(globals, renderInstManager, viewerInput); + + // TODO: What are these doing? + // removeTexMtxAnimator(this.morf.model, this.btkAnim.anm); + // removeTexNoAnimator(this.morf.model, this.btpAnim.anm); + + // mDoExt_modelEntryDL(globals, this.handModel, renderInstManager, viewerInput); + + // if (this->mItemModel != (J3DModel *)0x0) { + // dScnKy_env_light_c::setLightTevColorType + // (&d_kankyo::g_env_light,this->mItemModel,&(this->parent).parent.tevStr); + // m_Do_ext::mDoExt_modelEntryDL(this->mItemModel); + // } + + this.drawShadow(); + } + + public override execute(globals: dGlobals, deltaTimeFrames: number): void { + // char cVar1; + // byte bVar2; + // int iVar3; + + if (this.demoActorID >= 0) { + // partner_search(this); + // checkOrder(this); + + this.demo(globals, deltaTimeFrames); + + } + + // TODO: Idle animation + + // TODO: Shadowing based on the triangle that the NPC is standing on + // this.tevStr.roomNo = this.roomNo; + // bVar2 = dBgS::GetPolyColor(&d_com_inf_game::g_dComIfG_gameInfo.play.mBgS, + // &(this->parent).mObjAcch.parent.mGndChk.parent.mPolyInfo); + // (this->parent).parent.tevStr.mEnvrIdxOverride = bVar2; + + this.setMtx(false); + } + + private decideType(pcParam: number) { + const isEventBit0x2A80Set = false; + + this.type = 0xFF; + + // Outset Island has two Ls1 actors in layer 0. One is type 0, the other is type 3. Based on the status of + // dSv_event_c::isEventBit(&d_com_inf_game::g_dComIfG_gameInfo.info.mSavedata.mEvent, 0x2a80), only one is created. + switch (pcParam) { + case 0: if (isEventBit0x2A80Set) this.type = 0; break; + case 1: this.type = 1; break; + case 2: this.type = 2; break; + case 3: if (!isEventBit0x2A80Set) this.type = 3; break; + case 4: this.type = 4; break; + } + + return this.type != 0xFF; + } + + private createInit() { + switch (this.type) { + case 0: this.actionFunc = this.waitAction1.bind(this); break; + // (this->parent).parent.actor_status = (this->parent).parent.actor_status & ~DoNotExecuteIfDidNotDraw; + case 1: this.actionFunc = this.demoAction1.bind(this); break; + case 2: this.actionFunc = this.demoAction1.bind(this); break; + case 3: this.actionFunc = this.waitAction1.bind(this); break; + case 4: this.actionFunc = this.demoAction1.bind(this); break; + } + + this.play_animation(1.0 / 30.0); + + this.tevStr.roomNo = this.roomNo + + this.morf.setMorf(0.0); + this.setMtx(true); + } + + private createModels(globals: dGlobals) { + this.createBody(globals); + this.createHand(globals); + this.createItem(globals); + } + + private createBody(globals: dGlobals) { + const modelData = globals.resCtrl.getObjectIDRes(ResType.Model, this.arcName, 0xd); + for (let i = 0; i < modelData.modelMaterialData.materialData!.length; i++) { + // Material anim setup + } + + this.morf = new mDoExt_McaMorf(modelData, null, null, null, LoopMode.Once, 1.0, 0, -1); + + // TODO: Find and assign joint idxs (Head, hands, etc) + } + + private createHand(globals: dGlobals) { + const modelData = globals.resCtrl.getObjectIDRes(ResType.Model, this.arcName, 0xc); + this.handModel = new J3DModelInstance(modelData); + + // TODO: Find and assign joint idxs + } + + private createItem(globals: dGlobals) { + // J3DModelData *pModel; + // ulong uVar1; + // J3DModel *pJVar2; + + // this->mItemModel = (J3DModel *)0x0; + // pModel = (J3DModelData *) + // dRes_control_c::getRes + // ("Link",0x2f,d_com_inf_game::g_dComIfG_gameInfo.res_control.mObjectInfo,0x40); + // if (pModel == (J3DModelData *)0x0) { + // uVar1 = JUTAssertion::getSDevice(); + // JUTAssertion::showAssert(uVar1,"d_a_npc_ls1.cpp",0xc43,"a_mdl_dat != 0"); + // m_Do_printf::OSPanic("d_a_npc_ls1.cpp",0xc43,&DAT_8076cc31); + // } + // pJVar2 = m_Do_ext::mDoExt_J3DModel__create(pModel,0x80000,0x11000022); + // this->mItemModel = pJVar2; + // return this->mItemModel != (J3DModel *)0x0; + } + + private waitAction1(): void { + switch (this.state) { + case 0: { + // @TODO + } break; + + case 1: + case 2: + case 3: + case 4: + case 5: + default: + assert(false, `Unhandled Ls1 state: ${this.state}`); + } + } + + private demoAction1(): void { + + } + + private play_animation(deltaTimeFrames: number) { + // play_btp_anm(this); + // play_btk_anm(this); + + this.animStopped = this.morf.play(deltaTimeFrames); + if (this.morf.frameCtrl.currentTimeInFrames < this.animTime) { + this.animStopped = true; + } + this.animTime = this.morf.frameCtrl.currentTimeInFrames; + } + + private setMtx(param: boolean) { + vec3.copy(this.morf.model.baseScale, this.scale); + MtxTrans(this.pos, false, calc_mtx); + mDoMtx_ZXYrotM(calc_mtx, this.rot); + mat4.copy(this.morf.model.modelMatrix, calc_mtx); + this.morf.calc(); + + // TODO: Item handling + } + + private drawShadow() { + // TODO + } + + private demo(globals: dGlobals, deltaTimeFrames: number) { + // TODO: Lots happening here + dDemo_setDemoData(globals, deltaTimeFrames, this, 0x6a, this.morf, this.arcName); + return 1; + } +} + interface constructor extends fpc_bs__Constructor { PROCESS_NAME: fpc__ProcessName; } @@ -4742,4 +4963,5 @@ export function d_a__RegisterConstructors(globals: fGlobals): void { R(d_a_oship); R(d_a_obj_flame); R(d_a_ff); + R(d_a_npc_ls1); } diff --git a/src/ZeldaWindWaker/framework.ts b/src/ZeldaWindWaker/framework.ts index 2ad00a22f..832bc05ae 100644 --- a/src/ZeldaWindWaker/framework.ts +++ b/src/ZeldaWindWaker/framework.ts @@ -30,6 +30,7 @@ export const enum fpc__ProcessName { d_a_obj_wood = 0x010C, d_a_obj_flame = 0x010D, d_a_tbox = 0x0126, + d_a_npc_ls1 = 0x0143, d_a_kytag00 = 0x0181, d_a_kytag01 = 0x0182, d_a_obj_zouK = 0x018F, From abac1fac0a28b1441fe06d5389537d8b32a30829 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 12 Nov 2024 20:32:00 -0700 Subject: [PATCH 071/102] Aryll: Create and parent hand models --- src/ZeldaWindWaker/LegacyActor.ts | 7 +++++ src/ZeldaWindWaker/d_a.ts | 44 ++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/ZeldaWindWaker/LegacyActor.ts b/src/ZeldaWindWaker/LegacyActor.ts index 616d67781..5dda4d26a 100644 --- a/src/ZeldaWindWaker/LegacyActor.ts +++ b/src/ZeldaWindWaker/LegacyActor.ts @@ -262,6 +262,13 @@ function spawnLegacyActor(globals: dGlobals, legacy: d_a_noclip_legacy, actor: f m.bindTTK1(parseBTK(rarc, `btk/gmjwp00.btk`)); }); // NPCs + // Aryll + else if (actorName === 'Ls' || actorName === 'Ls1') fetchArchive(`Ls`).then((rarc) => { + const m = buildModel(rarc, `bdlm/ls.bdl`); + buildChildModel(rarc, `bdl/lshand.bdl`).setParentJoint(m, `handL`); + buildChildModel(rarc, `bdl/lshand.bdl`).setParentJoint(m, `handR`); + m.bindANK1(parseBCK(rarc, `bcks/ls_wait01.bck`)); + }); // Beedle else if (actorName === 'Bs1') fetchArchive(`Bs`).then((rarc) => { const m = buildModel(rarc, `bdlm/bs.bdl`); diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 3dd7cfa06..7dd1019c6 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -1,5 +1,5 @@ -import { ReadonlyVec3, mat4, quat, vec2, vec3 } from "gl-matrix"; +import { ReadonlyMat4, ReadonlyVec3, mat4, quat, vec2, vec3 } from "gl-matrix"; import { TransparentBlack, colorCopy, colorFromRGBA8, colorNewCopy, colorNewFromRGBA8 } from "../Color.js"; import { J3DModelData, J3DModelInstance, buildEnvMtx } from "../Common/JSYSTEM/J3D/J3DGraphBase.js"; import { LoopMode, TRK1, TTK1 } from "../Common/JSYSTEM/J3D/J3DLoader.js"; @@ -4725,6 +4725,8 @@ class d_a_npc_ls1 extends fopNpc_npc_c { private arcName = 'Ls'; private handModel: J3DModelInstance; + private jointMtxHandL: ReadonlyMat4; + private jointMtxHandR: ReadonlyMat4; private btkAnim = new mDoExt_btkAnm(); private btpAnim = new mDoExt_btpAnm(); private btkFrame: number; @@ -4766,7 +4768,7 @@ class d_a_npc_ls1 extends fopNpc_npc_c { // removeTexMtxAnimator(this.morf.model, this.btkAnim.anm); // removeTexNoAnimator(this.morf.model, this.btpAnim.anm); - // mDoExt_modelEntryDL(globals, this.handModel, renderInstManager, viewerInput); + mDoExt_modelEntryDL(globals, this.handModel, renderInstManager, viewerInput); // if (this->mItemModel != (J3DModel *)0x0) { // dScnKy_env_light_c::setLightTevColorType @@ -4778,10 +4780,6 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } public override execute(globals: dGlobals, deltaTimeFrames: number): void { - // char cVar1; - // byte bVar2; - // int iVar3; - if (this.demoActorID >= 0) { // partner_search(this); // checkOrder(this); @@ -4851,14 +4849,23 @@ class d_a_npc_ls1 extends fopNpc_npc_c { this.morf = new mDoExt_McaMorf(modelData, null, null, null, LoopMode.Once, 1.0, 0, -1); - // TODO: Find and assign joint idxs (Head, hands, etc) + const jointIdxHandL = modelData.bmd.jnt1.joints.findIndex(j => j.name == 'handL'); + const jointIdxHandR = modelData.bmd.jnt1.joints.findIndex(j => j.name == 'handR'); + this.jointMtxHandL = this.morf.model.shapeInstanceState.jointToWorldMatrixArray[jointIdxHandL]; + this.jointMtxHandR = this.morf.model.shapeInstanceState.jointToWorldMatrixArray[jointIdxHandR]; } private createHand(globals: dGlobals) { const modelData = globals.resCtrl.getObjectIDRes(ResType.Model, this.arcName, 0xc); this.handModel = new J3DModelInstance(modelData); - // TODO: Find and assign joint idxs + const handJointIdxL = modelData.bmd.jnt1.joints.findIndex(j => j.name == 'ls_handL'); + const handJointIdxR = modelData.bmd.jnt1.joints.findIndex(j => j.name == 'ls_handR'); + + this.handModel.jointMatrixCalcCallback = (dst: mat4, modelData: J3DModelData, i: number): void => { + if (i == handJointIdxL) { mat4.copy(dst, this.jointMtxHandL); } + else if (i == handJointIdxR) { mat4.copy(dst, this.jointMtxHandR); } + } } private createItem(globals: dGlobals) { @@ -4918,7 +4925,26 @@ class d_a_npc_ls1 extends fopNpc_npc_c { mat4.copy(this.morf.model.modelMatrix, calc_mtx); this.morf.calc(); - // TODO: Item handling + this.handModel.calcAnim(); + + // TODO: + // if( this.itemModel ) { + // this.jointMtxHandR; + // if (this->mItemPosType == '\0') { + // mDoMtx_stack_c::transM(5.5,-3.0,-2.0); + // fVar1 = *(float *)&this->mItemScale; + // mDoMtx_stack_c::scaleM(fVar1,fVar1,fVar1); + // m_Do_mtx::mDoMtx_XYZrotM(&mDoMtx_stack_c::now,-0x1d27,0x3b05,-0x5c71); + // } + // else { + // mDoMtx_stack_c::transM(5.7,-17.5,-1.0); + // fVar1 = *(float *)&this->mItemScale; + // mDoMtx_stack_c::scaleM(fVar1,fVar1,fVar1); + // m_Do_mtx::mDoMtx_XYZrotM(&mDoMtx_stack_c::now,-0x1d27,0x3b05,-0x5c71); + // } + // mtx::PSMTXCopy(&mDoMtx_stack_c::now,&this->mItemModel->mBaseMtx); + // (*(code *)this->mItemModel->vtbl->calc)(); + // } } private drawShadow() { From 45c19d1d9bb68cf3aeecc9465587625a00481542 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 12 Nov 2024 21:06:30 -0700 Subject: [PATCH 072/102] Aryll: Added telescope and parented to hand --- src/ZeldaWindWaker/d_a.ts | 69 ++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 7dd1019c6..bc995089e 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4724,9 +4724,13 @@ class d_a_npc_ls1 extends fopNpc_npc_c { private animTime: number; private arcName = 'Ls'; + private itemPosType: number = 1; + private itemScale: number = 1.0; + private itemModel: J3DModelInstance; private handModel: J3DModelInstance; private jointMtxHandL: ReadonlyMat4; private jointMtxHandR: ReadonlyMat4; + private btkAnim = new mDoExt_btkAnm(); private btpAnim = new mDoExt_btpAnm(); private btkFrame: number; @@ -4738,7 +4742,12 @@ class d_a_npc_ls1 extends fopNpc_npc_c { const success = this.decideType(this.parameters); if (!success) { return cPhs__Status.Error; } - const status = dComIfG_resLoad(globals, this.arcName); + let status = dComIfG_resLoad(globals, this.arcName); + if (status !== cPhs__Status.Complete) + return status; + + // noclip modification: Aryll's telescope comes from the Link arc. Load it now + status = dComIfG_resLoad(globals, "Link"); if (status !== cPhs__Status.Complete) return status; @@ -4770,11 +4779,10 @@ class d_a_npc_ls1 extends fopNpc_npc_c { mDoExt_modelEntryDL(globals, this.handModel, renderInstManager, viewerInput); - // if (this->mItemModel != (J3DModel *)0x0) { - // dScnKy_env_light_c::setLightTevColorType - // (&d_kankyo::g_env_light,this->mItemModel,&(this->parent).parent.tevStr); - // m_Do_ext::mDoExt_modelEntryDL(this->mItemModel); - // } + if (this.itemModel) { + setLightTevColorType(globals, this.itemModel, this.tevStr, viewerInput.camera); + mDoExt_modelEntryDL(globals, this.itemModel, renderInstManager, viewerInput); + } this.drawShadow(); } @@ -4869,22 +4877,8 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } private createItem(globals: dGlobals) { - // J3DModelData *pModel; - // ulong uVar1; - // J3DModel *pJVar2; - - // this->mItemModel = (J3DModel *)0x0; - // pModel = (J3DModelData *) - // dRes_control_c::getRes - // ("Link",0x2f,d_com_inf_game::g_dComIfG_gameInfo.res_control.mObjectInfo,0x40); - // if (pModel == (J3DModelData *)0x0) { - // uVar1 = JUTAssertion::getSDevice(); - // JUTAssertion::showAssert(uVar1,"d_a_npc_ls1.cpp",0xc43,"a_mdl_dat != 0"); - // m_Do_printf::OSPanic("d_a_npc_ls1.cpp",0xc43,&DAT_8076cc31); - // } - // pJVar2 = m_Do_ext::mDoExt_J3DModel__create(pModel,0x80000,0x11000022); - // this->mItemModel = pJVar2; - // return this->mItemModel != (J3DModel *)0x0; + const modelData = globals.resCtrl.getObjectIDRes(ResType.Model, "Link", 0x2f); + this.itemModel = new J3DModelInstance(modelData); } private waitAction1(): void { @@ -4927,24 +4921,19 @@ class d_a_npc_ls1 extends fopNpc_npc_c { this.handModel.calcAnim(); - // TODO: - // if( this.itemModel ) { - // this.jointMtxHandR; - // if (this->mItemPosType == '\0') { - // mDoMtx_stack_c::transM(5.5,-3.0,-2.0); - // fVar1 = *(float *)&this->mItemScale; - // mDoMtx_stack_c::scaleM(fVar1,fVar1,fVar1); - // m_Do_mtx::mDoMtx_XYZrotM(&mDoMtx_stack_c::now,-0x1d27,0x3b05,-0x5c71); - // } - // else { - // mDoMtx_stack_c::transM(5.7,-17.5,-1.0); - // fVar1 = *(float *)&this->mItemScale; - // mDoMtx_stack_c::scaleM(fVar1,fVar1,fVar1); - // m_Do_mtx::mDoMtx_XYZrotM(&mDoMtx_stack_c::now,-0x1d27,0x3b05,-0x5c71); - // } - // mtx::PSMTXCopy(&mDoMtx_stack_c::now,&this->mItemModel->mBaseMtx); - // (*(code *)this->mItemModel->vtbl->calc)(); - // } + if(this.itemModel) { + mat4.copy(calc_mtx, this.jointMtxHandR); + if (this.itemPosType == 0) { + MtxTrans([5.5, -3.0, -2.0], true); + } + else { + MtxTrans([5.7,-17.5,-1.0], true); + } + scaleMatrix(calc_mtx, calc_mtx, this.itemScale, this.itemScale, this.itemScale); + mDoMtx_XYZrotM(calc_mtx, [-0x1d27, 0x3b05, -0x5c71]); + mat4.copy(this.itemModel.modelMatrix, calc_mtx); + this.itemModel.calcAnim(); + } } private drawShadow() { From f9956b646de4f3af1aa287d61100d755b28b3c30 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Tue, 12 Nov 2024 23:02:07 -0700 Subject: [PATCH 073/102] Aryll: Added some basic idle animation support --- src/ZeldaWindWaker/d_a.ts | 172 +++++++++++++++++++++++++++++++++----- 1 file changed, 152 insertions(+), 20 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index bc995089e..081c1cbb6 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4715,13 +4715,31 @@ export class d_a_ff extends fopAc_ac_c { class fopNpc_npc_c extends fopAc_ac_c { protected morf: mDoExt_McaMorf; }; +class anm_prm_c { + anmIdx: number; + nextPrmIdx: number; + morf: number; + playSpeed: number; + loopMode: number; +}; + +function dNpc_setAnmIDRes(globals: dGlobals, pMorf: mDoExt_McaMorf, loopMode: number, morf: number, speed: number, animResId: number, arcName: string): boolean { + if (pMorf) { + const pAnimRes = globals.resCtrl.getObjectIDRes(ResType.Bck, arcName, animResId); + pMorf.setAnm(pAnimRes, loopMode, morf, speed, 0.0, -1.0); + return true; + } + return false; +} class d_a_npc_ls1 extends fopNpc_npc_c { public static PROCESS_NAME = fpc__ProcessName.d_a_npc_ls1; private type: number; private state = 0; + private animIdx = -1; private animStopped: boolean; private animTime: number; + private idleCountdown: number; private arcName = 'Ls'; private itemPosType: number = 1; @@ -4736,6 +4754,14 @@ class d_a_npc_ls1 extends fopNpc_npc_c { private btkFrame: number; private btpFrame: number; + private static bckIdxTable = [5, 6, 7, 8, 9, 10, 11, 2, 4, 3, 1, 1, 1, 0] + private static animParamsTable: anm_prm_c[] = [ + { anmIdx: 0xFF, nextPrmIdx: 0xFF, morf: 0.0, playSpeed: 0.0, loopMode: 0xFFFFFFFF, }, + { anmIdx: 0, nextPrmIdx: 1, morf: 8.0, playSpeed: 1.0, loopMode: 2, }, + { anmIdx: 5, nextPrmIdx: 1, morf: 8.0, playSpeed: 1.0, loopMode: 2, }, + { anmIdx: 10, nextPrmIdx: 2, morf: 8.0, playSpeed: 1.0, loopMode: 2, }, + { anmIdx: 5, nextPrmIdx: 1, morf: 8.0, playSpeed: 1.0, loopMode: 2, }]; + private actionFunc: () => void; public override subload(globals: dGlobals): cPhs__Status { @@ -4756,7 +4782,7 @@ class d_a_npc_ls1 extends fopNpc_npc_c { this.cullMtx = this.morf.model.modelMatrix; this.setCullSizeBox(-50.0, -20.0, -50.0, 50.0, 140.0, 50.0); - this.createInit(); + this.createInit(globals); return cPhs__Status.Next; } @@ -4788,16 +4814,17 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } public override execute(globals: dGlobals, deltaTimeFrames: number): void { - if (this.demoActorID >= 0) { + if (true || this.demoActorID >= 0) { // partner_search(this); // checkOrder(this); - this.demo(globals, deltaTimeFrames); - + const isDemo = this.demo(globals, deltaTimeFrames); + if (!isDemo) { + this.actionFunc(); + this.play_animation(deltaTimeFrames); + } } - // TODO: Idle animation - // TODO: Shadowing based on the triangle that the NPC is standing on // this.tevStr.roomNo = this.roomNo; // bVar2 = dBgS::GetPolyColor(&d_com_inf_game::g_dComIfG_gameInfo.play.mBgS, @@ -4825,14 +4852,13 @@ class d_a_npc_ls1 extends fopNpc_npc_c { return this.type != 0xFF; } - private createInit() { + private createInit(globals: dGlobals) { switch (this.type) { - case 0: this.actionFunc = this.waitAction1.bind(this); break; - // (this->parent).parent.actor_status = (this->parent).parent.actor_status & ~DoNotExecuteIfDidNotDraw; - case 1: this.actionFunc = this.demoAction1.bind(this); break; - case 2: this.actionFunc = this.demoAction1.bind(this); break; - case 3: this.actionFunc = this.waitAction1.bind(this); break; - case 4: this.actionFunc = this.demoAction1.bind(this); break; + case 0: this.actionFunc = this.waitAction1.bind(this, globals); break; + case 1: this.actionFunc = this.demoAction1.bind(this, globals); break; + case 2: this.actionFunc = this.demoAction1.bind(this, globals); break; + case 3: this.actionFunc = this.waitAction1.bind(this, globals); break; + case 4: this.actionFunc = this.demoAction1.bind(this, globals); break; } this.play_animation(1.0 / 30.0); @@ -4881,13 +4907,80 @@ class d_a_npc_ls1 extends fopNpc_npc_c { this.itemModel = new J3DModelInstance(modelData); } - private waitAction1(): void { + private wait1(globals: dGlobals) { + // char cVar3; + // short sVar1; + // s16 sVar2; + // cXyz local_18; + + // if ((char)this->field_0x850 < '\x03') { + // this->field_0x850 = 0; + // local_18.x = (this->parent).parent.current.pos.x; + // local_18.y = (this->parent).parent.current.pos.y; + // local_18.z = (this->parent).parent.current.pos.z; + // cVar3 = chk_areaIN(this,DAT_8076d39c,100.0,0x7fff,&local_18); + // if (cVar3 != '\0') { + // this->field_0x850 = 4; + // } + // } + + // this->field_0x853 = 0; + + if (this.animIdx == 0) { + const idleCountdown = Math.max(--this.idleCountdown, 0); + if (idleCountdown == 0) { + this.setAnm_NUM(globals, 1, true); + } + } + else if (this.animStopped) { + this.idleCountdown = Math.random() * 30 + 60; + this.setAnm_NUM(globals, 0, true); + } + return 1; + } + + private waitAction1(globals: dGlobals): void { + const isEventBit0x2A80Set = false; + switch (this.state) { case 0: { - // @TODO + // get_playerEvnPos((daNpc_Ls1_c *)&local_1c,(int)this); + // *(uint *)&this->field_0x7cc = local_1c; + // this->mPcProfile = local_18; + // this->mCreateRequest = local_14; + // get_playerEvnPos((daNpc_Ls1_c *)&local_28,(int)this); + // *(uint *)&this->field_0x7d8 = local_28; + // *(uint *)&this->field_0x7dc = local_24; + // *(short *)&this->field_0x7e0 = local_20; + // this->field_0x7e2 = bStack_1e; + // this->field_0x7e3 = bStack_1d; + this.state += 1; + // iVar1 = dSv_event_c::isEventBit + // (&d_com_inf_game::g_dComIfG_gameInfo.info.mSavedata.mEvent,0x2a80); + if (!isEventBit0x2A80Set) { + this.setStt(globals, 4); + } + else { + // iVar1 = dSv_event_c::isEventBit(&d_com_inf_game::g_dComIfG_gameInfo.info.mSavedata.mEvent,1) + // ; + // if (iVar1 == 0) { + // bVar3 = d_com_inf_game::dComIfGs_checkGetItem(Telescope); + // if (bVar3) { + // this->mRotY = (this->parent).parent.current.angle.y; + // setStt(this,'\x02'); + // } + // else { + // setStt(this,'\x01'); + // } + // } + // else { + // setStt(this,'\x03'); + // } + } } break; - case 1: + case 1: this.wait1(globals); break; + case 2: case 3: case 4: @@ -4897,8 +4990,45 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } } - private demoAction1(): void { + private demoAction1(globals: dGlobals): void { + + } + + private setAnim(globals: dGlobals, param: anm_prm_c) { + if (param.anmIdx > -1 && this.animIdx != param.anmIdx) { + const bckID = d_a_npc_ls1.bckIdxTable[param.anmIdx]; + dNpc_setAnmIDRes(globals, this.morf, param.loopMode, param.morf, param.playSpeed, bckID, this.arcName); + this.animIdx = param.anmIdx; + this.animStopped = false; + this.animTime = 0; + } + } + + private setAnm_NUM(globals: dGlobals, animIdx: number, hasTexAnim: boolean) { + if (hasTexAnim) { + // TODO: Facial texture animations + } + this.setAnim(globals, d_a_npc_ls1.animParamsTable[animIdx + 1]); + } + + private setStt(globals: dGlobals, animIdx: number) { + this.animIdx = animIdx; + switch (animIdx) { + case 1: + this.idleCountdown = 60 + Math.random() * 30; + break; + + case 4: + this.itemScale = 1.0; + break; + case 2: + case 3: + case 5: + default: + console.warn('Unsupport Ls1 animIdx'); + } + this.setAnim(globals, d_a_npc_ls1.animParamsTable[this.animIdx]); } private play_animation(deltaTimeFrames: number) { @@ -4921,13 +5051,13 @@ class d_a_npc_ls1 extends fopNpc_npc_c { this.handModel.calcAnim(); - if(this.itemModel) { + if (this.itemModel) { mat4.copy(calc_mtx, this.jointMtxHandR); if (this.itemPosType == 0) { MtxTrans([5.5, -3.0, -2.0], true); } else { - MtxTrans([5.7,-17.5,-1.0], true); + MtxTrans([5.7, -17.5, -1.0], true); } scaleMatrix(calc_mtx, calc_mtx, this.itemScale, this.itemScale, this.itemScale); mDoMtx_XYZrotM(calc_mtx, [-0x1d27, 0x3b05, -0x5c71]); @@ -4941,9 +5071,11 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } private demo(globals: dGlobals, deltaTimeFrames: number) { + if (this.demoActorID < 0) { return false; } + // TODO: Lots happening here dDemo_setDemoData(globals, deltaTimeFrames, this, 0x6a, this.morf, this.arcName); - return 1; + return true; } } From b7b98d8f92092c55cb928706e827ed1093aa3606 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 13 Nov 2024 09:10:33 -0700 Subject: [PATCH 074/102] d_demo: Fix issue with actors not clearing their demoActorID when demo ends --- src/ZeldaWindWaker/d_demo.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index e1341c15c..0d13299ff 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -169,15 +169,12 @@ class dDemo_actor_c extends TActor { mModel: J3DModelInstance; stbDataId: number; stbData: DataView; - mActorPcId: number; mBckId: number; mBtpId: number; mBtkId: number; mBrkId: number; - constructor(actor: fopAc_ac_c) { - super(); - } + constructor(public mActor: fopAc_ac_c) { super(); } checkEnable(mask: number) { return this.mFlags & mask; @@ -218,7 +215,7 @@ class dDemo_actor_c extends TActor { override JSGGetTranslation(dst: vec3) { vec3.copy(dst, this.mTranslation); } override JSGGetScaling(dst: vec3) { vec3.copy(dst, this.mScaling); } - override JSGGetRotation(dst: vec3) { + override JSGGetRotation(dst: vec3) { dst[0] = cM_sht2d(this.mRotation[0]); dst[1] = cM_sht2d(this.mRotation[1]); dst[2] = cM_sht2d(this.mRotation[2]); @@ -329,6 +326,8 @@ class dDemo_system_c implements TSystem { public remove() { this.mpActiveCamera = undefined; + + for (let demoActor of this.mpActors) { demoActor.mActor.demoActorID = -1; } this.mpActors = []; } } From 4dcaf0fc645f2a2449019b8badb27eea3c59b1ad Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 13 Nov 2024 09:13:02 -0700 Subject: [PATCH 075/102] Aryll: Simplified the idle animation logic Now we just set the anim during create, and don't bother with the NPC state management logic --- src/ZeldaWindWaker/d_a.ts | 139 +++----------------------------------- 1 file changed, 9 insertions(+), 130 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 081c1cbb6..1d0fbaf93 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4762,8 +4762,6 @@ class d_a_npc_ls1 extends fopNpc_npc_c { { anmIdx: 10, nextPrmIdx: 2, morf: 8.0, playSpeed: 1.0, loopMode: 2, }, { anmIdx: 5, nextPrmIdx: 1, morf: 8.0, playSpeed: 1.0, loopMode: 2, }]; - private actionFunc: () => void; - public override subload(globals: dGlobals): cPhs__Status { const success = this.decideType(this.parameters); if (!success) { return cPhs__Status.Error; } @@ -4815,12 +4813,8 @@ class d_a_npc_ls1 extends fopNpc_npc_c { public override execute(globals: dGlobals, deltaTimeFrames: number): void { if (true || this.demoActorID >= 0) { - // partner_search(this); - // checkOrder(this); - const isDemo = this.demo(globals, deltaTimeFrames); if (!isDemo) { - this.actionFunc(); this.play_animation(deltaTimeFrames); } } @@ -4853,14 +4847,7 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } private createInit(globals: dGlobals) { - switch (this.type) { - case 0: this.actionFunc = this.waitAction1.bind(this, globals); break; - case 1: this.actionFunc = this.demoAction1.bind(this, globals); break; - case 2: this.actionFunc = this.demoAction1.bind(this, globals); break; - case 3: this.actionFunc = this.waitAction1.bind(this, globals); break; - case 4: this.actionFunc = this.demoAction1.bind(this, globals); break; - } - + this.setAnim(globals, 2, false); this.play_animation(1.0 / 30.0); this.tevStr.roomNo = this.roomNo @@ -4907,128 +4894,20 @@ class d_a_npc_ls1 extends fopNpc_npc_c { this.itemModel = new J3DModelInstance(modelData); } - private wait1(globals: dGlobals) { - // char cVar3; - // short sVar1; - // s16 sVar2; - // cXyz local_18; - - // if ((char)this->field_0x850 < '\x03') { - // this->field_0x850 = 0; - // local_18.x = (this->parent).parent.current.pos.x; - // local_18.y = (this->parent).parent.current.pos.y; - // local_18.z = (this->parent).parent.current.pos.z; - // cVar3 = chk_areaIN(this,DAT_8076d39c,100.0,0x7fff,&local_18); - // if (cVar3 != '\0') { - // this->field_0x850 = 4; - // } - // } - - // this->field_0x853 = 0; - - if (this.animIdx == 0) { - const idleCountdown = Math.max(--this.idleCountdown, 0); - if (idleCountdown == 0) { - this.setAnm_NUM(globals, 1, true); - } - } - else if (this.animStopped) { - this.idleCountdown = Math.random() * 30 + 60; - this.setAnm_NUM(globals, 0, true); - } - return 1; - } - - private waitAction1(globals: dGlobals): void { - const isEventBit0x2A80Set = false; - - switch (this.state) { - case 0: { - // get_playerEvnPos((daNpc_Ls1_c *)&local_1c,(int)this); - // *(uint *)&this->field_0x7cc = local_1c; - // this->mPcProfile = local_18; - // this->mCreateRequest = local_14; - // get_playerEvnPos((daNpc_Ls1_c *)&local_28,(int)this); - // *(uint *)&this->field_0x7d8 = local_28; - // *(uint *)&this->field_0x7dc = local_24; - // *(short *)&this->field_0x7e0 = local_20; - // this->field_0x7e2 = bStack_1e; - // this->field_0x7e3 = bStack_1d; - this.state += 1; - // iVar1 = dSv_event_c::isEventBit - // (&d_com_inf_game::g_dComIfG_gameInfo.info.mSavedata.mEvent,0x2a80); - if (!isEventBit0x2A80Set) { - this.setStt(globals, 4); - } - else { - // iVar1 = dSv_event_c::isEventBit(&d_com_inf_game::g_dComIfG_gameInfo.info.mSavedata.mEvent,1) - // ; - // if (iVar1 == 0) { - // bVar3 = d_com_inf_game::dComIfGs_checkGetItem(Telescope); - // if (bVar3) { - // this->mRotY = (this->parent).parent.current.angle.y; - // setStt(this,'\x02'); - // } - // else { - // setStt(this,'\x01'); - // } - // } - // else { - // setStt(this,'\x03'); - // } - } - } break; - - case 1: this.wait1(globals); break; - - case 2: - case 3: - case 4: - case 5: - default: - assert(false, `Unhandled Ls1 state: ${this.state}`); - } - } - - private demoAction1(globals: dGlobals): void { - - } - - private setAnim(globals: dGlobals, param: anm_prm_c) { - if (param.anmIdx > -1 && this.animIdx != param.anmIdx) { - const bckID = d_a_npc_ls1.bckIdxTable[param.anmIdx]; - dNpc_setAnmIDRes(globals, this.morf, param.loopMode, param.morf, param.playSpeed, bckID, this.arcName); - this.animIdx = param.anmIdx; - this.animStopped = false; - this.animTime = 0; - } - } - - private setAnm_NUM(globals: dGlobals, animIdx: number, hasTexAnim: boolean) { + private setAnim(globals: dGlobals, animIdx: number, hasTexAnim: boolean) { if (hasTexAnim) { // TODO: Facial texture animations } - this.setAnim(globals, d_a_npc_ls1.animParamsTable[animIdx + 1]); - } - - private setStt(globals: dGlobals, animIdx: number) { - this.animIdx = animIdx; - switch (animIdx) { - case 1: - this.idleCountdown = 60 + Math.random() * 30; - break; - case 4: - this.itemScale = 1.0; - break; + const params = d_a_npc_ls1.animParamsTable[animIdx]; - case 2: - case 3: - case 5: - default: - console.warn('Unsupport Ls1 animIdx'); + if (params.anmIdx > -1 && this.animIdx != params.anmIdx) { + const bckID = d_a_npc_ls1.bckIdxTable[params.anmIdx]; + dNpc_setAnmIDRes(globals, this.morf, params.loopMode, params.morf, params.playSpeed, bckID, this.arcName); + this.animIdx = params.anmIdx; + this.animStopped = false; + this.animTime = 0; } - this.setAnim(globals, d_a_npc_ls1.animParamsTable[this.animIdx]); } private play_animation(deltaTimeFrames: number) { From 621498a0daee7faad1a18d49f323c84a030137ab Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 13 Nov 2024 09:22:17 -0700 Subject: [PATCH 076/102] JStudio: Small cleanup --- src/Common/JSYSTEM/JStudio.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index dc62fee72..2eb0884e2 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -62,7 +62,7 @@ export interface TSystem { // TVariableValue // Manages a single float, which will be updated each frame. This float can be updated using a variety of operations: // - Immediate(x): Set to single value. On Update(y), set the value to a single number then do nothing on future frames. -// - Time(x): Increase over time. On Update(y), set the value to x * y * mAge. +// - Time(x): Increase over time, mValue is the velocity. On Update(y), set the value to mValue * dt * mAge. // - FuncVal(x): Set to the output of a functor. See FVB for details. // // Normally, after update() the value can be retrieved from mValue(). Alternatively, if setOutput() is called that @@ -1815,16 +1815,12 @@ enum EStatus { Inactive = 1 << 3, } -// The runtime object that stores interpolated data each frame. Written to by a TAdapter. By default, the base object -// is empty and can get or set no data. JSystem expects each game to provide implementations of the TObject interface -// which are provided by a JStage:TSystem override export abstract class TBlockObject { - /* 0x0 */ size: number; - /* 0x4 */ type: string; // char[4] JMSG, JSND, JACT, ... - /* 0x8 */ flag: number; - /* 0xA id_size: number* - /* 0xC */ id: string; - /* 0xC + align(id_size, 4) */ data: Reader; + size: number; + type: string; // char[4] JMSG, JSND, JACT, ... + flag: number; + id: string; + data: Reader; } // This combines JStudio::TControl and JStudio::stb::TControl into a single class, for simplicity. From 83dc13cf2269120b9ae8d0813c9c031368c4ce24 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 13 Nov 2024 13:19:42 -0700 Subject: [PATCH 077/102] JStudio: Fix demo asserting due to uninitialized animMode --- src/Common/JSYSTEM/JStudio.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 2eb0884e2..e11ae2f61 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -598,8 +598,8 @@ class TActorAdaptor extends TAdaptor { public parentNodeID: number; public relation?: JStage.TObject; public relationNodeID: number; - public animMode: number; // See computeAnimFrame() - public animTexMode: number; // See computeAnimFrame() + public animMode: number = 0; // See computeAnimFrame() + public animTexMode: number = 0; // See computeAnimFrame() constructor( private mSystem: TSystem, @@ -607,7 +607,7 @@ class TActorAdaptor extends TAdaptor { ) { super(14); } private static computeAnimFrame(animMode: number, maxFrame: number, frame: number) { - const outsideType = animMode; + const outsideType = animMode & 0xFF; const reverse = animMode >> 8; if (reverse) { frame = maxFrame - frame; } From ccb08a53c8b799687ecd1710e7a8d39b455a0073 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 13 Nov 2024 13:47:29 -0700 Subject: [PATCH 078/102] JStudio: Fix Hermite interpolation The "Stolen Sister" cutscene in Outset can now complete --- src/Common/JSYSTEM/JStudio.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index e11ae2f61..d25ee8441 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -1427,7 +1427,6 @@ namespace FVB { prepare() { } setData(value: number) { this.value = value; } getValue(timeSec: number) { - debugger; // Untested. Remove once confirmed working return this.value; } } @@ -1751,10 +1750,10 @@ namespace FVB { setData(values: Float32Array, stride: number) { assert(stride == 3 || stride == 4); + this.stride = stride this.keys = values; - this.keyCount = values.length / this.stride; + this.keyCount = values.length / stride; this.curKeyIdx = 0; - this.stride = stride } getType() { return EFuncValType.ListParameter; } @@ -1762,7 +1761,7 @@ namespace FVB { getEndTime(): number { return this.keys[(this.keyCount - 1) * this.stride]; } getValue(timeSec: number): number { - debugger; // Untested. Remove after confirmed working. + // @TODO: Support range parameters like Outside // Remap (if requested) the time to our range const t = this.range.getParameter(timeSec, this.getStartTime(), this.getEndTime()); @@ -1778,7 +1777,7 @@ namespace FVB { } const ks = this.keys; - const c = this.curKeyIdx; + const c = this.curKeyIdx * this.stride; const l = c - this.stride; const value = Attribute.Interpolate.Hermite( t, ks[l + 0], ks[l + 1], ks[l + this.stride - 1], ks[c + 0], ks[c + 1], ks[c + 2]); From d4c18c4513212c2398b347e46fba15651209dd63 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 13 Nov 2024 13:53:56 -0700 Subject: [PATCH 079/102] Remove frameStart offset from the Awaken demo This was used for debugging --- src/ZeldaWindWaker/Main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index bab98261d..079591b12 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -1024,7 +1024,7 @@ class DemoDesc { } const demoDescs = [ - new DemoDesc("sea", "Awaken", "awake.stb", 44, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0, 525), + new DemoDesc("sea", "Awaken", "awake.stb", 44, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), new DemoDesc("sea", "Stolen Sister", "stolensister.stb", 44, 9, [0.0, 0.0, 20000.0], 0, 0, 0), new DemoDesc("sea", "Departure", "departure.stb", 44, 10, [-200000.0, 0.0, 320000.0], 0.0, 204, 0), new DemoDesc("sea", "Pirate Zelda Fly", "kaizoku_zelda_fly.stb", 44, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), From 98f38297167f72027e7dd0341750edf2e673c7e5 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 13 Nov 2024 15:29:36 -0700 Subject: [PATCH 080/102] New icon for the "Cutscenes" UI element (a play button) --- src/ZeldaWindWaker/Main.ts | 2 +- src/ui.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 079591b12..42c369f8c 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -369,7 +369,7 @@ export class WindWakerRenderer implements Viewer.SceneGfx { const demosPanel = new UI.Panel(); demosPanel.customHeaderBackgroundColor = UI.COOL_BLUE_COLOR; - demosPanel.setTitle(UI.LAYER_ICON, 'Cutscenes'); + demosPanel.setTitle(UI.CUTSCENE_ICON, 'Cutscenes'); const demoSelect = new UI.SingleSelect(); demoSelect.setStrings(this.demos.map(d => d.name)); demoSelect.onselectionchange = (idx: number) => { diff --git a/src/ui.ts b/src/ui.ts index 7ebf3cf15..18e5fe5d6 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -55,6 +55,7 @@ export const TIME_OF_DAY_ICON = ``; export const SAND_CLOCK_ICON = ''; export const VR_ICON = ``; +export const CUTSCENE_ICON = ` ` export function setChildren(parent: Element, children: Element[]): void { // We want to swap children around without removing them, since removing them will cause From 8939ad81b7d5aeb24040aa6269e0074754c6fb40 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 14 Nov 2024 06:25:02 -0700 Subject: [PATCH 081/102] d_demo: Added camera roll support --- src/ZeldaWindWaker/Main.ts | 7 ++++--- src/ZeldaWindWaker/d_demo.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 42c369f8c..08a497ede 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -491,18 +491,19 @@ export class WindWakerRenderer implements Viewer.SceneGfx { let viewPos = this.globals.cameraPosition; let targetPos = vec3.add(scratchVec3a, this.globals.cameraPosition, this.globals.cameraFwd); let upVec = vec3.set(scratchVec3b, 0, 1, 0); + let roll = 0.0; if(demoCam.mFlags & EDemoCamFlags.HasTargetPos) { targetPos = demoCam.mTargetPosition; } if(demoCam.mFlags & EDemoCamFlags.HasEyePos) { viewPos = demoCam.mViewPosition; } if(demoCam.mFlags & EDemoCamFlags.HasUpVec) { upVec = demoCam.mUpVector; } - mat4.targetTo(viewerInput.camera.worldMatrix, viewPos, targetPos, upVec); - if(demoCam.mFlags & EDemoCamFlags.HasFovY) { viewerInput.camera.fovY = demoCam.mFovy * MathConstants.DEG_TO_RAD; } - if(demoCam.mFlags & EDemoCamFlags.HasRoll) { if(demoCam.mRoll > 0) debugger; /* Untested. Remove once confirmed working */ } + if(demoCam.mFlags & EDemoCamFlags.HasRoll) { roll = demoCam.mRoll * MathConstants.DEG_TO_RAD; } if(demoCam.mFlags & EDemoCamFlags.HasAspect) { debugger; /* Untested. Remove once confirmed working */ } if(demoCam.mFlags & EDemoCamFlags.HasNearZ) { viewerInput.camera.near = demoCam.mProjNear; } if(demoCam.mFlags & EDemoCamFlags.HasFarZ) { viewerInput.camera.far = demoCam.mProjFar; } + mat4.targetTo(viewerInput.camera.worldMatrix, viewPos, targetPos, upVec); + mat4.rotateZ(viewerInput.camera.worldMatrix, viewerInput.camera.worldMatrix, roll); viewerInput.camera.setClipPlanes(viewerInput.camera.near, viewerInput.camera.far); viewerInput.camera.worldMatrixUpdated(); } diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 0d13299ff..9f2436f1b 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -142,7 +142,7 @@ class dDemo_camera_c extends TCamera { const camera = this.globals.camera; if (!camera) return 0.0; - return 0.0; // @TODO + return this.mRoll; // HACK: Instead of actually computing roll (complicated), just assume no one else is modifying it } From 83fb861c5e1b9c2863d4b03aa2185da36e6484c7 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 14 Nov 2024 06:26:34 -0700 Subject: [PATCH 082/102] JStudio: disable logging by default --- src/Common/JSYSTEM/JStudio.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index d25ee8441..e7794d2e7 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -228,7 +228,7 @@ abstract class TAdaptor { constructor( public mCount: number, public mVariableValues = nArray(mCount, i => new TVariableValue()), - public mEnableLogging = true, + public mEnableLogging = false, ) { } abstract adaptor_do_prepare(obj: STBObject): void; From b0c82a50d43765814d8f577f893aefc3cee4fcc6 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 14 Nov 2024 07:19:36 -0700 Subject: [PATCH 083/102] Autoplay demo if it is added to the SceneDesc --- src/ZeldaWindWaker/Main.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 08a497ede..f448e6e96 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -845,7 +845,7 @@ class d_s_play extends fopScn { class SceneDesc { public id: string; - public constructor(public stageDir: string, public name: string, public roomList: number[] = [0], public demo?: DemoDesc) { + public constructor(public stageDir: string, public name: string, public roomList: number[] = [0], public autoplayDemo?: DemoDesc) { this.id = stageDir; // Garbage hack. @@ -971,6 +971,9 @@ class SceneDesc { globals.renderer.demos = demoDescs.filter(d => d.stage == globals.stageName && (d.roomNo == -1 || this.roomList.includes(d.roomNo))); + // If requested, automatically start playing a demo + if(this.autoplayDemo) { this.autoplayDemo.load(globals); } + return renderer; } } @@ -1011,8 +1014,15 @@ class DemoDesc { } } + // noclip modification: ensure all the actors are created before we load the cutscene (in case we're auto-playing) + 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 + // From dEvDtStaff_c::specialProcPackage() let demoData; if(globals.roomCtrl.demoArcName) demoData = globals.modelCache.resCtrl.getObjectResByName(ResType.Stb, globals.roomCtrl.demoArcName, this.stbFilename); From 7beebc5a347ba1c2611e36ffa56ceb9387d74b38 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 15 Nov 2024 08:33:05 -0700 Subject: [PATCH 084/102] d_demo: Add exported ActorFlags enum --- src/ZeldaWindWaker/d_demo.ts | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 9f2436f1b..d77f07120 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -152,7 +152,19 @@ class dDemo_camera_c extends TCamera { } } -class dDemo_actor_c extends TActor { +export const enum EDemoActorFlags { + HasData = 1 << 0, + HasPos = 1 << 1, + HasScale = 1 << 2, + HasRot = 1 << 3, + HasShape = 1 << 4, + HasAnim = 1 << 5, + HasFrame = 1 << 6, + HasTexAnim = 1 << 7, + HasTexFrame = 1 << 8, +} + +export class dDemo_actor_c extends TActor { mName: string; mFlags: number; mTranslation = vec3.create(); @@ -224,55 +236,55 @@ class dDemo_actor_c extends TActor { override JSGSetData(id: number, data: DataView): void { this.stbDataId = id; this.stbData = data; // @TODO: Check that data makes sense - this.mFlags |= 0x01; + this.mFlags |= EDemoActorFlags.HasData; } override JSGSetTranslation(src: ReadonlyVec3) { vec3.copy(this.mTranslation, src); - this.mFlags |= 0x02; + this.mFlags |= EDemoActorFlags.HasPos; } override JSGSetScaling(src: ReadonlyVec3) { vec3.copy(this.mScaling, src); - this.mFlags |= 0x04; + this.mFlags |= EDemoActorFlags.HasScale; } override JSGSetRotation(src: ReadonlyVec3) { this.mRotation[0] = cM_deg2s(src[0]); this.mRotation[1] = cM_deg2s(src[1]); this.mRotation[2] = cM_deg2s(src[2]); - this.mFlags |= 0x08; + this.mFlags |= EDemoActorFlags.HasRot; } override JSGSetShape(id: number): void { this.mShapeId = id; - this.mFlags |= 0x10; + this.mFlags |= EDemoActorFlags.HasShape } override JSGSetAnimation(id: number): void { this.mNextBckId = id; this.mAnimationFrameMax = 3.402823e+38; - this.mFlags |= 0x20; + this.mFlags |= EDemoActorFlags.HasAnim; } override JSGSetAnimationFrame(x: number): void { this.mAnimationFrame = x; - this.mFlags |= 0x40; + this.mFlags |= EDemoActorFlags.HasFrame; } override JSGSetAnimationTransition(x: number): void { this.mAnimationTransition = x; - this.mFlags |= 0x40; + this.mFlags |= EDemoActorFlags.HasFrame; } override JSGSetTextureAnimation(id: number): void { this.mTexAnimation = id; - this.mFlags |= 0x80; + this.mFlags |= EDemoActorFlags.HasTexAnim; } override JSGSetTextureAnimationFrame(x: number): void { this.mTexAnimationFrame = x; - this.mFlags |= 0x100; + this.mFlags |= EDemoActorFlags.HasTexFrame; } } From 56de067a68a1c2cb2b02cd83bc1d7ea409e64d3c Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 07:44:20 -0700 Subject: [PATCH 085/102] JStudio: Remove all 'm' prefixes from member names --- src/Common/JSYSTEM/JStudio.ts | 494 +++++++++++++++++----------------- 1 file changed, 247 insertions(+), 247 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index e7794d2e7..71a07f609 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -69,27 +69,27 @@ export interface TSystem { // functor will be called during update(). //---------------------------------------------------------------------------------------------------------------------- class TVariableValue { - private mValue: number; - private mAge: number; // In frames - private mUpdateFunc?: (varval: TVariableValue, x: number) => void; - private mUpdateParam: number | FVB.TFunctionValue | undefined; - private mOutputFunc?: (val: number, adaptor: TAdaptor) => void; + private value: number; + private age: number; // In frames + private updateFunc?: (varval: TVariableValue, x: number) => void; + private updateParam: number | FVB.TFunctionValue | undefined; + private outputFunc?: (val: number, adaptor: TAdaptor) => void; - getValue() { return this.mValue; } - getValueU8() { return clamp(this.mValue, 0, 255); } + getValue() { return this.value; } + getValueU8() { return clamp(this.value, 0, 255); } forward(frameCount: number) { - if (Number.MAX_VALUE - this.mAge <= frameCount) { - this.mAge = Number.MAX_VALUE; + if (Number.MAX_VALUE - this.age <= frameCount) { + this.age = Number.MAX_VALUE; } else { - this.mAge += frameCount; + this.age += frameCount; } } update(secondsPerFrame: number, adaptor: TAdaptor): void { - if (this.mUpdateFunc) { - this.mUpdateFunc(this, secondsPerFrame); - if (this.mOutputFunc) this.mOutputFunc(this.mValue, adaptor); + if (this.updateFunc) { + this.updateFunc(this, secondsPerFrame); + if (this.outputFunc) this.outputFunc(this.value, adaptor); } } @@ -98,17 +98,17 @@ class TVariableValue { // Each frame, one of these (or nothing) will be called to update the value of each TVariableValue. //-------------------- private static update_immediate(varval: TVariableValue, secondsPerFrame: number): void { - varval.mValue = (varval.mUpdateParam as number); - varval.mUpdateFunc = undefined; + varval.value = (varval.updateParam as number); + varval.updateFunc = undefined; } private static update_time(varval: TVariableValue, secondsPerFrame: number): void { - varval.mValue = (varval.mUpdateParam as number) * (varval.mAge * secondsPerFrame); + varval.value = (varval.updateParam as number) * (varval.age * secondsPerFrame); } private static update_functionValue(varval: TVariableValue, secondsPerFrame: number): void { - const t = varval.mAge * secondsPerFrame; - varval.mValue = (varval.mUpdateParam as FVB.TFunctionValue).getValue(t); + const t = varval.age * secondsPerFrame; + varval.value = (varval.updateParam as FVB.TFunctionValue).getValue(t); } //-------------------- @@ -116,38 +116,38 @@ class TVariableValue { // Modify the function that will be called each Update() //-------------------- public setValue_none() { - this.mUpdateFunc = undefined; + this.updateFunc = undefined; } // Value will be set only on next update setValue_immediate(v: number): void { assert(v !== undefined); - this.mUpdateFunc = TVariableValue.update_immediate; - this.mAge = 0; - this.mUpdateParam = v; + this.updateFunc = TVariableValue.update_immediate; + this.age = 0; + this.updateParam = v; } // Value will be set to (mAge * v * x) each frame setValue_time(v: number): void { assert(v !== undefined); - this.mUpdateFunc = TVariableValue.update_time; - this.mAge = 0; - this.mUpdateParam = v; + this.updateFunc = TVariableValue.update_time; + this.age = 0; + this.updateParam = v; } // Value will be the result of a Function Value each frame setValue_functionValue(v?: FVB.TFunctionValue): void { assert(v !== undefined); - this.mUpdateFunc = TVariableValue.update_functionValue; - this.mAge = 0; - this.mUpdateParam = v; + this.updateFunc = TVariableValue.update_functionValue; + this.age = 0; + this.updateParam = v; } //-------------------- // Set Output //-------------------- setOutput(outputFunc?: (val: number, adaptor: TAdaptor) => void) { - this.mOutputFunc = outputFunc; + this.outputFunc = outputFunc; } } @@ -223,12 +223,12 @@ function readData(dataOp: EDataOp, dataOffset: number, dataSize: number, file: R } abstract class TAdaptor { - public mObject: JStage.TObject; + public object: JStage.TObject; constructor( - public mCount: number, - public mVariableValues = nArray(mCount, i => new TVariableValue()), - public mEnableLogging = false, + public count: number, + public variableValues = nArray(count, i => new TVariableValue()), + public enableLogging = true, ) { } abstract adaptor_do_prepare(obj: STBObject): void; @@ -239,8 +239,8 @@ abstract class TAdaptor { // Set a single VariableValue update function, with the option of using FuncVals adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: EDataOp, data: DataVal) { - const varval = this.mVariableValues[keyIdx]; - const control = obj.mControl; + const varval = this.variableValues[keyIdx]; + const control = obj.control; switch (dataOp) { case EDataOp.Void: varval.setValue_none(); break; @@ -257,45 +257,45 @@ abstract class TAdaptor { // Immediately set 3 consecutive VariableValue update functions from a single vec3 adaptor_setVariableValue_Vec(startKeyIdx: number, data: vec3) { - this.mVariableValues[startKeyIdx + 0].setValue_immediate(data[0]); - this.mVariableValues[startKeyIdx + 1].setValue_immediate(data[1]); - this.mVariableValues[startKeyIdx + 2].setValue_immediate(data[2]); + this.variableValues[startKeyIdx + 0].setValue_immediate(data[0]); + this.variableValues[startKeyIdx + 1].setValue_immediate(data[1]); + this.variableValues[startKeyIdx + 2].setValue_immediate(data[2]); } // Get the current value of 3 consecutive VariableValues, as a vector. E.g. Camera position. adaptor_getVariableValue_Vec(dst: vec3, startKeyIdx: number) { - dst[0] = this.mVariableValues[startKeyIdx + 0].getValue(); - dst[1] = this.mVariableValues[startKeyIdx + 1].getValue(); - dst[2] = this.mVariableValues[startKeyIdx + 2].getValue(); + dst[0] = this.variableValues[startKeyIdx + 0].getValue(); + dst[1] = this.variableValues[startKeyIdx + 1].getValue(); + dst[2] = this.variableValues[startKeyIdx + 2].getValue(); } // Immediately set 4 consecutive VariableValue update functions from a single GXColor (4 bytes) adaptor_setVariableValue_GXColor(startKeyIdx: number, data: GfxColor) { debugger; // @TODO: Confirm that all uses of this always have consecutive keyIdxs. JStudio remaps them. - this.mVariableValues[startKeyIdx + 0].setValue_immediate(data.r); - this.mVariableValues[startKeyIdx + 1].setValue_immediate(data.g); - this.mVariableValues[startKeyIdx + 2].setValue_immediate(data.b); - this.mVariableValues[startKeyIdx + 4].setValue_immediate(data.a); + this.variableValues[startKeyIdx + 0].setValue_immediate(data.r); + this.variableValues[startKeyIdx + 1].setValue_immediate(data.g); + this.variableValues[startKeyIdx + 2].setValue_immediate(data.b); + this.variableValues[startKeyIdx + 4].setValue_immediate(data.a); } // Get the current value of 4 consecutive VariableValues, as a GXColor. E.g. Fog color. adaptor_getVariableValue_GXColor(dst: GfxColor, startKeyIdx: number) { - dst.r = this.mVariableValues[startKeyIdx + 0].getValue(); - dst.g = this.mVariableValues[startKeyIdx + 1].getValue(); - dst.b = this.mVariableValues[startKeyIdx + 2].getValue(); - dst.a = this.mVariableValues[startKeyIdx + 2].getValue(); + dst.r = this.variableValues[startKeyIdx + 0].getValue(); + dst.g = this.variableValues[startKeyIdx + 1].getValue(); + dst.b = this.variableValues[startKeyIdx + 2].getValue(); + dst.a = this.variableValues[startKeyIdx + 2].getValue(); } adaptor_updateVariableValue(obj: STBObject, frameCount: number) { - const control = obj.mControl; - for (let vv of this.mVariableValues) { + const control = obj.control; + for (let vv of this.variableValues) { vv.forward(frameCount); - vv.update(control.mSecondsPerFrame, this); + vv.update(control.secondsPerFrame, this); } } log(msg: string) { - if (this.mEnableLogging) { console.debug(`[${this.mObject.JSGGetName()}] ${msg}`); } + if (this.enableLogging) { console.debug(`[${this.object.JSGGetName()}] ${msg}`); } } } @@ -305,131 +305,131 @@ abstract class TAdaptor { // Each frame the STB data is marched (see do_paragraph) to update one or more properties of the Object via its Adaptor. //---------------------------------------------------------------------------------------------------------------------- abstract class STBObject { - public mControl: TControl; - public mAdaptor: TAdaptor; - - private mId: string; - private mType: string; - private mFlags: number; - private mStatus: EStatus = EStatus.Still; - private mIsSequence: boolean = false; - private mSuspendFrames: number = 0; - private mData: Reader; - private pSequence: number; - private pSequence_next: number; - private mWait: number = 0; + public control: TControl; + public adaptor: TAdaptor; + + private id: string; + private type: string; + private flags: number; + private status: EStatus = EStatus.Still; + private isSequence: boolean = false; + private suspendFrames: number = 0; + private data: Reader; + private sequence: number; + private sequenceNext: number; + private wait: number = 0; constructor(control: TControl, blockObj?: TBlockObject, adaptor?: TAdaptor) { - this.mControl = control; + this.control = control; if (blockObj && adaptor) { - this.mAdaptor = adaptor; + this.adaptor = adaptor; - this.mId = blockObj.id; - this.mType = blockObj.type; - this.mFlags = blockObj.flag; - this.mData = blockObj.data; - this.pSequence = 0; - this.pSequence_next = 0xC + align(blockObj.id.length + 1, 4); + this.id = blockObj.id; + this.type = blockObj.type; + this.flags = blockObj.flag; + this.data = blockObj.data; + this.sequence = 0; + this.sequenceNext = 0xC + align(blockObj.id.length + 1, 4); } } // These are intended to be overridden by subclasses abstract do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void; - do_begin() { if (this.mAdaptor) this.mAdaptor.adaptor_do_begin(this); } - do_end() { if (this.mAdaptor) this.mAdaptor.adaptor_do_end(this); } + do_begin() { if (this.adaptor) this.adaptor.adaptor_do_begin(this); } + do_end() { if (this.adaptor) this.adaptor.adaptor_do_end(this); } // Done updating this frame. Compute our variable data (i.e. interpolate) and send to the game object. do_wait(frameCount: number) { - if (this.mAdaptor) this.mAdaptor.adaptor_updateVariableValue(this, frameCount); - if (this.mAdaptor) this.mAdaptor.adaptor_do_update(this, frameCount); + if (this.adaptor) this.adaptor.adaptor_updateVariableValue(this, frameCount); + if (this.adaptor) this.adaptor.adaptor_do_update(this, frameCount); } - do_data(id: number, data: DataView) { if (this.mAdaptor) this.mAdaptor.adaptor_do_data(this, id, data); } + do_data(id: number, data: DataView) { if (this.adaptor) this.adaptor.adaptor_do_data(this, id, data); } - getStatus() { return this.mStatus; } - getSuspendFrames(): number { return this.mSuspendFrames; } - isSuspended(): boolean { return this.mSuspendFrames > 0; } - setSuspend(frameCount: number) { this.mSuspendFrames = frameCount; } + getStatus() { return this.status; } + getSuspendFrames(): number { return this.suspendFrames; } + isSuspended(): boolean { return this.suspendFrames > 0; } + setSuspend(frameCount: number) { this.suspendFrames = frameCount; } reset(blockObj: TBlockObject) { - this.pSequence = 0; - this.mStatus = EStatus.Still; - this.pSequence_next = 0xC + align(blockObj.id.length + 1, 4); - this.mData = blockObj.data; - this.mWait = 0; + this.sequence = 0; + this.status = EStatus.Still; + this.sequenceNext = 0xC + align(blockObj.id.length + 1, 4); + this.data = blockObj.data; + this.wait = 0; } forward(frameCount: number): boolean { let hasWaited = false; while (true) { // Top bit of mFlags makes this object immediately inactive, restarting any existing sequence - if (this.mFlags & 0x8000) { - if (this.mStatus != EStatus.Inactive) { - this.mStatus = EStatus.Inactive; - if (this.mIsSequence) { + if (this.flags & 0x8000) { + if (this.status != EStatus.Inactive) { + this.status = EStatus.Inactive; + if (this.isSequence) { this.do_end(); } } return true; } - if (this.mStatus == EStatus.Inactive) { - assert(this.mIsSequence); + if (this.status == EStatus.Inactive) { + assert(this.isSequence); this.do_begin(); - this.mStatus = EStatus.Wait; + this.status = EStatus.Wait; } - if ((this.mControl && this.mControl.isSuspended()) || this.isSuspended()) { - if (this.mIsSequence) { - assert((this.mStatus == EStatus.Wait) || (this.mStatus == EStatus.Suspend)); - this.mStatus = EStatus.Suspend; + if ((this.control && this.control.isSuspended()) || this.isSuspended()) { + if (this.isSequence) { + assert((this.status == EStatus.Wait) || (this.status == EStatus.Suspend)); + this.status = EStatus.Suspend; this.do_wait(frameCount); } return true; } while (true) { - this.pSequence = this.pSequence_next; + this.sequence = this.sequenceNext; // If there is nothing left in the sequence, end it - if (!this.pSequence) { - if (this.mIsSequence) { - assert(this.mStatus != EStatus.Still); + if (!this.sequence) { + if (this.isSequence) { + assert(this.status != EStatus.Still); if (!hasWaited) { this.do_wait(0); } - this.mIsSequence = false; - this.mStatus = EStatus.End; + this.isSequence = false; + this.status = EStatus.End; this.do_end(); } return false; } // If we're not currently running a sequence, start it - if (!this.mIsSequence) { - assert(this.mStatus == EStatus.Still); - this.mIsSequence = true; + if (!this.isSequence) { + assert(this.status == EStatus.Still); + this.isSequence = true; this.do_begin(); } - this.mStatus = EStatus.Wait; + this.status = EStatus.Wait; - if (this.mWait == 0) { + if (this.wait == 0) { this.process_sequence(); - if (this.mWait == 0) { + if (this.wait == 0) { break; } } - assert(this.mWait > 0); + assert(this.wait > 0); hasWaited = true; - if (frameCount >= this.mWait) { - const wait = this.mWait; - frameCount -= this.mWait; - this.mWait = 0; + if (frameCount >= this.wait) { + const wait = this.wait; + frameCount -= this.wait; + this.wait = 0; this.do_wait(wait); } else { - this.mWait -= frameCount; + this.wait -= frameCount; this.do_wait(frameCount); return true; } @@ -438,8 +438,8 @@ abstract class STBObject { } private process_sequence() { - const view = this.mData.view; - let byteIdx = this.pSequence; + const view = this.data.view; + let byteIdx = this.sequence; let cmd = view.getUint8(byteIdx); let param = view.getUint32(byteIdx) & 0xFFFFFF; @@ -453,7 +453,7 @@ abstract class STBObject { } } - this.pSequence_next = next; + this.sequenceNext = next; switch (cmd) { case ESequenceCmd.End: @@ -464,7 +464,7 @@ abstract class STBObject { break; case ESequenceCmd.Wait: - this.mWait = param; + this.wait = param; break; case ESequenceCmd.Skip: @@ -472,17 +472,17 @@ abstract class STBObject { break; case ESequenceCmd.Suspend: - this.mSuspendFrames += param; + this.suspendFrames += param; break; case ESequenceCmd.Paragraph: byteIdx += 4; - while (byteIdx < this.pSequence_next) { + while (byteIdx < this.sequenceNext) { const para = TParagraph.parse(view, byteIdx); if (para.type <= 0xff) { - this.process_paragraph_reserved_(this.mData, para.dataSize, para.dataOffset, para.type); + this.process_paragraph_reserved_(this.data, para.dataSize, para.dataOffset, para.type); } else { - this.do_paragraph(this.mData, para.dataSize, para.dataOffset, para.type); + this.do_paragraph(this.data, para.dataSize, para.dataOffset, para.type); } byteIdx = para.nextOffset; } @@ -602,8 +602,8 @@ class TActorAdaptor extends TAdaptor { public animTexMode: number = 0; // See computeAnimFrame() constructor( - private mSystem: TSystem, - public override mObject: TActor, + private system: TSystem, + public override object: TActor, ) { super(14); } private static computeAnimFrame(animMode: number, maxFrame: number, frame: number) { @@ -619,45 +619,45 @@ class TActorAdaptor extends TAdaptor { } adaptor_do_prepare(obj: STBObject): void { - this.mVariableValues[EActorTrack.AnimTransition].setOutput(this.mObject.JSGSetAnimationTransition.bind(this.mObject)); + this.variableValues[EActorTrack.AnimTransition].setOutput(this.object.JSGSetAnimationTransition.bind(this.object)); - this.mVariableValues[EActorTrack.AnimFrame].setOutput((frame: number, adaptor: TAdaptor) => { - frame = TActorAdaptor.computeAnimFrame(this.animMode, this.mObject.JSGGetAnimationFrameMax(), frame); - this.mObject.JSGSetAnimationFrame(frame); + this.variableValues[EActorTrack.AnimFrame].setOutput((frame: number, adaptor: TAdaptor) => { + frame = TActorAdaptor.computeAnimFrame(this.animMode, this.object.JSGGetAnimationFrameMax(), frame); + this.object.JSGSetAnimationFrame(frame); }); - this.mVariableValues[EActorTrack.TexAnimFrame].setOutput((frame: number, adaptor: TAdaptor) => { - frame = TActorAdaptor.computeAnimFrame(this.animTexMode, this.mObject.JSGGetTextureAnimationFrameMax(), frame); - this.mObject.JSGSetTextureAnimationFrame(frame); + this.variableValues[EActorTrack.TexAnimFrame].setOutput((frame: number, adaptor: TAdaptor) => { + frame = TActorAdaptor.computeAnimFrame(this.animTexMode, this.object.JSGGetTextureAnimationFrameMax(), frame); + this.object.JSGSetTextureAnimationFrame(frame); }); } adaptor_do_begin(obj: STBObject): void { - this.mObject.JSGFEnableFlag(1); + this.object.JSGFEnableFlag(1); const pos = scratchVec3a; const rot = scratchVec3b; const scale = scratchVec3c; - this.mObject.JSGGetTranslation(pos); - this.mObject.JSGGetRotation(rot); - this.mObject.JSGGetScaling(scale); + this.object.JSGGetTranslation(pos); + this.object.JSGGetRotation(rot); + this.object.JSGGetScaling(scale); - if (obj.mControl.isTransformEnabled()) { - vec3.transformMat4(pos, pos, obj.mControl.getTransformOnGet()); - rot[1] -= obj.mControl.mTransformRotY!; + if (obj.control.isTransformEnabled()) { + vec3.transformMat4(pos, pos, obj.control.getTransformOnGet()); + rot[1] -= obj.control.transformRotY!; } this.adaptor_setVariableValue_Vec(EActorTrack.PosX, pos); this.adaptor_setVariableValue_Vec(EActorTrack.RotX, rot); this.adaptor_setVariableValue_Vec(EActorTrack.ScaleX, scale); - this.mVariableValues[EActorTrack.AnimTransition].setValue_immediate(this.mObject.JSGGetAnimationTransition()); - this.mVariableValues[EActorTrack.AnimFrame].setValue_immediate(this.mObject.JSGGetAnimationFrame()); - this.mVariableValues[EActorTrack.AnimFrame].setValue_immediate(this.mObject.JSGGetTextureAnimationFrame()); + this.variableValues[EActorTrack.AnimTransition].setValue_immediate(this.object.JSGGetAnimationTransition()); + this.variableValues[EActorTrack.AnimFrame].setValue_immediate(this.object.JSGGetAnimationFrame()); + this.variableValues[EActorTrack.AnimFrame].setValue_immediate(this.object.JSGGetTextureAnimationFrame()); } adaptor_do_end(obj: STBObject): void { - this.mObject.JSGFDisableFlag(1); + this.object.JSGFDisableFlag(1); } adaptor_do_update(obj: STBObject, frameCount: number): void { @@ -668,25 +668,25 @@ class TActorAdaptor extends TAdaptor { this.adaptor_getVariableValue_Vec(rot, EActorTrack.RotX); this.adaptor_getVariableValue_Vec(scale, EActorTrack.ScaleX); - if (obj.mControl.isTransformEnabled()) { - vec3.transformMat4(pos, pos, obj.mControl.getTransformOnSet()); - rot[1] += obj.mControl.mTransformRotY!; + if (obj.control.isTransformEnabled()) { + vec3.transformMat4(pos, pos, obj.control.getTransformOnSet()); + rot[1] += obj.control.transformRotY!; } - this.mObject.JSGSetTranslation(pos); - this.mObject.JSGSetRotation(rot); - this.mObject.JSGSetScaling(scale); + this.object.JSGSetTranslation(pos); + this.object.JSGSetRotation(rot); + this.object.JSGSetScaling(scale); } adaptor_do_data(obj: STBObject, id: number, data: DataView): void { this.log(`SetData: ${id}`); - this.mObject.JSGSetData(id, data); + this.object.JSGSetData(id, data); } adaptor_do_PARENT(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectName); this.log(`SetParent: ${data.asStr}`); - this.parent = this.mSystem.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); + this.parent = this.system.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); } adaptor_do_PARENT_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { @@ -707,14 +707,14 @@ class TActorAdaptor extends TAdaptor { adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { assert(dataOp == EDataOp.Immediate); this.log(`SetParentEnable: ${data}`); - if (data) { this.mObject.JSGSetParent(this.parent!, this.parentNodeID); } - else { this.mObject.JSGSetParent(null, 0xFFFFFFFF); } + if (data) { this.object.JSGSetParent(this.parent!, this.parentNodeID); } + else { this.object.JSGSetParent(null, 0xFFFFFFFF); } } adaptor_do_RELATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectName); this.log(`SetRelation: ${data.asStr!}`); - this.relation = this.mSystem.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); + this.relation = this.system.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); } adaptor_do_RELATION_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { @@ -735,19 +735,19 @@ class TActorAdaptor extends TAdaptor { adaptor_do_RELATION_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { assert(dataOp == EDataOp.Immediate); this.log(`SetRelationEnable: ${data}`); - this.mObject.JSGSetRelation(!!data, this.relation!, this.relationNodeID); + this.object.JSGSetRelation(!!data, this.relation!, this.relationNodeID); } adaptor_do_SHAPE(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectIdx); this.log(`SetShape: ${data.asInt!}`); - this.mObject.JSGSetShape(data.asInt!); + this.object.JSGSetShape(data.asInt!); } adaptor_do_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectIdx); this.log(`SetAnimation: ${(data.asInt!) & 0xFFFF} (${(data.asInt!) >> 4 & 0x01})`); - this.mObject.JSGSetAnimation(data.asInt!); + this.object.JSGSetAnimation(data.asInt!); } adaptor_do_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { @@ -759,7 +759,7 @@ class TActorAdaptor extends TAdaptor { adaptor_do_TEXTURE_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectIdx); this.log(`SetTexAnim: ${data}`); - this.mObject.JSGSetTextureAnimation(data.asInt!); + this.object.JSGSetTextureAnimation(data.asInt!); } adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { @@ -770,13 +770,13 @@ class TActorAdaptor extends TAdaptor { } class TActorObject extends STBObject { - override mAdaptor: TActorAdaptor; + override adaptor: TActorAdaptor; constructor( control: TControl, blockObj: TBlockObject, stageObj: JStage.TObject, - ) { super(control, blockObj, new TActorAdaptor(control.mSystem, stageObj as TActor)) } + ) { super(control, blockObj, new TActorAdaptor(control.system, stageObj as TActor)) } override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { const dataOp = (param & 0x1F) as EDataOp; @@ -808,40 +808,40 @@ class TActorObject extends STBObject { case 0x3b: keyIdx = EActorTrack.AnimFrame; break; case 0x4b: keyIdx = EActorTrack.AnimTransition; break; - case 0x39: this.mAdaptor.adaptor_do_SHAPE(dataOp, data, dataSize); return; - case 0x3a: this.mAdaptor.adaptor_do_ANIMATION(dataOp, data, dataSize); return; - case 0x43: this.mAdaptor.adaptor_do_ANIMATION_MODE(dataOp, data, dataSize); return; - case 0x4c: debugger; this.mAdaptor.adaptor_do_TEXTURE_ANIMATION(dataOp, data, dataSize); return; - case 0x4e: debugger; this.mAdaptor.adaptor_do_TEXTURE_ANIMATION_MODE(dataOp, data, dataSize); return; + case 0x39: this.adaptor.adaptor_do_SHAPE(dataOp, data, dataSize); return; + case 0x3a: this.adaptor.adaptor_do_ANIMATION(dataOp, data, dataSize); return; + case 0x43: this.adaptor.adaptor_do_ANIMATION_MODE(dataOp, data, dataSize); return; + case 0x4c: debugger; this.adaptor.adaptor_do_TEXTURE_ANIMATION(dataOp, data, dataSize); return; + case 0x4e: debugger; this.adaptor.adaptor_do_TEXTURE_ANIMATION_MODE(dataOp, data, dataSize); return; - case 0x30: debugger; this.mAdaptor.adaptor_do_PARENT(dataOp, data, dataSize); return; - case 0x31: debugger; this.mAdaptor.adaptor_do_PARENT_NODE(dataOp, data, dataSize); return; + case 0x30: debugger; this.adaptor.adaptor_do_PARENT(dataOp, data, dataSize); return; + case 0x31: debugger; this.adaptor.adaptor_do_PARENT_NODE(dataOp, data, dataSize); return; case 0x32: debugger; keyIdx = EActorTrack.Parent; if ((dataOp < 0x13) && (dataOp > 0x0F)) { debugger; - this.mAdaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); - this.mAdaptor.mVariableValues[keyIdx].setOutput((enabled, adaptor) => { + this.adaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); + this.adaptor.variableValues[keyIdx].setOutput((enabled, adaptor) => { (adaptor as TActorAdaptor).adaptor_do_PARENT_ENABLE(dataOp, enabled, dataSize) }); } - this.mAdaptor.adaptor_do_PARENT_ENABLE(dataOp, data.asInt!, dataSize); + this.adaptor.adaptor_do_PARENT_ENABLE(dataOp, data.asInt!, dataSize); break; - case 0x33: debugger; this.mAdaptor.adaptor_do_RELATION(dataOp, data, dataSize); return; - case 0x34: debugger; this.mAdaptor.adaptor_do_RELATION_NODE(dataOp, data, dataSize); return; + case 0x33: debugger; this.adaptor.adaptor_do_RELATION(dataOp, data, dataSize); return; + case 0x34: debugger; this.adaptor.adaptor_do_RELATION_NODE(dataOp, data, dataSize); return; case 0x35: debugger; keyIdx = EActorTrack.Relation; if ((dataOp < 0x13) && (dataOp > 0x0F)) { debugger; - this.mAdaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); - this.mAdaptor.mVariableValues[keyIdx].setOutput((enabled, adaptor) => { + this.adaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); + this.adaptor.variableValues[keyIdx].setOutput((enabled, adaptor) => { (adaptor as TActorAdaptor).adaptor_do_RELATION_ENABLE(dataOp, enabled, dataSize) }); } - this.mAdaptor.adaptor_do_RELATION_ENABLE(dataOp, data.asInt!, dataSize); + this.adaptor.adaptor_do_RELATION_ENABLE(dataOp, data.asInt!, dataSize); break; default: @@ -853,10 +853,10 @@ class TActorObject extends STBObject { let keyData = []; for (let i = 0; i < keyCount; i++) { keyData[i] = readData(dataOp, dataOffset + i * 4, dataSize, file); - this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); + this.adaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - this.mAdaptor.log(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp)}]`); + this.adaptor.log(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp)}]`); } } @@ -919,35 +919,35 @@ export abstract class TCamera extends JStage.TObject { class TCameraAdaptor extends TAdaptor { constructor( - override mObject: TCamera + override object: TCamera ) { super(11); } adaptor_do_prepare(obj: STBObject): void { - this.mVariableValues[ECameraTrack.FovY].setOutput(this.mObject.JSGSetProjectionFovy.bind(this.mObject)); - this.mVariableValues[ECameraTrack.Roll].setOutput(this.mObject.JSGSetViewRoll.bind(this.mObject)); - this.mVariableValues[ECameraTrack.DistNear].setOutput(this.mObject.JSGSetProjectionNear.bind(this.mObject)); - this.mVariableValues[ECameraTrack.DistFar].setOutput(this.mObject.JSGSetProjectionFar.bind(this.mObject)); + this.variableValues[ECameraTrack.FovY].setOutput(this.object.JSGSetProjectionFovy.bind(this.object)); + this.variableValues[ECameraTrack.Roll].setOutput(this.object.JSGSetViewRoll.bind(this.object)); + this.variableValues[ECameraTrack.DistNear].setOutput(this.object.JSGSetProjectionNear.bind(this.object)); + this.variableValues[ECameraTrack.DistFar].setOutput(this.object.JSGSetProjectionFar.bind(this.object)); } adaptor_do_begin(obj: STBObject): void { const camPos = scratchVec3a; const targetPos = scratchVec3b; - this.mObject.JSGGetViewPosition(camPos); - this.mObject.JSGGetViewTargetPosition(targetPos); + this.object.JSGGetViewPosition(camPos); + this.object.JSGGetViewTargetPosition(targetPos); - vec3.transformMat4(camPos, camPos, obj.mControl.getTransformOnGet()); - vec3.transformMat4(targetPos, targetPos, obj.mControl.getTransformOnGet()); + vec3.transformMat4(camPos, camPos, obj.control.getTransformOnGet()); + vec3.transformMat4(targetPos, targetPos, obj.control.getTransformOnGet()); this.adaptor_setVariableValue_Vec(ECameraTrack.PosX, camPos); this.adaptor_setVariableValue_Vec(ECameraTrack.TargetX, targetPos); - this.mVariableValues[ECameraTrack.FovY].setValue_immediate(this.mObject.JSGGetProjectionFovy()); - this.mVariableValues[ECameraTrack.Roll].setValue_immediate(this.mObject.JSGGetViewRoll()); - this.mVariableValues[ECameraTrack.DistNear].setValue_immediate(this.mObject.JSGGetProjectionNear()); - this.mVariableValues[ECameraTrack.DistFar].setValue_immediate(this.mObject.JSGGetProjectionFar()); + this.variableValues[ECameraTrack.FovY].setValue_immediate(this.object.JSGGetProjectionFovy()); + this.variableValues[ECameraTrack.Roll].setValue_immediate(this.object.JSGGetViewRoll()); + this.variableValues[ECameraTrack.DistNear].setValue_immediate(this.object.JSGGetProjectionNear()); + this.variableValues[ECameraTrack.DistFar].setValue_immediate(this.object.JSGGetProjectionFar()); } adaptor_do_end(obj: STBObject): void { - this.mObject.JSGFDisableFlag(1); + this.object.JSGFDisableFlag(1); } adaptor_do_update(obj: STBObject, frameCount: number): void { @@ -957,11 +957,11 @@ class TCameraAdaptor extends TAdaptor { this.adaptor_getVariableValue_Vec(camPos, ECameraTrack.PosX); this.adaptor_getVariableValue_Vec(targetPos, ECameraTrack.TargetX); - vec3.transformMat4(camPos, camPos, obj.mControl.getTransformOnSet()); - vec3.transformMat4(targetPos, targetPos, obj.mControl.getTransformOnSet()); + vec3.transformMat4(camPos, camPos, obj.control.getTransformOnSet()); + vec3.transformMat4(targetPos, targetPos, obj.control.getTransformOnSet()); - this.mObject.JSGSetViewPosition(camPos); - this.mObject.JSGSetViewTargetPosition(targetPos); + this.object.JSGSetViewPosition(camPos); + this.object.JSGSetViewTargetPosition(targetPos); } adaptor_do_data(obj: STBObject, id: number, data: DataView): void { @@ -1029,10 +1029,10 @@ class TCameraObject extends STBObject { let keyData = [] for (let i = 0; i < keyCount; i++) { keyData[i] = readData(dataOp, dataOffset + i * 4, dataSize, file); - this.mAdaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); + this.adaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - this.mAdaptor.log(`Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp)}]`); + this.adaptor.log(`Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp)}]`); } } @@ -1196,7 +1196,7 @@ namespace FVB { for (let i = 0; i < objCount; i++) { const idSize = file.view.getUint32(para.dataOffset + 4 + i * 8 + 0); const id = readString(file.buffer, para.dataOffset + 4 + i * 8 + 4, idSize); - const obj = pControl.mObjects.find(o => o.id == id); + const obj = pControl.objects.find(o => o.id == id); assert(!!obj); refer.fvs.push(obj.funcVal); } @@ -1210,7 +1210,7 @@ namespace FVB { const objCount = file.view.getUint32(para.dataOffset + 0); for (let i = 0; i < objCount; i++) { const idx = file.view.getUint32(para.dataOffset + 4 + i * 4); - const obj = pControl.mObjects[idx]; + const obj = pControl.objects[idx]; assert(!!obj); refer.fvs.push(obj.funcVal); } @@ -1241,7 +1241,7 @@ namespace FVB { } export class TControl { - public mObjects: TObject[] = []; + public objects: TObject[] = []; // Really this is a fvb::TFactory method public createObject(block: TBlock): TObject | undefined { @@ -1266,13 +1266,13 @@ namespace FVB { } public destroyObject_all() { - this.mObjects = []; + this.objects = []; } } export class TParse { constructor( - private mControl: TControl + private control: TControl ) { } private parseBlock(file: Reader, flags: number): boolean { @@ -1284,11 +1284,11 @@ namespace FVB { dataOffset: file.offset + align(8 + idLen, 4), } - const obj = this.mControl.createObject(block); + const obj = this.control.createObject(block); if (!obj) { return false; } - obj.prepare(block, this.mControl, file); - this.mControl.mObjects.push(obj); + obj.prepare(block, this.control, file); + this.control.objects.push(obj); return true; } @@ -1824,47 +1824,47 @@ export abstract class TBlockObject { // This combines JStudio::TControl and JStudio::stb::TControl into a single class, for simplicity. export class TControl { - public mSystem: TSystem; - public mFvbControl = new FVB.TControl(); - public mSecondsPerFrame: number = 1 / 30.0; - private mSuspendFrames: number; + public system: TSystem; + public fvbControl = new FVB.TControl(); + public secondsPerFrame: number = 1 / 30.0; + private suspendFrames: number; - public mTransformOrigin?: vec3; - public mTransformRotY?: number; - private mTransformOnGetMtx = mat4.create(); - private mTransformOnSetMtx = mat4.create(); + public transformOrigin?: vec3; + public transformRotY?: number; + private transformOnGetMtx = mat4.create(); + private transformOnSetMtx = mat4.create(); - private mStatus: EStatus = EStatus.Still; - private mObjects: STBObject[] = []; + private status: EStatus = EStatus.Still; + private objects: STBObject[] = []; // A special object that the STB file can use to suspend the demo (such as while waiting for player input) - private mControlObject = new TControlObject(this); + private controlObject = new TControlObject(this); constructor(system: TSystem) { - this.mSystem = system; + this.system = system; } - public isSuspended() { return this.mSuspendFrames > 0; } - public setSuspend(frameCount: number) { return this.mControlObject.setSuspend(frameCount); } + public isSuspended() { return this.suspendFrames > 0; } + public setSuspend(frameCount: number) { return this.controlObject.setSuspend(frameCount); } - public isTransformEnabled() { return !!this.mTransformOrigin; } - public getTransformOnSet() { return this.mTransformOnSetMtx; } - public getTransformOnGet() { return this.mTransformOnGetMtx; } + public isTransformEnabled() { return !!this.transformOrigin; } + public getTransformOnSet() { return this.transformOnSetMtx; } + public getTransformOnGet() { return this.transformOnGetMtx; } public transformSetOrigin(originPos: vec3, rotY: number) { - this.mTransformOrigin = originPos; - this.mTransformRotY = rotY; + this.transformOrigin = originPos; + this.transformRotY = rotY; // The "OnGet" matrix transforms from world space into demo space - mat4.fromYRotation(this.mTransformOnGetMtx, -rotY); - mat4.translate(this.mTransformOnGetMtx, this.mTransformOnGetMtx, vec3.negate(scratchVec3a, originPos)); + mat4.fromYRotation(this.transformOnGetMtx, -rotY); + mat4.translate(this.transformOnGetMtx, this.transformOnGetMtx, vec3.negate(scratchVec3a, originPos)); // The "OnSet" matrix is the inverse - mat4.fromTranslation(this.mTransformOnSetMtx, originPos); - mat4.rotateY(this.mTransformOnSetMtx, this.mTransformOnSetMtx, rotY); + mat4.fromTranslation(this.transformOnSetMtx, originPos); + mat4.rotateY(this.transformOnSetMtx, this.transformOnSetMtx, rotY); } public setControlObject(obj: TBlockObject) { - this.mControlObject.reset(obj); + this.controlObject.reset(obj); } public forward(frameCount: number): boolean { @@ -1872,10 +1872,10 @@ export class TControl { let andStatus = 0xFF; let orStatus = 0; - this.mSuspendFrames = this.mControlObject.getSuspendFrames(); - let shouldContinue = this.mControlObject.forward(frameCount); + this.suspendFrames = this.controlObject.getSuspendFrames(); + let shouldContinue = this.controlObject.forward(frameCount); - for (let obj of this.mObjects) { + for (let obj of this.objects) { const res = obj.forward(frameCount); shouldContinue ||= res; @@ -1884,12 +1884,12 @@ export class TControl { orStatus |= objStatus; } - this.mStatus = (andStatus | (orStatus << 0x10)); + this.status = (andStatus | (orStatus << 0x10)); return shouldContinue; } - public getFunctionValueByIdx(idx: number) { return this.mFvbControl.mObjects[idx]?.funcVal; } - public getFunctionValueByName(name: string) { return this.mFvbControl.mObjects.find(v => v.id == name)?.funcVal; } + public getFunctionValueByIdx(idx: number) { return this.fvbControl.objects[idx]?.funcVal; } + public getFunctionValueByName(name: string) { return this.fvbControl.objects.find(v => v.id == name)?.funcVal; } // Really this is a stb::TFactory method public createObject(blockObj: TBlockObject): STBObject | undefined { @@ -1905,27 +1905,27 @@ export class TControl { return undefined; } - const stageObj = this.mSystem.JSGFindObject(blockObj.id, objType); + const stageObj = this.system.JSGFindObject(blockObj.id, objType); if (!stageObj) { return undefined; } const obj = new objConstructor(this, blockObj, stageObj); - obj.mAdaptor.adaptor_do_prepare(obj); - this.mObjects.push(obj); + obj.adaptor.adaptor_do_prepare(obj); + this.objects.push(obj); return obj; } public destroyObject_all() { - this.mObjects = []; - this.mFvbControl.destroyObject_all(); + this.objects = []; + this.fvbControl.destroyObject_all(); } } export class TParse { constructor( - private mControl: TControl, - private mFvbParse = new FVB.TParse(mControl.mFvbControl) + private control: TControl, + private fvbParse = new FVB.TParse(control.fvbControl) ) { } // Parse an entire scene's worth of object sequences at once @@ -1939,7 +1939,7 @@ export class TParse { } if (blockObj.type == BLOCK_TYPE_CONTROL) { - this.mControl.setControlObject(blockObj); + this.control.setControlObject(blockObj); return true; } @@ -1953,7 +1953,7 @@ export class TParse { return true; } - const obj = this.mControl.createObject(blockObj); + const obj = this.control.createObject(blockObj); if (!obj) { if (flags & 0x40) { console.debug('Unhandled flag during parseBlockObject: 0x40'); @@ -1986,7 +1986,7 @@ export class TParse { const blockType = readString(file.buffer, byteIdx + 4, 4); if (blockType == 'JFVB') { - this.mFvbParse.parse(file.buffer.subarray(byteIdx + 8, blockSize - 8), flags) + this.fvbParse.parse(file.buffer.subarray(byteIdx + 8, blockSize - 8), flags) } else { this.parseBlockObject(new Reader(file.buffer, byteIdx), flags); } From 552ae086aac40103e44a94e85bd03608dfc2a8c1 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 07:49:28 -0700 Subject: [PATCH 086/102] d_demo: Remove 'm' prefix from all member variables --- src/ZeldaWindWaker/Main.ts | 16 +- src/ZeldaWindWaker/d_demo.ts | 274 +++++++++++++++++------------------ 2 files changed, 145 insertions(+), 145 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index f448e6e96..e39768ba4 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -493,14 +493,14 @@ export class WindWakerRenderer implements Viewer.SceneGfx { let upVec = vec3.set(scratchVec3b, 0, 1, 0); let roll = 0.0; - if(demoCam.mFlags & EDemoCamFlags.HasTargetPos) { targetPos = demoCam.mTargetPosition; } - if(demoCam.mFlags & EDemoCamFlags.HasEyePos) { viewPos = demoCam.mViewPosition; } - if(demoCam.mFlags & EDemoCamFlags.HasUpVec) { upVec = demoCam.mUpVector; } - if(demoCam.mFlags & EDemoCamFlags.HasFovY) { viewerInput.camera.fovY = demoCam.mFovy * MathConstants.DEG_TO_RAD; } - if(demoCam.mFlags & EDemoCamFlags.HasRoll) { roll = demoCam.mRoll * MathConstants.DEG_TO_RAD; } - if(demoCam.mFlags & EDemoCamFlags.HasAspect) { debugger; /* Untested. Remove once confirmed working */ } - if(demoCam.mFlags & EDemoCamFlags.HasNearZ) { viewerInput.camera.near = demoCam.mProjNear; } - if(demoCam.mFlags & EDemoCamFlags.HasFarZ) { viewerInput.camera.far = demoCam.mProjFar; } + if(demoCam.flags & EDemoCamFlags.HasTargetPos) { targetPos = demoCam.targetPosition; } + if(demoCam.flags & EDemoCamFlags.HasEyePos) { viewPos = demoCam.viewPosition; } + if(demoCam.flags & EDemoCamFlags.HasUpVec) { upVec = demoCam.upVector; } + if(demoCam.flags & EDemoCamFlags.HasFovY) { viewerInput.camera.fovY = demoCam.fovY * MathConstants.DEG_TO_RAD; } + if(demoCam.flags & EDemoCamFlags.HasRoll) { roll = demoCam.roll * MathConstants.DEG_TO_RAD; } + if(demoCam.flags & EDemoCamFlags.HasAspect) { debugger; /* Untested. Remove once confirmed working */ } + if(demoCam.flags & EDemoCamFlags.HasNearZ) { viewerInput.camera.near = demoCam.projNear; } + if(demoCam.flags & EDemoCamFlags.HasFarZ) { viewerInput.camera.far = demoCam.projFar; } mat4.targetTo(viewerInput.camera.worldMatrix, viewPos, targetPos, upVec); mat4.rotateZ(viewerInput.camera.worldMatrix, viewerInput.camera.worldMatrix, roll); diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index d77f07120..275ede5ba 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -29,15 +29,15 @@ export enum EDemoCamFlags { } class dDemo_camera_c extends TCamera { - mFlags: number = 0; - mProjNear: number = 0; - mProjFar: number = 0; - mFovy: number = 0; - mAspect: number = 0; - mViewPosition: vec3 = vec3.create(); - mUpVector: vec3 = vec3.create(); - mTargetPosition: vec3 = vec3.create(); - mRoll: number = 0; + flags: number = 0; + projNear: number = 0; + projFar: number = 0; + fovY: number = 0; + aspect: number = 0; + viewPosition: vec3 = vec3.create(); + upVector: vec3 = vec3.create(); + targetPosition: vec3 = vec3.create(); + roll: number = 0; constructor( private globals: dGlobals @@ -53,8 +53,8 @@ class dDemo_camera_c extends TCamera { } override JSGSetProjectionNear(v: number) { - this.mProjNear = v; - this.mFlags |= EDemoCamFlags.HasNearZ; + this.projNear = v; + this.flags |= EDemoCamFlags.HasNearZ; } override JSGGetProjectionFar(): number { @@ -66,8 +66,8 @@ class dDemo_camera_c extends TCamera { override JSGSetProjectionFar(v: number): void { - this.mProjFar = v; - this.mFlags |= EDemoCamFlags.HasFarZ; + this.projFar = v; + this.flags |= EDemoCamFlags.HasFarZ; } @@ -80,8 +80,8 @@ class dDemo_camera_c extends TCamera { override JSGSetProjectionFovy(v: number): void { - this.mFovy = v; - this.mFlags |= EDemoCamFlags.HasFovY; + this.fovY = v; + this.flags |= EDemoCamFlags.HasFovY; } @@ -94,8 +94,8 @@ class dDemo_camera_c extends TCamera { override JSGSetProjectionAspect(v: number) { - this.mAspect = v; - this.mFlags |= EDemoCamFlags.HasAspect; + this.aspect = v; + this.flags |= EDemoCamFlags.HasAspect; } @@ -105,8 +105,8 @@ class dDemo_camera_c extends TCamera { override JSGSetViewPosition(v: ReadonlyVec3) { - vec3.copy(this.mViewPosition, v); - this.mFlags |= EDemoCamFlags.HasEyePos; + vec3.copy(this.viewPosition, v); + this.flags |= EDemoCamFlags.HasEyePos; } @@ -119,8 +119,8 @@ class dDemo_camera_c extends TCamera { override JSGSetViewUpVector(v: ReadonlyVec3) { - vec3.copy(this.mUpVector, v); - this.mFlags |= EDemoCamFlags.HasUpVec; + vec3.copy(this.upVector, v); + this.flags |= EDemoCamFlags.HasUpVec; } @@ -133,8 +133,8 @@ class dDemo_camera_c extends TCamera { override JSGSetViewTargetPosition(v: ReadonlyVec3) { - vec3.copy(this.mTargetPosition, v); - this.mFlags |= EDemoCamFlags.HasTargetPos; + vec3.copy(this.targetPosition, v); + this.flags |= EDemoCamFlags.HasTargetPos; } @@ -142,13 +142,13 @@ class dDemo_camera_c extends TCamera { const camera = this.globals.camera; if (!camera) return 0.0; - return this.mRoll; // HACK: Instead of actually computing roll (complicated), just assume no one else is modifying it + return this.roll; // HACK: Instead of actually computing roll (complicated), just assume no one else is modifying it } override JSGSetViewRoll(v: number) { - this.mRoll = v; - this.mFlags |= EDemoCamFlags.HasRoll; + this.roll = v; + this.flags |= EDemoCamFlags.HasRoll; } } @@ -165,38 +165,38 @@ export const enum EDemoActorFlags { } export class dDemo_actor_c extends TActor { - mName: string; - mFlags: number; - mTranslation = vec3.create(); - mScaling = vec3.create(); - mRotation = vec3.create(); - mShapeId: number; - mNextBckId: number; - mAnimationFrame: number; - mAnimationTransition: number; - mAnimationFrameMax: number; - mTexAnimation: number; - mTexAnimationFrame: number; - mTexAnimationFrameMax: number; - mModel: J3DModelInstance; + name: string; + flags: number; + translation = vec3.create(); + scaling = vec3.create(); + rotation = vec3.create(); + shapeId: number; + nextBckId: number; + animFrame: number; + animTransition: number; + animFrameMax: number; + texAnim: number; + texAnimFrame: number; + textAnimFrameMax: number; + model: J3DModelInstance; stbDataId: number; stbData: DataView; - mBckId: number; - mBtpId: number; - mBtkId: number; - mBrkId: number; + bckId: number; + btpId: number; + btkId: number; + brkId: number; constructor(public mActor: fopAc_ac_c) { super(); } checkEnable(mask: number) { - return this.mFlags & mask; + return this.flags & mask; } getMorfParam() { // Doesn't have anim properties - if ((this.mFlags & 0x40) == 0) { + if ((this.flags & 0x40) == 0) { // Has STB data - if ((this.mFlags & 1) == 0) { + if ((this.flags & 1) == 0) { return 0.0; } else { switch (this.stbDataId) { @@ -210,90 +210,90 @@ export class dDemo_actor_c extends TActor { } } } else { - return this.mAnimationTransition; + return this.animTransition; } } - override JSGGetName() { return this.mName; } + override JSGGetName() { return this.name; } override JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { debugger; // I think this may be one of the shapeInstanceState matrices instead - mat4.copy(mtx, this.mModel.modelMatrix); + mat4.copy(mtx, this.model.modelMatrix); return 1; } - override JSGGetAnimationFrameMax() { return this.mAnimationFrameMax; } - override JSGGetTextureAnimationFrameMax() { return this.mTexAnimationFrameMax; } + override JSGGetAnimationFrameMax() { return this.animFrameMax; } + override JSGGetTextureAnimationFrameMax() { return this.textAnimFrameMax; } - override JSGGetTranslation(dst: vec3) { vec3.copy(dst, this.mTranslation); } - override JSGGetScaling(dst: vec3) { vec3.copy(dst, this.mScaling); } + override JSGGetTranslation(dst: vec3) { vec3.copy(dst, this.translation); } + override JSGGetScaling(dst: vec3) { vec3.copy(dst, this.scaling); } override JSGGetRotation(dst: vec3) { - dst[0] = cM_sht2d(this.mRotation[0]); - dst[1] = cM_sht2d(this.mRotation[1]); - dst[2] = cM_sht2d(this.mRotation[2]); + dst[0] = cM_sht2d(this.rotation[0]); + dst[1] = cM_sht2d(this.rotation[1]); + dst[2] = cM_sht2d(this.rotation[2]); } override JSGSetData(id: number, data: DataView): void { this.stbDataId = id; this.stbData = data; // @TODO: Check that data makes sense - this.mFlags |= EDemoActorFlags.HasData; + this.flags |= EDemoActorFlags.HasData; } override JSGSetTranslation(src: ReadonlyVec3) { - vec3.copy(this.mTranslation, src); - this.mFlags |= EDemoActorFlags.HasPos; + vec3.copy(this.translation, src); + this.flags |= EDemoActorFlags.HasPos; } override JSGSetScaling(src: ReadonlyVec3) { - vec3.copy(this.mScaling, src); - this.mFlags |= EDemoActorFlags.HasScale; + vec3.copy(this.scaling, src); + this.flags |= EDemoActorFlags.HasScale; } override JSGSetRotation(src: ReadonlyVec3) { - this.mRotation[0] = cM_deg2s(src[0]); - this.mRotation[1] = cM_deg2s(src[1]); - this.mRotation[2] = cM_deg2s(src[2]); - this.mFlags |= EDemoActorFlags.HasRot; + this.rotation[0] = cM_deg2s(src[0]); + this.rotation[1] = cM_deg2s(src[1]); + this.rotation[2] = cM_deg2s(src[2]); + this.flags |= EDemoActorFlags.HasRot; } override JSGSetShape(id: number): void { - this.mShapeId = id; - this.mFlags |= EDemoActorFlags.HasShape + this.shapeId = id; + this.flags |= EDemoActorFlags.HasShape } override JSGSetAnimation(id: number): void { - this.mNextBckId = id; - this.mAnimationFrameMax = 3.402823e+38; - this.mFlags |= EDemoActorFlags.HasAnim; + this.nextBckId = id; + this.animFrameMax = 3.402823e+38; + this.flags |= EDemoActorFlags.HasAnim; } override JSGSetAnimationFrame(x: number): void { - this.mAnimationFrame = x; - this.mFlags |= EDemoActorFlags.HasFrame; + this.animFrame = x; + this.flags |= EDemoActorFlags.HasFrame; } override JSGSetAnimationTransition(x: number): void { - this.mAnimationTransition = x; - this.mFlags |= EDemoActorFlags.HasFrame; + this.animTransition = x; + this.flags |= EDemoActorFlags.HasFrame; } override JSGSetTextureAnimation(id: number): void { - this.mTexAnimation = id; - this.mFlags |= EDemoActorFlags.HasTexAnim; + this.texAnim = id; + this.flags |= EDemoActorFlags.HasTexAnim; } override JSGSetTextureAnimationFrame(x: number): void { - this.mTexAnimationFrame = x; - this.mFlags |= EDemoActorFlags.HasTexFrame; + this.texAnimFrame = x; + this.flags |= EDemoActorFlags.HasTexFrame; } } class dDemo_system_c implements TSystem { - private mpActiveCamera?: dDemo_camera_c; - private mpActors: dDemo_actor_c[] = []; - // private mpAmbient: dDemo_ambient_c; - // private mpLight: dDemo_light_c[]; - // private mpFog: dDemo_fog_c; + private activeCamera?: dDemo_camera_c; + private actors: dDemo_actor_c[] = []; + // private ambient: dDemo_ambient_c; + // private lights: dDemo_light_c[]; + // private fog: dDemo_fog_c; constructor( private globals: dGlobals @@ -302,8 +302,8 @@ class dDemo_system_c implements TSystem { public JSGFindObject(objName: string, objType: JStage.EObject): JStage.TObject | undefined { switch (objType) { case JStage.EObject.Camera: - if (this.mpActiveCamera) return this.mpActiveCamera; - else return this.mpActiveCamera = new dDemo_camera_c(this.globals); + if (this.activeCamera) return this.activeCamera; + else return this.activeCamera = new dDemo_camera_c(this.globals); case JStage.EObject.Actor: case JStage.EObject.PreExistingActor: @@ -317,12 +317,12 @@ class dDemo_system_c implements TSystem { return undefined; } } - if (!this.mpActors[actor.demoActorID]) { - actor.demoActorID = this.mpActors.length; - this.mpActors[actor.demoActorID] = new dDemo_actor_c(actor); - this.mpActors[actor.demoActorID].mName = objName; + if (!this.actors[actor.demoActorID]) { + actor.demoActorID = this.actors.length; + this.actors[actor.demoActorID] = new dDemo_actor_c(actor); + this.actors[actor.demoActorID].name = objName; }; - return this.mpActors[actor.demoActorID]; + return this.actors[actor.demoActorID]; case JStage.EObject.Ambient: case JStage.EObject.Light: @@ -333,81 +333,81 @@ class dDemo_system_c implements TSystem { } } - public getCamera() { return this.mpActiveCamera; } - public getActor(actorID: number) { return this.mpActors[actorID]; } + public getCamera() { return this.activeCamera; } + public getActor(actorID: number) { return this.actors[actorID]; } public remove() { - this.mpActiveCamera = undefined; + this.activeCamera = undefined; - for (let demoActor of this.mpActors) { demoActor.mActor.demoActorID = -1; } - this.mpActors = []; + for (let demoActor of this.actors) { demoActor.mActor.demoActorID = -1; } + this.actors = []; } } export class dDemo_manager_c { - private mFrame: number; - private mFrameNoMsg: number; - private mMode = EDemoMode.None; - private mCurFile?: ArrayBufferSlice; + private frame: number; + private frameNoMsg: number; + private mode = EDemoMode.None; + private curFile?: ArrayBufferSlice; - private mParser: TParse; - private mSystem = new dDemo_system_c(this.globals); - private mControl: TControl = new TControl(this.mSystem); + private parser: TParse; + private system = new dDemo_system_c(this.globals); + private control: TControl = new TControl(this.system); constructor( private globals: dGlobals ) { } - getFrame() { return this.mFrame; } - getFrameNoMsg() { return this.mFrameNoMsg; } - getMode() { return this.mMode; } - getSystem() { return this.mSystem; } + getFrame() { return this.frame; } + getFrameNoMsg() { return this.frameNoMsg; } + getMode() { return this.mode; } + getSystem() { return this.system; } public create(data: ArrayBufferSlice, originPos?: vec3, rotY?: number, startFrame?: number): boolean { - this.mParser = new TParse(this.mControl); + this.parser = new TParse(this.control); - if (!this.mParser.parse(data, 0)) { + if (!this.parser.parse(data, 0)) { console.error('Failed to parse demo data'); return false; } - this.mControl.forward(startFrame || 0); + this.control.forward(startFrame || 0); if (originPos) { - this.mControl.transformSetOrigin(originPos, rotY || 0); + this.control.transformSetOrigin(originPos, rotY || 0); } - this.mFrame = 0; - this.mFrameNoMsg = 0; - this.mCurFile = data; - this.mMode = EDemoMode.Playing; + this.frame = 0; + this.frameNoMsg = 0; + this.curFile = data; + this.mode = EDemoMode.Playing; return true; } public remove() { - this.mControl.destroyObject_all(); - this.mSystem.remove(); - this.mCurFile = undefined; - this.mMode = 0; + this.control.destroyObject_all(); + this.system.remove(); + this.curFile = undefined; + this.mode = 0; } public update(): boolean { - if (!this.mCurFile) { + if (!this.curFile) { return false; } const dtFrames = this.globals.context.viewerInput.deltaTime / 1000.0 * 30; // noclip modification: If a demo is suspended (waiting for the user to interact with a message), just resume - if (this.mControl.isSuspended()) { this.mControl.setSuspend(0); } + if (this.control.isSuspended()) { this.control.setSuspend(0); } - if (this.mControl.forward(dtFrames)) { - this.mFrame += dtFrames; - if (!this.mControl.isSuspended()) { - this.mFrameNoMsg += dtFrames; + if (this.control.forward(dtFrames)) { + this.frame += dtFrames; + if (!this.control.isSuspended()) { + this.frameNoMsg += dtFrames; } } else { - this.mMode = EDemoMode.Ended; + this.mode = EDemoMode.Ended; } return true; } @@ -426,43 +426,43 @@ export function dDemo_setDemoData(globals: dGlobals, dtFrames: number, actor: fo if (enable & 2) { // actor.current.pos = demoActor.mTranslation; // actor.old.pos = actor.current.pos; - vec3.copy(actor.pos, demoActor.mTranslation); + vec3.copy(actor.pos, demoActor.translation); } if (enable & 8) { // actor.shape_angle = demoActor.mRotation; // actor.current.angle = actor.shape_angle; - vec3.copy(actor.rot, demoActor.mRotation); + vec3.copy(actor.rot, demoActor.rotation); } if (enable & 4) { - actor.scale = demoActor.mScaling; + actor.scale = demoActor.scaling; } if (!morf) return true; - demoActor.mModel = morf.model; + demoActor.model = morf.model; - if ((enable & 0x20) && (demoActor.mNextBckId != demoActor.mBckId)) { - const bckID = demoActor.mNextBckId; + if ((enable & 0x20) && (demoActor.nextBckId != demoActor.bckId)) { + const bckID = demoActor.nextBckId; if (bckID & 0x10000) arcName = globals.roomCtrl.demoArcName; assert(!!arcName); - demoActor.mBckId = bckID; + demoActor.bckId = bckID; const i_key = globals.resCtrl.getObjectIDRes(ResType.Bck, arcName, bckID); assert(!!i_key); // void* i_sound = dDemo_getJaiPointer(a_name, bck, soundCount, soundIdxs); morf.setAnm(i_key, -1 as LoopMode, demoActor.getMorfParam(), 1.0, 0.0, -1.0); - demoActor.mAnimationFrameMax = morf.frameCtrl.endFrame; + demoActor.animFrameMax = morf.frameCtrl.endFrame; } if (enable & 0x40) { - if (demoActor.mAnimationFrame > dtFrames) { - morf.frameCtrl.setFrame(demoActor.mAnimationFrame - dtFrames); + if (demoActor.animFrame > dtFrames) { + morf.frameCtrl.setFrame(demoActor.animFrame - dtFrames); morf.play(dtFrames); } else { - morf.frameCtrl.setFrame(demoActor.mAnimationFrame); + morf.frameCtrl.setFrame(demoActor.animFrame); } } else { morf.play(dtFrames); From fda16a9ac53f348d6b7d7dc017d06b5f11112f8d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 10:46:02 -0700 Subject: [PATCH 087/102] d_demo: Remove 'm' from member of DemoActor --- src/ZeldaWindWaker/d_demo.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 275ede5ba..6c675e311 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -186,7 +186,7 @@ export class dDemo_actor_c extends TActor { btkId: number; brkId: number; - constructor(public mActor: fopAc_ac_c) { super(); } + constructor(public actor: fopAc_ac_c) { super(); } checkEnable(mask: number) { return this.flags & mask; @@ -339,7 +339,7 @@ class dDemo_system_c implements TSystem { public remove() { this.activeCamera = undefined; - for (let demoActor of this.actors) { demoActor.mActor.demoActorID = -1; } + for (let demoActor of this.actors) { demoActor.actor.demoActorID = -1; } this.actors = []; } } From ee17e47efb108b2f9f38921412a3e50c40a75817 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 19:28:55 -0700 Subject: [PATCH 088/102] JStudio: `public` before all member variables --- src/Common/JSYSTEM/JStudio.ts | 329 +++++++++++++++++----------------- 1 file changed, 164 insertions(+), 165 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 71a07f609..9edf9dbee 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -29,20 +29,20 @@ export namespace JStage { }; export abstract class TObject { - JSGFDisableFlag(flag: number): void { this.JSGSetFlag(this.JSGGetFlag() & ~flag); } - JSGFEnableFlag(flag: number): void { this.JSGSetFlag(this.JSGGetFlag() | flag); } - - abstract JSGFGetType(): number; - JSGGetName(): string | undefined { return undefined; } - JSGGetFlag(): number { return 0; } - JSGSetFlag(flag: number): void { } - JSGGetData(unk0: number, data: Object, unk1: number): boolean { return false; } - JSGSetData(id: number, data: DataView): void { } - JSGGetParent(parentDst: JStage.TObject, unk: { x: number }): void { } - JSGSetParent(parent: JStage.TObject | null, unk: number): void { } - JSGSetRelation(related: boolean, obj: JStage.TObject, unk: number): void { } - JSGFindNodeID(id: string): number { return -1; } - JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { + public JSGFDisableFlag(flag: number): void { this.JSGSetFlag(this.JSGGetFlag() & ~flag); } + public JSGFEnableFlag(flag: number): void { this.JSGSetFlag(this.JSGGetFlag() | flag); } + + public abstract JSGFGetType(): number; + public JSGGetName(): string | undefined { return undefined; } + public JSGGetFlag(): number { return 0; } + public JSGSetFlag(flag: number): void { } + public JSGGetData(unk0: number, data: Object, unk1: number): boolean { return false; } + public JSGSetData(id: number, data: DataView): void { } + public JSGGetParent(parentDst: JStage.TObject, unk: { x: number }): void { } + public JSGSetParent(parent: JStage.TObject | null, unk: number): void { } + public JSGSetRelation(related: boolean, obj: JStage.TObject, unk: number): void { } + public JSGFindNodeID(id: string): number { return -1; } + public JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { mat4.identity(mtx); return 0; } @@ -75,10 +75,10 @@ class TVariableValue { private updateParam: number | FVB.TFunctionValue | undefined; private outputFunc?: (val: number, adaptor: TAdaptor) => void; - getValue() { return this.value; } - getValueU8() { return clamp(this.value, 0, 255); } + public getValue() { return this.value; } + public getValueU8() { return clamp(this.value, 0, 255); } - forward(frameCount: number) { + public forward(frameCount: number) { if (Number.MAX_VALUE - this.age <= frameCount) { this.age = Number.MAX_VALUE; } else { @@ -86,7 +86,7 @@ class TVariableValue { } } - update(secondsPerFrame: number, adaptor: TAdaptor): void { + public update(secondsPerFrame: number, adaptor: TAdaptor): void { if (this.updateFunc) { this.updateFunc(this, secondsPerFrame); if (this.outputFunc) this.outputFunc(this.value, adaptor); @@ -120,7 +120,7 @@ class TVariableValue { } // Value will be set only on next update - setValue_immediate(v: number): void { + public setValue_immediate(v: number): void { assert(v !== undefined); this.updateFunc = TVariableValue.update_immediate; this.age = 0; @@ -128,7 +128,7 @@ class TVariableValue { } // Value will be set to (mAge * v * x) each frame - setValue_time(v: number): void { + public setValue_time(v: number): void { assert(v !== undefined); this.updateFunc = TVariableValue.update_time; this.age = 0; @@ -136,7 +136,7 @@ class TVariableValue { } // Value will be the result of a Function Value each frame - setValue_functionValue(v?: FVB.TFunctionValue): void { + public setValue_functionValue(v?: FVB.TFunctionValue): void { assert(v !== undefined); this.updateFunc = TVariableValue.update_functionValue; this.age = 0; @@ -146,7 +146,7 @@ class TVariableValue { //-------------------- // Set Output //-------------------- - setOutput(outputFunc?: (val: number, adaptor: TAdaptor) => void) { + public setOutput(outputFunc?: (val: number, adaptor: TAdaptor) => void) { this.outputFunc = outputFunc; } } @@ -231,14 +231,14 @@ abstract class TAdaptor { public enableLogging = true, ) { } - abstract adaptor_do_prepare(obj: STBObject): void; - abstract adaptor_do_begin(obj: STBObject): void; - abstract adaptor_do_end(obj: STBObject): void; - abstract adaptor_do_update(obj: STBObject, frameCount: number): void; - abstract adaptor_do_data(obj: STBObject, id: number, data: DataView): void; + public abstract adaptor_do_prepare(obj: STBObject): void; + public abstract adaptor_do_begin(obj: STBObject): void; + public abstract adaptor_do_end(obj: STBObject): void; + public abstract adaptor_do_update(obj: STBObject, frameCount: number): void; + public abstract adaptor_do_data(obj: STBObject, id: number, data: DataView): void; // Set a single VariableValue update function, with the option of using FuncVals - adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: EDataOp, data: DataVal) { + public adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: EDataOp, data: DataVal) { const varval = this.variableValues[keyIdx]; const control = obj.control; @@ -256,21 +256,21 @@ abstract class TAdaptor { } // Immediately set 3 consecutive VariableValue update functions from a single vec3 - adaptor_setVariableValue_Vec(startKeyIdx: number, data: vec3) { + public adaptor_setVariableValue_Vec(startKeyIdx: number, data: vec3) { this.variableValues[startKeyIdx + 0].setValue_immediate(data[0]); this.variableValues[startKeyIdx + 1].setValue_immediate(data[1]); this.variableValues[startKeyIdx + 2].setValue_immediate(data[2]); } // Get the current value of 3 consecutive VariableValues, as a vector. E.g. Camera position. - adaptor_getVariableValue_Vec(dst: vec3, startKeyIdx: number) { + public adaptor_getVariableValue_Vec(dst: vec3, startKeyIdx: number) { dst[0] = this.variableValues[startKeyIdx + 0].getValue(); dst[1] = this.variableValues[startKeyIdx + 1].getValue(); dst[2] = this.variableValues[startKeyIdx + 2].getValue(); } // Immediately set 4 consecutive VariableValue update functions from a single GXColor (4 bytes) - adaptor_setVariableValue_GXColor(startKeyIdx: number, data: GfxColor) { + public adaptor_setVariableValue_GXColor(startKeyIdx: number, data: GfxColor) { debugger; // @TODO: Confirm that all uses of this always have consecutive keyIdxs. JStudio remaps them. this.variableValues[startKeyIdx + 0].setValue_immediate(data.r); this.variableValues[startKeyIdx + 1].setValue_immediate(data.g); @@ -279,14 +279,14 @@ abstract class TAdaptor { } // Get the current value of 4 consecutive VariableValues, as a GXColor. E.g. Fog color. - adaptor_getVariableValue_GXColor(dst: GfxColor, startKeyIdx: number) { + public adaptor_getVariableValue_GXColor(dst: GfxColor, startKeyIdx: number) { dst.r = this.variableValues[startKeyIdx + 0].getValue(); dst.g = this.variableValues[startKeyIdx + 1].getValue(); dst.b = this.variableValues[startKeyIdx + 2].getValue(); dst.a = this.variableValues[startKeyIdx + 2].getValue(); } - adaptor_updateVariableValue(obj: STBObject, frameCount: number) { + public adaptor_updateVariableValue(obj: STBObject, frameCount: number) { const control = obj.control; for (let vv of this.variableValues) { vv.forward(frameCount); @@ -294,7 +294,7 @@ abstract class TAdaptor { } } - log(msg: string) { + public log(msg: string) { if (this.enableLogging) { console.debug(`[${this.object.JSGGetName()}] ${msg}`); } } } @@ -335,23 +335,23 @@ abstract class STBObject { } // These are intended to be overridden by subclasses - abstract do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void; - do_begin() { if (this.adaptor) this.adaptor.adaptor_do_begin(this); } - do_end() { if (this.adaptor) this.adaptor.adaptor_do_end(this); } + public abstract do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void; + public do_begin() { if (this.adaptor) this.adaptor.adaptor_do_begin(this); } + public do_end() { if (this.adaptor) this.adaptor.adaptor_do_end(this); } // Done updating this frame. Compute our variable data (i.e. interpolate) and send to the game object. - do_wait(frameCount: number) { + public do_wait(frameCount: number) { if (this.adaptor) this.adaptor.adaptor_updateVariableValue(this, frameCount); if (this.adaptor) this.adaptor.adaptor_do_update(this, frameCount); } - do_data(id: number, data: DataView) { if (this.adaptor) this.adaptor.adaptor_do_data(this, id, data); } + public do_data(id: number, data: DataView) { if (this.adaptor) this.adaptor.adaptor_do_data(this, id, data); } - getStatus() { return this.status; } - getSuspendFrames(): number { return this.suspendFrames; } - isSuspended(): boolean { return this.suspendFrames > 0; } - setSuspend(frameCount: number) { this.suspendFrames = frameCount; } + public getStatus() { return this.status; } + public getSuspendFrames(): number { return this.suspendFrames; } + public isSuspended(): boolean { return this.suspendFrames > 0; } + public setSuspend(frameCount: number) { this.suspendFrames = frameCount; } - reset(blockObj: TBlockObject) { + public reset(blockObj: TBlockObject) { this.sequence = 0; this.status = EStatus.Still; this.sequenceNext = 0xC + align(blockObj.id.length + 1, 4); @@ -359,7 +359,7 @@ abstract class STBObject { this.wait = 0; } - forward(frameCount: number): boolean { + public forward(frameCount: number): boolean { let hasWaited = false; while (true) { // Top bit of mFlags makes this object immediately inactive, restarting any existing sequence @@ -524,7 +524,7 @@ class TControlObject extends STBObject { super(control) } - override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { } + public override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { } } @@ -570,27 +570,27 @@ function keyToString(enumValue: EActorTrack, count: number) { } export abstract class TActor extends JStage.TObject { - JSGFGetType() { return JStage.EObject.Actor; } - JSGGetTranslation(dst: vec3) { } - JSGSetTranslation(src: ReadonlyVec3) { } - JSGGetScaling(dst: vec3) { } - JSGSetScaling(src: ReadonlyVec3) { } - JSGGetRotation(dst: vec3) { } - JSGSetRotation(src: ReadonlyVec3) { } - JSGGetShape(): number { return -1; } - JSGSetShape(x: number): void { } - JSGGetAnimation(): number { return -1; } - JSGSetAnimation(x: number): void { } - JSGGetAnimationFrame(): number { return 0.0; } - JSGSetAnimationFrame(x: number): void { } - JSGGetAnimationFrameMax(): number { return 0.0; } - JSGGetAnimationTransition(): number { return 0.0; } - JSGSetAnimationTransition(x: number): void { } - JSGGetTextureAnimation(): number { return -1; } - JSGSetTextureAnimation(x: number): void { } - JSGGetTextureAnimationFrame(): number { return 0.0; } - JSGSetTextureAnimationFrame(x: number): void { } - JSGGetTextureAnimationFrameMax(): number { return 0.0; } + public JSGFGetType() { return JStage.EObject.Actor; } + public JSGGetTranslation(dst: vec3) { } + public JSGSetTranslation(src: ReadonlyVec3) { } + public JSGGetScaling(dst: vec3) { } + public JSGSetScaling(src: ReadonlyVec3) { } + public JSGGetRotation(dst: vec3) { } + public JSGSetRotation(src: ReadonlyVec3) { } + public JSGGetShape(): number { return -1; } + public JSGSetShape(x: number): void { } + public JSGGetAnimation(): number { return -1; } + public JSGSetAnimation(x: number): void { } + public JSGGetAnimationFrame(): number { return 0.0; } + public JSGSetAnimationFrame(x: number): void { } + public JSGGetAnimationFrameMax(): number { return 0.0; } + public JSGGetAnimationTransition(): number { return 0.0; } + public JSGSetAnimationTransition(x: number): void { } + public JSGGetTextureAnimation(): number { return -1; } + public JSGSetTextureAnimation(x: number): void { } + public JSGGetTextureAnimationFrame(): number { return 0.0; } + public JSGSetTextureAnimationFrame(x: number): void { } + public JSGGetTextureAnimationFrameMax(): number { return 0.0; } } class TActorAdaptor extends TAdaptor { @@ -618,7 +618,7 @@ class TActorAdaptor extends TAdaptor { return frame; } - adaptor_do_prepare(obj: STBObject): void { + public adaptor_do_prepare(obj: STBObject): void { this.variableValues[EActorTrack.AnimTransition].setOutput(this.object.JSGSetAnimationTransition.bind(this.object)); this.variableValues[EActorTrack.AnimFrame].setOutput((frame: number, adaptor: TAdaptor) => { @@ -632,7 +632,7 @@ class TActorAdaptor extends TAdaptor { }); } - adaptor_do_begin(obj: STBObject): void { + public adaptor_do_begin(obj: STBObject): void { this.object.JSGFEnableFlag(1); const pos = scratchVec3a; @@ -656,11 +656,11 @@ class TActorAdaptor extends TAdaptor { this.variableValues[EActorTrack.AnimFrame].setValue_immediate(this.object.JSGGetTextureAnimationFrame()); } - adaptor_do_end(obj: STBObject): void { + public adaptor_do_end(obj: STBObject): void { this.object.JSGFDisableFlag(1); } - adaptor_do_update(obj: STBObject, frameCount: number): void { + public adaptor_do_update(obj: STBObject, frameCount: number): void { const pos = scratchVec3a; const rot = scratchVec3b; const scale = scratchVec3c; @@ -678,18 +678,18 @@ class TActorAdaptor extends TAdaptor { this.object.JSGSetScaling(scale); } - adaptor_do_data(obj: STBObject, id: number, data: DataView): void { + public adaptor_do_data(obj: STBObject, id: number, data: DataView): void { this.log(`SetData: ${id}`); this.object.JSGSetData(id, data); } - adaptor_do_PARENT(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_PARENT(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectName); this.log(`SetParent: ${data.asStr}`); this.parent = this.system.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); } - adaptor_do_PARENT_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_PARENT_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { debugger; this.log(`SetParentNode: ${data}`); switch (dataOp) { @@ -704,20 +704,20 @@ class TActorAdaptor extends TAdaptor { } } - adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { + public adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { assert(dataOp == EDataOp.Immediate); this.log(`SetParentEnable: ${data}`); if (data) { this.object.JSGSetParent(this.parent!, this.parentNodeID); } else { this.object.JSGSetParent(null, 0xFFFFFFFF); } } - adaptor_do_RELATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_RELATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectName); this.log(`SetRelation: ${data.asStr!}`); this.relation = this.system.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); } - adaptor_do_RELATION_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_RELATION_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { debugger; this.log(`SetRelationNode: ${data}`); switch (dataOp) { @@ -732,37 +732,37 @@ class TActorAdaptor extends TAdaptor { } } - adaptor_do_RELATION_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { + public adaptor_do_RELATION_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { assert(dataOp == EDataOp.Immediate); this.log(`SetRelationEnable: ${data}`); this.object.JSGSetRelation(!!data, this.relation!, this.relationNodeID); } - adaptor_do_SHAPE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_SHAPE(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectIdx); this.log(`SetShape: ${data.asInt!}`); this.object.JSGSetShape(data.asInt!); } - adaptor_do_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectIdx); this.log(`SetAnimation: ${(data.asInt!) & 0xFFFF} (${(data.asInt!) >> 4 & 0x01})`); this.object.JSGSetAnimation(data.asInt!); } - adaptor_do_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.Immediate); this.log(`SetAnimationMode: ${data.asInt!}`); this.animMode = data.asInt!; } - adaptor_do_TEXTURE_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_TEXTURE_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.ObjectIdx); this.log(`SetTexAnim: ${data}`); this.object.JSGSetTextureAnimation(data.asInt!); } - adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { assert(dataOp == EDataOp.Immediate); this.log(`SetTexAnimMode: ${data}`); this.animTexMode = data.asInt!; @@ -778,7 +778,7 @@ class TActorObject extends STBObject { stageObj: JStage.TObject, ) { super(control, blockObj, new TActorAdaptor(control.system, stageObj as TActor)) } - override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { + public override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { const dataOp = (param & 0x1F) as EDataOp; const cmdType = param >> 5; @@ -892,29 +892,29 @@ function camKeyToString(enumValue: ECameraTrack, count: number) { } export abstract class TCamera extends JStage.TObject { - JSGFGetType() { return JStage.EObject.Camera; } - JSGGetProjectionType() { return true; } - JSGSetProjectionType(type: number) { } - JSGGetProjectionNear() { return 0.0; } - JSGSetProjectionNear(near: number) { } - JSGGetProjectionFar() { return Number.MAX_VALUE; } - JSGSetProjectionFar(far: number) { } - JSGGetProjectionFovy() { return 0.0 }; - JSGSetProjectionFovy(fovy: number) { }; - JSGGetProjectionAspect() { return 0.0 }; - JSGSetProjectionAspect(aspect: number) { }; - JSGGetProjectionField() { return 0.0 }; - JSGSetProjectionField(field: number) { }; - JSGGetViewType() { return true; }; - JSGSetViewType(type: number) { } - JSGGetViewPosition(dst: vec3) { vec3.zero(dst); } - JSGSetViewPosition(v: ReadonlyVec3) { } - JSGGetViewUpVector(dst: vec3) { vec3.zero(dst); } - JSGSetViewUpVector(v: ReadonlyVec3) { } - JSGGetViewTargetPosition(dst: vec3) { vec3.zero(dst); } - JSGSetViewTargetPosition(v: ReadonlyVec3) { } - JSGGetViewRoll() { return 0.0 }; - JSGSetViewRoll(roll: number) { }; + public JSGFGetType() { return JStage.EObject.Camera; } + public JSGGetProjectionType() { return true; } + public JSGSetProjectionType(type: number) { } + public JSGGetProjectionNear() { return 0.0; } + public JSGSetProjectionNear(near: number) { } + public JSGGetProjectionFar() { return Number.MAX_VALUE; } + public JSGSetProjectionFar(far: number) { } + public JSGGetProjectionFovy() { return 0.0 }; + public JSGSetProjectionFovy(fovy: number) { }; + public JSGGetProjectionAspect() { return 0.0 }; + public JSGSetProjectionAspect(aspect: number) { }; + public JSGGetProjectionField() { return 0.0 }; + public JSGSetProjectionField(field: number) { }; + public JSGGetViewType() { return true; }; + public JSGSetViewType(type: number) { } + public JSGGetViewPosition(dst: vec3) { vec3.zero(dst); } + public JSGSetViewPosition(v: ReadonlyVec3) { } + public JSGGetViewUpVector(dst: vec3) { vec3.zero(dst); } + public JSGSetViewUpVector(v: ReadonlyVec3) { } + public JSGGetViewTargetPosition(dst: vec3) { vec3.zero(dst); } + public JSGSetViewTargetPosition(v: ReadonlyVec3) { } + public JSGGetViewRoll() { return 0.0 }; + public JSGSetViewRoll(roll: number) { }; } class TCameraAdaptor extends TAdaptor { @@ -922,14 +922,14 @@ class TCameraAdaptor extends TAdaptor { override object: TCamera ) { super(11); } - adaptor_do_prepare(obj: STBObject): void { + public adaptor_do_prepare(obj: STBObject): void { this.variableValues[ECameraTrack.FovY].setOutput(this.object.JSGSetProjectionFovy.bind(this.object)); this.variableValues[ECameraTrack.Roll].setOutput(this.object.JSGSetViewRoll.bind(this.object)); this.variableValues[ECameraTrack.DistNear].setOutput(this.object.JSGSetProjectionNear.bind(this.object)); this.variableValues[ECameraTrack.DistFar].setOutput(this.object.JSGSetProjectionFar.bind(this.object)); } - adaptor_do_begin(obj: STBObject): void { + public adaptor_do_begin(obj: STBObject): void { const camPos = scratchVec3a; const targetPos = scratchVec3b; this.object.JSGGetViewPosition(camPos); @@ -946,11 +946,11 @@ class TCameraAdaptor extends TAdaptor { this.variableValues[ECameraTrack.DistFar].setValue_immediate(this.object.JSGGetProjectionFar()); } - adaptor_do_end(obj: STBObject): void { + public adaptor_do_end(obj: STBObject): void { this.object.JSGFDisableFlag(1); } - adaptor_do_update(obj: STBObject, frameCount: number): void { + public adaptor_do_update(obj: STBObject, frameCount: number): void { const camPos = scratchVec3a; const targetPos = scratchVec3b; @@ -964,21 +964,21 @@ class TCameraAdaptor extends TAdaptor { this.object.JSGSetViewTargetPosition(targetPos); } - adaptor_do_data(obj: STBObject, id: number, data: DataView): void { + public adaptor_do_data(obj: STBObject, id: number, data: DataView): void { // This is not used by TWW. Untested. debugger; } // Custom adaptor functions. These can be called from within TCameraObject::do_paragraph() - adaptor_do_PARENT(dataOp: EDataOp, data: number | string, unk0: number): void { + public adaptor_do_PARENT(dataOp: EDataOp, data: number | string, unk0: number): void { debugger; } - adaptor_do_PARENT_NODE(dataOp: EDataOp, data: number | string, unk0: number): void { + public adaptor_do_PARENT_NODE(dataOp: EDataOp, data: number | string, unk0: number): void { debugger; } - adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number | string, unk0: number): void { + public adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number | string, unk0: number): void { debugger; } } @@ -990,7 +990,7 @@ class TCameraObject extends STBObject { stageObj: JStage.TObject, ) { super(control, blockObj, new TCameraAdaptor(stageObj as TCamera)) } - override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { + public override do_paragraph(file: Reader, dataSize: number, dataOffset: number, param: number): void { const dataOp = (param & 0x1F) as EDataOp; const cmdType = param >> 5; @@ -1058,7 +1058,7 @@ class TParagraph { dataOffset: number; nextOffset: number; - static parse(view: DataView, byteIdx: number): TParagraph { + public static parse(view: DataView, byteIdx: number): TParagraph { // The top bit of the paragraph determines if the type and size are 16 bit (if set), or 32 (if not set) let dataSize = view.getUint16(byteIdx); let type; @@ -1129,15 +1129,15 @@ namespace FVB { protected refer?: Attribute.Refer; protected interpolate?: Attribute.Interpolate; - abstract getType(): EFuncValType; - abstract prepare(): void; - abstract getValue(arg: number): number; + public abstract getType(): EFuncValType; + public abstract prepare(): void; + public abstract getValue(arg: number): number; - getAttrRange() { return this.range; } - getAttrRefer() { return this.refer; } - getAttrInterpolate() { return this.interpolate; } + public getAttrRange() { return this.range; } + public getAttrRefer() { return this.refer; } + public getAttrInterpolate() { return this.interpolate; } - static toFunction_outside(type: EExtrapolationType): (frame: number, maxFrame: number) => number { + public static toFunction_outside(type: EExtrapolationType): (frame: number, maxFrame: number) => number { switch (type) { case EExtrapolationType.Raw: return (f, m) => f; case EExtrapolationType.Repeat: return (f, m) => { f = f % m; return f < 0 ? f + m : f; } @@ -1159,9 +1159,9 @@ namespace FVB { this.id = block.id; } - abstract prepare_data(para: TParagraph, control: TControl, file: Reader): void; + public abstract prepare_data(para: TParagraph, control: TControl, file: Reader): void; - prepare(block: TBlock, pControl: TControl, file: Reader) { + public prepare(block: TBlock, pControl: TControl, file: Reader) { const blockNext = file.offset + block.size; file.offset = blockNext; @@ -1328,18 +1328,18 @@ namespace FVB { private progress: number = 0; private adjust: number = 0; - prepare() { + public prepare() { // Progress updated here } - set(begin: number, end: number) { + public set(begin: number, end: number) { this.begin = begin; this.end = end; this.diff = end - begin; assert(this.diff >= 0); } - getParameter(time: number, startTime: number, endTime: number): number { + public getParameter(time: number, startTime: number, endTime: number): number { // @NOTE: Does not currently support, Progress, Adjust, or Outside modifications. These can only be set // in an FVB paragraph, so attempt to set them will be caught in FVB.TObject.prepare(). return time; @@ -1352,15 +1352,15 @@ namespace FVB { export class Interpolate { private type = EInterpolateType.None; - prepare() { } - set(type: EInterpolateType) { this.type = type; } - get() { return this.type; } + public prepare() { } + public set(type: EInterpolateType) { this.type = type; } + public get() { return this.type; } - static Linear(t: number, t0: number, v0: number, t1: number, v1: number) { + public static Linear(t: number, t0: number, v0: number, t1: number, v1: number) { return v0 + ((v1 - v0) * (t - t0)) / (t1 - t0); } - static BSpline_Nonuniform(t: number, controlPoints: Float64Array, knotVector: Float64Array) { + public static BSpline_Nonuniform(t: number, controlPoints: Float64Array, knotVector: Float64Array) { const knot0 = knotVector[0]; const knot1 = knotVector[1]; const knot2 = knotVector[2]; @@ -1387,7 +1387,7 @@ namespace FVB { return (term1 * controlPoints[0]) + (term2 * controlPoints[1]) + (term3 * controlPoints[2]) + (term4 * controlPoints[3]); } - static Hermite(c0: number, c1: number, x: number, c2: number, x2: number, c3: number, x3: number) { + public static Hermite(c0: number, c1: number, x: number, c2: number, x2: number, c3: number, x3: number) { let a: number; let b: number; let c: number; @@ -1413,7 +1413,7 @@ namespace FVB { class TObject_Constant extends FVB.TObject { override funcVal = new FunctionValue_Constant; - override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + public override prepare_data(para: TParagraph, control: TControl, file: Reader): void { assert(para.dataSize == 4); const value = file.view.getFloat32(para.dataOffset); this.funcVal.setData(value); @@ -1423,10 +1423,10 @@ namespace FVB { class FunctionValue_Constant extends TFunctionValue { private value: number = 0; - getType() { return EFuncValType.Constant; } - prepare() { } - setData(value: number) { this.value = value; } - getValue(timeSec: number) { + public getType() { return EFuncValType.Constant; } + public prepare() { } + public setData(value: number) { this.value = value; } + public getValue(timeSec: number) { return this.value; } } @@ -1438,7 +1438,7 @@ namespace FVB { class TObject_ListParameter extends FVB.TObject { override funcVal = new FunctionValue_ListParameter; - override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + public override prepare_data(para: TParagraph, control: TControl, file: Reader): void { assert(para.dataSize >= 8); // Each Key contains 2 floats, a time and value const keyCount = file.view.getUint32(para.dataOffset + 0); @@ -1456,7 +1456,7 @@ namespace FVB { private curKeyIdx: number; private interpFunc: (t: number) => number; - prepare(): void { + public prepare(): void { this.range.prepare(); this.interpolate.prepare(); @@ -1476,18 +1476,18 @@ namespace FVB { } } - setData(values: Float32Array) { + public setData(values: Float32Array) { this.keys = values; this.keyCount = values.length / 2; this.curKeyIdx = 0; } - getType() { return EFuncValType.ListParameter; } - getStartTime() { return this.keys[0]; } - getEndTime(): number { return this.keys[this.keys.length - 2]; } + public getType() { return EFuncValType.ListParameter; } + public getStartTime() { return this.keys[0]; } + public getEndTime(): number { return this.keys[this.keys.length - 2]; } // Interpolate between our keyframes, given the current time - getValue(timeSec: number): number { + public getValue(timeSec: number): number { // Remap (if requested) the time to our range const t = this.range.getParameter(timeSec, this.getStartTime(), this.getEndTime()); @@ -1510,7 +1510,7 @@ namespace FVB { return value; } - interpolateBSpline(t: number): number { + public interpolateBSpline(t: number): number { const c = this.curKeyIdx * 2; const controlPoints = new Float64Array(4); @@ -1588,18 +1588,18 @@ namespace FVB { return Attribute.Interpolate.BSpline_Nonuniform(t, controlPoints, knotVector); } - interpolateNone(t: number) { + public interpolateNone(t: number) { debugger; // Untested. Remove after confirmed working. return this.keys[this.curKeyIdx]; } - interpolateLinear(t: number) { + public interpolateLinear(t: number) { const ks = this.keys; const c = this.curKeyIdx * 2; return Attribute.Interpolate.Linear(t, ks[c - 2], ks[c - 1], ks[c + 0], ks[c + 1]); } - interpolatePlateau(t: number) { + public interpolatePlateau(t: number) { console.error('Plateau interpolation not yet implemented') debugger; // Untested. Remove after confirmed working. return this.interpolateNone(t); @@ -1625,7 +1625,7 @@ namespace FVB { class TObject_Composite extends FVB.TObject { override funcVal = new FunctionValue_Composite; - override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + public override prepare_data(para: TParagraph, control: TControl, file: Reader): void { assert(para.dataSize >= 8); const compositeOp = file.view.getUint32(para.dataOffset + 0); @@ -1654,14 +1654,14 @@ namespace FVB { class FunctionValue_Composite extends TFunctionValue { protected override refer = new Attribute.Refer(); - override prepare(): void { } - override getType(): EFuncValType { return EFuncValType.Composite; } - setData(func: (ref: TFunctionValue[], dataVal: number, t: number) => number, dataVal: number) { + public override prepare(): void { } + public override getType(): EFuncValType { return EFuncValType.Composite; } + public setData(func: (ref: TFunctionValue[], dataVal: number, t: number) => number, dataVal: number) { this.func = func; this.dataVal = dataVal; } - getValue(timeSec: number): number { + public getValue(timeSec: number): number { return this.func(this.refer.fvs, this.dataVal, timeSec); } @@ -1713,7 +1713,6 @@ namespace FVB { return val / dataVal; } - private func: (ref: TFunctionValue[], dataVal: number, t: number) => number; private dataVal: number; } @@ -1723,9 +1722,9 @@ namespace FVB { // Use hermite interpolation to compute a value from a list //---------------------------------------------------------------------------------------------------------------------- class TObject_Hermite extends FVB.TObject { - override funcVal = new FunctionValue_Hermite; + public override funcVal = new FunctionValue_Hermite; - override prepare_data(para: TParagraph, control: TControl, file: Reader): void { + public override prepare_data(para: TParagraph, control: TControl, file: Reader): void { assert(para.dataSize >= 8); const keyCount = file.view.getUint32(para.dataOffset + 0) & 0xFFFFFFF; @@ -1746,9 +1745,9 @@ namespace FVB { private curKeyIdx: number; private stride: number; - prepare(): void { this.range.prepare(); } + public prepare(): void { this.range.prepare(); } - setData(values: Float32Array, stride: number) { + public setData(values: Float32Array, stride: number) { assert(stride == 3 || stride == 4); this.stride = stride this.keys = values; @@ -1756,11 +1755,11 @@ namespace FVB { this.curKeyIdx = 0; } - getType() { return EFuncValType.ListParameter; } - getStartTime() { return this.keys[0]; } - getEndTime(): number { return this.keys[(this.keyCount - 1) * this.stride]; } + public getType() { return EFuncValType.ListParameter; } + public getStartTime() { return this.keys[0]; } + public getEndTime(): number { return this.keys[(this.keyCount - 1) * this.stride]; } - getValue(timeSec: number): number { + public getValue(timeSec: number): number { // @TODO: Support range parameters like Outside // Remap (if requested) the time to our range From b3080840a690041a81e3c1dd5066fc7dcd178e02 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 19:30:03 -0700 Subject: [PATCH 089/102] d_demo: `public` before all member variables --- src/ZeldaWindWaker/d_demo.ts | 80 ++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 6c675e311..2c6000ca6 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -43,21 +43,21 @@ class dDemo_camera_c extends TCamera { private globals: dGlobals ) { super() } - override JSGGetName() { return 'Cam'; } + public override JSGGetName() { return 'Cam'; } - override JSGGetProjectionNear(): number { + public override JSGGetProjectionNear(): number { const camera = this.globals.camera; if (!camera) return 0.0; return camera.near; } - override JSGSetProjectionNear(v: number) { + public override JSGSetProjectionNear(v: number) { this.projNear = v; this.flags |= EDemoCamFlags.HasNearZ; } - override JSGGetProjectionFar(): number { + public override JSGGetProjectionFar(): number { const camera = this.globals.camera; if (!camera) return 1.0; @@ -65,13 +65,13 @@ class dDemo_camera_c extends TCamera { } - override JSGSetProjectionFar(v: number): void { + public override JSGSetProjectionFar(v: number): void { this.projFar = v; this.flags |= EDemoCamFlags.HasFarZ; } - override JSGGetProjectionFovy(): number { + public override JSGGetProjectionFovy(): number { const camera = this.globals.camera; if (!camera) return 60.0; @@ -79,13 +79,13 @@ class dDemo_camera_c extends TCamera { } - override JSGSetProjectionFovy(v: number): void { + public override JSGSetProjectionFovy(v: number): void { this.fovY = v; this.flags |= EDemoCamFlags.HasFovY; } - override JSGGetProjectionAspect() { + public override JSGGetProjectionAspect() { const camera = this.globals.camera; if (!camera) return 1.3333; @@ -93,24 +93,24 @@ class dDemo_camera_c extends TCamera { } - override JSGSetProjectionAspect(v: number) { + public override JSGSetProjectionAspect(v: number) { this.aspect = v; this.flags |= EDemoCamFlags.HasAspect; } - override JSGGetViewPosition(dst: vec3) { + public override JSGGetViewPosition(dst: vec3) { vec3.copy(dst, this.globals.cameraPosition); } - override JSGSetViewPosition(v: ReadonlyVec3) { + public override JSGSetViewPosition(v: ReadonlyVec3) { vec3.copy(this.viewPosition, v); this.flags |= EDemoCamFlags.HasEyePos; } - override JSGGetViewUpVector(dst: vec3) { + public override JSGGetViewUpVector(dst: vec3) { const camera = this.globals.camera; if (!camera) vec3.set(dst, 0, 1, 0); @@ -118,13 +118,13 @@ class dDemo_camera_c extends TCamera { } - override JSGSetViewUpVector(v: ReadonlyVec3) { + public override JSGSetViewUpVector(v: ReadonlyVec3) { vec3.copy(this.upVector, v); this.flags |= EDemoCamFlags.HasUpVec; } - override JSGGetViewTargetPosition(dst: vec3) { + public override JSGGetViewTargetPosition(dst: vec3) { const camera = this.globals.camera; if (!camera) vec3.set(dst, 0, 0, 0); @@ -132,13 +132,13 @@ class dDemo_camera_c extends TCamera { } - override JSGSetViewTargetPosition(v: ReadonlyVec3) { + public override JSGSetViewTargetPosition(v: ReadonlyVec3) { vec3.copy(this.targetPosition, v); this.flags |= EDemoCamFlags.HasTargetPos; } - override JSGGetViewRoll() { + public override JSGGetViewRoll() { const camera = this.globals.camera; if (!camera) return 0.0; @@ -146,7 +146,7 @@ class dDemo_camera_c extends TCamera { } - override JSGSetViewRoll(v: number) { + public override JSGSetViewRoll(v: number) { this.roll = v; this.flags |= EDemoCamFlags.HasRoll; } @@ -188,11 +188,11 @@ export class dDemo_actor_c extends TActor { constructor(public actor: fopAc_ac_c) { super(); } - checkEnable(mask: number) { + public checkEnable(mask: number) { return this.flags & mask; } - getMorfParam() { + public getMorfParam() { // Doesn't have anim properties if ((this.flags & 0x40) == 0) { // Has STB data @@ -214,75 +214,75 @@ export class dDemo_actor_c extends TActor { } } - override JSGGetName() { return this.name; } + public override JSGGetName() { return this.name; } - override JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { + public override JSGGetNodeTransformation(nodeId: number, mtx: mat4): number { debugger; // I think this may be one of the shapeInstanceState matrices instead mat4.copy(mtx, this.model.modelMatrix); return 1; } - override JSGGetAnimationFrameMax() { return this.animFrameMax; } - override JSGGetTextureAnimationFrameMax() { return this.textAnimFrameMax; } + public override JSGGetAnimationFrameMax() { return this.animFrameMax; } + public override JSGGetTextureAnimationFrameMax() { return this.textAnimFrameMax; } - override JSGGetTranslation(dst: vec3) { vec3.copy(dst, this.translation); } - override JSGGetScaling(dst: vec3) { vec3.copy(dst, this.scaling); } - override JSGGetRotation(dst: vec3) { + public override JSGGetTranslation(dst: vec3) { vec3.copy(dst, this.translation); } + public override JSGGetScaling(dst: vec3) { vec3.copy(dst, this.scaling); } + public override JSGGetRotation(dst: vec3) { dst[0] = cM_sht2d(this.rotation[0]); dst[1] = cM_sht2d(this.rotation[1]); dst[2] = cM_sht2d(this.rotation[2]); } - override JSGSetData(id: number, data: DataView): void { + public override JSGSetData(id: number, data: DataView): void { this.stbDataId = id; this.stbData = data; // @TODO: Check that data makes sense this.flags |= EDemoActorFlags.HasData; } - override JSGSetTranslation(src: ReadonlyVec3) { + public override JSGSetTranslation(src: ReadonlyVec3) { vec3.copy(this.translation, src); this.flags |= EDemoActorFlags.HasPos; } - override JSGSetScaling(src: ReadonlyVec3) { + public override JSGSetScaling(src: ReadonlyVec3) { vec3.copy(this.scaling, src); this.flags |= EDemoActorFlags.HasScale; } - override JSGSetRotation(src: ReadonlyVec3) { + public override JSGSetRotation(src: ReadonlyVec3) { this.rotation[0] = cM_deg2s(src[0]); this.rotation[1] = cM_deg2s(src[1]); this.rotation[2] = cM_deg2s(src[2]); this.flags |= EDemoActorFlags.HasRot; } - override JSGSetShape(id: number): void { + public override JSGSetShape(id: number): void { this.shapeId = id; this.flags |= EDemoActorFlags.HasShape } - override JSGSetAnimation(id: number): void { + public override JSGSetAnimation(id: number): void { this.nextBckId = id; this.animFrameMax = 3.402823e+38; this.flags |= EDemoActorFlags.HasAnim; } - override JSGSetAnimationFrame(x: number): void { + public override JSGSetAnimationFrame(x: number): void { this.animFrame = x; this.flags |= EDemoActorFlags.HasFrame; } - override JSGSetAnimationTransition(x: number): void { + public override JSGSetAnimationTransition(x: number): void { this.animTransition = x; this.flags |= EDemoActorFlags.HasFrame; } - override JSGSetTextureAnimation(id: number): void { + public override JSGSetTextureAnimation(id: number): void { this.texAnim = id; this.flags |= EDemoActorFlags.HasTexAnim; } - override JSGSetTextureAnimationFrame(x: number): void { + public override JSGSetTextureAnimationFrame(x: number): void { this.texAnimFrame = x; this.flags |= EDemoActorFlags.HasTexFrame; } @@ -358,10 +358,10 @@ export class dDemo_manager_c { private globals: dGlobals ) { } - getFrame() { return this.frame; } - getFrameNoMsg() { return this.frameNoMsg; } - getMode() { return this.mode; } - getSystem() { return this.system; } + 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 { this.parser = new TParse(this.control); From d2439d18ed79702194a2600359854f323a36d13c Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 19:37:05 -0700 Subject: [PATCH 090/102] Use `| null` for optional members/params instead of `?` --- src/Common/JSYSTEM/JStudio.ts | 46 +++++++++++++++++------------------ src/ZeldaWindWaker/Main.ts | 2 +- src/ZeldaWindWaker/d_demo.ts | 18 +++++++------- src/ZeldaWindWaker/d_stage.ts | 2 +- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 9edf9dbee..7aa8e942e 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -33,7 +33,7 @@ export namespace JStage { public JSGFEnableFlag(flag: number): void { this.JSGSetFlag(this.JSGGetFlag() | flag); } public abstract JSGFGetType(): number; - public JSGGetName(): string | undefined { return undefined; } + public JSGGetName(): string | null { return null; } public JSGGetFlag(): number { return 0; } public JSGSetFlag(flag: number): void { } public JSGGetData(unk0: number, data: Object, unk1: number): boolean { return false; } @@ -55,7 +55,7 @@ export namespace JStage { // modified by a cutscene. Each game should override JSGFindObject() to supply or create objects for manipulation. //---------------------------------------------------------------------------------------------------------------------- export interface TSystem { - JSGFindObject(objId: string, objType: JStage.EObject): JStage.TObject | undefined; + JSGFindObject(objId: string, objType: JStage.EObject): JStage.TObject | null; } //---------------------------------------------------------------------------------------------------------------------- @@ -71,9 +71,9 @@ export interface TSystem { class TVariableValue { private value: number; private age: number; // In frames - private updateFunc?: (varval: TVariableValue, x: number) => void; - private updateParam: number | FVB.TFunctionValue | undefined; - private outputFunc?: (val: number, adaptor: TAdaptor) => void; + private updateFunc: ((varval: TVariableValue, x: number) => void) | null = null; + private updateParam: number | FVB.TFunctionValue | null; + private outputFunc: ((val: number, adaptor: TAdaptor) => void) | null = null; public getValue() { return this.value; } public getValueU8() { return clamp(this.value, 0, 255); } @@ -99,7 +99,7 @@ class TVariableValue { //-------------------- private static update_immediate(varval: TVariableValue, secondsPerFrame: number): void { varval.value = (varval.updateParam as number); - varval.updateFunc = undefined; + varval.updateFunc = null; } private static update_time(varval: TVariableValue, secondsPerFrame: number): void { @@ -116,7 +116,7 @@ class TVariableValue { // Modify the function that will be called each Update() //-------------------- public setValue_none() { - this.updateFunc = undefined; + this.updateFunc = null; } // Value will be set only on next update @@ -136,7 +136,7 @@ class TVariableValue { } // Value will be the result of a Function Value each frame - public setValue_functionValue(v?: FVB.TFunctionValue): void { + public setValue_functionValue(v: FVB.TFunctionValue | null = null): void { assert(v !== undefined); this.updateFunc = TVariableValue.update_functionValue; this.age = 0; @@ -146,7 +146,7 @@ class TVariableValue { //-------------------- // Set Output //-------------------- - public setOutput(outputFunc?: (val: number, adaptor: TAdaptor) => void) { + public setOutput(outputFunc: ((val: number, adaptor: TAdaptor) => void) | null = null) { this.outputFunc = outputFunc; } } @@ -319,7 +319,7 @@ abstract class STBObject { private sequenceNext: number; private wait: number = 0; - constructor(control: TControl, blockObj?: TBlockObject, adaptor?: TAdaptor) { + constructor(control: TControl, blockObj: TBlockObject | null = null, adaptor: TAdaptor | null = null) { this.control = control; if (blockObj && adaptor) { @@ -594,9 +594,9 @@ export abstract class TActor extends JStage.TObject { } class TActorAdaptor extends TAdaptor { - public parent?: JStage.TObject; + public parent: JStage.TObject | null = null; public parentNodeID: number; - public relation?: JStage.TObject; + public relation: JStage.TObject | null = null; public relationNodeID: number; public animMode: number = 0; // See computeAnimFrame() public animTexMode: number = 0; // See computeAnimFrame() @@ -1125,9 +1125,9 @@ namespace FVB { }; export abstract class TFunctionValue { - protected range?: Attribute.Range; - protected refer?: Attribute.Refer; - protected interpolate?: Attribute.Interpolate; + protected range: Attribute.Range | null = null; + protected refer: Attribute.Refer | null = null; + protected interpolate: Attribute.Interpolate | null = null; public abstract getType(): EFuncValType; public abstract prepare(): void; @@ -1244,7 +1244,7 @@ namespace FVB { public objects: TObject[] = []; // Really this is a fvb::TFactory method - public createObject(block: TBlock): TObject | undefined { + public createObject(block: TBlock): TObject | null { switch (block.type) { case EFuncValType.Composite: return new TObject_Composite(block); @@ -1261,7 +1261,7 @@ namespace FVB { default: console.warn('Unknown FVB type: ', block.type); debugger; - return undefined; + return null; } } @@ -1828,8 +1828,8 @@ export class TControl { public secondsPerFrame: number = 1 / 30.0; private suspendFrames: number; - public transformOrigin?: vec3; - public transformRotY?: number; + public transformOrigin: vec3 | null = null; + public transformRotY: number | null = null; private transformOnGetMtx = mat4.create(); private transformOnSetMtx = mat4.create(); @@ -1887,11 +1887,11 @@ export class TControl { return shouldContinue; } - public getFunctionValueByIdx(idx: number) { return this.fvbControl.objects[idx]?.funcVal; } + public getFunctionValueByIdx(idx: number) { return this.fvbControl.objects[idx].funcVal; } public getFunctionValueByName(name: string) { return this.fvbControl.objects.find(v => v.id == name)?.funcVal; } // Really this is a stb::TFactory method - public createObject(blockObj: TBlockObject): STBObject | undefined { + public createObject(blockObj: TBlockObject): STBObject | null { let objConstructor; let objType: JStage.EObject; switch (blockObj.type) { @@ -1901,12 +1901,12 @@ export class TControl { case 'JLIT': case 'JFOG': default: - return undefined; + return null; } const stageObj = this.system.JSGFindObject(blockObj.id, objType); if (!stageObj) { - return undefined; + return null; } const obj = new objConstructor(this, blockObj, stageObj); diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index e39768ba4..2ccd18909 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -941,7 +941,7 @@ class SceneDesc { // dStage_dt_c_stageLoader() // dMap_c::create() - globals.roomCtrl.demoArcName = undefined; + globals.roomCtrl.demoArcName = null; const vrbox = resCtrl.getStageResByName(ResType.Model, `Stage`, `vr_sky.bdl`); if (vrbox !== null) { diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 2c6000ca6..720a97228 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -162,7 +162,7 @@ export const enum EDemoActorFlags { HasFrame = 1 << 6, HasTexAnim = 1 << 7, HasTexFrame = 1 << 8, -} +} export class dDemo_actor_c extends TActor { name: string; @@ -289,7 +289,7 @@ export class dDemo_actor_c extends TActor { } class dDemo_system_c implements TSystem { - private activeCamera?: dDemo_camera_c; + private activeCamera: dDemo_camera_c | null; private actors: dDemo_actor_c[] = []; // private ambient: dDemo_ambient_c; // private lights: dDemo_light_c[]; @@ -299,7 +299,7 @@ class dDemo_system_c implements TSystem { private globals: dGlobals ) { } - public JSGFindObject(objName: string, objType: JStage.EObject): JStage.TObject | undefined { + public JSGFindObject(objName: string, objType: JStage.EObject): JStage.TObject | null { switch (objType) { case JStage.EObject.Camera: if (this.activeCamera) return this.activeCamera; @@ -314,7 +314,7 @@ class dDemo_system_c implements TSystem { actor = {} as fopAc_ac_c; } else { console.warn('Demo failed to find actor', objName); - return undefined; + return null; } } if (!this.actors[actor.demoActorID]) { @@ -329,7 +329,7 @@ class dDemo_system_c implements TSystem { case JStage.EObject.Fog: default: console.debug('[JSGFindObject] Unhandled type: ', objType); - return undefined; + return null; } } @@ -337,7 +337,7 @@ class dDemo_system_c implements TSystem { public getActor(actorID: number) { return this.actors[actorID]; } public remove() { - this.activeCamera = undefined; + this.activeCamera = null; for (let demoActor of this.actors) { demoActor.actor.demoActorID = -1; } this.actors = []; @@ -348,7 +348,7 @@ export class dDemo_manager_c { private frame: number; private frameNoMsg: number; private mode = EDemoMode.None; - private curFile?: ArrayBufferSlice; + private curFile: ArrayBufferSlice | null; private parser: TParse; private system = new dDemo_system_c(this.globals); @@ -387,7 +387,7 @@ export class dDemo_manager_c { public remove() { this.control.destroyObject_all(); this.system.remove(); - this.curFile = undefined; + this.curFile = null; this.mode = 0; } @@ -417,7 +417,7 @@ export class dDemo_manager_c { * Called by Actor update functions to update their data from the demo version of the actor. */ export function dDemo_setDemoData(globals: dGlobals, dtFrames: number, actor: fopAc_ac_c, flagMask: number, - morf?: mDoExt_McaMorf, arcName?: string, soundCount?: number, soundIdxs?: number[], soundMaterialID?: number, reverb?: number) { + morf: mDoExt_McaMorf | null = null, arcName: string | null = null) { const demoActor = globals.scnPlay.demo.getSystem().getActor(actor.demoActorID); if (!demoActor) return false; diff --git a/src/ZeldaWindWaker/d_stage.ts b/src/ZeldaWindWaker/d_stage.ts index 590e914d3..8cf12f436 100644 --- a/src/ZeldaWindWaker/d_stage.ts +++ b/src/ZeldaWindWaker/d_stage.ts @@ -529,7 +529,7 @@ export class dStage_roomStatus_c { export class dStage_roomControl_c { public status: dStage_roomStatus_c[] = nArray(64, () => new dStage_roomStatus_c()); - public demoArcName?: string; + public demoArcName: string | null = null; constructor() { for (let i = 0; i < this.status.length; i++) { From 471fe658628c6273cdea8ba4ed855d8a74df1f34 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 19:53:07 -0700 Subject: [PATCH 091/102] JStudio: Use enum stringification rather than const enums and mapping functions --- src/Common/JSYSTEM/JStudio.ts | 61 +++++------------------------------ 1 file changed, 8 insertions(+), 53 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 7aa8e942e..6c41b0e3a 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -156,7 +156,7 @@ class TVariableValue { // TAdaptor // Connects the STBObject to a Game Object. Manages tracks of TVariableValues, updates their values on the Game object. //---------------------------------------------------------------------------------------------------------------------- -const enum EDataOp { +enum EDataOp { None = 0, Void = 1, // Disable updates for this track. Immediate = 2, // Set the value on this track to an immediate value. @@ -173,19 +173,6 @@ class DataVal { asStr?: string; } -function dataOpToString(enumValue: EDataOp) { - switch (enumValue) { - case EDataOp.None: return "None" - case EDataOp.Void: return "Void" - case EDataOp.Immediate: return "Immediate" - case EDataOp.Time: return "Time" - case EDataOp.FuncValName: return "FuncVal" - case EDataOp.FuncValIdx: return "FuncVal" - case EDataOp.ObjectName: return "Obj" - case EDataOp.ObjectIdx: return "Obj" - } -} - function dataToValue(keyData: DataVal[], dataOp: number) { const vals = keyData.map(d => { switch (dataOp) { @@ -531,7 +518,7 @@ class TControlObject extends STBObject { //---------------------------------------------------------------------------------------------------------------------- // Actor //---------------------------------------------------------------------------------------------------------------------- -const enum EActorTrack { +enum EActorTrack { AnimFrame = 0, AnimTransition = 1, TexAnimFrame = 2, @@ -550,25 +537,6 @@ const enum EActorTrack { Relation = 13, } -function keyToString(enumValue: EActorTrack, count: number) { - switch (enumValue) { - case EActorTrack.AnimFrame: return "AnimFrame" - case EActorTrack.AnimTransition: return "AnimTransition" - case EActorTrack.TexAnimFrame: return "TexAnimFrame" - case EActorTrack.PosX: return count == 3 ? 'POS' : 'PosX'; - case EActorTrack.PosY: return "PosY" - case EActorTrack.PosZ: return "PosZ" - case EActorTrack.RotX: return count == 3 ? 'ROT' : 'RotX'; - case EActorTrack.RotY: return "RotY" - case EActorTrack.RotZ: return "RotZ" - case EActorTrack.ScaleX: return count == 3 ? 'SCALE' : 'ScaleX'; - case EActorTrack.ScaleY: return "ScaleY" - case EActorTrack.ScaleZ: return "ScaleZ" - case EActorTrack.Parent: return "Parent" - case EActorTrack.Relation: return "Relation" - } -} - export abstract class TActor extends JStage.TObject { public JSGFGetType() { return JStage.EObject.Actor; } public JSGGetTranslation(dst: vec3) { } @@ -855,15 +823,16 @@ class TActorObject extends STBObject { keyData[i] = readData(dataOp, dataOffset + i * 4, dataSize, file); this.adaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - - this.adaptor.log(`Set${keyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp)}]`); + + const keyName = EActorTrack[keyIdx].slice(0, keyCount > 0 ? -1 : undefined); + this.adaptor.log(`Set${keyName}: ${EDataOp[dataOp]} [${dataToValue(keyData, dataOp)}]`); } } //---------------------------------------------------------------------------------------------------------------------- // Camera //---------------------------------------------------------------------------------------------------------------------- -const enum ECameraTrack { +enum ECameraTrack { PosX = 0x00, PosY = 0x01, PosZ = 0x02, @@ -876,21 +845,6 @@ const enum ECameraTrack { DistFar = 0x09, } -function camKeyToString(enumValue: ECameraTrack, count: number) { - switch (enumValue) { - case ECameraTrack.PosX: return "PosX"; - case ECameraTrack.PosY: return "PosY"; - case ECameraTrack.PosZ: return "PosZ"; - case ECameraTrack.TargetX: return "TargetX"; - case ECameraTrack.TargetY: return "TargetY"; - case ECameraTrack.TargetZ: return "TargetZ"; - case ECameraTrack.FovY: return "FovY"; - case ECameraTrack.Roll: return "Roll"; - case ECameraTrack.DistNear: return "DistNear"; - case ECameraTrack.DistFar: return "DistFar"; - } -} - export abstract class TCamera extends JStage.TObject { public JSGFGetType() { return JStage.EObject.Camera; } public JSGGetProjectionType() { return true; } @@ -1032,7 +986,8 @@ class TCameraObject extends STBObject { this.adaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); } - this.adaptor.log(`Set${camKeyToString(keyIdx, keyCount)}: ${dataOpToString(dataOp)} [${dataToValue(keyData, dataOp)}]`); + const keyName = ECameraTrack[keyIdx].slice(0, keyCount > 0 ? -1 : undefined); + this.adaptor.log(`Set${keyName}: ${EDataOp[dataOp]} [${dataToValue(keyData, dataOp)}]`); } } From b4759fca443605d722ef2c58f2de1af00d966ebd Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 20:04:12 -0700 Subject: [PATCH 092/102] Remove old comment regarding `removeTexMtxAnimator` usage The game attaches animation classes during `draw()` and then immediately removes them. Noclip uses a separate, nondestructive, style of animation logic. --- src/ZeldaWindWaker/d_a.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 1d0fbaf93..0491c1940 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4797,10 +4797,6 @@ class d_a_npc_ls1 extends fopNpc_npc_c { this.morf.entryDL(globals, renderInstManager, viewerInput); - // TODO: What are these doing? - // removeTexMtxAnimator(this.morf.model, this.btkAnim.anm); - // removeTexNoAnimator(this.morf.model, this.btpAnim.anm); - mDoExt_modelEntryDL(globals, this.handModel, renderInstManager, viewerInput); if (this.itemModel) { From 2d9d5f3809db797e045dd2086bf4cee7886d6105 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 20:07:15 -0700 Subject: [PATCH 093/102] Use `interface` instead of `class` for data-only types --- src/ZeldaWindWaker/d_a.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 0491c1940..c908481ff 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4715,7 +4715,7 @@ export class d_a_ff extends fopAc_ac_c { class fopNpc_npc_c extends fopAc_ac_c { protected morf: mDoExt_McaMorf; }; -class anm_prm_c { +interface anm_prm_c { anmIdx: number; nextPrmIdx: number; morf: number; From 0ef39a0aa393c2ac8fae26091a9e61538bc3435f Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 20:24:12 -0700 Subject: [PATCH 094/102] Merged cM__Deg2Short into cM_deg2s (the game's original name) Changed one use case to use the new name --- src/ZeldaTwilightPrincess/d_a.ts | 4 ++-- src/ZeldaWindWaker/SComponent.ts | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ZeldaTwilightPrincess/d_a.ts b/src/ZeldaTwilightPrincess/d_a.ts index 776535b22..c36f925c9 100644 --- a/src/ZeldaTwilightPrincess/d_a.ts +++ b/src/ZeldaTwilightPrincess/d_a.ts @@ -8,7 +8,7 @@ import { JPABaseEmitter } from "../Common/JSYSTEM/JPA.js"; import { BTIData } from "../Common/JSYSTEM/JUTTexture.js"; import { MathConstants, Vec3UnitY, invlerp, saturate, scaleMatrix } from "../MathHelpers.js"; import { TSDraw } from "../SuperMarioGalaxy/DDraw.js"; -import { cLib_addCalc, cLib_addCalc2, cLib_addCalcAngleS2, cLib_addCalcAngleS_, cLib_chaseF, cLib_targetAngleX, cLib_targetAngleY, cM__Short2Rad, cM__Deg2Short, cM_atan2s } from "../ZeldaWindWaker/SComponent.js"; +import { cLib_addCalc, cLib_addCalc2, cLib_addCalcAngleS2, cLib_addCalcAngleS_, cLib_chaseF, cLib_targetAngleX, cLib_targetAngleY, cM__Short2Rad, cM_deg2s, cM_atan2s } from "../ZeldaWindWaker/SComponent.js"; import { dBgW } from "../ZeldaWindWaker/d_bg.js"; import { MtxPosition, MtxTrans, calc_mtx, mDoMtx_XrotM, mDoMtx_YrotM, mDoMtx_YrotS, mDoMtx_ZXYrotM, mDoMtx_ZrotM } from "../ZeldaWindWaker/m_do_mtx.js"; import { GfxDevice } from "../gfx/platform/GfxPlatform.js"; @@ -3100,7 +3100,7 @@ class d_a_obj_magLiftRot extends fopAc_ac_c { private modeMove(deltaTimeFrames: number): void { this.speedF = cLib_chaseF(this.speedF, 8.0, 0.05 * deltaTimeFrames); - const speed = cM__Deg2Short(this.speedF); + const speed = cM_deg2s(this.speedF); this.rot[2] = cLib_addCalcAngleS_(this.rot[2], this.rotTarget, 1, speed * deltaTimeFrames, 1); if ((this.rotTarget - this.rot[2]) === 0) { diff --git a/src/ZeldaWindWaker/SComponent.ts b/src/ZeldaWindWaker/SComponent.ts index 966efd464..b14a9d7ab 100644 --- a/src/ZeldaWindWaker/SComponent.ts +++ b/src/ZeldaWindWaker/SComponent.ts @@ -113,8 +113,8 @@ export function cM_rndFX(max: number): number { return 2.0 * (max * (Math.random() - 0.5)); } -export function cM_deg2s(deg: number): number { - return deg * 182.04445; +export function cM_deg2s(v: number): number { + return cM__Rad2Short(v * MathConstants.DEG_TO_RAD); } export function cM_sht2d(rad: number) { @@ -133,10 +133,6 @@ export function cM__Rad2Short(v: number): number { return v * (0x8000 / Math.PI); } -export function cM__Deg2Short(v: number): number { - return cM__Rad2Short(v * MathConstants.DEG_TO_RAD); -} - export function cLib_targetAngleX(p0: ReadonlyVec3, p1: ReadonlyVec3): number { const dy = p1[1] - p0[1]; const dist = cLib_distanceXZ(p0, p1); From b5ee2b0b080d3a043b40ceb32bd5f0c9352dfecb Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 21:21:17 -0700 Subject: [PATCH 095/102] JStudio: Use type discriminators to simplify/improve paragraph data handling Thanks Jasper! --- src/Common/JSYSTEM/JStudio.ts | 184 ++++++++++++++++------------------ 1 file changed, 85 insertions(+), 99 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index 6c41b0e3a..e591c1634 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -167,42 +167,27 @@ enum EDataOp { ObjectIdx = 0x19, // Same as ObjectName, but by object index }; -class DataVal { - asInt?: number; - asFloat?: number; - asStr?: string; -} - -function dataToValue(keyData: DataVal[], dataOp: number) { - const vals = keyData.map(d => { - switch (dataOp) { - case EDataOp.FuncValIdx: - case EDataOp.ObjectIdx: - return d.asInt; - case EDataOp.FuncValName: - case EDataOp.ObjectName: - return d.asStr; - default: - return d.asFloat; - } - }); - return vals; -} +type ParagraphData = ( + { dataOp: EDataOp.Void | EDataOp.None; value: null; } | + { dataOp: EDataOp.FuncValName | EDataOp.ObjectName; value: string; } | + { dataOp: EDataOp.FuncValIdx | EDataOp.ObjectIdx; value: number; } | + { dataOp: EDataOp.Immediate | EDataOp.Time; value: number; valueInt: number } +); // Parse data from a DataView as either a number or a string, based on the dataOp -function readData(dataOp: EDataOp, dataOffset: number, dataSize: number, file: Reader): DataVal { +function readData(dataOp: EDataOp, dataOffset: number, dataSize: number, file: Reader): ParagraphData { switch (dataOp) { case EDataOp.Immediate: case EDataOp.Time: - return { asInt: file.view.getUint32(dataOffset), asFloat: file.view.getFloat32(dataOffset) }; + return { dataOp, value: file.view.getFloat32(dataOffset), valueInt: file.view.getUint32(dataOffset) }; case EDataOp.FuncValIdx: case EDataOp.ObjectIdx: - return { asInt: file.view.getUint32(dataOffset) }; + return { dataOp, value: file.view.getUint32(dataOffset) }; case EDataOp.FuncValName: case EDataOp.ObjectName: - return { asStr: readString(file.buffer, dataOffset, dataSize) }; + return { dataOp, value: readString(file.buffer, dataOffset, dataSize) }; default: assert(false, 'Unsupported data operation'); @@ -225,18 +210,18 @@ abstract class TAdaptor { public abstract adaptor_do_data(obj: STBObject, id: number, data: DataView): void; // Set a single VariableValue update function, with the option of using FuncVals - public adaptor_setVariableValue(obj: STBObject, keyIdx: number, dataOp: EDataOp, data: DataVal) { + public adaptor_setVariableValue(obj: STBObject, keyIdx: number, data: ParagraphData) { const varval = this.variableValues[keyIdx]; const control = obj.control; - switch (dataOp) { + switch (data.dataOp) { case EDataOp.Void: varval.setValue_none(); break; - case EDataOp.Immediate: varval.setValue_immediate(data.asFloat!); break; - case EDataOp.Time: varval.setValue_time(data.asFloat!); break; - case EDataOp.FuncValName: varval.setValue_functionValue(control.getFunctionValueByName(data.asStr!)); break; - case EDataOp.FuncValIdx: varval.setValue_functionValue(control.getFunctionValueByIdx(data.asInt!)); break; + case EDataOp.Immediate: varval.setValue_immediate(data.value); break; + case EDataOp.Time: varval.setValue_time(data.value); break; + case EDataOp.FuncValName: varval.setValue_functionValue(control.getFunctionValueByName(data.value)); break; + case EDataOp.FuncValIdx: varval.setValue_functionValue(control.getFunctionValueByIdx(data.value)); break; default: - console.debug('Unsupported dataOp: ', dataOp); + console.debug('Unsupported dataOp: ', data.dataOp); debugger; return; } @@ -651,89 +636,89 @@ class TActorAdaptor extends TAdaptor { this.object.JSGSetData(id, data); } - public adaptor_do_PARENT(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.ObjectName); - this.log(`SetParent: ${data.asStr}`); - this.parent = this.system.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); + public adaptor_do_PARENT(data: ParagraphData): void { + assert(data.dataOp == EDataOp.ObjectName); + this.log(`SetParent: ${data.value}`); + this.parent = this.system.JSGFindObject(data.value, JStage.EObject.PreExistingActor); } - public adaptor_do_PARENT_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_PARENT_NODE(data: ParagraphData): void { debugger; - this.log(`SetParentNode: ${data}`); - switch (dataOp) { + this.log(`SetParentNode: ${data.value}`); + switch (data.dataOp) { case EDataOp.ObjectName: if (this.parent) - this.parentNodeID = this.parent.JSGFindNodeID(data.asStr!); + this.parentNodeID = this.parent.JSGFindNodeID(data.value); break; case EDataOp.ObjectIdx: - this.parentNodeID = data.asInt!; + this.parentNodeID = data.value; break; default: assert(false); } } - public adaptor_do_PARENT_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { - assert(dataOp == EDataOp.Immediate); - this.log(`SetParentEnable: ${data}`); - if (data) { this.object.JSGSetParent(this.parent!, this.parentNodeID); } + public adaptor_do_PARENT_ENABLE(data: ParagraphData): void { + assert(data.dataOp == EDataOp.Immediate); + this.log(`SetParentEnable: ${data.valueInt}`); + if (data.valueInt) { this.object.JSGSetParent(this.parent!, this.parentNodeID); } else { this.object.JSGSetParent(null, 0xFFFFFFFF); } } - public adaptor_do_RELATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.ObjectName); - this.log(`SetRelation: ${data.asStr!}`); - this.relation = this.system.JSGFindObject(data.asStr!, JStage.EObject.PreExistingActor); + public adaptor_do_RELATION(data: ParagraphData): void { + assert(data.dataOp == EDataOp.ObjectName); + this.log(`SetRelation: ${data.value}`); + this.relation = this.system.JSGFindObject(data.value, JStage.EObject.PreExistingActor); } - public adaptor_do_RELATION_NODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { + public adaptor_do_RELATION_NODE(data: ParagraphData): void { debugger; - this.log(`SetRelationNode: ${data}`); - switch (dataOp) { + this.log(`SetRelationNode: ${data.value}`); + switch (data.dataOp) { case EDataOp.ObjectName: if (this.relation) - this.relationNodeID = this.relation.JSGFindNodeID(data.asStr!); + this.relationNodeID = this.relation.JSGFindNodeID(data.value); break; case EDataOp.ObjectIdx: - this.relationNodeID = data.asInt!; + this.relationNodeID = data.value; break; default: assert(false); } } - public adaptor_do_RELATION_ENABLE(dataOp: EDataOp, data: number, dataSize: number): void { - assert(dataOp == EDataOp.Immediate); - this.log(`SetRelationEnable: ${data}`); - this.object.JSGSetRelation(!!data, this.relation!, this.relationNodeID); + public adaptor_do_RELATION_ENABLE(data: ParagraphData): void { + assert(data.dataOp == EDataOp.Immediate); + this.log(`SetRelationEnable: ${data.valueInt}`); + this.object.JSGSetRelation(!!data.valueInt, this.relation!, this.relationNodeID); } - public adaptor_do_SHAPE(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.ObjectIdx); - this.log(`SetShape: ${data.asInt!}`); - this.object.JSGSetShape(data.asInt!); + public adaptor_do_SHAPE(data: ParagraphData): void { + assert(data.dataOp == EDataOp.ObjectIdx); + this.log(`SetShape: ${data.value}`); + this.object.JSGSetShape(data.value); } - public adaptor_do_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.ObjectIdx); - this.log(`SetAnimation: ${(data.asInt!) & 0xFFFF} (${(data.asInt!) >> 4 & 0x01})`); - this.object.JSGSetAnimation(data.asInt!); + public adaptor_do_ANIMATION(data: ParagraphData): void { + assert(data.dataOp == EDataOp.ObjectIdx); + this.log(`SetAnimation: ${(data.value) & 0xFFFF} (${(data.value) >> 4 & 0x01})`); + this.object.JSGSetAnimation(data.value); } - public adaptor_do_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.Immediate); - this.log(`SetAnimationMode: ${data.asInt!}`); - this.animMode = data.asInt!; + public adaptor_do_ANIMATION_MODE(data: ParagraphData): void { + assert(data.dataOp == EDataOp.Immediate); + this.log(`SetAnimationMode: ${data.valueInt}`); + this.animMode = data.valueInt; } - public adaptor_do_TEXTURE_ANIMATION(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.ObjectIdx); - this.log(`SetTexAnim: ${data}`); - this.object.JSGSetTextureAnimation(data.asInt!); + public adaptor_do_TEXTURE_ANIMATION(data: ParagraphData): void { + assert(data.dataOp == EDataOp.ObjectIdx); + this.log(`SetTexAnim: ${data.value}`); + this.object.JSGSetTextureAnimation(data.value); } - public adaptor_do_TEXTURE_ANIMATION_MODE(dataOp: EDataOp, data: DataVal, dataSize: number): void { - assert(dataOp == EDataOp.Immediate); - this.log(`SetTexAnimMode: ${data}`); - this.animTexMode = data.asInt!; + public adaptor_do_TEXTURE_ANIMATION_MODE(data: ParagraphData): void { + assert(data.dataOp == EDataOp.Immediate); + this.log(`SetTexAnimMode: ${data.valueInt}`); + this.animTexMode = data.valueInt; } } @@ -776,40 +761,41 @@ class TActorObject extends STBObject { case 0x3b: keyIdx = EActorTrack.AnimFrame; break; case 0x4b: keyIdx = EActorTrack.AnimTransition; break; - case 0x39: this.adaptor.adaptor_do_SHAPE(dataOp, data, dataSize); return; - case 0x3a: this.adaptor.adaptor_do_ANIMATION(dataOp, data, dataSize); return; - case 0x43: this.adaptor.adaptor_do_ANIMATION_MODE(dataOp, data, dataSize); return; - case 0x4c: debugger; this.adaptor.adaptor_do_TEXTURE_ANIMATION(dataOp, data, dataSize); return; - case 0x4e: debugger; this.adaptor.adaptor_do_TEXTURE_ANIMATION_MODE(dataOp, data, dataSize); return; + case 0x39: this.adaptor.adaptor_do_SHAPE(data); return; + case 0x3a: this.adaptor.adaptor_do_ANIMATION(data); return; + case 0x43: this.adaptor.adaptor_do_ANIMATION_MODE(data); return; + case 0x4c: debugger; this.adaptor.adaptor_do_TEXTURE_ANIMATION(data); return; + case 0x4e: debugger; this.adaptor.adaptor_do_TEXTURE_ANIMATION_MODE(data); return; - case 0x30: debugger; this.adaptor.adaptor_do_PARENT(dataOp, data, dataSize); return; - case 0x31: debugger; this.adaptor.adaptor_do_PARENT_NODE(dataOp, data, dataSize); return; + case 0x30: debugger; this.adaptor.adaptor_do_PARENT(data); return; + case 0x31: debugger; this.adaptor.adaptor_do_PARENT_NODE(data); return; case 0x32: debugger; keyIdx = EActorTrack.Parent; - if ((dataOp < 0x13) && (dataOp > 0x0F)) { + if (dataOp == EDataOp.FuncValIdx || dataOp == EDataOp.FuncValName) { debugger; - this.adaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); + this.adaptor.adaptor_setVariableValue(this, keyIdx, data); this.adaptor.variableValues[keyIdx].setOutput((enabled, adaptor) => { - (adaptor as TActorAdaptor).adaptor_do_PARENT_ENABLE(dataOp, enabled, dataSize) + (adaptor as TActorAdaptor).adaptor_do_PARENT_ENABLE({ dataOp:EDataOp.Immediate, value: enabled, valueInt: enabled }); }); + } else { + this.adaptor.adaptor_do_PARENT_ENABLE(data); } - this.adaptor.adaptor_do_PARENT_ENABLE(dataOp, data.asInt!, dataSize); break; - case 0x33: debugger; this.adaptor.adaptor_do_RELATION(dataOp, data, dataSize); return; - case 0x34: debugger; this.adaptor.adaptor_do_RELATION_NODE(dataOp, data, dataSize); return; + case 0x33: debugger; this.adaptor.adaptor_do_RELATION(data); return; + case 0x34: debugger; this.adaptor.adaptor_do_RELATION_NODE(data); return; case 0x35: debugger; keyIdx = EActorTrack.Relation; if ((dataOp < 0x13) && (dataOp > 0x0F)) { debugger; - this.adaptor.adaptor_setVariableValue(this, keyIdx, dataOp, data); + this.adaptor.adaptor_setVariableValue(this, keyIdx, data); this.adaptor.variableValues[keyIdx].setOutput((enabled, adaptor) => { - (adaptor as TActorAdaptor).adaptor_do_RELATION_ENABLE(dataOp, enabled, dataSize) + (adaptor as TActorAdaptor).adaptor_do_RELATION_ENABLE({ dataOp:EDataOp.Immediate, value: enabled, valueInt: enabled }); }); } - this.adaptor.adaptor_do_RELATION_ENABLE(dataOp, data.asInt!, dataSize); + this.adaptor.adaptor_do_RELATION_ENABLE(data); break; default: @@ -821,11 +807,11 @@ class TActorObject extends STBObject { let keyData = []; for (let i = 0; i < keyCount; i++) { keyData[i] = readData(dataOp, dataOffset + i * 4, dataSize, file); - this.adaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); + this.adaptor.adaptor_setVariableValue(this, keyIdx + i, keyData[i]); } - + const keyName = EActorTrack[keyIdx].slice(0, keyCount > 0 ? -1 : undefined); - this.adaptor.log(`Set${keyName}: ${EDataOp[dataOp]} [${dataToValue(keyData, dataOp)}]`); + this.adaptor.log(`Set${keyName}: ${EDataOp[dataOp]} [${keyData.map(k => k.value)}]`); } } @@ -983,11 +969,11 @@ class TCameraObject extends STBObject { let keyData = [] for (let i = 0; i < keyCount; i++) { keyData[i] = readData(dataOp, dataOffset + i * 4, dataSize, file); - this.adaptor.adaptor_setVariableValue(this, keyIdx + i, dataOp, keyData[i]); + this.adaptor.adaptor_setVariableValue(this, keyIdx + i, keyData[i]); } const keyName = ECameraTrack[keyIdx].slice(0, keyCount > 0 ? -1 : undefined); - this.adaptor.log(`Set${keyName}: ${EDataOp[dataOp]} [${dataToValue(keyData, dataOp)}]`); + this.adaptor.log(`Set${keyName}: ${EDataOp[dataOp]} [${keyData.map(k => k.value)}]`); } } From bfeb6d93e15600fb6d0c0137b4ab7227274abaae Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 22:17:57 -0700 Subject: [PATCH 096/102] Demos are now selected the same as Scenes. - Added "Cutscenes" category with the Wind Waker section - Currently all cutscenes are listed - Clicking one will load the correct Scene and begin playing the demo --- src/ZeldaWindWaker/Main.ts | 163 +++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 81 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 2ccd18909..9080f91e9 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -373,7 +373,7 @@ export class WindWakerRenderer implements Viewer.SceneGfx { const demoSelect = new UI.SingleSelect(); demoSelect.setStrings(this.demos.map(d => d.name)); demoSelect.onselectionchange = (idx: number) => { - this.demos[idx].load(this.globals) + this.demos[idx].playDemo(this.globals) }; demosPanel.contents.appendChild(demoSelect.elem); @@ -844,8 +844,9 @@ class d_s_play extends fopScn { class SceneDesc { public id: string; + protected globals: dGlobals; - public constructor(public stageDir: string, public name: string, public roomList: number[] = [0], public autoplayDemo?: DemoDesc) { + public constructor(public stageDir: string, public name: string, public roomList: number[] = [0]) { this.id = stageDir; // Garbage hack. @@ -894,6 +895,7 @@ class SceneDesc { const symbolMap = new SymbolMap(modelCache.getFileData(`extra.crg1_arc`)); const globals = new dGlobals(context, modelCache, symbolMap, framework); + this.globals = globals; globals.stageName = this.stageDir; const renderer = new WindWakerRenderer(device, globals); @@ -967,37 +969,41 @@ class SceneDesc { dStage_dt_c_roomReLoader(globals, globals.roomCtrl.status[roomNo].data, dzr); } - // Build a list of all the demos (cutscenes) that are playable in this scene - globals.renderer.demos = demoDescs.filter(d => - d.stage == globals.stageName && (d.roomNo == -1 || this.roomList.includes(d.roomNo))); - - // If requested, automatically start playing a demo - if(this.autoplayDemo) { this.autoplayDemo.load(globals); } - return renderer; } } -class DemoDesc { +class DemoDesc extends SceneDesc { + private scene: SceneDesc; + public constructor( - public stage: string, - public name: string, + public override stageDir: string, + public override name: string, + public override roomList: number[], public stbFilename: string, - public roomNo: number, public layer: number, public offsetPos?:vec3, public rotY: number = 0, public startCode?: number, public eventFlags?: number, public startFrame?: number, // noclip modification for easier debugging - ) {} + ) { + super(stageDir, name, roomList); + assert(this.roomList.length === 1); + } - async load(globals: dGlobals) { + public override async createScene(device: GfxDevice, context: SceneContext): Promise { + const res = await super.createScene(device, context); + this.playDemo(this.globals); + return res; + } + + async playDemo(globals: dGlobals) { globals.scnPlay.demo.remove(); // 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.roomNo]?.data.lbnk; + const lbnk = globals.roomCtrl.status[this.roomList[0]]?.data.lbnk; if (lbnk) { const bank = lbnk[this.layer]; if (bank != 0xFF) { @@ -1014,7 +1020,7 @@ class DemoDesc { } } - // noclip modification: ensure all the actors are created before we load the cutscene (in case we're auto-playing) + // 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); @@ -1031,73 +1037,11 @@ class DemoDesc { if( demoData ) { globals.scnPlay.demo.create(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; } } -const demoDescs = [ - new DemoDesc("sea", "Awaken", "awake.stb", 44, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), - new DemoDesc("sea", "Stolen Sister", "stolensister.stb", 44, 9, [0.0, 0.0, 20000.0], 0, 0, 0), - new DemoDesc("sea", "Departure", "departure.stb", 44, 10, [-200000.0, 0.0, 320000.0], 0.0, 204, 0), - new DemoDesc("sea", "Pirate Zelda Fly", "kaizoku_zelda_fly.stb", 44, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), - new DemoDesc("sea_T", "Title Screen", "title.stb", 44, 0, [-220000.0, 0.0, 320000.0], 180.0, 0, 0), - - new DemoDesc("ADMumi", "warp_in.stb", "warp_in.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 200, 0), - new DemoDesc("ADMumi", "warphole.stb", "warphole.stb", 0, 10, [0.0, 0.0, 0.0], 0.0, 219, 0), - new DemoDesc("ADMumi", "runaway_majuto.stb", "runaway_majuto.stb", 0, 11, [0, 0, 0], 0, 0, 0), - new DemoDesc("ADMumi", "towerd.stb", "towerd.stb", 0, 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), - new DemoDesc("ADMumi", "towerf.stb", "towerf.stb", 0, 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), - new DemoDesc("ADMumi", "towern.stb", "towern.stb", 0, 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), - - new DemoDesc("A_mori", "meet_tetra.stb", "meet_tetra.stb", 0, 0, [0, 0, 0], 0, 0, 0), - new DemoDesc("Adanmae", "howling.stb", "howling.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), - new DemoDesc("Atorizk", "dragontale.stb", "dragontale.stb", 0, 0, [0, 0, 0], 0, 0, 0), - new DemoDesc("ENDumi", "ending.stb", "ending.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), - new DemoDesc("Edaichi", "dance_zola.stb", "dance_zola.stb", 0, 8, [0.0, 0.0, 0.0], 0, 226, 0), - new DemoDesc("Ekaze", "dance_kokiri.stb", "dance_kokiri.stb", 0, 8, [0.0, 0.0, 0.0], 0, 229, 0), - new DemoDesc("GTower", "g2before.stb", "g2before.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), - new DemoDesc("GTower", "endhr.stb", "endhr.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 0, 0), - new DemoDesc("GanonK", "kugutu_ganon.stb", "kugutu_ganon.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), - new DemoDesc("GanonK", "to_roof.stb", "to_roof.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 4, 0), - new DemoDesc("Hyroom", "rebirth_hyral.stb", "rebirth_hyral.stb", 0, 8, [0.0, -2100.0, 3910.0], 0.0, 249, 0), - new DemoDesc("Hyrule", "warp_out.stb", "warp_out.stb", 0, 8, [0, 0, 0], 0, 201, 0), - new DemoDesc("Hyrule", "seal.stb", "seal.stb", 0, 4, [0.0, 0.0, 0.0], 0, 0, 0), - new DemoDesc("LinkRM", "tale.stb", "tale.stb", 0, 8, [0, 0, 0], 0, 0, 0), - new DemoDesc("LinkRM", "tale_2.stb", "tale_2.stb", 0, 8, [0, 0, 0], 0, 0, 0), - new DemoDesc("LinkRM", "get_shield.stb", "get_shield.stb", 0, 9, [0, 0, 0], 0, 201, 0), - new DemoDesc("LinkRM", "tale_2.stb", "tale_2.stb", 0, 8, [0, 0, 0], 0, 0, 0), - new DemoDesc("M2ganon", "attack_ganon.stb", "attack_ganon.stb", 0, 1, [2000.0, 11780.0, -8000.0], 0.0, 244, 0), - new DemoDesc("M2tower", "rescue.stb", "rescue.stb", 0, 1, [3214.0, 3939.0, -3011.0], 57.5, 20, 0), - new DemoDesc("M_DaiB", "pray_zola.stb", "pray_zola.stb", 0, 8, [0, 0, 0], 0, 229, 0), - new DemoDesc("MajyuE", "maju_shinnyu.stb", "maju_shinnyu.stb", 0, 0, [0, 0, 0], 0, 0, 0), - new DemoDesc("Mjtower", "find_sister.stb", "find_sister.stb", 0, 0, [4889.0, 0.0, -2635.0], 57.5, 0, 0), - new DemoDesc("Obombh", "bombshop.stb", "bombshop.stb", 0, 0, [0.0, 0.0, 0.0], 0.0, 200, 0), - new DemoDesc("Omori", "getperl_deku.stb", "getperl_deku.stb", 0, 9, [0, 0, 0], 0, 214, 0), - new DemoDesc("Omori", "meet_deku.stb", "meet_deku.stb", 0, 8, [0, 0, 0], 0, 213, 0), - new DemoDesc("Otkura", "awake_kokiri.stb", "awake_kokiri.stb", 0, 8, [0, 0, 0], 0, 0, 0), - new DemoDesc("Pjavdou", "getperl_jab.stb", "getperl_jab.stb", 0, 8, [0.0, 0.0, 0.0], 0.0, 0, 0), - new DemoDesc("kazeB", "pray_kokiri.stb", "pray_kokiri.stb", 0, 8, [0.0, 300.0, 0.0], 0.0, 232, 0), - - new DemoDesc("kenroom", "awake_zelda.stb", "awake_zelda.stb", 0, 9, [0.0, 0.0, 0.0], 0.0, 2, 0), - new DemoDesc("kenroom", "master_sword.stb", "master_sword.stb", 0, 0, [-124.0, -3223.0, -7823.0], 180.0, 248, 0), - new DemoDesc("kenroom", "swing_sword.stb", "swing_sword.stb", 0, 10, [-124.0, -3223.0, -7823.0], 180.0, 249, 0), - - new DemoDesc("sea", "Fairy", "fairy.stb", 9, 8, [-180000.0, 740.0, -199937.0], 25.0, 2, 0), - new DemoDesc("sea", "fairy_flag_on.stb", "fairy_flag_on.stb", 9, 8, [-180000.0, 740.0, -199937.0], 25.0, 2, 0), - - new DemoDesc("sea", "Zola Awakens", "awake_zola.stb", 13, 8, [200000.0, 0.0, -200000.0], 0, 227, 0), - new DemoDesc("sea", "Get Komori Pearl", "getperl_komori.stb", 13, 9, [200000.0, 0.0, -200000.0], 0, 0, 0), - - new DemoDesc("sea", "meetshishioh.stb", "meetshishioh.stb", 11, 8, [0.0, 0.0, -200000.0], 0, 128, 0), - - // These are present in the sea_T event_list.dat, but not in the room's lbnk. They are only playable from "sea". - new DemoDesc("sea_T", "Awaken", "awake.stb", 255, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), - new DemoDesc("sea_T", "Departure", "departure.stb", 255, 0, [-200000.0, 0.0, 320000.0], 0.0, 0, 0), - new DemoDesc("sea_T", "PirateZeldaFly", "kaizoku_zelda_fly.stb", 255, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), - - // The game expects this STB file to be in Stage/Ocean/Stage.arc, but it is not. Must be a leftover. - new DemoDesc("Ocean", "counter.stb", "counter.stb", -1, 0, [0, 0, 0], 0, 0, 0), -] - // Location names taken from CryZe's Debug Menu. // https://github.com/CryZe/WindWakerDebugMenu/blob/master/src/warp_menu/consts.rs const sceneDescs = [ @@ -1120,6 +1064,63 @@ const sceneDescs = [ new SceneDesc("PShip", "Ghost Ship"), new SceneDesc("Obshop", "Beedle's Shop", [1]), + "Cutscenes", + new DemoDesc("sea_T", "Title Screen", [44], "title.stb", 0, [-220000.0, 0.0, 320000.0], 180.0, 0, 0), + new DemoDesc("sea", "Awaken", [44], "awake.stb", 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea", "Stolen Sister", [44], "stolensister.stb", 9, [0.0, 0.0, 20000.0], 0, 0, 0), + new DemoDesc("sea", "Departure", [44], "departure.stb", 10, [-200000.0, 0.0, 320000.0], 0.0, 204, 0), + new DemoDesc("sea", "Pirate Zelda Fly", [44], "kaizoku_zelda_fly.stb", 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), + new DemoDesc("sea", "Zola Awakens", [13], "awake_zola.stb", 8, [200000.0, 0.0, -200000.0], 0, 227, 0), + new DemoDesc("sea", "Get Komori Pearl", [13], "getperl_komori.stb", 9, [200000.0, 0.0, -200000.0], 0, 0, 0), + new DemoDesc("sea", "Meet the King of Red Lions", [11], "meetshishioh.stb", 8, [0.0, 0.0, -200000.0], 0, 128, 0), + new DemoDesc("sea", "Fairy", [9], "fairy.stb", 8, [-180000.0, 740.0, -199937.0], 25.0, 2, 0), + new DemoDesc("sea", "fairy_flag_on.stb", [9], "fairy_flag_on.stb", 8, [-180000.0, 740.0, -199937.0], 25.0, 2, 0), + new DemoDesc("ADMumi", "warp_in.stb", [0], "warp_in.stb", 9, [0.0, 0.0, 0.0], 0.0, 200, 0), + new DemoDesc("ADMumi", "warphole.stb", [0], "warphole.stb", 10, [0.0, 0.0, 0.0], 0.0, 219, 0), + new DemoDesc("ADMumi", "runaway_majuto.stb", [0], "runaway_majuto.stb", 11, [0, 0, 0], 0, 0, 0), + new DemoDesc("ADMumi", "towerd.stb", [0], "towerd.stb", 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), + new DemoDesc("ADMumi", "towerf.stb", [0], "towerf.stb", 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), + new DemoDesc("ADMumi", "towern.stb", [0], "towern.stb", 8, [-50179.0, -1000.0, 7070.0], 90.0, 0, 0), + new DemoDesc("A_mori", "meet_tetra.stb", [0], "meet_tetra.stb", 0, [0, 0, 0], 0, 0, 0), + new DemoDesc("Adanmae", "howling.stb", [0], "howling.stb", 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("Atorizk", "dragontale.stb", [0], "dragontale.stb", 0, [0, 0, 0], 0, 0, 0), + new DemoDesc("ENDumi", "ending.stb", [0], "ending.stb", 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("Edaichi", "dance_zola.stb", [0], "dance_zola.stb", 8, [0.0, 0.0, 0.0], 0, 226, 0), + new DemoDesc("Ekaze", "dance_kokiri.stb", [0], "dance_kokiri.stb", 8, [0.0, 0.0, 0.0], 0, 229, 0), + new DemoDesc("GTower", "g2before.stb", [0], "g2before.stb", 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("GTower", "endhr.stb", [0], "endhr.stb", 9, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("GanonK", "kugutu_ganon.stb", [0], "kugutu_ganon.stb", 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("GanonK", "to_roof.stb", [0], "to_roof.stb", 9, [0.0, 0.0, 0.0], 0.0, 4, 0), + new DemoDesc("Hyroom", "rebirth_hyral.stb", [0], "rebirth_hyral.stb", 8, [0.0, -2100.0, 3910.0], 0.0, 249, 0), + new DemoDesc("Hyrule", "warp_out.stb", [0], "warp_out.stb", 8, [0, 0, 0], 0, 201, 0), + new DemoDesc("Hyrule", "seal.stb", [0], "seal.stb", 4, [0.0, 0.0, 0.0], 0, 0, 0), + new DemoDesc("LinkRM", "tale.stb", [0], "tale.stb", 8, [0, 0, 0], 0, 0, 0), + new DemoDesc("LinkRM", "tale_2.stb", [0], "tale_2.stb", 8, [0, 0, 0], 0, 0, 0), + new DemoDesc("LinkRM", "get_shield.stb", [0], "get_shield.stb", 9, [0, 0, 0], 0, 201, 0), + new DemoDesc("LinkRM", "tale_2.stb", [0], "tale_2.stb", 8, [0, 0, 0], 0, 0, 0), + new DemoDesc("M2ganon", "attack_ganon.stb", [0], "attack_ganon.stb", 1, [2000.0, 11780.0, -8000.0], 0.0, 244, 0), + new DemoDesc("M2tower", "rescue.stb", [0], "rescue.stb", 1, [3214.0, 3939.0, -3011.0], 57.5, 20, 0), + new DemoDesc("M_DaiB", "pray_zola.stb", [0], "pray_zola.stb", 8, [0, 0, 0], 0, 229, 0), + new DemoDesc("MajyuE", "maju_shinnyu.stb", [0], "maju_shinnyu.stb", 0, [0, 0, 0], 0, 0, 0), + new DemoDesc("Mjtower", "find_sister.stb", [0], "find_sister.stb", 0, [4889.0, 0.0, -2635.0], 57.5, 0, 0), + new DemoDesc("Obombh", "bombshop.stb", [0], "bombshop.stb", 0, [0.0, 0.0, 0.0], 0.0, 200, 0), + new DemoDesc("Omori", "getperl_deku.stb", [0], "getperl_deku.stb", 9, [0, 0, 0], 0, 214, 0), + new DemoDesc("Omori", "meet_deku.stb", [0], "meet_deku.stb", 8, [0, 0, 0], 0, 213, 0), + new DemoDesc("Otkura", "awake_kokiri.stb", [0], "awake_kokiri.stb", 8, [0, 0, 0], 0, 0, 0), + new DemoDesc("Pjavdou", "getperl_jab.stb", [0], "getperl_jab.stb", 8, [0.0, 0.0, 0.0], 0.0, 0, 0), + new DemoDesc("kazeB", "pray_kokiri.stb", [0], "pray_kokiri.stb", 8, [0.0, 300.0, 0.0], 0.0, 232, 0), + new DemoDesc("kenroom", "awake_zelda.stb", [0], "awake_zelda.stb", 9, [0.0, 0.0, 0.0], 0.0, 2, 0), + new DemoDesc("kenroom", "master_sword.stb", [0], "master_sword.stb", 0, [-124.0, -3223.0, -7823.0], 180.0, 248, 0), + new DemoDesc("kenroom", "swing_sword.stb", [0], "swing_sword.stb", 10, [-124.0, -3223.0, -7823.0], 180.0, 249, 0), + + // These are present in the sea_T event_list.dat, but not in the room's lbnk. They are only playable from "sea". + // new DemoDesc("sea_T", "Awaken", "awake.stb", 255, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), + // new DemoDesc("sea_T", "Departure", "departure.stb", 255, 0, [-200000.0, 0.0, 320000.0], 0.0, 0, 0), + // new DemoDesc("sea_T", "PirateZeldaFly", "kaizoku_zelda_fly.stb", 255, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), + + // The game expects this STB file to be in Stage/Ocean/Stage.arc, but it is not. Must be a leftover. + // new DemoDesc("Ocean", "counter.stb", "counter.stb", -1, 0, [0, 0, 0], 0, 0, 0), + "Outset Island", new SceneDesc("sea_T", "Title Screen", [44]), new SceneDesc("sea", "Outset Island", [44]), From 0e495ab4f8e428eb548ed8565733a7118c84569d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 22:18:42 -0700 Subject: [PATCH 097/102] Remove demo UI panels --- src/ZeldaWindWaker/Main.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 9080f91e9..8e2a62547 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -323,7 +323,6 @@ export class WindWakerRenderer implements Viewer.SceneGfx { public renderHelper: GXRenderHelperGfx; public rooms: WindWakerRoom[] = []; - public demos: DemoDesc[] = [] public extraTextures: ZWWExtraTextures; public renderCache: GfxRenderCache; @@ -367,16 +366,6 @@ export class WindWakerRenderer implements Viewer.SceneGfx { roomsPanel.setTitle(UI.LAYER_ICON, 'Rooms'); roomsPanel.setLayers(this.rooms); - const demosPanel = new UI.Panel(); - demosPanel.customHeaderBackgroundColor = UI.COOL_BLUE_COLOR; - demosPanel.setTitle(UI.CUTSCENE_ICON, 'Cutscenes'); - const demoSelect = new UI.SingleSelect(); - demoSelect.setStrings(this.demos.map(d => d.name)); - demoSelect.onselectionchange = (idx: number) => { - this.demos[idx].playDemo(this.globals) - }; - demosPanel.contents.appendChild(demoSelect.elem); - const renderHacksPanel = new UI.Panel(); renderHacksPanel.customHeaderBackgroundColor = UI.COOL_BLUE_COLOR; renderHacksPanel.setTitle(UI.RENDER_HACKS_ICON, 'Render Hacks'); @@ -407,9 +396,7 @@ export class WindWakerRenderer implements Viewer.SceneGfx { renderHacksPanel.contents.appendChild(wireframe.elem); } - const panels = [roomsPanel, scenarioPanel, renderHacksPanel]; - if( this.demos.length > 0 ) { panels.push(demosPanel); } - return panels; + return [roomsPanel, scenarioPanel, renderHacksPanel]; } // For people to play around with. From f504613748928fe9300dca92ca7c71563c2af6bd Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 22:30:48 -0700 Subject: [PATCH 098/102] Small cleanup --- src/ZeldaWindWaker/Main.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 8e2a62547..4a4587350 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -988,9 +988,11 @@ class DemoDesc extends SceneDesc { async playDemo(globals: dGlobals) { 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; + const lbnk = globals.roomCtrl.status[this.roomList[0]].data.lbnk; if (lbnk) { const bank = lbnk[this.layer]; if (bank != 0xFF) { From 9adf36993814cc5dccd856c5db2fc26e6636ad86 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 22:44:38 -0700 Subject: [PATCH 099/102] Move most cutscenes to a separate demoDesc list When a demo is complete enough to be worth viewing, move it to the sceneDescs list --- src/ZeldaWindWaker/Main.ts | 69 +++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 4a4587350..ec5c16b85 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -960,7 +960,7 @@ class SceneDesc { } } -class DemoDesc extends SceneDesc { +class DemoDesc extends SceneDesc implements Viewer.SceneDesc { private scene: SceneDesc; public constructor( @@ -1031,31 +1031,13 @@ class DemoDesc extends SceneDesc { } } -// Location names taken from CryZe's Debug Menu. -// https://github.com/CryZe/WindWakerDebugMenu/blob/master/src/warp_menu/consts.rs -const sceneDescs = [ - "The Great Sea", - new SceneDesc("sea", "The Great Sea", [ - 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, - 43, 44, 45, 46, 47, 48, 49, - ]), - - new SceneDesc("Asoko", "Tetra's Ship"), - new SceneDesc("Abship", "Submarine"), - new SceneDesc("Abesso", "Cabana"), - new SceneDesc("Ocean", "Boating Course"), - new SceneDesc("ShipD", "Islet of Steel"), - new SceneDesc("PShip", "Ghost Ship"), - new SceneDesc("Obshop", "Beedle's Shop", [1]), - - "Cutscenes", - new DemoDesc("sea_T", "Title Screen", [44], "title.stb", 0, [-220000.0, 0.0, 320000.0], 180.0, 0, 0), - new DemoDesc("sea", "Awaken", [44], "awake.stb", 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), +// Move these into sceneDescs once they are complete enough to be worth viewing. +// Extracted from the game using a modified version of the excellent wwrando/wwlib/stage_searcher.py script from LagoLunatic +// Most of this data comes from the PLAY action of the PACKAGE actor in an event from a Stage's event_list.dat. This +// action has properties for the room and layer that these cutscenes are designed for. HOWEVER, this data is often missing. +// It has been reconstructed by cross-referencing each Room's lbnk section (which points to a Demo*.arc file for each layer), +// the .stb files contained in each of those Objects/Demo*.arc files, and the FileName attribute from the event action. +const demoDescs = [ new DemoDesc("sea", "Stolen Sister", [44], "stolensister.stb", 9, [0.0, 0.0, 20000.0], 0, 0, 0), new DemoDesc("sea", "Departure", [44], "departure.stb", 10, [-200000.0, 0.0, 320000.0], 0.0, 204, 0), new DemoDesc("sea", "Pirate Zelda Fly", [44], "kaizoku_zelda_fly.stb", 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), @@ -1103,12 +1085,39 @@ const sceneDescs = [ new DemoDesc("kenroom", "swing_sword.stb", [0], "swing_sword.stb", 10, [-124.0, -3223.0, -7823.0], 180.0, 249, 0), // These are present in the sea_T event_list.dat, but not in the room's lbnk. They are only playable from "sea". - // new DemoDesc("sea_T", "Awaken", "awake.stb", 255, 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), - // new DemoDesc("sea_T", "Departure", "departure.stb", 255, 0, [-200000.0, 0.0, 320000.0], 0.0, 0, 0), - // new DemoDesc("sea_T", "PirateZeldaFly", "kaizoku_zelda_fly.stb", 255, 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), + new DemoDesc("sea_T", "Awaken", [0xFF], "awake.stb", 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea_T", "Departure", [0xFF], "departure.stb", 0, [-200000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea_T", "PirateZeldaFly", [0xFF], "kaizoku_zelda_fly.stb", 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), // The game expects this STB file to be in Stage/Ocean/Stage.arc, but it is not. Must be a leftover. - // new DemoDesc("Ocean", "counter.stb", "counter.stb", -1, 0, [0, 0, 0], 0, 0, 0), + new DemoDesc("Ocean", "counter.stb", [-1], "counter.stb", 0, [0, 0, 0], 0, 0, 0), +] + +// Location names taken from CryZe's Debug Menu. +// https://github.com/CryZe/WindWakerDebugMenu/blob/master/src/warp_menu/consts.rs +const sceneDescs = [ + "The Great Sea", + new SceneDesc("sea", "The Great Sea", [ + 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, + ]), + + new SceneDesc("Asoko", "Tetra's Ship"), + new SceneDesc("Abship", "Submarine"), + new SceneDesc("Abesso", "Cabana"), + new SceneDesc("Ocean", "Boating Course"), + new SceneDesc("ShipD", "Islet of Steel"), + new SceneDesc("PShip", "Ghost Ship"), + new SceneDesc("Obshop", "Beedle's Shop", [1]), + + "Cutscenes", + new DemoDesc("sea_T", "Title Screen", [44], "title.stb", 0, [-220000.0, 0.0, 320000.0], 180.0, 0, 0), + new DemoDesc("sea", "Awaken", [44], "awake.stb", 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), "Outset Island", new SceneDesc("sea_T", "Title Screen", [44]), From 42e74d61a3f6e9b43cc44ec69b64dfb129f93f36 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 20 Nov 2024 21:46:05 -0700 Subject: [PATCH 100/102] Disable mouse input when cutscene is playing --- src/ZeldaWindWaker/Main.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index ec5c16b85..6437a04df 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -493,6 +493,10 @@ export class WindWakerRenderer implements Viewer.SceneGfx { mat4.rotateZ(viewerInput.camera.worldMatrix, viewerInput.camera.worldMatrix, roll); viewerInput.camera.setClipPlanes(viewerInput.camera.near, viewerInput.camera.far); viewerInput.camera.worldMatrixUpdated(); + + this.globals.context.inputManager.isMouseEnabled = false; + } else { + this.globals.context.inputManager.isMouseEnabled = true; } this.globals.camera = viewerInput.camera; From 86326f550f4aa6793ee5a79563dbb935ee701a6e Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 21 Nov 2024 07:24:04 -0700 Subject: [PATCH 101/102] d_demo cleanup Added public/private to data members Remove some redundant type info when using = --- src/ZeldaWindWaker/d_demo.ts | 60 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index 720a97228..f5c07aed2 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -29,15 +29,15 @@ export enum EDemoCamFlags { } class dDemo_camera_c extends TCamera { - flags: number = 0; - projNear: number = 0; - projFar: number = 0; - fovY: number = 0; - aspect: number = 0; - viewPosition: vec3 = vec3.create(); - upVector: vec3 = vec3.create(); - targetPosition: vec3 = vec3.create(); - roll: number = 0; + public flags = 0; + public projNear = 0; + public projFar = 0; + public fovY = 0; + public aspect = 0; + public viewPosition = vec3.create(); + public upVector = vec3.create(); + public targetPosition = vec3.create(); + public roll = 0; constructor( private globals: dGlobals @@ -127,7 +127,7 @@ class dDemo_camera_c extends TCamera { public override JSGGetViewTargetPosition(dst: vec3) { const camera = this.globals.camera; if (!camera) - vec3.set(dst, 0, 0, 0); + vec3.zero(dst); vec3.add(dst, this.globals.cameraPosition, this.globals.cameraFwd); } @@ -165,26 +165,26 @@ export const enum EDemoActorFlags { } export class dDemo_actor_c extends TActor { - name: string; - flags: number; - translation = vec3.create(); - scaling = vec3.create(); - rotation = vec3.create(); - shapeId: number; - nextBckId: number; - animFrame: number; - animTransition: number; - animFrameMax: number; - texAnim: number; - texAnimFrame: number; - textAnimFrameMax: number; - model: J3DModelInstance; - stbDataId: number; - stbData: DataView; - bckId: number; - btpId: number; - btkId: number; - brkId: number; + public name: string; + public flags: number; + public translation = vec3.create(); + public scaling = vec3.create(); + public rotation = vec3.create(); + public shapeId: number; + public nextBckId: number; + public animFrame: number; + public animTransition: number; + public animFrameMax: number; + public texAnim: number; + public texAnimFrame: number; + public textAnimFrameMax: number; + public model: J3DModelInstance; + public stbDataId: number; + public stbData: DataView; + public bckId: number; + public btpIed: number; + public btkId: number; + public brkId: number; constructor(public actor: fopAc_ac_c) { super(); } From 29efd76d19f4ec54f752d8c12ab25cba4a53822e Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 21 Nov 2024 07:25:12 -0700 Subject: [PATCH 102/102] Small cleanup --- src/ZeldaWindWaker/Main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 6437a04df..182597c24 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -793,8 +793,8 @@ class d_s_play extends fopScn { this.demo.update(); // From executeEvtManager() -> SpecialProcPackage() - if (globals.scnPlay.demo.getMode() == EDemoMode.Ended) { - globals.scnPlay.demo.remove(); + if (this.demo.getMode() == EDemoMode.Ended) { + this.demo.remove(); } }