diff --git a/src/lib/index.ts b/src/lib/index.ts index 67323c4..52a4d5f 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -19,3 +19,6 @@ export * from './model/M2SkinProfile.js'; export * from './model/M2Batch.js'; export * from './model/M2Material.js'; export * from './model/M2Texture.js'; +export * from './model/M2Camera.js'; +export * from './model/M2Track.js'; +export * from './model/M2SplineKey.js'; diff --git a/src/lib/model/M2Camera.ts b/src/lib/model/M2Camera.ts new file mode 100644 index 0000000..8d3bd9e --- /dev/null +++ b/src/lib/model/M2Camera.ts @@ -0,0 +1,88 @@ +import { M2Track, M2SplineKey } from '../index.js'; + +class M2Camera { + #cameraId: number; + #fieldOfView: number; + #farClip: number; + #nearClip: number; + #positionTrack: M2Track>; + #positionBase: number[]; + #targetTrack: M2Track>; + #targetBase: number[]; + #rollTrack: M2Track>; + + constructor( + cameraId: number, + fieldOfView: number, + farClip: number, + nearClip: number, + positionTrack: M2Track>, + positionBase: number[], + targetTrack: M2Track>, + targetBase: number[], + rollTrack: M2Track>, + ) { + this.#cameraId = cameraId; + this.#fieldOfView = fieldOfView; + this.#farClip = farClip; + this.#nearClip = nearClip; + this.#positionTrack = positionTrack; + this.#positionBase = positionBase; + this.#targetTrack = targetTrack; + this.#targetBase = targetBase; + this.#rollTrack = rollTrack; + } + + get cameraId() { + return this.#cameraId; + } + + get fieldOfView() { + return this.#fieldOfView; + } + + get farClip() { + return this.#farClip; + } + + get nearClip() { + return this.#nearClip; + } + + get positionTrack() { + return this.#positionTrack; + } + + get positionBase() { + return this.#positionBase; + } + + get targetTrack() { + return this.#targetTrack; + } + + get targetBase() { + return this.#targetBase; + } + + get rollTrack() { + return this.#rollTrack; + } + + toObject() { + return { + cameraId: this.#cameraId, + fieldOfView: this.#fieldOfView, + farClip: this.#farClip, + nearClip: this.#nearClip, + positionTrack: this.#positionTrack.toObject(), + positionBase: this.#positionBase, + targetTrack: this.#targetTrack.toObject(), + targetBase: this.#targetBase, + rollTrack: this.#rollTrack.toObject(), + }; + } +} + +export default M2Camera; +export { M2Camera }; diff --git a/src/lib/model/M2Model.ts b/src/lib/model/M2Model.ts index 732b440..dfa4292 100644 --- a/src/lib/model/M2Model.ts +++ b/src/lib/model/M2Model.ts @@ -2,18 +2,14 @@ import { IoMode, IoSource, openStream } from '@wowserhq/io'; import * as io from '@wowserhq/io'; import * as m2Io from './io/m2.js'; import { M2_BONE_FLAG, M2_MODEL_FLAG } from './const.js'; -import { - M2Sequence, - M2Track, - M2TextureTransform, - M2TextureWeight, - M2Color, - M2Bone, -} from './types.js'; +import { M2Sequence, M2TextureTransform, M2TextureWeight, M2Color, M2Bone } from './types.js'; import M2Texture, { M2_TEXTURE_COMBINER, M2_TEXTURE_COORD } from './M2Texture.js'; import M2Material from './M2Material.js'; import { m2typedArray } from './io/common.js'; import M2Bounds from './M2Bounds.js'; +import M2Camera from './M2Camera.js'; +import M2Track from './M2Track.js'; +import M2SplineKey from './M2SplineKey.js'; class M2Model { #name: string; @@ -44,6 +40,8 @@ class M2Model { #sequences: M2Sequence[] = []; #loops: Uint32Array; + #cameras: M2Camera[] = []; + get bones() { return this.#bones; } @@ -124,6 +122,10 @@ class M2Model { return this.#vertices; } + get cameras() { + return this.#cameras; + } + load(source: IoSource) { const stream = openStream(source, IoMode.Read); @@ -164,6 +166,8 @@ class M2Model { this.#sequences = data.sequences; + this.#loadCameras(data); + return this; } @@ -180,6 +184,38 @@ class M2Model { ); } } + + createTrack(data: any): M2Track> { + const splineKeys: M2SplineKey[] = []; + for (const spline of data.sequenceKeys) { + splineKeys.push(new M2SplineKey(spline.value, spline.inTan, spline.outTan)); + } + + return new M2Track>( + data.trackType, + data.loopIndex, + data.sequenceTimes, + splineKeys, + ); + } + + #loadCameras(data: any) { + for (const cameraData of data.cameras) { + this.#cameras.push( + new M2Camera( + cameraData.cameraId, + cameraData.fieldOfView, + cameraData.farClip, + cameraData.nearClip, + this.createTrack(cameraData.positionTrack), + cameraData.positionBase, + this.createTrack(cameraData.targetTrack), + cameraData.targetBase, + this.createTrack(cameraData.rollTrack), + ), + ); + } + } } export default M2Model; diff --git a/src/lib/model/M2SplineKey.ts b/src/lib/model/M2SplineKey.ts new file mode 100644 index 0000000..0d7c1f4 --- /dev/null +++ b/src/lib/model/M2SplineKey.ts @@ -0,0 +1,34 @@ +class M2SplineKey { + #value: T; + #inTan: T; + #outTan: T; + + constructor(value: T, inTan: T, outTan: T) { + this.#value = value; + this.#inTan = inTan; + this.#outTan = outTan; + } + + get value() { + return this.#value; + } + + get inTan() { + return this.#inTan; + } + + get outTan() { + return this.#outTan; + } + + toObject() { + return { + value: this.#value, + inTan: this.#inTan, + outTan: this.#outTan, + }; + } +} + +export default M2SplineKey; +export { M2SplineKey }; diff --git a/src/lib/model/M2Track.ts b/src/lib/model/M2Track.ts new file mode 100644 index 0000000..566c74b --- /dev/null +++ b/src/lib/model/M2Track.ts @@ -0,0 +1,45 @@ +type Serializable = { + toObject(); +}; + +class M2Track { + #type: number; + #loopIndex: number; + #sequenceTimes: Uint32Array; + #sequenceKeys: T[]; + + constructor(type: number, loopIndex: number, sequenceTimes: Uint32Array, sequenceKeys: T[]) { + this.#type = type; + this.#loopIndex = loopIndex; + this.#sequenceTimes = sequenceTimes; + this.#sequenceKeys = sequenceKeys; + } + + get type() { + return this.#type; + } + + get loopIndex() { + return this.#loopIndex; + } + + get sequenceTimes() { + return this.#sequenceTimes; + } + + get sequenceKeys() { + return this.#sequenceKeys; + } + + toObject() { + return { + type: this.#type, + loopIndex: this.#loopIndex, + sequenceTimes: this.#sequenceTimes, + sequenceKeys: this.#sequenceKeys.map((sequenceKey) => sequenceKey.toObject()), + }; + } +} + +export default M2Track; +export { M2Track }; diff --git a/src/lib/model/io/common.ts b/src/lib/model/io/common.ts index 6791b9d..2e953ed 100644 --- a/src/lib/model/io/common.ts +++ b/src/lib/model/io/common.ts @@ -8,6 +8,13 @@ const m2array = (type: IoType): IoType => new M2ArrayIo(type); const m2typedArray = (type: IoType, elements = 1): IoType => new M2TypedArrayIo(type, elements); +const m2splineKey = (type: IoType): IoType => + io.struct({ + value: type, + inTan: type, + outTan: type, + }); + const m2string: IoType = new M2StringIo(); const m2range: IoType = io.struct({ @@ -20,12 +27,12 @@ const m2bounds: IoType = io.struct({ radius: io.float32le, }); -const m2track = (type: IoType, elements = 1): IoType => +const m2track = (type: IoType): IoType => io.struct({ trackType: io.uint16le, loopIndex: io.uint16le, sequenceTimes: m2array(m2typedArray(io.uint32le)), - sequenceKeys: m2array(m2typedArray(type, elements)), + sequenceKeys: m2array(type), }); -export { m2array, m2typedArray, m2string, m2range, m2bounds, m2track }; +export { m2array, m2typedArray, m2string, m2range, m2bounds, m2track, m2splineKey }; diff --git a/src/lib/model/io/m2.ts b/src/lib/model/io/m2.ts index 5c115a1..689c673 100644 --- a/src/lib/model/io/m2.ts +++ b/src/lib/model/io/m2.ts @@ -1,6 +1,14 @@ import * as io from '@wowserhq/io'; import { IoType } from '@wowserhq/io'; -import { m2array, m2typedArray, m2bounds, m2range, m2string, m2track } from './common.js'; +import { + m2array, + m2typedArray, + m2bounds, + m2range, + m2string, + m2track, + m2splineKey, +} from './common.js'; const m2sequence = io.struct({ id: io.uint16le, @@ -23,9 +31,9 @@ const m2compBone = io.struct({ parentIndex: io.int16le, distToParent: io.uint16le, boneNameCrc: io.uint32le, - translationTrack: m2track(io.float32le, 3), - rotationTrack: m2track(io.int16le, 4), - scaleTrack: m2track(io.float32le, 3), + translationTrack: m2track(m2typedArray(io.float32le, 3)), + rotationTrack: m2track(m2typedArray(io.int16le, 4)), + scaleTrack: m2track(m2typedArray(io.float32le, 3)), pivot: io.typedArray(io.float32le, { size: 3 }), }); @@ -38,8 +46,8 @@ const m2vertex = io.struct({ }); const m2color = io.struct({ - colorTrack: m2track(io.float32le, 3), - alphaTrack: m2track(io.int16le), + colorTrack: m2track(m2typedArray(io.float32le, 3)), + alphaTrack: m2track(m2typedArray(io.int16le)), }); const m2texture = io.struct({ @@ -49,13 +57,13 @@ const m2texture = io.struct({ }); const m2textureWeight = io.struct({ - weightTrack: m2track(io.int16le), + weightTrack: m2track(m2typedArray(io.int16le)), }); const m2textureTransform = io.struct({ - translationTrack: m2track(io.float32le, 3), - rotationTrack: m2track(io.float32le, 4), - scalingTrack: m2track(io.float32le, 3), + translationTrack: m2track(m2typedArray(io.float32le, 3)), + rotationTrack: m2track(m2typedArray(io.float32le, 4)), + scalingTrack: m2track(m2typedArray(io.float32le, 3)), }); const m2material = io.struct({ @@ -68,7 +76,7 @@ const m2attachment = io.struct({ boneIndex: io.uint16le, padding: io.uint16le, position: io.typedArray(io.float32le, { size: 3 }), - visibilityTrack: m2track(io.uint8), + visibilityTrack: m2track(m2typedArray(io.uint8)), }); const m2event = io.struct({ @@ -80,6 +88,8 @@ const m2event = io.struct({ todo: io.typedArray(io.uint8, { size: 12 }), }); +const m2vector = io.typedArray(io.float32le, { size: 3 }); + const m2light = io.struct({ lightType: io.uint16le, boneIndex: io.uint16le, @@ -93,8 +103,11 @@ const m2camera = io.struct({ fieldOfView: io.float32le, farClip: io.float32le, nearClip: io.float32le, - /* TODO tracks */ - todo: io.typedArray(io.uint8, { size: 84 }), + positionTrack: m2track(m2splineKey(m2vector)), + positionBase: m2vector, + targetTrack: m2track(m2splineKey(m2vector)), + targetBase: m2vector, + rollTrack: m2track(m2splineKey(io.float32le)), }); const m2ribbon = io.struct({ diff --git a/src/lib/model/types.ts b/src/lib/model/types.ts index 1cd075d..89cfd28 100644 --- a/src/lib/model/types.ts +++ b/src/lib/model/types.ts @@ -65,4 +65,20 @@ type M2TextureWeight = { weightTrack: M2Track; }; -export { M2Bone, M2Color, M2Sequence, M2SkinSection, M2TextureTransform, M2TextureWeight, M2Track }; +type M2Camera = { + cameraId: number; + fieldOfView: number; + farClip: number; + nearClip: number; +}; + +export { + M2Bone, + M2Color, + M2Sequence, + M2SkinSection, + M2TextureTransform, + M2TextureWeight, + M2Track, + M2Camera, +};