From 89576c33bd99345eb2475ae5a52dbd63b90a6fbc Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Sun, 30 Jul 2023 21:28:48 +0200 Subject: [PATCH] Work on public APIs --- src/data/Camera.ts | 53 ++++++- src/data/Matrix4x4.ts | 282 +++++++++++++++++++++++++++++------ src/data/Node.ts | 182 +++++++++++++++++++++- src/data/Scene.ts | 3 + src/oktaeder.ts | 4 + src/resources/IndexBuffer.ts | 2 +- src/resources/Texture2D.ts | 41 +++++ src/shader.ts | 19 +++ 8 files changed, 538 insertions(+), 48 deletions(-) diff --git a/src/data/Camera.ts b/src/data/Camera.ts index a033b61..5486c17 100644 --- a/src/data/Camera.ts +++ b/src/data/Camera.ts @@ -52,7 +52,33 @@ export class CameraOrthographic { this._node = null; } - detach(): Camera { + set name(value: string) { this._name = value; } + get name(): string { return this._name; } + + set verticalSize(value: number) { this._verticalSize = value; } + get verticalSize(): number { return this._verticalSize; } + + set nearPlane(value: number) { this._nearPlane = value; } + get nearPlane(): number { return this._nearPlane; } + + set farPlane(value: number) { this._farPlane = value; } + get farPlane(): number { return this._farPlane; } + + attach(node: Node): CameraOrthographic { + if (this._node !== null) { + this._node._camera = null; + } + + if (node._camera !== null) { + node._camera._node = null; + } + + node._camera = this; + this._node = node; + return this; + } + + detach(): CameraOrthographic { if (this._node === null) { return this; } @@ -93,7 +119,30 @@ export class CameraPerspective { this._node = null; } - detach(): Camera { + set name(value: string) { this._name = value; } + get name(): string { return this._name; } + + set nearPlane(value: number) { this._nearPlane = value; } + get nearPlane(): number { return this._nearPlane; } + + set farPlane(value: number) { this._farPlane = value; } + get farPlane(): number { return this._farPlane; } + + attach(node: Node): CameraPerspective { + if (this._node !== null) { + this._node._camera = null; + } + + if (node._camera !== null) { + node._camera._node = null; + } + + node._camera = this; + this._node = node; + return this; + } + + detach(): CameraPerspective { if (this._node === null) { return this; } diff --git a/src/data/Matrix4x4.ts b/src/data/Matrix4x4.ts index f186531..bbcbe80 100644 --- a/src/data/Matrix4x4.ts +++ b/src/data/Matrix4x4.ts @@ -90,6 +90,15 @@ export class Matrix4x4 { return new Matrix4x4(...tuple); } + static identity(): Matrix4x4 { + return new Matrix4x4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ) + } + static fromTranslation(translation: Vector3Object): Matrix4x4 { return new Matrix4x4( 1, 0, 0, 0, @@ -127,22 +136,22 @@ export class Matrix4x4 { ); } - static fromTRS(t: Vector3Object, r: QuaternionObject, s: Vector3Object): Matrix4x4 { - const xx = r.x * r.x; - const xy = r.x * r.y; - const xz = r.x * r.z; - const xw = r.x * r.w; - const yy = r.y * r.y; - const yz = r.y * r.z; - const yw = r.y * r.w; - const zz = r.z * r.z; - const zw = r.z * r.w; + static fromTranslationRotationScale(translation: Vector3Object, rotation: QuaternionObject, scale: Vector3Object): Matrix4x4 { + const xx = rotation.x * rotation.x; + const xy = rotation.x * rotation.y; + const xz = rotation.x * rotation.z; + const xw = rotation.x * rotation.w; + const yy = rotation.y * rotation.y; + const yz = rotation.y * rotation.z; + const yw = rotation.y * rotation.w; + const zz = rotation.z * rotation.z; + const zw = rotation.z * rotation.w; return new Matrix4x4( - s.x * (1 - 2 * (yy + zz)), s.x * 2 * (xy + zw), s.x * 2 * (xz - yw), 0, - s.y * 2 * (xy - zw), s.y * (1 - 2 * (xx + zz)), s.y * 2 * (yz + xw), 0, - s.z * 2 * (xz + yw), s.z * 2 * (yz - xw), s.z * (1 - 2 * (xx + yy)), 0, - t.x, t.y, t.z, 1, + scale.x * (1 - 2 * (yy + zz)), scale.x * 2 * (xy + zw), scale.x * 2 * (xz - yw), 0, + scale.y * 2 * (xy - zw), scale.y * (1 - 2 * (xx + zz)), scale.y * 2 * (yz + xw), 0, + scale.z * 2 * (xz + yw), scale.z * 2 * (yz - xw), scale.z * (1 - 2 * (xx + yy)), 0, + translation.x, translation.y, translation.z, 1, ); } @@ -186,6 +195,26 @@ export class Matrix4x4 { return this; } + setIdentity(): Matrix4x4 { + this.ix = 1; + this.iy = 0; + this.iz = 0; + this.iw = 0; + this.jx = 0; + this.jy = 1; + this.jz = 0; + this.jw = 0; + this.kx = 0; + this.ky = 0; + this.kz = 1; + this.kw = 0; + this.tx = 0; + this.ty = 0; + this.tz = 0; + this.tw = 1; + return this; + } + setTranslation(translation: Vector3Object): Matrix4x4 { this.ix = 1; this.iy = 0; @@ -256,58 +285,225 @@ export class Matrix4x4 { return this; } - setTRS(t: Vector3Object, r: QuaternionObject, s: Vector3Object): Matrix4x4 { - const xx = r.x * r.x; - const xy = r.x * r.y; - const xz = r.x * r.z; - const xw = r.x * r.w; - const yy = r.y * r.y; - const yz = r.y * r.z; - const yw = r.y * r.w; - const zz = r.z * r.z; - const zw = r.z * r.w; + setTranslationRotationScale(translation: Vector3Object, rotation: QuaternionObject, scale: Vector3Object): Matrix4x4 { + const xx = rotation.x * rotation.x; + const xy = rotation.x * rotation.y; + const xz = rotation.x * rotation.z; + const xw = rotation.x * rotation.w; + const yy = rotation.y * rotation.y; + const yz = rotation.y * rotation.z; + const yw = rotation.y * rotation.w; + const zz = rotation.z * rotation.z; + const zw = rotation.z * rotation.w; - this.ix = s.x * (1 - 2 * (yy + zz)); - this.iy = s.x * 2 * (xy + zw); - this.iz = s.x * 2 * (xz - yw); + this.ix = scale.x * (1 - 2 * (yy + zz)); + this.iy = scale.x * 2 * (xy + zw); + this.iz = scale.x * 2 * (xz - yw); this.iw = 0; - this.jx = s.y * 2 * (xy - zw); - this.jy = s.y * (1 - 2 * (xx + zz)); - this.jz = s.y * 2 * (yz + xw); + this.jx = scale.y * 2 * (xy - zw); + this.jy = scale.y * (1 - 2 * (xx + zz)); + this.jz = scale.y * 2 * (yz + xw); this.jw = 0; - this.kx = s.z * 2 * (xz + yw); - this.ky = s.z * 2 * (yz - xw); - this.kz = s.z * (1 - 2 * (xx + yy)); + this.kx = scale.z * 2 * (xz + yw); + this.ky = scale.z * 2 * (yz - xw); + this.kz = scale.z * (1 - 2 * (xx + yy)); this.kw = 0; - this.tx = t.x; - this.ty = t.y; - this.tz = t.z; + this.tx = translation.x; + this.ty = translation.y; + this.tz = translation.z; this.tw = 1; return this; } - add(m: Matrix4x4): Matrix4x4 { - throw new Error("TODO"); + addMatrix(m: Matrix4x4): Matrix4x4 { + this.ix += m.ix; + this.iy += m.iy; + this.iz += m.iz; + this.iw += m.iw; + this.jx += m.jx; + this.jy += m.jy; + this.jz += m.jz; + this.jw += m.jw; + this.kx += m.kx; + this.ky += m.ky; + this.kz += m.kz; + this.kw += m.kw; + this.tx += m.tx; + this.ty += m.ty; + this.tz += m.tz; + this.tw += m.tw; return this; } - sub(m: Matrix4x4): Matrix4x4 { - throw new Error("TODO"); + addMatrices(a: Matrix4x4, b: Matrix4x4): Matrix4x4 { + this.ix = a.ix + b.ix; + this.iy = a.iy + b.iy; + this.iz = a.iz + b.iz; + this.iw = a.iw + b.iw; + this.jx = a.jx + b.jx; + this.jy = a.jy + b.jy; + this.jz = a.jz + b.jz; + this.jw = a.jw + b.jw; + this.kx = a.kx + b.kx; + this.ky = a.ky + b.ky; + this.kz = a.kz + b.kz; + this.kw = a.kw + b.kw; + this.tx = a.tx + b.tx; + this.ty = a.ty + b.ty; + this.tz = a.tz + b.tz; + this.tw = a.tw + b.tw; + return this; + } + + subMatrix(m: Matrix4x4): Matrix4x4 { + this.ix -= m.ix; + this.iy -= m.iy; + this.iz -= m.iz; + this.iw -= m.iw; + this.jx -= m.jx; + this.jy -= m.jy; + this.jz -= m.jz; + this.jw -= m.jw; + this.kx -= m.kx; + this.ky -= m.ky; + this.kz -= m.kz; + this.kw -= m.kw; + this.tx -= m.tx; + this.ty -= m.ty; + this.tz -= m.tz; + this.tw -= m.tw; + return this; + } + + subMatrices(a: Matrix4x4, b: Matrix4x4): Matrix4x4 { + this.ix = a.ix - b.ix; + this.iy = a.iy - b.iy; + this.iz = a.iz - b.iz; + this.iw = a.iw - b.iw; + this.jx = a.jx - b.jx; + this.jy = a.jy - b.jy; + this.jz = a.jz - b.jz; + this.jw = a.jw - b.jw; + this.kx = a.kx - b.kx; + this.ky = a.ky - b.ky; + this.kz = a.kz - b.kz; + this.kw = a.kw - b.kw; + this.tx = a.tx - b.tx; + this.ty = a.ty - b.ty; + this.tz = a.tz - b.tz; + this.tw = a.tw - b.tw; + return this; + } + + negate(): Matrix4x4 { + this.ix = -this.ix; + this.iy = -this.iy; + this.iz = -this.iz; + this.iw = -this.iw; + this.jx = -this.jx; + this.jy = -this.jy; + this.jz = -this.jz; + this.jw = -this.jw; + this.kx = -this.kx; + this.ky = -this.ky; + this.kz = -this.kz; + this.kw = -this.kw; + this.tx = -this.tx; + this.ty = -this.ty; + this.tz = -this.tz; + this.tw = -this.tw; return this; } mulScalar(k: number): Matrix4x4 { - throw new Error("TODO"); + this.ix *= k; + this.iy *= k; + this.iz *= k; + this.iw *= k; + this.jx *= k; + this.jy *= k; + this.jz *= k; + this.jw *= k; + this.kx *= k; + this.ky *= k; + this.kz *= k; + this.kw *= k; + this.tx *= k; + this.ty *= k; + this.tz *= k; + this.tw *= k; return this; } mulMatrix(m: Matrix4x4): Matrix4x4 { - throw new Error("TODO"); + const ix = this.ix * m.ix + this.jx * m.iy + this.kx * m.iz + this.tx * m.iw; + const iy = this.iy * m.ix + this.jy * m.iy + this.ky * m.iz + this.ty * m.iw; + const iz = this.iz * m.ix + this.jz * m.iy + this.kz * m.iz + this.tz * m.iw; + const iw = this.iw * m.ix + this.jw * m.iy + this.kw * m.iz + this.tw * m.iw; + const jx = this.ix * m.jx + this.jx * m.jy + this.kx * m.jz + this.tx * m.jw; + const jy = this.iy * m.jx + this.jy * m.jy + this.ky * m.jz + this.ty * m.jw; + const jz = this.iz * m.jx + this.jz * m.jy + this.kz * m.jz + this.tz * m.jw; + const jw = this.iw * m.jx + this.jw * m.jy + this.kw * m.jz + this.tw * m.jw; + const kx = this.ix * m.kx + this.jx * m.ky + this.kx * m.kz + this.tx * m.kw; + const ky = this.iy * m.kx + this.jy * m.ky + this.ky * m.kz + this.ty * m.kw; + const kz = this.iz * m.kx + this.jz * m.ky + this.kz * m.kz + this.tz * m.kw; + const kw = this.iw * m.kx + this.jw * m.ky + this.kw * m.kz + this.tw * m.kw; + const tx = this.ix * m.tx + this.jx * m.ty + this.kx * m.tz + this.tx * m.tw; + const ty = this.iy * m.tx + this.jy * m.ty + this.ky * m.tz + this.ty * m.tw; + const tz = this.iz * m.tx + this.jz * m.ty + this.kz * m.tz + this.tz * m.tw; + const tw = this.iw * m.tx + this.jw * m.ty + this.kw * m.tz + this.tw * m.tw; + this.ix = ix; + this.iy = iy; + this.iz = iz; + this.iw = iw; + this.jx = jx; + this.jy = jy; + this.jz = jz; + this.jw = jw; + this.kx = kx; + this.ky = ky; + this.kz = kz; + this.kw = kw; + this.tx = tx; + this.ty = ty; + this.tz = tz; + this.tw = tw; return this; } premulMatrix(m: Matrix4x4): Matrix4x4 { - throw new Error("TODO"); + const ix = m.ix * this.ix + m.jx * this.iy + m.kx * this.iz + m.tx * this.iw; + const iy = m.iy * this.ix + m.jy * this.iy + m.ky * this.iz + m.ty * this.iw; + const iz = m.iz * this.ix + m.jz * this.iy + m.kz * this.iz + m.tz * this.iw; + const iw = m.iw * this.ix + m.jw * this.iy + m.kw * this.iz + m.tw * this.iw; + const jx = m.ix * this.jx + m.jx * this.jy + m.kx * this.jz + m.tx * this.jw; + const jy = m.iy * this.jx + m.jy * this.jy + m.ky * this.jz + m.ty * this.jw; + const jz = m.iz * this.jx + m.jz * this.jy + m.kz * this.jz + m.tz * this.jw; + const jw = m.iw * this.jx + m.jw * this.jy + m.kw * this.jz + m.tw * this.jw; + const kx = m.ix * this.kx + m.jx * this.ky + m.kx * this.kz + m.tx * this.kw; + const ky = m.iy * this.kx + m.jy * this.ky + m.ky * this.kz + m.ty * this.kw; + const kz = m.iz * this.kx + m.jz * this.ky + m.kz * this.kz + m.tz * this.kw; + const kw = m.iw * this.kx + m.jw * this.ky + m.kw * this.kz + m.tw * this.kw; + const tx = m.ix * this.tx + m.jx * this.ty + m.kx * this.tz + m.tx * this.tw; + const ty = m.iy * this.tx + m.jy * this.ty + m.ky * this.tz + m.ty * this.tw; + const tz = m.iz * this.tx + m.jz * this.ty + m.kz * this.tz + m.tz * this.tw; + const tw = m.iw * this.tx + m.jw * this.ty + m.kw * this.tz + m.tw * this.tw; + this.ix = ix; + this.iy = iy; + this.iz = iz; + this.iw = iw; + this.jx = jx; + this.jy = jy; + this.jz = jz; + this.jw = jw; + this.kx = kx; + this.ky = ky; + this.kz = kz; + this.kw = kw; + this.tx = tx; + this.ty = ty; + this.tz = tz; + this.tw = tw; return this; } } diff --git a/src/data/Node.ts b/src/data/Node.ts index 0e8d987..4d5ed84 100644 --- a/src/data/Node.ts +++ b/src/data/Node.ts @@ -44,7 +44,10 @@ export class Node { /** backreference */ _parent: Node | null; + _localMatrixNeedsUpdate: boolean; _localMatrix: Matrix4x4; + + _worldMatrixNeedsUpdate: boolean; _worldMatrix: Matrix4x4; constructor({ @@ -71,8 +74,21 @@ export class Node { this._parent = null; - this._localMatrix = Matrix4x4.fromTRS(this._translation, this._rotation, this._scale); - this._worldMatrix = Matrix4x4.fromObject(this._localMatrix); + this._localMatrixNeedsUpdate = true; + this._localMatrix = new Matrix4x4( + NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, + ); + + this._worldMatrixNeedsUpdate = true; + this._worldMatrix = new Matrix4x4( + NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, + ); if (this._camera !== null) { this._camera._node = this; @@ -84,6 +100,168 @@ export class Node { } } } + + setTranslation(value: Vector3Object): Node { + this._translation.setObject(value); + this._localMatrixNeedsUpdate = true; + this._setWorldMatrixNeedsUpdateRecursive(true); + return this; + } + + getTranslation(res: Vector3): Vector3 { + res.setObject(this._translation); + return res; + } + + setRotation(value: QuaternionObject): Node { + this._rotation.setObject(value); + this._localMatrixNeedsUpdate = true; + this._setWorldMatrixNeedsUpdateRecursive(true); + return this; + } + + getRotation(res: Quaternion): Quaternion { + res.setObject(this._rotation); + return res; + } + + setScale(value: Vector3Object): Node { + this._scale.setObject(value); + this._localMatrixNeedsUpdate = true; + this._setWorldMatrixNeedsUpdateRecursive(true); + return this; + } + + getScale(res: Vector3): Vector3 { + res.setObject(this._scale); + return res; + } + + set camera(value: Camera | null) { + if (value !== null) { + this.attachCamera(value); + } else { + this.detachCamera(); + } + } + get camera(): Camera | null { return this._camera; } + + attachCamera(camera: Camera): Node { + if (this._camera !== null) { + this._camera._node = null; + } + + this._camera = camera; + + if (camera._node !== null) { + camera._node._camera = null; + } + + camera._node = this; + this._camera = camera; + return this; + } + + detachCamera(): Node { + if (this._camera === null) { + return this; + } + + this._camera._node = null; + this._camera = null; + return this; + } + + set mesh(value: Mesh | null) { this._mesh = value; } + get mesh(): Mesh | null { return this._mesh; } + + setMaterials(value: readonly Material[]): Node { + this._materials.length = 0; + this._materials.push(...value); + return this; + } + + getMaterials(res: Material[]): Material[] { + res.length = 0; + res.push(...this._materials); + return res; + } + + // TODO children + + set parent(value: Node | null) { + if (value !== null) { + this.attach(value); + } else { + this.detach(); + } + } + get parent(): Node | null { return this._parent; } + + attach(node: Node): Node { + if (this._parent !== null) { + this._parent._children.splice(this._parent._children.indexOf(this), 1); + } + + node._children.push(this); + this._parent = node; + this._setWorldMatrixNeedsUpdateRecursive(true); + return this; + } + + detach(): Node { + if (this._parent === null) { + return this; + } + + this._parent._children.splice(this._parent._children.indexOf(this), 1); + this._parent = null; + this._setWorldMatrixNeedsUpdateRecursive(true); + return this; + } + + getLocalMatrix(res: Matrix4x4): Matrix4x4 { + this._updateLocalMatrix(); + res.setObject(this._localMatrix); + return res; + } + + getWorldMatrix(res: Matrix4x4): Matrix4x4 { + this._updateWorldMatrix(); + res.setObject(this._worldMatrix); + return res; + } + + _updateLocalMatrix(): Node { + if (!this._localMatrixNeedsUpdate) { + return this; + } + this._localMatrix.setTranslationRotationScale(this._translation, this._rotation, this._scale); + this._localMatrixNeedsUpdate = false; + return this; + } + + _updateWorldMatrix(): Node { + if (!this._worldMatrixNeedsUpdate) { + return this; + } + this._updateLocalMatrix(); + if (this._parent !== null) { + this._parent.getWorldMatrix(this._worldMatrix); + this._worldMatrix.premulMatrix(this._localMatrix); + } else { + this._worldMatrix.setObject(this._localMatrix); + } + return this; + } + + _setWorldMatrixNeedsUpdateRecursive(value: boolean): Node { + this._localMatrixNeedsUpdate = true; + for (const child of this._children) { + child._setWorldMatrixNeedsUpdateRecursive(value); + } + return this; + } } Object.defineProperty(Node.prototype, "type", { value: "Node" }); diff --git a/src/data/Scene.ts b/src/data/Scene.ts index 614fa21..d35499f 100644 --- a/src/data/Scene.ts +++ b/src/data/Scene.ts @@ -28,6 +28,9 @@ export class Scene { this._nodes = nodes; } + + set name(value: string) { this._name = value; } + get name(): string { return this._name; } } Object.defineProperty(Scene.prototype, "type", { value: "Scene" }); diff --git a/src/oktaeder.ts b/src/oktaeder.ts index 911eaed..caf9fa4 100644 --- a/src/oktaeder.ts +++ b/src/oktaeder.ts @@ -148,6 +148,10 @@ export class Renderer { depthStoreOp: "store", }, }); + + void scene; + void camera; + pass.end(); const commandBuffer = encoder.finish(); diff --git a/src/resources/IndexBuffer.ts b/src/resources/IndexBuffer.ts index 1e17c42..42765b4 100644 --- a/src/resources/IndexBuffer.ts +++ b/src/resources/IndexBuffer.ts @@ -107,7 +107,7 @@ export class IndexBuffer { ensureSizeDiscard({ indexFormat = this._indexFormat, indexCount = this.indexCount, - }): IndexBuffer { + }: IndexBufferResizeProps): IndexBuffer { if (this.indexCount >= indexCount && indexSize(this._indexFormat) >= indexSize(indexFormat)) { return this; } diff --git a/src/resources/Texture2D.ts b/src/resources/Texture2D.ts index 373c24c..cf2acec 100644 --- a/src/resources/Texture2D.ts +++ b/src/resources/Texture2D.ts @@ -23,6 +23,12 @@ export interface Texture2DProps { readonly format: Texture2DFormat; } +export interface Texture2DResizeProps { + readonly width?: number; + readonly height?: number; + readonly format?: Texture2DFormat; +} + export interface Texture2DAdvancedWriteProps { readonly origin: Vector2Object | Vector2Tuple, readonly data: BufferSource | SharedArrayBuffer, @@ -69,6 +75,11 @@ export class Texture2D { this._format = format; } + /** + * Destroys owned GPU resources. The texture should not be used after + * calling this method. + * @returns `this` for chaining + */ dispose(): Texture2D { this._texture.destroy(); return this; @@ -124,6 +135,36 @@ export class Texture2D { ); return this; } + + /** + * Resize the texture and/or change its format, discarding currently stored + * data. + * @param props Desired texture properties. Any unspecified property will + * stay unchanged. + * @returns `this` for chaining + */ + resizeDiscard({ + width = this._texture.width, + height = this._texture.height, + format = this._format, + }: Texture2DResizeProps): Texture2D { + this._texture.destroy(); + + const gpuFormat = gpuTextureFormat(format); + + this._texture = this._renderer._device.createTexture({ + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, + size: { width, height }, + format: gpuFormat, + label: this._name + }); + this._textureView = this._texture.createView({ + format: gpuFormat, + dimension: "2d", + label: `${this._name}.textureView`, + }); + return this; + } } Object.defineProperty(Texture2D.prototype, "type", { value: "Texture2D" }); diff --git a/src/shader.ts b/src/shader.ts index 18e0c7e..e5aa6ae 100644 --- a/src/shader.ts +++ b/src/shader.ts @@ -132,5 +132,24 @@ fn frag(fragment: Varyings) -> @location(0) vec2 { let occlusionTexel = texture(_OcclusionTexture, _Sampler, fragment.lightTexCoord); occlusion += _Material.occlusionTextureStrength * (occlusionTexel.r - 1.0); ` : ""} + + let positionVS = fragment.positionVS; + ${normal ? ` + let geometricNormalVS = fragment.normalVS; + ` : ` + let dPositionVSdx = dpdx(positionVS); + let dPositionVSdy = dpdy(positionVS); + let geometricNormalVS = normalize(cross(dPositionVSdx, dPositionVSdy)); + let actualNormalVS = geometricNormalVS; + `} + ${texCoord ? ` + ` : ` + let actualNormalVS = geometricNormalVS; + `} + ${tangent ? ` + let tangentVS = + ` : ` + let actualNormalVS = geometricNormalVS; + `} }`; }