import test from "node:test"; import assert from "node:assert/strict"; import { Matrix3, Matrix4, Vector3 } from "three"; import { descriptorSourceMatrix } from "../lib/environment-assembly.mjs"; import { convertNativeTerrainChunks, sourceTerrainToThreeMatrix } from "./terrain-geometry.mjs"; function makeChunk(id, coordinateX, xOffset, normalX = 0) { const vertices = [ [xOffset, 0, 0], [xOffset + 1, 0, 0], [xOffset, 0, 1], [xOffset + 1, 0, 1], ]; const normals = Array.from({ length: 4 }, () => [normalX, 1, 0]).flat(); return { id, coordinate: { x: coordinateX, z: 0 }, dimension: 1, positions: Float32Array.from(vertices.flat()), normals: Float32Array.from(normals), uvs: Float32Array.from([0, 0, 1, 0, 0, 1, 1, 1]), // Source topology is upward facing in left-handed Runewaker coordinates. indices: Uint16Array.from([0, 2, 3, 0, 0, 0]), triangleVisibilityFlags: Uint8Array.from([1, 0]), visibleSourceTriangles: 1, maskedSourceTriangles: 1, layers: [{ layerId: 4, tileScale: 12, sourceDDS: "textures/tile.dds" }], splatMaps: [{ mapIndex: 0, splatDDS: "weights/a.dds", linearPngFile: "weights/a.png", channelLayerIds: [4] }], material: { variantNames: [`${id}-layer-4`], weightVariantNames: [`${id}-weight-0`] }, }; } test("native terrain conversion applies primary inverse and reflected meter bridge", () => { const primary = { translation: [10, 5, -2], rotation: [0, Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)], scale: [2, 1, 3], }; const transform = sourceTerrainToThreeMatrix(primary, 0.1); const sourceWorldPoint = new Vector3(10, 5, -2).applyMatrix4(descriptorSourceMatrix(primary)); const expected = new Vector3(1, 0.5, 0.2); const actual = sourceWorldPoint.clone().applyMatrix4(transform); assert.ok(actual.distanceTo(expected) < 1e-6); const normalMatrix = new Matrix3().getNormalMatrix(transform); const normal = new Vector3(1, 1, 0).applyMatrix3(normalMatrix).normalize(); assert.ok(normal.x > 0 && normal.y > 0); }); test("visual indices preserve each source slot with reflected-Z winding; collision omits masked faces", () => { const transform = new Matrix4().makeScale(0.1, 0.1, -0.1); const chunks = [makeChunk("left", 0, 0), makeChunk("right", 1, 1, 0.01)]; const converted = convertNativeTerrainChunks(chunks, transform); assert.equal(converted.vertexCount, 8, "chunk grid vertices remain distinct at borders to retain native normals"); assert.deepEqual([...converted.chunks[0].visualIndices], [0, 3, 2, 0, 0, 0]); assert.deepEqual([...converted.chunks[1].visualIndices], [4, 7, 6, 4, 4, 4]); assert.deepEqual([...converted.chunks[0].collisionIndices], [0, 3, 2]); assert.deepEqual([...converted.chunks[1].collisionIndices], [4, 7, 6]); assert.deepEqual([...converted.chunks[0].uvs], [0, 0, 1, 0, 0, 1, 1, 1]); assert.equal(converted.report.sourceTriangleSlots, 4); assert.equal(converted.report.visibleSourceTriangles, 2); assert.equal(converted.report.maskedSourceTriangles, 2); assert.equal(converted.report.collisionTriangles, 2); assert.equal(converted.report.positionWelding.performed, false); assert.equal(converted.report.positionWelding.sharedGridVertexPairs, 2); assert.ok(converted.report.positionWelding.maxSharedNativeNormalDelta > 0); assert.equal(converted.report.windingValidation.trianglesAgainstNativeNormal, 0); assert.equal(converted.report.windingValidation.trianglesWithNativeNormalAgreement, 2); }); test("chunk borders that disagree in transformed position fail instead of silently welding", () => { const left = makeChunk("left", 0, 0); const right = makeChunk("right", 1, 1); right.positions[0] += 1; assert.throws( () => convertNativeTerrainChunks([left, right], new Matrix4().makeScale(0.1, 0.1, -0.1)), /disagrees with a neighbor at terrain grid vertex/, ); }); test("combined native grids retain indices beyond 65535 without wrapping or dropping masked slots", () => { const chunks = Array.from({ length: 16 }, (_, x) => { const dimension = 64; const positions = []; const normals = []; const uvs = []; for (let z = 0; z <= dimension; z++) for (let column = 0; column <= dimension; column++) { positions.push(x * dimension + column, 0, z); normals.push(0, 1, 0); uvs.push(column / dimension, z / dimension); } return { ...makeChunk(`chunk-${x}`, x, x * dimension), dimension, positions: Float32Array.from(positions), normals: Float32Array.from(normals), uvs: Float32Array.from(uvs), indices: Uint16Array.from([0, 4160, 4224, 0, 0, 0]), }; }); const converted = convertNativeTerrainChunks(chunks, new Matrix4().makeScale(0.1, 0.1, -0.1)); const last = converted.chunks.at(-1); assert.equal(converted.vertexCount, 67600); assert.ok(last.visualIndices instanceof Uint32Array); assert.ok(last.collisionIndices instanceof Uint32Array); assert.deepEqual([...last.visualIndices], [63375, 67599, 67535, 63375, 63375, 63375]); assert.deepEqual([...last.collisionIndices], [63375, 67599, 67535]); assert.equal(converted.report.maskedSourceTriangles, 16); assert.equal(converted.report.collisionTriangles, 16); });