From e2cef2d17277443ba6bea79468c28d40f41da732 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Wed, 26 Jul 2023 23:13:16 +0200 Subject: [PATCH] Define basic data structure --- package.json | 7 +- pnpm-lock.yaml | 7 ++ src/Camera.ts | 115 +++++++++++++++++++ src/Color.ts | 266 ++++++++++++++++++++++++++++++++++++++++++++ src/IndexBuffer.ts | 49 ++++++++ src/Material.ts | 76 +++++++++++++ src/Matrix4x4.ts | 246 ++++++++++++++++++++++++++++++++++++++++ src/Mesh.ts | 42 +++++++ src/Node.ts | 84 ++++++++++++++ src/Quaternion.ts | 73 ++++++++++++ src/Scene.ts | 37 ++++++ src/Texture2D.ts | 72 ++++++++++++ src/Vector3.ts | 100 +++++++++++++++++ src/VertexBuffer.ts | 57 ++++++++++ src/oktaeder.ts | 19 +++- tsconfig.json | 1 + 16 files changed, 1249 insertions(+), 2 deletions(-) create mode 100644 src/Camera.ts create mode 100644 src/Color.ts create mode 100644 src/IndexBuffer.ts create mode 100644 src/Material.ts create mode 100644 src/Matrix4x4.ts create mode 100644 src/Mesh.ts create mode 100644 src/Node.ts create mode 100644 src/Quaternion.ts create mode 100644 src/Scene.ts create mode 100644 src/Texture2D.ts create mode 100644 src/Vector3.ts create mode 100644 src/VertexBuffer.ts diff --git a/package.json b/package.json index 26bdf05..7a4c5c6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,11 @@ "name": "oktaeder", "version": "0.1.0", "description": "3D rendering library for WebGPU", - "keywords": ["3d", "gltf", "wegbpu"], + "keywords": [ + "3d", + "gltf", + "wegbpu" + ], "homepage": "https://github.com/iszn11/oktaeder", "bugs": { "url": "https://github.com/iszn11/oktaeder/issues" @@ -20,6 +24,7 @@ "tslib": "^2.6.1" }, "devDependencies": { + "@webgpu/types": "^0.1.34", "typescript": "5.1.6" }, "exports": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35a6da2..c0537c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,12 +10,19 @@ dependencies: version: 2.6.1 devDependencies: + '@webgpu/types': + specifier: ^0.1.34 + version: 0.1.34 typescript: specifier: 5.1.6 version: 5.1.6 packages: + /@webgpu/types@0.1.34: + resolution: {integrity: sha512-9mXtH+CC8q+Ku7Z+1XazNIte81FvfdXwR2lLRO7Ykzjd/hh1J1krJa0gtnkF1kvP11psUmKEPKo7iMTeEcUpNA==} + dev: true + /tslib@2.6.1: resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} dev: false diff --git a/src/Camera.ts b/src/Camera.ts new file mode 100644 index 0000000..c6fd564 --- /dev/null +++ b/src/Camera.ts @@ -0,0 +1,115 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Node } from "./Node"; + +export type Camera = CameraOrthographic | CameraPerspective; + +export interface CameraOrthographicProps { + readonly name?: string; + + readonly verticalSize: number; + readonly nearPlane: number; + readonly farPlane: number; +} + +export interface CameraPerspectiveProps { + readonly name?: string; + + readonly verticalFovRad: number; + readonly nearPlane: number; + readonly farPlane: number; +} + +export class CameraOrthographic { + + readonly type!: "CameraOrthographic"; + + _name: string; + + _verticalSize: number; + _nearPlane: number; + _farPlane: number; + + /** backreference */ + _node: Node | undefined; + + constructor({ + name = "", + verticalSize, + nearPlane, + farPlane, + }: CameraOrthographicProps) { + Object.defineProperty(this, "type", { value: "CameraOrthographic" }); + + this._name = name; + + this._verticalSize = verticalSize; + this._nearPlane = nearPlane; + this._farPlane = farPlane; + + this._node = undefined; + } + + detach(): Camera { + if (this._node === undefined) { + return this; + } + + this._node._camera = undefined; + this._node = undefined; + return this; + } +} + +export class CameraPerspective { + + readonly type!: "CameraPerspective"; + + _name: string; + + _verticalFovRad: number; + _nearPlane: number; + _farPlane: number; + + /** backreference */ + _node: Node | undefined; + + constructor({ + name = "", + verticalFovRad, + nearPlane, + farPlane, + }: CameraPerspectiveProps) { + Object.defineProperty(this, "type", { value: "CameraPerspective" }); + + this._name = name; + + this._verticalFovRad = verticalFovRad; + this._nearPlane = nearPlane; + this._farPlane = farPlane; + + this._node = undefined; + } + + detach(): Camera { + if (this._node === undefined) { + return this; + } + + this._node._camera = undefined; + this._node = undefined; + return this; + } +} + +export function isCameraOrthographic(value: unknown): value is CameraOrthographic { + return Boolean(value) && (value as CameraOrthographic).type === "CameraOrthographic"; +} + +export function isCameraPerspective(value: unknown): value is CameraPerspective { + return Boolean(value) && (value as CameraPerspective).type === "CameraPerspective"; +} diff --git a/src/Color.ts b/src/Color.ts new file mode 100644 index 0000000..b8c485d --- /dev/null +++ b/src/Color.ts @@ -0,0 +1,266 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Vector3Object } from "./Vector3"; + +/* Named colors + * black #000000 (0, 0, 0, 1) + * silver #C0C0C0 (192 / 255, 192 / 255, 192 / 255, 1) + * gray #808080 (128 / 255, 128 / 255, 128 / 255, 1) + * white #FFFFFF (1, 1, 1, 1) + * maroon #800000 (128 / 255, 0, 0, 1) + * red #FF0000 (1, 0, 0, 1) + * purple #800080 (128 / 255, 0, 128 / 255, 1) + * fuchsia #FF00FF (1, 0, 1, 1) + * green #008000 (0, 128 / 255, 0, 1) + * lime #00FF00 (0, 255, 0, 1) + * olive #808000 (128 / 255, 128 / 255, 0, 1) + * yellow #FFFF00 (1, 1, 0, 1) + * navy #000080 (0, 0, 128 / 255, 1) + * blue #0000FF (0, 0, 1, 1) + * teal #008080 (0, 128 / 255, 128 / 255, 1) + * aqua #00FFFF (0, 1, 1, 1) + * orange #FFA500 (1, 165 / 255, 0, 1) + */ + +export type ColorName = + | "black" + | "silver" + | "gray" + | "white" + | "maroon" + | "red" + | "purple" + | "fuchsia" + | "green" + | "lime" + | "olive" + | "yellow" + | "navy" + | "blue" + | "teal" + | "aqua" + | "orange" + ; + +export interface ColorObject { + readonly r: number; + readonly g: number; + readonly b: number; +} + +export type ColorTuple = readonly [r: number, g: number, b: number]; + +export class Color { + + readonly type!: "Color"; + + r: number; + g: number; + b: number; + + constructor(r: number, g: number, b: number) { + Object.defineProperty(this, "type", { value: "Color" }); + + this.r = r; + this.g = g; + this.b = b; + } + + static fromObject(object: ColorObject): Color { + return new Color(object.r, object.g, object.b); + } + + static fromTuple(tuple: ColorTuple): Color { + return new Color(...tuple); + } + + static fromName(name: ColorName): Color { + switch (name) { + case "black": return new Color(0, 0, 0); + case "silver": return new Color(192 / 255, 192 / 255, 192 / 255); + case "gray": return new Color(128 / 255, 128 / 255, 128 / 255); + case "white": return new Color(1, 1, 1); + case "maroon": return new Color(128 / 255, 0, 0); + case "red": return new Color(1, 0, 0); + case "purple": return new Color(128 / 255, 0, 128 / 255); + case "fuchsia": return new Color(1, 0, 1); + case "green": return new Color(0, 128 / 255, 0); + case "lime": return new Color(0, 255, 0); + case "olive": return new Color(128 / 255, 128 / 255, 0); + case "yellow": return new Color(1, 1, 0); + case "navy": return new Color(0, 0, 128 / 255); + case "blue": return new Color(0, 0, 1); + case "teal": return new Color(0, 128 / 255, 128 / 255); + case "aqua": return new Color(0, 1, 1); + case "orange": return new Color(1, 165 / 255, 0); + } + } + + static fromVector3(vector: Vector3Object): Color { + return new Color(vector.x, vector.y, vector.z); + } + + static white(): Color { + return new Color(1, 1, 1); + } + + static black(): Color { + return new Color(0, 0, 0); + } + + set(r: number, g: number, b: number): Color { + this.r = r; + this.g = g; + this.b = b; + return this; + } + + setObject(object: ColorObject): Color { + this.r = object.r; + this.g = object.g; + this.b = object.b; + return this; + } + + setTuple(tuple: ColorTuple): Color { + this.r = tuple[0]; + this.g = tuple[1]; + this.b = tuple[2]; + return this; + } + + setName(name: ColorName): Color { + switch (name) { + case "black": + this.r = 0; + this.g = 0; + this.b = 0; + break; + case "silver": + this.r = 192 / 255; + this.g = 192 / 255; + this.b = 192 / 255; + break; + case "gray": + this.r = 128 / 255; + this.g = 128 / 255; + this.b = 128 / 255; + break; + case "white": + this.r = 1; + this.g = 1; + this.b = 1; + break; + case "maroon": + this.r = 128 / 255; + this.g = 0; + this.b = 0; + break; + case "red": + this.r = 1; + this.g = 0; + this.b = 0; + break; + case "purple": + this.r = 128 / 255; + this.g = 0; + this.b = 128 / 255; + break; + case "fuchsia": + this.r = 1; + this.g = 0; + this.b = 1; + break; + case "green": + this.r = 0; + this.g = 128 / 255; + this.b = 0; + break; + case "lime": + this.r = 0; + this.g = 255; + this.b = 0; + break; + case "olive": + this.r = 128 / 255; + this.g = 128 / 255; + this.b = 0; + break; + case "yellow": + this.r = 1; + this.g = 1; + this.b = 0; + break; + case "navy": + this.r = 0; + this.g = 0; + this.b = 128 / 255; + break; + case "blue": + this.r = 0; + this.g = 0; + this.b = 1; + break; + case "teal": + this.r = 0; + this.g = 128 / 255; + this.b = 128 / 255; + break; + case "aqua": + this.r = 0; + this.g = 1; + this.b = 1; + break; + case "orange": + this.r = 1; + this.g = 165 / 255; + this.b = 0; + break; + } + return this; + } + + setVector3(vector: Vector3Object): Color { + this.r = vector.x; + this.g = vector.y; + this.b = vector.z; + return this; + } + + setWhite(): Color { + this.r = 1; + this.g = 1; + this.b = 1; + return this; + } + + setBlack(): Color { + this.r = 0; + this.g = 0; + this.b = 0; + return this; + } + + setR(r: number): Color { + this.r = r; + return this; + } + + setG(g: number): Color { + this.g = g; + return this; + } + + setB(b: number): Color { + this.b = b; + return this; + } +} + +export function isColor(value: unknown): value is Color { + return Boolean(value) && (value as Color).type === "Color"; +} diff --git a/src/IndexBuffer.ts b/src/IndexBuffer.ts new file mode 100644 index 0000000..106ecaa --- /dev/null +++ b/src/IndexBuffer.ts @@ -0,0 +1,49 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +export const INDEX_SIZE = 2; + +export class IndexBuffer { + + readonly type!: "IndexBuffer"; + + _device: GPUDevice; + _buffer: GPUBuffer; + + constructor(device: GPUDevice, indexCount: number) { + Object.defineProperty(this, "type", { value: "IndexBuffer" }); + + this._device = device; + this._buffer = device.createBuffer({ + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.INDEX, + size: indexCount * INDEX_SIZE, + }); + } + + dispose(): IndexBuffer { + this._buffer.destroy(); + return this; + } + + get vertexCount(): number { + return this._buffer.size / INDEX_SIZE | 0; + } + + writeArray(offset: number, indices: readonly number[]): IndexBuffer { + const array = new Uint16Array(indices); + this._device.queue.writeBuffer(this._buffer, offset * INDEX_SIZE | 0, array); + return this; + } + + writeTypedArray(offset: number, indices: Uint16Array): IndexBuffer { + this._device.queue.writeBuffer(this._buffer, offset * INDEX_SIZE | 0, indices); + return this; + } +} + +export function isIndexBuffer(value: unknown): value is IndexBuffer { + return Boolean(value) && (value as IndexBuffer).type === "IndexBuffer"; +} diff --git a/src/Material.ts b/src/Material.ts new file mode 100644 index 0000000..49a9615 --- /dev/null +++ b/src/Material.ts @@ -0,0 +1,76 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Color, ColorObject } from "./Color"; + +export interface MaterialProps { + name?: string; + + baseColor?: ColorObject; + metallic?: number; + roughness?: number; + emissive?: ColorObject; + partialCoverage?: number; + transmission?: ColorObject; + collimation?: number; + ior?: number; + + doubleSided?: boolean; +} + +export class Material { + + readonly type!: "Material"; + + _name: string; + + _baseColor: Color; + _metallic: number; + _roughness: number; + _emissive: Color; + _partialCoverage: number; + _transmission: Color; + _collimation: number; + _ior: number; + + _doubleSided: boolean; + + constructor({ + name = "", + baseColor, + metallic = 1, + roughness = 1, + emissive, + partialCoverage = 1, + transmission, + collimation = 1, + ior = 1.45, + doubleSided = false, + }: MaterialProps) { + Object.defineProperty(this, "type", { value: "Material" }); + + this._name = name; + + this._baseColor = baseColor !== undefined ? Color.fromObject(baseColor) : Color.white(); + this._metallic = metallic; + this._roughness = roughness; + this._emissive = emissive !== undefined ? Color.fromObject(emissive) : Color.black(); + this._partialCoverage = partialCoverage; + this._transmission = transmission !== undefined ? Color.fromObject(transmission) : Color.black(); + this._collimation = collimation; + this._ior = ior; + + this._doubleSided = doubleSided; + } + + get isTransparent(): boolean { + return this._partialCoverage < 1 || this._transmission.r > 0 || this._transmission.g > 0 || this._transmission.b > 0; + } +} + +export function isMaterial(value: unknown): value is Material { + return Boolean(value) && (value as Material).type === "Material"; +} diff --git a/src/Matrix4x4.ts b/src/Matrix4x4.ts new file mode 100644 index 0000000..e9fc07c --- /dev/null +++ b/src/Matrix4x4.ts @@ -0,0 +1,246 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { QuaternionObject } from "./Quaternion"; +import { Vector3Object } from "./Vector3"; + +export interface Matrix4x4Object { + readonly ix: number; + readonly iy: number; + readonly iz: number; + readonly iw: number; + readonly jx: number; + readonly jy: number; + readonly jz: number; + readonly jw: number; + readonly kx: number; + readonly ky: number; + readonly kz: number; + readonly kw: number; + readonly tx: number; + readonly ty: number; + readonly tz: number; + readonly tw: number; +} + +export type Matrix4x4Tuple = readonly [ + ix: number, iy: number, iz: number, iw: number, + jx: number, jy: number, jz: number, jw: number, + kx: number, ky: number, kz: number, kw: number, + tx: number, ty: number, tz: number, tw: number, +]; + +export class Matrix4x4 { + + readonly type!: "Matrix4x4"; + + ix: number; + iy: number; + iz: number; + iw: number; + jx: number; + jy: number; + jz: number; + jw: number; + kx: number; + ky: number; + kz: number; + kw: number; + tx: number; + ty: number; + tz: number; + tw: number; + + constructor( + ix: number, iy: number, iz: number, iw: number, + jx: number, jy: number, jz: number, jw: number, + kx: number, ky: number, kz: number, kw: number, + tx: number, ty: number, tz: number, tw: number + ) { + Object.defineProperty(this, "type", { value: "Matrix4x4" }); + + 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; + } + + static fromObject(object: Matrix4x4Object): Matrix4x4 { + return new Matrix4x4( + object.ix, object.iy, object.iz, object.iw, + object.jx, object.jy, object.jz, object.jw, + object.kx, object.ky, object.kz, object.kw, + object.tx, object.ty, object.tz, object.tw, + ); + } + + static fromTuple(tuple: Matrix4x4Tuple): Matrix4x4 { + return new Matrix4x4(...tuple); + } + + static fromTranslation(translation: Vector3Object): Matrix4x4 { + return new Matrix4x4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + translation.x, translation.y, translation.z, 1, + ); + } + + static fromQuaternion(quaternion: QuaternionObject): Matrix4x4 { + const xx = quaternion.x * quaternion.x; + const xy = quaternion.x * quaternion.y; + const xz = quaternion.x * quaternion.z; + const xw = quaternion.x * quaternion.w; + const yy = quaternion.y * quaternion.y; + const yz = quaternion.y * quaternion.z; + const yw = quaternion.y * quaternion.w; + const zz = quaternion.z * quaternion.z; + const zw = quaternion.z * quaternion.w; + + return new Matrix4x4( + 1 - 2 * (yy + zz), 2 * (xy + zw), 2 * (xz - yw), 0, + 2 * (xy - zw), 1 - 2 * (xx + zz), 2 * (yz + xw), 0, + 2 * (xz + yw), 2 * (yz - xw), 1 - 2 * (xx + yy), 0, + 0, 0, 0, 1, + ); + } + + static fromScale(scale: Vector3Object): Matrix4x4 { + return new Matrix4x4( + scale.x, 0, 0, 0, + 0, scale.y, 0, 0, + 0, 0, scale.z, 0, + 0, 0, 0, 1 + ); + } + + setObject(object: Matrix4x4Object): Matrix4x4 { + this.ix = object.ix; + this.iy = object.iy; + this.iz = object.iz; + this.iw = object.iw; + this.jx = object.jx; + this.jy = object.jy; + this.jz = object.jz; + this.jw = object.jw; + this.kx = object.kx; + this.ky = object.ky; + this.kz = object.kz; + this.kw = object.kw; + this.tx = object.tx; + this.ty = object.ty; + this.tz = object.tz; + this.tw = object.tw; + return this; + } + + setTuple(tuple: Matrix4x4Tuple): Matrix4x4 { + this.ix = tuple[0]; + this.iy = tuple[1]; + this.iz = tuple[2]; + this.iw = tuple[3]; + this.jx = tuple[4]; + this.jy = tuple[5]; + this.jz = tuple[6]; + this.jw = tuple[7]; + this.kx = tuple[8]; + this.ky = tuple[9]; + this.kz = tuple[10]; + this.kw = tuple[11]; + this.tx = tuple[12]; + this.ty = tuple[13]; + this.tz = tuple[14]; + this.tw = tuple[15]; + return this; + } + + setTranslation(translation: Vector3Object): 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 = translation.x; + this.ty = translation.y; + this.tz = translation.z; + this.tw = 1; + return this; + } + + setQuaternion(quaternion: QuaternionObject): Matrix4x4 { + const xx = quaternion.x * quaternion.x; + const xy = quaternion.x * quaternion.y; + const xz = quaternion.x * quaternion.z; + const xw = quaternion.x * quaternion.w; + const yy = quaternion.y * quaternion.y; + const yz = quaternion.y * quaternion.z; + const yw = quaternion.y * quaternion.w; + const zz = quaternion.z * quaternion.z; + const zw = quaternion.z * quaternion.w; + + this.ix = 1 - 2 * (yy + zz); + this.iy = 2 * (xy + zw); + this.iz = 2 * (xz - yw); + this.iw = 0; + this.jx = 2 * (xy - zw); + this.jy = 1 - 2 * (xx + zz); + this.jz = 2 * (yz + xw); + this.jw = 0; + this.kx = 2 * (xz + yw); + this.ky = 2 * (yz - xw); + this.kz = 1 - 2 * (xx + yy); + this.kw = 0; + this.tx = 0; + this.ty = 0; + this.tz = 0; + this.tw = 1; + return this; + } + + setScale(scale: Vector3Object): Matrix4x4 { + this.ix = scale.x; + this.iy = 0; + this.iz = 0; + this.iw = 0; + this.jx = 0; + this.jy = scale.y; + this.jz = 0; + this.jw = 0; + this.kx = 0; + this.ky = 0; + this.kz = scale.z; + this.kw = 0; + this.tx = 0; + this.ty = 0; + this.tz = 0; + this.tw = 1; + return this; + } +} + +export function isMatrix4x4(value: unknown): value is Matrix4x4 { + return Boolean(value) && (value as Matrix4x4).type === "Matrix4x4"; +} diff --git a/src/Mesh.ts b/src/Mesh.ts new file mode 100644 index 0000000..96be3d8 --- /dev/null +++ b/src/Mesh.ts @@ -0,0 +1,42 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { IndexBuffer } from "./IndexBuffer"; +import { VertexBuffer } from "./VertexBuffer"; + +export interface MeshProps { + readonly name?: string; + + readonly vertexBuffer: VertexBuffer; + readonly indexBuffer: IndexBuffer; +} + +export class Mesh { + + readonly type!: "Mesh" + + _name: string; + + _vertexBuffer: VertexBuffer; + _indexBuffer: IndexBuffer; + + constructor({ + name = "", + vertexBuffer, + indexBuffer, + }: MeshProps) { + Object.defineProperty(this, "type", { value: "Mesh" }); + + this._name = name; + + this._vertexBuffer = vertexBuffer; + this._indexBuffer = indexBuffer + } +} + +export function isMesh(value: unknown): value is Mesh { + return Boolean(value) && (value as Mesh).type === "Mesh"; +} diff --git a/src/Node.ts b/src/Node.ts new file mode 100644 index 0000000..f71b3ec --- /dev/null +++ b/src/Node.ts @@ -0,0 +1,84 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Camera } from "./Camera"; +import { Mesh } from "./Mesh"; +import { Quaternion, QuaternionObject } from "./Quaternion"; +import { Vector3, Vector3Object } from "./Vector3"; + +export interface NodeProps { + readonly name?: string; + + readonly translation?: Vector3Object; + readonly rotation?: QuaternionObject; + readonly scale?: Vector3Object; + + readonly camera?: Camera; + readonly mesh?: Mesh; + + readonly children?: Node[]; +} + +export class Node { + + readonly type!: "Node"; + + _name: string; + + _translation: Vector3; + _rotation: Quaternion; + _scale: Vector3; + + /** unique */ + _camera: Camera | undefined; + /** shared */ + _mesh: Mesh | undefined; + + /** unique */ + _children: Node[]; + + /** backreference */ + _parent: Node | undefined; + + constructor({ + name = "", + translation, + rotation, + scale, + camera, + mesh, + children = [], + }: NodeProps) { + Object.defineProperty(this, "type", { value: "Node" }); + + this._name = name; + + this._translation = translation !== undefined ? Vector3.fromObject(translation) : Vector3.zero(); + this._rotation = rotation !== undefined ? Quaternion.fromObject(rotation) : Quaternion.identity(); + this._scale = scale !== undefined ? Vector3.fromObject(scale) : Vector3.one(); + + this._camera = camera; + this._mesh = mesh; + + this._children = children; + + this._parent = undefined; + + if (this._camera !== undefined) { + this._camera._node = this; + } + + if (this._children !== undefined) { + for (const child of this._children) { + child._parent = this; + } + } + } +} + +export function isNode(value: unknown): value is Node { + return Boolean(value) && (value as Node).type === "Node"; +} diff --git a/src/Quaternion.ts b/src/Quaternion.ts new file mode 100644 index 0000000..2030942 --- /dev/null +++ b/src/Quaternion.ts @@ -0,0 +1,73 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +export interface QuaternionObject { + readonly x: number; + readonly y: number; + readonly z: number; + readonly w: number; +} + +export type QuaternionTuple = readonly [x: number, y: number, z: number, w: number]; + +export class Quaternion { + + readonly type!: "Quaternion"; + + x: number; + y: number; + z: number; + w: number; + + constructor(x: number, y: number, z: number, w: number) { + Object.defineProperty(this, "type", { value: "Quaternion" }); + + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + static fromObject(object: QuaternionObject): Quaternion { + return new Quaternion(object.x, object.y, object.z, object.w); + } + + static fromTuple(tuple: QuaternionTuple): Quaternion { + return new Quaternion(...tuple); + } + + static identity(): Quaternion { + return new Quaternion(0, 0, 0, 1); + } + + setObject(object: QuaternionObject): Quaternion { + this.x = object.x; + this.y = object.y; + this.z = object.z; + this.w = object.w; + return this; + } + + setTuple(tuple: QuaternionTuple): Quaternion { + this.x = tuple[0]; + this.y = tuple[1]; + this.z = tuple[2]; + this.w = tuple[3]; + return this; + } + + setIdentity(): Quaternion { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 1; + return this; + } +} + +export function isQuaternion(value: unknown): value is Quaternion { + return Boolean(value) && (value as Quaternion).type === "Quaternion"; +} diff --git a/src/Scene.ts b/src/Scene.ts new file mode 100644 index 0000000..61cdcfb --- /dev/null +++ b/src/Scene.ts @@ -0,0 +1,37 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Node } from "./Node"; + +export interface SceneProps { + readonly name?: string; + + readonly nodes?: Node[]; +} + +export class Scene { + + readonly type!: "Scene"; + + _name: string | undefined; + + _nodes: Node[]; + + constructor({ + name = "", + nodes = [], + }: SceneProps) { + Object.defineProperty(this, "type", { value: "Scene" }); + + this._name = name; + + this._nodes = nodes; + } +} + +export function isScene(value: unknown): value is Scene { + return Boolean(value) && (value as Scene).type === "Scene"; +} diff --git a/src/Texture2D.ts b/src/Texture2D.ts new file mode 100644 index 0000000..227c60e --- /dev/null +++ b/src/Texture2D.ts @@ -0,0 +1,72 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +export interface Texture2DProps { + name?: string; + device: GPUDevice; + width: number; + height: number; +} + +export class Texture2D { + + readonly type!: "Texture2D"; + + _name: string; + + _device: GPUDevice; + _texture: GPUTexture; + _textureView: GPUTextureView; + + constructor({ + name = "", + device, + width, + height, + }: Texture2DProps) { + Object.defineProperty(this, "type", { value: "Texture2D" }); + + this._name = name; + + this._device = device; + this._texture = device.createTexture({ + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, + size: { width, height }, + format: "rgba8unorm", + }); + this._textureView = this._texture.createView({ + format: "rgba8unorm", + dimension: "2d", + }); + } + + dispose(): Texture2D { + this._texture.destroy(); + return this; + } + + get width(): number { + return this._texture.width; + } + + get height(): number { + return this._texture.height; + } + + writeTypedArray(data: Uint8Array): Texture2D { + this._device.queue.writeTexture( + { texture: this._texture }, + data, + { bytesPerRow: 4 * this._texture.width }, + { width: this._texture.width, height: this._texture.height }, + ); + return this; + } +} + +export function isTexture2D(value: unknown): value is Texture2D { + return Boolean(value) && (value as Texture2D).type === "Texture2D"; +} diff --git a/src/Vector3.ts b/src/Vector3.ts new file mode 100644 index 0000000..3db78fb --- /dev/null +++ b/src/Vector3.ts @@ -0,0 +1,100 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +export interface Vector3Object { + readonly x: number; + readonly y: number; + readonly z: number; +} + +export type Vector3Tuple = readonly [x: number, y: number, z: number]; + +export class Vector3 { + + readonly type!: "Vector3"; + + x: number; + y: number; + z: number; + + constructor(x: number, y: number, z: number) { + Object.defineProperty(this, "type", { value: "Vector3" }); + + this.x = x; + this.y = y; + this.z = z; + } + + static fromObject(object: Vector3Object): Vector3 { + return new Vector3(object.x, object.y, object.z); + } + + static fromTuple(tuple: Vector3Tuple): Vector3 { + return new Vector3(...tuple); + } + + static zero(): Vector3 { + return new Vector3(0, 0, 0); + } + + static one(): Vector3 { + return new Vector3(1, 1, 1); + } + + set(x: number, y: number, z: number): Vector3 { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + setObject(object: Vector3Object): Vector3 { + this.x = object.x; + this.y = object.y; + this.z = object.z; + return this; + } + + setTuple(tuple: Vector3Tuple): Vector3 { + this.x = tuple[0]; + this.y = tuple[1]; + this.z = tuple[2]; + return this; + } + + setZero(): Vector3 { + this.x = 0; + this.y = 0; + this.z = 0; + return this; + } + + setOne(): Vector3 { + this.x = 1; + this.y = 1; + this.z = 1; + return this; + } + + setX(x: number): Vector3 { + this.x = x; + return this; + } + + setY(y: number): Vector3 { + this.y = y; + return this; + } + + setZ(z: number): Vector3 { + this.z = z; + return this; + } +} + +export function isVector3(value: unknown): value is Vector3 { + return Boolean(value) && (value as Vector3).type === "Vector3"; +} diff --git a/src/VertexBuffer.ts b/src/VertexBuffer.ts new file mode 100644 index 0000000..5b755dc --- /dev/null +++ b/src/VertexBuffer.ts @@ -0,0 +1,57 @@ +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Vector3Object } from "./Vector3"; + +export const VERTEX_SIZE = 12; + +export class VertexBuffer { + + readonly type!: "VertexBuffer"; + + _device: GPUDevice; + _buffer: GPUBuffer; + + constructor(device: GPUDevice, vertexCount: number) { + Object.defineProperty(this, "type", { value: "VertexBuffer" }); + + this._device = device; + this._buffer = device.createBuffer({ + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX, + size: vertexCount * VERTEX_SIZE, + }); + } + + dispose(): VertexBuffer { + this._buffer.destroy(); + return this; + } + + get vertexCount(): number { + return this._buffer.size / VERTEX_SIZE | 0; + } + + writeArray(offset: number, vertices: readonly Vector3Object[]): VertexBuffer { + const array = new Float32Array(vertices.length * 3); + for (let vi = 0, ptr = 0; vi < vertices.length; ++vi) { + const vertex = vertices[vi]!; + array[ptr++] = vertex.x; + array[ptr++] = vertex.y; + array[ptr++] = vertex.z; + } + this._device.queue.writeBuffer(this._buffer, offset * VERTEX_SIZE | 0, array); + return this; + } + + writeTypedArray(offset: number, vertices: Float32Array): VertexBuffer { + this._device.queue.writeBuffer(this._buffer, offset * VERTEX_SIZE | 0, vertices); + return this; + } +} + +export function isVertexBuffer(value: unknown): value is VertexBuffer { + return Boolean(value) && (value as VertexBuffer).type === "VertexBuffer"; +} diff --git a/src/oktaeder.ts b/src/oktaeder.ts index cb0ff5c..f78f1c6 100644 --- a/src/oktaeder.ts +++ b/src/oktaeder.ts @@ -1 +1,18 @@ -export {}; +/*! + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +export * from "./Camera"; +export * from "./Color"; +export * from "./IndexBuffer"; +export * from "./Material"; +export * from "./Matrix4x4"; +export * from "./Mesh"; +export * from "./Node"; +export * from "./Quaternion"; +export * from "./Scene"; +export * from "./Texture2D"; +export * from "./Vector3"; +export * from "./VertexBuffer"; diff --git a/tsconfig.json b/tsconfig.json index 631321b..62fd6cc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "target": "ES2022", "lib": ["ES2022", "DOM"], + "typeRoots": ["./node_modules/@webgpu/types"], "sourceMap": true, "declaration": true,