diff --git a/demo-01-ts-webgl/index.ts b/demo-01-ts-webgl/index.ts index a9b159a..568ee07 100644 --- a/demo-01-ts-webgl/index.ts +++ b/demo-01-ts-webgl/index.ts @@ -6,20 +6,20 @@ import { Camera } from './lib/camera' import { initBasicRenderProgram, drawScene, createShadowMap, initShadowRenderProgram } from './lib/BasicRenderProgram' import { loadObj } from './lib/loaders/ObjLoader' import { InputState } from './lib/input' -import { m4fromPositionAndEuler, m4lookAt, m4vectorMultiply, m4yRotation } from './lib/mat4' +import { m4fromPositionAndEuler, m4lookAt } from './lib/mat4' import { initGlState } from './lib/gl' import { getWorldRayFromClipSpaceAndCamera, rayIntersectsScene, sortBySceneDepth } from './lib/raycast' import { getPointerClickInClipSpace } from './lib/events' -import { Vec3, Vec4, calculateOrbitPosition } from './lib/vec' +import { Color, POS_ORIGIN, ROT_NONE, Vec3, calculateOrbitPosition } from './lib/vec' type NodeName = "yellow tree" | "orange tree" | "green tree" | "floor"; -const meshColorMap: Record = { - "yellow tree": [0.7, 0.7, 0.01 ], - "orange tree": [0.6, 0.3, 0.001], - "green tree": [0.1, 0.6, 0.1], - "floor": [0.2, 0.2, 0.4] +const meshColorMap: Record = { + "yellow tree": { r: 0.7, g: 0.7, b: 0.01 }, + "orange tree": { r: 0.6, g: 0.3, b: 0.001 }, + "green tree": { r: 0.1, g: 0.6, b: 0.1 }, + "floor": { r: 0.2, g: 0.2, b: 0.4} }; type Orbit = { @@ -33,7 +33,7 @@ const orbit: Orbit = { azimuth: Math.PI * -0.2, // horizontal angle, in radians elevation: 3 * Math.PI / 4, // vertical angle, in radians radius: 15, - target: [-3, 2, -2], + target: {x: -3, y: 2, z: -2}, sensitivity: 0.01, } @@ -78,35 +78,35 @@ if (!basicRenderProgram) { const vertices = await loadObj('/rainbowtree.obj'); -const yellowTree = initSceneNode(m4fromPositionAndEuler( [0,0,0], [0, Math.PI /2, 0]), +const yellowTree = initSceneNode(m4fromPositionAndEuler( POS_ORIGIN, { x: 0, y: Math.PI /2, z: 0}), { vertices, material: { color: meshColorMap["yellow tree"], - specularColor: [0.2,0.2,0.2], + specularColor: { r: 0.2, g: 0.2, b:0.2 }, shininess: 0.9 }}, "yellow tree") const orangeTree = initSceneNode( - m4fromPositionAndEuler( [5,0,0], [0, Math.PI /2, 0]), + m4fromPositionAndEuler( { x: 5, y: 0, z: 0 }, { x: 0, y: Math.PI /2, z: 0 }), { vertices, material: { color: meshColorMap["orange tree"], - specularColor: [0.2,0.2,0.2], + specularColor: { r: 0.2, g: 0.2, b: 0.2}, shininess: 0.9 }}, "orange tree") const greenTree = initSceneNode( - m4fromPositionAndEuler( [5,0,0], [0, Math.PI /2, 0]), + m4fromPositionAndEuler( { x: 5, y: 0, z: 0}, { x: 0, y: Math.PI /2, z: 0 }), { vertices, material: { color: meshColorMap["green tree"], - specularColor: [0.2,0.2,0.2], + specularColor: { r: 0.2, g: 0.2, b: 0.2}, shininess: 0.5 }}, "green tree") @@ -145,12 +145,12 @@ const floorVertices: Vertices = { } -const floorNode = initSceneNode(m4fromPositionAndEuler( [0,0.1,0], [0, 0, 0]), +const floorNode = initSceneNode(m4fromPositionAndEuler( { x: 0, y: 0.1, z: 0}, ROT_NONE), { vertices: floorVertices, material: { color: meshColorMap["floor"], - specularColor: [0.2,0.2,0.2], + specularColor: { r: 0.2, g: 0.2, b: 0.2}, shininess: 0.9 }}, "floor") @@ -161,17 +161,17 @@ const scene = [yellowTree, floorNode] // create lights const ambientLight: AmbientLight = { - color: [0.2, 0.2, 0.2] + color: { r: 0.2, g: 0.2, b: 0.2 } }; const directionalLight: DirectionalLight = { - rotation : [ 0.0, -0.8 , -0.5], - color : [0.6, 0.6, 0.6], + rotation : { x: 0, y: -0.8 , z: -0.5}, + color : { r: 0.6, g: 0.6, b: 0.6 }, }; const pointLight: PointLight = { - position: [ 0, 5.0, 5], - color: [ 0.7, 0.7, 0.7], + position: { x: 0, y: 5.0, z: 5 }, + color: {r: 0.7, g: 0.7, b: 0.7 }, constant: 1.0, linear: 0.009, quadratic: 0.032 @@ -184,7 +184,7 @@ const cameraPosition = calculateOrbitPosition( orbit.radius ); -const up: Vec3 = [0, 1, 0] +const up: Vec3 = {x: 0, y: 1, z: 0 } const camera: Camera = { fieldOfViewRadians: degToRad(60), aspect: canvas.clientWidth / canvas.clientHeight, @@ -197,7 +197,7 @@ const camera: Camera = { const input: InputState = { - pointerPosition: [0,0] + pointerPosition: {x: 0, y: 0 } } @@ -250,12 +250,12 @@ canvas.addEventListener('pointerdown', () => { /////////////////////////// -let lastTime = 0; +// let lastTime = 0; function animate(time: DOMHighResTimeStamp) { time *= 0.001 // convert from millis to seconds - const dt = time - lastTime; - lastTime = time; - updateLight(pointLight, dt) + // const dt = time - lastTime; + // lastTime = time; + // updateLight(pointLight, dt) // this both break under the shadow mapping // updateDirectionalLight(directionalLight, time) resizeCanvasToDisplaySize(canvas); camera.aspect = canvas.clientWidth / canvas.clientHeight; @@ -302,24 +302,24 @@ function resizeCanvasToDisplaySize(canvas: HTMLCanvasElement): void { } } + + // function updateDirectionalLight(light: DirectionalLight, time: number) { // const oldRotation = light.rotation -// light.rotation = [oldRotation[0], Math.sin(time), oldRotation[2]] +// light.rotation = { x: oldRotation.x, y: Math.sin(time), z: oldRotation.z } // } -function updateLight(pointLight: PointLight, dt: number) { - const rotator = m4yRotation(Math.PI / (dt * 10000)); - const oldTransform: Vec4 = [ - pointLight.position[0], - pointLight.position[1], - pointLight.position[2], - 0.0 - ]; +// function updateLight(pointLight: PointLight, dt: number) { +// const rotator = m4yRotation(Math.PI / (dt * 10000)); +// const oldTransform: Vec4 = { +// ...pointLight.position, +// w: 0.0 +// }; - const newTransform = m4vectorMultiply(oldTransform, rotator); - pointLight.position = [newTransform[0],newTransform[1],newTransform[2]] -} \ No newline at end of file +// const newTransform = m4PositionMultiply(oldTransform, rotator); +// pointLight.position = { x: newTransform.x, y: newTransform.y, z: newTransform.z } +// } \ No newline at end of file diff --git a/demo-01-ts-webgl/lib/BasicRenderProgram.ts b/demo-01-ts-webgl/lib/BasicRenderProgram.ts index 49e5f64..c3349e3 100644 --- a/demo-01-ts-webgl/lib/BasicRenderProgram.ts +++ b/demo-01-ts-webgl/lib/BasicRenderProgram.ts @@ -1,6 +1,6 @@ import { AttributeBinding, createProgramFromRaw } from "./shaderUtils" import { AmbientLight, DirectionalLight, PointLight } from "./light"; -import { m4fromPositionAndEuler, m4inverse, m4multiply, m4orthographic, m4perspective, m4PositionMultiply, m4xRotation, Mat4 } from "./mat4"; +import { m4fromPositionAndEuler, m4inverse, m4multiply, m4orthographic, m4perspective, m4PositionMultiply, m4ToArray, m4xRotation, Mat4 } from "./mat4"; import { Camera } from "./camera"; import { InputState } from "./input"; @@ -14,7 +14,7 @@ import shadowFragmentSource from "./shaders/depth-only.frag?raw" import { GlState } from "./gl"; import { Mesh } from "./mesh"; import { SceneNode } from "./scene"; -import { Vec3 } from "./vec"; +import { colorToArray, eulerToArray, Vec3, vec3ToArray } from "./vec"; function guaranteeUniformLocation( gl: WebGL2RenderingContext, @@ -103,31 +103,31 @@ export function updateUniforms( camera.aspect, camera.near, camera.far) - gl.uniformMatrix4fv(renderProgram.worldMatrixUniformLocation, false, shapeWorld); - gl.uniformMatrix4fv(renderProgram.viewUniformLocation, false, viewMatrix); - gl.uniformMatrix4fv(renderProgram.projectionUniformLocation, false, projectionMatrix); + gl.uniformMatrix4fv(renderProgram.worldMatrixUniformLocation, false, m4ToArray(shapeWorld)); + gl.uniformMatrix4fv(renderProgram.viewUniformLocation, false, m4ToArray(viewMatrix)); + gl.uniformMatrix4fv(renderProgram.projectionUniformLocation, false, m4ToArray(projectionMatrix)); // update material uniforms - gl.uniform3fv(renderProgram.materialUniform.colorLocation, mesh.material.color); - gl.uniform3fv(renderProgram.materialUniform.specularColorLocation, mesh.material.specularColor); + gl.uniform3fv(renderProgram.materialUniform.colorLocation, colorToArray(mesh.material.color)); + gl.uniform3fv(renderProgram.materialUniform.specularColorLocation, colorToArray(mesh.material.specularColor)); gl.uniform1f(renderProgram.materialUniform.shininessLocation, mesh.material.shininess); // update light uniforms // set ambient light - gl.uniform3fv(renderProgram.ambientLightUniform.colorLocation,ambientLight.color); + gl.uniform3fv(renderProgram.ambientLightUniform.colorLocation, colorToArray(ambientLight.color)); // set directional light - gl.uniform3fv(renderProgram.directionalLightUniform.colorLocation,directionalLight.color); - gl.uniform3fv(renderProgram.directionalLightUniform.rotationLocation,directionalLight.rotation); + gl.uniform3fv(renderProgram.directionalLightUniform.colorLocation,colorToArray(directionalLight.color)); + gl.uniform3fv(renderProgram.directionalLightUniform.rotationLocation, eulerToArray(directionalLight.rotation)); // shadows // shadow map must be bound - gl.uniformMatrix4fv(renderProgram.shadowUniform.lightViewLocation, false, lightViewProjectionMatrix); + gl.uniformMatrix4fv(renderProgram.shadowUniform.lightViewLocation, false, m4ToArray(lightViewProjectionMatrix)); // set point light - gl.uniform3fv(renderProgram.pointLightUniform.colorLocation,pointLight.color); - gl.uniform3fv(renderProgram.pointLightUniform.positionLocation,pointLight.position); + gl.uniform3fv(renderProgram.pointLightUniform.colorLocation, colorToArray(pointLight.color)); + gl.uniform3fv(renderProgram.pointLightUniform.positionLocation, vec3ToArray(pointLight.position)); gl.uniform1f(renderProgram.pointLightUniform.constantLocation,pointLight.constant); gl.uniform1f(renderProgram.pointLightUniform.linearLocation,pointLight.linear); gl.uniform1f(renderProgram.pointLightUniform.quadraticLocation,pointLight.quadratic); @@ -439,13 +439,15 @@ export function drawScene( // Compute light's view-projection matrix (for directional light) - const [x,y,z] = directionalLight.rotation + const x = directionalLight.rotation.x + const y = directionalLight.rotation.y + const z = directionalLight.rotation.z const xMatrix = m4xRotation(x) const yMatrix = m4xRotation(y) const zMatrix = m4xRotation(z) - const imaginaryCameraPosition: Vec3 = [10,10,10] + const imaginaryCameraPosition: Vec3 = { x: 10,y: 10, z: 10} let effectiveCameraPosition = m4PositionMultiply(imaginaryCameraPosition,xMatrix) effectiveCameraPosition = m4PositionMultiply(effectiveCameraPosition,yMatrix) effectiveCameraPosition = m4PositionMultiply(effectiveCameraPosition,zMatrix) @@ -583,8 +585,8 @@ export function drawShadowScene( const drawInitializedMesh = (mesh: Mesh) => { const vao = glState.vaos.get(mesh._id!)!; gl.bindVertexArray(vao); - gl.uniformMatrix4fv(shadowProgram.u_model, false, node._worldTransform); - gl.uniformMatrix4fv(shadowProgram.u_lightViewProj, false, lightViewProj); + gl.uniformMatrix4fv(shadowProgram.u_model, false, m4ToArray(node._worldTransform)); + gl.uniformMatrix4fv(shadowProgram.u_lightViewProj, false, m4ToArray(lightViewProj)); if (mesh.vertices.indices) { gl.drawElements(gl.TRIANGLES, mesh.vertices.indices.length, gl.UNSIGNED_SHORT, 0); } else { @@ -608,4 +610,5 @@ export function drawShadowScene( node.children.forEach(child => drawShadowScene(glState, [child], renderProgram, shadowProgram, lightViewProj)); } }); -} \ No newline at end of file +} + diff --git a/demo-01-ts-webgl/lib/events.ts b/demo-01-ts-webgl/lib/events.ts index d9efedf..c022273 100644 --- a/demo-01-ts-webgl/lib/events.ts +++ b/demo-01-ts-webgl/lib/events.ts @@ -17,5 +17,5 @@ export function getPointerClickInClipSpace( x = x / canvas.width * 2 -1; y = y / canvas.height * -2 + 1; - return [x,y] + return {x,y} } \ No newline at end of file diff --git a/demo-01-ts-webgl/lib/light.ts b/demo-01-ts-webgl/lib/light.ts index 5caa378..81ed04f 100644 --- a/demo-01-ts-webgl/lib/light.ts +++ b/demo-01-ts-webgl/lib/light.ts @@ -1,7 +1,7 @@ -import { Vec3 } from "./vec"; +import { Color, Vec3, Euler } from "./vec"; export type PointLight = { - color: Vec3; + color: Color; position: Vec3; constant: number; linear: number; @@ -9,10 +9,10 @@ export type PointLight = { } export type DirectionalLight = { - color: Vec3; - rotation: Vec3; + color: Color; + rotation: Euler; }; export type AmbientLight = { - color: Vec3; + color: Color; } diff --git a/demo-01-ts-webgl/lib/mat4.ts b/demo-01-ts-webgl/lib/mat4.ts index 2720b5e..ca71f57 100644 --- a/demo-01-ts-webgl/lib/mat4.ts +++ b/demo-01-ts-webgl/lib/mat4.ts @@ -1,175 +1,218 @@ -import { Ray } from './raycast'; -import { Vec3, Vec4, normalize, subtractVectors, cross } from './vec' +import { Vec3, subtractVectors, cross, normalize, Vec4 } from './vec' -export type Mat4 = [ +export type Mat4Array = [ number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, ] +export type Mat4Scaling = [ + [number, 0, 0, 0], + [0, number, 0, 0], + [0, 0, number, 0], + [0, 0, 0, 1] +] + +export type Mat4Translation = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [number, number, number, 1] +] + +export type Mat4XRotation = [ + [1, 0, 0, 0], + [0, number, number, 0], + [0, number, number, 0], + [0, 0, 0, 1], +] + +export type Mat4YRotation = [ + [number, 0, number, 0], + [0, 1, 0, 0], + [number, 0, number, 0], + [0, 0, 0, 1], +] + +export type Mat4ZRotation = [ + [number, number, 0, 0], + [number, number, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], +] + +export type Mat4 = [ + [number, number, number, number], + [number, number, number, number], + [number, number, number, number], + [number, number, number, number] +] + +export function m4ToArray(p: Mat4): Mat4Array { + + const m: Mat4Array = [ + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + ] + + m[0] = p[0][0] + m[1] = p[0][1] + m[2] = p[0][2] + m[3] = p[0][3] + m[4] = p[1][0] + m[5] = p[1][1] + m[6] = p[1][2] + m[7] = p[1][3] + m[8] = p[2][0] + m[9] = p[2][1] + m[10] = p[2][2] + m[11] = p[2][3] + m[12] = p[3][0] + m[13] = p[3][1] + m[14] = p[3][2] + m[15] = p[3][3] + + return m + +} export function m4lookAt(cameraPosition: Vec3, target: Vec3, up: Vec3): Mat4 { - const zAxis = normalize( - subtractVectors(cameraPosition, target)); - const xAxis = normalize(cross(up, zAxis)); - const yAxis = normalize(cross(zAxis, xAxis)); + const zAxis = subtractVectors(cameraPosition, target) + normalize(zAxis) + + const xAxis = cross(up, zAxis); + normalize(xAxis) + const yAxis = cross(zAxis, xAxis); + normalize(yAxis) + return [ - xAxis[0], xAxis[1], xAxis[2], 0, - yAxis[0], yAxis[1], yAxis[2], 0, - zAxis[0], zAxis[1], zAxis[2], 0, - cameraPosition[0], - cameraPosition[1], - cameraPosition[2], - 1, + [xAxis.x, xAxis.y, xAxis.z, 0], + [yAxis.x, yAxis.y, yAxis.z, 0], + [zAxis.x, zAxis.y, zAxis.z, 0], + [cameraPosition.x, cameraPosition.y, cameraPosition.z, 1] ]; } -export function m4perspective(fieldOfViewInRadians: number, aspect: number, near: number, far: number): Mat4 { +export type Mat4Projection = [ + [number, 0, 0, 0], + [0, number, 0, 0], + [0, 0, number,-1|0], + [number, number, number, 0|1], +] + +export function m4perspective(fieldOfViewInRadians: number, aspect: number, near: number, far: number): Mat4Projection { const f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians); const rangeInv = 1.0 / (near - far); return [ - f / aspect, 0, 0, 0, - 0, f, 0, 0, - 0, 0, (near + far) * rangeInv, -1, - 0, 0, near * far * rangeInv * 2, 0 + [f / aspect, 0, 0, 0], + [0, f, 0, 0], + [0, 0, (near + far) * rangeInv, -1], + [0, 0, near * far * rangeInv * 2, 0] ]; } -export function m4orthographic(left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4 { +export function m4orthographic(left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4Projection { const lr = 1 / (left - right); const bt = 1 / (bottom - top); const nf = 1 / (near - far); return [ - -2 * lr, 0, 0, 0, - 0, -2 * bt, 0, 0, - 0, 0, 2 * nf, 0, - (left + right) * lr, (top + bottom) * bt, (far + near) * nf, 1 + [-2 * lr, 0, 0, 0], + [0, -2 * bt, 0, 0], + [0, 0, 2 * nf, 0], + [(left + right) * lr, (top + bottom) * bt, (far + near) * nf, 1] ]; } -export function m4projection(width: number, height: number, depth: number): Mat4 { - // Note: This matrix flips the Y axis so 0 is at the top. - return [ - 2 / width, 0, 0, 0, - 0, -2 / height, 0, 0, - 0, 0, 2 / depth, 0, - -1, 1, 0, 1, - ]; - } - export function m4multiply(a: Mat4, b: Mat4): Mat4 { - - const a00 = a[0 * 4 + 0]!; - const a01 = a[0 * 4 + 1]!; - const a02 = a[0 * 4 + 2]!; - const a03 = a[0 * 4 + 3]!; - const a10 = a[1 * 4 + 0]!; - const a11 = a[1 * 4 + 1]!; - const a12 = a[1 * 4 + 2]!; - const a13 = a[1 * 4 + 3]!; - const a20 = a[2 * 4 + 0]!; - const a21 = a[2 * 4 + 1]!; - const a22 = a[2 * 4 + 2]!; - const a23 = a[2 * 4 + 3]!; - const a30 = a[3 * 4 + 0]!; - const a31 = a[3 * 4 + 1]!; - const a32 = a[3 * 4 + 2]!; - const a33 = a[3 * 4 + 3]!; - const b00 = b[0 * 4 + 0]!; - const b01 = b[0 * 4 + 1]!; - const b02 = b[0 * 4 + 2]!; - const b03 = b[0 * 4 + 3]!; - const b10 = b[1 * 4 + 0]!; - const b11 = b[1 * 4 + 1]!; - const b12 = b[1 * 4 + 2]!; - const b13 = b[1 * 4 + 3]!; - const b20 = b[2 * 4 + 0]!; - const b21 = b[2 * 4 + 1]!; - const b22 = b[2 * 4 + 2]!; - const b23 = b[2 * 4 + 3]!; - const b30 = b[3 * 4 + 0]!; - const b31 = b[3 * 4 + 1]!; - const b32 = b[3 * 4 + 2]!; - const b33 = b[3 * 4 + 3]!; + return [ - b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, - b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, - b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, - b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, - b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, - b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, - b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, - b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, - b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, - b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, - b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, - b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, - b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, - b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, - b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, - b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33, - ]; + + [ b[0][0] * a[0][0] + b[0][1] * a[1][0] + b[0][2] * a[2][0] + b[0][3] * a[3][0], + b[0][0] * a[0][1] + b[0][1] * a[1][1] + b[0][2] * a[2][1] + b[0][3] * a[3][1], + b[0][0] * a[0][2] + b[0][1] * a[1][2] + b[0][2] * a[2][2] + b[0][3] * a[3][2], + b[0][0] * a[0][3] + b[0][1] * a[1][3] + b[0][2] * a[2][3] + b[0][3] * a[3][3] + ], + [ b[1][0] * a[0][0] + b[1][1] * a[1][0] + b[1][2] * a[2][0] + b[1][3] * a[3][0], + b[1][0] * a[0][1] + b[1][1] * a[1][1] + b[1][2] * a[2][1] + b[1][3] * a[3][1], + b[1][0] * a[0][2] + b[1][1] * a[1][2] + b[1][2] * a[2][2] + b[1][3] * a[3][2], + b[1][0] * a[0][3] + b[1][1] * a[1][3] + b[1][2] * a[2][3] + b[1][3] * a[3][3] + ], + [ b[2][0] * a[0][0] + b[2][1] * a[1][0] + b[2][2] * a[2][0] + b[2][3] * a[3][0], + b[2][0] * a[0][1] + b[2][1] * a[1][1] + b[2][2] * a[2][1] + b[2][3] * a[3][1], + b[2][0] * a[0][2] + b[2][1] * a[1][2] + b[2][2] * a[2][2] + b[2][3] * a[3][2], + b[2][0] * a[0][3] + b[2][1] * a[1][3] + b[2][2] * a[2][3] + b[2][3] * a[3][3] + ], + [ b[3][0] * a[0][0] + b[3][1] * a[1][0] + b[3][2] * a[2][0] + b[3][3] * a[3][0], + b[3][0] * a[0][1] + b[3][1] * a[1][1] + b[3][2] * a[2][1] + b[3][3] * a[3][1], + b[3][0] * a[0][2] + b[3][1] * a[1][2] + b[3][2] * a[2][2] + b[3][3] * a[3][2], + b[3][0] * a[0][3] + b[3][1] * a[1][3] + b[3][2] * a[2][3] + b[3][3] * a[3][3] + ] + ] + } -export function m4translation(tx: number, ty: number, tz: number): Mat4 { + +export function m4translation(tx: number, ty: number, tz: number): Mat4Translation { return [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - tx, ty, tz, 1, + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [tx, ty, tz, 1], ]; } -export function m4xRotation(angleInRadians: number): Mat4 { +export function m4xRotation(angleInRadians: number): Mat4XRotation { const c = Math.cos(angleInRadians); const s = Math.sin(angleInRadians); return [ - 1, 0, 0, 0, - 0, c, s, 0, - 0, -s, c, 0, - 0, 0, 0, 1, + [1, 0, 0, 0], + [0, c, s, 0], + [0, -s, c, 0], + [0, 0, 0, 1], ]; } -export function m4yRotation(angleInRadians: number): Mat4 { +export function m4yRotation(angleInRadians: number): Mat4YRotation { const c = Math.cos(angleInRadians); const s = Math.sin(angleInRadians); return [ - c, 0, -s, 0, - 0, 1, 0, 0, - s, 0, c, 0, - 0, 0, 0, 1, + [c, 0, -s, 0], + [0, 1, 0, 0], + [s, 0, c, 0], + [0, 0, 0, 1], ]; } -export function m4zRotation(angleInRadians: number): Mat4 { +export function m4zRotation(angleInRadians: number): Mat4ZRotation { const c = Math.cos(angleInRadians); const s = Math.sin(angleInRadians); return [ - c, s, 0, 0, - -s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1, + [ c, s, 0, 0], + [-s, c, 0, 0], + [ 0, 0, 1, 0], + [ 0, 0, 0, 1], ]; } export function m4scaling(sx: number, sy: number, sz: number): Mat4 { return [ - sx, 0, 0, 0, - 0, sy, 0, 0, - 0, 0, sz, 0, - 0, 0, 0, 1, + [sx, 0, 0, 0], + [0, sy, 0, 0], + [0, 0, sz, 0], + [0, 0, 0, 1], ]; } @@ -194,170 +237,151 @@ export function m4scaling(sx: number, sy: number, sz: number): Mat4 { } -/** - * Transposes a matrix. - * @param {Matrix4} m matrix to transpose. - * @param {Matrix4} [dst] optional matrix to store result - * @return {Matrix4} dst or a new matrix if none provided - * @memberOf module:webgl-3d-math - */ + export function m4transpose(m: Mat4) { const dst: Mat4 = [ - 0,0,0,0, - 0,0,0,0, - 0,0,0,0, - 0,0,0,0, + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], ]; - dst[ 0] = m[0]; - dst[ 1] = m[4]; - dst[ 2] = m[8]; - dst[ 3] = m[12]; - dst[ 4] = m[1]; - dst[ 5] = m[5]; - dst[ 6] = m[9]; - dst[ 7] = m[13]; - dst[ 8] = m[2]; - dst[ 9] = m[6]; - dst[10] = m[10]; - dst[11] = m[14]; - dst[12] = m[3]; - dst[13] = m[7]; - dst[14] = m[11]; - dst[15] = m[15]; + dst[0][0] = m[0][0]; + dst[0][1] = m[1][0]; + dst[0][2] = m[2][0]; + dst[0][3] = m[3][0]; + + dst[1][0] = m[0][1]; + dst[1][1] = m[1][1]; + dst[1][2] = m[2][1]; + dst[1][3] = m[3][1]; + + dst[2][0] = m[0][2]; + dst[2][1] = m[1][2]; + dst[2][2] = m[2][2]; + dst[2][3] = m[3][2]; + + dst[3][0] = m[0][3]; + dst[3][1] = m[1][3]; + dst[3][2] = m[2][3]; + dst[3][3] = m[3][3]; return dst; } export function m4inverse(m: Mat4): Mat4 { - const m00 = m[0 * 4 + 0]!; - const m01 = m[0 * 4 + 1]!; - const m02 = m[0 * 4 + 2]!; - const m03 = m[0 * 4 + 3]!; - const m10 = m[1 * 4 + 0]!; - const m11 = m[1 * 4 + 1]!; - const m12 = m[1 * 4 + 2]!; - const m13 = m[1 * 4 + 3]!; - const m20 = m[2 * 4 + 0]!; - const m21 = m[2 * 4 + 1]!; - const m22 = m[2 * 4 + 2]!; - const m23 = m[2 * 4 + 3]!; - const m30 = m[3 * 4 + 0]!; - const m31 = m[3 * 4 + 1]!; - const m32 = m[3 * 4 + 2]!; - const m33 = m[3 * 4 + 3]!; - const tmp_0 = m22 * m33; - const tmp_1 = m32 * m23; - const tmp_2 = m12 * m33; - const tmp_3 = m32 * m13; - const tmp_4 = m12 * m23; - const tmp_5 = m22 * m13; - const tmp_6 = m02 * m33; - const tmp_7 = m32 * m03; - const tmp_8 = m02 * m23; - const tmp_9 = m22 * m03; - const tmp_10 = m02 * m13; - const tmp_11 = m12 * m03; - const tmp_12 = m20 * m31; - const tmp_13 = m30 * m21; - const tmp_14 = m10 * m31; - const tmp_15 = m30 * m11; - const tmp_16 = m10 * m21; - const tmp_17 = m20 * m11; - const tmp_18 = m00 * m31; - const tmp_19 = m30 * m01; - const tmp_20 = m00 * m21; - const tmp_21 = m20 * m01; - const tmp_22 = m00 * m11; - const tmp_23 = m10 * m01; - - const t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - - (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31); - const t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - - (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31); - const t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - - (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31); - const t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - - (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21); - - const d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3); + + const tmp_0 = m[2][2] * m[3][3]; + const tmp_1 = m[3][2] * m[2][3]; + const tmp_2 = m[1][2] * m[3][3]; + const tmp_3 = m[3][2] * m[1][3]; + const tmp_4 = m[1][2] * m[2][3]; + const tmp_5 = m[2][2] * m[1][3]; + const tmp_6 = m[0][2] * m[3][3]; + const tmp_7 = m[3][2] * m[0][3]; + const tmp_8 = m[0][2] * m[2][3]; + const tmp_9 = m[2][2] * m[0][3]; + const tmp_10 = m[0][2] * m[1][3]; + const tmp_11 = m[1][2] * m[0][3]; + const tmp_12 = m[2][0] * m[3][1]; + const tmp_13 = m[3][0] * m[2][1]; + const tmp_14 = m[1][0] * m[3][1]; + const tmp_15 = m[3][0] * m[1][1]; + const tmp_16 = m[1][0] * m[2][1]; + const tmp_17 = m[2][0] * m[1][1]; + const tmp_18 = m[0][0] * m[3][1]; + const tmp_19 = m[3][0] * m[0][1]; + const tmp_20 = m[0][0] * m[2][1]; + const tmp_21 = m[2][0] * m[0][1]; + const tmp_22 = m[0][0] * m[1][1]; + const tmp_23 = m[1][0] * m[0][1]; + + const t0 = (tmp_0 * m[1][1] + tmp_3 * m[2][1] + tmp_4 * m[3][1]) - + (tmp_1 * m[1][1] + tmp_2 * m[2][1] + tmp_5 * m[3][1]); + const t1 = (tmp_1 * m[0][1] + tmp_6 * m[2][1] + tmp_9 * m[3][1]) - + (tmp_0 * m[0][1] + tmp_7 * m[2][1] + tmp_8 * m[3][1]); + const t2 = (tmp_2 * m[0][1] + tmp_7 * m[1][1] + tmp_10 * m[3][1]) - + (tmp_3 * m[0][1] + tmp_6 * m[1][1] + tmp_11 * m[3][1]); + const t3 = (tmp_5 * m[0][1] + tmp_8 * m[1][1] + tmp_11 * m[2][1]) - + (tmp_4 * m[0][1] + tmp_9 * m[1][1] + tmp_10 * m[2][1]); + + const d = 1.0 / (m[0][0] * t0 + m[1][0] * t1 + m[2][0] * t2 + m[3][0] * t3); return [ - d * t0, - d * t1, - d * t2, - d * t3, - d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - - (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)), - d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - - (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)), - d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - - (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)), - d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - - (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)), - d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - - (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)), - d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - - (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)), - d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - - (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)), - d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - - (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)), - d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - - (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)), - d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - - (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)), - d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - - (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)), - d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - - (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)) - ]; + [ + d * t0, + d * t1, + d * t2, + d * t3, + ], + [ + d * ((tmp_1 * m[1][0] + tmp_2 * m[2][0] + tmp_5 * m[3][0]) - + (tmp_0 * m[1][0] + tmp_3 * m[2][0] + tmp_4 * m[3][0])), + d * ((tmp_0 * m[0][0] + tmp_7 * m[2][0] + tmp_8 * m[3][0]) - + (tmp_1 * m[0][0] + tmp_6 * m[2][0] + tmp_9 * m[3][0])), + d * ((tmp_3 * m[0][0] + tmp_6 * m[1][0] + tmp_11 * m[3][0]) - + (tmp_2 * m[0][0] + tmp_7 * m[1][0] + tmp_10 * m[3][0])), + d * ((tmp_4 * m[0][0] + tmp_9 * m[1][0] + tmp_10 * m[2][0]) - + (tmp_5 * m[0][0] + tmp_8 * m[1][0] + tmp_11 * m[2][0])), + ], + [ + d * ((tmp_12 * m[1][3] + tmp_15 * m[2][3] + tmp_16 * m[3][3]) - + (tmp_13 * m[1][3] + tmp_14 * m[2][3] + tmp_17 * m[3][3])), + d * ((tmp_13 * m[0][3] + tmp_18 * m[2][3] + tmp_21 * m[3][3]) - + (tmp_12 * m[0][3] + tmp_19 * m[2][3] + tmp_20 * m[3][3])), + d * ((tmp_14 * m[0][3] + tmp_19 * m[1][3] + tmp_22 * m[3][3]) - + (tmp_15 * m[0][3] + tmp_18 * m[1][3] + tmp_23 * m[3][3])), + d * ((tmp_17 * m[0][3] + tmp_20 * m[1][3] + tmp_23 * m[2][3]) - + (tmp_16 * m[0][3] + tmp_21 * m[1][3] + tmp_22 * m[2][3])), + ], + [ + d * ((tmp_14 * m[2][2] + tmp_17 * m[3][2] + tmp_13 * m[1][2]) - + (tmp_16 * m[3][2] + tmp_12 * m[1][2] + tmp_15 * m[2][2])), + d * ((tmp_20 * m[3][2] + tmp_12 * m[0][2] + tmp_19 * m[2][2]) - + (tmp_18 * m[2][2] + tmp_21 * m[3][2] + tmp_13 * m[0][2])), + d * ((tmp_18 * m[1][2] + tmp_23 * m[3][2] + tmp_15 * m[0][2]) - + (tmp_22 * m[3][2] + tmp_14 * m[0][2] + tmp_19 * m[1][2])), + d * ((tmp_22 * m[2][2] + tmp_16 * m[0][2] + tmp_21 * m[1][2]) - + (tmp_20 * m[1][2] + tmp_23 * m[2][2] + tmp_17 * m[0][2])) + ] + ] + } -export function m4vectorMultiply(v: Vec4, m: Mat4): Vec4 { - const dst: Vec4 = [0,0,0,0]; - for (let i = 0; i < 4; ++i) { - for (let j = 0; j < 4; ++j) { - dst[i]! += v[j]! * m[j * 4 + i]!; // ts is not smart enough to see that we set dst[i] to a number already - } + +export function m4Vec4multiply(m: Mat4, v: Vec4): Vec4 { + + return { + x: v.x * m[0][0] + v.y * m[1][0] + v.z * m[2][0] + v.w * m[3][0], + y: v.x * m[0][1] + v.y * m[1][1] + v.z * m[2][1] + v.w * m[3][1], + z: v.x * m[0][2] + v.y * m[1][2] + v.z * m[2][2] + v.w * m[3][2], + w: v.x * m[0][3] + v.y * m[1][3] + v.z * m[2][3] + v.w * m[3][3] } - return dst; } + export function m4PositionMultiply(v: Vec3, m: Mat4): Vec3 { - const v1: Vec4 = [...v, 1] - const dst: Vec4 = [0,0,0,0]; - for (let i = 0; i < 4; ++i) { - for (let j = 0; j < 4; ++j) { - dst[i]! += v1[j]! * m[j * 4 + i]!; // ts is not smart enough to see that we set dst[i] to a number already - } - } - return [dst[0]/dst[3],dst[1]/dst[3],dst[2]/dst[3]]; - } + + const v4 = {...v, w: 1} + const dst = m4Vec4multiply(m, v4) + + return {x: dst.x/dst.w, y: dst.y/dst.w, z: dst.z/dst.w } +} export function m4DirectionMultiply(v: Vec3, m: Mat4): Vec3 { - const v1: Vec4 = [...v, 0] - const dst: Vec4 = [0,0,0,0]; - for (let i = 0; i < 4; ++i) { - for (let j = 0; j < 4; ++j) { - dst[i]! += v1[j]! * m[j * 4 + i]!; // ts is not smart enough to see that we set dst[i] to a number already - } - } - return [dst[0],dst[1],dst[2]]; - } + + const v4 = {...v, w: 0} + const dst = m4Vec4multiply(m, v4) + + return { x: dst.x, y: dst.y, z: dst.z }; -export function m4RayMultiply(ray: Ray, m: Mat4) { - return { - origin: m4PositionMultiply(ray.origin, m), - direction: m4DirectionMultiply(ray.direction, m) - - } } + export function m4fromPositionAndEuler(position: Vec3, euler: Vec3): Mat4 { - let mat4 = m4translate(m4yRotation(0), position[0], position[1], position[2]) ; - mat4 = m4xRotate(mat4, euler[0]); - mat4 = m4yRotate(mat4, euler[1]); - mat4 = m4zRotate(mat4, euler[2]); + let mat4 = m4translate(m4yRotation(0), position.x, position.y, position.z) ; + mat4 = m4xRotate(mat4, euler.x); + mat4 = m4yRotate(mat4, euler.y); + mat4 = m4zRotate(mat4, euler.z); return mat4; } \ No newline at end of file diff --git a/demo-01-ts-webgl/lib/mesh.ts b/demo-01-ts-webgl/lib/mesh.ts index 0bb3f7d..fb791af 100644 --- a/demo-01-ts-webgl/lib/mesh.ts +++ b/demo-01-ts-webgl/lib/mesh.ts @@ -1,5 +1,5 @@ -import { Vec3 } from "./vec"; +import { Color } from "./vec"; export type Vertices = { positions: Float32Array, @@ -9,8 +9,8 @@ export type Vertices = { } export type Material = { - color: Vec3 - specularColor: Vec3 + color: Color + specularColor: Color shininess: number } diff --git a/demo-01-ts-webgl/lib/raycast.ts b/demo-01-ts-webgl/lib/raycast.ts index 67dee8a..43cf2fc 100644 --- a/demo-01-ts-webgl/lib/raycast.ts +++ b/demo-01-ts-webgl/lib/raycast.ts @@ -60,26 +60,18 @@ export function rayIntersectsVertices(ray: Ray, vertices: Vertices): Intersectio const intersections: Intersection[] = [] const positions = vertices.positions - - // console.log("positions", positions) - // console.log("length", positions.length) for (let i = 0; i < positions.length; i += 9) { const triangle: Triangle = [ - [positions[i]!,positions[i+1]!,positions[i+2]!], - [positions[i+3]!,positions[i+4]!,positions[i+5]!], - [positions[i+6]!,positions[i+7]!,positions[i+8]!] + {x: positions[i]!, y: positions[i+1]!,z: positions[i+2]!}, + {x: positions[i+3]!,y: positions[i+4]!,z: positions[i+5]!}, + {x: positions[i+6]!,y: positions[i+7]!,z: positions[i+8]!} ] const intersectionPoint = rayIntersectsTriangle(ray, triangle) - - // console.log("i", i) - // console.log("triangle", triangle) - // console.log("intersection", intersection) if (intersectionPoint) { - // console.log("adding intersection", intersection) intersections.push({ point: intersectionPoint, triangleIdx: i / 9}) } } @@ -109,7 +101,7 @@ export function rayIntersectsSceneNode(ray: Ray, node: SceneNode): Intersection[ while (nodeStack.length > 0) { const nodeUnderTest = nodeStack.pop()! - // console.log(nodeUnderTest) + if (nodeUnderTest.mesh) { // transform the ray into mesh space const inverseTransform = m4inverse(nodeUnderTest._worldTransform) @@ -179,9 +171,10 @@ export function getWorldRayFromClipSpaceAndCamera( clipSpacePoint: Vec2, camera: Camera) { - const [x,y] = clipSpacePoint - const nearPoint: Vec3 = [x, y, -1]; - const farPoint: Vec3 = [x, y, 1]; + const x = clipSpacePoint.x + const y = clipSpacePoint.y + const nearPoint: Vec3 = {x, y, z: -1}; + const farPoint: Vec3 = {x, y, z: 1}; const viewMatrix = m4inverse(camera.transform); const projectionMatrix = getProjectionMatrix(camera); @@ -194,11 +187,11 @@ export function getWorldRayFromClipSpaceAndCamera( const rayDirection = subtractVectors(worldFar, worldNear); - const rayDirNorm = normalize(rayDirection); + normalize(rayDirection); const worldRay: Ray = { origin: rayOrigin, - direction: rayDirNorm as Vec3 + direction: rayDirection }; return worldRay @@ -213,7 +206,7 @@ export function sortBySceneDepth(intersections: Intersection[], camera: Camera const glPosA = m4PositionMultiply(a.point, viewProj) const glPosB = m4PositionMultiply(b.point, viewProj) - return glPosA[2] - glPosB[2] + return glPosA.z - glPosB.z }) return sorted diff --git a/demo-01-ts-webgl/lib/scene.ts b/demo-01-ts-webgl/lib/scene.ts index 86bdd56..e804d43 100644 --- a/demo-01-ts-webgl/lib/scene.ts +++ b/demo-01-ts-webgl/lib/scene.ts @@ -1,5 +1,6 @@ import { m4fromPositionAndEuler, m4multiply, Mat4 } from "./mat4"; import { Mesh } from "./mesh"; +import { POS_ORIGIN, ROT_NONE } from "./vec"; export type SceneNode = { @@ -16,7 +17,7 @@ export type Scene = SceneNode[] export function updateWorldTransform(node: SceneNode) { // n.b. this assumes the parent world transform is always up-to-date so we must keep it that way - const parentWorldTransform = node.parent?._worldTransform ?? m4fromPositionAndEuler([0,0,0], [0,0,0]); + const parentWorldTransform = node.parent?._worldTransform ?? m4fromPositionAndEuler(POS_ORIGIN, ROT_NONE); node._worldTransform = m4multiply(parentWorldTransform, node._localTransform); node.children.forEach(child => updateWorldTransform(child)) diff --git a/demo-01-ts-webgl/lib/vec.ts b/demo-01-ts-webgl/lib/vec.ts index cc846bd..526c9e2 100644 --- a/demo-01-ts-webgl/lib/vec.ts +++ b/demo-01-ts-webgl/lib/vec.ts @@ -1,37 +1,97 @@ -export type Vec2 = [number, number] -export type Vec3 = [number, number, number] -export type Vec4 = [number, number, number, number] +export type Vec2 = { x: number, y: number } +export type Vec3 = { x: number, y: number, z: number } +export type Vec4 = { x: number, y: number, z: number, w: number } + +export type Euler = Vec3 +export type Color = { r: number, g: number, b: number } + +export const QUAT_ORIGIN: Vec4 = { x: 0, y: 0, z: 0, w: 0 } +export const POS_ORIGIN: Vec3 = { x: 0, y: 0, z: 0 } +export const ROT_NONE: Vec3 = { x: 0, y: 0, z: 0 } + +export function colorToArray(col: Color): [number, number, number] { + return [col.r, col.g, col.b] +} + +export function eulerToArray(eul: Euler): [number, number, number] { + return [eul.x, eul.y, eul.z] +} + +export function vec3ToArray(vec: Vec3): [number, number, number] { + return [vec.x, vec.y, vec.z] +} + +export function vec4ToArray(vec: Vec4): [number, number, number, number] { + return [vec.x, vec.y, vec.z, vec.w] +} export function subtractVectors(a: Vec3, b: Vec3): Vec3 { - return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; + return { + x: a.x - b.x, + y: a.y - b.y, + z: a.z - b.z + }; } export function addVectors(a: Vec3, b: Vec3): Vec3 { - return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; + return { + x: a.x + b.x, + y: a.y + b.y, + z: a.z + b.z + }; } export function scaleVector(vec: Vec3, scalar: number): Vec3 { - return [vec[0] * scalar, vec[1] * scalar, vec[2] * scalar] + return { + x: vec.x * scalar, + y: vec.y * scalar, + z: vec.z * scalar + } +} + +// operates in-place +export function normalize(v: Vec3): void { + const length = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + // make sure we don't divide by 0. + if (length > 0.00001) { + + v.x /= length + v.y /= length + v.z /= length + + } else { + + v.x = 0, + v.y = 0, + v.z = 0 + }; } -export function normalize(v: Vec3): Vec3 { - const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); +// immutable, returns a new one +export function normalized(v: Vec3): Vec3 { + const length = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); // make sure we don't divide by 0. if (length > 0.00001) { - return [v[0] / length, v[1] / length, v[2] / length]; + return { + x: v.x / length, + y: v.y / length, + z: v.z / length + }; } else { - return [0, 0, 0]; + return {x: 0, y: 0, z: 0}; } } export function cross(a: Vec3, b: Vec3): Vec3 { - return [a[1] * b[2] - a[2] * b[1], - a[2] * b[0] - a[0] * b[2], - a[0] * b[1] - a[1] * b[0]]; + return { + x: a.y * b.z - a.z * b.y, + y: a.z * b.x - a.x * b.z, + z: a.x * b.y - a.y * b.x + }; } export function dot(a: Vec3, b: Vec3): number { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + return a.x * b.x + a.y * b.y + a.z * b.z; } export function calculateOrbitPosition( @@ -44,9 +104,9 @@ export function calculateOrbitPosition( elevation = Math.max(0.01, Math.min(Math.PI / 2 - 0.01, elevation)); // Spherical to Cartesian - const x = orbitTarget[0] + orbitRadius * Math.sin(elevation) * Math.sin(azimuth); - const y = orbitTarget[1] + orbitRadius * Math.cos(elevation); - const z = orbitTarget[2] + orbitRadius * Math.sin(elevation) * Math.cos(azimuth); + const x = orbitTarget.x + orbitRadius * Math.sin(elevation) * Math.sin(azimuth); + const y = orbitTarget.y + orbitRadius * Math.cos(elevation); + const z = orbitTarget.z + orbitRadius * Math.sin(elevation) * Math.cos(azimuth); - return [x,y,z] + return {x,y,z} } \ No newline at end of file diff --git a/ts-tests/raycastScene.test.ts b/ts-tests/raycastScene.test.ts index 8378293..0a78bff 100644 --- a/ts-tests/raycastScene.test.ts +++ b/ts-tests/raycastScene.test.ts @@ -3,6 +3,7 @@ import { Intersection, Ray, rayIntersectsSceneNode } from "demo-01-ts-webgl/lib/ import { Material, Vertices } from 'demo-01-ts-webgl/lib/mesh'; import { initSceneNode, setParent } from 'demo-01-ts-webgl/lib/scene'; import { m4fromPositionAndEuler } from 'demo-01-ts-webgl/lib/mat4'; +import { POS_ORIGIN, ROT_NONE } from 'demo-01-ts-webgl/lib/vec'; const positions = new Float32Array([ @@ -31,49 +32,49 @@ const vertices: Vertices = { } const material: Material = { - color: [1,1,1], - specularColor: [1,1,1], + color: { r: 1, g: 1, b:1}, + specularColor: {r: 1, g: 1, b: 1}, shininess: 0 } test('it correctly finds an intersection with position transform', () => { - const node = initSceneNode(m4fromPositionAndEuler([-2,0,0], [0,0,0]), { + const node = initSceneNode(m4fromPositionAndEuler({x: -2, y: 0,z: 0}, ROT_NONE), { vertices, material }) const ray: Ray = { - origin: [-11, 0.5, 0], - direction: [0, -1, 0] + origin: {x: -11, y: 0.5, z: 0}, + direction: {x: 0, y: -1, z: 0} } const result = rayIntersectsSceneNode(ray, node) - const expected: Intersection[] = [{ point: [-11, 0.0, 0], triangleIdx: 0}] + const expected: Intersection[] = [{ point: {x: -11, y: 0.0, z: 0}, triangleIdx: 0}] expect(result, "intersection is correct").toEqual(expected) }) test('it correctly finds an intersection with multiple position transforms', () => { - const node = initSceneNode(m4fromPositionAndEuler([-2,0,0], [0,0,0]), { + const node = initSceneNode(m4fromPositionAndEuler({x: -2, y: 0, z: 0}, ROT_NONE), { vertices, material }) - const parentNode = initSceneNode(m4fromPositionAndEuler([-2,0,0], [0,0,0])) + const parentNode = initSceneNode(m4fromPositionAndEuler({ x: -2, y: 0, z: 0}, ROT_NONE)) setParent(node, parentNode) const ray: Ray = { - origin: [-11, 0.5, 0], - direction: [0, -1, 0] + origin: {x: -11, y: 0.5, z: 0}, + direction: {x: 0, y: -1, z: 0} } const result = rayIntersectsSceneNode(ray, parentNode) - const expected: Intersection[] = [{ point: [-11, 0.0, 0], triangleIdx: 0}] + const expected: Intersection[] = [{ point: { x: -11, y: 0.0, z: 0}, triangleIdx: 0}] expect(result, "intersection is correct").toEqual(expected) }) @@ -81,25 +82,36 @@ test('it correctly finds an intersection with multiple position transforms', () test('it correctly finds an intersection with rotation transform', () => { - const node = initSceneNode(m4fromPositionAndEuler([0,0,0], [0,0,Math.PI/4]), { + const node = initSceneNode(m4fromPositionAndEuler(POS_ORIGIN, {x: 0, y: 0, z: Math.PI/4}), { vertices, material }) const ray: Ray = { - origin: [-1, 2, 0], - direction: [0, -1, 0] + origin: { x:-1, y: 2, z: 0}, + direction: {x: 0, y: -1, z: 0} } const result = rayIntersectsSceneNode(ray, node) - const expected: Intersection[] = [{ point: [-1, -1, 0], triangleIdx: 0}] + const expected: Intersection[] = [{ point: {x: -1, y: -1, z: 0}, triangleIdx: 0}] for (let i = 0; i < expected.length; i++) { - const elementofResult = result[0]!.point[i]! - const elementOfExpected = expected[0]!.point[i]! - expect(elementofResult, "intersection element is correct") - .toBeCloseTo(elementOfExpected, 6) + const elementofResult = result[0] + const elementOfExpected = expected[0]! + + expect(elementofResult, "intersection element is preset") + .not.toBe(undefined) + + + expect(elementofResult!.point.x, "intersection point x is correct") + .toBeCloseTo(elementOfExpected.point.x, 6) + + expect(elementofResult!.point.y, "intersection point y is correct") + .toBeCloseTo(elementOfExpected.point.y, 6) + + expect(elementofResult!.point.z, "intersection point z is correct") + .toBeCloseTo(elementOfExpected.point.z, 6) } }) \ No newline at end of file diff --git a/ts-tests/raycastTriangle.test.ts b/ts-tests/raycastTriangle.test.ts index d4d88ba..6bb7a84 100644 --- a/ts-tests/raycastTriangle.test.ts +++ b/ts-tests/raycastTriangle.test.ts @@ -1,21 +1,25 @@ import { expect, test } from 'vitest' import { Ray, rayIntersectsTriangle, Triangle } from "demo-01-ts-webgl/lib/raycast"; -import { normalize } from 'demo-01-ts-webgl/lib/vec'; +import { normalized } from 'demo-01-ts-webgl/lib/vec'; // triangle is symmetrical x-y and just a bit back from origin z -const triangle: Triangle = [[1,0,0.1], [0,1,0.1], [-1, 0, 0.1]] +const triangle: Triangle = [ + { x: 1, y: 0, z: 0.1}, + { x: 0, y: 1, z: 0.1}, + { x: -1, y: 0, z: 0.1} +] test('it correctly finds an intersection', () => { const ray: Ray = { - origin: [0.5, 0.5, 0], - direction: [0, 0, 1] + origin: {x: 0.5, y: 0.5, z: 0}, + direction: {x: 0, y: 0, z: 1 } } const result = rayIntersectsTriangle(ray, triangle) - const expected = [0.5, 0.5, 0.1] + const expected = {x: 0.5, y: 0.5, z: 0.1} expect(result, "intersection is correct").toEqual(expected) }) @@ -24,13 +28,13 @@ test(`it correctly finds an intersection even though the ray direction is not a unit vector`, () => { const ray: Ray = { - origin: [0.5, 0.5, 0], - direction: [0, 0, 2] + origin: {x: 0.5, y: 0.5, z: 0}, + direction: {x: 0, y: 0, z: 2 } } const result = rayIntersectsTriangle(ray, triangle) - const expected = [0.5, 0.5, 0.1] + const expected = {x: 0.5, y: 0.5, z: 0.1} expect(result, "intersection is correct").toEqual(expected) }) @@ -40,13 +44,13 @@ test(`it correctly finds an intersection and doesnt reach the triangle`, () => { const ray: Ray = { - origin: [0.5, 0.5, 0], - direction: [0, 0, 0.012] + origin: {x: 0.5, y: 0.5, z: 0}, + direction: {x: 0, y: 0, z: 0.012} } const result = rayIntersectsTriangle(ray, triangle) - const expected = [0.5, 0.5, 0.1] + const expected = {x: 0.5, y: 0.5, z: 0.1} expect(result, "intersection is correct").toEqual(expected) }) @@ -54,13 +58,13 @@ test(`it correctly finds an intersection test('it correctly finds another intersection', () => { const ray: Ray = { - origin: [0.2, 0.5, 0], - direction: [0, 0, 1] + origin: {x: 0.2, y: 0.5, z: 0}, + direction: {x: 0, y: 0, z: 1} } const result = rayIntersectsTriangle(ray, triangle) - const expected = [0.2, 0.5, 0.1] + const expected = {x: 0.2, y: 0.5, z: 0.1} expect(result, "intersection is correct").toEqual(expected) }) @@ -69,8 +73,8 @@ test('it correctly finds no intersection based on origin', () => { // pointing away from the triangle const ray: Ray = { - origin: [0.5, 0.5, 0.2], - direction: [0, 0, 1] + origin: {x: 0.5, y: 0.5, z: 0.2}, + direction: {x: 0, y: 0, z: 1 } } const result = rayIntersectsTriangle(ray, triangle) @@ -84,8 +88,8 @@ test('it correctly finds no intersection based on another origin', () => { // passing by the side of the triangle const ray: Ray = { - origin: [1.5, 0.5, 0.2], - direction: [0, 0, 1] + origin: { x: 1.5, y: 0.5, z: 0.2}, + direction: {x: 0, y: 0, z: 1 } } const result = rayIntersectsTriangle(ray, triangle) @@ -99,8 +103,8 @@ test('it correctly finds no intersection based on direction', () => { // this should intersect the triangles plane, but not the triangle itself const ray: Ray = { - origin: [0.5, 0.5, -10], - direction: normalize([0, 1, 1]) + origin: {x: 0.5, y: 0.5, z: -10}, + direction: normalized({x: 0, y: 1, z:1}) } const result = rayIntersectsTriangle(ray, triangle) @@ -112,11 +116,15 @@ test('it correctly finds no intersection based on direction', () => { test('it correctly finds no intersection based on origin', () => { - const triangle: Triangle = [ [ -10, 0, -10 ], [ -10, 0, 10 ], [ 10, 0, -10 ] ] + const triangle: Triangle = [ + { x: -10, y: 0, z: -10 }, + { x: -10, y: 0, z: 10 }, + { x: 10, y: 0, z: -10 } + ] // this should intersect the triangles plane, but not the triangle itself const ray: Ray = { - origin: [110, 0.5, 0], - direction: [0, -1, 0] + origin: {x: 110, y: 0.5, z: 0}, + direction: {x: 0, y: -1, z: 0} } const result = rayIntersectsTriangle(ray, triangle) @@ -128,11 +136,14 @@ test('it correctly finds no intersection based on origin', () => { test('it correctly finds no intersection based on origin', () => { - const triangle: Triangle = [ [ -10, 0, -10 ], [ -10, 0, 10 ], [ 10, 0, -10 ] ] + const triangle: Triangle = [ + { x: -10, y: 0, z: -10 }, + { x: -10, y: 0, z: 10 }, + { x: 10, y: 0, z: -10 } ] // this should intersect the triangles plane, but not the triangle itself const ray: Ray = { - origin: [1, 0.5, 0], - direction: [0, -1, 0] + origin: { x: 1, y: 0.5, z: 0}, + direction: {x: 0, y: -1, z: 0} } const result = rayIntersectsTriangle(ray, triangle) diff --git a/ts-tests/raycastVertices.test.ts b/ts-tests/raycastVertices.test.ts index 2e4699f..f0784ff 100644 --- a/ts-tests/raycastVertices.test.ts +++ b/ts-tests/raycastVertices.test.ts @@ -34,13 +34,13 @@ const meshVertices: Vertices = { test('it correctly finds an intersection in the first triangle', () => { const ray: Ray = { - origin: [-1, 0.5, 0], - direction: [0, -1, 0] + origin: { x: -1, y: 0.5, z: 0}, + direction: {x: 0, y: -1, z: 0} } const result = rayIntersectsVertices(ray, meshVertices) - const expected: Intersection[] = [{ point: [-1, 0.0, 0], triangleIdx: 0 }] + const expected: Intersection[] = [{ point: {x: -1, y: 0.0, z: 0}, triangleIdx: 0 }] expect(result, "intersection is correct").toEqual(expected) }) @@ -48,13 +48,13 @@ test('it correctly finds an intersection in the first triangle', () => { test('it correctly finds an intersection in the last triangle', () => { const ray: Ray = { - origin: [1, 0.5, 0], - direction: [0, -1, 0] + origin: {x: 1, y: 0.5, z: 0 }, + direction: {x: 0, y: -1, z: 0 } } const result = rayIntersectsVertices(ray, meshVertices) - const expected: Intersection = { point: [1, 0.0, 0], triangleIdx: 1 } + const expected: Intersection = { point: {x: 1, y: 0.0, z: 0}, triangleIdx: 1 } expect(result.length, "only one hit").toEqual(1) expect(result[0], "intersection is correct").toEqual(expected) @@ -63,8 +63,8 @@ test('it correctly finds an intersection in the last triangle', () => { test('it correctly finds no intersection', () => { const ray: Ray = { - origin: [110, 0.5, 0], - direction: [0, -1, 0] + origin: {x: 110, y: 0.5, z: 0}, + direction: {x: 0, y: -1, z: 0} } const result = rayIntersectsVertices(ray, meshVertices) diff --git a/ts-tests/scene.test.ts b/ts-tests/scene.test.ts index c4cbd66..4120eb8 100644 --- a/ts-tests/scene.test.ts +++ b/ts-tests/scene.test.ts @@ -1,14 +1,14 @@ import { expect, test } from 'vitest' import { setParent, initSceneNode } from "demo-01-ts-webgl/lib/scene"; import { m4fromPositionAndEuler } from 'demo-01-ts-webgl/lib/mat4'; +import { POS_ORIGIN, ROT_NONE } from 'demo-01-ts-webgl/lib/vec'; test('setParent updates parent and children correctly', () => { -const childNode = initSceneNode(m4fromPositionAndEuler([1, 1, 1], [0, 0, 0])) +const childNode = initSceneNode(m4fromPositionAndEuler({x: 1, y: 1, z: 1}, ROT_NONE)) -const parentNode1 = initSceneNode(m4fromPositionAndEuler([0, 0, 0], [0, 0, 0])) - -const parentNode2 = initSceneNode(m4fromPositionAndEuler([0, 0, 0], [0, 0, 0])) +const parentNode1 = initSceneNode(m4fromPositionAndEuler(POS_ORIGIN, ROT_NONE)) +const parentNode2 = initSceneNode(m4fromPositionAndEuler(POS_ORIGIN, ROT_NONE)) setParent(childNode, parentNode1);