add option to compare map versions in viewer

This commit is contained in:
Skillbert
2023-10-12 20:29:07 +02:00
parent dde0dabf25
commit 873dc99f7c
7 changed files with 177 additions and 55 deletions

View File

@@ -26,6 +26,7 @@ export type maplabels = {
upper: number,
} | null
rightclick_1?: string | null
unktext_0b?: string | null
polygon?: {
pointcount: number,
points: {
@@ -38,14 +39,14 @@ export type maplabels = {
number,
number,
],
always_1: number,
always_1: (number|1),
back_color: [
number,
number,
number,
number,
],
pointplanes: number[],
pointplanes: (number[]|null),
} | null
rightclick_2?: string | null
category?: number | null
@@ -55,7 +56,8 @@ export type maplabels = {
lower: number,
upper: number,
} | null
unknown_15?: number[] | null
unknown_15?: number | null
unknown_16?: number | null
background_sprite?: number | null
legacy_switch?: {
varbit: number,

View File

@@ -26,6 +26,7 @@ import { HSL2RGB, HSL2RGBfloat, packedHSL2HSL } from "../utils";
import { loadProcTexture } from "./proceduraltexture";
import { maplabels } from "../../generated/maplabels";
import { minimapLocMaterial } from "../rs3shaders";
import { DependencyGraph, getDependencies } from "../scripts/dependencies";
const constModelOffset = 1000000;
@@ -199,6 +200,7 @@ export class EngineCache extends CachingFileSource {
mapMapscenes: mapscenes[] = [];
mapMaplabels: maplabels[] = [];
jsonSearchCache = new Map<string, { files: Promise<any[]>, schema: JSONSchema6Definition }>();
dependencyGraph: Promise<DependencyGraph> | null = null;
legacyData: LegacyData | null = null;
classicData: ClassicConfig | null = null;
@@ -269,6 +271,11 @@ export class EngineCache extends CachingFileSource {
return this;
}
async getDependencyGraph() {
this.dependencyGraph ??= getDependencies(this, { lazyMapChunks: true });
return this.dependencyGraph;
}
async getGameFile(type: keyof LegacyData & keyof typeof cacheMajors, id: number) {
if (this.legacyData) {
return this.legacyData[type][id];

View File

@@ -214,7 +214,7 @@ export class Openrs2CacheSource extends cache.DirectCacheFileSource {
if (this.fscache && typeof crc != "undefined" && crc != 0) {//TODO fix places that use a magic 0 crc
cachedfile = await this.fscache.getFile(major, minor, crc);
} else {
console.log("uncachable", major, minor, crc);
// console.log("uncachable", major, minor, crc);
}
let rawfile = cachedfile ?? await this.downloadFile(major, minor);
if (this.fscache && !cachedfile && typeof crc != "undefined" && crc != 0) {

View File

@@ -183,7 +183,6 @@ export function mapsquareLocDependencies(grid: TileGrid, deps: DependencyGraph,
}
outlocgroups.push(outgroup);
for (let loc of group) {
let first = loc[0];
v0.set(0, 0, 0);
v1.set(0, 0, 0);
for (let mesh of loc) {
@@ -194,7 +193,7 @@ export function mapsquareLocDependencies(grid: TileGrid, deps: DependencyGraph,
v1.max(v2);
}
}
//8 vertices, one for each bounding box corner
boxAttribute.setXYZ(0, v0.x, v0.y, v0.z);
boxAttribute.setXYZ(1, v0.x, v0.y, v1.z);
@@ -204,7 +203,8 @@ export function mapsquareLocDependencies(grid: TileGrid, deps: DependencyGraph,
boxAttribute.setXYZ(5, v1.x, v0.y, v1.z);
boxAttribute.setXYZ(6, v1.x, v1.y, v0.z);
boxAttribute.setXYZ(7, v1.x, v1.y, v1.z);
let first = loc[0];
let trans = transformVertexPositions(boxAttribute, first.morph, grid, first.maxy, rect.x * tiledimensions * rs2ChunkSize, rect.z * tiledimensions * rs2ChunkSize);
// trans.newpos.applyMatrix4(trans.matrix);
let bounds = [...trans.newpos.array as Float32Array].map(v => v | 0);
@@ -335,8 +335,7 @@ export function compareLocDependencies(chunka: ChunkLocDependencies[], chunkb: C
return vertsets;
}
export async function mapdiffmesh(scene: ThreejsSceneCache, points: number[][]) {
let col = [255, 0, 0] as [number, number, number];
export async function mapdiffmesh(scene: ThreejsSceneCache, points: number[][], col: [number, number, number] = [255, 0, 0]) {
let tri = (model: ModelBuilder, verts: number[], a: number, b: number, c: number) => model.mat(-1).addTriangle(col,
verts.slice(a * 3, a * 3 + 3) as any,
verts.slice(b * 3, b * 3 + 3) as any,

View File

@@ -7,7 +7,7 @@ import { cacheMajors } from "../constants";
import { parse } from "../opdecoder";
import { canvasToImageFile, flipImage, pixelsToImageFile } from "../imgutils";
import { EngineCache, ThreejsSceneCache } from "../3d/modeltothree";
import { crc32addInt, DependencyGraph, getDependencies } from "../scripts/dependencies";
import { crc32addInt, DependencyGraph } from "../scripts/dependencies";
import { ScriptOutput } from "../scriptrunner";
import { CallbackPromise, delay, stringToFileRange, trickleTasks } from "../utils";
import { drawCollision } from "./collisionimage";
@@ -227,7 +227,8 @@ export async function runMapRender(output: ScriptOutput, filesource: CacheFileSo
if (areas.length == 1) {
deparea = { x: areas[0].x - 2, z: areas[0].z - 2, xsize: areas[0].xsize + 2, zsize: areas[0].zsize + 2 };
}
var deps = await getDependencies(engine, { area: deparea });
var deps = await engine.getDependencyGraph();
await deps.preloadChunkDependencies({ area: deparea });
} catch (e) {
console.error(e);
progress.updateProp("deps", "starting dependency graph");
@@ -523,7 +524,7 @@ class SimpleHasher {
let exists = false;
for (let z = rect.z; z < rect.z + rect.zsize; z++) {
for (let x = rect.x; x < rect.x + rect.xsize; x++) {
exists ||= this.depstracker.deps.hasEntry(this.depstracker.deps.makeDeptName("mapsquare", x + z * worldStride));
exists ||= this.depstracker.deps.hasEntry("mapsquare", x + z * worldStride);
}
}
return exists;

View File

@@ -14,7 +14,7 @@ const depids = arrayEnum(["material", "model", "item", "loc", "mapsquare", "sequ
const depidmap = Object.fromEntries(depids.map((q, i) => [q, i]));
export type DepTypes = typeof depids[number];
type DepArgs = { area?: MapRect, modelMaterials?: boolean } | undefined;
type DepArgs = { area?: MapRect } | undefined;
type DepCallback = (holdertype: DepTypes, holderId: number, deptType: DepTypes, depId: number) => void;
type HashCallback = (depType: DepTypes, depId: number, hash: number, version: number) => void;
type DepCollector = (cache: EngineCache, addDep: DepCallback, addHash: HashCallback, args: DepArgs) => Promise<void>;
@@ -35,6 +35,25 @@ const mapsquareDeps: DepCollector = async (cache, addDep, addHash) => {
}
}
function chunkDeps(data: ChunkData, addDep: DepCallback, addHash: HashCallback) {
let squareindex = data.mapsquarex + data.mapsquarez * worldStride;
addHash("mapsquare", squareindex, data.chunkfilehash, data.chunkfileversion);
for (let loc of data.rawlocs) {
addDep("loc", loc.id, "mapsquare", squareindex);
}
//batch these before adding for performance
let overlays = new Set<number>();
let underlays = new Set<number>();
for (let tile of data.tiles) {
if (tile.overlay != null) { overlays.add(tile.overlay); }
if (tile.underlay != null) { underlays.add(tile.underlay); }
}
//set iterators are same as insertion order according to the spec
overlays.forEach(id => addDep("overlay", id, "mapsquare", squareindex));
underlays.forEach(id => addDep("overlay", id, "mapsquare", squareindex));
}
const mapsquareDeps2: DepCollector = async (cache, addDep, addHash, args) => {
await trickleTasksTwoStep(20, function* () {
let rect = args?.area ?? { x: 0, z: 0, xsize: 100, zsize: 200 };
@@ -45,22 +64,7 @@ const mapsquareDeps2: DepCollector = async (cache, addDep, addHash, args) => {
}
}, data => {
if (!data) { return; }
let squareindex = data.mapsquarex + data.mapsquarez * worldStride;
addHash("mapsquare", squareindex, data.chunkfilehash, data.chunkfileversion);
for (let loc of data.rawlocs) {
addDep("loc", loc.id, "mapsquare", squareindex);
}
//batch these before adding for performance
let overlays = new Set<number>();
let underlays = new Set<number>();
for (let tile of data.tiles) {
if (tile.overlay != null) { overlays.add(tile.overlay); }
if (tile.underlay != null) { underlays.add(tile.underlay); }
}
overlays.forEach(id => addDep("overlay", id, "mapsquare", squareindex));
underlays.forEach(id => addDep("overlay", id, "mapsquare", squareindex));
chunkDeps(data, addDep, addHash);
});
}
@@ -297,20 +301,20 @@ const modelDeps: DepCollector = async (cache, addDep, addHash, opts) => {
if (!modelindex) { continue; }
addHash("model", modelindex.minor, modelindex.crc, modelindex.version);
if (opts?.modelMaterials) {
let file = await cache.getFile(modelindex.major, modelindex.minor, modelindex.crc);
let model = parse.models.read(file, cache);
for (let mesh of model.meshes) {
if (mesh.materialArgument != 0) {
addDep("material", mesh.materialArgument - 1, "model", modelindex.minor);
}
}
}
// if (opts?.modelMaterials) {
// let file = await cache.getFile(modelindex.major, modelindex.minor, modelindex.crc);
// let model = parse.models.read(file, cache);
// for (let mesh of model.meshes) {
// if (mesh.materialArgument != 0) {
// addDep("material", mesh.materialArgument - 1, "model", modelindex.minor);
// }
// }
// }
}
}
export type DependencyGraph = (typeof getDependencies) extends ((...args: any[]) => Promise<infer T>) ? T : never;
export async function getDependencies(cache: EngineCache, args?: DepArgs) {
export async function getDependencies(cache: EngineCache, args?: {}) {
let dependentsMap = new Map<string, string[]>();
let dependencyMap = new Map<string, string[]>();
let hashes = new Map<string, number>();
@@ -341,8 +345,21 @@ export async function getDependencies(cache: EngineCache, args?: DepArgs) {
globalThis.dependentsMap = dependentsMap;
let runDependencyGroup = async (run: DepCollector, args) => {
try {
console.log(`starting ${run.name}`);
let t = Date.now();
await run(cache, addDep, addHash, args);
console.log(`finished ${run.name}, duration ${((Date.now() - t) / 1000).toFixed(1)}`);
} catch (e) {
debugger;
throw e;
}
}
let runs: DepCollector[] = [
mapsquareDeps2,
// mapsquareDeps2,
locationDeps,
itemDeps,
animgroupDeps,
@@ -357,16 +374,12 @@ export async function getDependencies(cache: EngineCache, args?: DepArgs) {
// framesetDeps,
];
try {
for (let run of runs) {
console.log(`starting ${run.name}`);
let t = Date.now();
await run(cache, addDep, addHash, args);
console.log(`finished ${run.name}, duration ${((Date.now() - t) / 1000).toFixed(1)}`);
}
} catch (e) {
debugger;
throw e;
for (let run of runs) {
await runDependencyGroup(run, args);
}
let preloadChunkDependencies = (args?: DepArgs) => {
return runDependencyGroup(mapsquareDeps2, args);
}
let makeDeptName = (deptType: DepTypes, id: number) => {
@@ -404,12 +417,19 @@ export async function getDependencies(cache: EngineCache, args?: DepArgs) {
return crc;
}
let hasEntry = (depname: string) => {
return hashes.has(depname);
let hasEntry = (deptType: DepTypes, depId: number) => {
return hashes.has(makeDeptName(deptType, depId));
}
return { dependencyMap, dependentsMap, maxVersion, cascadeDependencies, makeDeptName, hashDependencies, hasEntry };
let insertMapChunk = (data: ChunkData) => {
chunkDeps(data, addDep, addHash);
let squareindex = data.mapsquarex + data.mapsquarez * worldStride;
return makeDeptName("mapsquare", squareindex);
}
return { dependencyMap, dependentsMap, maxVersion, cascadeDependencies, makeDeptName, hashDependencies, hasEntry, insertMapChunk, preloadChunkDependencies };
}
//TODO remove
globalThis.getdeps = getDependencies;

View File

@@ -32,6 +32,7 @@ import { MaterialData } from '../3d/jmat';
import { extractCacheFiles } from '../scripts/extractfiles';
import { debugProcTexture } from '../3d/proceduraltexture';
import { MapRenderDatabaseBacked } from '../map/backends';
import { compareFloorDependencies, compareLocDependencies, mapdiffmesh, mapsquareFloorDependencies, mapsquareLocDependencies } from '../map/chunksummary';
type LookupMode = "model" | "item" | "npc" | "object" | "material" | "map" | "avatar" | "spotanim" | "scenario" | "scripts";
@@ -1589,8 +1590,20 @@ function SceneSpotAnim(p: LookupModeProps) {
</React.Fragment>
)
}
type DiffMesh = {
a: ThreejsSceneCache,
b: ThreejsSceneCache,
info: any,
floora: number,
floorb: number,
visible: boolean,
floormesh: ThreeJsSceneElementSource,
locsmesh: ThreeJsSceneElementSource,
remove: () => void
}
type SceneMapState = {
chunkgroups: { rect: MapRect, models: Map<ThreejsSceneCache, RSMapChunk> }[],
chunkgroups: { rect: MapRect, models: Map<ThreejsSceneCache, RSMapChunk>, diffs: DiffMesh[] }[],
center: { x: number, z: number },
toggles: Record<string, boolean>,
selectionData: any,
@@ -1621,6 +1634,71 @@ export class SceneMapModel extends React.Component<LookupModeProps, SceneMapStat
showModal({ title: "Map view" }, <Map2dView chunks={this.state.chunkgroups.map(q => q.models.get(this.props.ctx!.sceneCache)!).filter(q => q)} gridsize={512} mapscenes={true} />);
}
async diffCaches(floora = 3, floorb = 3) {
let group = this.state.chunkgroups[0];
if (!this.props.ctx || !group) {
return;
}
let arr = [...group.models.entries()];
if (arr.length != 1 && arr.length != 2) {
console.log("//TODO can currenly only diff with 1 or 2 caches loaded");
return;
}
let [entrya, entryb] = (arr.length == 1 ? [arr[0], arr[0]] : arr);
let chunka = await entrya[1].chunkdata;
let chunkb = await entryb[1].chunkdata;
let cachea = entrya[0];
let cacheb = entryb[0];
let depsa = await cachea.engine.getDependencyGraph();
depsa.insertMapChunk(chunka.chunks[0]);
let depsb = await cacheb.engine.getDependencyGraph();
depsb.insertMapChunk(chunkb.chunks[0]);
let floordepsa = (chunka.chunks.length == 0 ? [] : mapsquareFloorDependencies(chunka.grid, depsa, chunka.chunks[0]));
let locdepsa = mapsquareLocDependencies(chunka.grid, depsa, chunka.modeldata, chunka.rect);
let floordepsb = (chunkb.chunks.length == 0 ? [] : mapsquareFloorDependencies(chunkb.grid, depsb, chunkb.chunks[0]));
let locdepsb = mapsquareLocDependencies(chunkb.grid, depsb, chunkb.modeldata, chunkb.rect);
let floordifs = compareFloorDependencies(floordepsa, floordepsb, floora, floorb);
let locdifs = compareLocDependencies(locdepsa, locdepsb, floora, floorb);
let floordifmesh = await mapdiffmesh(globalThis.sceneCache, floordifs, [255, 0, 0]);
let locdifmesh = await mapdiffmesh(globalThis.sceneCache, locdifs, [0, 255, 0]);
let diffgroup: DiffMesh = {
a: cachea,
b: cacheb,
info: {
floordepsa,
floordepsb,
locdepsa,
locdepsb
},
floora,
floorb,
visible: true,
floormesh: {
getSceneElements() { return { modelnode: (!diffgroup.visible ? undefined : floordifmesh) } }
},
locsmesh: {
getSceneElements() { return { modelnode: (!diffgroup.visible ? undefined : locdifmesh) } }
},
remove: () => {
group.diffs = group.diffs.filter(q => q != diffgroup);
this.props.ctx?.renderer.removeSceneElement(diffgroup.floormesh);
this.props.ctx?.renderer.removeSceneElement(diffgroup.locsmesh);
this.forceUpdate();
}
};
this.props.ctx.renderer.addSceneElement(diffgroup.floormesh);
this.props.ctx.renderer.addSceneElement(diffgroup.locsmesh);
group.diffs.push(diffgroup);
this.forceUpdate();
}
@boundMethod
async meshSelected(e: ThreeJsRendererEvents["select"]) {
this.selectCleanup.forEach(q => q());
@@ -1714,7 +1792,7 @@ export class SceneMapModel extends React.Component<LookupModeProps, SceneMapStat
let newstate: Partial<SceneMapState> = {};
newstate.center = center;
if (!group) {
group = { rect, models: new Map() };
group = { rect, models: new Map(), diffs: [] };
newstate.chunkgroups = [...prevstate.chunkgroups, group];
}
group.models.set(sceneCache, chunk);
@@ -1896,6 +1974,21 @@ export class SceneMapModel extends React.Component<LookupModeProps, SceneMapStat
</div>
))}
{this.state.versions.length > 1 && <input type="button" className="sub-btn" value="Toggle" onClick={this.toggleCache} />}
{this.state.chunkgroups.flatMap((group, groupi) => group.diffs.map((diff, i) => {
let metaa = diff.a.engine.getCacheMeta();
let metab = diff.a == diff.b ? metaa : diff.b.engine.getCacheMeta();
return (
<div key={groupi + "-" + i} style={{ clear: "both" }}>
<label title={diff.a == diff.b ? metaa.descr : `cache a:${metaa.descr}\n\n${metab.descr}`}>
<input type="checkbox" checked={diff.visible} onChange={e => { diff.visible = e.currentTarget.checked; this.props.ctx?.renderer.sceneElementsChanged(); this.forceUpdate(); }} />
{diff.a.engine.getBuildNr()}, floor: {diff.floora}
-
{diff.b.engine.getBuildNr()}, floor: {diff.floorb}
</label>
<input type="button" className="sub-btn" onClick={diff.remove} style={{ float: "right" }} value="x" />
</div>
)
}))}
<JsonDisplay obj={this.state.selectionData} />
</div>
)}