feat: initialize sunrise with 100-player shared rooms

This commit is contained in:
2026-10-30 16:36:35 +09:00
commit 31d6eac4c2
257 changed files with 66063 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
import type { BufferGeometry } from 'three';
import { SUMMIT_TERRAIN } from '../constants/summitTerrain.ts';
import { SUMMIT_PLATEAU } from '../constants/summitPlateau.ts';
import { TERRAIN } from '../constants/terrain.ts';
import { structurePosition } from './structures.ts';
import { terrainGeometryHeightAt } from './terrainGeometry.ts';
import type { TerrainMetadata } from './terrainGeometry.ts';
/** Apply only to the Gwanaksan mesh. Original height arrays and reference origin stay unchanged. */
export function applySummitTerrain(geometry: BufferGeometry, meta: TerrainMetadata) {
const start = structurePosition(SUMMIT_TERRAIN.start.latitude, SUMMIT_TERRAIN.start.longitude, meta.origin);
// Sample all residuals BEFORE editing any vertex, avoiding order-dependent corrections.
const controls = SUMMIT_TERRAIN.controls.map(control => {
const point = structurePosition(control.latitude, control.longitude, meta.origin);
const original = terrainGeometryHeightAt(geometry, point.x, point.z) + meta.observerSurfaceElevation
+ (point.x * point.x + point.z * point.z) / (2 * TERRAIN.earthRadiusMeters);
return { x: point.x, z: point.z, residual: control.elevation - original };
});
const positions = geometry.getAttribute('position');
const key = (x: number, z: number) => `${Math.round(x * 1000)},${Math.round(z * 1000)}`;
const slab = new Map(SUMMIT_PLATEAU.heights.map(([x,z,height]) => [key(x,z),height]));
const xs = [...new Set(SUMMIT_PLATEAU.heights.map(p=>p[0]))].sort((a,b)=>a-b);
const zs = [...new Set(SUMMIT_PLATEAU.heights.map(p=>p[1]))].sort((a,b)=>a-b);
const authoredHeight = (x:number,z:number) => {
if(x<xs[0] || x>xs.at(-1)! || z<zs[0] || z>zs.at(-1)!)return undefined;
const cell=(axis:number[],v:number)=>{let lo=0,hi=axis.length-1;while(hi-lo>1){const m=(lo+hi)>>1;if(axis[m]<=v)lo=m;else hi=m;}return lo;};
const i=cell(xs,x),j=cell(zs,z),u=(x-xs[i])/(xs[i+1]-xs[i]),v=(z-zs[j])/(zs[j+1]-zs[j]);
const a=slab.get(key(xs[i],zs[j]))!,b=slab.get(key(xs[i+1],zs[j]))!;
const c=slab.get(key(xs[i],zs[j+1]))!,d=slab.get(key(xs[i+1],zs[j+1]))!;
return u+v<=1 ? a*(1-u-v)+b*u+c*v : b*(1-v)+c*(1-u)+d*(u+v-1);
};
for (let i = 0; i < positions.count; i++) {
const x = positions.getX(i), z = positions.getZ(i);
const radius = Math.hypot(x - start.x, z - start.z);
if (radius >= SUMMIT_TERRAIN.outerRadius) continue;
let weighted = 0, weights = 0;
for (const control of controls) {
const distanceSquared = (x - control.x) ** 2 + (z - control.z) ** 2;
if (distanceSquared < 1e-8) { weighted = control.residual; weights = 1; break; }
const weight = 1 / (distanceSquared * distanceSquared);
weighted += control.residual * weight;
weights += weight;
}
const t = Math.max(0, (radius - SUMMIT_TERRAIN.coreRadius) / (SUMMIT_TERRAIN.outerRadius - SUMMIT_TERRAIN.coreRadius));
const fade = 1 - t * t * (3 - 2 * t);
positions.setY(i, positions.getY(i) + weighted / weights * fade);
const authored = authoredHeight(x - start.x, z - start.z);
if (authored !== undefined) positions.setY(i, authored - meta.observerSurfaceElevation
- (x * x + z * z) / (2 * TERRAIN.earthRadiusMeters));
}
positions.needsUpdate = true;
geometry.computeVertexNormals();
geometry.computeBoundingSphere();
}