decode maplabel and more 3d minimap rendering

This commit is contained in:
Skillbert
2023-07-02 13:10:34 +02:00
parent 7f5cf205a5
commit 08d5a5b548
11 changed files with 164 additions and 56 deletions

View File

@@ -169,6 +169,11 @@ export type items = {
neverStackable?: true | null
unknown_A7?: true | null
unknown_A8?: true | null
unknown_B2?: true | null
big_value?: [
number,
number,
] | null
extra?: {
prop: number,
intvalue: number | null,

6
generated/maplabels.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
// GENERATED DO NOT EDIT
// This source data is located at '..\src\opcodes\maplabels.jsonc'
// run `npm run filetypes` to rebuild
export type maplabels = any;
// TypeError: parserFunctions[chunkdef[0]] is not a function

View File

@@ -1,5 +1,5 @@
// GENERATED DO NOT EDIT
// This source data is located at '..\src\opcodes\mapsquare_underlays.json'
// This source data is located at '..\src\opcodes\mapsquare_underlays.jsonc'
// run `npm run filetypes` to rebuild
export type mapsquare_underlays = {

View File

@@ -130,7 +130,7 @@ export type ModelExtrasLocation = {
worldz: number,
rotation: number,
mirror: boolean,
type: number,
isGroundDecor: boolean,
level: number,
locationInstance: WorldLocation
}
@@ -752,7 +752,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, skybox?: boolean, mask?: MapRect[] };
export type ChunkModelData = { floors: FloorMeshData[], models: MapsquareLocation[], overlays: PlacedModel[], chunk: ChunkData, grid: TileGrid };
export async function getMapsquareData(engine: EngineCache, chunkx: number, chunkz: number) {
@@ -963,19 +963,7 @@ async function mapsquareFloors(scene: ThreejsSceneCache, grid: TileGrid, chunk:
}
for (let level = 0; level < squareLevels; level++) {
let base = mapsquareMesh(grid, chunk, level, atlas, false, true, false);
floors.push(base);
if (opts?.minimap) {
let mini = {
...base,
extra: {
...base.extra,
modelgroup: "minimap" + level
},
minimap: true
};
floors.push(mini);
}
floors.push(mapsquareMesh(grid, chunk, level, atlas, false, true, false));
if (opts?.map2d) {
floors.push(mapsquareMesh(grid, chunk, level, atlas, false, false, true));
}
@@ -1391,7 +1379,7 @@ function mapsquareObjectModels(cache: CacheFileSource, locs: WorldLocation[]) {
worldz: inst.z,
rotation: inst.rotation,
mirror: !!objectmeta.mirror,
type: inst.type,
isGroundDecor: inst.type == 22 && !objectmeta.unknown_49,
level: inst.visualLevel,
locationInstance: inst
};
@@ -2123,7 +2111,6 @@ function mapsquareMesh(grid: TileGrid, chunk: ChunkData, level: number, atlas: S
showhidden,
tileinfos,
worldmap,
minimap: false,
vertexstride: vertexstride,
//TODO i'm not actually using these, can get rid of it again
@@ -2171,7 +2158,7 @@ function floorToThree(scene: ThreejsSceneCache, floor: FloorMeshData) {
mat.vertexColors = true;
if (!floor.showhidden) {
if (!floor.worldmap) {
augmentThreeJsFloorMaterial(mat, floor.minimap);
augmentThreeJsFloorMaterial(mat, false);
let img = floor.atlas.convert();
//no clue why this doesn't work

View File

@@ -1,7 +1,7 @@
import { parse } from "../opdecoder";
import { appearanceUrl, avatarStringToBytes, avatarToModel } from "./avatar";
import * as THREE from "three";
import { ThreejsSceneCache, mergeModelDatas, ob3ModelToThree, mergeNaiveBoneids, constModelsIds } from '../3d/modeltothree';
import { ThreejsSceneCache, mergeModelDatas, ob3ModelToThree, mergeNaiveBoneids, constModelsIds, augmentThreeJsFloorMaterial } from '../3d/modeltothree';
import { ModelModifications, constrainedMap, TypedEmitter, CallbackPromise } from '../utils';
import { boundMethod } from 'autobind-decorator';
import { resolveMorphedObject, modifyMesh, MapRect, ParsemapOpts, parseMapsquare, mapsquareModels, mapsquareToThreeSingle, ChunkData, TileGrid, mapsquareSkybox, generateLocationMeshgroups, PlacedMesh, classicChunkSize, rs2ChunkSize, tiledimensions, ModelExtras } from '../3d/mapsquare';
@@ -365,12 +365,13 @@ export type RSMapChunkData = {
chunkSize: number,
groups: Set<string>,
sky: { skybox: Object3D, fogColor: number[], skyboxModelid: number } | null,
generatedMinimap: boolean,
modeldata: PlacedMesh[][],
chunkmodels: Group[],
rect: MapRect
}
export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData }> implements ThreeJsSceneElementSource {
export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData, changed: undefined }> implements ThreeJsSceneElementSource {
chunkdata: Promise<RSMapChunkData>;
loaded: RSMapChunkData | null = null;
cache: ThreejsSceneCache;
@@ -379,7 +380,7 @@ export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData }> impleme
renderscene: ThreeJsRenderer | null = null;
toggles: Record<string, boolean> = {};
rect: MapRect;
minimapmode = false;
generated
cleanup() {
this.listeners = {};
@@ -414,32 +415,75 @@ export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData }> impleme
}
onModelLoaded() {
this.setToggles(this.toggles, this.minimapmode);
this.setToggles(this.toggles);
this.emit("loaded", this.loaded!);
this.emit("changed", undefined);
this.renderscene?.sceneElementsChanged();
// this.renderscene?.setCameraLimits();//TODO fix this, current bounding box calc is too large
}
setToggles(toggles: Record<string, boolean>, minimap = this.minimapmode) {
generateMinimap() {
if (!this.loaded) { throw new Error("model not loaded yet"); }
if (this.loaded.generatedMinimap) { return; }
this.rootnode.traverse(child => {
if (child instanceof THREE.Mesh) {
let extra = child.userData as ModelExtras | undefined;
let cloned: Mesh | null = null;
if (extra?.modeltype == "location" || extra?.modeltype == "locationgroup") {
cloned = child.clone();
cloned.position.y -= 0.1 * tiledimensions;
cloned.updateMatrix();
cloned.updateMatrixWorld();
if (extra.modeltype == "location" && extra.isGroundDecor) {
cloned = null;
} else if (extra.modeltype == "locationgroup") {
let geo = cloned.geometry.clone();
let indexclone = geo.index!.clone();
let original = geo.index!.array as any as Float32Array;
let array = indexclone.array as any as Float32Array;
let pos = 0;
let startpos = 0;
for (let i = 0; i < extra.subranges.length; i++) {
let endpos = extra.subranges[i];
let obj = extra.subobjects[i];
if (obj.modeltype == "location" && !obj.isGroundDecor) {
array.set(original.slice(startpos, endpos), pos);
pos += endpos - startpos
}
startpos = endpos;
}
geo.setDrawRange(0, pos);
geo.setIndex(indexclone);
if (pos == 0) {
cloned = null;
}
}
}
if (extra?.modeltype == "floor") {
cloned = child.clone();
cloned.material = (cloned.material as Material).clone();
augmentThreeJsFloorMaterial(cloned.material, true);
}
if (extra && cloned) {
let modelgroup = `mini_${extra.modelgroup}`;
cloned.userData = { ...extra, modelgroup } satisfies ModelExtras;
this.loaded!.groups.add(modelgroup);
child.parent!.add(cloned);
}
}
});
this.emit("changed", undefined);
}
setToggles(toggles: Record<string, boolean>) {
this.toggles = toggles;
this.rootnode.traverse(node => {
if (node.userData.modelgroup) {
let newvis = toggles[node.userData.modelgroup] ?? true;
node.traverse(child => {
if (child instanceof THREE.Mesh) {
// let extra = child.userData as ModelExtras | undefined;
// if (extra?.modeltype == "location" || extra?.modeltype == "locationgroup") {
// child.position.y = (minimap ? -0.1 : 0) * tiledimensions;
// child.updateMatrix();
// child.updateMatrixWorld();
// } else if (extra?.modeltype == "floor") {
// let qq=child.material as Material;
// qq
// //
// } else {
// //
// }
child.visible = newvis;
}
});
@@ -447,12 +491,12 @@ export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData }> impleme
});
}
constructor(rect: MapRect, cache: ThreejsSceneCache, extraopts?: ParsemapOpts) {
constructor(rect: MapRect, cache: ThreejsSceneCache, extraopts?: ParsemapOpts & { minimap?: boolean }) {
super();
this.rect = rect;
this.cache = cache;
this.chunkdata = (async () => {
let opts: ParsemapOpts = { invisibleLayers: true, minimap: true, collision: true, map2d: false, padfloor: true, skybox: false, ...extraopts };
let opts: ParsemapOpts = { invisibleLayers: true, collision: true, map2d: false, padfloor: true, skybox: false, ...extraopts };
let { grid, chunks } = await parseMapsquare(cache.engine, rect, opts);
let processedChunks = await Promise.all(chunks.map(async chunkdata => {
let chunk = await mapsquareModels(cache, grid, chunkdata, opts);
@@ -493,7 +537,10 @@ export class RSMapChunk extends TypedEmitter<{ loaded: RSMapChunkData }> impleme
let modeldata = processedChunks.flatMap(q => q.locmeshes.byLogical);
let chunkmodels = processedChunks.map(q => q.group);
this.loaded = { grid, chunks, groups, sky, modeldata, chunkmodels, chunkSize, rect };
this.loaded = { grid, chunks, groups, sky, modeldata, chunkmodels, chunkSize, rect, generatedMinimap: false };
if (extraopts?.minimap) {
this.generateMinimap();
}
this.onModelLoaded();
return this.loaded;
})();

View File

@@ -63,6 +63,7 @@ export const cacheConfigPages = {
environments: 29,
animgroups: 32,
mapscenes: 34,
maplabels: 36,
//used before 488 (feb 2008)
locs_old: 6,

View File

@@ -0,0 +1,48 @@
{
"0x01": { "name": "sprite", "read": "varuint" },
"0x02": { "name": "sprite_hover", "read": "varuint" },
"0x03": { "name": "text", "read": "string" },
"0x04": { "name": "color_1", "read": ["tuple","ubyte","ubyte","ubyte"] },
"0x05": { "name": "color_2", "read": ["tuple","ubyte","ubyte","ubyte"] },
"0x06": { "name": "font_size", "read": "ubyte" },
"0x07": { "name": "unknown_07", "read": "ubyte" },
"0x08": { "name": "unknown_08", "read": "ubyte" },
"0x09": { "name": "toggle_1", "read": ["struct",
["varbit","ushort"],
["varp","ushort"],
["lower","uint"],
["upper","uint"]
] },
"0x0a": { "name": "rightclick_1", "read": "string" },
"0x0f": { "name": "polygon", "read": ["struct",
["pointcount","ubyte"],
["points",["array",["ref","pointcount"],["struct",
["x","short"],
["y","short"]
]]],
["color",["tuple","ubyte","ubyte","ubyte","ubyte"]],
["always_1","ubyte"],
["back_color",["tuple","ubyte","ubyte","ubyte","ubyte"]],
["pointplanes",["array",["ref","pointcount"],"ubyte"]]
] },
"0x11": { "name": "rightclick_2", "read": "string" },
"0x13": { "name": "category", "read": "ushort" },
"0x14": { "name": "toggle_2", "read": ["struct",
["varbit","ushort"],
["varp","ushort"],
["lower","uint"],
["upper","uint"]
] },
"0x15": { "name": "unknown_15", "read": ["array",6,"ubyte"] },
"0x19": { "name": "background_sprite", "read": "varuint" },
"0x1a": { "name": "legacy_switch", "read": ["struct",
["varbit","ushort"],
["varp","ushort"],
["value","ubyte"],
["default_ref","ushort"],
["legacy_ref","ushort"]
] },
"0x1c": { "name": "unknown_1c", "read": "ubyte" },
"0x1e": { "name": "unknown_1e", "read": "ubyte" },
"0xF9": { "name": "extra", "read": "extrasmap" }
}

View File

@@ -118,6 +118,7 @@ function allParsers() {
particles_1: FileParser.fromJson<import("../generated/particles_1").particles_1>(require("./opcodes/particles_1.jsonc")),
audio: FileParser.fromJson<import("../generated/audio").audio>(require("./opcodes/audio.jsonc")),
proctexture: FileParser.fromJson<import("../generated/proctexture").proctexture>(require("./opcodes/proctexture.jsonc")),
oldproctexture: FileParser.fromJson<import("../generated/oldproctexture").oldproctexture>(require("./opcodes/oldproctexture.jsonc"))
oldproctexture: FileParser.fromJson<import("../generated/oldproctexture").oldproctexture>(require("./opcodes/oldproctexture.jsonc")),
maplabels: FileParser.fromJson<import("../generated/maplabels").maplabels>(require("./opcodes/maplabels.jsonc")),
}
}

View File

@@ -493,6 +493,7 @@ export const cacheFileJsonModes = constrainedMap<JsonBasedFile>()({
mapscenes: { parser: parse.mapscenes, lookup: singleMinorIndex(cacheMajors.config, cacheConfigPages.mapscenes) },
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) },
particles0: { parser: parse.particles_0, lookup: singleMinorIndex(cacheMajors.particles, 0) },
particles1: { parser: parse.particles_1, lookup: singleMinorIndex(cacheMajors.particles, 1) },

View File

@@ -1662,22 +1662,21 @@ export class SceneMapModel extends React.Component<LookupModeProps, SceneMapStat
if (!sceneCache || !renderer) { return; }
let chunk = new RSMapChunk(rect, sceneCache, { skybox: true });
chunk.once("loaded", async () => {
let combined = chunk.rootnode;
chunk.on("changed", () => {
let toggles = this.state.toggles;
[...chunk.loaded!.groups].sort((a, b) => a.localeCompare(b)).forEach(q => {
if (typeof toggles[q] != "boolean") {
toggles[q] = !q.match(/(floorhidden|collision|walls|map|mapscenes)/);
toggles[q] = !q.match(/^(floorhidden|collision|walls|map|mapscenes)/);
}
});
this.setState({ toggles });
chunk.setToggles(toggles);
})
chunk.once("loaded", () => {
let combined = chunk.rootnode;
let center = this.state.center;
combined.position.add(new Vector3(-center.x, 0, -center.z));
chunk.addToScene(renderer);
chunk.setToggles(toggles);
this.setState({ toggles });
});
let center = this.state.center;

View File

@@ -72,6 +72,9 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
private vr360cam: VR360Render | null = null;
private forceAspectRatio: number | null = null;
private standardLights: Group;
private minimapLights: Group;
private camMode: RenderCameraMode = "standard";
private camera: THREE.PerspectiveCamera;
private topdowncam: THREE.OrthographicCamera;
@@ -154,15 +157,23 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
this.modelnode.scale.set(1 / 512, 1 / 512, -1 / 512);
this.scene.add(this.modelnode);
//TODO figure out which lights work or not
scene.add(new THREE.AmbientLight(0xffffff, 0.7));
//classic light config
this.standardLights = new Group();
this.standardLights.add(new THREE.AmbientLight(0xffffff, 0.7));
var dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(75, 300, -75);
scene.add(dirLight);
this.standardLights.add(dirLight);
let hemilight = new THREE.HemisphereLight(0xffffff, 0x888844);
scene.add(hemilight);
this.standardLights.add(hemilight);
scene.add(this.standardLights);
//minimap lights
this.minimapLights = new Group();
let minidirlight = new THREE.DirectionalLight(0xffffff, 1.2);
minidirlight.position.set(-1, 1, 1);
this.minimapLights.add(minidirlight);
this.minimapLights.add(new THREE.AmbientLight(0xffffff, 0.8))
scene.add(this.minimapLights);
this.scene.fog = new THREE.Fog("#FFFFFF", 10000, 10000);
@@ -517,7 +528,9 @@ export class ThreeJsRenderer extends TypedEmitter<ThreeJsRendererEvents>{
-(cnvy - cnvrect.y) / cnvrect.height * 2 + 1,
);
raycaster.setFromCamera(mousepos, this.camera);
let currentcam = this.camMode == "standard" ? this.getStandardCamera() : this.camMode == "topdown" ? this.getTopdownCamera() : null;
if (!currentcam) { return; }
raycaster.setFromCamera(mousepos, currentcam);
let intersects = raycaster.intersectObjects(this.scene.children);
let firstloggable = true;