From dc9deee5a72a3fc8bb394647b6b6b1e956c119f7 Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Fri, 3 Jan 2020 23:04:57 +0100 Subject: [PATCH] The model viewer can now show any character class' body type. --- src/core/util.ts | 26 +- .../loading/CharacterClassAssetLoader.ts | 196 ++++++++++++++- src/viewer/model/CharacterClassModel.ts | 103 +++++++- src/viewer/stores/Model3DStore.ts | 226 +++++------------- 4 files changed, 349 insertions(+), 202 deletions(-) diff --git a/src/core/util.ts b/src/core/util.ts index 125f8160..5810529f 100644 --- a/src/core/util.ts +++ b/src/core/util.ts @@ -14,6 +14,22 @@ export function arrays_equal( return true; } +/** + * @param min - The minimum value, inclusive. + * @param max - The maximum value, exclusive. + * @returns A random integer between `min` and `max`. + */ +export function random_integer(min: number, max: number): number { + return min + Math.floor(Math.random() * (max - min)); +} + +/** + * @returns A random element from `array`. + */ +export function random_array_element(array: readonly T[]): T { + return array[random_integer(0, array.length)]; +} + export function array_buffers_equal(a: ArrayBuffer, b: ArrayBuffer): boolean { if (a.byteLength !== b.byteLength) return false; @@ -27,16 +43,6 @@ export function array_buffers_equal(a: ArrayBuffer, b: ArrayBuffer): boolean { return true; } -export function create_array(length: number, value: (index: number) => T): T[] { - const array = Array(length); - - for (let i = 0; i < length; i++) { - array[i] = value(i); - } - - return array; -} - /** * Returns the given filename without the file extension. */ diff --git a/src/viewer/loading/CharacterClassAssetLoader.ts b/src/viewer/loading/CharacterClassAssetLoader.ts index 51f60392..2accd6f3 100644 --- a/src/viewer/loading/CharacterClassAssetLoader.ts +++ b/src/viewer/loading/CharacterClassAssetLoader.ts @@ -6,9 +6,24 @@ import { NjcmModel } from "../../core/data_formats/parsing/ninja/njcm"; import { NjMotion, parse_njm } from "../../core/data_formats/parsing/ninja/motion"; import { Disposable } from "../../core/observable/Disposable"; import { DisposablePromise } from "../../core/DisposablePromise"; -import { CharacterClassModel } from "../model/CharacterClassModel"; +import { + CharacterClassModel, + FOMAR, + FOMARL, + FONEWEARL, + FONEWM, + HUCASEAL, + HUCAST, + HUMAR, + HUNEWEARL, + RACASEAL, + RACAST, + RAMAR, + RAMARL, +} from "../model/CharacterClassModel"; import { parse_xvm, XvrTexture } from "../../core/data_formats/parsing/ninja/texture"; import { parse_afs } from "../../core/data_formats/parsing/afs"; +import { SectionId } from "../../core/model"; export class CharacterClassAssetLoader implements Disposable { private readonly nj_object_cache: Map< @@ -53,6 +68,8 @@ export class CharacterClassAssetLoader implements Disposable { private load_all_nj_objects( model: CharacterClassModel, ): DisposablePromise> { + const tex_ids = texture_ids(model, SectionId.Viridia, 0); + return this.load_body_part_geometry(model.name, "Body").then(body => { if (!body) { throw new Error(`Couldn't load body for player class ${model.name}.`); @@ -64,7 +81,7 @@ export class CharacterClassAssetLoader implements Disposable { } // Shift by 1 for the section ID and once for every body texture ID. - let shift = 1 + model.body_tex_ids.length; + let shift = 1 + tex_ids.body.length; this.shift_texture_ids(head, shift); this.add_to_bone(body, head, 59); @@ -77,7 +94,7 @@ export class CharacterClassAssetLoader implements Disposable { return body; } - shift += model.head_tex_ids.length; + shift += tex_ids.head.length; this.shift_texture_ids(hair, shift); this.add_to_bone(head, hair, 0); @@ -88,7 +105,7 @@ export class CharacterClassAssetLoader implements Disposable { return this.load_body_part_geometry(model.name, "Accessory", 0).then( accessory => { if (accessory) { - shift += model.hair_tex_ids.length; + shift += tex_ids.hair.length; this.shift_texture_ids(accessory, shift); this.add_to_bone(hair, accessory, 0); } @@ -139,11 +156,15 @@ export class CharacterClassAssetLoader implements Disposable { } } - load_textures(model: CharacterClassModel): Promise { - let xvr_texture = this.xvr_texture_cache.get(model.name); + async load_textures( + model: CharacterClassModel, + section_id: SectionId, + body: number, + ): Promise { + let xvr_textures = this.xvr_texture_cache.get(model.name); - if (!xvr_texture) { - xvr_texture = this.http_client + if (!xvr_textures) { + xvr_textures = this.http_client .get(`/player/${model.name}Tex.afs`) .array_buffer() .then(buffer => { @@ -159,7 +180,16 @@ export class CharacterClassAssetLoader implements Disposable { }); } - return xvr_texture; + const tex = await xvr_textures; + const tex_ids = texture_ids(model, section_id, body); + + return [ + tex_ids.section_id, + ...tex_ids.body, + ...tex_ids.head, + ...tex_ids.hair, + ...tex_ids.accessories, + ].map(idx => (idx == undefined ? undefined : tex[idx])); } load_animation(animation_id: number, bone_count: number): Promise { @@ -181,3 +211,151 @@ export class CharacterClassAssetLoader implements Disposable { function character_class_to_url(player_class: string, body_part: string, no?: number): string { return `/player/${player_class}${body_part}${no == null ? "" : no}.nj`; } + +function texture_ids( + model: CharacterClassModel, + section_id: SectionId, + body: number, +): { + section_id: number; + body: number[]; + head: number[]; + hair: (number | undefined)[]; + accessories: (number | undefined)[]; +} { + switch (model) { + case HUMAR: { + const body_idx = body * 3; + return { + section_id: section_id + 126, + body: [body_idx, body_idx + 1, body_idx + 2, body + 108], + head: [54, 55], + hair: [94, 95], + accessories: [], + }; + } + case HUNEWEARL: { + const body_idx = body * 13; + return { + section_id: section_id + 299, + body: [ + body_idx + 13, + body_idx, + body_idx + 1, + body_idx + 2, + body_idx + 3, + 277, + body + 281, + ], + head: [235, 239], + hair: [260, 259], + accessories: [], + }; + } + case HUCAST: { + const body_idx = body * 5; + return { + section_id: section_id + 275, + body: [body_idx, body_idx + 1, body_idx + 2, body + 250], + // Eyes don't look correct because NJCM material chunks (which contain alpha blending + // details) aren't parsed yet. Material.blending should be AdditiveBlending. + head: [body_idx + 3, body_idx + 4], + hair: [], + accessories: [], + }; + } + case HUCASEAL: { + const body_idx = body * 5; + return { + section_id: section_id + 375, + body: [body_idx, body_idx + 1, body_idx + 2], + head: [body_idx + 3, body_idx + 4], + hair: [], + accessories: [], + }; + } + case RAMAR: { + const body_idx = body * 7; + return { + section_id: section_id + 197, + body: [body_idx + 4, body_idx + 5, body_idx + 6, body + 179], + head: [126, 127], + hair: [166, 167], + accessories: [undefined, undefined, body_idx + 2], + }; + } + case RAMARL: { + const body_idx = body * 16; + return { + section_id: section_id + 322, + body: [body_idx + 15, body_idx + 1, body_idx], + head: [288], + hair: [308, 309], + accessories: [undefined, undefined, body_idx + 8], + }; + } + case RACAST: { + const body_idx = body * 5; + return { + section_id: section_id + 300, + body: [body_idx, body_idx + 1, body_idx + 2, body_idx + 3, body + 275], + head: [body_idx + 4], + hair: [], + accessories: [], + }; + } + case RACASEAL: { + const body_idx = body * 5; + return { + section_id: section_id + 375, + body: [body + 350, body_idx, body_idx + 1, body_idx + 2], + head: [body_idx + 3], + hair: [body_idx + 4], + accessories: [], + }; + } + case FOMAR: { + const body_idx = body === 0 ? 0 : body * 15 + 2; + return { + section_id: section_id + 310, + body: [body_idx + 12, body_idx + 13, body_idx + 14, body_idx], + head: [276, 272], + hair: [undefined, 296, 297], + accessories: [body_idx + 4], + }; + } + case FOMARL: { + const body_idx = body * 16; + return { + section_id: section_id + 310, + body: [body_idx, body_idx + 2, body_idx + 1, 322 /*hands*/], + head: [288], + hair: [undefined, undefined, 308], + accessories: [body_idx + 3, body_idx + 4], + }; + } + case FONEWM: { + const body_idx = body * 17; + return { + section_id: section_id + 344, + body: [body_idx + 4, 340 /*hands*/, body_idx, body_idx + 5], + head: [306, 310], + hair: [undefined, undefined, 330], + // ID 16 for glasses is incorrect but looks decent. + accessories: [body_idx + 6, body_idx + 16, 330], + }; + } + case FONEWEARL: { + const body_idx = body * 26; + return { + section_id: section_id + 505, + body: [body_idx + 1, body_idx, body_idx + 2, 501 /*hands*/], + head: [472, 468], + hair: [undefined, undefined, 492], + accessories: [body_idx + 12, body_idx + 13], + }; + } + default: + throw new Error(`No textures for character class ${model.name}.`); + } +} diff --git a/src/viewer/model/CharacterClassModel.ts b/src/viewer/model/CharacterClassModel.ts index a1d84951..91c31308 100644 --- a/src/viewer/model/CharacterClassModel.ts +++ b/src/viewer/model/CharacterClassModel.ts @@ -1,39 +1,118 @@ -import { SectionIds } from "../../core/model"; -import { create_array } from "../../core/util"; - export class CharacterClassModel { readonly name: string; + readonly body_style_count: number; readonly head_style_count: number; readonly hair_style_count: number; readonly hair_styles_with_accessory: Set; - /** - * Can be indexed with {@link SectionId} - */ - readonly section_id_tex_ids: readonly number[]; - readonly body_tex_ids: readonly number[]; + readonly body_tex_ids: readonly number[][]; readonly head_tex_ids: readonly (number | undefined)[]; readonly hair_tex_ids: readonly (number | undefined)[]; readonly accessory_tex_ids: readonly (number | undefined)[]; constructor(props: { name: string; + body_style_count?: number; head_style_count: number; hair_style_count: number; hair_styles_with_accessory: Set; - section_id_tex_id: number; - body_tex_ids: number[]; + body_tex_ids?: number[][]; head_tex_ids?: (number | undefined)[]; hair_tex_ids?: (number | undefined)[]; accessory_tex_ids?: (number | undefined)[]; }) { this.name = props.name; + this.body_style_count = props.body_style_count ?? 1; this.head_style_count = props.head_style_count; this.hair_style_count = props.hair_style_count; this.hair_styles_with_accessory = props.hair_styles_with_accessory; - this.section_id_tex_ids = create_array(SectionIds.length, i => props.section_id_tex_id + i); - this.body_tex_ids = props.body_tex_ids; + this.body_tex_ids = props.body_tex_ids ?? []; this.head_tex_ids = props.head_tex_ids ?? []; this.hair_tex_ids = props.hair_tex_ids ?? []; this.accessory_tex_ids = props.accessory_tex_ids ?? []; } } + +export const HUMAR = new CharacterClassModel({ + name: "HUmar", + body_style_count: 18, + head_style_count: 1, + hair_style_count: 10, + hair_styles_with_accessory: new Set([6]), +}); +export const HUNEWEARL = new CharacterClassModel({ + name: "HUnewearl", + body_style_count: 18, + head_style_count: 1, + hair_style_count: 10, + hair_styles_with_accessory: new Set(), +}); +export const HUCAST = new CharacterClassModel({ + name: "HUcast", + body_style_count: 25, + head_style_count: 5, + hair_style_count: 0, + hair_styles_with_accessory: new Set(), +}); +export const HUCASEAL = new CharacterClassModel({ + name: "HUcaseal", + body_style_count: 25, + head_style_count: 5, + hair_style_count: 0, + hair_styles_with_accessory: new Set(), +}); +export const RAMAR = new CharacterClassModel({ + name: "RAmar", + body_style_count: 18, + head_style_count: 1, + hair_style_count: 10, + hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), +}); +export const RAMARL = new CharacterClassModel({ + name: "RAmarl", + body_style_count: 18, + head_style_count: 1, + hair_style_count: 10, + hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), +}); +export const RACAST = new CharacterClassModel({ + name: "RAcast", + body_style_count: 25, + head_style_count: 5, + hair_style_count: 0, + hair_styles_with_accessory: new Set(), +}); +export const RACASEAL = new CharacterClassModel({ + name: "RAcaseal", + body_style_count: 25, + head_style_count: 5, + hair_style_count: 0, + hair_styles_with_accessory: new Set(), +}); +export const FOMAR = new CharacterClassModel({ + name: "FOmar", + body_style_count: 18, + head_style_count: 1, + hair_style_count: 10, + hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), +}); +export const FOMARL = new CharacterClassModel({ + name: "FOmarl", + body_style_count: 18, + head_style_count: 1, + hair_style_count: 10, + hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), +}); +export const FONEWM = new CharacterClassModel({ + name: "FOnewm", + body_style_count: 18, + head_style_count: 1, + hair_style_count: 10, + hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), +}); +export const FONEWEARL = new CharacterClassModel({ + name: "FOnewearl", + body_style_count: 18, + head_style_count: 1, + hair_style_count: 10, + hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), +}); diff --git a/src/viewer/stores/Model3DStore.ts b/src/viewer/stores/Model3DStore.ts index f4706350..912d1852 100644 --- a/src/viewer/stores/Model3DStore.ts +++ b/src/viewer/stores/Model3DStore.ts @@ -2,7 +2,21 @@ import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCur import { Endianness } from "../../core/data_formats/Endianness"; import { NjMotion, parse_njm } from "../../core/data_formats/parsing/ninja/motion"; import { NjObject, parse_nj, parse_xj } from "../../core/data_formats/parsing/ninja"; -import { CharacterClassModel } from "../model/CharacterClassModel"; +import { + CharacterClassModel, + FOMAR, + FOMARL, + FONEWEARL, + FONEWM, + HUCASEAL, + HUCAST, + HUMAR, + HUNEWEARL, + RACASEAL, + RACAST, + RAMAR, + RAMARL, +} from "../model/CharacterClassModel"; import { CharacterClassAnimationModel } from "../model/CharacterClassAnimationModel"; import { WritableProperty } from "../../core/observable/property/WritableProperty"; import { read_file } from "../../core/read_file"; @@ -15,6 +29,7 @@ import { Store } from "../../core/stores/Store"; import { LogManager } from "../../core/Logger"; import { ListProperty } from "../../core/observable/property/list/ListProperty"; import { parse_afs } from "../../core/data_formats/parsing/afs"; +import { random_array_element, random_integer } from "../../core/util"; import { SectionIds } from "../../core/model"; const logger = LogManager.get("viewer/stores/ModelStore"); @@ -41,132 +56,18 @@ export class Model3DStore extends Store { private readonly _animation_frame: WritableProperty = property(0); readonly models: readonly CharacterClassModel[] = [ - new CharacterClassModel({ - name: "HUmar", - head_style_count: 1, - hair_style_count: 10, - hair_styles_with_accessory: new Set([6]), - section_id_tex_id: 126, - body_tex_ids: [0, 1, 2, 108], - head_tex_ids: [54, 55], - hair_tex_ids: [94, 95], - }), - new CharacterClassModel({ - name: "HUnewearl", - head_style_count: 1, - hair_style_count: 10, - hair_styles_with_accessory: new Set(), - section_id_tex_id: 299, - body_tex_ids: [13, 0, 1, 2, 3, 277, 281], - head_tex_ids: [235, 239], - hair_tex_ids: [260, 259], - }), - new CharacterClassModel({ - name: "HUcast", - head_style_count: 5, - hair_style_count: 0, - hair_styles_with_accessory: new Set(), - section_id_tex_id: 275, - body_tex_ids: [0, 1, 2, 250], - // Eyes don't look correct because NJCM material chunks (which contain alpha blending - // details) aren't parsed yet. Material.blending should be AdditiveBlending. - head_tex_ids: [3, 4], - }), - new CharacterClassModel({ - name: "HUcaseal", - head_style_count: 5, - hair_style_count: 0, - hair_styles_with_accessory: new Set(), - section_id_tex_id: 375, - body_tex_ids: [0, 1, 2], - head_tex_ids: [3, 4], - }), - new CharacterClassModel({ - name: "RAmar", - head_style_count: 1, - hair_style_count: 10, - hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - section_id_tex_id: 197, - body_tex_ids: [4, 5, 6, 179], - head_tex_ids: [126, 127], - hair_tex_ids: [166, 167], - accessory_tex_ids: [undefined, undefined, 2], - }), - new CharacterClassModel({ - name: "RAmarl", - head_style_count: 1, - hair_style_count: 10, - hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - section_id_tex_id: 322, - body_tex_ids: [15, 1, 0], - head_tex_ids: [288], - hair_tex_ids: [308, 309], - accessory_tex_ids: [undefined, undefined, 8], - }), - new CharacterClassModel({ - name: "RAcast", - head_style_count: 5, - hair_style_count: 0, - hair_styles_with_accessory: new Set(), - section_id_tex_id: 300, - body_tex_ids: [0, 1, 2, 3, 275], - head_tex_ids: [4], - }), - new CharacterClassModel({ - name: "RAcaseal", - head_style_count: 5, - hair_style_count: 0, - hair_styles_with_accessory: new Set(), - section_id_tex_id: 375, - body_tex_ids: [350, 0, 1, 2], - head_tex_ids: [3], - hair_tex_ids: [4], - }), - new CharacterClassModel({ - name: "FOmar", - head_style_count: 1, - hair_style_count: 10, - hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - section_id_tex_id: 310, - body_tex_ids: [12, 13, 14, 0], - head_tex_ids: [276, 272], - hair_tex_ids: [undefined, 296, 297], - accessory_tex_ids: [4], - }), - new CharacterClassModel({ - name: "FOmarl", - head_style_count: 1, - hair_style_count: 10, - hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - section_id_tex_id: 326, - body_tex_ids: [0, 2, 1, 322], - head_tex_ids: [288], - hair_tex_ids: [undefined, undefined, 308], - accessory_tex_ids: [3, 4], - }), - new CharacterClassModel({ - name: "FOnewm", - head_style_count: 1, - hair_style_count: 10, - hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - section_id_tex_id: 344, - body_tex_ids: [4, 340, 0, 5], - head_tex_ids: [306, 310], - hair_tex_ids: [undefined, undefined, 330], - // ID 16 for glasses is incorrect but looks decent. - accessory_tex_ids: [6, 16, 330], - }), - new CharacterClassModel({ - name: "FOnewearl", - head_style_count: 1, - hair_style_count: 10, - hair_styles_with_accessory: new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - section_id_tex_id: 505, - body_tex_ids: [1, 0, 2, 501], - head_tex_ids: [472, 468], - hair_tex_ids: [undefined, undefined, 492], - accessory_tex_ids: [12, 13], - }), + HUMAR, + HUNEWEARL, + HUCAST, + HUCASEAL, + RAMAR, + RAMARL, + RACAST, + RACASEAL, + FOMAR, + FOMARL, + FONEWM, + FONEWEARL, ]; readonly animations: readonly CharacterClassAnimationModel[] = new Array(572) @@ -195,29 +96,21 @@ export class Model3DStore extends Store { this.current_animation.observe(({ value }) => this.load_animation(value)), ); - this.set_current_model(this.models[Math.floor(Math.random() * this.models.length)]); + this.set_current_model(random_array_element(this.models)); } - set_current_model = (current_model: CharacterClassModel): void => { + set_current_model = (current_model?: CharacterClassModel): void => { this._current_model.val = current_model; }; - clear_current_model = (): void => { - this._current_model.val = undefined; - }; - set_show_skeleton = (show_skeleton: boolean): void => { this._show_skeleton.val = show_skeleton; }; - set_current_animation = (animation: CharacterClassAnimationModel): void => { + set_current_animation = (animation?: CharacterClassAnimationModel): void => { this._current_animation.val = animation; }; - clear_current_animation = (): void => { - this._current_animation.val = undefined; - }; - set_animation_playing = (playing: boolean): void => { this._animation_playing.val = playing; }; @@ -237,7 +130,8 @@ export class Model3DStore extends Store { const cursor = new ArrayBufferCursor(buffer, Endianness.Little); if (file.name.endsWith(".nj")) { - this.clear_current_model(); + this.set_current_model(undefined); + this._current_textures.clear(); const nj_object = parse_nj(cursor)[0]; @@ -247,7 +141,8 @@ export class Model3DStore extends Store { has_skeleton: true, }); } else if (file.name.endsWith(".xj")) { - this.clear_current_model(); + this.set_current_model(undefined); + this._current_textures.clear(); const nj_object = parse_xj(cursor)[0]; @@ -257,7 +152,7 @@ export class Model3DStore extends Store { has_skeleton: false, }); } else if (file.name.endsWith(".njm")) { - this.clear_current_animation(); + this.set_current_animation(undefined); this._current_nj_motion.val = undefined; const nj_data = this.current_nj_data.val; @@ -267,22 +162,18 @@ export class Model3DStore extends Store { this._current_nj_motion.val = parse_njm(cursor, nj_data.bone_count); } } else if (file.name.endsWith(".xvm")) { - if (this.current_model) { - this._current_textures.val = parse_xvm(cursor).textures; - } + this._current_textures.val = parse_xvm(cursor).textures; } else if (file.name.endsWith(".afs")) { - if (this.current_model) { - const files = parse_afs(cursor); - const textures: XvrTexture[] = []; + const files = parse_afs(cursor); + const textures: XvrTexture[] = []; - for (const file of files) { - textures.push( - ...parse_xvm(new ArrayBufferCursor(file, Endianness.Little)).textures, - ); - } - - this._current_textures.val = textures; + for (const file of files) { + textures.push( + ...parse_xvm(new ArrayBufferCursor(file, Endianness.Little)).textures, + ); } + + this._current_textures.val = textures; } else { logger.error(`Unknown file extension in filename "${file.name}".`); } @@ -292,32 +183,26 @@ export class Model3DStore extends Store { }; private load_model = async (model?: CharacterClassModel): Promise => { - this.clear_current_animation(); + this.set_current_animation(undefined); if (model) { try { + this.set_current_nj_data(undefined); + const nj_object = await this.asset_loader.load_geometry(model); + this._current_textures.val = await this.asset_loader.load_textures( + model, + random_array_element(SectionIds), + random_integer(0, model.body_style_count), + ); + this.set_current_nj_data({ nj_object, // Ignore the bones from the head parts. bone_count: model ? 64 : nj_object.bone_count(), has_skeleton: true, }); - - const textures = await this.asset_loader.load_textures(model); - - this._current_textures.val = [ - textures[ - model.section_id_tex_ids[Math.floor(Math.random() * SectionIds.length)] - ], - ...[ - ...model.body_tex_ids, - ...model.head_tex_ids, - ...model.hair_tex_ids, - ...model.accessory_tex_ids, - ].map(id => (id == undefined ? undefined : textures[id])), - ]; } catch (e) { logger.error(`Couldn't load model for ${model.name}.`); this._current_nj_data.val = undefined; @@ -327,8 +212,7 @@ export class Model3DStore extends Store { } }; - private set_current_nj_data(nj_data: NjData): void { - this._current_textures.clear(); + private set_current_nj_data(nj_data?: NjData): void { this._current_nj_data.val = nj_data; }