diff --git a/src/data_formats/parsing/ninja/index.ts b/src/data_formats/parsing/ninja/index.ts index 334a9e9a..9239b80e 100644 --- a/src/data_formats/parsing/ninja/index.ts +++ b/src/data_formats/parsing/ninja/index.ts @@ -1,6 +1,6 @@ -import { Vec3 } from "../../Vec3"; import { BufferCursor } from "../../BufferCursor"; -import { NjModel, parse_nj_model } from "./nj"; +import { Vec3 } from "../../Vec3"; +import { NjcmModel, parse_njcm_model } from "./njcm"; import { parse_xj_model, XjModel } from "./xj"; // TODO: @@ -9,7 +9,7 @@ import { parse_xj_model, XjModel } from "./xj"; const ANGLE_TO_RAD = (2 * Math.PI) / 65536; -export type NinjaVertex = { +export type NjVertex = { position: Vec3; normal?: Vec3; bone_weight: number; @@ -17,26 +17,34 @@ export type NinjaVertex = { calc_continue: boolean; }; -export type NinjaModel = NjModel | XjModel; +export type NjModel = NjcmModel | XjModel; -export class NinjaObject { - evaluation_flags: NinjaEvaluationFlags; +export function is_njcm_model(model: NjModel): model is NjcmModel { + return model.type === "njcm"; +} + +export function is_xj_model(model: NjModel): model is XjModel { + return model.type === "xj"; +} + +export class NjObject { + evaluation_flags: NjEvaluationFlags; model: M | undefined; position: Vec3; rotation: Vec3; // Euler angles in radians. scale: Vec3; - children: NinjaObject[]; + children: NjObject[]; - private bone_cache = new Map | null>(); + private bone_cache = new Map | null>(); private _bone_count = -1; constructor( - evaluation_flags: NinjaEvaluationFlags, + evaluation_flags: NjEvaluationFlags, model: M | undefined, position: Vec3, rotation: Vec3, // Euler angles in radians. scale: Vec3, - children: NinjaObject[] + children: NjObject[] ) { this.evaluation_flags = evaluation_flags; this.model = model; @@ -56,7 +64,7 @@ export class NinjaObject { return this._bone_count; } - get_bone(bone_id: number): NinjaObject | undefined { + get_bone(bone_id: number): NjObject | undefined { let bone = this.bone_cache.get(bone_id); // Strict check because null means there's no bone with this id. @@ -69,10 +77,10 @@ export class NinjaObject { } private get_bone_internal( - object: NinjaObject, + object: NjObject, bone_id: number, id_ref: [number] - ): NinjaObject | undefined { + ): NjObject | undefined { if (!object.evaluation_flags.skip) { const id = id_ref[0]++; this.bone_cache.set(id, object); @@ -91,7 +99,7 @@ export class NinjaObject { } } -export type NinjaEvaluationFlags = { +export type NjEvaluationFlags = { no_translate: boolean; no_rotate: boolean; no_scale: boolean; @@ -102,19 +110,19 @@ export type NinjaEvaluationFlags = { shape_skip: boolean; }; -export function parse_nj(cursor: BufferCursor): NinjaObject[] { - return parse_ninja(cursor, parse_nj_model, []); +export function parse_nj(cursor: BufferCursor): NjObject[] { + return parse_ninja(cursor, parse_njcm_model, []); } -export function parse_xj(cursor: BufferCursor): NinjaObject[] { +export function parse_xj(cursor: BufferCursor): NjObject[] { return parse_ninja(cursor, parse_xj_model, undefined); } -function parse_ninja( +function parse_ninja( cursor: BufferCursor, parse_model: (cursor: BufferCursor, context: any) => M, context: any -): NinjaObject[] { +): NjObject[] { while (cursor.bytes_left) { // Ninja uses a little endian variant of the IFF format. // IFF files contain chunks preceded by an 8-byte header. @@ -137,11 +145,11 @@ function parse_ninja( } // TODO: cache model and object offsets so we don't reparse the same data. -function parse_sibling_objects( +function parse_sibling_objects( cursor: BufferCursor, parse_model: (cursor: BufferCursor, context: any) => M, context: any -): NinjaObject[] { +): NjObject[] { const eval_flags = cursor.u32(); const no_translate = (eval_flags & 0b1) !== 0; const no_rotate = (eval_flags & 0b10) !== 0; @@ -166,8 +174,8 @@ function parse_sibling_objects( const sibling_offset = cursor.u32(); let model: M | undefined; - let children: NinjaObject[]; - let siblings: NinjaObject[]; + let children: NjObject[]; + let siblings: NjObject[]; if (model_offset) { cursor.seek_start(model_offset); @@ -188,7 +196,7 @@ function parse_sibling_objects( siblings = []; } - const object = new NinjaObject( + const object = new NjObject( { no_translate, no_rotate, diff --git a/src/data_formats/parsing/ninja/nj.ts b/src/data_formats/parsing/ninja/njcm.ts similarity index 82% rename from src/data_formats/parsing/ninja/nj.ts rename to src/data_formats/parsing/ninja/njcm.ts index 68c3a42e..ce2de6f4 100644 --- a/src/data_formats/parsing/ninja/nj.ts +++ b/src/data_formats/parsing/ninja/njcm.ts @@ -1,7 +1,7 @@ import Logger from "js-logger"; import { BufferCursor } from "../../BufferCursor"; import { Vec3 } from "../../Vec3"; -import { NinjaVertex } from "../ninja"; +import { NjVertex } from "."; const logger = Logger.get("data_formats/parsing/ninja/nj"); @@ -12,19 +12,19 @@ const logger = Logger.get("data_formats/parsing/ninja/nj"); // - animation // - deal with vertex information contained in triangle strips -export type NjModel = { - type: "nj"; +export type NjcmModel = { + type: "njcm"; /** * Sparse array of vertices. */ - vertices: NinjaVertex[]; - meshes: NjTriangleStrip[]; + vertices: NjVertex[]; + meshes: NjcmTriangleStrip[]; // materials: [], bounding_sphere_center: Vec3; bounding_sphere_radius: number; }; -enum NjChunkType { +enum NjcmChunkType { Unknown, Null, Bits, @@ -38,72 +38,72 @@ enum NjChunkType { End, } -type NjChunk = { - type: NjChunkType; +type NjcmChunk = { + type: NjcmChunkType; type_id: number; } & ( - | NjUnknownChunk - | NjNullChunk - | NjBitsChunk - | NjCachePolygonListChunk - | NjDrawPolygonListChunk - | NjTinyChunk - | NjMaterialChunk - | NjVertexChunk - | NjVolumeChunk - | NjStripChunk - | NjEndChunk); + | NjcmUnknownChunk + | NjcmNullChunk + | NjcmBitsChunk + | NjcmCachePolygonListChunk + | NjcmDrawPolygonListChunk + | NjcmTinyChunk + | NjcmMaterialChunk + | NjcmVertexChunk + | NjcmVolumeChunk + | NjcmStripChunk + | NjcmEndChunk); -type NjUnknownChunk = { - type: NjChunkType.Unknown; +type NjcmUnknownChunk = { + type: NjcmChunkType.Unknown; }; -type NjNullChunk = { - type: NjChunkType.Null; +type NjcmNullChunk = { + type: NjcmChunkType.Null; }; -type NjBitsChunk = { - type: NjChunkType.Bits; +type NjcmBitsChunk = { + type: NjcmChunkType.Bits; }; -type NjCachePolygonListChunk = { - type: NjChunkType.CachePolygonList; +type NjcmCachePolygonListChunk = { + type: NjcmChunkType.CachePolygonList; cache_index: number; offset: number; }; -type NjDrawPolygonListChunk = { - type: NjChunkType.DrawPolygonList; +type NjcmDrawPolygonListChunk = { + type: NjcmChunkType.DrawPolygonList; cache_index: number; }; -type NjTinyChunk = { - type: NjChunkType.Tiny; +type NjcmTinyChunk = { + type: NjcmChunkType.Tiny; }; -type NjMaterialChunk = { - type: NjChunkType.Material; +type NjcmMaterialChunk = { + type: NjcmChunkType.Material; }; -type NjVertexChunk = { - type: NjChunkType.Vertex; - vertices: NjVertex[]; +type NjcmVertexChunk = { + type: NjcmChunkType.Vertex; + vertices: NjcmVertex[]; }; -type NjVolumeChunk = { - type: NjChunkType.Volume; +type NjcmVolumeChunk = { + type: NjcmChunkType.Volume; }; -type NjStripChunk = { - type: NjChunkType.Strip; - triangle_strips: NjTriangleStrip[]; +type NjcmStripChunk = { + type: NjcmChunkType.Strip; + triangle_strips: NjcmTriangleStrip[]; }; -type NjEndChunk = { - type: NjChunkType.End; +type NjcmEndChunk = { + type: NjcmChunkType.End; }; -type NjVertex = { +type NjcmVertex = { index: number; position: Vec3; normal?: Vec3; @@ -112,7 +112,7 @@ type NjVertex = { calc_continue: boolean; }; -type NjTriangleStrip = { +type NjcmTriangleStrip = { ignore_light: boolean; ignore_specular: boolean; ignore_ambient: boolean; @@ -121,27 +121,27 @@ type NjTriangleStrip = { flat_shading: boolean; environment_mapping: boolean; clockwise_winding: boolean; - vertices: NjMeshVertex[]; + vertices: NjcmMeshVertex[]; }; -type NjMeshVertex = { +type NjcmMeshVertex = { index: number; normal?: Vec3; }; -export function parse_nj_model(cursor: BufferCursor, cached_chunk_offsets: number[]): NjModel { +export function parse_njcm_model(cursor: BufferCursor, cached_chunk_offsets: number[]): NjcmModel { const vlist_offset = cursor.u32(); // Vertex list const plist_offset = cursor.u32(); // Triangle strip index list const bounding_sphere_center = new Vec3(cursor.f32(), cursor.f32(), cursor.f32()); const bounding_sphere_radius = cursor.f32(); - const vertices: NinjaVertex[] = []; - const meshes: NjTriangleStrip[] = []; + const vertices: NjVertex[] = []; + const meshes: NjcmTriangleStrip[] = []; if (vlist_offset) { cursor.seek_start(vlist_offset); for (const chunk of parse_chunks(cursor, cached_chunk_offsets, true)) { - if (chunk.type === NjChunkType.Vertex) { + if (chunk.type === NjcmChunkType.Vertex) { for (const vertex of chunk.vertices) { vertices[vertex.index] = { position: vertex.position, @@ -159,14 +159,14 @@ export function parse_nj_model(cursor: BufferCursor, cached_chunk_offsets: numbe cursor.seek_start(plist_offset); for (const chunk of parse_chunks(cursor, cached_chunk_offsets, false)) { - if (chunk.type === NjChunkType.Strip) { + if (chunk.type === NjcmChunkType.Strip) { meshes.push(...chunk.triangle_strips); } } } return { - type: "nj", + type: "njcm", vertices, meshes, bounding_sphere_center, @@ -179,8 +179,8 @@ function parse_chunks( cursor: BufferCursor, cached_chunk_offsets: number[], wide_end_chunks: boolean -): NjChunk[] { - const chunks: NjChunk[] = []; +): NjcmChunk[] { + const chunks: NjcmChunk[] = []; let loop = true; while (loop) { @@ -191,19 +191,19 @@ function parse_chunks( if (type_id === 0) { chunks.push({ - type: NjChunkType.Null, + type: NjcmChunkType.Null, type_id, }); } else if (1 <= type_id && type_id <= 3) { chunks.push({ - type: NjChunkType.Bits, + type: NjcmChunkType.Bits, type_id, }); } else if (type_id === 4) { const cache_index = flags; const offset = cursor.position; chunks.push({ - type: NjChunkType.CachePolygonList, + type: NjcmChunkType.CachePolygonList, type_id, cache_index, offset, @@ -220,53 +220,53 @@ function parse_chunks( } chunks.push({ - type: NjChunkType.DrawPolygonList, + type: NjcmChunkType.DrawPolygonList, type_id, cache_index, }); } else if (8 <= type_id && type_id <= 9) { size = 2; chunks.push({ - type: NjChunkType.Tiny, + type: NjcmChunkType.Tiny, type_id, }); } else if (17 <= type_id && type_id <= 31) { size = 2 + 2 * cursor.u16(); chunks.push({ - type: NjChunkType.Material, + type: NjcmChunkType.Material, type_id, }); } else if (32 <= type_id && type_id <= 50) { size = 2 + 4 * cursor.u16(); chunks.push({ - type: NjChunkType.Vertex, + type: NjcmChunkType.Vertex, type_id, vertices: parse_vertex_chunk(cursor, type_id, flags), }); } else if (56 <= type_id && type_id <= 58) { size = 2 + 2 * cursor.u16(); chunks.push({ - type: NjChunkType.Volume, + type: NjcmChunkType.Volume, type_id, }); } else if (64 <= type_id && type_id <= 75) { size = 2 + 2 * cursor.u16(); chunks.push({ - type: NjChunkType.Strip, + type: NjcmChunkType.Strip, type_id, triangle_strips: parse_triangle_strip_chunk(cursor, type_id, flags), }); } else if (type_id === 255) { size = wide_end_chunks ? 2 : 0; chunks.push({ - type: NjChunkType.End, + type: NjcmChunkType.End, type_id, }); loop = false; } else { size = 2 + 2 * cursor.u16(); chunks.push({ - type: NjChunkType.Unknown, + type: NjcmChunkType.Unknown, type_id, }); logger.warn(`Unknown chunk type ${type_id} at offset ${chunk_start_position}.`); @@ -282,7 +282,7 @@ function parse_vertex_chunk( cursor: BufferCursor, chunk_type_id: number, flags: number -): NjVertex[] { +): NjcmVertex[] { if (chunk_type_id < 32 || chunk_type_id > 50) { logger.warn(`Unknown vertex chunk type ${chunk_type_id}.`); return []; @@ -294,10 +294,10 @@ function parse_vertex_chunk( const index = cursor.u16(); const vertex_count = cursor.u16(); - const vertices: NjVertex[] = []; + const vertices: NjcmVertex[] = []; for (let i = 0; i < vertex_count; ++i) { - const vertex: NjVertex = { + const vertex: NjcmVertex = { index: index + i, position: new Vec3( cursor.f32(), // x @@ -374,7 +374,7 @@ function parse_triangle_strip_chunk( cursor: BufferCursor, chunk_type_id: number, flags: number -): NjTriangleStrip[] { +): NjcmTriangleStrip[] { const render_flags = { ignore_light: (flags & 0b1) !== 0, ignore_specular: (flags & 0b10) !== 0, @@ -432,17 +432,17 @@ function parse_triangle_strip_chunk( const [parse_texture_coords, parse_color, parse_normal, parse_texture_coords_hires] = options; - const strips: NjTriangleStrip[] = []; + const strips: NjcmTriangleStrip[] = []; for (let i = 0; i < strip_count; ++i) { const winding_flag_and_index_count = cursor.i16(); const clockwise_winding = winding_flag_and_index_count < 1; const index_count = Math.abs(winding_flag_and_index_count); - const vertices: NjMeshVertex[] = []; + const vertices: NjcmMeshVertex[] = []; for (let j = 0; j < index_count; ++j) { - const vertex: NjMeshVertex = { + const vertex: NjcmMeshVertex = { index: cursor.u16(), }; vertices.push(vertex); diff --git a/src/data_formats/parsing/ninja/xj.ts b/src/data_formats/parsing/ninja/xj.ts index fa0a055e..056e5d5d 100644 --- a/src/data_formats/parsing/ninja/xj.ts +++ b/src/data_formats/parsing/ninja/xj.ts @@ -1,6 +1,6 @@ import { BufferCursor } from "../../BufferCursor"; import { Vec3 } from "../../Vec3"; -import { NinjaVertex } from "../ninja"; +import { NjVertex } from "../ninja"; // TODO: // - textures @@ -10,7 +10,7 @@ import { NinjaVertex } from "../ninja"; export type XjModel = { type: "xj"; - vertices: NinjaVertex[]; + vertices: NjVertex[]; meshes: XjTriangleStrip[]; }; diff --git a/src/rendering/animation.ts b/src/rendering/animation.ts index 4a35513e..fbfeac98 100644 --- a/src/rendering/animation.ts +++ b/src/rendering/animation.ts @@ -8,7 +8,7 @@ import { QuaternionKeyframeTrack, VectorKeyframeTrack, } from "three"; -import { NinjaModel, NinjaObject } from "../data_formats/parsing/ninja"; +import { NjModel, NjObject } from "../data_formats/parsing/ninja"; import { NjInterpolation, NjKeyframeTrackType, @@ -18,16 +18,16 @@ import { export const PSO_FRAME_RATE = 30; export function create_animation_clip( - object: NinjaObject, - motion: NjMotion + nj_object: NjObject, + nj_motion: NjMotion ): AnimationClip { const interpolation = - motion.interpolation === NjInterpolation.Spline ? InterpolateSmooth : InterpolateLinear; + nj_motion.interpolation === NjInterpolation.Spline ? InterpolateSmooth : InterpolateLinear; const tracks: KeyframeTrack[] = []; - motion.motion_data.forEach((motion_data, bone_id) => { - const bone = object.get_bone(bone_id); + nj_motion.motion_data.forEach((motion_data, bone_id) => { + const bone = nj_object.get_bone(bone_id); if (!bone) return; motion_data.tracks.forEach(({ type, keyframes }) => { @@ -71,7 +71,7 @@ export function create_animation_clip( return new AnimationClip( "Animation", - (motion.frame_count - 1) / PSO_FRAME_RATE, + (nj_motion.frame_count - 1) / PSO_FRAME_RATE, tracks ).optimize(); } diff --git a/src/rendering/models.ts b/src/rendering/models.ts index 04674900..5a4090ea 100644 --- a/src/rendering/models.ts +++ b/src/rendering/models.ts @@ -15,8 +15,8 @@ import { Vector3, } from "three"; import { vec3_to_threejs } from "."; -import { NinjaModel, NinjaObject } from "../data_formats/parsing/ninja"; -import { NjModel } from "../data_formats/parsing/ninja/nj"; +import { NjModel, NjObject, is_njcm_model } from "../data_formats/parsing/ninja"; +import { NjcmModel } from "../data_formats/parsing/ninja/njcm"; import { XjModel } from "../data_formats/parsing/ninja/xj"; const DEFAULT_MATERIAL = new MeshLambertMaterial({ @@ -34,14 +34,14 @@ const NO_ROTATION = new Quaternion(0, 0, 0, 1); const NO_SCALE = new Vector3(1, 1, 1); export function ninja_object_to_buffer_geometry( - object: NinjaObject, + object: NjObject, material: Material = DEFAULT_MATERIAL ): BufferGeometry { return new Object3DCreator(material).create_buffer_geometry(object); } export function ninja_object_to_skinned_mesh( - object: NinjaObject, + object: NjObject, material: Material = DEFAULT_SKINNED_MATERIAL ): SkinnedMesh { return new Object3DCreator(material).create_skinned_mesh(object); @@ -93,7 +93,7 @@ class Object3DCreator { this.material = material; } - create_buffer_geometry(object: NinjaObject): BufferGeometry { + create_buffer_geometry(object: NjObject): BufferGeometry { this.object_to_geometry(object, undefined, new Matrix4()); const geom = new BufferGeometry(); @@ -108,7 +108,7 @@ class Object3DCreator { return geom; } - create_skinned_mesh(object: NinjaObject): SkinnedMesh { + create_skinned_mesh(object: NjObject): SkinnedMesh { const geom = this.create_buffer_geometry(object); geom.addAttribute("skinIndex", new Uint16BufferAttribute(this.bone_indices, 4)); geom.addAttribute("skinWeight", new Float32BufferAttribute(this.bone_weights, 4)); @@ -123,7 +123,7 @@ class Object3DCreator { } private object_to_geometry( - object: NinjaObject, + object: NjObject, parent_bone: Bone | undefined, parent_matrix: Matrix4 ): void { @@ -184,15 +184,15 @@ class Object3DCreator { } } - private model_to_geometry(model: NinjaModel, matrix: Matrix4): void { - if (model.type === "nj") { - this.nj_model_to_geometry(model, matrix); + private model_to_geometry(model: NjModel, matrix: Matrix4): void { + if (is_njcm_model(model)) { + this.njcm_model_to_geometry(model, matrix); } else { this.xj_model_to_geometry(model, matrix); } } - private nj_model_to_geometry(model: NjModel, matrix: Matrix4): void { + private njcm_model_to_geometry(model: NjcmModel, matrix: Matrix4): void { const normal_matrix = new Matrix3().getNormalMatrix(matrix); const new_vertices = model.vertices.map(vertex => { diff --git a/src/stores/ModelViewerStore.ts b/src/stores/ModelViewerStore.ts index 480694f2..043cf9bc 100644 --- a/src/stores/ModelViewerStore.ts +++ b/src/stores/ModelViewerStore.ts @@ -2,7 +2,7 @@ import Logger from "js-logger"; import { action, observable } from "mobx"; import { AnimationAction, AnimationClip, AnimationMixer, SkinnedMesh } from "three"; import { BufferCursor } from "../data_formats/BufferCursor"; -import { NinjaModel, NinjaObject, parse_nj, parse_xj } from "../data_formats/parsing/ninja"; +import { NjModel, NjObject, parse_nj, parse_xj } from "../data_formats/parsing/ninja"; import { parse_njm, NjMotion } from "../data_formats/parsing/ninja/motion"; import { PlayerModel, PlayerAnimation } from "../domain"; import { create_animation_clip, PSO_FRAME_RATE } from "../rendering/animation"; @@ -10,7 +10,7 @@ import { ninja_object_to_skinned_mesh } from "../rendering/models"; import { get_player_data, get_player_animation_data } from "./binary_assets"; const logger = Logger.get("stores/ModelViewerStore"); -const nj_object_cache: Map>> = new Map(); +const nj_object_cache: Map>> = new Map(); const nj_motion_cache: Map> = new Map(); class ModelViewerStore { @@ -33,7 +33,7 @@ class ModelViewerStore { .map((_, i) => new PlayerAnimation(i, `Animation ${i + 1}`)); @observable.ref current_player_model?: PlayerModel; - @observable.ref current_model?: NinjaObject; + @observable.ref current_model?: NjObject; @observable.ref current_bone_count: number = 0; @observable.ref current_obj3d?: SkinnedMesh; @@ -130,7 +130,7 @@ class ModelViewerStore { private set_model = action( "set_model", - (model: NinjaObject, player_model?: PlayerModel) => { + (model: NjObject, player_model?: PlayerModel) => { if (this.current_obj3d && this.animation) { this.animation.mixer.stopAllAction(); this.animation.mixer.uncacheRoot(this.current_obj3d); @@ -174,8 +174,8 @@ class ModelViewerStore { }; private add_to_bone( - object: NinjaObject, - head_part: NinjaObject, + object: NjObject, + head_part: NjObject, bone_id: number ): void { const bone = object.get_bone(bone_id); @@ -187,7 +187,7 @@ class ModelViewerStore { } } - private async get_player_ninja_object(model: PlayerModel): Promise> { + private async get_player_ninja_object(model: PlayerModel): Promise> { let ninja_object = nj_object_cache.get(model.name); if (ninja_object) { @@ -199,7 +199,7 @@ class ModelViewerStore { } } - private async get_all_assets(model: PlayerModel): Promise> { + private async get_all_assets(model: PlayerModel): Promise> { const body_data = await get_player_data(model.name, "Body"); const body = parse_nj(new BufferCursor(body_data, true))[0];