feat: initialize sunrise with 100-player shared rooms
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import { BufferGeometry, Color, Group, Mesh, MeshStandardMaterial, RepeatWrapping, ShaderChunk, SRGBColorSpace, TextureLoader, Vector2 } from 'three';
|
||||
import { SUMMIT_MATERIALS } from '../constants/summitMaterials.ts';
|
||||
import { SUMMIT_PLATEAU } from '../constants/summitPlateau.ts';
|
||||
|
||||
/** Illustrative land cover, not photogrammetry. Uses the same corridors as walking. */
|
||||
export function createSummitTrails(geometry: BufferGeometry, onTextureLoaded: ()=>void = ()=>{}, start={x:0,z:0}) {
|
||||
const group=new Group();
|
||||
group.name='Summit trails and estimated land cover (200m)';
|
||||
const loader=new TextureLoader();
|
||||
const extraTextures: import('three').Texture[]=[];
|
||||
let disposed=false;
|
||||
let rockMaterial: MeshStandardMaterial;
|
||||
function createMesh() {
|
||||
const name='forest';
|
||||
const settings=SUMMIT_MATERIALS.forest;
|
||||
const load=(kind:string,surface=name)=>{
|
||||
const texture=loader.load(`${SUMMIT_MATERIALS.baseUrl}/${surface}-${kind}.jpg`,()=>{if(!disposed)onTextureLoaded();});
|
||||
texture.wrapS=texture.wrapT=RepeatWrapping;
|
||||
texture.anisotropy=SUMMIT_MATERIALS.anisotropy;
|
||||
if(kind==='color')texture.colorSpace=SRGBColorSpace;
|
||||
return texture;
|
||||
};
|
||||
const material=new MeshStandardMaterial({color:new Color(settings.tint),vertexColors:true,
|
||||
map:load('color'),normalMap:load('normal'),roughnessMap:load('roughness'),
|
||||
normalScale:new Vector2(settings.normalStrength,settings.normalStrength),roughness:1,
|
||||
polygonOffset:true,polygonOffsetFactor:-1,polygonOffsetUnits:-1});
|
||||
{
|
||||
material.color.set('#ffffff');
|
||||
const pathColor=load('color','path'),pathNormal=load('normal','path'),pathRoughness=load('roughness','path');
|
||||
extraTextures.push(pathColor,pathNormal,pathRoughness);
|
||||
const rockColor=load('color','rock'),rockNormal=load('normal','rock'),rockRoughness=load('roughness','rock');
|
||||
extraTextures.push(rockColor,rockNormal,rockRoughness);
|
||||
const plateauColor=loader.load(`${SUMMIT_MATERIALS.baseUrl}/plateau-color.png`,()=>{if(!disposed)onTextureLoaded();});
|
||||
plateauColor.colorSpace=SRGBColorSpace;plateauColor.anisotropy=SUMMIT_MATERIALS.anisotropy;extraTextures.push(plateauColor);
|
||||
rockMaterial=new MeshStandardMaterial({map:rockColor,normalMap:rockNormal,roughnessMap:rockRoughness,
|
||||
normalScale:new Vector2(SUMMIT_MATERIALS.rock.normalStrength,SUMMIT_MATERIALS.rock.normalStrength),roughness:1});
|
||||
const detail=SUMMIT_MATERIALS.rock;
|
||||
const secondaryUv=`uv*${detail.secondaryScale}+vec2(${detail.secondaryOffset.join(',')})`;
|
||||
const secondaryBlend=`${detail.secondaryBlend}+${detail.secondaryVariation}*sin(uv.x*0.71+sin(uv.y*0.53))`;
|
||||
rockMaterial.onBeforeCompile=shader=>{
|
||||
shader.fragmentShader=`vec4 rockDetail(sampler2D tex,vec2 uv) {
|
||||
return mix(texture2D(tex,uv),texture2D(tex,${secondaryUv}),${secondaryBlend});
|
||||
}\n`+shader.fragmentShader;
|
||||
for(const [chunk,map,uv] of [['map_fragment','map','vMapUv'],['normal_fragment_maps','normalMap','vNormalMapUv'],['roughnessmap_fragment','roughnessMap','vRoughnessMapUv']] as const)
|
||||
shader.fragmentShader=shader.fragmentShader.replace(`#include <${chunk}>`,ShaderChunk[chunk].replaceAll(`texture2D( ${map}, ${uv} )`,`rockDetail(${map},${uv})`));
|
||||
};
|
||||
rockMaterial.customProgramCacheKey=()=>`route-rock-detail-${detail.secondaryScale}-${secondaryBlend}`;
|
||||
// Keep leaf detail near the observer; avoid obvious repeated bands across distant slopes.
|
||||
material.onBeforeCompile=shader=>{
|
||||
shader.uniforms.farGroundColor={value:new Color(SUMMIT_MATERIALS.forest.farColor)};
|
||||
shader.uniforms.forestTint={value:new Color(SUMMIT_MATERIALS.forest.tint)};
|
||||
shader.uniforms.trailColor={value:pathColor};shader.uniforms.trailNormal={value:pathNormal};shader.uniforms.trailRoughness={value:pathRoughness};
|
||||
shader.uniforms.rockColor={value:rockColor};shader.uniforms.rockNormal={value:rockNormal};shader.uniforms.rockRoughness={value:rockRoughness};
|
||||
shader.uniforms.plateauColor={value:plateauColor};
|
||||
shader.vertexShader='attribute float trailDistance,rockDistance; varying float vTrailDistance,vRockDistance;\n'+shader.vertexShader;
|
||||
shader.vertexShader=shader.vertexShader.replace('#include <begin_vertex>','#include <begin_vertex>\nvTrailDistance=trailDistance;vRockDistance=rockDistance;');
|
||||
const edge=SUMMIT_MATERIALS.edgeBlend;
|
||||
const rockEdge=SUMMIT_MATERIALS.rockBlend;
|
||||
shader.fragmentShader=`uniform vec3 farGroundColor,forestTint;
|
||||
uniform sampler2D trailColor,trailNormal,trailRoughness;
|
||||
uniform sampler2D rockColor,rockNormal,rockRoughness;
|
||||
uniform sampler2D plateauColor;
|
||||
varying float vTrailDistance,vRockDistance;
|
||||
vec2 summitDx,summitDy;
|
||||
float summitRock,summitTrail,summitFade;
|
||||
vec2 rockUv(vec2 uv) { return uv*${(SUMMIT_MATERIALS.forest.tileMeters/SUMMIT_MATERIALS.rock.tileMeters).toFixed(6)}; }
|
||||
float rockWeight(vec2 uv) {
|
||||
float scatter=sin(uv.x*9.7+sin(uv.y*11.3))*sin(uv.y*7.1+uv.x*4.3);
|
||||
return 1.0-smoothstep(${rockEdge.innerMeters},${rockEdge.outerMeters},vRockDistance+scatter*${rockEdge.irregularityMeters});
|
||||
}
|
||||
float trailWeight(vec2 uv) {
|
||||
float scatter=sin(uv.x*13.7+sin(uv.y*8.3))*sin(uv.y*17.1+uv.x*3.1);
|
||||
return 1.0-smoothstep(${edge.innerMeters},${edge.outerMeters},vTrailDistance+scatter*${edge.irregularityMeters});
|
||||
}
|
||||
vec4 summitTex(sampler2D tex,vec2 uv,float scale) {
|
||||
return textureGrad(tex,uv,summitDx*scale,summitDy*scale);
|
||||
}
|
||||
vec4 summitGroundSample(sampler2D tex,vec2 uv) {
|
||||
float blend=0.5+0.5*sin(uv.x*0.71+sin(uv.y*0.53));
|
||||
return mix(summitTex(tex,uv,1.0),summitTex(tex,uv*1.37+vec2(0.31,0.73),1.37),blend);
|
||||
}
|
||||
vec4 summitRockSample(sampler2D tex,vec2 uv) {
|
||||
float scale=${(SUMMIT_MATERIALS.forest.tileMeters/SUMMIT_MATERIALS.rock.tileMeters).toFixed(6)};
|
||||
return mix(summitTex(tex,uv,scale),summitTex(tex,${secondaryUv},scale*${detail.secondaryScale}),${secondaryBlend});
|
||||
}
|
||||
// Uniform interiors sample one material; only transition fragments sample neighbours.
|
||||
vec4 summitSample(sampler2D groundMap,sampler2D dirtMap,sampler2D stoneMap,vec2 uv,int kind) {
|
||||
vec4 stone=vec4(0.0),dirt=vec4(0.0);
|
||||
if(summitRock>0.0) {
|
||||
stone=summitRockSample(stoneMap,rockUv(uv));
|
||||
vec2 local=vec2(uv.x,-uv.y)*${SUMMIT_MATERIALS.forest.tileMeters.toFixed(1)}-vec2(${start.x.toFixed(6)},${start.z.toFixed(6)});
|
||||
vec2 atlas=(local-vec2(${SUMMIT_PLATEAU.atlasBounds.slice(0,2).join(',')}))/vec2(${SUMMIT_PLATEAU.atlasBounds[2]-SUMMIT_PLATEAU.atlasBounds[0]},${SUMMIT_PLATEAU.atlasBounds[3]-SUMMIT_PLATEAU.atlasBounds[1]});
|
||||
float atlasMask=smoothstep(0.0,0.035,min(min(atlas.x,atlas.y),min(1.0-atlas.x,1.0-atlas.y)));
|
||||
if(kind==0)stone.rgb=mix(stone.rgb,texture2D(plateauColor,vec2(atlas.x,1.0-atlas.y)).rgb,atlasMask*0.95);
|
||||
if(kind==1)stone.xy=mix(stone.xy,vec2(0.5)+(stone.xy-0.5)*0.32,atlasMask);
|
||||
if(kind==1)stone.xy=(stone.xy-0.5)*${SUMMIT_MATERIALS.rock.normalStrength/SUMMIT_MATERIALS.forest.normalStrength}+0.5;
|
||||
if(summitRock>=1.0)return stone;
|
||||
}
|
||||
if(summitTrail>0.0) {
|
||||
dirt=summitTex(dirtMap,uv,1.0);
|
||||
if(kind==1)dirt.xy=(dirt.xy-0.5)*${SUMMIT_MATERIALS.path.normalStrength/SUMMIT_MATERIALS.forest.normalStrength}+0.5;
|
||||
if(summitTrail>=1.0)return mix(dirt,stone,summitRock);
|
||||
}
|
||||
vec4 ground=summitGroundSample(groundMap,uv);
|
||||
if(kind==0)ground.rgb=mix(ground.rgb*forestTint,farGroundColor,summitFade);
|
||||
return mix(mix(ground,dirt,summitTrail),stone,summitRock);
|
||||
}\n`+shader.fragmentShader;
|
||||
shader.fragmentShader=shader.fragmentShader.replace('#include <map_fragment>',
|
||||
`// Derivatives are computed before divergent branches to preserve mip selection at edges.
|
||||
summitDx=dFdx(vMapUv);summitDy=dFdy(vMapUv);
|
||||
summitRock=rockWeight(vMapUv);summitTrail=trailWeight(vMapUv);
|
||||
summitFade=smoothstep(${SUMMIT_MATERIALS.forest.fadeNear.toFixed(1)},${SUMMIT_MATERIALS.forest.fadeFar.toFixed(1)},length(vViewPosition))*0.9;
|
||||
${ShaderChunk.map_fragment.replace('texture2D( map, vMapUv )','summitSample(map,trailColor,rockColor,vMapUv,0)')}`);
|
||||
shader.fragmentShader=shader.fragmentShader.replace('#include <normal_fragment_maps>',
|
||||
ShaderChunk.normal_fragment_maps.replaceAll('texture2D( normalMap, vNormalMapUv )','summitSample(normalMap,trailNormal,rockNormal,vNormalMapUv,1)'));
|
||||
shader.fragmentShader=shader.fragmentShader.replace('#include <roughnessmap_fragment>',
|
||||
ShaderChunk.roughnessmap_fragment.replace('texture2D( roughnessMap, vRoughnessMapUv )','summitSample(roughnessMap,trailRoughness,rockRoughness,vRoughnessMapUv,2)'));
|
||||
};
|
||||
}
|
||||
const mesh=new Mesh(geometry,material);mesh.name=`Summit ${name} PBR surface`;group.add(mesh);
|
||||
return mesh;
|
||||
}
|
||||
const meshes=[createMesh()];
|
||||
return {group,rockMaterial:rockMaterial!,dispose(){
|
||||
disposed=true;
|
||||
rockMaterial.dispose();
|
||||
extraTextures.forEach(texture=>texture.dispose());
|
||||
for(const {geometry,material} of meshes) {
|
||||
geometry.dispose();material.map?.dispose();material.normalMap?.dispose();material.roughnessMap?.dispose();material.dispose();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user