diff --git a/public/shaders/std-gerstner.wgsl b/public/shaders/std-gerstner.wgsl new file mode 100644 index 00000000..7fda9f30 --- /dev/null +++ b/public/shaders/std-gerstner.wgsl @@ -0,0 +1,30 @@ +fn rotateDir(a: vec2, rad: f32) -> vec2 { + let sinC = sin(rad); + let cosC = cos(rad); + return vec2( + a.x * cosC - a.y * sinC, + a.x * sinC + a.y * cosC + ); +} + +fn gerstner(uv: vec2, t: f32) -> mat2x3 { + var displacement = vec3(0.0, 0.0, 0.0); + var normal = vec3(0.0, 0.0, 0.0); + for (var i = 0u; i < scene.numGerstnerWaves; i++) { + let wave = gerstnerWaves.ms[i]; + // let D = rotateDir(wave.D, 0.7); + let D = wave.D; + let dot_w_d_uv_phi_t = wave.w * dot(D, uv) + wave.phi * t; + let _cos = cos(dot_w_d_uv_phi_t); + let _sin = sin(dot_w_d_uv_phi_t); + displacement.x += wave.Q * wave.A * D.x * _cos; + displacement.z += wave.Q * wave.A * D.y * _cos; + displacement.y += wave.A * _sin; + normal.x += -1.0 * D.x * wave.w * wave.A * _cos; + normal.z += -1.0 * D.y * wave.w * wave.A * _cos; + normal.y += wave.Q * wave.w * wave.A * _sin; + } + normal.y = 1.0 - normal.y; + normalize(normal); + return mat2x3(displacement, normal); +} \ No newline at end of file diff --git a/public/shaders/std-ocean.wgsl b/public/shaders/std-ocean.wgsl index 0a48c480..f22124df 100644 --- a/public/shaders/std-ocean.wgsl +++ b/public/shaders/std-ocean.wgsl @@ -1,7 +1,8 @@ struct VertexOutput { // TODO(@darzu): change @location(0) normal : vec3, - @location(1) color : vec3, + // @location(0) @interpolate(flat) normal : vec3, + @location(1) @interpolate(flat) color : vec3, @location(2) worldPos: vec4, @location(3) uv: vec2, @location(4) @interpolate(flat) surface: u32, @@ -56,25 +57,6 @@ fn getShadowVis(shadowPos: vec3, normal: vec3, lightDir: vec3, in return visibility; } -fn gerstner(uv: vec2, t: f32) -> mat2x3 { - var displacement = vec3(0.0, 0.0, 0.0); - var normal = vec3(0.0, 0.0, 0.0); - for (var i = 0u; i < scene.numGerstnerWaves; i++) { - let wave = gerstnerWaves.ms[i]; - displacement = displacement + - vec3(wave.Q * wave.A + wave.D.x * cos(dot(wave.w * wave.D, uv) + wave.phi * t), - wave.A * sin(dot(wave.w * wave.D, uv) + wave.phi * t), - wave.Q * wave.A + wave.D.y * cos(dot(wave.w * wave.D, uv) + wave.phi * t)); - normal = normal + - vec3(-1.0 * wave.D.x * wave.w * wave.A * cos(wave.w * dot(wave.D, uv) + wave.phi * t), - wave.Q * wave.w * wave.A * sin(wave.w * dot(wave.D, uv) + wave.phi * t), - -1.0 * wave.D.y * wave.w * wave.A * cos(wave.w * dot(wave.D, uv) + wave.phi * t)); - } - normal.y = 1.0 - normal.y; - normalize(normal); - return mat2x3(displacement, normal); -} - @vertex fn vert_main(input: VertexInput) -> VertexOutput { let position = input.position; @@ -88,10 +70,13 @@ fn vert_main(input: VertexInput) -> VertexOutput { // TODO(@darzu): we're not totally sure about x,y,z vs normal,tangent,perp let surfBasis = mat3x3(perp, normal, tangent); let gerst = gerstner(input.uv * 1000, scene.time * .001); - let displacedPos = position + surfBasis * gerst[0]; - //let displacedPos = flattenedPos + gerst[0]; - let gerstNormal = surfBasis * gerst[1]; - //let gerstNormal = gerst[1]; + + // let displacedPos = position + surfBasis * gerst[0]; + // let gerstNormal = surfBasis * gerst[1]; + // FLATTENED: + let displacedPos = flattenedPos + gerst[0]; + let gerstNormal = gerst[1]; + // let displacedPos = flattenedPos + wave1; // let displacedPos = position + wave0; // let displacedPos = position + wave1; @@ -120,35 +105,39 @@ fn vert_main(input: VertexInput) -> VertexOutput { struct FragOut { @location(0) color: vec4, - @location(1) surface: vec2, + @location(1) normal: vec4, + @location(2) surface: vec2, } @fragment fn frag_main(input: VertexOutput) -> FragOut { let normal = normalize(input.normal); - var lightingColor: vec3 = vec3(0.0, 0.0, 0.0); - let unlit = 0u; - for (var i: u32 = 0u; i < scene.numPointLights; i++) { - let light = pointLights.ms[i]; - let toLight = light.position - input.worldPos.xyz; - let distance = length(toLight); - let attenuation = 1.0 / (light.constant + light.linear * distance + - light.quadratic * distance * distance); - let angle = clamp(dot(normalize(toLight), input.normal), 0.0, 1.0); - // XY is in (-1, 1) space, Z is in (0, 1) space - let posFromLight = (pointLights.ms[i].viewProj * input.worldPos).xyz; + // var lightingColor: vec3 = vec3(0.0, 0.0, 0.0); + // let unlit = 1u; + // for (var i: u32 = 0u; i < scene.numPointLights; i++) { + // let light = pointLights.ms[i]; + // let toLight = light.position - input.worldPos.xyz; + // let distance = length(toLight); + // let attenuation = 1.0 / (light.constant + light.linear * distance + + // light.quadratic * distance * distance); + // let angle = clamp(dot(normalize(toLight), input.normal), 0.0, 1.0); + // // XY is in (-1, 1) space, Z is in (0, 1) space + // let posFromLight = (pointLights.ms[i].viewProj * input.worldPos).xyz; - // Convert XY to (0, 1), Y is flipped because texture coords are Y-down. - let shadowPos = vec3(posFromLight.xy * vec2(0.5, -0.5) + vec2(0.5, 0.5), - posFromLight.z - ); - let shadowVis = getShadowVis(shadowPos, input.normal, normalize(toLight), i); - //lightingColor = lightingColor + clamp(abs((light.ambient * attenuation) + (light.diffuse * angle * attenuation * shadowVis)), vec3(0.0), vec3(1.0)); - //lightingColor += light.ambient; - lightingColor = lightingColor + f32(1u - unlit) * ((light.ambient * attenuation) + (light.diffuse * angle * attenuation * shadowVis)); - } - let litColor = input.color * (lightingColor + vec3(f32(unlit))); + // // Convert XY to (0, 1), Y is flipped because texture coords are Y-down. + // let shadowPos = vec3(posFromLight.xy * vec2(0.5, -0.5) + vec2(0.5, 0.5), + // posFromLight.z + // ); + // let shadowVis = getShadowVis(shadowPos, input.normal, normalize(toLight), i); + // //lightingColor = lightingColor + clamp(abs((light.ambient * attenuation) + (light.diffuse * angle * attenuation * shadowVis)), vec3(0.0), vec3(1.0)); + // //lightingColor += light.ambient; + // lightingColor = lightingColor + f32(1u - unlit) * ((light.ambient * attenuation) + (light.diffuse * angle * attenuation * shadowVis)); + // } + // let litColor = input.color * (lightingColor + vec3(f32(unlit))); + + // let litColor = mix(vec3(0.1, 0.3, 0.8), vec3(0.015), input.uv.x); + let litColor = mix(vec3(0.1, 0.3, 0.8), vec3(0.1, 0.05, 0.1), input.uv.x); var out: FragOut; @@ -156,6 +145,7 @@ fn frag_main(input: VertexOutput) -> FragOut { out.surface.r = input.surface; out.surface.g = input.id; + out.normal = vec4(normal, 0.0); return out; } diff --git a/public/shaders/std-outline.wgsl b/public/shaders/std-outline.wgsl index cb935613..7e79e142 100644 --- a/public/shaders/std-outline.wgsl +++ b/public/shaders/std-outline.wgsl @@ -95,7 +95,7 @@ fn frag_main(@location(0) uv : vec2) -> @location(0) vec4 { color *= 1.0 + edgeLum; // TODO(@darzu): DEBUG WIREFRAME // color *= 2.0; - // color = vec3(0.8); + color = vec3(0.8); } // DEBUG WIREFRAME // else { diff --git a/src/game/assets.ts b/src/game/assets.ts index dfe0e7ac..42493c55 100644 --- a/src/game/assets.ts +++ b/src/game/assets.ts @@ -93,6 +93,7 @@ const MeshTransforms: Partial<{ mat4.fromYRotation(mat4.create(), -Math.PI * 0.5), vec3.fromValues(-5, 0, 0) ), + // ocean: mat4.fromScaling(mat4.create(), [0.1, 0.1, 0.1]), ocean: mat4.fromScaling(mat4.create(), [2, 2, 2]), }; @@ -168,8 +169,28 @@ const MeshModify: Partial<{ // console.log("getMeshAsGrid failed!"); // console.error(e); // } + const xLen = grid.length; const yLen = grid[0].length; + + // redo quad indices based on the grid (optional?) + for (let xi = 0; xi < xLen - 1; xi++) { + for (let yi = 0; yi < yLen - 1; yi++) { + const qi = gridXYtoQuad(xi, yi); + vec4.copy(m.quad[qi], [ + grid[xi][yi], + grid[xi + 1][yi], + grid[xi + 1][yi + 1], + grid[xi][yi + 1], + ]); + } + } + function gridXYtoQuad(xi: number, yi: number): number { + const qi = yi + xi * (yLen - 1); + assert(qi < m.quad.length, "quads and grid mismatch!"); + return qi; + } + // console.log(`xLen:${xLen},yLen:${yLen}`); const uvs = m.pos.map((_, vi) => vec2.create()); m.uvs = uvs; @@ -182,45 +203,132 @@ const MeshModify: Partial<{ // instead of per quad, for vertex displacement (e.g. waves) // purposes? - //set tangents + // set tangents and normals m.tangents = m.pos.map(() => vec3.create()); m.normals = m.pos.map(() => vec3.create()); - for (let xIndex = 0; xIndex < grid.length; xIndex++) { - for (let yIndex = 0; yIndex < grid[0].length; yIndex++) { + for (let xi = 0; xi < grid.length; xi++) { + for (let yi = 0; yi < grid[0].length; yi++) { let normal: vec3; let tangent: vec3; - if (xIndex + 1 < grid.length && yIndex + 1 < grid[0].length) { - const pos = m.pos[grid[xIndex][yIndex]]; - const posNX = m.pos[grid[xIndex + 1][yIndex]]; - const posNY = m.pos[grid[xIndex][yIndex + 1]]; + if (xi + 1 < grid.length && yi + 1 < grid[0].length) { + const pos = m.pos[grid[xi][yi]]; + const posNX = m.pos[grid[xi + 1][yi]]; + const posNY = m.pos[grid[xi][yi + 1]]; normal = computeTriangleNormal(pos, posNX, posNY); - tangent = vec3.sub(m.tangents[grid[xIndex][yIndex]], posNX, pos); + tangent = vec3.sub(m.tangents[grid[xi][yi]], posNX, pos); vec3.normalize(tangent, tangent); - } else if (xIndex + 1 >= grid.length) { - normal = m.normals[grid[xIndex - 1][yIndex]]; - tangent = m.tangents[grid[xIndex - 1][yIndex]]; - } else if (yIndex + 1 >= grid[0].length) { - normal = m.normals[grid[xIndex][yIndex - 1]]; - tangent = m.tangents[grid[xIndex][yIndex - 1]]; + } else if (xi + 1 >= grid.length) { + normal = m.normals[grid[xi - 1][yi]]; + tangent = m.tangents[grid[xi - 1][yi]]; + } else if (yi + 1 >= grid[0].length) { + normal = m.normals[grid[xi][yi - 1]]; + tangent = m.tangents[grid[xi][yi - 1]]; } else { assert(false); } - vec3.copy(m.normals[grid[xIndex][yIndex]], normal); - vec3.copy(m.tangents[grid[xIndex][yIndex]], tangent); + vec3.copy(m.normals[grid[xi][yi]], normal); + vec3.copy(m.tangents[grid[xi][yi]], tangent); + } + } + + // TODO(@darzu): testing subdivide + // for (let i = 0; i < 100; i++) { + // subdivideQuad(i); + // } + let startXi = Math.floor(grid.length * 0.85); + let endXi = Math.floor(grid.length * 1.0); + let startYi = Math.floor(grid[0].length * 0.0); + let endYi = Math.floor(grid[0].length * 0.2); + for (let xi = startXi; xi < endXi; xi++) { + for (let yi = startYi; yi < endYi; yi++) { + subdivideQuad(gridXYtoQuad(xi, yi), 2); } } - // console.dir(uvs); - // console.log(` - // X: - // ${max(uvs.map((uv) => uv[0]))} - // ${min(uvs.map((uv) => uv[0]))} - // Y: - // ${max(uvs.map((uv) => uv[1]))} - // ${min(uvs.map((uv) => uv[1]))} - // `); + // console.dir(m); + + // console.dir({ + // uvMin: [min(m.uvs.map((a) => a[0])), min(m.uvs.map((a) => a[1]))], + // uvMax: [max(m.uvs.map((a) => a[0])), max(m.uvs.map((a) => a[1]))], + // }); + + // console.dir(m.uvs); + // console.dir({ minX, maxX, minZ, maxZ }); + return m; + + function subdivideQuad(quadIdx: number, recurse = 0) { + // maintain: colors, normals, pos, quad, surfaceIds, tangents, uvs + let [tl, tr, br, bl] = m.quad[quadIdx]; + let tm = m.pos.push(midPos(tl, tr)) - 1; + let rm = m.pos.push(midPos(tr, br)) - 1; + let bm = m.pos.push(midPos(br, bl)) - 1; + let lm = m.pos.push(midPos(bl, tl)) - 1; + let mm = m.pos.push(midPos(tm, bm)) - 1; + + // all quads are: top-left, top-right, bottom-right, bottom-left + let q_tl = quadIdx; // re-use + m.quad[q_tl] = [tl, tm, mm, lm]; + let q_tr = m.quad.push([tm, tr, rm, mm]) - 1; + let q_br = m.quad.push([mm, rm, br, bm]) - 1; + let q_bl = m.quad.push([lm, mm, bm, bl]) - 1; + + // init per-vertex (set later) + for (let p of [tm, rm, bm, lm, mm]) { + m.uvs![p] = vec2.create(); + m.tangents![p] = vec3.create(); + m.normals![p] = vec3.create(); + } + + // update quad properties + for (let q of [q_tl, q_tr, q_br, q_bl]) { + // per quad: + m.colors[q] = vec3.clone(m.colors[quadIdx]); + m.surfaceIds![q] = q; + // per provoking vert: + let vs = m.quad[q]; + m.normals![vs[0]] = computeTriangleNormal( + m.pos[vs[0]], + m.pos[vs[1]], + m.pos[vs[2]] + ); + m.tangents![vs[0]] = vec3.sub( + vec3.create(), + m.pos[vs[1]], + m.pos[vs[0]] + ); + vec3.normalize(m.tangents![vs[0]], m.tangents![vs[0]]); + } + // per vertex: + m.uvs![tm] = midUV(tl, tr); + m.uvs![rm] = midUV(tr, br); + m.uvs![bm] = midUV(br, bl); + m.uvs![lm] = midUV(bl, tl); + m.uvs![mm] = midUV(tm, bm); + + // recurse? + if (recurse > 0) { + subdivideQuad(q_tl, recurse - 1); + subdivideQuad(q_tr, recurse - 1); + subdivideQuad(q_br, recurse - 1); + subdivideQuad(q_bl, recurse - 1); + } + + function midPos(p0: number, p1: number): vec3 { + return vec3.fromValues( + (m.pos[p0][0] + m.pos[p1][0]) * 0.5, + (m.pos[p0][1] + m.pos[p1][1]) * 0.5, + (m.pos[p0][2] + m.pos[p1][2]) * 0.5 + ); + } + function midUV(p0: number, p1: number): vec2 { + return vec2.fromValues( + (m.uvs![p0][0] + m.uvs![p1][0]) * 0.5, + (m.uvs![p0][1] + m.uvs![p1][1]) * 0.5 + ); + } + } function setUV( x: number, @@ -252,14 +360,6 @@ const MeshModify: Partial<{ ]; setUV(nX, nY, dir, newDist, branch); } - // console.dir({ - // uvMin: [min(m.uvs.map((a) => a[0])), min(m.uvs.map((a) => a[1]))], - // uvMax: [max(m.uvs.map((a) => a[0])), max(m.uvs.map((a) => a[1]))], - // }); - - // console.dir(m.uvs); - // console.dir({ minX, maxX, minZ, maxZ }); - return m; }, }; @@ -714,13 +814,13 @@ onInit(async (em) => { const assetsPromise = loadAssets(renderer.renderer); assetLoader.promise = assetsPromise; - try { - const result = await assetsPromise; - em.addSingletonComponent(AssetsDef, result); - } catch (failureReason) { - // TODO(@darzu): fail more gracefully - throw `Failed to load assets: ${failureReason}`; - } + // try { + const result = await assetsPromise; + em.addSingletonComponent(AssetsDef, result); + // } catch (failureReason) { + // // TODO(@darzu): fail more gracefully + // throw `Failed to load assets: ${failureReason}`; + // } }); async function loadTxtInternal(relPath: string): Promise { diff --git a/src/game/game-hyperspace.ts b/src/game/game-hyperspace.ts index 362763b1..2f7f2938 100644 --- a/src/game/game-hyperspace.ts +++ b/src/game/game-hyperspace.ts @@ -11,7 +11,11 @@ import { shadowDepthTextures, shadowPipelines, } from "../render/pipelines/std-shadow.js"; -import { initStars, renderStars } from "../render/pipelines/std-stars.js"; +import { + emissionTexturePtr, + initStars, + renderStars, +} from "../render/pipelines/std-stars.js"; import { AssetsDef } from "./assets.js"; import { AuthorityDef, MeDef } from "../net/components.js"; import { createPlayer } from "./player.js"; @@ -22,12 +26,18 @@ import { noisePipes } from "../render/pipelines/std-noise.js"; import { DevConsoleDef } from "../console.js"; import { initOcean, OceanDef, oceanJfa, UVPosDef, UVDirDef } from "./ocean.js"; import { asyncTimeout } from "../util.js"; -import { vec2, vec3 } from "../gl-matrix.js"; +import { quat, vec2, vec3 } from "../gl-matrix.js"; import { AnimateToDef, EASE_INQUAD } from "../animate-to.js"; import { createSpawner, SpawnerDef } from "./spawner.js"; import { tempVec3 } from "../temp-pool.js"; import { createDarkStarNow, STAR1_COLOR, STAR2_COLOR } from "./darkstar.js"; import { renderOceanPipe } from "../render/pipelines/std-ocean.js"; +import { + normalsTexturePtr, + positionsTexturePtr, + surfacesTexturePtr, +} from "../render/pipelines/std-scene.js"; +import { createGhost } from "./game-sandbox.js"; // export let jfaMaxStep = VISUALIZE_JFA ? 0 : 999; @@ -81,12 +91,16 @@ export async function initHyperspaceGame(em: EntityManager) { "debugLoop" ); - const grid = [[...shadowDepthTextures]]; + // const grid = [[...shadowDepthTextures]]; // // // [oceanJfa._inputMaskTex, oceanJfa._uvMaskTex], // // // [oceanJfa.voronoiTex, shadowDepthTexture], // ]; + const grid = [ + [normalsTexturePtr, surfacesTexturePtr], + [positionsTexturePtr, emissionTexturePtr], + ]; // let grid = noiseGridFrame; // const grid = [[oceanJfa._voronoiTexs[0]], [oceanJfa._voronoiTexs[1]]]; @@ -101,11 +115,11 @@ export async function initHyperspaceGame(em: EntityManager) { stdRenderPipeline, renderOceanPipe, outlineRender, - //renderStars, - //...blurPipelines, + // renderStars, + // ...blurPipelines, postProcess, - //...(res.dev.showConsole ? gridCompose : []), + // ...(res.dev.showConsole ? gridCompose : []), ]; }, "hyperspaceGame" @@ -113,11 +127,10 @@ export async function initHyperspaceGame(em: EntityManager) { const res = await em.whenResources(AssetsDef, RendererDef); - // const ghost = createGhost(em); + const ghost = createGhost(em); // em.ensureComponentOn(ghost, RenderableConstructDef, res.assets.cube.proto); - // ghost.controllable.speed *= 3; - // ghost.controllable.sprintMul *= 3; - + ghost.controllable.speed *= 3; + ghost.controllable.sprintMul *= 3; { // // debug camera // vec3.copy(ghost.position, [-185.02, 66.25, -69.04]); @@ -131,6 +144,13 @@ export async function initHyperspaceGame(em: EntityManager) { // vec3.copy(g.cameraFollow.positionOffset, [0.0, 0.0, 0.0]); // g.cameraFollow.yawOffset = 0.0; // g.cameraFollow.pitchOffset = -0.486; + + let g = ghost; + vec3.copy(g.position, [-56.8, 9.21, 9.16]); + quat.copy(g.rotation, [0.0, 1.0, 0.0, 0.06]); + vec3.copy(g.cameraFollow.positionOffset, [2.0, 2.0, 8.0]); + g.cameraFollow.yawOffset = 0.0; + g.cameraFollow.pitchOffset = -0.523; } // one-time GPU jobs diff --git a/src/game/game-sandbox.ts b/src/game/game-sandbox.ts index a37f2b06..fbb8bac8 100644 --- a/src/game/game-sandbox.ts +++ b/src/game/game-sandbox.ts @@ -66,6 +66,7 @@ import { ControllableDef } from "./controllable.js"; import { GlobalCursor3dDef } from "./cursor.js"; import { ForceDef, SpringGridDef } from "./spring.js"; import { TextDef } from "./ui.js"; +import { AuthorityDef, MeDef } from "../net/components.js"; export const GhostDef = EM.defineComponent("ghost", () => ({})); @@ -83,6 +84,19 @@ export function createGhost(em: EntityManager) { // quat.rotateX(g.cameraFollow.rotationOffset, quat.IDENTITY, -Math.PI / 8); em.ensureComponentOn(g, LinearVelocityDef); + em.whenResources(RendererDef, AssetsDef).then((res) => { + em.ensureComponentOn( + g, + RenderableConstructDef, + res.assets.cube.proto, + true + // false + ); + }); + em.whenResources(MeDef).then((res) => { + em.ensureComponentOn(g, AuthorityDef, res.me.pid); + }); + return g; } diff --git a/src/game/ocean.ts b/src/game/ocean.ts index 3a207b00..9e246743 100644 --- a/src/game/ocean.ts +++ b/src/game/ocean.ts @@ -4,7 +4,7 @@ import { createRef, Ref } from "../em_helpers.js"; import { EM, EntityManager } from "../entity-manager.js"; import { vec3, vec2, mat3, mat4 } from "../gl-matrix.js"; import { InputsDef } from "../inputs.js"; -import { clamp } from "../math.js"; +import { clamp, mathMap, mathMix, mathWrap } from "../math.js"; import { PhysicsParentDef, PositionDef, @@ -167,31 +167,48 @@ export async function initOcean() { ); // console.log("adding OceanDef"); - - const gerstnerWaves = [ - createGerstnerWave( - 1.08 * 2.0, - 10 * 0.5, - randNormalVec2(vec2.create()), - 0.5 / 20.0, - 0.5 - ), - createGerstnerWave( - 1.08 * 2.0, - 10 * 0.5, - randNormalVec2(vec2.create()), - 0.5 / 20.0, - 0.5 - ), - createGerstnerWave( - 1.08 * 2.0, - 2 * 0.5, - randNormalVec2(vec2.create()), - 0.5 / 4.0, - 1 - ), - //createGerstnerWave(1, 0.5, randNormalVec2(vec2.create()), 0.5 / 1.0, 3), + let dirs: vec2[] = [ + // randNormalVec2(vec2.create()), + // randNormalVec2(vec2.create()), + // randNormalVec2(vec2.create()), + // randNormalVec2(vec2.create()), + // vec2.normalize(vec2.create(), [0.5, 0.5]), + // vec2.normalize(vec2.create(), [0.5, 0.5]), + // vec2.normalize(vec2.create(), [0.5, 0.5]), + // vec2.normalize(vec2.create(), [0.5, 0.5]), ]; + // dirs[0] = randNormalVec2(vec2.create()); + dirs[0] = vec2.fromValues(0.5, 0.5); + vec2.normalize(dirs[0], dirs[0]); + let lastDir = dirs[0]; + while (dirs.length < 10) { + const nextDir = vec2.rotate( + vec2.create(), + lastDir, + vec2.ZEROS, + (Math.PI + 1.4) * 0.5 + ); + dirs.push(nextDir); + lastDir = nextDir; + } + + const S = 0.05; + const L = 20; + const A = 0.4; + const gerstnerWaves = createGerstnerWaves({ + steepTotal: 1.0, + // prettier-ignore + waves: [ + // { steep: 10.0, amp: 1, len: 1 / 0.5, dirHr: 2.0, speed: 0.5 }, + // { steep: 10.0, amp: 1, len: 1 / 0.5, dirHr: 6.0, speed: 0.5 }, + { len: 0.1 * L, dir: [0.99, 0.1], steep: 0.9, amp: 4 * A, speed: 9.94 * S }, + { len: 0.1 * L, dir: [-0.37, 0.9], steep: 0.55, amp: 5 * A, speed: 15 * S }, + { len: 0.4 * L, dir: [-0.87, 0.4], steep: 1.0, amp: 1 * A, speed: 12 * S }, + { len: 0.8 * L, dir: [-0.3, 0.9], steep: 0.7, amp: 0.5 * A, speed: 0.9 * S }, + { len: 1.0 * L, dir: [0.001, 0.9], steep: 0.51, amp: -0.3 * A, speed: 0.7 * S }, + { len: 1.2 * L, dir: [0.98, -0.1], steep: 0.35, amp: 0.2 * A, speed: 0.5 * S }, + ], + }); const uvToPos = (out: vec3, uv: vec2) => { const x = uv[0] * uvToPosReader.size[0]; @@ -244,6 +261,8 @@ export async function initOcean() { // outDisp[1] = pos[1] + disp[1]; // outDisp[2] = pos[2] + disp[2] * 0.5; vec3.add(outDisp, pos, disp); + // TODO(@darzu): HACK + vec3.copy(outDisp, pos); const gNorm = vec3.add( tempVec3(), @@ -257,8 +276,10 @@ export async function initOcean() { vec3.copy(outNorm, gNorm); // HACK: smooth out norm? - vec3.add(outNorm, outNorm, vec3.scale(tempVec3(), norm, 2.0)); - vec3.normalize(outNorm, outNorm); + // vec3.copy(outNorm, norm); + vec3.set(outNorm, 0, 1, 0); + // vec3.add(outNorm, outNorm, vec3.scale(tempVec3(), norm, 2.0)); + // vec3.normalize(outNorm, outNorm); }; // TODO(@darzu): hacky hacky way to do this @@ -369,6 +390,7 @@ EM.registerSystem( const forwardish = vec3.sub(tempVec3(), aheadPos, e.position); const newNorm = tempVec3(); res.ocean.uvToGerstnerDispAndNorm(tempVec3(), newNorm, e.uvPos); + // const newNorm = res.ocean.uvToNorm(tempVec3(), e.uvPos); quatFromUpForward(e.rotation, newNorm, forwardish); // console.log( // `UVDir ${[e.uvDir[0], e.uvDir[1]]} -> ${quatDbg(e.rotation)}` @@ -379,14 +401,64 @@ EM.registerSystem( "oceanUVDirToRot" ); -function createGerstnerWave( +type GerstnerSet = { + steepTotal: number; + waves: ({ + steep: number; + amp: number; + len: number; + speed: number; + } & ({ dirHr: number } | { dir: vec2 }))[]; +}; + +function hrToDir(hour: number): vec2 { + hour = mathWrap(hour, 12); + return vec2.rotate( + vec2.create(), + [1, 0], + vec2.ZEROS, + mathMap(hour, 0, 12, Math.PI * 1.5, 3.5 * Math.PI) + ); +} + +function createGerstnerWaves(set: GerstnerSet): GerstnerWaveTS[] { + const res: GerstnerWaveTS[] = []; + const steepDenom = set.waves.reduce((p, n) => p + n.amp * (1 / n.len), 0); + const steepBudget = set.steepTotal / steepDenom; + const steepSum = set.waves.reduce((p, n) => p + n.steep, 0); + const steepScale = steepBudget / steepSum; + // TODO(@darzu): confirm/fix this math! + console.log( + ` + steepDenom: ${steepDenom}, + steepBudget: ${steepBudget}, + steepSum: ${steepSum}, + steepScale: ${steepScale}, + ` + ); + for (let w of set.waves) { + const A = w.amp; + const D = "dirHr" in w ? hrToDir(w.dirHr) : w.dir; // vec2.normalize(vec2.create(), w.dir); + const W = 1 / w.len; + const Q = w.steep * steepScale; + console.log(`Q: ${Q}`); + const phi = w.speed; + res.push(_createGerstnerWave(Q, A, D, W, phi)); + console.dir(res[res.length - 1]); + } + return res; +} + +function _createGerstnerWave( Q: number, A: number, D: vec2, w: number, phi: number ): GerstnerWaveTS { - return { D, Q, A, w, phi, padding1: 0, padding2: 0 }; + const res = { D, Q, A, w, phi, padding1: 0, padding2: 0 }; + // console.dir(res); + return res; } // TODO(@darzu): debug movement on the ocean diff --git a/src/math.ts b/src/math.ts index d07b74b7..cb8b0a5c 100644 --- a/src/math.ts +++ b/src/math.ts @@ -57,6 +57,15 @@ export function mathMap( const progress = (n - inMin) / (inMax - inMin); return progress * (outMax - outMin) + outMin; } +export function mathWrap(n: number, max: number): number { + // TODO(@darzu): support min? + const r = max; + const p = ((n % r) + r) % r; // TODO(@darzu): probably a more compact way to do this + return p; +} +export function mathMix(a: number, b: number, p: number): number { + return a * (1 - p) + b * p; +} export function mathMapNEase( n: number, inMin: number, diff --git a/src/render/gpu-helper.ts b/src/render/gpu-helper.ts index eb930ecd..a29d3c4e 100644 --- a/src/render/gpu-helper.ts +++ b/src/render/gpu-helper.ts @@ -99,7 +99,7 @@ export function createRenderTextureToQuad( let fSnip = `return ${returnWgslType}(inPx);`; if (inputArity === 2 && outArity === 4) - fSnip = `return ${returnWgslType}(inPx.xy, 0.0, 0.0);`; + fSnip = `return ${returnWgslType}(vec2(inPx.xy), 0.0, 0.0);`; if (fragSnippet) { if (isFunction(fragSnippet)) @@ -125,7 +125,7 @@ export function createRenderTextureToQuad( ? `.xyzw` : never(inputArity); - return ` + const shader = ` ${libs ? libs.map((l) => shaders[l].code).join("\n") : ""} ${shaders["std-screen-quad-vert"].code} @@ -144,6 +144,8 @@ export function createRenderTextureToQuad( ${fSnip} } `; + // console.log(shader); + return shader; }; const pipeline = CY.createRenderPipeline(name, { globals: [ diff --git a/src/render/gpu-struct.ts b/src/render/gpu-struct.ts index 94f72e64..b5df1b98 100644 --- a/src/render/gpu-struct.ts +++ b/src/render/gpu-struct.ts @@ -62,6 +62,7 @@ export const TexTypeToElementArity: Partial< r32float: 1, rgba16float: 4, rg16float: 2, + rg16uint: 2, r16float: 1, r8unorm: 1, r8snorm: 1, diff --git a/src/render/mesh-pool.ts b/src/render/mesh-pool.ts index 483f48bd..9e22193d 100644 --- a/src/render/mesh-pool.ts +++ b/src/render/mesh-pool.ts @@ -71,7 +71,7 @@ function logMeshPoolStats(opts: MeshPoolOpts) { const uniStruct = opts.unis.struct; if (MAX_INDICES < maxVerts) - throw `Too many vertices (${maxVerts})! W/ Uint16, we can only support '${maxVerts}' verts`; + throw `Too many vertices (${maxVerts})! W/ Uint16, we can only support '${MAX_INDICES}' verts`; // log our estimated space usage stats console.log( diff --git a/src/render/pipelines/std-compose.ts b/src/render/pipelines/std-compose.ts index 9805ec64..2e3f90f5 100644 --- a/src/render/pipelines/std-compose.ts +++ b/src/render/pipelines/std-compose.ts @@ -21,13 +21,22 @@ export function createGridComposePipelines( let pipes: CyRenderPipelinePtr[] = []; - for (let ri = 0; ri < grid.length; ri++) { - for (let ci = 0; ci < grid[ri].length; ci++) { + const rCount = grid.length; + for (let ri = 0; ri < rCount; ri++) { + const cCount = grid[ri].length; + for (let ci = 0; ci < cCount; ci++) { const tex = grid[ri][ci]; - const xMin = uvStartX + ci * (uvWidth + padding); - const xMax = xMin + uvWidth; - const yMax = uvStartY - ri * (uvHeight + padding); - const yMin = yMax - uvHeight; + let xMin = uvStartX + ci * (uvWidth + padding); + let xMax = xMin + uvWidth; + let yMax = uvStartY - ri * (uvHeight + padding); + let yMin = yMax - uvHeight; + // HACK: when we're working with 2x2, we shrink the images for easier nav + // TODO(@darzu): this is the common case; we should support this in a more + // principled way + if (ci === 0 && cCount === 2) xMax -= 0.25; + if (ci === 1 && cCount === 2) xMin += 0.25; + if (ri === 0 && rCount === 2) yMin += 0.25; + if (ri === 1 && rCount === 2) yMax -= 0.25; pipes.push( createRenderTextureToQuad( `composeViews_${ci}x${ri}`, diff --git a/src/render/pipelines/std-ocean.ts b/src/render/pipelines/std-ocean.ts index 81298a88..6ef131df 100644 --- a/src/render/pipelines/std-ocean.ts +++ b/src/render/pipelines/std-ocean.ts @@ -11,10 +11,11 @@ import { litTexturePtr, mainDepthTex, surfacesTexturePtr, + normalsTexturePtr, } from "./std-scene.js"; import { shadowDepthTextures } from "./std-shadow.js"; -const MAX_OCEAN_VERTS = 10000; +const MAX_OCEAN_VERTS = 65000; // capped by uint16 max const MAX_OCEAN_MESHES = 1; // TODO(@darzu): change @@ -69,7 +70,7 @@ export const OceanUniStruct = createCyStruct( export type OceanUniTS = CyToTS; export type OceanMeshHandle = MeshHandle; -const MAX_GERSTNER_WAVES = 8; +const MAX_GERSTNER_WAVES = 12; export const GerstnerWaveStruct = createCyStruct( { @@ -189,6 +190,10 @@ export const renderOceanPipe = CY.createRenderPipeline("oceanRender", { ptr: litTexturePtr, clear: "never", }, + { + ptr: normalsTexturePtr, + clear: "never", + }, { ptr: surfacesTexturePtr, clear: "never", @@ -197,6 +202,7 @@ export const renderOceanPipe = CY.createRenderPipeline("oceanRender", { depthStencil: mainDepthTex, shader: (shaderSet) => ` ${shaderSet["std-rand"].code} + ${shaderSet["std-gerstner"].code} ${shaderSet["std-ocean"].code} `, }); diff --git a/src/render/shader-loader.ts b/src/render/shader-loader.ts index 16e1d972..fb0aae76 100644 --- a/src/render/shader-loader.ts +++ b/src/render/shader-loader.ts @@ -18,6 +18,7 @@ export const ShaderPaths = [ "std-screen-quad-vert", "std-rand", "std-stars", + "std-gerstner", ] as const; export type ShaderName = typeof ShaderPaths[number];