import { getPath } from "./common.js"; import { loadGltf } from "./index2.js"; import { loadLight } from "./light.js"; import { loadFlat } from "./flat.js"; if (!navigator.gpu) { throw new Error("WebGPU not supported on this browser."); } const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { throw new Error("No WebGPU adapter"); } const device = await adapter.requestDevice(); const canvas = document.querySelector("canvas"); canvas.width = canvas.clientWidth canvas.height = canvas.clientHeight const context = canvas.getContext("webgpu"); const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, }); const encoder = device.createCommandEncoder(); const renderPass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), loadOp: "clear", clearValue: { r: 0.9, g: 0.5, b: 0.2, a: 1 }, storeOp: "store", }] }); renderPass.end(); const commandBuffer = encoder.finish(); device.queue.submit([commandBuffer]); const memory = new WebAssembly.Memory({ initial: 258 * 2 }); const module = await WebAssembly.instantiateStreaming(fetch("src/module.wasm"), { env: { memory: memory, log: console.log, } }); function createDepthTexture() { return device.createTexture({ size: [canvas.width, canvas.height], format: 'depth24plus', usage: GPUTextureUsage.RENDER_ATTACHMENT, }); } var depthTexture = undefined; const eyeValueX = document.getElementById("eye-value-x"); const eyeValueY = document.getElementById("eye-value-y"); const eyeValueZ = document.getElementById("eye-value-z"); const yawValue = document.getElementById("yaw"); const pitchValue = document.getElementById("pitch"); function recreateDepth(force) { if (canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height || force === true) { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; if (depthTexture !== undefined) { depthTexture.destroy(); } depthTexture = createDepthTexture(); } } const matrixSize = 4 * 4 * 4; const float4Size = 4 * 4; const float3Size = 4 * 3; const viewUniformBufferSize = (matrixSize * 2) + (float4Size * 2); const viewUniformBuffer = device.createBuffer({ label: "view uniform buffer", size: viewUniformBufferSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); const cameraStateSize = (float3Size * 3) + (4 * 3) + (float3Size * 1); const viewStateAddress = module.instance.exports.mem_alloc(viewUniformBufferSize); const cameraStateAddress = module.instance.exports.mem_alloc(cameraStateSize); module.instance.exports.camera_init(cameraStateAddress); //const gltfRenderer = await loadGltf(device, canvasFormat, viewUniformBuffer, memory, module); //const lightRenderer = await loadLight(device, canvasFormat, viewUniformBuffer); const flatRenderer = await loadFlat(device, canvasFormat, viewUniformBuffer); const KEY = { A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90, Left: 37, Up: 38, Right: 39, Down: 40, }; const keyCodes = new Set(Object.values(KEY)); const keyState = {}; for (const code of Object.values(KEY)) { keyState[code] = false; } function handleKeydown(e) { //console.log(e.keyCode); if (keyCodes.has(e.keyCode)) { keyState[e.keyCode] = true; } } function handleKeyup(e) { if (keyCodes.has(e.keyCode)) { keyState[e.keyCode] = false; } } window.addEventListener('keydown', handleKeydown, false); window.addEventListener('keyup', handleKeyup, false); function updateView() { const lu = keyState[KEY.W] === true; const ll = keyState[KEY.A] === true; const ld = keyState[KEY.S] === true; const lr = keyState[KEY.D] === true; const lal = keyState[KEY.Q] === true; const lar = keyState[KEY.E] === true; const ru = keyState[KEY.Up] === true; const rl = keyState[KEY.Left] === true; const rd = keyState[KEY.Down] === true; const rr = keyState[KEY.Right] === true; module.instance.exports.camera_move(cameraStateAddress, lu, ll, ld, lr, lal, lar, ru, rl, rd, rr); const aspect = canvas.width / canvas.height; module.instance.exports.camera_view_projection(cameraStateAddress, viewStateAddress, aspect); device.queue.writeBuffer(viewUniformBuffer, 0, memory.buffer, viewStateAddress, viewUniformBufferSize); const cameraStateF32 = new Float32Array(memory.buffer, cameraStateAddress, cameraStateSize); /* eyeValueX.innerHTML = cameraStateF32[0].toFixed(1); eyeValueY.innerHTML = cameraStateF32[1].toFixed(1); eyeValueZ.innerHTML = cameraStateF32[2].toFixed(1); yawValue.innerHTML = cameraStateF32[9].toFixed(2); pitchValue.innerHTML = cameraStateF32[10].toFixed(2); */ } const controlMain = document.getElementById("control-main"); function remap(low1, high1, low2, high2, value) { return low2 + (value - low1) * (high2 - low2) / (high1 - low1); } const sliderDefaults = { "circle radius": 385, "circle thickness": 284, "mass12 mix": 1, "particle count": 360, "particle mass1": 126, "particle mass2": 417, "particle size": 51, "test intensity": 0, }; class Slider { constructor(label, low, high, max = 1024) { const labelE = document.createElement("span"); labelE.className = "label"; labelE.innerHTML = label; const valueE = document.createElement("div"); valueE.className = "value"; const inputE = document.createElement("input"); inputE.className = "range"; inputE.min = 0; inputE.max = max; inputE.type = "range"; const spanE = document.createElement("span"); spanE.className = "range-span code"; valueE.appendChild(inputE); valueE.appendChild(spanE); controlMain.appendChild(labelE); controlMain.appendChild(valueE); this.label = label; this.input = inputE; this.span = spanE; this.low = low; this.high = high; this.lastValue = undefined; const storageValue = localStorage.getItem(this.label); if (storageValue !== null) { this.input.value = storageValue; } else if (this.label in sliderDefaults) { this.input.value = sliderDefaults[this.label]; } this.update(); } update() { const rawValue = parseInt(this.input.value); const value = remap(this.input.min, this.input.max, this.low, this.high, this.input.value); if (rawValue !== this.lastValue) { this.lastValue = rawValue; localStorage.setItem(this.label, rawValue); } if (this.high > 10) { this.span.innerHTML = value.toFixed(0); } else { this.span.innerHTML = value.toFixed(5); } return value; } } const sliders = { particleSize: new Slider("particle size", 0.0, 0.05), particleMass1: new Slider("particle mass1", 0.0, 0.01), particleMass2: new Slider("particle mass2", 0.0, 1.0), particleCount: new Slider("particle count", 0.0, 1024.0), circleRadius: new Slider("circle radius", 0.0, 0.5), circleThickness: new Slider("circle thickness", 0.0, 0.01), testIntensity: new Slider("test intensity", 0.0, 2.0), mass12Mix: new Slider("mass12 mix", 0.0, 1.0, 1), lineThickness: new Slider("line thickness", 0.0, 0.005), lineLength: new Slider("line length", 0.0, 0.025), }; const mousePosition = new Float32Array([0, 0, 0, 0]); function updateSliders() { return { mousePosition: mousePosition, particleSize: sliders.particleSize.update(), particleMass1: sliders.particleMass1.update(), particleMass2: sliders.particleMass2.update(), particleCount: sliders.particleCount.update(), circleRadius: sliders.circleRadius.update(), circleThickness: sliders.circleThickness.update(), testIntensity: sliders.testIntensity.update(), mass12Mix: sliders.mass12Mix.update(), lineThickness: sliders.lineThickness.update(), lineLength: sliders.lineLength.update(), }; } function onClick(e) { mousePosition[0] = e.clientX / canvas.width; mousePosition[1] = 1.0 - (e.clientY / canvas.height); } canvas.addEventListener("click", onClick); var canRender = false; var frameNumber = 0; function render2() { if (canRender === true) { const configuration = updateSliders(); canRender = false; recreateDepth(false); const colorView = context.getCurrentTexture().createView(); updateView(); const encoder = device.createCommandEncoder(); const renderPass = encoder.beginRenderPass({ colorAttachments: [{ view: colorView, loadOp: "clear", clearValue: { r: 0.2, g: 0.2, b: 0.4, a: 1 }, storeOp: "store", }], depthStencilAttachment: { view: depthTexture.createView(), depthClearValue: 1.0, depthLoadOp: 'clear', depthStoreOp: 'store', }, }); //gltfRenderer.render(renderPass); //lightRenderer.render(renderPass); flatRenderer.render(device, renderPass, frameNumber, configuration); renderPass.end(); const commandBuffer = encoder.finish(); device.queue.submit([commandBuffer]); frameNumber = (frameNumber + 1) % 2; } requestAnimationFrame(render2); } function setCanRender() { canRender = true; } setInterval(setCanRender, 66.67); // 15 fps recreateDepth(true); requestAnimationFrame(render2);