add skeletal anims back to viewer

This commit is contained in:
skillbert
2022-04-25 17:14:34 +02:00
parent 29f5323e47
commit dd3379fff2
9 changed files with 186 additions and 85 deletions

View File

@@ -2,7 +2,7 @@ import { Stream, packedHSL2HSL, HSL2RGB } from "./utils";
import { cacheMajors } from "../constants";
import { CacheFileSource, SubFile } from "../cache";
import { parseFrames, parseFramemaps, parseSequences, parseSkeletalAnim } from "../opdecoder";
import { AnimationClip, Bone, Euler, KeyframeTrack, Matrix3, Matrix4, Object3D, Quaternion, QuaternionKeyframeTrack, Skeleton, Vector3, VectorKeyframeTrack } from "three";
import { AnimationClip, Bone, Euler, KeyframeTrack, Matrix3, Matrix4, Object3D, Quaternion, QuaternionKeyframeTrack, Skeleton, SkeletonHelper, SkinnedMesh, Vector3, VectorKeyframeTrack } from "three";
import { skeletalanim } from "../../generated/skeletalanim";
import { framemaps } from "../../generated/framemaps";
import { ThreejsSceneCache } from "./ob3tothree";
@@ -21,22 +21,14 @@ import { MountableAnimation } from "./animationframes";
//28895 elder egg, currently bugged
export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: number): Promise<MountableAnimation> {
let anim = parseSkeletalAnim.read(await cache.getFileById(cacheMajors.skeletalAnims, animid));
let base = parseFramemaps.read(await cache.getFileById(cacheMajors.framemaps, anim.framebase));
export async function mountSkeletalSkeleton(rootnode: Object3D, cache: ThreejsSceneCache, framebaseid: number) {
let base = parseFramemaps.read(await cache.getFileById(cacheMajors.framemaps, framebaseid));
if (!base.skeleton) {
throw new Error("framebase does not have skeleton");
}
let convertedtracks: KeyframeTrack[] = [];
let animtracks = anim.tracks.sort((a, b) => {
if (a.boneid != b.boneid) { return a.boneid - b.boneid; }
return a.type_0to9 - b.type_0to9;
});
let bones: Bone[] = [];
let binds: Matrix4[] = [];
// let binds: Matrix4[] = [];
let rootbones: Bone[] = [];
let tmp = new Matrix4();
let prematrix = new Matrix4().makeScale(1, 1, -1);
@@ -59,12 +51,35 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
// "", +bone.scale.x.toFixed(2), +bone.scale.y.toFixed(2), +bone.scale.z.toFixed(2));
bone.updateMatrixWorld();
bones[id] = bone;
binds[id] = matrix;
// binds[id] = matrix;
}
prematrix.invert();
binds.forEach(q => q.multiply(prematrix));
// prematrix.invert();
// binds.forEach(q => q.multiply(prematrix));
let skeleton = new Skeleton(bones);
if (rootbones.length != 0) { rootnode.add(...rootbones); }
rootnode.updateMatrixWorld(true);
let childbind = new Matrix4().copy(rootnode.matrixWorld);
//TODO find out whats wrong with my own inverses
skeleton.calculateInverses();
rootnode.traverse(node => {
if (node instanceof SkinnedMesh) {
node.bind(skeleton, childbind);
}
});
}
export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: number) {
let anim = parseSkeletalAnim.read(await cache.getFileById(cacheMajors.skeletalAnims, animid));
let convertedtracks: KeyframeTrack[] = [];
let animtracks = anim.tracks.sort((a, b) => {
if (a.boneid != b.boneid) { return a.boneid - b.boneid; }
return a.type_0to9 - b.type_0to9;
});
let actiontypemap: { t: "unknown" | "rotate" | "translate" | "scale", a: number }[] = [
{ t: "unknown", a: 0 },
@@ -80,16 +95,16 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
{ t: "scale", a: 2 },
//10-16 unknown
{ t: "unknown", a: 0 },
{ t: "unknown", a: 0 },
{ t: "unknown", a: 0 },
{ t: "unknown", a: 0 },
{ t: "unknown", a: 0 },
{ t: "unknown", a: 0 },
{ t: "unknown", a: 0 },
{ t: "unknown", a: 0 },//109 hits, -1 3x, 0 103x, 1 3x
{ t: "unknown", a: 0 },//109 hits, -1 6x, 0 103x
{ t: "unknown", a: 0 },//109 hits, -1 4x, 0 94x, 1 15x
{ t: "unknown", a: 0 },//4k hits, 0x 3400, 42 600x
{ t: "unknown", a: 0 },//4k hits, sort of spread between 0-0.015, most at bounderies
{ t: "unknown", a: 0 },//4k hits, spread between -0.3-0.1, most at bounderies
{ t: "unknown", a: 0 },//2k hits, spread between -1-1, most at bounderies or 0
]
console.log(animtracks);
for (let index = 0; index < animtracks.length;) {
let track = animtracks[index];
@@ -110,13 +125,9 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
if (t2.a == 2) { zvalues = track2.chunks; }
index++;
}
let bone = bones[boneid];
if (!bone) {
console.log("animation track without bone", boneid, track.boneid);
continue;
}
let bonename = bone.name;
// if (track.bonetype_01or3 == 3) { continue; }
// if (boneid >= 6 && boneid <= 8) { continue; }
let bonename = "bone_" + boneid;
let defaultvalue = (tracktype.t == "scale" ? 1 : 9);
let intp = (v: { time: number, value: number[] }[] | null, i: number, t: number) => {
@@ -131,6 +142,9 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
let data: number[] = [];
let euler = new Euler();
let quat = new Quaternion();
// if (tracktype.t == "scale") {
// console.log(xvalues, yvalues, zvalues);
// }
// let time = new Float32Array(timearray.map(q => q * 0.020));
for (let ix = 0, iy = 0, iz = 0, idata = 0; ;) {
let tx = xvalues?.[ix]?.time ?? Infinity;
@@ -177,5 +191,5 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
let clip = new AnimationClip("anim_" + (Math.random() * 1000 | 0), undefined, convertedtracks);
return { skeleton, clip, rootbones };
return { clip, framebaseid: anim.framebase };
}

View File

@@ -172,7 +172,7 @@ export function parseOb3Model(modelfile: Buffer) {
let boneid = rawbuf[dataindex++] | (rawbuf[dataindex++] << 8);//manual 16bit building since it might not be alligned
let actualweight = (weight != 0 ? weight : remainder);
remainder -= weight;
skinIdBuffer[i * 4 + j] = (boneid == 65535 ? 0 : boneid);
skinIdBuffer[i * 4 + j] = (boneid == 65535 ? 0 : boneid);//TODO this should be boneid+1since we're shifting in -1 to 0?
skinWeightBuffer[i * 4 + j] = actualweight;
if (boneid >= bonecount) {
bonecount = boneid + 2;//we are adding a root bone at 0, and count is max+1

View File

@@ -8,7 +8,7 @@
let filehandle = null;
setInterval(() => {
filehandle?.requestPermission().then(q => console.log(q));
filehandle?.requestPermission();
}, 1000 * 60);
onmessage = (e) => {

View File

@@ -4,10 +4,16 @@
["endtime","uint"],
["unk_always0","ubyte"],
["tracks",["array","ushort",["struct",
//seems to correlate to action type
//1=standard bone(1-9) 2=unknown(7x 3, 14x 7, 14x 8), 3=unknown(10-15), 3=unknown(16)
["unk_1to4","ubyte"],
//boneid+0x40, or boneid+0x4040 if 2 byte
["boneid","varushort"],
//animation type 1-3=rotatexyz, 4-6=translatexyz 7-9=scalexyz
["type_0to9","ubyte"],
["$packetlength","ushort"],
//interpolation mode? 0=euler, 1=linear, 3lin/log???
//0=rotation (1,2,3), 1=translation (4,5,6,13,14,15,some 16), 3=scale (7,8,9,10,11,12,some 16,some 3)
["bonetype_01or3","ubyte"],
["always0","ushort"],
["flag2","bool"],

View File

@@ -81,7 +81,7 @@ export const parseAnimgroupConfigs = new FileParser<import("../generated/animgro
export const parseModels = new FileParser<import("../generated/models").models>(require("./opcodes/models.json"));
export const parseSpotAnims = new FileParser<import("../generated/spotanims").spotanims>(require("./opcodes/spotanims.json"));
export const parseRootCacheIndex = new FileParser<import("../generated/rootcacheindex").rootcacheindex>(require("./opcodes/rootcacheindex.json"));
export const parseSkeletalAnim = new FileParser<import("../generated/skeletalanim").skeletalanim>(require("./opcodes/skeletalanim.json"));
export const parseSkeletalAnim = new FileParser<import("../generated/skeletalanim").skeletalanim>(require("./opcodes/skeletalanim.jsonc"));
export const parseMaterials = new FileParser<import("../generated/materials").materials>(require("./opcodes/materials.jsonc"));
export const parseQuickchatCategories = new FileParser<import("../generated/quickchatcategories").quickchatcategories>(require("./opcodes/quickchatcategories.jsonc"));
export const parseQuickchatLines = new FileParser<import("../generated/quickchatlines").quickchatlines>(require("./opcodes/quickchatlines.jsonc"));

View File

@@ -94,4 +94,53 @@ async function start() {
}
}
}
start();
start();
async function loadSkeletons() {
let source = new GameCacheLoader();
let skelindex = await source.getIndexFile(cacheMajors.skeletalAnims);
let files: Buffer[] = [];
for (let index of skelindex) {
if (!index) { continue; }
if (files.length % 50 == 0) { console.log(files.length); }
files.push(await source.getFile(index.major, index.minor, index.crc));
}
return function* () {
for (let file of files) {
yield parseSkeletalAnim.read(file);
}
}
};
globalThis.loadSkeletons = loadSkeletons;
async function render() {
let skelfiles = await loadSkeletons()
let points: number[] = [];
for (let skel of skelfiles()) {
for (let track of skel.tracks) {
if (track.type_0to9 >= 1 && track.type_0to9 <= 3) {
points.push(...track.chunks.flatMap(q => q.value[0]))
}
}
}
let min = Infinity, max = -Infinity;
for (let p of points) { min = Math.min(min, p); max = Math.max(max, p) }
let n = 21;
let start = -10;
let end = 10;
let size = (end - start) / (n - 1);
let buckets = new Array(n).fill(0);
let misses = 0;
for (let p of points) {
let i = Math.floor((p - start) / size);
if (i >= 0 && i < n) { buckets[i]++; }
else { misses++; }
}
console.log("min", min);
console.log("max", max);
console.log("misses", misses);
buckets.forEach((n, i) => console.log((i * size + start).toFixed(1), n))
}

View File

@@ -1,5 +1,5 @@
import { parseAnimgroupConfigs, parseEnvironments, parseItem, parseNpc, parseObject } from "../opdecoder";
import { parseAnimgroupConfigs, parseEnvironments, parseItem, parseNpc, parseObject, parseSkeletalAnim } from "../opdecoder";
import { ThreeJsRenderer } from "./threejsrender";
import { cacheConfigPages, cacheMajors } from "../constants";
import * as React from "react";
@@ -19,6 +19,8 @@ import { appearanceUrl, avatarStringToBytes, avatarToModel } from "../3d/avatar"
import { ModelBrowser } from "./scenenodes";
import "./fsapi";
if (module.hot) {
module.hot.accept(["../3d/ob3togltf", "../3d/ob3tothree"]);
}
@@ -158,6 +160,25 @@ class App extends React.Component<{}, { renderer: ThreeJsRenderer | null, cache:
requestFiles() {
ensureCachePermission().then(engine => {
this.setState({ cache: new ThreejsSceneCache(engine) });
//TODO remove
let source = engine.source;
globalThis.loadSkeletons = async function run() {
let skelindex = await source.getIndexFile(cacheMajors.skeletalAnims);
let files: Buffer[] = [];
for (let index of skelindex) {
if (!index) { continue; }
if (files.length % 50 == 0) { console.log(files.length); }
files.push(await source.getFile(index.major, index.minor, index.crc));
}
return function* () {
for (let file of files) {
yield parseSkeletalAnim.read(file);
}
}
};
});
}

View File

@@ -18,7 +18,7 @@ import { ParsedTexture } from "../3d/textures";
import { appearanceUrl, avatarStringToBytes, avatarToModel } from "../3d/avatar";
import { ThreeJsRenderer } from "./threejsrender";
import { ModelData, parseOb3Model } from "../3d/ob3togltf";
import { parseSkeletalAnimation } from "../3d/animationskeletal";
import { mountSkeletalSkeleton, parseSkeletalAnimation } from "../3d/animationskeletal";
import { TypedEmitter } from "../3d/utils";
import { objects } from "../../generated/objects";
import { items } from "../../generated/items";
@@ -78,23 +78,27 @@ export class ModelBrowser extends React.Component<{ render: ThreeJsRenderer, cac
// // }));
// }
render() {
setMode(mode: LookupMode) {
localStorage.rsmv_lastmode = mode;
this.setState({ mode, search: "" });
}
render() {
let ModeComp = LookupModeComponentMap[this.state.mode];
return (
<React.Fragment>
<div className="sidebar-browser-tab-strip">
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "item" })} onClick={() => this.setState({ mode: "item" })}>Items IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "npc" })} onClick={() => this.setState({ mode: "npc" })}>NPCs IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "object" })} onClick={() => this.setState({ mode: "object" })}>Obj/Locs IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "avatar" })} onClick={() => this.setState({ mode: "avatar" })}>Avatar</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "model" })} onClick={() => this.setState({ mode: "model" })}>Model IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "map" })} onClick={() => this.setState({ mode: "map" })}>Map</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "material" })} onClick={() => this.setState({ mode: "material" })}>Material IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "spotanim" })} onClick={() => this.setState({ mode: "spotanim" })}>Spotanims</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "scenario" })} onClick={() => this.setState({ mode: "scenario" })}>Scenario</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "item" })} onClick={() => this.setMode("item")}>Items IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "npc" })} onClick={() => this.setMode("npc")}>NPCs IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "object" })} onClick={() => this.setMode("object")}>Obj/Locs IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "avatar" })} onClick={() => this.setMode("avatar")}>Avatar</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "model" })} onClick={() => this.setMode("model")}>Model IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "map" })} onClick={() => this.setMode("map")}>Map</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "material" })} onClick={() => this.setMode("material")}>Material IDs</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "spotanim" })} onClick={() => this.setMode("spotanim")}>Spotanims</div>
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "scenario" })} onClick={() => this.setMode("scenario")}>Scenario</div>
</div>
<ModeComp cache={this.props.cache} scene={this.props.render} />
<ModeComp cache={this.props.cache} scene={this.props.render} initialId={this.state.search} />
</React.Fragment>
);
}
@@ -143,20 +147,24 @@ export class RSModel extends TypedEmitter<{ loaded: undefined }>{
renderscene: ThreeJsRenderer | null = null;
targetAnimId = -1;
skeletontype: "none" | "baked" | "full" = "none";
skeletonHelper: SkeletonHelper | null = null;
cleanup() {
this.listeners = {};
this.rootnode.removeFromParent();
this.skeletonHelper?.removeFromParent();
if (this.renderscene) {
this.renderscene.animationMixers.delete(this.mixer);
}
this.renderscene = null;
}
addToScene(scene: ThreeJsRenderer, node = scene.modelnode) {
this.renderscene = scene;
scene.animationMixers.add(this.mixer);
node.add(this.rootnode);
if (this.skeletonHelper) { node.add(this.skeletonHelper); }
scene.forceFrame();
}
@@ -208,8 +216,8 @@ export class RSModel extends TypedEmitter<{ loaded: undefined }>{
this.mixer.stopAllAction();
let action = this.mixer.clipAction(clip, mesh);
action.play();
// let skelhelper = new SkeletonHelper(mesh);
// this.rootnode.add(skelhelper);
this.skeletonHelper = new SkeletonHelper(mesh);
this.renderscene?.scene.add(this.skeletonHelper);
this.mountedanim = clip;
}
@@ -224,8 +232,14 @@ export class RSModel extends TypedEmitter<{ loaded: undefined }>{
let clip: AnimationClip;
if (seq.skeletal_animation) {
throw new Error("todo");//TODO
// mount = await parseSkeletalAnimation(this.cache, seq.skeletal_animation);
let anim = await parseSkeletalAnimation(this.cache, seq.skeletal_animation);
clip = anim.clip;
let loaded = this.loaded ?? await this.model;
if (this.skeletontype != "full") {
if (this.skeletontype != "none") { throw new Error("wrong skeleton type already mounted to model"); }
await mountSkeletalSkeleton(loaded.mesh, this.cache, anim.framebaseid);
this.skeletontype = "full";
}
} else if (seq.frames) {
let frameanim = await parseAnimationSequence4(this.cache, seq.frames);
let loaded = this.loaded ?? await this.model;
@@ -262,7 +276,7 @@ type ScenarioComponent = {
}
class InputCommitted2 extends React.Component<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>>{
class InputCommitted extends React.Component<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>>{
el: HTMLInputElement | null = null;
@boundMethod
onChange(e: Event) {
@@ -286,22 +300,11 @@ class InputCommitted2 extends React.Component<React.DetailedHTMLProps<React.Inpu
}
}
function InputCommitted(p: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>) {
let ref = React.useCallback((e: HTMLInputElement | null) => {
if (e && p.onChange) {
e.addEventListener("change", p.onChange as any);
return () => e.removeEventListener("change", p.onChange as any);
}
}, [p.onChange]);
let newp = { ...p, onChange: undefined, value: undefined, defaultValue: undefined };
return <input ref={ref} {...newp} />;
}
function ScenarioAnimControl(p: { anim: ScenarioComponent["anims"][number], onChange: (v: ScenarioComponent["anims"][number] | null) => void }) {
return (
<form>
<InputCommitted2 type="number" value={p.anim.animid} onChange={e => p.onChange({ ...p.anim, animid: +e.currentTarget.value })} />
<InputCommitted2 type="number" value={p.anim.startTime} onChange={e => p.onChange({ ...p.anim, startTime: +e.currentTarget.value })} />
<InputCommitted type="number" value={p.anim.animid} onChange={e => p.onChange({ ...p.anim, animid: +e.currentTarget.value })} />
<InputCommitted type="number" value={p.anim.startTime} onChange={e => p.onChange({ ...p.anim, startTime: +e.currentTarget.value })} />
<div onClick={() => p.onChange(null)}>delete</div>
</form>
);
@@ -320,12 +323,13 @@ function ScenarioControl(p: { comp: ScenarioComponent, onChange: (v: ScenarioCom
};
return <ScenarioAnimControl key={i} anim={anim} onChange={onchange} />
})}
<div onClick={e => p.onChange({ ...p.comp, anims: [...p.comp.anims, { animid: -1, startTime: 0 }] })}>add anim</div>
<div onClick={e => p.onChange(null)}>delete</div>
</div>
)
}
export class SceneScenario extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { components: ScenarioComponent[], addType: keyof typeof primitiveModelInits }>{
export class SceneScenario extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { components: ScenarioComponent[], addType: keyof typeof primitiveModelInits }>{
models = new Map<ScenarioComponent, RSModel>();
@@ -377,7 +381,7 @@ export class SceneScenario extends React.Component<{ scene: ThreeJsRenderer, cac
this.models.delete(oldcomp);
if (model && newcomp) {
this.models.set(newcomp, model);
model.setAnimation(newcomp.anims[0].animid);
if (newcomp.anims.length != 0) { model.setAnimation(newcomp.anims[0].animid); }
}
if (newcomp) { components[index] = newcomp; }
else { components.splice(index, 1); }
@@ -476,7 +480,7 @@ async function locToModel(cache: ThreejsSceneCache, id: number) {
return { models, animids, loc: obj };
}
export class ScenePlayer extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { avaitems: items[] | null, animset: animgroupconfigs | null }> {
export class ScenePlayer extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { avaitems: items[] | null, animset: animgroupconfigs | null }> {
model: RSModel | null = null;
modelid = "";
@@ -494,6 +498,7 @@ export class ScenePlayer extends React.Component<{ scene: ThreeJsRenderer, cache
@boundMethod
async setModel(modelid: string) {
localStorage.rsmv_lastsearch = modelid;
if (this.model && this.modelid != modelid) {
this.model.cleanup();
}
@@ -516,7 +521,7 @@ export class ScenePlayer extends React.Component<{ scene: ThreeJsRenderer, cache
render() {
return (
<React.Fragment>
<StringInput onChange={this.setModel} />
<StringInput onChange={this.setModel} initialid={this.props.initialId} />
{this.state.avaitems?.map((item, index) => {
return <div key={index}>{item.name ?? "no name"}</div>
})}
@@ -530,7 +535,7 @@ function JsonDisplay(p: { obj: any }) {
return (<pre className="json-block">{prettyJson(p.obj)}</pre>);
}
export class SceneRawModel extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }> {
export class SceneRawModel extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }> {
model: RSModel | null = null;
modelid: number = -1;
@@ -540,6 +545,7 @@ export class SceneRawModel extends React.Component<{ scene: ThreeJsRenderer, cac
@boundMethod
setModel(modelid: number) {
localStorage.rsmv_lastsearch = modelid;
if (this.model && this.modelid != modelid) {
this.model.cleanup();
}
@@ -555,13 +561,13 @@ export class SceneRawModel extends React.Component<{ scene: ThreeJsRenderer, cac
render() {
return (
<React.Fragment>
<IdInput onChange={this.setModel} />
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
</React.Fragment>
)
}
}
export class SceneMaterial extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { matdata: materials | null }> {
export class SceneMaterial extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { matdata: materials | null }> {
model: RSModel | null = null;
modelid: number = -1;
@@ -578,6 +584,7 @@ export class SceneMaterial extends React.Component<{ scene: ThreeJsRenderer, cac
@boundMethod
async setModel(modelid: number) {
localStorage.rsmv_lastsearch = modelid;
if (this.model && this.modelid != modelid) {
this.model.cleanup();
}
@@ -617,14 +624,14 @@ export class SceneMaterial extends React.Component<{ scene: ThreeJsRenderer, cac
render() {
return (
<React.Fragment>
<IdInput onChange={this.setModel} />
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
<JsonDisplay obj={this.state.matdata} />
</React.Fragment>
)
}
}
export class SceneLocation extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { locdata: objects | null }> {
export class SceneLocation extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { locdata: objects | null }> {
model: RSModel | null = null;
modelid: number = -1;
@@ -641,6 +648,7 @@ export class SceneLocation extends React.Component<{ scene: ThreeJsRenderer, cac
@boundMethod
async setModel(modelid: number) {
localStorage.rsmv_lastsearch = modelid;
if (this.model && this.modelid != modelid) {
this.model.cleanup();
}
@@ -661,14 +669,14 @@ export class SceneLocation extends React.Component<{ scene: ThreeJsRenderer, cac
render() {
return (
<React.Fragment>
<IdInput onChange={this.setModel} />
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
<JsonDisplay obj={this.state.locdata} />
</React.Fragment>
)
}
}
export class SceneItem extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { itemdata: items | null }> {
export class SceneItem extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { itemdata: items | null }> {
model: RSModel | null = null;
modelid: number = -1;
@@ -685,6 +693,7 @@ export class SceneItem extends React.Component<{ scene: ThreeJsRenderer, cache:
@boundMethod
async setModel(modelid: number) {
localStorage.rsmv_lastsearch = modelid;
if (this.model && this.modelid != modelid) {
this.model.cleanup();
}
@@ -710,13 +719,13 @@ export class SceneItem extends React.Component<{ scene: ThreeJsRenderer, cache:
render() {
return (
<React.Fragment>
<IdInput onChange={this.setModel} />
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
<JsonDisplay obj={this.state.itemdata} />
</React.Fragment>
)
}
}
export class SceneNpc extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { npcdata: npcs | null }> {
export class SceneNpc extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { npcdata: npcs | null }> {
model: RSModel | null = null;
modelid: number = -1;
@@ -733,6 +742,7 @@ export class SceneNpc extends React.Component<{ scene: ThreeJsRenderer, cache: T
@boundMethod
async setModel(modelid: number) {
localStorage.rsmv_lastsearch = modelid;
if (this.model && this.modelid != modelid) {
this.model.cleanup();
}
@@ -753,14 +763,14 @@ export class SceneNpc extends React.Component<{ scene: ThreeJsRenderer, cache: T
render() {
return (
<React.Fragment>
<IdInput onChange={this.setModel} />
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
<JsonDisplay obj={this.state.npcdata} />
</React.Fragment>
)
}
}
export class SceneSpotAnim extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { animdata: spotanims | null }> {
export class SceneSpotAnim extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { animdata: spotanims | null }> {
model: RSModel | null = null;
modelid: number = -1;
@@ -777,6 +787,7 @@ export class SceneSpotAnim extends React.Component<{ scene: ThreeJsRenderer, cac
@boundMethod
async setModel(modelid: number) {
localStorage.rsmv_lastsearch = modelid;
if (this.model && this.modelid != modelid) {
this.model.cleanup();
}
@@ -797,7 +808,7 @@ export class SceneSpotAnim extends React.Component<{ scene: ThreeJsRenderer, cac
render() {
return (
<React.Fragment>
<IdInput onChange={this.setModel} />
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
<JsonDisplay obj={this.state.animdata} />
</React.Fragment>
)
@@ -808,7 +819,7 @@ type SceneMapState = {
center: { x: number, z: number },
toggles: Record<string, boolean>
};
export class SceneMapModel extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, SceneMapState> {
export class SceneMapModel extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, SceneMapState> {
oldautoframe: boolean;
constructor(p) {
super(p);
@@ -984,7 +995,7 @@ export class SceneMapModel extends React.Component<{ scene: ThreeJsRenderer, cac
}
const LookupModeComponentMap: Record<LookupMode, { new(p: any): React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, any> }> = {
const LookupModeComponentMap: Record<LookupMode, { new(p: any): React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, any> }> = {
model: SceneRawModel,
item: SceneItem,
avatar: ScenePlayer,

View File

@@ -19,7 +19,7 @@ module.exports = {
diff: "./src/scripts/cachediff.ts",
deps: "./src/scripts/dependencies.ts",
quickchatlookup: "./src/scripts/quickchatlookup.ts",
scrapeavatars: "./src/scripts/scrapeavatars.ts",
scrapeavatars: "./src/scripts/scrapeavatars.ts"
},
module: {
rules: [