feat: initialize sunrise with 100-player shared rooms
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { BufferGeometry, Float32BufferAttribute } from 'three';
|
||||
import { encodeSummitGeometry, decodeSummitGeometry, loadSummitAssets } from '../src/scene/summitAssets.ts';
|
||||
import { gzipSync } from 'node:zlib';
|
||||
import { buildSummitGeometry,stitchSummitGeometry } from '../src/scene/summitGeometry.ts';
|
||||
import { createSummitWalking } from '../src/controls/summitWalking.ts';
|
||||
import { createTerrainGeometry,terrainGeometryHeightAt } from '../src/scene/terrainGeometry.ts';
|
||||
import { applySummitTerrain } from '../src/scene/summitTerrain.ts';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { SUMMIT_ASSETS } from '../src/constants/summitAssets.ts';
|
||||
import { SUMMIT_MATERIALS } from '../src/constants/summitMaterials.ts';
|
||||
|
||||
test('baked summit shares vertices and preserves every triangle attribute exactly',()=>{
|
||||
const geometry=new BufferGeometry();
|
||||
const order=[0,1,2,2,1,3];
|
||||
for(const [name,size] of [['position',3],['normal',3],['uv',2],['color',3],['trailDistance',1],['rockDistance',1]] as const) {
|
||||
geometry.setAttribute(name,new Float32BufferAttribute(order.flatMap(i=>Array.from({length:size},(_,j)=>i*.123+j*.35)),size));
|
||||
}
|
||||
const bytes=encodeSummitGeometry(geometry),restored=decodeSummitGeometry(bytes);
|
||||
assert.equal(restored.getAttribute('position').count,4);
|
||||
assert.equal(restored.getIndex()!.count,6);
|
||||
for(const name of Object.keys(geometry.attributes)) {
|
||||
const a=geometry.getAttribute(name),b=restored.getAttribute(name);
|
||||
for(let i=0;i<6;i++) for(let j=0;j<a.itemSize;j++)
|
||||
assert.equal(b.array[restored.getIndex()!.getX(i)*a.itemSize+j],a.array[i*a.itemSize+j]);
|
||||
}
|
||||
assert.throws(()=>decodeSummitGeometry(bytes.slice(0,-4)));
|
||||
geometry.dispose();restored.dispose();
|
||||
});
|
||||
|
||||
test('summit loader accepts both browser-decoded gzip and raw gzip hosting',async()=>{
|
||||
const geometry=new BufferGeometry();
|
||||
for(const [name,size] of [['position',3],['normal',3],['uv',2],['color',3],['trailDistance',1],['rockDistance',1]] as const)
|
||||
geometry.setAttribute(name,new Float32BufferAttribute(new Float32Array(size*3),size));
|
||||
const bytes=encodeSummitGeometry(geometry),originalFetch=globalThis.fetch;
|
||||
try {
|
||||
for(const automatic of [true,false]) {
|
||||
globalThis.fetch=async input=>String(input).includes('.json')
|
||||
? Response.json({version:1,bufferHash:'test',byteLength:bytes.byteLength})
|
||||
: new Response(automatic?bytes:gzipSync(new Uint8Array(bytes)),{headers:automatic?{'Content-Encoding':'gzip'}:{}});
|
||||
const loaded=await loadSummitAssets();assert.equal(loaded.getIndex()!.count,3);loaded.dispose();
|
||||
}
|
||||
} finally {globalThis.fetch=originalFetch;geometry.dispose();}
|
||||
});
|
||||
|
||||
test('adaptive interior triangles follow terrain and never shortcut material transition bands',()=>{
|
||||
const meta=JSON.parse(readFileSync(new URL('../public/terrain/gwanaksan.json',import.meta.url),'utf8'));
|
||||
const raw=readFileSync(new URL('../public/terrain/gwanaksan.f32',import.meta.url));
|
||||
const terrain=createTerrainGeometry(meta,new Float32Array(raw.buffer,raw.byteOffset,raw.byteLength/4),true);
|
||||
applySummitTerrain(terrain,meta);
|
||||
const walking=createSummitWalking(meta.origin),mesh=buildSummitGeometry(walking,terrain,true);
|
||||
const p=mesh.getAttribute('position');
|
||||
let large=0;
|
||||
for(let i=0;i<p.count;i+=3) {
|
||||
const x=(p.getX(i)+p.getX(i+1)+p.getX(i+2))/3,z=(p.getZ(i)+p.getZ(i+1)+p.getZ(i+2))/3;
|
||||
const y=(p.getY(i)+p.getY(i+1)+p.getY(i+2))/3;
|
||||
assert.ok(Math.abs(y-terrainGeometryHeightAt(terrain,x,z)-.06)<.0001,'surface stays on source triangle');
|
||||
const edge=Math.max(...[0,1,2].map(j=>Math.hypot(p.getX(i+j)-p.getX(i+(j+1)%3),p.getZ(i+j)-p.getZ(i+(j+1)%3))));
|
||||
if(edge<=1.4)continue;
|
||||
large++;
|
||||
assert.ok(edge<=SUMMIT_ASSETS.interiorMaxEdge+.0001);
|
||||
const r=SUMMIT_MATERIALS.rockBlend,t=SUMMIT_MATERIALS.edgeBlend;
|
||||
for(let j=0;j<3;j++) {
|
||||
const rock=walking.rockDistance(p.getX(i+j),p.getZ(i+j)),path=walking.pathDistance(p.getX(i+j),p.getZ(i+j));
|
||||
assert.ok(rock<r.innerMeters-r.irregularityMeters || rock>r.outerMeters+r.irregularityMeters &&
|
||||
(path<t.innerMeters-t.irregularityMeters||path>t.outerMeters+t.irregularityMeters));
|
||||
}
|
||||
}
|
||||
assert.ok(large>1000);terrain.dispose();mesh.dispose();
|
||||
});
|
||||
|
||||
test('coarse triangles reuse adjoining fine-edge vertices and attributes',()=>{
|
||||
const g=new BufferGeometry();
|
||||
const points=[[0,0,0],[4,0,0],[0,0,4],[0,0,0],[2,0,-1],[2,0,0],[2,0,0],[2,0,-1],[4,0,0]];
|
||||
g.setAttribute('position',new Float32BufferAttribute(points.flat(),3));
|
||||
g.setAttribute('color',new Float32BufferAttribute(points.flatMap(p=>p[0]===2&&p[2]===0?[.2,.3,.4]:[1,1,1]),3));
|
||||
const stitched=stitchSummitGeometry(g),p=stitched.getAttribute('position'),color=stitched.getAttribute('color');
|
||||
assert.equal(p.count,12);
|
||||
let midpointUses=0;
|
||||
for(let i=0;i<p.count;i++)if(p.getX(i)===2&&p.getZ(i)===0){midpointUses++;assert.equal(color.getX(i),Math.fround(.2));}
|
||||
assert.equal(midpointUses,4);
|
||||
g.dispose();stitched.dispose();
|
||||
});
|
||||
Reference in New Issue
Block a user