11import '@/css/base.css' ;
22import './cornhole.css' ;
33
4+ const FIXED_DT = 1 / 60 ;
5+ const MAX_SUBSTEPS = 5 ; // cap to prevent "spiral of death"
6+
47import {
58 CourseKeyboardControls ,
69 MeshLoader ,
@@ -11,7 +14,8 @@ import {
1114 VolumetricClouds ,
1215 app ,
1316 generateSetupData ,
14- UILoadingScreen
17+ UILoadingScreen ,
18+ FuseRenderer
1519} from '@opengolfsim/fuse' ;
1620import { Water } from 'three/examples/jsm/Addons.js' ;
1721import groundBeachModel from './models/GroundBeach.glb?url' ;
@@ -28,7 +32,7 @@ const stopThreshold = 0.05;
2832const baseBoardDistance = 7 ;
2933let boardZOffset = 10 ;
3034
31- const fogColor = new THREE . Color ( '#fffcee ' ) ;
35+ const fogColor = new THREE . Color ( '#bddefc ' ) ;
3236const skyColor = new THREE . Color ( '#bddefc' ) ;
3337
3438const textureLoader = new THREE . TextureLoader ( ) ;
@@ -201,7 +205,7 @@ async function loadGameBoards() {
201205 setupBoard ( 'red' , boardMeshOriginal ) ;
202206}
203207
204- function setupScene ( ) {
208+ async function setupScene ( ) {
205209 gameContext . scene = new THREE . Scene ( ) ;
206210 gameContext . scene . background = new THREE . Color ( skyColor ) ;
207211 gameContext . scene . fog = new THREE . Fog ( fogColor , 10 , 140 ) ;
@@ -223,8 +227,6 @@ function setupScene() {
223227 gameContext . scene . add ( directionalLight ) ;
224228 directionalLight . target . position . set ( 0 , 0 , 0 ) ;
225229
226-
227-
228230 const waterGroup = new THREE . Group ( ) ;
229231 const underwaterGeometry = new THREE . PlaneGeometry ( 300 , 100 ) ;
230232 const underwaterMaterial = new THREE . MeshBasicMaterial ( { color : new THREE . Color ( '#1a5972' ) } ) ;
@@ -239,8 +241,8 @@ function setupScene() {
239241 const waterGeometry = new THREE . PlaneGeometry ( 300 , 100 ) ;
240242
241243 gameContext . water . object = new Water ( waterGeometry , {
242- textureWidth : 512 ,
243- textureHeight : 512 ,
244+ textureWidth : 256 ,
245+ textureHeight : 256 ,
244246 waterNormals : textureLoader . load (
245247 waterNormals ,
246248 ( texture ) => {
@@ -278,6 +280,7 @@ function setupScene() {
278280 gameContext . scene . add ( waterGroup ) ;
279281
280282
283+
281284}
282285
283286function createSky ( ) {
@@ -309,7 +312,7 @@ async function createGround(width = 100, depth = 100) {
309312 tex . wrapT = THREE . RepeatWrapping ;
310313 tex . repeat . set ( 100 , 100 ) ; // tile 50x across, 100x down the 100x200 plane
311314 tex . colorSpace = THREE . SRGBColorSpace ; // correct color rendering
312- tex . anisotropy = gameContext . renderer . capabilities . getMaxAnisotropy ( ) ;
315+ tex . anisotropy = gameContext . renderer . getMaxAnisotropy ( ) ;
313316
314317 const floorMaterial = new THREE . MeshStandardMaterial ( {
315318 map : tex ,
@@ -321,6 +324,7 @@ async function createGround(width = 100, depth = 100) {
321324 // const groundMesh = await loadMesh('models/GroundBeach.glb', true);
322325 const groundMesh = await gameContext . meshLoader ?. load ( groundBeachModel , true ) ;
323326 console . log ( 'groundMesh' , groundMesh . geometry ) ;
327+ // const geo = new THREE.PlaneGeometry(100, 100);
324328
325329 const mesh = new THREE . Mesh ( groundMesh . geometry , floorMaterial ) ;
326330 // floor.rotation.x = -Math.PI / 2;
@@ -469,14 +473,22 @@ async function setupGame() {
469473 launchShot ( { ballSpeed : 12 + ( Math . random ( ) * 2 ) , verticalLaunchAngle : 35 , horizontalLaunchAngle : - 5 + ( Math . random ( ) * 10 ) } ) ;
470474 } ) ;
471475
472- gameContext . renderer = new THREE . WebGLRenderer ( { canvas : document . getElementById ( 'canvas' ) , antialias : true } ) ;
473- gameContext . renderer . setSize ( window . innerWidth , window . innerHeight ) ;
474- gameContext . renderer . setPixelRatio ( Math . min ( window . devicePixelRatio , 1.5 ) ) ;
475- gameContext . renderer . shadowMap . enabled = true ;
476- gameContext . renderer . shadowMap . type = THREE . PCFSoftShadowMap ;
476+ const canvas = document . getElementById ( 'canvas' ) ;
477+ if ( ! canvas ) {
478+ throw new Error ( 'Missing canvas!' ) ;
479+ }
480+ gameContext . renderer = new FuseRenderer ( {
481+ canvas,
482+ antialias : true
483+ // renderMode: 'webgpu'
484+ } ) ;
485+ // gameContext.renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true });
486+ // gameContext.renderer.setSize(window.innerWidth, window.innerHeight);
487+ // gameContext.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
488+ // gameContext.renderer.shadowMap.enabled = true;
489+ // gameContext.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
490+
477491
478- gameContext . meshLoader = new MeshLoader ( gameContext . renderer ) ;
479-
480492 // window.addEventListener('resize', () => {
481493 // if (gameContext.camera) {
482494 // gameContext.camera.aspect = window.innerWidth / window.innerHeight;
@@ -486,14 +498,23 @@ async function setupGame() {
486498 // });
487499
488500
489- setupScene ( ) ;
490- await createGround ( ) ;
491- gameContext . camera = new ShotPerspectiveCamera ( gameContext . renderer , gameContext . ground . mesh , {
501+ await setupScene ( ) ;
502+
503+ gameContext . camera = new ShotPerspectiveCamera ( {
504+ canvas,
492505 fov : 30 ,
493506 cameraOffsetX : 0 ,
494507 cameraOffsetYZ : [ 1.5 , 1 ] ,
495508 } ) ;
496509
510+ await gameContext . renderer . init ( ) ;
511+
512+ gameContext . meshLoader = new MeshLoader ( gameContext . renderer . renderer ) ;
513+
514+ await createGround ( ) ;
515+
516+ gameContext . camera . setScene ( gameContext . ground . mesh ) ;
517+
497518 const geo = new THREE . BoxGeometry ( 0.06 , 2 , 0.06 ) ;
498519 const mat = new THREE . MeshBasicMaterial ( { color : 'red' , transparent : true , opacity : 0.8 } ) ;
499520
@@ -504,7 +525,7 @@ async function setupGame() {
504525 gameContext . scene . add ( gameContext . aimMesh ) ;
505526
506527 gameContext . camera ?. setPositions ( gameContext . startPoint , gameContext . aimPoint ) ;
507- createSky ( ) ;
528+ // createSky();
508529
509530
510531 await loadGameBoards ( ) ;
@@ -539,12 +560,13 @@ function loadGame() {
539560 gameContext . timer . connect ( document ) ;
540561
541562 gameContext . loadingScreen = new UILoadingScreen ( document . body , { loadingPrefix : 'Filling the bags' } ) ;
542- gameContext . loadingScreen . on ( 'load' , ( ) => {
563+ gameContext . loadingScreen . on ( 'load' , async ( ) => {
543564 gameContext . clock . start ( ) ;
544565 requestAnimationFrame ( animate ) ;
545566 } ) ;
546567 gameContext . loadingScreen . load ( setupGame ) ;
547568
569+ document . body . style . opacity = '1' ;
548570
549571 // requestAnimationFrame(animate);
550572}
@@ -767,25 +789,36 @@ function animate(animDelta) {
767789 const delta = gameContext . timer . getDelta ( ) ;
768790 gameContext . timer . update ( animDelta ) ;
769791
770- app . world . step ( gameContext . eventQueue ) ;
771- gameContext . clouds . update ( ) ;
772-
773- gameContext . eventQueue . drainCollisionEvents ( ( h1 , h2 , started ) => {
774- if ( ! started ) return ; // only care about entering, not leaving
775- const blueHole = gameContext . boards . blue . colliderHole . handle ;
776- const redHole = gameContext . boards . red . colliderHole . handle ;
792+ // fixed-rate physics
793+ gameContext . accumulator += delta ;
777794
778- let holeTeam = null ;
779- if ( h1 === blueHole || h2 === blueHole ) holeTeam = 'blue' ;
780- else if ( h1 === redHole || h2 === redHole ) holeTeam = 'red' ;
781- if ( ! holeTeam ) return ;
795+ // Clamp so a long hitch doesn't queue dozens of steps
796+ if ( gameContext . accumulator > FIXED_DT * MAX_SUBSTEPS ) {
797+ gameContext . accumulator = FIXED_DT * MAX_SUBSTEPS ;
798+ }
782799
783- const bagHandle = ( h1 === blueHole || h1 === redHole ) ? h2 : h1 ;
784- const bag = gameContext . bags . find ( b => b . collider . handle === bagHandle ) ;
785- if ( bag ) {
786- bag . inHole = holeTeam ;
787- }
788- } ) ;
800+ while ( gameContext . accumulator >= FIXED_DT ) {
801+ app . world . timestep = FIXED_DT ;
802+ app . world . step ( gameContext . eventQueue ) ;
803+ gameContext . accumulator -= FIXED_DT ;
804+
805+ // Drain collision events *inside* the loop so you don't
806+ // miss events from intermediate substeps
807+ gameContext . eventQueue . drainCollisionEvents ( ( h1 , h2 , started ) => {
808+ if ( ! started ) return ;
809+ const blueHole = gameContext . boards . blue . colliderHole . handle ;
810+ const redHole = gameContext . boards . red . colliderHole . handle ;
811+
812+ let holeTeam = null ;
813+ if ( h1 === blueHole || h2 === blueHole ) holeTeam = 'blue' ;
814+ else if ( h1 === redHole || h2 === redHole ) holeTeam = 'red' ;
815+ if ( ! holeTeam ) return ;
816+
817+ const bagHandle = ( h1 === blueHole || h1 === redHole ) ? h2 : h1 ;
818+ const bag = gameContext . bags . find ( b => b . collider . handle === bagHandle ) ;
819+ if ( bag ) bag . inHole = holeTeam ;
820+ } ) ;
821+ }
789822
790823 // Sync meshes to physics
791824 for ( const bag of gameContext . bags ) {
@@ -835,13 +868,11 @@ function animate(animDelta) {
835868 gameContext . startPoint ,
836869 gameContext . aimPoint
837870 ) ;
838- if ( aimChanged ) {
839- // gameContext.aimPoint.copy();
840- }
841871
842872 if ( gameContext . aimMesh ) gameContext . aimMesh . position . copy ( gameContext . aimPoint ) ;
843873
844- gameContext . camera ?. render ( gameContext . scene , gameContext . fog ) ;
874+ // gameContext.camera?.render(gameContext.scene, gameContext.fog);
875+ gameContext . renderer ?. render ( gameContext . scene , gameContext . camera , gameContext . fog ) ;
845876
846877 gameContext . stats ?. end ( ) ;
847878}
0 commit comments