import { Engine } from "/framework/Engine.js"; import { Scene } from "/framework/Scene.js"; import { Mesh } from "/framework/Mesh.js"; import { OceanPipeline } from "./pipelines/OceanPipeline.js"; import { OceanTests } from "./tests/OceanTests.js"; import { events } from "./events.js"; class OceanApp { static bitReverse( x, bits ) { let n = x; let r = 0; for ( let i = 0; i < bits; i++ ) { r = ( r << 1 ) | ( n & 1 ); n = n >> 1; } return r; } async run( ) { const eventsHandler = new events( ); const domElements = eventsHandler.getDomElements( ); const statusElement = domElements.statusElement; const canvas = domElements.canvas; const waveHeightSlider = domElements.waveHeightSlider; const waveHeightValue = domElements.waveHeightValue; const wavelengthSlider = domElements.wavelengthSlider; const wavelengthValue = domElements.wavelengthValue; const resolutionSlider = domElements.resolutionSlider; const resolutionValue = domElements.resolutionValue; const tilingSlider = domElements.tilingSlider; const tilingValue = domElements.tilingValue; const wireframeToggle = domElements.wireframeToggle; const pauseToggle = domElements.pauseToggle; const shadingModeSelect = domElements.shadingModeSelect; const stepButton = domElements.stepButton; const dumpHeightButton = domElements.dumpHeightButton; function resizeCanvasToDisplay( ) { const width = window.innerWidth; const height = window.innerHeight; if ( canvas.width !== width || canvas.height !== height ) { canvas.width = width; canvas.height = height; } } resizeCanvasToDisplay( ); if ( !navigator.gpu ) { statusElement.textContent = "WebGPU not supported in this browser."; return; } statusElement.textContent = "Requesting WebGPU device…"; const adapter = await navigator.gpu.requestAdapter( ); const device = await adapter.requestDevice( ); const engine = new Engine( device ); const renderSystem = engine.createRenderSystem( canvas ); let scene = new Scene( ); let pipelines = []; let currentMeshResolution = 1024; let currentWavelengthScale = 1.0; let currentTiling = 1; function getPrimaryPipeline( ) { return pipelines.length > 0 ? pipelines[ 0 ] : null; } async function rebuildScene( ) { const previousPrimary = getPrimaryPipeline( ); statusElement.textContent = "Rebuilding pipelines…"; const newScene = new Scene( ); const meshRes = currentMeshResolution; const tiling = currentTiling; const pipeline = new OceanPipeline( engine, canvas ); pipeline.gridSize = 64; pipeline.meshResolution = meshRes; pipeline.memory.set( "canvasRef", canvas ); await pipeline.create( ); // restore camera from previous primary pipeline if available if ( previousPrimary && previousPrimary.camera && pipeline.camera ) { const prevCam = previousPrimary.camera; const newCam = pipeline.camera; newCam.yaw = prevCam.yaw; newCam.pitch = prevCam.pitch; newCam.distance = prevCam.distance; newCam.fovRadians = prevCam.fovRadians; newCam.near = prevCam.near; newCam.far = prevCam.far; if ( prevCam.target && newCam.target ) { newCam.target.x = prevCam.target.x; newCam.target.y = prevCam.target.y; newCam.target.z = prevCam.target.z; } if ( prevCam.up && newCam.up ) { newCam.up.x = prevCam.up.x; newCam.up.y = prevCam.up.y; newCam.up.z = prevCam.up.z; } if ( typeof newCam.update === "function" ) { newCam.update( ); } } // global settings if ( waveHeightSlider && waveHeightValue ) { const hValue = parseFloat( waveHeightSlider.value ); if ( !isNaN( hValue ) ) { pipeline.setHeightScale( hValue ); waveHeightValue.textContent = hValue.toFixed( 0 ); } } if ( wavelengthSlider && wavelengthValue && typeof pipeline.setWavelengthScale === "function" ) { const wValue = parseFloat( wavelengthSlider.value ); if ( !isNaN( wValue ) && wValue > 0 ) { pipeline.setWavelengthScale( wValue ); wavelengthValue.textContent = wValue.toFixed( 2 ); } } if ( wireframeToggle ) { if ( wireframeToggle.checked ) { pipeline.setRenderMode( "wireframe" ); } else { pipeline.setRenderMode( "solid" ); } } if ( shadingModeSelect ) { pipeline.setShadingMode( shadingModeSelect.value || "lighting" ); } if ( pauseToggle ) { pipeline.setPaused( pauseToggle.checked ); } // tiling (number of tiles around center) pipeline.setTiling( tiling ); const tileRange = Math.max( 0, tiling - 1 ); const tilesPerRow = tileRange * 2 + 1; const tileCount = tilesPerRow * tilesPerRow; const block = pipeline.getBlockByName( "ocean" ); if ( block ) { const skyPass = block.getPass( "SkySphere" ); if ( skyPass && skyPass.shader ) { const skyMesh = new Mesh( ); skyMesh.addShader( skyPass.shader ); newScene.addMesh( skyMesh ); } const renderPassName = wireframeToggle && wireframeToggle.checked ? "RenderWire" : "RenderSolid"; const renderPass = block.getPass( renderPassName ); if ( renderPass && renderPass.shader ) { const mesh = new Mesh( ); mesh.addShader( renderPass.shader ); mesh.setInstanceCount( tileCount ); newScene.addMesh( mesh ); } } pipelines = [ pipeline ]; scene = newScene; statusElement.textContent = "Simulating ocean (drag to orbit, scroll to zoom)"; } await rebuildScene( ); eventsHandler.resizeCanvasToDisplay = resizeCanvasToDisplay; eventsHandler.getPrimaryPipeline = getPrimaryPipeline; eventsHandler.getPipelines = function( ) { return pipelines; }; eventsHandler.rebuildScene = rebuildScene; eventsHandler.getScene = function( ) { return scene; }; eventsHandler.getRenderSystem = function( ) { return renderSystem; }; eventsHandler.setCurrentMeshResolution = function( value ) { currentMeshResolution = value; }; eventsHandler.setCurrentWavelengthScale = function( value ) { currentWavelengthScale = value; }; eventsHandler.setCurrentTiling = function( value ) { currentTiling = value; }; eventsHandler.setAutoRotate = function( value ) { autoRotate = value; }; eventsHandler.setup( ); // expose FFT test helper const oceanTests = new OceanTests( getPrimaryPipeline ); window.testFft = async function( ) { return oceanTests.testFft( ); }; window.testSpectrum = async function( ) { return oceanTests.testSpectrum( ); }; let autoRotate = true; let lastAutoRotateTime = performance.now(); async function frame( ) { const now = performance.now(); if ( autoRotate ) { const dt = ( now - lastAutoRotateTime ) / 1000; const primary = getPrimaryPipeline(); if ( primary && primary.camera ) { const rotationSpeed = 0.15; primary.camera.yaw += rotationSpeed * dt; primary.camera.update(); } } lastAutoRotateTime = now; const list = pipelines.slice( ); const run = async function( ) { for ( const p of list ) { await p.bindBuffers( ); await p.execute( ); } if ( scene && renderSystem ) { renderSystem.render( scene ); } }; try { await run( ); requestAnimationFrame( frame ); } catch ( error ) { console.error( error ); statusElement.textContent = "Error while drawing ocean."; } } requestAnimationFrame( frame ); } } const app = new OceanApp( ); app.run( );