mirror of
https://github.com/skillbert/rsmv.git
synced 2025-12-23 21:47:48 -05:00
decode maplabel and more 3d minimap rendering
This commit is contained in:
5
generated/items.d.ts
vendored
5
generated/items.d.ts
vendored
@@ -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
6
generated/maplabels.d.ts
vendored
Normal 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
|
||||
2
generated/mapsquare_underlays.d.ts
vendored
2
generated/mapsquare_underlays.d.ts
vendored
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
})();
|
||||
|
||||
@@ -63,6 +63,7 @@ export const cacheConfigPages = {
|
||||
environments: 29,
|
||||
animgroups: 32,
|
||||
mapscenes: 34,
|
||||
maplabels: 36,
|
||||
|
||||
//used before 488 (feb 2008)
|
||||
locs_old: 6,
|
||||
|
||||
48
src/opcodes/maplabels.jsonc
Normal file
48
src/opcodes/maplabels.jsonc
Normal 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" }
|
||||
}
|
||||
@@ -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")),
|
||||
}
|
||||
}
|
||||
@@ -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) },
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user