rewrite some map hashing logic

This commit is contained in:
Skillbert
2024-03-21 16:51:27 +01:00
parent 19b6df1952
commit ad30fcc28b
10 changed files with 189 additions and 140 deletions

View File

@@ -21,6 +21,7 @@ import { CanvasImage } from "../imgutils";
import { minimapFloorMaterial, minimapWaterMaterial } from "../rs3shaders";
import { mapsquare_tiles_nxt } from "../../generated/mapsquare_tiles_nxt";
import { crc32addInt } from "../libs/crc32util";
import { generateFloorHashBoxes, generateLocationHashBoxes } from "../map/chunksummary";
export const tiledimensions = 512;
@@ -912,7 +913,7 @@ export class TileGrid implements TileGridSource {
}
}
export type ParsemapOpts = { padfloor?: boolean, invisibleLayers?: boolean, collision?: boolean, map2d?: boolean, minimap?: boolean, skybox?: boolean, mask?: MapRect[] };
export type ParsemapOpts = { padfloor?: boolean, invisibleLayers?: boolean, collision?: boolean, map2d?: boolean, minimap?: boolean, hashboxes?: boolean, skybox?: boolean, mask?: MapRect[] };
export async function getMapsquareData(engine: EngineCache, chunkx: number, chunkz: number) {
let squareSize = (engine.classicData ? classicChunkSize : rs2ChunkSize);
@@ -1165,7 +1166,6 @@ export type RSMapChunkData = {
grid: TileGrid,
chunk: ChunkData | null,
chunkSize: number,
groups: Set<string>,
sky: { skybox: Object3D, fogColor: number[], skyboxModelid: number } | null,
modeldata: Map<WorldLocation, PlacedMesh[]>,
chunkroot: THREE.Group,
@@ -1211,6 +1211,12 @@ export async function renderMapSquare(cache: ThreejsSceneCache, parsedsquare: Re
let rawboxes = mapsquareCollisionToThree(grid, chunk, level, true);
if (rawboxes) { chunkroot.add(rawboxes); }
}
if (opts.hashboxes) {
for (let level = 0; level < squareLevels; level++) {
chunkroot.add(await generateLocationHashBoxes(cache, locmeshes.byLogical, grid, chunk.mapsquarex, chunk.mapsquarez, level));
chunkroot.add(await generateFloorHashBoxes(cache, grid, chunk, level));
}
}
modeldata = locmeshes.byLogical;
} else {
modeldata = new Map();
@@ -1218,11 +1224,7 @@ export async function renderMapSquare(cache: ThreejsSceneCache, parsedsquare: Re
let sky = (chunk && opts?.skybox ? await mapsquareSkybox(cache, chunk) : null);
let chunkSize = (cache.engine.classicData ? classicChunkSize : rs2ChunkSize);
let groups = new Set<string>();
chunkroot?.traverse(node => {
if (node.userData.modelgroup) {
groups.add(node.userData.modelgroup);
}
if (node instanceof THREE.Mesh) {
let parent: THREE.Object3D | null = node;
let iswireframe = false;
@@ -1239,7 +1241,7 @@ export async function renderMapSquare(cache: ThreejsSceneCache, parsedsquare: Re
}
});
return { chunkx, chunkz, grid, chunk, groups, sky, modeldata, chunkroot, chunkSize, locRenders };
return { chunkx, chunkz, grid, chunk, sky, modeldata, chunkroot, chunkSize, locRenders };
}
type SimpleTexturePackerAlloc = { u: number, v: number, usize: number, vsize: number, x: number, y: number, repeatWidth: number, repeatHeight: number, totalpixels: number, img: CanvasImage }

View File

@@ -1,6 +1,6 @@
import { Box2, BufferAttribute, Group, Matrix4, Vector2, Vector3 } from "three";
import { objects } from "../../generated/objects";
import { ChunkData, getTileHeight, MapRect, ModelExtrasLocation, PlacedMesh, PlacedMeshBase, rs2ChunkSize, tiledimensions, TileGrid, transformVertexPositions, WorldLocation } from "../3d/mapsquare";
import { ChunkData, getTileHeight, MapRect, ModelExtras, ModelExtrasLocation, PlacedMesh, PlacedMeshBase, PlacedModel, rs2ChunkSize, tiledimensions, TileGrid, tileshapes, transformVertexPositions, WorldLocation } from "../3d/mapsquare";
import { ob3ModelToThree, ThreejsSceneCache } from "../3d/modeltothree";
import { ModelBuilder } from "../3d/modelutils";
import { DependencyGraph } from "../scripts/dependencies";
@@ -9,6 +9,7 @@ import { CacheFileSource } from "../cache";
import { RenderedMapMeta } from ".";
import { crc32addInt } from "../libs/crc32util";
import type { RSMapChunk } from "../3d/modelnodes";
import { getOrInsert } from "../utils";
export function getLocImageHash(grid: TileGrid, info: WorldLocation) {
let loc = info.location;
@@ -29,8 +30,7 @@ export function getLocImageHash(grid: TileGrid, info: WorldLocation) {
}
let baseheight = getTileHeight(grid, info.x, info.z, info.plane);
let hash = modelPlacementHash(info, false);
hash = crc32addInt(info.resolvedlocid, hash);
let hash = modelPlacementHash(info);
for (let height of subgrid) {
hash = crc32addInt(height - baseheight, hash);
}
@@ -150,34 +150,39 @@ export function mapsquareFloorDependencies(grid: TileGrid, deps: DependencyGraph
const groupsize = 2;
for (let x = 0; x < chunk.tilerect.xsize; x += groupsize) {
for (let z = 0; z < chunk.tilerect.zsize; z += groupsize) {
let tilehashes = new Array(grid.levels).fill(0);
let tilehashes = new Array<number>(grid.levels).fill(0);
let maxy = 0;
//can't use Set here since we need determinisitic order
let overlays: number[] = [];
let underlays: number[] = [];
for (let level = 0; level < grid.levels; level++) {
let minlevel = level;
for (let dx = 0; dx < groupsize; dx++) {
for (let dz = 0; dz < groupsize; dz++) {
for (let level = 0; level < grid.levels; level++) {
let tilehash = 0;
let tile = grid.getTile(chunk.tilerect.x + x + dx, chunk.tilerect.z + z + dz, level);
// if (!tile) { throw new Error("missing tile"); }
if (!tile || (!tile.underlayVisible && !tile.overlayVisible)) { continue; }
let tilehash = tilehashes[tile.effectiveVisualLevel];
minlevel = Math.min(minlevel, tile.effectiveVisualLevel);
let rawtile = tile.debug_raw;
//TODO make a nxt branch here
if (!rawtile) { throw new Error("can't calculate chunkhash since rawtile isn't set"); }
tilehash = crc32addInt(rawtile.flags, tilehash);//TODO get rid of this one
tilehash = crc32addInt(rawtile.height ?? -1, tilehash);
tilehash = crc32addInt(rawtile.overlay ?? -1, tilehash);
tilehash = crc32addInt(rawtile.settings ?? -1, tilehash);
tilehash = crc32addInt(rawtile.shape ?? -1, tilehash);
tilehash = crc32addInt(rawtile.underlay ?? -1, tilehash);
tilehash = crc32addInt(tile.effectiveVisualLevel, tilehash);
if (rawtile.overlay != null && overlays.indexOf(rawtile.overlay) == -1) { overlays.push(rawtile.overlay); }
if (rawtile.underlay != null && underlays.indexOf(rawtile.underlay) == -1) { underlays.push(rawtile.underlay); }
maxy = Math.max(maxy, tile.y, tile.y01, tile.y10, tile.y11);
tilehashes[tile.effectiveVisualLevel] = tilehash;
for (let i = tile.effectiveVisualLevel; i < grid.levels; i++) {
tilehashes[i] = crc32addInt(tilehash, tilehashes[i]);
}
}
}
}
@@ -207,17 +212,10 @@ export function mapsquareLocDependencies(grid: TileGrid, deps: DependencyGraph,
const v1 = new Vector3();
const v2 = new Vector3();
let locgroups = new Map<number, PlacedMeshBase<ModelExtrasLocation>[][]>();
for (let models of locs.values()) {
let first = models[0];//TODO why only index 0, i forgot
if (first.extras.modeltype == "location") {
let group = locgroups.get(first.extras.locationid);
if (!group) {
group = [];
locgroups.set(first.extras.locationid, group);
}
group.push(models as PlacedMeshBase<ModelExtrasLocation>[]);
}
let locgroups = new Map<number, WorldLocation[]>();
for (let loc of locs.keys()) {
let group = getOrInsert(locgroups, loc.locid, () => []);
group.push(loc);
}
let outlocgroups: ChunkLocDependencies[] = [];
@@ -231,9 +229,10 @@ export function mapsquareLocDependencies(grid: TileGrid, deps: DependencyGraph,
}
outlocgroups.push(outgroup);
for (let loc of group) {
let models = locs.get(loc)!;
v0.set(0, 0, 0);
v1.set(0, 0, 0);
for (let mesh of loc) {
for (let mesh of models) {
let posattr = mesh.model.attributes.pos;
for (let i = 0; i < posattr.count; i++) {
v2.set(posattr.getX(i), posattr.getY(i), posattr.getZ(i));
@@ -252,18 +251,18 @@ export function mapsquareLocDependencies(grid: TileGrid, deps: DependencyGraph,
boxAttribute.setXYZ(6, v1.x, v1.y, v0.z);
boxAttribute.setXYZ(7, v1.x, v1.y, v1.z);
let first = loc[0];
let first = models[0];
let trans = transformVertexPositions(boxAttribute, first.morph, grid, first.maxy, chunkx * rs2ChunkSize * tiledimensions, chunkz * rs2ChunkSize * tiledimensions);
let bounds = [...trans.array as Float32Array].map(v => v | 0);
outgroup.instances.push({
plane: first.extras.locationInstance.plane,
x: first.extras.locationInstance.x,
z: first.extras.locationInstance.z,
rotation: first.extras.locationInstance.rotation,
type: first.extras.locationInstance.type,
plane: loc.plane,
x: loc.x,
z: loc.z,
rotation: loc.rotation,
type: loc.type,
visualLevel: first.extras.locationInstance.visualLevel,
placementhash: modelPlacementHash(first.extras.locationInstance),
visualLevel: loc.visualLevel,
placementhash: modelPlacementHash(loc),
bounds: bounds
});
}
@@ -273,16 +272,14 @@ export function mapsquareLocDependencies(grid: TileGrid, deps: DependencyGraph,
return outlocgroups;
}
export function compareFloorDependencies(tilesa: ChunkTileDependencies[], tilesb: ChunkTileDependencies[], levela: number, levelb: number) {
let vertsets: number[][] = [];
let addtile = (tile: ChunkTileDependencies) => {
function tileSetVertices(tile: ChunkTileDependencies) {
let x0 = tile.x * tiledimensions;
let x1 = x0 + tile.xzsize * tiledimensions;
let z0 = tile.z * tiledimensions;
let z1 = z0 + tile.xzsize * tiledimensions;
let y0 = 0;
let y1 = tile.maxy;
vertsets.push([
return [
x0, y0, z0,
x0, y0, z1,
x0, y1, z0,
@@ -291,8 +288,11 @@ export function compareFloorDependencies(tilesa: ChunkTileDependencies[], tilesb
x1, y0, z1,
x1, y1, z0,
x1, y1, z1,
]);
];
}
export function compareFloorDependencies(tilesa: ChunkTileDependencies[], tilesb: ChunkTileDependencies[], levela: number, levelb: number) {
let vertsets: number[][] = [];
for (let i = 0; i < tilesa.length; i++) {
let tilea = tilesa[i];
let tileb = tilesb[i];
@@ -302,18 +302,12 @@ export function compareFloorDependencies(tilesa: ChunkTileDependencies[], tilesb
mismatch = true;
} else if (tilea.tilehashes.length <= maxfloor || tileb.tilehashes.length <= maxfloor) {
mismatch = true;
} else {
for (let level = 0; level <= maxfloor; level++) {
let hasha = level <= levela ? tilea.tilehashes[level] : 0;
let hashb = level <= levelb ? tileb.tilehashes[level] : 0;
if (hasha != hashb) {
} else if (tilea.tilehashes[levela] != tileb.tilehashes[levelb]) {
mismatch = true
}
}
}
if (mismatch) {
addtile(tilea);
addtile(tileb);
vertsets.push(tileSetVertices(tilea));
vertsets.push(tileSetVertices(tileb));
}
}
return vertsets;
@@ -323,8 +317,9 @@ export function compareLocDependencies(chunka: ChunkLocDependencies[], chunkb: C
let vertsets: number[][] = [];
let iloca = 0, ilocb = 0;
while (true) {
let loca = chunka[iloca];
let locb = chunkb[ilocb];
//explicit bounds check because reading past end is really bad for performance apparently
let loca = (iloca < chunka.length ? chunka[iloca] : undefined);
let locb = (ilocb < chunkb.length ? chunkb[ilocb] : undefined);
if (!loca && !locb) { break }
else if (loca && locb && loca.id == locb.id) {
@@ -371,7 +366,7 @@ export function compareLocDependencies(chunka: ChunkLocDependencies[], chunkb: C
ilocb++;
} else if (!loca || locb && locb.id < loca.id) {
//locb inserted
vertsets.push(...locb.instances.map(q => q.bounds));
vertsets.push(...locb!.instances.map(q => q.bounds));
ilocb++;
} else if (!locb || loca && loca.id < locb.id) {
//locb inserted
@@ -389,8 +384,8 @@ export async function mapdiffmesh(scene: ThreejsSceneCache, points: number[][],
verts.slice(c * 3, c * 3 + 3) as any
);
let models = new Group();
models.position.x -= tiledimensions * rs2ChunkSize / 2;
models.position.z -= tiledimensions * rs2ChunkSize / 2;
models.matrixAutoUpdate = false;
models.updateMatrix();
for (let group of points) {
let model = new ModelBuilder();
//double-sided box through each vertex
@@ -423,6 +418,54 @@ type KMeansBucket = {
};
export async function generateLocationHashBoxes(scene: ThreejsSceneCache, locs: Map<WorldLocation, PlacedMesh[]>, grid: TileGrid, chunkx: number, chunkz: number, level: number) {
let deps = await scene.engine.getDependencyGraph();
await deps.preloadChunkDependencies({ area: { x: chunkx, z: chunkz, xsize: 1, zsize: 1 } });
let locdeps = mapsquareLocDependencies(grid, deps, locs, chunkx, chunkz);
let group = new Group();
for (let loc of locdeps) {
for (let inst of loc.instances) {
if (inst.visualLevel != level) { continue; }
let totalhash = loc.dependencyhash ^ inst.placementhash;
let color = [(totalhash >> 16) & 0xff, (totalhash >> 8) & 0xff, (totalhash >> 0) & 0xff] as [number, number, number];
group.add(await mapdiffmesh(scene, [inst.bounds], color));
}
}
group.userData = {
modeltype: "overlay",
isclickable: false,
modelgroup: "hashbox_objects" + level,
level
} satisfies ModelExtras;
return group;
}
export async function generateFloorHashBoxes(scene: ThreejsSceneCache, grid: TileGrid, chunk: ChunkData, level: number) {
let deps = await scene.engine.getDependencyGraph();
await deps.preloadChunkDependencies({ area: { x: chunk.mapsquarex, z: chunk.mapsquarez, xsize: 1, zsize: 1 } });
let floordeps = mapsquareFloorDependencies(grid, deps, chunk);
let group = new Group();
for (let dep of floordeps) {
let totalhash = 0;
totalhash = crc32addInt(dep.dephash, totalhash);
totalhash = crc32addInt(dep.tilehashes[level], totalhash);
let color = [(totalhash >> 16) & 0xff, (totalhash >> 8) & 0xff, (totalhash >> 0) & 0xff] as [number, number, number];
let verts = tileSetVertices(dep);
group.add(await mapdiffmesh(scene, [verts], color));
}
group.userData = {
modeltype: "overlay",
isclickable: false,
modelgroup: "hashbox_floor" + level,
level
} satisfies ModelExtras
return group;
}
/**
* this class is wayyy overkill for what is currently used
*/
@@ -647,64 +690,39 @@ function rendermeans(rects: Box2[], buckets: KMeansBucket[]) {
}
}
//TODO remove
globalThis.lochash = mapsquareLocDependencies;
globalThis.floorhash = mapsquareFloorDependencies;
globalThis.comparehash = compareLocDependencies;
globalThis.diffmodel = mapdiffmesh;
globalThis.test = async (low: number, high: number) => {
globalThis.deps ??= await globalThis.getdeps(globalThis.engine, { area: { x: 49, z: 49, xsize: 3, zsize: 3 } });
globalThis.test = async (chunka: RSMapChunk, levela: number, levelb = 0, chunkb = chunka) => {
let depsa = await chunka.cache.engine.getDependencyGraph();
let depsb = await chunkb.cache.engine.getDependencyGraph();
await depsa.preloadChunkDependencies({ area: { x: chunka.chunkx, z: chunka.chunkz, xsize: 1, zsize: 1 } });
await depsb.preloadChunkDependencies({ area: { x: chunkb.chunkx, z: chunkb.chunkz, xsize: 1, zsize: 1 } });
await chunka.chunkdata;
await chunkb.chunkdata;
if (!chunka.loaded || !chunkb.loaded) { return; }
let chunk: RSMapChunk = globalThis.chunk;
if (!chunk.loaded) { return; }
let locdeps = mapsquareLocDependencies(chunk.loaded.grid, globalThis.deps, chunk.loaded.modeldata, chunk.loaded.chunkx, chunk.loaded.chunkz);
let locdifs = compareLocDependencies(locdeps, locdeps, low, high);
let locdifmesh = await mapdiffmesh(globalThis.sceneCache, locdifs);
globalThis.render.modelnode.add(locdifmesh);
let locsa = mapsquareLocDependencies(chunka.loaded.grid, depsa, chunka.loaded.modeldata, chunka.chunkx, chunka.chunkz);
let locsb = mapsquareLocDependencies(chunkb.loaded.grid, depsb, chunkb.loaded.modeldata, chunkb.chunkx, chunkb.chunkz);
let floordeps = mapsquareFloorDependencies(globalThis.chunk.loaded.grid, globalThis.deps, globalThis.chunk.loaded.chunks[0]);
let floordifs = compareFloorDependencies(floordeps, floordeps, low, high);
let floordifmesh = await mapdiffmesh(globalThis.sceneCache, floordifs);
globalThis.render.modelnode.add(floordifmesh);
let cmplocs = compareLocDependencies(locsa, locsb, levela, levelb);
let cmplocsmesh = await mapdiffmesh(chunka.cache, cmplocs);
chunka.rootnode.children[0].add(cmplocsmesh);
cmplocsmesh.userData = { modeltype: "overlay", isclickable: false, modelgroup: `cmplocs_${levela}_${levelb}`, level: levela } satisfies ModelExtras;
let frame = () => {
let floorproj = globalThis.render.camera.projectionMatrix.clone().multiply(globalThis.render.camera.matrixWorldInverse).multiply(floordifmesh.matrixWorld);
let locproj = globalThis.render.camera.projectionMatrix.clone().multiply(globalThis.render.camera.matrixWorldInverse).multiply(locdifmesh.matrixWorld);
let floora = mapsquareFloorDependencies(chunka.loaded.grid, depsa, chunka.loaded.chunk!);
let floorb = mapsquareFloorDependencies(chunkb.loaded.grid, depsb, chunkb.loaded.chunk!);
let grid = new ImageDiffGrid();
grid.addPolygons(floorproj, floordifs);
grid.addPolygons(locproj, locdifs);
// let { grid, gridsize, buckets } = calculateDiffArea(locproj, locdifs);
let res = grid.calculateDiffArea(512, 512);
//TODO remove
let rects: Box2[] = [];
for (let i = 0; i < grid.grid.length; i++) {
if (grid.grid[i]) {
let x = i % grid.gridsize;
let y = Math.floor(i / grid.gridsize);
rects.push(new Box2(new Vector2(
-1 + 2 * x / grid.gridsize,
-1 + 2 * y / grid.gridsize
), new Vector2(
-1 + 2 * (x + 1) / grid.gridsize,
-1 + 2 * (y + 1) / grid.gridsize
)));
}
let cmpfloor = compareFloorDependencies(floora, floorb, levela, levelb);
let cmpfloormesh = await mapdiffmesh(chunka.cache, cmpfloor);
chunka.rootnode.children[0].add(cmpfloormesh);
cmpfloormesh.userData = { modeltype: "overlay", isclickable: false, modelgroup: `cmpfloor_${levela}_${levelb}`, level: levela } satisfies ModelExtras;
chunka.emit("changed", undefined);
return chunka;
}
rendermeans(rects, res.buckets);
requestAnimationFrame(frame);
}
frame();
}
function modelPlacementHash(loc: WorldLocation, includeposition = true) {
function modelPlacementHash(loc: WorldLocation) {
let hash = 0;
if (includeposition) {
hash = crc32addInt(loc.x, hash);
hash = crc32addInt(loc.z, hash);
hash = crc32addInt(loc.plane, hash);
}
hash = crc32addInt(loc.resolvedlocid, hash);
hash = crc32addInt(loc.rotation, hash);
hash = crc32addInt(loc.type, hash);
if (loc.placement) {

View File

@@ -138,6 +138,9 @@ async function mipCanvas(render: MapRender, files: (UniqueMapFile | null)[], for
if (!f) { return null; }
let res = await render.getFileResponse(f.name);
let mimetype = res.headers.get("content-type");
let hashheader = res.headers.get("x-amz-meta-mapfile-hash");
if (typeof hashheader == "string" && +hashheader != f.hash) { throw new Error("hash mismatch while creating mip file"); }
let outx = (i % 2) * subtilesize;
let outy = Math.floor(i / 2) * subtilesize;
if (avgfilter) {

View File

@@ -641,6 +641,7 @@ function bufferParser(args: unknown[], parent: ChunkParentCallback, typedef: Typ
let len = lengthtype.read(state);
let bytelen = len * vectorLength * type.constr.BYTES_PER_ELEMENT;
let backing = new ArrayBuffer(bytelen);
if (state.scan + bytelen > state.endoffset) { throw new Error("trying to read outside buffer bounds"); }
let bytes = Buffer.from(backing);
bytes.set(state.buffer.subarray(state.scan, state.scan + bytelen));
state.scan += bytelen;

View File

@@ -627,6 +627,7 @@ export const cacheFileJsonModes = constrainedMap<JsonBasedFile>()({
environments: { parser: parse.environments, lookup: singleMinorIndex(cacheMajors.config, cacheConfigPages.environments) },
animgroupconfigs: { parser: parse.animgroupConfigs, lookup: singleMinorIndex(cacheMajors.config, cacheConfigPages.animgroups) },
maplabels: { parser: parse.maplabels, lookup: singleMinorIndex(cacheMajors.config, cacheConfigPages.maplabels) },
mapzones: { parser: parse.mapZones, lookup: singleMinorIndex(cacheMajors.worldmap, 0) },
cutscenes: { parser: parse.cutscenes, lookup: noArchiveIndex(cacheMajors.cutscenes) },
particles0: { parser: parse.particles_0, lookup: singleMinorIndex(cacheMajors.particles, 0) },

View File

@@ -346,10 +346,10 @@ function spriteCss(spritedata: interfaces["spritedata"] & {}) {
}
async function spritePromise(ctx: UiRenderContext, spriteid: number) {
let imgcss = "none";
if (spriteid != -1) {
let actualid = spriteid & 0xffffff;
let flags = spriteid >> 24;
let imgcss = "none";
if (actualid != -1) {
if (flags != 0) { console.log("sprite flags", flags); }
let spritebuf = await ctx.source.getFileById(cacheMajors.sprites, actualid);
let img = expandSprite(parseSprite(spritebuf)[0]);

View File

@@ -23,9 +23,17 @@ import { drawTexture } from "../imgutils";
import { RsUIViewer } from "./rsuiviewer";
import { ClientScriptViewer } from "./cs2viewer";
//work around typescript being weird when compiling for browser
const electron = require("electron/renderer") as typeof import("electron/renderer");
const hasElectrion = !!electron.ipcRenderer;
//see if we have access to a valid electron import
let electron: typeof import("electron/renderer") | null = (() => {
try {
let electron = require("electron/renderer");
//some enviroments polyfill an empty mock object, this also catches when electron is imported from a main process and exports only a string
if (electron?.ipcRenderer) {
return electron;
}
} catch (e) { }
return null;
})();
export type SavedCacheSource = {
type: string
@@ -57,7 +65,7 @@ export async function downloadBlob(name: string, blob: Blob) {
/**@deprecated requires a service worker and is pretty sketchy, also no actual streaming output file sources atm */
export async function downloadStream(name: string, stream: ReadableStream) {
if (!hasElectrion) {
if (!electron) {
let url = new URL(`download_${Math.random() * 10000 | 0}_${name}`, document.location.href).href;
let sw = await navigator.serviceWorker.ready;
if (!sw.active) { throw new Error("no service worker"); }
@@ -208,7 +216,7 @@ export class CacheSelector extends React.Component<{ onOpen: (c: SavedCacheSourc
@boundMethod
async clickOpenNative() {
if (!hasElectrion) { return; }
if (!electron) { return; }
let dir: import("electron").OpenDialogReturnValue = await electron.ipcRenderer.invoke("openfolder", path.resolve(process.env.ProgramData!, "jagex/runescape"));
if (!dir.canceled) {
this.props.onOpen({ type: "autofs", location: dir.filePaths[0], writable: !!globalThis.writecache });//TODO propper ui for this
@@ -278,14 +286,14 @@ export class CacheSelector extends React.Component<{ onOpen: (c: SavedCacheSourc
render() {
return (
<React.Fragment>
{hasElectrion && (
{electron && (
<React.Fragment>
<h2>Native local RS3 cache</h2>
<p>Only works when running in electron</p>
<input type="button" className="sub-btn" onClick={this.clickOpenNative} value="Open native cache" />
</React.Fragment>
)}
{hasElectrion && (
{electron && (
<React.Fragment>
<h2>Jagex Servers</h2>
<p>Download directly from content servers. Only works when running in electron</p>
@@ -413,7 +421,7 @@ export async function openSavedCache(source: SavedCacheSource, remember: boolean
if (source.type == "openrs2") {
cache = await Openrs2CacheSource.fromId(+source.cachename);
}
if (hasElectrion && source.type == "autofs") {
if (electron && source.type == "autofs") {
let fs = new CLIScriptFS(source.location);
cache = await selectFsCache(fs, { writable: source.writable });
}

View File

@@ -1631,7 +1631,8 @@ type SceneMapState = {
center: { x: number, z: number },
toggles: Record<string, boolean>,
selectionData: any,
versions: { cache: ThreejsSceneCache, visible: boolean }[]
versions: { cache: ThreejsSceneCache, visible: boolean }[],
extramodels: boolean
};
export class SceneMapModel extends React.Component<LookupModeProps, SceneMapState> {
selectCleanup: (() => void)[] = [];
@@ -1642,7 +1643,8 @@ export class SceneMapModel extends React.Component<LookupModeProps, SceneMapStat
center: { x: 0, z: 0 },
toggles: Object.create(null),
selectionData: undefined,
versions: []
versions: [],
extramodels: false
}
}
@@ -1782,11 +1784,17 @@ export class SceneMapModel extends React.Component<LookupModeProps, SceneMapStat
const renderer = this.props.ctx?.renderer;
if (!sceneCache || !renderer) { return; }
let chunk = RSMapChunk.create(sceneCache, chunkx, chunkz, { skybox: true });
let chunk = RSMapChunk.create(sceneCache, chunkx, chunkz, { skybox: true, map2d: this.state.extramodels, hashboxes: this.state.extramodels, minimap: this.state.extramodels });
chunk.on("changed", () => {
let toggles = this.state.toggles;
let changed = false;
[...chunk.loaded!.groups].sort((a, b) => a.localeCompare(b)).forEach(q => {
let groups = new Set<string>();
chunk.rootnode.traverse(node => {
if (node.userData.modelgroup) {
groups.add(node.userData.modelgroup);
}
});
[...groups].sort((a, b) => a.localeCompare(b)).forEach(q => {
if (typeof toggles[q] != "boolean") {
toggles[q] = !!q.match(/^(floor|objects)\d+/);
// toggles[q] = !!q.match(/^mini_(floor|objects)0/);
@@ -1952,6 +1960,7 @@ export class SceneMapModel extends React.Component<LookupModeProps, SceneMapStat
{this.state.chunkgroups.length == 0 && (
<React.Fragment>
<StringInput onChange={this.onSubmit} initialid={initid} />
<label><input type="checkbox" checked={this.state.extramodels} onChange={e => this.setState({ extramodels: e.currentTarget.checked })} />Load extra modes</label>
<p>Input format: x,z[,xsize=1,[zsize=xsize]]</p>
<p>Coordinates are in so-called mapsquare coordinates, each mapsquare is 64x64 tiles in size. The entire RuneScape map is laid out in one plane and is 100x200 mapsquares in size.</p>
</React.Fragment>

View File

@@ -8,8 +8,16 @@ import VR360Viewer from "../libs/vr360viewer";
import { CLIScriptFS, ScriptFS, ScriptOutput, ScriptState } from "../scriptrunner";
import path from "path";
//work around typescript being weird when compiling for browser
const electron = require("electron/renderer");
//see if we have access to a valid electron import
let electron: typeof import("electron/renderer") | null = (() => {
try {
let electron = require("electron/renderer");
if (electron?.ipcRenderer) {
return electron;
}
} catch (e) { }
return null;
})();
export type UIScriptFile = { name: string, kind: "file", data: Buffer | string }
@@ -519,7 +527,7 @@ export function UIScriptFiles(p: { fs?: UIScriptFS | null, ctx: UIContext }) {
let clicksave = async () => {
if (!p.fs) { return; }
let subfs: ScriptFS;
if (!!electron.ipcRenderer) {
if (electron) {
let dir: Electron.OpenDialogReturnValue = await electron.ipcRenderer.invoke("openfolder", path.resolve(process.env.HOME!, "downloads"));
if (dir.canceled || !dir.filePaths[0]) { return; }
subfs = new CLIScriptFS(dir.filePaths[0]);

View File

@@ -46,7 +46,6 @@ module.exports = {
"sharp": { commonjs: "sharp" },
"zlib": { commonjs: "zlib" },
"lzma": { commonjs: "lzma" },
"cmd-ts": { commonjs: "cmd-ts" },
"comment-json": { commonjs: "comment-json" },
"gl": { commonjs: "gl" },
"canvas": { commonjs: "canvas" }