diff --git a/package-lock.json b/package-lock.json index cc46d56..33e1637 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.10.1", "license": "MIT", "dependencies": { - "@wowserhq/io": "^2.0.2" + "@wowserhq/io": "^2.0.2", + "gl-matrix": "^3.4.3" }, "devDependencies": { "@commitlint/config-conventional": "^18.4.3", @@ -3110,6 +3111,11 @@ "node": ">=10" } }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -7832,6 +7838,11 @@ } } }, + "gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", diff --git a/package.json b/package.json index 53d45a4..b0b472d 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "wowser" ], "dependencies": { - "@wowserhq/io": "^2.0.2" + "@wowserhq/io": "^2.0.2", + "gl-matrix": "^3.4.3" }, "devDependencies": { "@commitlint/config-conventional": "^18.4.3", diff --git a/src/lib/map/Map.ts b/src/lib/map/Map.ts index 44e63cf..59bb922 100644 --- a/src/lib/map/Map.ts +++ b/src/lib/map/Map.ts @@ -1,4 +1,5 @@ import { IoMode, IoSource, openStream } from '@wowserhq/io'; +import { quat } from 'gl-matrix'; import * as wdtIo from './io/wdt.js'; import { AREA_INFO_FLAG, @@ -56,6 +57,37 @@ class Map { }; } + /** + * Convert the given position vector in the doodad or map obj def coordinate system into a vector + * in the map coordinate system. + * + * @param defPosition - Vector in doodad / map obj def coordinate system + */ + static getNormalizedDefPosition(defPosition: Float32Array) { + const normalized = new Float32Array(3); + + normalized[0] = MAP_CORNER_X - defPosition[2]; + normalized[1] = MAP_CORNER_Y - defPosition[0]; + normalized[2] = defPosition[1]; + + return normalized; + } + + /** + * Convert the given Euler angle in the doodad or map obj def coordinate system into a quaternion + * in the map coordinate system. + * + * @param defRotation - Euler angle in doodad / map obj def coordinate system + */ + static getNormalizedDefRotation(defRotation: Float32Array) { + const normalized = quat.create(); + + // glMatrix defaults to the correct euler order (zyx) to convert to the map coordinate system + quat.fromEuler(normalized, defRotation[2], defRotation[0], defRotation[1] + 180); + + return normalized as Float32Array; + } + load(source: IoSource) { const stream = openStream(source, IoMode.Read); const chunks = wdtIo.wdt.read(stream); diff --git a/src/lib/map/MapArea.ts b/src/lib/map/MapArea.ts index 40eb70b..bd17666 100644 --- a/src/lib/map/MapArea.ts +++ b/src/lib/map/MapArea.ts @@ -2,6 +2,7 @@ import { IoMode, IoSource, openStream } from '@wowserhq/io'; import * as adtIo from './io/adt.js'; import * as commonIo from './io/common.js'; import { indexChunks } from '../util.js'; +import Map from './Map.js'; import MapChunk from './MapChunk.js'; import MapLayer from './MapLayer.js'; import MapDoodadDef from './MapDoodadDef.js'; @@ -68,7 +69,7 @@ class MapArea { return this; } - #loadDoodadDefs(defs: any[], areaData: Map) { + #loadDoodadDefs(defs: any[], areaData: globalThis.Map) { if (!defs || defs.length === 0) { return; } @@ -80,13 +81,16 @@ class MapArea { names.offset = nameOfs[def.nameId]; const name = commonIo.mapString.read(names); + const normalizedPosition = Map.getNormalizedDefPosition(def.position); + const normalizedRotation = Map.getNormalizedDefRotation(def.rotation); + this.#doodadDefs.push( - new MapDoodadDef(name, def.uniqueId, def.position, def.rotation, def.scale), + new MapDoodadDef(name, def.uniqueId, normalizedPosition, normalizedRotation, def.scale), ); } } - #loadObjDefs(defs: any[], areaData: Map) { + #loadObjDefs(defs: any[], areaData: globalThis.Map) { if (!defs || defs.length === 0) { return; } @@ -98,17 +102,24 @@ class MapArea { names.offset = nameOfs[def.nameId]; const name = commonIo.mapString.read(names); - this.#objDefs.push(new MapObjDef(name, def.uniqueId, def.position, def.rotation)); + const normalizedPosition = Map.getNormalizedDefPosition(def.position); + const normalizedRotation = Map.getNormalizedDefRotation(def.rotation); + + this.#objDefs.push(new MapObjDef(name, def.uniqueId, normalizedPosition, normalizedRotation)); } } - #loadChunks(chunks: any[], areaData: Map) { + #loadChunks(chunks: any[], areaData: globalThis.Map) { for (const chunk of chunks) { this.#loadChunk(chunk.header, indexChunks(chunk.data), areaData); } } - #loadChunk(chunkHeader: any, chunkData: Map, areaData: Map) { + #loadChunk( + chunkHeader: any, + chunkData: globalThis.Map, + areaData: globalThis.Map, + ) { const chunk = new MapChunk(chunkHeader.position, chunkHeader.holes); // Vertices diff --git a/src/lib/map/MapDoodadDef.ts b/src/lib/map/MapDoodadDef.ts index 071c0aa..0f2f597 100644 --- a/src/lib/map/MapDoodadDef.ts +++ b/src/lib/map/MapDoodadDef.ts @@ -1,12 +1,8 @@ -import { MAP_CORNER_X, MAP_CORNER_Y } from './const.js'; - class MapDoodadDef { #name: string; #id: number; #position: Float32Array; - #mapPosition: Float32Array; #rotation: Float32Array; - #mapRotation: Float32Array; #scale: number; constructor( @@ -21,18 +17,6 @@ class MapDoodadDef { this.#position = position; this.#rotation = rotation; this.#scale = scale; - - // Doodad defs (map placements) are stored in a Y-up right-handed coordinate system - - this.#mapPosition = new Float32Array(3); - this.#mapPosition[0] = MAP_CORNER_X - position[2]; - this.#mapPosition[1] = MAP_CORNER_Y - position[0]; - this.#mapPosition[2] = position[1]; - - this.#mapRotation = new Float32Array(3); - this.#mapRotation[0] = rotation[2]; - this.#mapRotation[1] = rotation[0]; - this.#mapRotation[2] = rotation[1] - 180; } get name() { @@ -43,22 +27,20 @@ class MapDoodadDef { return this.#id; } + /** + * Returns vector representing doodad placement position in the map coordinate system. + */ get position() { return this.#position; } - get mapPosition() { - return this.#mapPosition; - } - + /** + * Returns quaternion representing doodad placement rotation in the map coordinate system. + */ get rotation() { return this.#rotation; } - get mapRotation() { - return this.#mapRotation; - } - get scale() { return this.#scale; } diff --git a/src/lib/map/MapObjDef.ts b/src/lib/map/MapObjDef.ts index 2d5811c..cd053f0 100644 --- a/src/lib/map/MapObjDef.ts +++ b/src/lib/map/MapObjDef.ts @@ -4,7 +4,7 @@ class MapObjDef { #position: Float32Array; #rotation: Float32Array; - constructor(name, id, position, rotation) { + constructor(name: string, id: number, position: Float32Array, rotation: Float32Array) { this.#name = name; this.#id = id; this.#position = position; @@ -19,10 +19,16 @@ class MapObjDef { return this.#id; } + /** + * Returns vector representing doodad placement position in the map coordinate system. + */ get position() { return this.#position; } + /** + * Returns quaternion representing doodad placement rotation in the map coordinate system. + */ get rotation() { return this.#rotation; } diff --git a/src/spec/map/MapArea.spec.ts b/src/spec/map/MapArea.spec.ts index bab49a7..94d7ad6 100644 --- a/src/spec/map/MapArea.spec.ts +++ b/src/spec/map/MapArea.spec.ts @@ -14,6 +14,23 @@ describe('MapArea', () => { expect(mapArea.doodadDefs.length).toBe(381); expect(mapArea.objDefs.length).toBe(19); }); + + test('should contain doodad defs in the expected format', () => { + const map = new Map().load('./fixture/map/continent.wdt'); + const mapArea = new MapArea(map.layerSplatDepth).load('./fixture/map/continent4229.adt'); + + const doodadDef1 = mapArea.doodadDefs.find((def) => def.id === 206410); + expect(doodadDef1.name.split(/\\/).at(-1)).toBe('TIRISFALLGLADECANOPYTREE05.M2'); + expect(doodadDef1.rotation).toEqual(new Float32Array([0, 0, 1, 6.123234262925839e-17])); + + const doodadDef2 = mapArea.doodadDefs.find((def) => def.id === 2542963); + expect(doodadDef2.name.split(/\\/).at(-1)).toBe('TIRISFALLGLADECANOPYTREE05.M2'); + expect(doodadDef2.rotation).toEqual( + new Float32Array([ + 0.0701502189040184, -0.038644179701805115, 0.9901213645935059, 0.11508788913488388, + ]), + ); + }); }); describe('pvpzone', () => {