mirror of
https://github.com/skillbert/rsmv.git
synced 2025-12-23 21:47:48 -05:00
map render dedupe fixes
This commit is contained in:
@@ -18,6 +18,7 @@ import { MaterialData } from "./jmat";
|
||||
import { legacyMajors } from "../cache/legacycache";
|
||||
import { classicGroups } from "../cache/classicloader";
|
||||
import { mapImageCamera } from "../map";
|
||||
import { findImageBounds, pixelsToImageFile, sliceImage } from "../imgutils";
|
||||
|
||||
|
||||
export type SimpleModelDef = {
|
||||
@@ -404,6 +405,7 @@ export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData, changed:
|
||||
toggles: Record<string, boolean> = {};
|
||||
chunkx: number;
|
||||
chunkz: number;
|
||||
globalname = "";
|
||||
|
||||
constructor(cache: ThreejsSceneCache, preparsed: ReturnType<typeof parseMapsquare>, chunkx: number, chunkz: number, opts: ParsemapOpts) {
|
||||
super();
|
||||
@@ -441,8 +443,9 @@ export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData, changed:
|
||||
group.traverse(q => q.layers.set(1));
|
||||
this.loaded.chunkroot.add(group);
|
||||
|
||||
let cam = mapImageCamera(loc.x + this.rootnode.position.x / tiledimensions - 16, loc.z + this.rootnode.position.z / tiledimensions - 16, 32, 0.15, 0.25);
|
||||
let img = await this.renderscene!.takeMapPicture(cam, -1, -1, false, group);
|
||||
// let cam = mapImageCamera(loc.x + this.rootnode.position.x / tiledimensions - 16, loc.z + this.rootnode.position.z / tiledimensions - 16, 32, 0.15, 0.25);
|
||||
let cam = this.renderscene!.getCurrent2dCamera()!;
|
||||
let img = await this.renderscene!.takeMapPicture(cam, 256, 256, false, group);
|
||||
group.removeFromParent();
|
||||
model.map(q => q.mesh.setSectionHide(q, false));
|
||||
return img;
|
||||
@@ -466,8 +469,10 @@ export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData, changed:
|
||||
|
||||
cleanup() {
|
||||
this.listeners = {};
|
||||
|
||||
delete globalThis[`chunk_${this.chunkx}_${this.chunkz}`];
|
||||
if (this.globalname) {
|
||||
delete globalThis[this.globalname];
|
||||
this.globalname = "";
|
||||
}
|
||||
//only clear vertex memory for now, materials might be reused and are up to the scenecache
|
||||
this.chunkdata.then(q => q.chunkroot.traverse(obj => {
|
||||
if (obj instanceof Mesh) { obj.geometry.dispose(); }
|
||||
@@ -494,7 +499,14 @@ export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData, changed:
|
||||
this.renderscene = scene;
|
||||
scene.addSceneElement(this);
|
||||
|
||||
globalThis[`chunk_${this.chunkx}_${this.chunkz}`] = this;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let name = `chunk_${this.chunkx}_${this.chunkz}${i == 0 ? "" : `_${i}`}`;
|
||||
if (!globalThis[name]) {
|
||||
globalThis[name] = this;
|
||||
this.globalname = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onModelLoaded() {
|
||||
|
||||
3
src/cache/openrs2loader.ts
vendored
3
src/cache/openrs2loader.ts
vendored
@@ -76,9 +76,8 @@ export function validOpenrs2Caches() {
|
||||
1480,
|
||||
|
||||
644,257,//incomplete textures
|
||||
1456,//missing materials
|
||||
1456,1665,//missing materials
|
||||
1479,//missing items could probably be worked around
|
||||
//254,255,667,256,257,1479,3
|
||||
];
|
||||
let allcaches: Openrs2CacheMeta[] = await fetch(`${endpoint}/caches.json`).then(q => q.json());
|
||||
let checkedcaches = allcaches.filter(q =>
|
||||
|
||||
@@ -151,7 +151,7 @@ export async function renderAppearance(scene: ThreejsSceneCache, mode: "player"
|
||||
render.setCameraLimits(new Vector3(0, 0.85, 0));
|
||||
|
||||
let modelfile = Buffer.from(await exportThreeJsGltf(render.getModelNode()));
|
||||
let img = await render.takeCanvasPicture();
|
||||
let img = await render.takeScenePicture();
|
||||
let imgfile = await pixelsToImageFile(img, "png", 1);
|
||||
|
||||
render.dispose();
|
||||
|
||||
@@ -10,6 +10,7 @@ import { RenderedMapMeta } from ".";
|
||||
import { crc32addInt } from "../libs/crc32util";
|
||||
import type { RSMapChunk } from "../3d/modelnodes";
|
||||
import { getOrInsert } from "../utils";
|
||||
import { ThreeJsRenderer } from "../viewer/threejsrender";
|
||||
|
||||
export function getLocImageHash(grid: TileGrid, info: WorldLocation) {
|
||||
let loc = info.location;
|
||||
@@ -465,6 +466,29 @@ export async function generateFloorHashBoxes(scene: ThreejsSceneCache, grid: Til
|
||||
return group;
|
||||
}
|
||||
|
||||
export function pointsIntersectProjection(projection: Matrix4, points: number[][]) {
|
||||
//make them local vars to prevent writing into old space
|
||||
const min = new Vector3();
|
||||
const max = new Vector3();
|
||||
const tmp = new Vector3();
|
||||
for (let group of points) {
|
||||
for (let i = 0; i < group.length; i += 3) {
|
||||
tmp.set(group[i + 0], group[i + 1], group[i + 2]);
|
||||
tmp.applyMatrix4(projection);
|
||||
if (i == 0) {
|
||||
min.copy(tmp);
|
||||
max.copy(tmp);
|
||||
} else {
|
||||
min.min(tmp);
|
||||
max.max(tmp);
|
||||
}
|
||||
}
|
||||
if (min.x < 1 && max.x > -1 && min.y < 1 && max.y > -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* this class is wayyy overkill for what is currently used
|
||||
@@ -473,30 +497,6 @@ export class ImageDiffGrid {
|
||||
gridsize = 64;
|
||||
grid = new Uint8Array(this.gridsize * this.gridsize);
|
||||
|
||||
anyInside(projection: Matrix4, points: number[][]) {
|
||||
//make them local vars to prevent writing into old space
|
||||
const min = new Vector3();
|
||||
const max = new Vector3();
|
||||
const tmp = new Vector3();
|
||||
for (let group of points) {
|
||||
for (let i = 0; i < group.length; i += 3) {
|
||||
tmp.set(group[i + 0], group[i + 1], group[i + 2]);
|
||||
tmp.applyMatrix4(projection);
|
||||
if (i == 0) {
|
||||
min.copy(tmp);
|
||||
max.copy(tmp);
|
||||
} else {
|
||||
min.min(tmp);
|
||||
max.max(tmp);
|
||||
}
|
||||
}
|
||||
if (min.x < 1 && max.x > -1 && min.y < 1 && max.y > -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addPolygons(projection: Matrix4, points: number[][]) {
|
||||
const v0 = new Vector3();
|
||||
const v1 = new Vector3();
|
||||
@@ -717,7 +717,28 @@ globalThis.test = async (chunka: RSMapChunk, levela: number, levelb = 0, chunkb
|
||||
|
||||
chunka.emit("changed", undefined);
|
||||
|
||||
return chunka;
|
||||
|
||||
return () => {
|
||||
let render = globalThis.render as ThreeJsRenderer;
|
||||
let cam = render.getCurrent2dCamera();
|
||||
if (!cam) { return; }
|
||||
|
||||
chunka.rootnode.updateWorldMatrix(true, false);
|
||||
let modelmatrix = new Matrix4().makeTranslation(
|
||||
chunka.chunkx * tiledimensions * chunka.loaded!.chunkSize,
|
||||
0,
|
||||
chunka.chunkz * tiledimensions * chunka.loaded!.chunkSize,
|
||||
).premultiply(chunka.rootnode.matrixWorld);
|
||||
|
||||
let proj = cam.projectionMatrix.clone()
|
||||
.multiply(cam.matrixWorldInverse)
|
||||
.multiply(modelmatrix);
|
||||
|
||||
let locschanged = pointsIntersectProjection(proj, cmplocs);
|
||||
let floorchanged = pointsIntersectProjection(proj, cmpfloor);
|
||||
let anychanged = locschanged || floorchanged;
|
||||
return { locschanged, floorchanged, anychanged };
|
||||
}
|
||||
}
|
||||
|
||||
function modelPlacementHash(loc: WorldLocation) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ScriptOutput } from "../scriptrunner";
|
||||
import { AsyncReturnType, CallbackPromise, delay, stringToFileRange, trickleTasks } from "../utils";
|
||||
import { drawCollision } from "./collisionimage";
|
||||
import prettyJson from "json-stringify-pretty-compact";
|
||||
import { ChunkRenderMeta, chunkSummary, compareFloorDependencies, compareLocDependencies, ImageDiffGrid, mapsquareFloorDependencies, mapsquareLocDependencies, RenderDepsTracker, RenderDepsVersionInstance } from "./chunksummary";
|
||||
import { ChunkRenderMeta, chunkSummary, compareFloorDependencies, compareLocDependencies, mapsquareFloorDependencies, mapsquareLocDependencies, pointsIntersectProjection, RenderDepsTracker, RenderDepsVersionInstance } from "./chunksummary";
|
||||
import { RSMapChunk } from "../3d/modelnodes";
|
||||
import * as zlib from "zlib";
|
||||
import { Camera, Matrix4, Object3D, OrthographicCamera, Vector3 } from "three";
|
||||
@@ -708,7 +708,7 @@ const rendermodeInteractions: RenderMode<"interactions"> = function (engine, con
|
||||
let rect = { x: singlerect.x * loaded.chunkSize, z: singlerect.z * loaded.chunkSize, xsize: loaded.chunkSize, zsize: loaded.chunkSize };
|
||||
let { hashes, locdatas, locs } = chunkSummary(loaded.grid, loaded.modeldata, rect);
|
||||
let emptyimagecount = 0;
|
||||
let hashimgs: Record<number, { img: string, center: number[], loc: number, dx: number, dy: number }> = {};
|
||||
let hashimgs: Record<number, { img: string, center: number[], loc: number, dx: number, dy: number, w: number, h: number }> = {};
|
||||
for (let [hash, { center, locdata }] of hashes) {
|
||||
let ops = [locdata.location.actions_0, locdata.location.actions_1, locdata.location.actions_2, locdata.location.actions_3, locdata.location.actions_4].filter((q): q is string => !!q);
|
||||
let model = loaded.locRenders.get(locdata);
|
||||
@@ -728,7 +728,6 @@ const rendermodeInteractions: RenderMode<"interactions"> = function (engine, con
|
||||
let ypos = baseheight / tiledimensions + center[1];
|
||||
let cam = mapImageCamera(locdata.x + center[0] + ypos * thiscnf.dxdy - ntiles / 2, locdata.z + center[2] + ypos * thiscnf.dzdy - ntiles / 2, ntiles, thiscnf.dxdy, thiscnf.dzdy);
|
||||
let img = await renderer.renderer.takeMapPicture(cam, ntiles * thiscnf.pxpersquare, ntiles * thiscnf.pxpersquare, false, group);
|
||||
flipImage(img);
|
||||
group.removeFromParent();
|
||||
|
||||
model.map(q => q.mesh.setSectionHide(q, false));
|
||||
@@ -746,6 +745,8 @@ const rendermodeInteractions: RenderMode<"interactions"> = function (engine, con
|
||||
loc: locdata.locid,
|
||||
dx: bounds.x - img.width / 2,
|
||||
dy: bounds.y - img.height / 2,
|
||||
w: bounds.width,
|
||||
h: bounds.height,
|
||||
center,
|
||||
img: `data:image/${format};base64,${imgfile.toString("base64")}`
|
||||
};
|
||||
@@ -804,7 +805,6 @@ const rendermode3d: RenderMode<"3d" | "minimap"> = function (engine, config, cnf
|
||||
|
||||
findparent: for (let parentoption of parentCandidates) {
|
||||
optloop: for (let versionMatch of await parentinfo.findMatches(this.datarect, parentoption.name)) {
|
||||
let diff = new ImageDiffGrid();
|
||||
let isdirty = false;
|
||||
for (let chunk of chunks) {
|
||||
let other = versionMatch.metas.find(q => q.x == chunk.x && q.z == chunk.z);
|
||||
@@ -827,8 +827,8 @@ const rendermode3d: RenderMode<"3d" | "minimap"> = function (engine, config, cnf
|
||||
// if (locs.length + floor.length > 400) {
|
||||
// continue optloop;
|
||||
// }
|
||||
isdirty ||= diff.anyInside(proj, locs);
|
||||
isdirty ||= diff.anyInside(proj, floor);
|
||||
isdirty ||= pointsIntersectProjection(proj, locs);
|
||||
isdirty ||= pointsIntersectProjection(proj, floor);
|
||||
if (isdirty) {
|
||||
break;
|
||||
}
|
||||
@@ -852,7 +852,6 @@ const rendermode3d: RenderMode<"3d" | "minimap"> = function (engine, config, cnf
|
||||
let img: ImageData | null = null;
|
||||
if (!parentFile) {
|
||||
img = await renderer.renderer.takeMapPicture(cam, tiles * pxpersquare, tiles * pxpersquare, thiscnf.mode == "minimap");
|
||||
flipImage(img);
|
||||
// isImageEmpty(img, "black");
|
||||
|
||||
//keep reference to dedupe similar renders
|
||||
|
||||
@@ -1062,7 +1062,7 @@ function ExportSceneMenu(p: { ctx: UIContextReady, renderopts: ThreeJsSceneEleme
|
||||
let changeImg = async (instCrop = cropimg, instSize = imgsize) => {
|
||||
if (p.renderopts!.camMode == "vr360") { instCrop = false; }
|
||||
|
||||
let newpixels = await p.ctx.renderer.takeCanvasPicture(instSize.w || undefined, instSize.h || undefined);
|
||||
let newpixels = await p.ctx.renderer.takeScenePicture(instSize.w || undefined, instSize.h || undefined);
|
||||
let newimg = makeImageData(newpixels.data, newpixels.width, newpixels.height);
|
||||
let cnv = document.createElement("canvas");
|
||||
let ctx = cnv.getContext("2d")!;
|
||||
|
||||
@@ -326,7 +326,7 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
async guaranteeGlCalls(glfunction: () => void | Promise<void>) {
|
||||
async guaranteeGlCalls<T>(glfunction: () => T | Promise<T>): Promise<T> {
|
||||
let waitContext = () => {
|
||||
if (!this.renderer.getContext().isContextLost()) {
|
||||
return;
|
||||
@@ -345,7 +345,7 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
await waitContext();
|
||||
//it seems like the first render after a context loss is always failed, force 2 renders this way
|
||||
let prerenderlosses = this.contextLossCountLastRender;
|
||||
await glfunction();
|
||||
let res = await glfunction();
|
||||
|
||||
//new stack frame to let all errors resolve
|
||||
await delay(1);
|
||||
@@ -356,7 +356,7 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
console.log("lost and regained context during render " + new Date());
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
return res;
|
||||
}
|
||||
throw new Error("Failed to render frame after 5 retries");
|
||||
}
|
||||
@@ -435,7 +435,7 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
}
|
||||
}
|
||||
|
||||
async takeCanvasPicture(width = this.canvas.width, height = this.canvas.height) {
|
||||
async takeScenePicture(width = this.canvas.width, height = this.canvas.height) {
|
||||
let rendertarget: THREE.WebGLRenderTarget | null = null;
|
||||
if (width != this.canvas.width || height != this.canvas.height) {
|
||||
let gl = this.renderer.getContext();
|
||||
@@ -448,7 +448,7 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
});
|
||||
// (rendertarget as any).isXRRenderTarget = true;
|
||||
}
|
||||
await this.guaranteeGlCalls(() => {
|
||||
return this.guaranteeGlCalls(() => {
|
||||
let oldtarget = this.renderer.getRenderTarget();
|
||||
this.renderer.setRenderTarget(rendertarget);
|
||||
this.resizeViewToRendererSize();
|
||||
@@ -465,7 +465,14 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
}
|
||||
this.renderer.setRenderTarget(oldtarget);
|
||||
this.resizeViewToRendererSize();
|
||||
return this.getFrameBufferPixels();
|
||||
});
|
||||
}
|
||||
|
||||
getFrameBufferPixels() {
|
||||
let rendertarget = this.renderer.getRenderTarget();
|
||||
let width = rendertarget?.width ?? this.canvas.width;
|
||||
let height = rendertarget?.height ?? this.canvas.height;
|
||||
let buf = new Uint8Array(width * height * 4);//node-gl doesn't accept clamped
|
||||
if (rendertarget) {
|
||||
this.renderer.readRenderTargetPixels(rendertarget as any, 0, 0, width, height, buf);
|
||||
@@ -479,12 +486,11 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
return r;
|
||||
}
|
||||
|
||||
async takeMapPicture(cam: Camera, framesizex = -1, framesizey = -1, linearcolor = false, highlight: Object3D | null = null) {
|
||||
takeMapPicture(cam: Camera, framesizex = -1, framesizey = -1, linearcolor = false, highlight: Object3D | null = null) {
|
||||
if (framesizex != -1 && framesizey != -1) {
|
||||
this.renderer.setSize(framesizex, framesizey);
|
||||
}
|
||||
let img: ImageData | null = null;
|
||||
await this.guaranteeGlCalls(() => {
|
||||
return this.guaranteeGlCalls(() => {
|
||||
let opaqueBackground = !highlight;
|
||||
//change render settings
|
||||
let oldcolorspace = this.renderer.outputColorSpace;
|
||||
@@ -492,7 +498,6 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
this.renderer.setClearColor(new THREE.Color(0, 0, 0), (opaqueBackground ? 255 : 0));
|
||||
this.scene.background = (opaqueBackground ? new THREE.Color(0, 0, 0) : null);
|
||||
|
||||
let ctx = this.renderer.getContext();
|
||||
if (!highlight) {
|
||||
this.renderScene(cam);
|
||||
} else {
|
||||
@@ -504,16 +509,15 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
cam.layers.mask = old;
|
||||
}
|
||||
|
||||
let pixelbuffer = new Uint8ClampedArray(ctx.canvas.width * ctx.canvas.height * 4);
|
||||
ctx.readPixels(0, 0, ctx.canvas.width, ctx.canvas.height, ctx.RGBA, ctx.UNSIGNED_BYTE, pixelbuffer);
|
||||
img = makeImageData(pixelbuffer, ctx.canvas.width, ctx.canvas.height);
|
||||
let img = this.getFrameBufferPixels();
|
||||
|
||||
//restore render settings
|
||||
this.renderer.outputColorSpace = oldcolorspace;
|
||||
this.renderer.setClearColor(new THREE.Color(0, 0, 0), 0);
|
||||
this.scene.background = null;
|
||||
|
||||
return img;
|
||||
});
|
||||
return img!;
|
||||
}
|
||||
|
||||
setCameraPosition(pos: Vector3) {
|
||||
@@ -697,13 +701,10 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
|
||||
this.renderer.clearColor();
|
||||
this.renderer.clearDepth();
|
||||
this.renderer.render(scene, itemcam);
|
||||
this.renderer.setRenderTarget(oldtarget);
|
||||
let img = this.getFrameBufferPixels()
|
||||
|
||||
let buf = new Uint8Array(width * height * 4);//node-gl doesn't accept clamped
|
||||
this.renderer.readRenderTargetPixels(rendertarget, 0, 0, width, height, buf);
|
||||
let r = makeImageData(buf, width, height);
|
||||
flipImage(r);
|
||||
return r;
|
||||
this.renderer.setRenderTarget(oldtarget);
|
||||
return img;
|
||||
}
|
||||
let dispose = () => rendertarget?.dispose();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user