From 43a4c7503dfd7a1ed034fc7900dca3962280574c Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Mon, 1 Jul 2019 08:53:16 +0200 Subject: [PATCH] Refactored binary data format code. --- src/bin_data/loading/areas.ts | 83 ------------------- src/bin_data/loading/entities.ts | 53 ------------ src/bin_data/loading/player.ts | 76 ----------------- .../BufferCursor.test.ts | 0 .../BufferCursor.ts | 0 .../compression/prs/compress.ts | 0 .../compression/prs/decompress.ts | 2 +- .../compression/prs/index.test.ts | 0 .../compression/prs/index.ts | 0 .../encryption/prc.ts | 0 .../parsing/geometry.ts | 2 +- .../parsing/itempmt.ts | 0 .../parsing/ninja/index.ts | 0 .../parsing/ninja/motion.ts | 0 .../parsing/ninja/nj.ts | 4 +- .../parsing/ninja/xj.ts | 2 +- src/{bin_data => data_formats}/parsing/prc.ts | 2 +- .../parsing/quest/bin.test.ts | 0 .../parsing/quest/bin.ts | 2 +- .../parsing/quest/dat.test.ts | 0 .../parsing/quest/dat.ts | 2 +- .../parsing/quest/index.test.ts | 0 .../parsing/quest/index.ts | 2 +- .../parsing/quest/qst.test.ts | 0 .../parsing/quest/qst.ts | 2 +- src/{bin_data => data_formats}/parsing/rlc.ts | 2 +- .../parsing/unitxt.ts | 0 src/domain/index.ts | 4 +- src/rendering/QuestRenderer.ts | 30 +++---- src/rendering/animation.ts | 2 +- src/rendering/entities.test.ts | 10 +-- src/rendering/models.ts | 6 +- src/stores/AreaStore.ts | 81 +++++++++++++++++- src/stores/EntityStore.ts | 57 +++++++++++++ src/stores/ModelViewerStore.ts | 63 ++++++++++++-- src/stores/QuestEditorStore.ts | 14 ++-- .../loading => stores}/binary_assets.ts | 8 +- static/update_ephinea_data.ts | 12 +-- static/update_generic_data.ts | 4 +- 39 files changed, 249 insertions(+), 276 deletions(-) delete mode 100644 src/bin_data/loading/areas.ts delete mode 100644 src/bin_data/loading/entities.ts delete mode 100644 src/bin_data/loading/player.ts rename src/{bin_data => data_formats}/BufferCursor.test.ts (100%) rename src/{bin_data => data_formats}/BufferCursor.ts (100%) rename src/{bin_data => data_formats}/compression/prs/compress.ts (100%) rename src/{bin_data => data_formats}/compression/prs/decompress.ts (97%) rename src/{bin_data => data_formats}/compression/prs/index.test.ts (100%) rename src/{bin_data => data_formats}/compression/prs/index.ts (100%) rename src/{bin_data => data_formats}/encryption/prc.ts (100%) rename src/{bin_data => data_formats}/parsing/geometry.ts (99%) rename src/{bin_data => data_formats}/parsing/itempmt.ts (100%) rename src/{bin_data => data_formats}/parsing/ninja/index.ts (100%) rename src/{bin_data => data_formats}/parsing/ninja/motion.ts (100%) rename src/{bin_data => data_formats}/parsing/ninja/nj.ts (99%) rename src/{bin_data => data_formats}/parsing/ninja/xj.ts (98%) rename src/{bin_data => data_formats}/parsing/prc.ts (91%) rename src/{bin_data => data_formats}/parsing/quest/bin.test.ts (100%) rename src/{bin_data => data_formats}/parsing/quest/bin.ts (99%) rename src/{bin_data => data_formats}/parsing/quest/dat.test.ts (100%) rename src/{bin_data => data_formats}/parsing/quest/dat.ts (99%) rename src/{bin_data => data_formats}/parsing/quest/index.test.ts (100%) rename src/{bin_data => data_formats}/parsing/quest/index.ts (99%) rename src/{bin_data => data_formats}/parsing/quest/qst.test.ts (100%) rename src/{bin_data => data_formats}/parsing/quest/qst.ts (99%) rename src/{bin_data => data_formats}/parsing/rlc.ts (94%) rename src/{bin_data => data_formats}/parsing/unitxt.ts (100%) create mode 100644 src/stores/EntityStore.ts rename src/{bin_data/loading => stores}/binary_assets.ts (96%) diff --git a/src/bin_data/loading/areas.ts b/src/bin_data/loading/areas.ts deleted file mode 100644 index cb534a3b..00000000 --- a/src/bin_data/loading/areas.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Object3D } from 'three'; -import { Section } from '../../domain'; -import { get_area_render_data, get_area_collision_data } from './binary_assets'; -import { parseCRel, parseNRel } from '../parsing/geometry'; - -// -// Caches -// -const sections_cache: Map> = new Map(); -const render_geometry_cache: Map> = new Map(); -const collision_geometry_cache: Map> = new Map(); - -export async function get_area_sections( - episode: number, - area_id: number, - area_variant: number -): Promise { - const sections = sections_cache.get(`${episode}-${area_id}-${area_variant}`); - - if (sections) { - return sections; - } else { - return get_area_sections_and_render_geometry( - episode, area_id, area_variant - ).then(({ sections }) => sections); - } -} - -export async function get_area_render_geometry( - episode: number, - area_id: number, - area_variant: number -): Promise { - const object_3d = render_geometry_cache.get(`${episode}-${area_id}-${area_variant}`); - - if (object_3d) { - return object_3d; - } else { - return get_area_sections_and_render_geometry( - episode, area_id, area_variant - ).then(({ object3d }) => object3d); - } -} - -export function get_area_collision_geometry( - episode: number, - area_id: number, - area_variant: number -): Promise { - const object_3d = collision_geometry_cache.get(`${episode}-${area_id}-${area_variant}`); - - if (object_3d) { - return object_3d; - } else { - const object_3d = get_area_collision_data( - episode, area_id, area_variant - ).then(parseCRel); - collision_geometry_cache.set(`${area_id}-${area_variant}`, object_3d); - return object_3d; - } -} - -function get_area_sections_and_render_geometry( - episode: number, - area_id: number, - area_variant: number -): Promise<{ sections: Section[], object3d: Object3D }> { - const promise = get_area_render_data( - episode, area_id, area_variant - ).then(parseNRel); - - const sections = new Promise((resolve, reject) => { - promise.then(({ sections }) => resolve(sections)).catch(reject); - }); - const object_3d = new Promise((resolve, reject) => { - promise.then(({ object3d }) => resolve(object3d)).catch(reject); - }); - - sections_cache.set(`${episode}-${area_id}-${area_variant}`, sections); - render_geometry_cache.set(`${episode}-${area_id}-${area_variant}`, object_3d); - - return promise; -} diff --git a/src/bin_data/loading/entities.ts b/src/bin_data/loading/entities.ts deleted file mode 100644 index 5be77a93..00000000 --- a/src/bin_data/loading/entities.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { BufferGeometry } from 'three'; -import { NpcType, ObjectType } from '../../domain'; -import { ninja_object_to_buffer_geometry } from '../../rendering/models'; -import { BufferCursor } from '../BufferCursor'; -import { parse_nj, parse_xj } from '../parsing/ninja'; -import { get_npc_data, get_object_data } from './binary_assets'; - -const npc_cache: Map> = new Map(); -const object_cache: Map> = new Map(); - -export function get_npc_geometry(npc_type: NpcType): Promise { - let mesh = npc_cache.get(String(npc_type.id)); - - if (mesh) { - return mesh; - } else { - mesh = get_npc_data(npc_type).then(({ url, data }) => { - const cursor = new BufferCursor(data, true); - const nj_objects = url.endsWith('.nj') ? parse_nj(cursor) : parse_xj(cursor); - - if (nj_objects.length) { - return ninja_object_to_buffer_geometry(nj_objects[0]); - } else { - throw new Error(`Could not parse ${url}.`); - } - }); - - npc_cache.set(String(npc_type.id), mesh); - return mesh; - } -} - -export function get_object_geometry(object_type: ObjectType): Promise { - let geometry = object_cache.get(String(object_type.id)); - - if (geometry) { - return geometry; - } else { - geometry = get_object_data(object_type).then(({ url, data }) => { - const cursor = new BufferCursor(data, true); - const nj_objects = url.endsWith('.nj') ? parse_nj(cursor) : parse_xj(cursor); - - if (nj_objects.length) { - return ninja_object_to_buffer_geometry(nj_objects[0]); - } else { - throw new Error('File could not be parsed into a BufferGeometry.'); - } - }); - - object_cache.set(String(object_type.id), geometry); - return geometry; - } -} diff --git a/src/bin_data/loading/player.ts b/src/bin_data/loading/player.ts deleted file mode 100644 index 648fc4dd..00000000 --- a/src/bin_data/loading/player.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { PlayerModel } from "../../domain"; -import { BufferCursor } from "../BufferCursor"; -import { NinjaModel, NinjaObject, parse_nj } from "../parsing/ninja"; -import { get_player_data } from "./binary_assets"; - -const cache: Map>> = new Map(); - -export function get_player_ninja_object(model: PlayerModel): Promise> { - let ninja_object = cache.get(model.name); - - if (ninja_object) { - return ninja_object; - } else { - ninja_object = get_all_assets(model); - cache.set(model.name, ninja_object); - return ninja_object; - } -} - -async function 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]; - - if (!body) { - throw new Error(`Couldn't parse body for player class ${model.name}.`); - } - - const head_data = await get_player_data(model.name, 'Head', 0); - const head = parse_nj(new BufferCursor(head_data, true))[0]; - - if (head) { - add_to_bone(body, head, 59); - } - - if (model.hair_styles_count > 0) { - const hair_data = await get_player_data(model.name, 'Hair', 0); - const hair = parse_nj(new BufferCursor(hair_data, true))[0]; - - if (hair) { - add_to_bone(body, hair, 59); - } - - if (model.hair_styles_with_accessory.has(0)) { - const accessory_data = await get_player_data(model.name, 'Accessory', 0); - const accessory = parse_nj(new BufferCursor(accessory_data, true))[0]; - - if (accessory) { - add_to_bone(body, accessory, 59); - } - } - } - - return body; -} - -function add_to_bone( - object: NinjaObject, - head_part: NinjaObject, - bone_id: number, - id_ref: [number] = [0] -) { - if (!object.evaluation_flags.skip) { - const id = id_ref[0]++; - - if (id === bone_id) { - object.evaluation_flags.hidden = false; - object.evaluation_flags.break_child_trace = false; - object.children.push(head_part); - return; - } - } - - for (const child of object.children) { - add_to_bone(child, head_part, bone_id, id_ref); - } -} diff --git a/src/bin_data/BufferCursor.test.ts b/src/data_formats/BufferCursor.test.ts similarity index 100% rename from src/bin_data/BufferCursor.test.ts rename to src/data_formats/BufferCursor.test.ts diff --git a/src/bin_data/BufferCursor.ts b/src/data_formats/BufferCursor.ts similarity index 100% rename from src/bin_data/BufferCursor.ts rename to src/data_formats/BufferCursor.ts diff --git a/src/bin_data/compression/prs/compress.ts b/src/data_formats/compression/prs/compress.ts similarity index 100% rename from src/bin_data/compression/prs/compress.ts rename to src/data_formats/compression/prs/compress.ts diff --git a/src/bin_data/compression/prs/decompress.ts b/src/data_formats/compression/prs/decompress.ts similarity index 97% rename from src/bin_data/compression/prs/decompress.ts rename to src/data_formats/compression/prs/decompress.ts index 205cb88f..29399038 100644 --- a/src/bin_data/compression/prs/decompress.ts +++ b/src/data_formats/compression/prs/decompress.ts @@ -1,7 +1,7 @@ import { BufferCursor } from '../../BufferCursor'; import Logger from 'js-logger'; -const logger = Logger.get('bin_data/compression/prs/decompress'); +const logger = Logger.get('data_formats/compression/prs/decompress'); export function decompress(cursor: BufferCursor) { const ctx = new Context(cursor); diff --git a/src/bin_data/compression/prs/index.test.ts b/src/data_formats/compression/prs/index.test.ts similarity index 100% rename from src/bin_data/compression/prs/index.test.ts rename to src/data_formats/compression/prs/index.test.ts diff --git a/src/bin_data/compression/prs/index.ts b/src/data_formats/compression/prs/index.ts similarity index 100% rename from src/bin_data/compression/prs/index.ts rename to src/data_formats/compression/prs/index.ts diff --git a/src/bin_data/encryption/prc.ts b/src/data_formats/encryption/prc.ts similarity index 100% rename from src/bin_data/encryption/prc.ts rename to src/data_formats/encryption/prc.ts diff --git a/src/bin_data/parsing/geometry.ts b/src/data_formats/parsing/geometry.ts similarity index 99% rename from src/bin_data/parsing/geometry.ts rename to src/data_formats/parsing/geometry.ts index ac3e4e97..7266799c 100644 --- a/src/bin_data/parsing/geometry.ts +++ b/src/data_formats/parsing/geometry.ts @@ -14,7 +14,7 @@ import { import { Vec3, Section } from '../../domain'; import Logger from 'js-logger'; -const logger = Logger.get('bin_data/parsing/geometry'); +const logger = Logger.get('data_formats/parsing/geometry'); export function parseCRel(arrayBuffer: ArrayBuffer): Object3D { const dv = new DataView(arrayBuffer); diff --git a/src/bin_data/parsing/itempmt.ts b/src/data_formats/parsing/itempmt.ts similarity index 100% rename from src/bin_data/parsing/itempmt.ts rename to src/data_formats/parsing/itempmt.ts diff --git a/src/bin_data/parsing/ninja/index.ts b/src/data_formats/parsing/ninja/index.ts similarity index 100% rename from src/bin_data/parsing/ninja/index.ts rename to src/data_formats/parsing/ninja/index.ts diff --git a/src/bin_data/parsing/ninja/motion.ts b/src/data_formats/parsing/ninja/motion.ts similarity index 100% rename from src/bin_data/parsing/ninja/motion.ts rename to src/data_formats/parsing/ninja/motion.ts diff --git a/src/bin_data/parsing/ninja/nj.ts b/src/data_formats/parsing/ninja/nj.ts similarity index 99% rename from src/bin_data/parsing/ninja/nj.ts rename to src/data_formats/parsing/ninja/nj.ts index 0d6e4fc0..b184621b 100644 --- a/src/bin_data/parsing/ninja/nj.ts +++ b/src/data_formats/parsing/ninja/nj.ts @@ -1,9 +1,9 @@ import Logger from 'js-logger'; import { BufferCursor } from '../../BufferCursor'; import { Vec3 } from '../../../domain'; -import { NinjaVertex } from '.'; +import { NinjaVertex } from '../ninja'; -const logger = Logger.get('bin_data/parsing/ninja/nj'); +const logger = Logger.get('data_formats/parsing/ninja/nj'); // TODO: // - textures diff --git a/src/bin_data/parsing/ninja/xj.ts b/src/data_formats/parsing/ninja/xj.ts similarity index 98% rename from src/bin_data/parsing/ninja/xj.ts rename to src/data_formats/parsing/ninja/xj.ts index b604e23a..c9c86ea8 100644 --- a/src/bin_data/parsing/ninja/xj.ts +++ b/src/data_formats/parsing/ninja/xj.ts @@ -1,6 +1,6 @@ import { BufferCursor } from '../../BufferCursor'; import { Vec3 } from '../../../domain'; -import { NinjaVertex } from '.'; +import { NinjaVertex } from '../ninja'; // TODO: // - textures diff --git a/src/bin_data/parsing/prc.ts b/src/data_formats/parsing/prc.ts similarity index 91% rename from src/bin_data/parsing/prc.ts rename to src/data_formats/parsing/prc.ts index 30f91078..a78754e7 100644 --- a/src/bin_data/parsing/prc.ts +++ b/src/data_formats/parsing/prc.ts @@ -3,7 +3,7 @@ import { decrypt } from "../encryption/prc"; import { decompress } from "../compression/prs"; import Logger from 'js-logger'; -const logger = Logger.get('bin_data/parsing/prc'); +const logger = Logger.get('data_formats/parsing/prc'); /** * Decrypts and decompresses a .prc file. diff --git a/src/bin_data/parsing/quest/bin.test.ts b/src/data_formats/parsing/quest/bin.test.ts similarity index 100% rename from src/bin_data/parsing/quest/bin.test.ts rename to src/data_formats/parsing/quest/bin.test.ts diff --git a/src/bin_data/parsing/quest/bin.ts b/src/data_formats/parsing/quest/bin.ts similarity index 99% rename from src/bin_data/parsing/quest/bin.ts rename to src/data_formats/parsing/quest/bin.ts index 0a494a58..7b5c00cf 100644 --- a/src/bin_data/parsing/quest/bin.ts +++ b/src/data_formats/parsing/quest/bin.ts @@ -1,7 +1,7 @@ import { BufferCursor } from '../../BufferCursor'; import Logger from 'js-logger'; -const logger = Logger.get('bin_data/parsing/quest/bin'); +const logger = Logger.get('data_formats/parsing/quest/bin'); export interface BinFile { questNumber: number; diff --git a/src/bin_data/parsing/quest/dat.test.ts b/src/data_formats/parsing/quest/dat.test.ts similarity index 100% rename from src/bin_data/parsing/quest/dat.test.ts rename to src/data_formats/parsing/quest/dat.test.ts diff --git a/src/bin_data/parsing/quest/dat.ts b/src/data_formats/parsing/quest/dat.ts similarity index 99% rename from src/bin_data/parsing/quest/dat.ts rename to src/data_formats/parsing/quest/dat.ts index dd97c109..555aecab 100644 --- a/src/bin_data/parsing/quest/dat.ts +++ b/src/data_formats/parsing/quest/dat.ts @@ -2,7 +2,7 @@ import { groupBy } from 'lodash'; import { BufferCursor } from '../../BufferCursor'; import Logger from 'js-logger'; -const logger = Logger.get('bin_data/parsing/quest/dat'); +const logger = Logger.get('data_formats/parsing/quest/dat'); const OBJECT_SIZE = 68; const NPC_SIZE = 72; diff --git a/src/bin_data/parsing/quest/index.test.ts b/src/data_formats/parsing/quest/index.test.ts similarity index 100% rename from src/bin_data/parsing/quest/index.test.ts rename to src/data_formats/parsing/quest/index.test.ts diff --git a/src/bin_data/parsing/quest/index.ts b/src/data_formats/parsing/quest/index.ts similarity index 99% rename from src/bin_data/parsing/quest/index.ts rename to src/data_formats/parsing/quest/index.ts index cbe6759d..d4460838 100644 --- a/src/bin_data/parsing/quest/index.ts +++ b/src/data_formats/parsing/quest/index.ts @@ -15,7 +15,7 @@ import { import { area_store } from '../../../stores/AreaStore'; import Logger from 'js-logger'; -const logger = Logger.get('bin_data/parsing/quest'); +const logger = Logger.get('data_formats/parsing/quest'); /** * High level parsing function that delegates to lower level parsing functions. diff --git a/src/bin_data/parsing/quest/qst.test.ts b/src/data_formats/parsing/quest/qst.test.ts similarity index 100% rename from src/bin_data/parsing/quest/qst.test.ts rename to src/data_formats/parsing/quest/qst.test.ts diff --git a/src/bin_data/parsing/quest/qst.ts b/src/data_formats/parsing/quest/qst.ts similarity index 99% rename from src/bin_data/parsing/quest/qst.ts rename to src/data_formats/parsing/quest/qst.ts index bab91541..9778a486 100644 --- a/src/bin_data/parsing/quest/qst.ts +++ b/src/data_formats/parsing/quest/qst.ts @@ -1,7 +1,7 @@ import { BufferCursor } from '../../BufferCursor'; import Logger from 'js-logger'; -const logger = Logger.get('bin_data/parsing/quest/qst'); +const logger = Logger.get('data_formats/parsing/quest/qst'); interface QstContainedFile { name: string; diff --git a/src/bin_data/parsing/rlc.ts b/src/data_formats/parsing/rlc.ts similarity index 94% rename from src/bin_data/parsing/rlc.ts rename to src/data_formats/parsing/rlc.ts index 9677737a..3f279ffa 100644 --- a/src/bin_data/parsing/rlc.ts +++ b/src/data_formats/parsing/rlc.ts @@ -2,7 +2,7 @@ import { BufferCursor } from "../BufferCursor"; import Logger from 'js-logger'; import { parse_prc } from "./prc"; -const logger = Logger.get('bin_data/parsing/rlc'); +const logger = Logger.get('data_formats/parsing/rlc'); const MARKER = 'RelChunkVer0.20'; /** diff --git a/src/bin_data/parsing/unitxt.ts b/src/data_formats/parsing/unitxt.ts similarity index 100% rename from src/bin_data/parsing/unitxt.ts rename to src/data_formats/parsing/unitxt.ts diff --git a/src/domain/index.ts b/src/domain/index.ts index 9edbfe07..21773e19 100644 --- a/src/domain/index.ts +++ b/src/domain/index.ts @@ -1,7 +1,7 @@ import { computed, observable } from 'mobx'; import { Object3D } from 'three'; -import { BufferCursor } from '../bin_data/BufferCursor'; -import { DatNpc, DatObject, DatUnknown } from '../bin_data/parsing/quest/dat'; +import { BufferCursor } from '../data_formats/BufferCursor'; +import { DatNpc, DatObject, DatUnknown } from '../data_formats/parsing/quest/dat'; import { NpcType } from './NpcType'; import { ObjectType } from './ObjectType'; import { enumValues as enum_values } from '../enums'; diff --git a/src/rendering/QuestRenderer.ts b/src/rendering/QuestRenderer.ts index 1b706a90..4e70805d 100644 --- a/src/rendering/QuestRenderer.ts +++ b/src/rendering/QuestRenderer.ts @@ -1,6 +1,6 @@ import { Intersection, Mesh, MeshLambertMaterial, Object3D, Plane, Raycaster, Vector2, Vector3 } from "three"; -import { get_area_collision_geometry, get_area_render_geometry } from "../bin_data/loading/areas"; import { Area, Quest, QuestEntity, QuestNpc, QuestObject, Section, Vec3 } from "../domain"; +import { area_store } from "../stores/AreaStore"; import { quest_editor_store } from "../stores/QuestEditorStore"; import { NPC_COLOR, NPC_HOVER_COLOR, NPC_SELECTED_COLOR, OBJECT_COLOR, OBJECT_HOVER_COLOR, OBJECT_SELECTED_COLOR } from "./entities"; import { Renderer } from "./Renderer"; @@ -100,7 +100,7 @@ export class QuestRenderer extends Renderer { this.renderer.render(this.scene, this.camera); } - private update_geometry() { + private async update_geometry() { this.scene.remove(this.obj_geometry); this.scene.remove(this.npc_geometry); this.obj_geometry = new Object3D(); @@ -117,22 +117,22 @@ export class QuestRenderer extends Renderer { const variant = this.quest.area_variants.find(v => v.area.id === area_id); const variant_id = (variant && variant.id) || 0; - get_area_collision_geometry(episode, area_id, variant_id).then(geometry => { - if (this.quest && this.area) { - this.scene.remove(this.collision_geometry); + const collision_geometry = await area_store.get_area_collision_geometry(episode, area_id, variant_id); - this.reset_camera(new Vector3(0, 800, 700), new Vector3(0, 0, 0)); + if (this.quest && this.area) { + this.scene.remove(this.collision_geometry); - this.collision_geometry = geometry; - this.scene.add(geometry); - } - }); + this.reset_camera(new Vector3(0, 800, 700), new Vector3(0, 0, 0)); - get_area_render_geometry(episode, area_id, variant_id).then(geometry => { - if (this.quest && this.area) { - this.render_geometry = geometry; - } - }); + this.collision_geometry = collision_geometry; + this.scene.add(collision_geometry); + } + + const render_geometry = await area_store.get_area_render_geometry(episode, area_id, variant_id); + + if (this.quest && this.area) { + this.render_geometry = render_geometry; + } } } diff --git a/src/rendering/animation.ts b/src/rendering/animation.ts index 21fff8d3..244f6c93 100644 --- a/src/rendering/animation.ts +++ b/src/rendering/animation.ts @@ -1,5 +1,5 @@ import { AnimationClip, Euler, InterpolateLinear, InterpolateSmooth, KeyframeTrack, Quaternion, QuaternionKeyframeTrack, VectorKeyframeTrack } from "three"; -import { NjAction, NjInterpolation, NjKeyframeTrackType } from "../bin_data/parsing/ninja/motion"; +import { NjAction, NjInterpolation, NjKeyframeTrackType } from "../data_formats/parsing/ninja/motion"; const PSO_FRAME_RATE = 30; diff --git a/src/rendering/entities.test.ts b/src/rendering/entities.test.ts index be98afbf..d06bc31b 100644 --- a/src/rendering/entities.test.ts +++ b/src/rendering/entities.test.ts @@ -1,12 +1,12 @@ import { CylinderBufferGeometry, MeshLambertMaterial, Object3D, Vector3 } from 'three'; -import { DatNpc, DatObject } from '../bin_data/parsing/quest/dat'; +import { DatNpc, DatObject } from '../data_formats/parsing/quest/dat'; import { NpcType, ObjectType, QuestNpc, QuestObject, Vec3 } from '../domain'; import { create_npc_mesh, create_object_mesh, NPC_COLOR, OBJECT_COLOR } from './entities'; const cylinder = new CylinderBufferGeometry(3, 3, 20).translate(0, 10, 0); test('create geometry for quest objects', () => { - const object = new QuestObject(7, 13, new Vec3(17, 19, 23), new Vec3(), ObjectType.PrincipalWarp, {} as DatObject); + const object = new QuestObject(7, 13, new Vec3(17, 19, 23), new Vec3(0, 0, 0), ObjectType.PrincipalWarp, {} as DatObject); const geometry = create_object_mesh(object, cylinder); expect(geometry).toBeInstanceOf(Object3D); @@ -19,7 +19,7 @@ test('create geometry for quest objects', () => { }); test('create geometry for quest NPCs', () => { - const npc = new QuestNpc(7, 13, new Vec3(17, 19, 23), new Vec3(), NpcType.Booma, {} as DatNpc); + const npc = new QuestNpc(7, 13, new Vec3(17, 19, 23), new Vec3(0, 0, 0), NpcType.Booma, {} as DatNpc); const geometry = create_npc_mesh(npc, cylinder); expect(geometry).toBeInstanceOf(Object3D); @@ -32,7 +32,7 @@ test('create geometry for quest NPCs', () => { }); test('geometry position changes when entity position changes element-wise', () => { - const npc = new QuestNpc(7, 13, new Vec3(17, 19, 23), new Vec3(), NpcType.Booma, {} as DatNpc); + const npc = new QuestNpc(7, 13, new Vec3(17, 19, 23), new Vec3(0, 0, 0), NpcType.Booma, {} as DatNpc); const geometry = create_npc_mesh(npc, cylinder); npc.position = new Vec3(2, 3, 5).add(npc.position); @@ -40,7 +40,7 @@ test('geometry position changes when entity position changes element-wise', () = }); test('geometry position changes when entire entity position changes', () => { - const npc = new QuestNpc(7, 13, new Vec3(17, 19, 23), new Vec3(), NpcType.Booma, {} as DatNpc); + const npc = new QuestNpc(7, 13, new Vec3(17, 19, 23), new Vec3(0, 0, 0), NpcType.Booma, {} as DatNpc); const geometry = create_npc_mesh(npc, cylinder); npc.position = new Vec3(2, 3, 5); diff --git a/src/rendering/models.ts b/src/rendering/models.ts index dddec1a2..fc7bd4fa 100644 --- a/src/rendering/models.ts +++ b/src/rendering/models.ts @@ -1,8 +1,8 @@ import { Bone, BufferGeometry, DoubleSide, Euler, Float32BufferAttribute, Material, Matrix3, Matrix4, MeshLambertMaterial, Quaternion, Skeleton, SkinnedMesh, Uint16BufferAttribute, Vector3 } from 'three'; import { vec3_to_threejs } from '.'; -import { NinjaModel, NinjaObject } from '../bin_data/parsing/ninja'; -import { NjModel } from '../bin_data/parsing/ninja/nj'; -import { XjModel } from '../bin_data/parsing/ninja/xj'; +import { NinjaModel, NinjaObject } from '../data_formats/parsing/ninja'; +import { NjModel } from '../data_formats/parsing/ninja/nj'; +import { XjModel } from '../data_formats/parsing/ninja/xj'; const DEFAULT_MATERIAL = new MeshLambertMaterial({ color: 0xFF00FF, diff --git a/src/stores/AreaStore.ts b/src/stores/AreaStore.ts index fb1380d4..8055abba 100644 --- a/src/stores/AreaStore.ts +++ b/src/stores/AreaStore.ts @@ -1,4 +1,7 @@ -import { Area, AreaVariant } from '../domain'; +import { Area, AreaVariant, Section } from '../domain'; +import { Object3D } from 'three'; +import { parseCRel, parseNRel } from '../data_formats/parsing/geometry'; +import { get_area_render_data, get_area_collision_data } from './binary_assets'; function area(id: number, name: string, order: number, variants: number) { const area = new Area(id, name, order, []); @@ -7,6 +10,10 @@ function area(id: number, name: string, order: number, variants: number) { return area; } +const sections_cache: Map> = new Map(); +const render_geometry_cache: Map> = new Map(); +const collision_geometry_cache: Map> = new Map(); + class AreaStore { areas: Area[][]; @@ -84,6 +91,78 @@ class AreaStore { return area_variant; } + + async get_area_sections( + episode: number, + area_id: number, + area_variant: number + ): Promise { + const sections = sections_cache.get(`${episode}-${area_id}-${area_variant}`); + + if (sections) { + return sections; + } else { + return this.get_area_sections_and_render_geometry( + episode, area_id, area_variant + ).then(({ sections }) => sections); + } + } + + async get_area_render_geometry( + episode: number, + area_id: number, + area_variant: number + ): Promise { + const object_3d = render_geometry_cache.get(`${episode}-${area_id}-${area_variant}`); + + if (object_3d) { + return object_3d; + } else { + return this.get_area_sections_and_render_geometry( + episode, area_id, area_variant + ).then(({ object3d }) => object3d); + } + } + + async get_area_collision_geometry( + episode: number, + area_id: number, + area_variant: number + ): Promise { + const object_3d = collision_geometry_cache.get(`${episode}-${area_id}-${area_variant}`); + + if (object_3d) { + return object_3d; + } else { + const object_3d = get_area_collision_data( + episode, area_id, area_variant + ).then(parseCRel); + collision_geometry_cache.set(`${area_id}-${area_variant}`, object_3d); + return object_3d; + } + } + + private get_area_sections_and_render_geometry( + episode: number, + area_id: number, + area_variant: number + ): Promise<{ sections: Section[], object3d: Object3D }> { + const promise = get_area_render_data( + episode, area_id, area_variant + ).then(parseNRel); + + const sections = new Promise((resolve, reject) => { + promise.then(({ sections }) => resolve(sections)).catch(reject); + }); + const object_3d = new Promise((resolve, reject) => { + promise.then(({ object3d }) => resolve(object3d)).catch(reject); + }); + + sections_cache.set(`${episode}-${area_id}-${area_variant}`, sections); + render_geometry_cache.set(`${episode}-${area_id}-${area_variant}`, object_3d); + + return promise; + } } export const area_store = new AreaStore(); diff --git a/src/stores/EntityStore.ts b/src/stores/EntityStore.ts new file mode 100644 index 00000000..72926285 --- /dev/null +++ b/src/stores/EntityStore.ts @@ -0,0 +1,57 @@ +import { BufferGeometry } from "three"; +import { NpcType, ObjectType } from "../domain"; +import { BufferCursor } from "../data_formats/BufferCursor"; +import { get_npc_data, get_object_data } from "./binary_assets"; +import { ninja_object_to_buffer_geometry } from "../rendering/models"; +import { parse_nj, parse_xj } from "../data_formats/parsing/ninja"; + +const npc_cache: Map> = new Map(); +const object_cache: Map> = new Map(); + +class EntityStore { + async get_npc_geometry(npc_type: NpcType): Promise { + let mesh = npc_cache.get(npc_type.id); + + if (mesh) { + return mesh; + } else { + mesh = get_npc_data(npc_type).then(({ url, data }) => { + const cursor = new BufferCursor(data, true); + const nj_objects = url.endsWith('.nj') ? parse_nj(cursor) : parse_xj(cursor); + + if (nj_objects.length) { + return ninja_object_to_buffer_geometry(nj_objects[0]); + } else { + throw new Error(`Could not parse ${url}.`); + } + }); + + npc_cache.set(npc_type.id, mesh); + return mesh; + } + } + + async get_object_geometry(object_type: ObjectType): Promise { + let geometry = object_cache.get(object_type.id); + + if (geometry) { + return geometry; + } else { + geometry = get_object_data(object_type).then(({ url, data }) => { + const cursor = new BufferCursor(data, true); + const nj_objects = url.endsWith('.nj') ? parse_nj(cursor) : parse_xj(cursor); + + if (nj_objects.length) { + return ninja_object_to_buffer_geometry(nj_objects[0]); + } else { + throw new Error('File could not be parsed into a BufferGeometry.'); + } + }); + + object_cache.set(object_type.id, geometry); + return geometry; + } + } +} + +export const entity_store = new EntityStore(); diff --git a/src/stores/ModelViewerStore.ts b/src/stores/ModelViewerStore.ts index 1a28c6b0..fee6b8d1 100644 --- a/src/stores/ModelViewerStore.ts +++ b/src/stores/ModelViewerStore.ts @@ -1,15 +1,16 @@ import Logger from 'js-logger'; import { action, observable } from "mobx"; import { AnimationClip, AnimationMixer, Object3D } from "three"; -import { BufferCursor } from "../bin_data/BufferCursor"; -import { NinjaModel, NinjaObject, parse_nj, parse_xj } from "../bin_data/parsing/ninja"; -import { parse_njm_4 } from "../bin_data/parsing/ninja/motion"; +import { BufferCursor } from "../data_formats/BufferCursor"; +import { NinjaModel, NinjaObject, parse_nj, parse_xj } from "../data_formats/parsing/ninja"; +import { parse_njm_4 } from "../data_formats/parsing/ninja/motion"; import { create_animation_clip } from "../rendering/animation"; import { ninja_object_to_skinned_mesh } from "../rendering/models"; import { PlayerModel } from '../domain'; -import { get_player_ninja_object } from '../bin_data/loading/player'; +import { get_player_data } from './binary_assets'; const logger = Logger.get('stores/ModelViewerStore'); +const cache: Map>> = new Map(); class ModelViewerStore { readonly models: PlayerModel[] = [ @@ -32,7 +33,7 @@ class ModelViewerStore { @observable.ref animation_mixer?: AnimationMixer; load_model = async (model: PlayerModel) => { - const object = await get_player_ninja_object(model); + const object = await this.get_player_ninja_object(model); this.set_model(object); } @@ -51,7 +52,7 @@ class ModelViewerStore { if (model) { if (this.current_model && filename && /^pl[A-Z](hed|hai|cap)\d\d.nj$/.test(filename)) { - this.add_to_bone(this.current_model, model, 59, [0]); + this.add_to_bone(this.current_model, model, 59); } else { this.current_model = model; } @@ -93,7 +94,7 @@ class ModelViewerStore { object: NinjaObject, head_part: NinjaObject, bone_id: number, - id_ref: [number] + id_ref: [number] = [0] ) { if (!object.evaluation_flags.skip) { const id = id_ref[0]++; @@ -123,6 +124,54 @@ class ModelViewerStore { const action = this.animation_mixer.clipAction(clip); action.play(); }) + + private get_player_ninja_object(model: PlayerModel): Promise> { + let ninja_object = cache.get(model.name); + + if (ninja_object) { + return ninja_object; + } else { + ninja_object = this.get_all_assets(model); + cache.set(model.name, ninja_object); + return ninja_object; + } + } + + 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]; + + if (!body) { + throw new Error(`Couldn't parse body for player class ${model.name}.`); + } + + const head_data = await get_player_data(model.name, 'Head', 0); + const head = parse_nj(new BufferCursor(head_data, true))[0]; + + if (head) { + this.add_to_bone(body, head, 59); + } + + if (model.hair_styles_count > 0) { + const hair_data = await get_player_data(model.name, 'Hair', 0); + const hair = parse_nj(new BufferCursor(hair_data, true))[0]; + + if (hair) { + this.add_to_bone(body, hair, 59); + } + + if (model.hair_styles_with_accessory.has(0)) { + const accessory_data = await get_player_data(model.name, 'Accessory', 0); + const accessory = parse_nj(new BufferCursor(accessory_data, true))[0]; + + if (accessory) { + this.add_to_bone(body, accessory, 59); + } + } + } + + return body; + } } export const model_viewer_store = new ModelViewerStore(); diff --git a/src/stores/QuestEditorStore.ts b/src/stores/QuestEditorStore.ts index ee865a1d..38f80d7a 100644 --- a/src/stores/QuestEditorStore.ts +++ b/src/stores/QuestEditorStore.ts @@ -1,11 +1,11 @@ import Logger from 'js-logger'; import { action, observable } from 'mobx'; -import { BufferCursor } from '../bin_data/BufferCursor'; -import { get_area_sections } from '../bin_data/loading/areas'; -import { get_npc_geometry, get_object_geometry } from '../bin_data/loading/entities'; -import { parse_quest, write_quest_qst } from '../bin_data/parsing/quest'; +import { BufferCursor } from '../data_formats/BufferCursor'; +import { parse_quest, write_quest_qst } from '../data_formats/parsing/quest'; import { Area, Quest, QuestEntity, Section, Vec3 } from '../domain'; import { create_npc_mesh as create_npc_object_3d, create_object_mesh as create_object_object_3d } from '../rendering/entities'; +import { area_store } from './AreaStore'; +import { entity_store } from './EntityStore'; const logger = Logger.get('stores/QuestEditorStore'); @@ -65,7 +65,7 @@ class QuestEditorStore { if (quest) { // Load section data. for (const variant of quest.area_variants) { - const sections = await get_area_sections( + const sections = await area_store.get_area_sections( quest.episode, variant.area.id, variant.id @@ -75,7 +75,7 @@ class QuestEditorStore { // Generate object geometry. for (const object of quest.objects.filter(o => o.area_id === variant.area.id)) { try { - const object_geom = await get_object_geometry(object.type); + const object_geom = await entity_store.get_object_geometry(object.type); this.set_section_on_visible_quest_entity(object, sections); object.object_3d = create_object_object_3d(object, object_geom); } catch (e) { @@ -86,7 +86,7 @@ class QuestEditorStore { // Generate NPC geometry. for (const npc of quest.npcs.filter(npc => npc.area_id === variant.area.id)) { try { - const npc_geom = await get_npc_geometry(npc.type); + const npc_geom = await entity_store.get_npc_geometry(npc.type); this.set_section_on_visible_quest_entity(npc, sections); npc.object_3d = create_npc_object_3d(npc, npc_geom); } catch (e) { diff --git a/src/bin_data/loading/binary_assets.ts b/src/stores/binary_assets.ts similarity index 96% rename from src/bin_data/loading/binary_assets.ts rename to src/stores/binary_assets.ts index 25724137..b839c8bf 100644 --- a/src/bin_data/loading/binary_assets.ts +++ b/src/stores/binary_assets.ts @@ -1,4 +1,4 @@ -import { NpcType, ObjectType } from '../../domain'; +import { NpcType, ObjectType } from '../domain'; export function get_area_render_data( episode: number, @@ -145,7 +145,7 @@ function get_area_asset( function npc_type_to_url(npc_type: NpcType): string { switch (npc_type) { - // The dubswitch model in in XJ format. + // The dubswitch model is in XJ format. case NpcType.Dubswitch: return `/npcs/${npc_type.code}.xj`; // Episode II VR Temple @@ -196,10 +196,10 @@ function object_type_to_url(object_type: ObjectType): string { case ObjectType.FallingRock: case ObjectType.DesertFixedTypeBoxBreakableCrystals: case ObjectType.BeeHive: - return `/objects/${String(object_type.pso_id)}.nj`; + return `/objects/${object_type.pso_id}.nj`; default: - return `/objects/${String(object_type.pso_id)}.xj`; + return `/objects/${object_type.pso_id}.xj`; } } diff --git a/static/update_ephinea_data.ts b/static/update_ephinea_data.ts index 7bd11a2b..12587040 100644 --- a/static/update_ephinea_data.ts +++ b/static/update_ephinea_data.ts @@ -1,12 +1,12 @@ import fs from 'fs'; -import { BufferCursor } from '../src/bin_data/BufferCursor'; -import { parseItemPmt, ItemPmt } from '../src/bin_data/parsing/itempmt'; -import { parseUnitxt, Unitxt } from '../src/bin_data/parsing/unitxt'; +import { BufferCursor } from '../src/data_formats/BufferCursor'; +import { parseItemPmt, ItemPmt } from '../src/data_formats/parsing/itempmt'; +import { parseUnitxt, Unitxt } from '../src/data_formats/parsing/unitxt'; import { Difficulties, Difficulty, Episode, Episodes, NpcType, SectionId, SectionIds } from '../src/domain'; import { NpcTypes } from '../src/domain/NpcType'; import { BoxDropDto, EnemyDropDto, ItemTypeDto, QuestDto } from '../src/dto'; import { updateDropsFromWebsite } from './update_drops_ephinea'; -import { parse_quest } from '../src/bin_data/parsing/quest'; +import { parse_quest } from '../src/data_formats/parsing/quest'; import Logger from 'js-logger'; const logger = Logger.get('static/update_ephinea_data'); @@ -14,8 +14,8 @@ const logger = Logger.get('static/update_ephinea_data'); Logger.useDefaults({ defaultLevel: Logger.ERROR }); logger.setLevel(Logger.INFO); Logger.get('static/update_drops_ephinea').setLevel(Logger.INFO); -Logger.get('bin_data/parsing/quest').setLevel(Logger.OFF); -Logger.get('bin_data/parsing/quest/bin').setLevel(Logger.OFF); +Logger.get('data_formats/parsing/quest').setLevel(Logger.OFF); +Logger.get('data_formats/parsing/quest/bin').setLevel(Logger.OFF); /** * Used by static data generation scripts. diff --git a/static/update_generic_data.ts b/static/update_generic_data.ts index cabb609a..c7b449e8 100644 --- a/static/update_generic_data.ts +++ b/static/update_generic_data.ts @@ -1,7 +1,7 @@ import fs from "fs"; import Logger from 'js-logger'; -import { BufferCursor } from "../src/bin_data/BufferCursor"; -import { parse_rlc } from "../src/bin_data/parsing/rlc"; +import { BufferCursor } from "../src/data_formats/BufferCursor"; +import { parse_rlc } from "../src/data_formats/parsing/rlc"; const logger = Logger.get('static/update_generic_data');