diff --git a/src/3d/mapsquare.ts b/src/3d/mapsquare.ts index 3437ffe..dd600ba 100644 --- a/src/3d/mapsquare.ts +++ b/src/3d/mapsquare.ts @@ -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, sky: { skybox: Object3D, fogColor: number[], skyboxModelid: number } | null, modeldata: Map, 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(); 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 } diff --git a/src/map/chunksummary.ts b/src/map/chunksummary.ts index db51cc8..fd007a9 100644 --- a/src/map/chunksummary.ts +++ b/src/map/chunksummary.ts @@ -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(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 dx = 0; dx < groupsize; dx++) { - for (let dz = 0; dz < groupsize; dz++) { - for (let level = 0; level < grid.levels; level++) { + 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++) { + 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[][]>(); - 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[]); - } + let locgroups = new Map(); + 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,26 +272,27 @@ export function mapsquareLocDependencies(grid: TileGrid, deps: DependencyGraph, return outlocgroups; } +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; + return [ + x0, y0, z0, + x0, y0, z1, + x0, y1, z0, + x0, y1, z1, + x1, y0, z0, + x1, y0, z1, + x1, y1, z0, + x1, y1, z1, + ]; +} + export function compareFloorDependencies(tilesa: ChunkTileDependencies[], tilesb: ChunkTileDependencies[], levela: number, levelb: number) { let vertsets: number[][] = []; - let addtile = (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([ - x0, y0, z0, - x0, y0, z1, - x0, y1, z0, - x0, y1, z1, - x1, y0, z0, - x1, y0, z1, - x1, y1, z0, - x1, y1, z1, - ]); - } 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) { - mismatch = true - } - } + } 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, 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; - rendermeans(rects, res.buckets); - requestAnimationFrame(frame); - } - frame(); + chunka.emit("changed", undefined); + + return chunka; } -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) { diff --git a/src/map/mipper.ts b/src/map/mipper.ts index c265ab3..37158e6 100644 --- a/src/map/mipper.ts +++ b/src/map/mipper.ts @@ -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) { diff --git a/src/opcode_reader.ts b/src/opcode_reader.ts index b3b91b4..9977fd1 100644 --- a/src/opcode_reader.ts +++ b/src/opcode_reader.ts @@ -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; diff --git a/src/scripts/filetypes.ts b/src/scripts/filetypes.ts index bdcf920..30fe536 100644 --- a/src/scripts/filetypes.ts +++ b/src/scripts/filetypes.ts @@ -627,6 +627,7 @@ export const cacheFileJsonModes = constrainedMap()({ 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) }, diff --git a/src/scripts/renderrsinterface.ts b/src/scripts/renderrsinterface.ts index 0fbfa00..7bbf7ce 100644 --- a/src/scripts/renderrsinterface.ts +++ b/src/scripts/renderrsinterface.ts @@ -346,10 +346,10 @@ function spriteCss(spritedata: interfaces["spritedata"] & {}) { } async function spritePromise(ctx: UiRenderContext, spriteid: number) { - let actualid = spriteid & 0xffffff; - let flags = spriteid >> 24; let imgcss = "none"; - if (actualid != -1) { + if (spriteid != -1) { + let actualid = spriteid & 0xffffff; + let flags = spriteid >> 24; if (flags != 0) { console.log("sprite flags", flags); } let spritebuf = await ctx.source.getFileById(cacheMajors.sprites, actualid); let img = expandSprite(parseSprite(spritebuf)[0]); diff --git a/src/viewer/maincomponents.tsx b/src/viewer/maincomponents.tsx index b49f58d..9030fda 100644 --- a/src/viewer/maincomponents.tsx +++ b/src/viewer/maincomponents.tsx @@ -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 ( - {hasElectrion && ( + {electron && (

Native local RS3 cache

Only works when running in electron

)} - {hasElectrion && ( + {electron && (

Jagex Servers

Download directly from content servers. Only works when running in electron

@@ -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 }); } diff --git a/src/viewer/scenenodes.tsx b/src/viewer/scenenodes.tsx index 3634431..a0148af 100644 --- a/src/viewer/scenenodes.tsx +++ b/src/viewer/scenenodes.tsx @@ -1631,7 +1631,8 @@ type SceneMapState = { center: { x: number, z: number }, toggles: Record, selectionData: any, - versions: { cache: ThreejsSceneCache, visible: boolean }[] + versions: { cache: ThreejsSceneCache, visible: boolean }[], + extramodels: boolean }; export class SceneMapModel extends React.Component { selectCleanup: (() => void)[] = []; @@ -1642,7 +1643,8 @@ export class SceneMapModel extends React.Component { let toggles = this.state.toggles; let changed = false; - [...chunk.loaded!.groups].sort((a, b) => a.localeCompare(b)).forEach(q => { + let groups = new Set(); + 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 +

Input format: x,z[,xsize=1,[zsize=xsize]]

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.

diff --git a/src/viewer/scriptsui.tsx b/src/viewer/scriptsui.tsx index fbb97e0..53fded1 100644 --- a/src/viewer/scriptsui.tsx +++ b/src/viewer/scriptsui.tsx @@ -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]); diff --git a/webpack.config.js b/webpack.config.js index b3e9108..15c7ada 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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" }