├── docs ├── .nojekyll ├── assets │ └── images │ │ ├── icons.png │ │ ├── icons@2x.png │ │ ├── widgets.png │ │ └── widgets@2x.png ├── modules │ ├── _mesh_staticmesh_box_.html │ ├── _mesh_staticmesh_plane_.html │ ├── _camera_sphericalcamera_.html │ ├── _renderer_index_.html │ ├── _mesh_staticmesh_index_.html │ ├── _aabb_index_.html │ └── _camera_index_.html ├── interfaces │ ├── _aabb_index_.iaabboptions.html │ ├── _ray_index_.iaabbintersection.html │ ├── _camera_abstractcamera_.abstractcamera.listenerfn.html │ ├── _renderer_abstractrenderer_.irendereroptions.html │ ├── _renderer_index_.iuniformupdateentry.html │ └── _aabb_index_.iaabbbounding.html └── globals.html ├── .eslintignore ├── .gitattributes ├── src ├── Camera │ ├── inertial-turntable-camera.d.ts │ ├── index.ts │ ├── normalized-interaction-events.d.ts │ ├── SphericalCamera.ts │ └── AbstractCamera.ts ├── Mesh │ └── StaticMesh │ │ ├── index.ts │ │ ├── Plane.ts │ │ └── Box.ts ├── index.ts ├── Renderer │ └── AbstractRenderer.ts ├── AABB │ └── index.ts ├── Sampler │ └── index.ts ├── Shader │ └── index.ts ├── utils.ts ├── Buffer │ └── index.ts ├── constants.ts ├── Texture │ └── index.ts ├── Ray │ └── index.ts ├── Frame │ └── index.ts └── QueueCommander │ └── index.ts ├── tsconfig.json ├── LICENSE ├── rollup.config.js ├── package.json ├── .gitignore ├── README.md └── .eslintrc.js /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.mjs 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/Camera/inertial-turntable-camera.d.ts: -------------------------------------------------------------------------------- 1 | declare module "inertial-turntable-camera"; 2 | -------------------------------------------------------------------------------- /src/Camera/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AbstractCamera"; 2 | export * from "./SphericalCamera"; 3 | -------------------------------------------------------------------------------- /src/Camera/normalized-interaction-events.d.ts: -------------------------------------------------------------------------------- 1 | declare module "normalized-interaction-events"; 2 | -------------------------------------------------------------------------------- /src/Mesh/StaticMesh/index.ts: -------------------------------------------------------------------------------- 1 | export {Box} from "./Box"; 2 | export {Plane} from "./Plane"; 3 | -------------------------------------------------------------------------------- /docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/YUE/HEAD/docs/assets/images/icons.png -------------------------------------------------------------------------------- /docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/YUE/HEAD/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/YUE/HEAD/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/YUE/HEAD/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export * from "./constants"; 4 | 5 | export * from "./Buffer"; 6 | export * from "./Camera"; 7 | export * from "./Container"; 8 | export * from "./Frame"; 9 | export * from "./Material"; 10 | export * from "./Mesh"; 11 | export * from "./Ray"; 12 | export * from "./Renderer"; 13 | export * from "./Sampler"; 14 | export * from "./Shader"; 15 | export * from "./Texture"; 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "esnext", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "strict": true, 8 | "strictNullChecks": false, 9 | "noImplicitAny": true, 10 | "noEmit": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "outDir": "dist" 14 | }, 15 | "include": [ 16 | "src" 17 | ], 18 | "exclude": [ 19 | "dist", 20 | "build", 21 | "node_modules", 22 | "rollup.config.js" 23 | ], 24 | "typeRoots": [ 25 | "./node_modules/@webgpu/types/" 26 | ] 27 | } -------------------------------------------------------------------------------- /src/Mesh/StaticMesh/Plane.ts: -------------------------------------------------------------------------------- 1 | import {IMaterialAttributeOptions, SHADER_ATTRIBUTE} from "../.."; 2 | 3 | export class Plane { 4 | 5 | public static get AttributeLayout(): IMaterialAttributeOptions[] { 6 | return [ 7 | {name: "Position", id: 0, type: SHADER_ATTRIBUTE.FLOAT3, byteOffset: 0x0 * Float32Array.BYTES_PER_ELEMENT}, 8 | {name: "Normal", id: 1, type: SHADER_ATTRIBUTE.FLOAT3, byteOffset: 0x3 * Float32Array.BYTES_PER_ELEMENT}, 9 | {name: "UV", id: 2, type: SHADER_ATTRIBUTE.FLOAT2, byteOffset: 0x6 * Float32Array.BYTES_PER_ELEMENT}, 10 | ]; 11 | } 12 | 13 | public static get Attributes() { 14 | return new Float32Array([ 15 | -1.0, 0.0, -1.0, 0.0, -1.0, 0.0, 1.0, 1.0, 16 | 1.0, 0.0, -1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 17 | 1.0, 0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 18 | -1.0, 0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 1.0 19 | ]); 20 | } 21 | 22 | public static get Indices() { 23 | return new Uint32Array([ 24 | 0, 1, 2, 25 | 2, 3, 0 26 | ]); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Felix Maier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import clear from "rollup-plugin-clear"; 2 | import commonjs from "rollup-plugin-commonjs"; 3 | import resolve from "rollup-plugin-node-resolve"; 4 | import typescript from "rollup-plugin-typescript2"; 5 | //import { terser } from "rollup-plugin-terser"; 6 | 7 | export default { 8 | input: "src/index.ts", 9 | output: [ 10 | { 11 | name: "yue", 12 | file: "dist/index.esm.js", 13 | format: "esm", 14 | plugins: [] 15 | }, 16 | { 17 | name: "yue", 18 | file: "dist/index.js", 19 | format: "cjs", 20 | plugins: [] 21 | }, 22 | /*{ 23 | name: "yue", 24 | file: "dist/index.min.js", 25 | format: "cjs", 26 | plugins: [ 27 | terser() 28 | ] 29 | },*/ 30 | ], 31 | plugins: [ 32 | clear({ 33 | targets: ["dist"] 34 | }), 35 | typescript({ 36 | rollupCommonJSResolveHack: true, 37 | clean: true, 38 | tsconfig: "./tsconfig.json", 39 | module: "esnext" 40 | }), 41 | commonjs({ 42 | include: [ 43 | "node_modules/**" 44 | ], 45 | sourceMap: false, 46 | transformMixedEsModules: true 47 | }), 48 | resolve({ 49 | jsnext: true, 50 | browser: true 51 | }) 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yue.js", 3 | "version": "0.0.6", 4 | "main": "dist/index.js", 5 | "module": "dist/index.esm.js", 6 | "types": "dist/index.d.ts", 7 | "exports": { 8 | "import": "./dist/index.esm.js", 9 | "require": "./dist/index.js" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "scripts": { 15 | "docs": "typedoc", 16 | "build": "tsc && rollup -c rollup.config.js", 17 | "watch": "rollup -c rollup.config.js -w" 18 | }, 19 | "devDependencies": { 20 | "@typescript-eslint/eslint-plugin": "^4.2.0", 21 | "@typescript-eslint/parser": "^4.2.0", 22 | "@webgpu/types": "0.0.31", 23 | "eslint": "^7.9.0", 24 | "eslint-loader": "^4.0.2", 25 | "eslint-plugin-eslint-comments": "^3.2.0", 26 | "eslint-plugin-tsdoc": "^0.2.7", 27 | "rollup": "^2.32.0", 28 | "rollup-plugin-clear": "^2.0.7", 29 | "rollup-plugin-commonjs": "^10.1.0", 30 | "rollup-plugin-node-resolve": "^5.2.0", 31 | "rollup-plugin-terser": "^7.0.2", 32 | "rollup-plugin-typescript2": "^0.28.0", 33 | "tslib": "^2.0.3", 34 | "typedoc": "^0.19.2", 35 | "typescript": "^4.0.3" 36 | }, 37 | "dependencies": { 38 | "eventemitter3": "^4.0.4", 39 | "gl-matrix": "^3.3.0", 40 | "inertial-turntable-camera": "^2.0.4", 41 | "normalized-interaction-events": "^2.0.1" 42 | }, 43 | "description": "Personal WebGPU based 3D renderer.", 44 | "directories": { 45 | "doc": "docs" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "git+https://github.com/maierfelix/yue.git" 50 | }, 51 | "keywords": [ 52 | "3d", 53 | "renderer", 54 | "webgpu" 55 | ], 56 | "author": "Felix Maier", 57 | "license": "MIT", 58 | "bugs": { 59 | "url": "https://github.com/maierfelix/yue/issues" 60 | }, 61 | "homepage": "https://github.com/maierfelix/yue#readme" 62 | } 63 | -------------------------------------------------------------------------------- /src/Renderer/AbstractRenderer.ts: -------------------------------------------------------------------------------- 1 | import {EventEmitter} from "../utils"; 2 | import {Frame} from "../Frame"; 3 | 4 | export interface IRendererOptions { 5 | canvas?: HTMLCanvasElement; 6 | } 7 | 8 | export const RENDERER_DEFAULT_OPTIONS: IRendererOptions = { 9 | canvas: null 10 | }; 11 | 12 | const CANVAS_DEFAULT_WIDTH = 480; 13 | const CANVAS_DEFAULT_HEIGHT = 320; 14 | 15 | export abstract class AbstractRenderer extends EventEmitter { 16 | 17 | private _canvas: HTMLCanvasElement = null; 18 | 19 | /** 20 | * @param options - Create options 21 | */ 22 | public constructor(options?: IRendererOptions) { 23 | super(); 24 | // Normalize options 25 | options = Object.assign({}, RENDERER_DEFAULT_OPTIONS, options); 26 | // Process options 27 | this._canvas = options.canvas; 28 | // Create internal canvas 29 | if (!(this._canvas instanceof HTMLCanvasElement)) { 30 | this._canvas = document.createElement("canvas"); 31 | this._canvas.width = CANVAS_DEFAULT_WIDTH; 32 | this._canvas.height = CANVAS_DEFAULT_HEIGHT; 33 | } 34 | } 35 | 36 | /** 37 | * The rendering surface to render into 38 | * TODO: allow other targets such as FBOs 39 | */ 40 | public getCanvas(): HTMLCanvasElement { return this._canvas; } 41 | 42 | /** 43 | * The width of the rendering surface 44 | */ 45 | public getWidth(): number { return this._canvas.width; } 46 | 47 | /** 48 | * The height of the rendering surface 49 | */ 50 | public getHeight(): number { return this._canvas.height; } 51 | 52 | /** 53 | * Resize the rendering surface 54 | * @param width - The destination width after resize 55 | * @param height - The destination height after resize 56 | */ 57 | public resize(width: number, height: number) { 58 | this._canvas.width = width; 59 | this._canvas.height = height; 60 | this.emit("resize"); 61 | } 62 | 63 | /** 64 | * Render a frame 65 | * @param frame - The frame to render 66 | */ 67 | public abstract render(frame: Frame): void; 68 | 69 | /** 70 | * Destroy this Object 71 | */ 72 | public destroy(): void { 73 | this._canvas = null; 74 | this.emit("destroy"); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | 105 | dist 106 | package-lock.json 107 | -------------------------------------------------------------------------------- /src/Mesh/StaticMesh/Box.ts: -------------------------------------------------------------------------------- 1 | import {IMaterialAttributeOptions, SHADER_ATTRIBUTE} from "../.."; 2 | 3 | export class Box { 4 | 5 | public static get AttributeLayout(): IMaterialAttributeOptions[] { 6 | return [ 7 | {name: "Position", id: 0, type: SHADER_ATTRIBUTE.FLOAT3, byteOffset: 0x0 * Float32Array.BYTES_PER_ELEMENT}, 8 | {name: "Normal", id: 1, type: SHADER_ATTRIBUTE.FLOAT3, byteOffset: 0x3 * Float32Array.BYTES_PER_ELEMENT}, 9 | {name: "UV", id: 2, type: SHADER_ATTRIBUTE.FLOAT2, byteOffset: 0x6 * Float32Array.BYTES_PER_ELEMENT}, 10 | ]; 11 | } 12 | 13 | public static get Attributes() { 14 | return new Float32Array([ 15 | -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 16 | 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 17 | 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 18 | -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 19 | -1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 20 | -1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0, 21 | 1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, 22 | 1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 1.0, 23 | -1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 24 | -1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 25 | 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 26 | 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 27 | -1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 28 | 1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 29 | 1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 1.0, 30 | -1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 0.0, 1.0, 31 | 1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 32 | 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 33 | 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 34 | 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 35 | -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 36 | -1.0, -1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 37 | -1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 1.0, 38 | -1.0, 1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0 39 | ]); 40 | } 41 | 42 | public static get Indices() { 43 | return new Uint32Array([ 44 | 0, 1, 2, 45 | 2, 3, 0, 46 | 4, 5, 6, 47 | 6, 7, 4, 48 | 8, 9, 10, 49 | 10, 11, 8, 50 | 12, 13, 14, 51 | 14, 15, 12, 52 | 16, 17, 18, 53 | 18, 19, 16, 54 | 20, 21, 22, 55 | 22, 23, 20 56 | ]); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/AABB/index.ts: -------------------------------------------------------------------------------- 1 | import {vec3} from "gl-matrix"; 2 | 3 | export interface IAABBBounding { 4 | min: vec3; 5 | max: vec3; 6 | } 7 | 8 | export interface IAABBOptions { 9 | 10 | } 11 | 12 | export const AABB_DEFAULT_OPTIONS: IAABBOptions = { 13 | 14 | }; 15 | 16 | export class AABB { 17 | 18 | private _min: vec3 = null; 19 | private _max: vec3 = null; 20 | 21 | /** 22 | * @param options - Create options 23 | */ 24 | public constructor(options?: IAABBOptions) { 25 | // Normalize options 26 | options = Object.assign({}, AABB_DEFAULT_OPTIONS, options); 27 | // Process options 28 | this._min = vec3.create(); 29 | this._max = vec3.create(); 30 | } 31 | 32 | /** 33 | * The minimum size of the AABB 34 | */ 35 | public getMin(): vec3 { return this._min; } 36 | 37 | /** 38 | * The maximum size of the AABB 39 | */ 40 | public getMax(): vec3 { return this._max; } 41 | 42 | /** 43 | * The center of the AABB 44 | */ 45 | public getCenter(): vec3 { 46 | const out = vec3.create(); 47 | vec3.add(out, this.getMin(), this.getMax()); 48 | vec3.scale(out, out, 0.5); 49 | return out; 50 | } 51 | 52 | /** 53 | * The size of the AABB 54 | */ 55 | public getSize(): vec3 { 56 | const out = vec3.create(); 57 | vec3.subtract(out, this.getMax(), this.getMin()); 58 | return out; 59 | } 60 | 61 | /** 62 | * Indicates if this and the provided AABB are intersecting 63 | * @param aabb - The AABB to check 64 | */ 65 | public intersectsAABB(aabb: AABB): boolean { 66 | const min = aabb.getMin(); 67 | const max = aabb.getMax(); 68 | return !( 69 | max[0] < this._min[0] || min[0] > this._max[0] || 70 | max[1] < this._min[1] || min[1] > this._max[1] || 71 | max[2] < this._min[2] || min[2] > this._max[2] 72 | ); 73 | } 74 | 75 | /** 76 | * Indicates if this AABB contains the provided AABB 77 | * @param aabb - The AABB to check 78 | */ 79 | public containsAABB(aabb: AABB): boolean { 80 | const min = aabb.getMin(); 81 | const max = aabb.getMax(); 82 | return ( 83 | this._min[0] <= min[0] && max[0] <= this._max[0] && 84 | this._min[1] <= min[1] && max[1] <= this._max[1] && 85 | this._min[2] <= min[2] && max[2] <= this._max[2] 86 | ); 87 | } 88 | 89 | /** 90 | * Indicates if this AABB contains the provided point 91 | * @param pt - The point to check 92 | */ 93 | public containsPoint(pt: vec3): boolean { 94 | return !( 95 | pt[0] < this._min[0] || pt[0] > this._max[0] || 96 | pt[1] < this._min[1] || pt[1] > this._max[1] || 97 | pt[2] < this._min[2] || pt[2] > this._max[2] 98 | ); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/Sampler/index.ts: -------------------------------------------------------------------------------- 1 | import {SAMPLER_FILTER_MODE, SAMPLER_WRAP_MODE} from "../constants"; 2 | import {Renderer} from "../Renderer"; 3 | 4 | export interface ISamplerAdressModeOptions { 5 | U: SAMPLER_WRAP_MODE; 6 | V: SAMPLER_WRAP_MODE; 7 | W: SAMPLER_WRAP_MODE; 8 | } 9 | 10 | export interface ISamplerOptions { 11 | name?: string; 12 | addressMode?: ISamplerAdressModeOptions; 13 | filterMode?: SAMPLER_FILTER_MODE; 14 | } 15 | 16 | export const SAMPLER_DEFAULT_OPTIONS: ISamplerOptions = { 17 | name: null, 18 | addressMode: { 19 | U: SAMPLER_WRAP_MODE.REPEAT, 20 | V: SAMPLER_WRAP_MODE.REPEAT, 21 | W: SAMPLER_WRAP_MODE.REPEAT 22 | }, 23 | filterMode: SAMPLER_FILTER_MODE.LINEAR 24 | }; 25 | 26 | export class Sampler { 27 | 28 | private _name: string = null; 29 | private _addressMode: ISamplerAdressModeOptions = null; 30 | private _filterMode: SAMPLER_FILTER_MODE = SAMPLER_FILTER_MODE.NONE; 31 | 32 | private _resource: GPUSampler = null; 33 | 34 | /** 35 | * @param options - Create options 36 | */ 37 | public constructor(options?: ISamplerOptions) { 38 | // Normalize options 39 | options = Object.assign({}, SAMPLER_DEFAULT_OPTIONS, options); 40 | // Process options 41 | this.setName(options.name); 42 | this._addressMode = Object.assign({}, options.addressMode); 43 | this._filterMode = options.filterMode; 44 | } 45 | 46 | /** 47 | * The sampler name 48 | */ 49 | public getName(): string { return this._name; } 50 | 51 | /** 52 | * Update the sampler name 53 | * @param value - The new sampler name 54 | */ 55 | public setName(value: string): void { this._name = value; } 56 | 57 | /** 58 | * The sampler address mode 59 | */ 60 | public getAddressMode(): ISamplerAdressModeOptions { return Object.assign({}, this._addressMode); } 61 | 62 | /** 63 | * The sampler filter mode 64 | */ 65 | public getFilterMode(): SAMPLER_FILTER_MODE { return this._filterMode; } 66 | 67 | /** 68 | * The GPU sampler resource 69 | */ 70 | public getResource(): GPUSampler { return this._resource; } 71 | 72 | /** 73 | * Create the GPU resource of the sampler 74 | * @param renderer - Renderer instance 75 | * @param descriptor - The descriptor used to create the sampler 76 | */ 77 | public create(renderer: Renderer, descriptor: GPUSamplerDescriptor): void { 78 | if (this._resource === null) { 79 | const device = renderer.getDevice(); 80 | const instance = device.createSampler(descriptor); 81 | this._resource = instance; 82 | } 83 | } 84 | 85 | /** 86 | * Update this sampler 87 | */ 88 | public update(_renderer: Renderer): void { 89 | 90 | } 91 | 92 | /** 93 | * Destroy this Object 94 | */ 95 | public destroy(): void { 96 | this._name = null; 97 | this._addressMode = null; 98 | this._resource = null; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/Shader/index.ts: -------------------------------------------------------------------------------- 1 | import {CompileGLSL} from "../utils"; 2 | import {SHADER_STAGE} from "../constants"; 3 | import {Renderer} from "../Renderer"; 4 | 5 | type ShaderCodeType = ( 6 | Uint32Array | // SPIRV 7 | string // GLSL 8 | ); 9 | 10 | export interface IShaderOptions { 11 | name?: string; 12 | stage: SHADER_STAGE; 13 | code: ShaderCodeType; 14 | } 15 | 16 | export const SHADER_DEFAULT_OPTIONS: IShaderOptions = { 17 | name: null, 18 | stage: SHADER_STAGE.NONE, 19 | code: null 20 | }; 21 | 22 | function ToShaderStageString(stage: SHADER_STAGE): string { 23 | switch (stage) { 24 | case SHADER_STAGE.NONE: 25 | throw new Error(`'SHADER_STAGE.NONE' is not a valid shader stage`); 26 | case SHADER_STAGE.VERTEX: 27 | return "vertex"; 28 | case SHADER_STAGE.FRAGMENT: 29 | return "fragment"; 30 | } 31 | } 32 | 33 | export class Shader { 34 | 35 | private _name: string = null; 36 | private _stage: SHADER_STAGE = SHADER_STAGE.NONE; 37 | private _code: ShaderCodeType = null; 38 | 39 | private _resource: GPUShaderModule = null; 40 | 41 | /** 42 | * @param options - Create options 43 | */ 44 | public constructor(options?: IShaderOptions) { 45 | // Normalize options 46 | options = Object.assign({}, SHADER_DEFAULT_OPTIONS, options); 47 | // Process options 48 | this.setName(options.name); 49 | this._stage = options.stage; 50 | // In case GLSL was passed, compile it into SPIRV 51 | if (typeof options.code === "string") { 52 | this._code = CompileGLSL(options.code, ToShaderStageString(options.stage)); 53 | } else { 54 | this._code = options.code; 55 | } 56 | } 57 | 58 | /** 59 | * The shader name 60 | */ 61 | public getName(): string { return this._name; } 62 | 63 | /** 64 | * Update the shader name 65 | * @param value - The new shader name 66 | */ 67 | public setName(value: string): void { this._name = value; } 68 | 69 | /** 70 | * Returns the shader's GLSL or SPIRV code 71 | */ 72 | public getCode(): ShaderCodeType { return this._code; } 73 | 74 | /** 75 | * Returns the shader's stage 76 | */ 77 | public getStage(): SHADER_STAGE { return this._stage; } 78 | 79 | /** 80 | * The GPU shader resource 81 | */ 82 | public getResource(): GPUShaderModule { return this._resource; } 83 | 84 | /** 85 | * Create the GPU resource of the shader 86 | * @param renderer - Renderer instance 87 | * @param descriptor - The descriptor used to create the shader 88 | */ 89 | public create(renderer: Renderer, descriptor: GPUShaderModuleDescriptor): void { 90 | if (this._resource === null) { 91 | const device = renderer.getDevice(); 92 | const instance = device.createShaderModule(descriptor); 93 | this._resource = instance; 94 | } 95 | } 96 | 97 | /** 98 | * Destroy this Object 99 | */ 100 | public destroy(): void { 101 | this._name = null; 102 | this._code = null; 103 | this._resource = null; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "eventemitter3"; 2 | 3 | export {EventEmitter}; 4 | 5 | let glslangModule: any = null; 6 | /** 7 | * Load the glslang module 8 | */ 9 | export async function LoadGLSLang() { 10 | if (glslangModule !== null) return glslangModule; 11 | const module = await (window["eval"])(`import("https://unpkg.com/@webgpu/glslang@0.0.15/dist/web-devel/glslang.js")`); 12 | glslangModule = await module.default(); 13 | return glslangModule; 14 | } 15 | 16 | /** 17 | * Compiles the provided GLSL code into SPIR-V 18 | * @param glsl - The GLSL string to compile 19 | * @param type - The shader type of the provided GLSL code 20 | */ 21 | export function CompileGLSL(glsl: string, type: string): Uint32Array { 22 | return glslangModule.compileGLSL(glsl, type); 23 | } 24 | 25 | let uid: number = 0; 26 | /** 27 | * Returns a unique id 28 | */ 29 | export function GetUniqueId(): number { 30 | return uid++; 31 | } 32 | 33 | /** 34 | * Fetches a text from the provided path 35 | * @param path - The path to fetch the text from 36 | */ 37 | export function FetchText(path: string): Promise { 38 | return new Promise(resolve => { 39 | fetch(path).then(resp => resp.text().then(resolve)); 40 | }); 41 | } 42 | 43 | /** 44 | * Log to the console 45 | * @param args - The arguments to log 46 | */ 47 | export function Log(...args: any[]) { 48 | // eslint-disable-next-line no-console 49 | console.log(...args); 50 | } 51 | 52 | /** 53 | * Log a warning to the console 54 | * @param args - The arguments to log 55 | */ 56 | export function Warn(...args: any[]) { 57 | // eslint-disable-next-line no-console 58 | console.warn(...args); 59 | } 60 | 61 | /** 62 | * Log an error to the console 63 | * @param args - The arguments to log 64 | */ 65 | export function Error(...args: any[]) { 66 | // eslint-disable-next-line no-console 67 | console.error(...args); 68 | } 69 | 70 | /** 71 | * Throws an unrachable error 72 | */ 73 | export function Unreachable(): void { 74 | throw new ReferenceError(`Unreachable code hit`); 75 | } 76 | 77 | /** 78 | * Returns the hash of the provided string 79 | * @param str - The string to hash 80 | */ 81 | export function GetStringHash(str: string): number { 82 | let hash = 0; 83 | for (let ii = 0; ii < str.length; ++ii) { 84 | hash = ((hash << 5) - hash + str.charCodeAt(ii)) << 0; 85 | } 86 | return hash; 87 | } 88 | 89 | /** 90 | * Returns a high-resolution timestamp 91 | */ 92 | export function GetTimeStamp(): number { 93 | // node environment 94 | /*if (typeof process !== "undefined" && typeof window === "undefined") { 95 | return require("perf_hooks").performance.now(); 96 | }*/ 97 | // browser environment 98 | return (performance as any).now(); 99 | } 100 | 101 | /** 102 | * Fixates a number at the provided range 103 | * @param value - The value to fixate 104 | * @param range - The range to fixate 105 | */ 106 | export function FixateToZero(value: number, range: number): number { 107 | if (value > 0 && value <= range) return 0.0; 108 | if (value < 0 && value >= -range) return 0.0; 109 | return value; 110 | } 111 | 112 | /** 113 | * Clamps a value between the provided min and max range 114 | * @param value - The value to clamp 115 | * @param min - The minimum range 116 | * @param max - The maximum range 117 | */ 118 | export function Clamp(value: number, min: number, max: number): number { 119 | return Math.max(Math.min(max, value), min); 120 | } 121 | -------------------------------------------------------------------------------- /src/Buffer/index.ts: -------------------------------------------------------------------------------- 1 | import {BUFFER_FORMAT} from "../constants"; 2 | import {Renderer} from "../Renderer"; 3 | 4 | export interface IBufferOptions { 5 | name?: string; 6 | format: BUFFER_FORMAT; 7 | byteLength: number; 8 | } 9 | 10 | export const BUFFER_DEFAULT_OPTIONS: IBufferOptions = { 11 | name: null, 12 | format: BUFFER_FORMAT.NONE, 13 | byteLength: 0, 14 | }; 15 | 16 | export interface IBufferUpdate { 17 | offset: number; 18 | data: ArrayBufferView; 19 | } 20 | 21 | export class Buffer { 22 | 23 | private _name: string = null; 24 | private _format: BUFFER_FORMAT = BUFFER_FORMAT.NONE; 25 | private _byteLength: number = 0; 26 | 27 | private _updateQueue: IBufferUpdate[] = []; 28 | 29 | private _resource: GPUBuffer = null; 30 | 31 | /** 32 | * @param options - Create options 33 | */ 34 | public constructor(options?: IBufferOptions) { 35 | // Normalize options 36 | options = Object.assign({}, BUFFER_DEFAULT_OPTIONS, options); 37 | // Process options 38 | this.setName(options.name); 39 | this._format = options.format; 40 | this._byteLength = options.byteLength; 41 | } 42 | 43 | /** 44 | * The buffer name 45 | */ 46 | public getName(): string { return this._name; } 47 | 48 | /** 49 | * Update the buffer name 50 | * @param value - The new buffer name 51 | */ 52 | public setName(value: string): void { this._name = value; } 53 | 54 | /** 55 | * The buffer format 56 | */ 57 | public getFormat(): BUFFER_FORMAT { return this._format; } 58 | 59 | /** 60 | * The byte length of the buffer 61 | */ 62 | public getByteLength(): number { return this._byteLength; } 63 | 64 | /** 65 | * The GPU buffer resource 66 | */ 67 | public getResource(): GPUBuffer { return this._resource; } 68 | 69 | /** 70 | * Create the GPU resource of the buffer 71 | * @param renderer - Renderer instance 72 | * @param descriptor - The descriptor used to create the buffer 73 | */ 74 | public create(renderer: Renderer, descriptor: GPUBufferDescriptor): void { 75 | if (this._resource === null) { 76 | const device = renderer.getDevice(); 77 | const instance = device.createBuffer(descriptor); 78 | this._resource = instance; 79 | } 80 | } 81 | 82 | /** 83 | * Uploads the provided data to the GPU resource 84 | * @param data - The data to copy over 85 | * @param startByteOffset - The starting byte offset into the GPU resource 86 | */ 87 | public setSubData(data: any, startByteOffset: number = 0x0): void { 88 | // Do some quick validations 89 | if (!(ArrayBuffer.isView(data))) { 90 | throw new TypeError(`Parameter 'data' must be an ArrayBufferView`); 91 | } 92 | if (startByteOffset < 0) { 93 | throw new RangeError(`Parameter 'startByteOffset' must be a positive number`); 94 | } 95 | if (startByteOffset + data.byteLength > this.getByteLength()) { 96 | throw new RangeError(`Data copy overflows the byte length of the buffer`); 97 | } 98 | // Enqueue data copy 99 | this._updateQueue.push({offset: startByteOffset, data}); 100 | } 101 | 102 | /** 103 | * Update this buffer 104 | */ 105 | public update(renderer: Renderer): void { 106 | const queue = renderer.getDevice().defaultQueue; 107 | const updateQueue = this._updateQueue; 108 | const resource = this.getResource(); 109 | if (resource !== null) { 110 | for (let ii = 0; ii < updateQueue.length; ++ii) { 111 | const {offset, data} = updateQueue[ii]; 112 | queue.writeBuffer(resource, offset, data); 113 | updateQueue.splice(ii--, 1); 114 | } 115 | } 116 | } 117 | 118 | /** 119 | * Destroy this Object 120 | */ 121 | public destroy(): void { 122 | this._name = null; 123 | this._resource = null; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Supported shader stages 3 | */ 4 | export enum SHADER_STAGE { 5 | NONE = 0 << 0, 6 | VERTEX = 1 << 0, 7 | FRAGMENT = 1 << 1 8 | } 9 | 10 | /** 11 | * Supported shader vertex attributes 12 | */ 13 | export enum SHADER_ATTRIBUTE { 14 | NONE, 15 | UCHAR2, 16 | UCHAR4, 17 | CHAR2, 18 | CHAR4, 19 | UCHAR2_NORM, 20 | UCHAR4_NORM, 21 | CHAR2_NORM, 22 | CHAR4_NORM, 23 | USHORT2, 24 | USHORT4, 25 | SHORT2, 26 | SHORT4, 27 | USHORT2_NORM, 28 | USHORT4_NORM, 29 | SHORT2_NORM, 30 | SHORT4_NORM, 31 | HALF2, 32 | HALF4, 33 | FLOAT, 34 | FLOAT2, 35 | FLOAT3, 36 | FLOAT4, 37 | UINT, 38 | UINT2, 39 | UINT3, 40 | UINT4, 41 | INT, 42 | INT2, 43 | INT3, 44 | INT4 45 | } 46 | 47 | /** 48 | * Supported shader uniforms 49 | */ 50 | export enum SHADER_UNIFORM { 51 | NONE, 52 | UNIFORM_BUFFER, 53 | STORAGE_BUFFER, 54 | STORAGE_BUFFER_READONLY, 55 | SAMPLER, 56 | TEXTURE, 57 | STORAGE_TEXTURE 58 | } 59 | 60 | /** 61 | * Supported culling modes 62 | */ 63 | export enum MATERIAL_CULL_MODE { 64 | NONE, 65 | FRONT, 66 | BACK 67 | } 68 | 69 | /** 70 | * Supported blending modes 71 | */ 72 | export enum MATERIAL_BLEND_MODE { 73 | NONE, 74 | PREMULTIPLY 75 | } 76 | 77 | /** 78 | * Supported depth comparison modes 79 | */ 80 | export enum MATERIAL_DEPTH_COMPARISON_MODE { 81 | NONE, 82 | NEVER, 83 | LESS, 84 | GREATER, 85 | EQUAL, 86 | NOT_EQUAL, 87 | LESS_EQUAL, 88 | GREATER_EQUAL, 89 | ALWAYS 90 | } 91 | 92 | /** 93 | * Supported color masks 94 | */ 95 | export enum MATERIAL_COLOR_MASK { 96 | NONE = 0 << 0, 97 | RED = 1 << 0, 98 | GREEN = 1 << 1, 99 | BLUE = 1 << 2, 100 | ALPHA = 1 << 3, 101 | ALL = 1 << 4 102 | } 103 | 104 | /** 105 | * Supported sampler filtering modes 106 | */ 107 | export enum SAMPLER_FILTER_MODE { 108 | NONE, 109 | NEAREST, 110 | LINEAR 111 | } 112 | 113 | /** 114 | * Supported sampler wrapping modes 115 | */ 116 | export enum SAMPLER_WRAP_MODE { 117 | NONE, 118 | CLAMP_TO_EDGE, 119 | REPEAT, 120 | MIRROR_REPEAT 121 | } 122 | 123 | /** 124 | * Supported frame commands for attachments 125 | */ 126 | export enum FRAME_COMMAND { 127 | NONE, 128 | READ, 129 | WRITE, 130 | CLEAR 131 | } 132 | 133 | /** 134 | * Supported buffer formats 135 | */ 136 | export enum BUFFER_FORMAT { 137 | NONE = 0 << 0, 138 | COPY_SOURCE = 1 << 0, 139 | COPY_DESTINATION = 1 << 1, 140 | UNIFORM = 1 << 2, 141 | STORAGE = 1 << 3 142 | } 143 | 144 | /** 145 | * Supported texture formats 146 | */ 147 | export enum TEXTURE_FORMAT { 148 | NONE, 149 | // 8-bit formats 150 | R8_UNORM, 151 | R8_SNORM, 152 | R8_UINT, 153 | R8_SINT, 154 | // 16-bit formats 155 | R16_UINT, 156 | R16_SINT, 157 | R16_FLOAT, 158 | RG8_UNORM, 159 | RG8_SNORM, 160 | RG8_UINT, 161 | RG8_SINT, 162 | // 32-bit formats 163 | R32_UINT, 164 | R32_SINT, 165 | R32_FLOAT, 166 | RG16_UINT, 167 | RG16_SINT, 168 | RG16_FLOAT, 169 | RGBA8_UNORM, 170 | RGBA8_UNORM_SRGB, 171 | RGBA8_SNORM, 172 | RGBA8_UINT, 173 | RGBA8_SINT, 174 | BGRA8_UNORM, 175 | BGRA8_UNORM_SRGB, 176 | // 64-bit formats 177 | RG32_UINT, 178 | RG32_SINT, 179 | RG32_FLOAT, 180 | RGBA16_UINT, 181 | RGBA16_SINT, 182 | RGBA16_FLOAT, 183 | // 128-bit formats 184 | RGBA32_UINT, 185 | RGBA32_SINT, 186 | RGBA32_FLOAT, 187 | // BC compressed formats 188 | BC1_RGBA_UNORM, 189 | BC1_RGBA_UNORM_SRGB, 190 | BC2_RGBA_UNORM, 191 | BC2_RGBA_UNORM_SRGB, 192 | BC3_RGBA_UNORM, 193 | BC3_RGBA_UNORM_SRGB, 194 | BC4_R_UNORM, 195 | BC4_R_SNORM, 196 | BC5_RG_UNORM, 197 | BC5_RG_SNORM, 198 | BC6H_RGB_UFLOAT, 199 | BC6H_RGB_FLOAT, 200 | BC7_RGBA_UNORM, 201 | BC7_RGBA_UNORM_SRGB, 202 | // Depth formats 203 | DEPTH24_PLUS, 204 | DEPTH32_FLOAT, 205 | } 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YUE 2 | 3 | Personal WebGPU based 3D renderer. 4 | 5 | ### Installation: 6 | ```` 7 | npm install yue.js 8 | ```` 9 | 10 | ### Demo: 11 | 12 | 13 | 14 | 15 | 16 | ### API Example: 17 | 18 | ````ts 19 | import * as YUE from "yue"; 20 | 21 | (async() => { 22 | const renderer = await new YUE.Renderer({canvas}).create(); 23 | 24 | const camera = new YUE.SphericalCamera(); 25 | camera.attachControl(renderer.getCanvas()); 26 | // Do an initial resize to fit the screen 27 | camera.resize(window.innerWidth, window.innerHeight); 28 | 29 | const depthAttachment = new YUE.Texture({ 30 | width: window.innerWidth, 31 | height: window.innerHeight, 32 | isRenderable: true, 33 | format: YUE.TEXTURE_FORMAT.DEPTH32_FLOAT 34 | }); 35 | 36 | const frame = new YUE.Frame({ 37 | depthAttachment: { 38 | attachment: depthAttachment, 39 | readCommand: YUE.FRAME_COMMAND.CLEAR, 40 | writeCommand: YUE.FRAME_COMMAND.WRITE 41 | }, 42 | colorAttachments: [ 43 | { 44 | // Render into swapchain texture 45 | attachment: renderer.getSwapchainTexture(), 46 | clearColor: [1, 1, 1, 1], 47 | readCommand: YUE.FRAME_COMMAND.CLEAR, 48 | writeCommand: YUE.FRAME_COMMAND.WRITE 49 | } 50 | ] 51 | }); 52 | frame.attachCamera(camera); 53 | 54 | const scene = new YUE.Container(); 55 | frame.addChild(scene); 56 | 57 | const vertexShaderCode = await fetchText("./shaders/grid.vert"); 58 | const fragmentShaderCode = await fetchText("./shaders/grid.frag"); 59 | 60 | const vertexShader = new YUE.Shader({stage: YUE.SHADER_STAGE.VERTEX, code: vertexShaderCode}); 61 | const fragmentShader = new YUE.Shader({stage: YUE.SHADER_STAGE.FRAGMENT, code: fragmentShaderCode}); 62 | 63 | const material = new YUE.Material({ 64 | attributes: YUE.Plane.AttributeLayout, 65 | vertexShader: { 66 | shader: vertexShader, 67 | uniforms: [ 68 | {name: "Camera", id: 0, type: YUE.SHADER_UNIFORM.UNIFORM_BUFFER, isShared: true, byteLength: 64}, 69 | {name: "Object", id: 1, type: YUE.SHADER_UNIFORM.UNIFORM_BUFFER, byteLength: 64} 70 | ] 71 | }, 72 | fragmentShader: { 73 | shader: fragmentShader, 74 | uniforms: [] 75 | }, 76 | depthOutput: { 77 | format: YUE.TEXTURE_FORMAT.DEPTH32_FLOAT 78 | }, 79 | colorOutputs: [ 80 | {format: renderer.getSwapchainFormat()}, 81 | ], 82 | cullMode: YUE.MATERIAL_CULL_MODE.NONE 83 | }); 84 | 85 | const mesh = new YUE.Mesh({ 86 | material: material, 87 | indices: YUE.Plane.Indices, 88 | scale: new Float32Array([1, 1, 1]), 89 | translation: new Float32Array([0, 0, 0]), 90 | attributes: YUE.Plane.Attributes 91 | }); 92 | scene.addChild(mesh); 93 | 94 | // Simple ray cast from screen center 95 | const ray = new YUE.Ray({camera}).fromCameraCenter(); 96 | // Contains intersection point in world-space 97 | const doesIntersect = ray.intersectsAABB(mesh.getBoundings()) && mesh.intersectRay(ray); 98 | // Indicates if the scene is inside the camera's frustum 99 | const isInsideFrustum = camera.intersectsAABB(scene.getBoundings()); 100 | 101 | // Update loop 102 | setTimeout(function updateLoop() { 103 | (async function() { 104 | await frame.update(renderer); 105 | setTimeout(updateLoop); 106 | })(); 107 | }); 108 | 109 | // Draw loop 110 | requestAnimationFrame(function drawLoop(time: number) { 111 | requestAnimationFrame(drawLoop); 112 | // Render frames 113 | renderer.render(frame); 114 | // Update model matrix of the mesh 115 | mesh.updateUniform("Object", mesh.getModelMatrix()); 116 | // Update camera matrix of the material 117 | // This update affects all objects using this material since the uniform 'Camera' is shared 118 | material.updateUniform("Camera", camera.getViewProjectionMatrix()); 119 | }); 120 | 121 | })(); 122 | ```` 123 | -------------------------------------------------------------------------------- /src/Camera/SphericalCamera.ts: -------------------------------------------------------------------------------- 1 | import {mat4, vec3} from "gl-matrix"; 2 | 3 | import {AbstractCamera, ICameraOptions} from "./AbstractCamera"; 4 | 5 | /// 6 | import * as InteractionEvents from "normalized-interaction-events"; 7 | /// 8 | import * as IntertialTurntableCamera from "inertial-turntable-camera"; 9 | 10 | export class SphericalCamera extends AbstractCamera { 11 | 12 | private _instance: any = null; 13 | 14 | /** 15 | * @param options - Create options 16 | */ 17 | public constructor(options?: ICameraOptions) { 18 | super(options); 19 | // Setup camera 20 | const instance = IntertialTurntableCamera.default({ 21 | phi: 0.5, 22 | theta: 1, 23 | aspectRatio: this.getAspect(), 24 | distance: 2 25 | }); 26 | // Update camera settings 27 | { 28 | instance.params.near = 0.01; 29 | instance.params.far = 8192.0; 30 | instance.params.panDecayTime = 50; 31 | instance.params.zoomDecayTime = 50; 32 | instance.params.rotationDecayTime = 50; 33 | instance.params.zoomAboutCursor = false; 34 | instance.params.rotateAboutCenter = true; 35 | const prevYRange = instance.params.distance * Math.tan(instance.params.fovY * 0.5); 36 | instance.params.fovY = 40 * Math.PI / 180.0; 37 | instance.params.distance = prevYRange / Math.tan(instance.params.fovY * 0.5); 38 | } 39 | this._instance = instance; 40 | } 41 | 42 | /** 43 | * The instance of the spherical camera 44 | */ 45 | public getInstance(): any { return this._instance; } 46 | 47 | /** 48 | * Attaches controls to the provided element 49 | * @param element - HTML canvas element to listen for events 50 | */ 51 | public attachControl(element: HTMLCanvasElement): void { 52 | InteractionEvents.default(element) 53 | // Listen for mouse wheel events 54 | .on("wheel", (e: any) => { 55 | this._instance.zoom(e.x, e.y, Math.exp(-e.dy * 0.5) - 1.0); 56 | e.originalEvent.preventDefault(); 57 | }) 58 | // Listen for mouse move events 59 | .on("mousemove", (e: any) => { 60 | if (!e.active) return; 61 | // Left mouse button pressed 62 | if (e.buttons === 2) { 63 | // Translate camera when shift is pressed 64 | if (e.mods.shift) { 65 | this._instance.pan(e.dx, e.dy); 66 | // Rotate camera 67 | } else { 68 | this._instance.rotate(-e.dx * Math.PI * 0.5, -e.dy * Math.PI * 0.5); 69 | } 70 | // Translate camera when middle mouse button is pressed 71 | } else if (e.buttons === 4) { 72 | this._instance.pan(e.dx, e.dy); 73 | } 74 | e.originalEvent.preventDefault(); 75 | }); 76 | // Resize camera based on the element's boundings 77 | { 78 | this.setWidth(element.width); 79 | this.setHeight(element.height); 80 | } 81 | } 82 | 83 | /** 84 | * Resize the camera 85 | * @param width - The new camera width 86 | * @param height - The new camera height 87 | */ 88 | public resize(width: number, height: number): void { 89 | this._instance.resize(width / height); 90 | super.resize(width, height); 91 | } 92 | 93 | /** 94 | * Should be called every frame 95 | */ 96 | public update(): void { 97 | this.getInstance().tick({ 98 | near: this.getInstance().params.distance * 0.01, 99 | far: (this.getInstance().params.distance * 2) + 200 100 | }); 101 | const mView = this._instance.state.view; 102 | const mProjection = this._instance.state.projection; 103 | this.updateTransforms(mView, mProjection); 104 | this.updateFrustum(); 105 | this.emit("update"); 106 | } 107 | 108 | /** 109 | * Destroy this Object 110 | */ 111 | public destroy(): void { 112 | this._instance = null; 113 | super.destroy(); 114 | } 115 | 116 | /** 117 | * Updates the internal transforms and matrices 118 | * @param mCameraView - The view matrix of the camera model 119 | * @param mCameraProjection - The projection matrix of the camera model 120 | */ 121 | public updateTransforms(mCameraView: mat4, mCameraProjection: mat4): void { 122 | super.updateTransforms(mCameraView, mCameraProjection); 123 | // Translation vector 124 | const vTranslation = this._instance.state.eye; 125 | vec3.copy(this.getTranslation(), vTranslation); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/Texture/index.ts: -------------------------------------------------------------------------------- 1 | import {TEXTURE_FORMAT} from "../constants"; 2 | import {Renderer} from "../Renderer"; 3 | 4 | export interface ITextureOptions { 5 | name?: string; 6 | data?: ArrayBufferView; 7 | width: number; 8 | height: number; 9 | depth?: number; 10 | bytesPerRow?: number; 11 | isRenderable?: boolean; 12 | format: TEXTURE_FORMAT; 13 | } 14 | 15 | export const TEXTURE_DEFAULT_OPTIONS: ITextureOptions = { 16 | name: null, 17 | data: null, 18 | width: 0, 19 | height: 0, 20 | depth: 1, 21 | bytesPerRow: 0, 22 | isRenderable: false, 23 | format: TEXTURE_FORMAT.NONE 24 | }; 25 | 26 | export class Texture { 27 | 28 | private _name: string = null; 29 | private _data: ArrayBufferView = null; 30 | private _width: number = 0; 31 | private _height: number = 0; 32 | private _depth: number = 0; 33 | private _bytesPerRow: number = 0; 34 | private _format: TEXTURE_FORMAT = TEXTURE_FORMAT.NONE; 35 | private _isRenderable: boolean = false; 36 | 37 | private _resource: GPUTexture = null; 38 | private _resourceView: GPUTextureView = null; 39 | 40 | /** 41 | * @param options - Create options 42 | */ 43 | public constructor(options?: ITextureOptions) { 44 | // Normalize options 45 | options = Object.assign({}, TEXTURE_DEFAULT_OPTIONS, options); 46 | // Process options 47 | this.setName(options.name); 48 | this._data = options.data; 49 | this._width = options.width; 50 | this._height = options.height; 51 | this._depth = options.depth; 52 | this._bytesPerRow = options.bytesPerRow; 53 | this._format = options.format; 54 | this._isRenderable = options.isRenderable; 55 | } 56 | 57 | /** 58 | * The texture name 59 | */ 60 | public getName(): string { return this._name; } 61 | /** 62 | * Update the texture name 63 | * @param value - The new texture name 64 | */ 65 | public setName(value: string): void { this._name = value; } 66 | 67 | /** 68 | * The texture data 69 | */ 70 | public getData(): ArrayBufferView { return this._data; } 71 | 72 | /** 73 | * The texture width 74 | */ 75 | public getWidth(): number { return this._width; } 76 | 77 | /** 78 | * The texture height 79 | */ 80 | public getHeight(): number { return this._height; } 81 | 82 | /** 83 | * The texture depth 84 | */ 85 | public getDepth(): number { return this._depth; } 86 | 87 | /** 88 | * The texture bytes per row 89 | */ 90 | public getBytesPerRow(): TEXTURE_FORMAT { return this._bytesPerRow; } 91 | 92 | /** 93 | * The texture format 94 | */ 95 | public getFormat(): TEXTURE_FORMAT { return this._format; } 96 | 97 | /** 98 | * Indicates if this texture is renderable 99 | */ 100 | public isRenderable(): boolean { return this._isRenderable; } 101 | 102 | /** 103 | * The GPU texture resource 104 | */ 105 | public getResource(): GPUTexture { return this._resource; } 106 | 107 | /** 108 | * The GPU texture view resource 109 | */ 110 | public getResourceView(): GPUTextureView { return this._resourceView; } 111 | 112 | /** 113 | * Create the GPU resource of the sampler 114 | * @param renderer - Renderer instance 115 | * @param descriptor - The descriptor used to create the texture 116 | */ 117 | public create(renderer: Renderer, descriptor: GPUTextureDescriptor): void { 118 | const device = renderer.getDevice(); 119 | // Reserve GPUTexture in case it doesn't exist yet 120 | if (this._resource === null) { 121 | const texture = device.createTexture(descriptor); 122 | this._resource = texture; 123 | this._resourceView = texture.createView(); 124 | // Data was provided for the texture that needs to be copied 125 | if (this.getData() !== null) { 126 | const imageData = this.getData(); 127 | const width = this.getWidth(); 128 | const height = this.getHeight(); 129 | const depth = this.getDepth(); 130 | const bytesPerRow = this.getBytesPerRow(); 131 | const resource = this.getResource(); 132 | renderer.getQueueCommander().transferDataToTexture(resource, imageData, width, height, depth, bytesPerRow, null); 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Update this texture 139 | */ 140 | public update(_renderer: Renderer): void { 141 | 142 | } 143 | 144 | /** 145 | * Destroy this Object 146 | */ 147 | public destroy(): void { 148 | this._name = null; 149 | this._data = null; 150 | this._resource = null; 151 | this._resourceView = null; 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | plugins: [ 4 | "@typescript-eslint/eslint-plugin", 5 | "eslint-plugin-tsdoc", 6 | "eslint-comments" 7 | ], 8 | extends: [ 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 11 | ], 12 | parser: "@typescript-eslint/parser", 13 | parserOptions: { 14 | createDefaultProgram: true, 15 | project: [ 16 | "./tsconfig.json", 17 | ], 18 | tsconfigRootDir: __dirname, 19 | sourceType: "module" 20 | }, 21 | rules: { 22 | "computed-property-spacing": ["error", "never"], 23 | "array-bracket-spacing": ["error", "never"], 24 | "object-curly-spacing": ["error", "never"], 25 | "@typescript-eslint/type-annotation-spacing": ["error", { 26 | before: false, 27 | after: true 28 | }], 29 | "space-in-parens": ["error", "never"], 30 | "space-before-blocks": ["error", "always"], 31 | "space-before-function-paren": ["error", "never"], 32 | "semi": [2, "always"], 33 | "tsdoc/syntax": "warn", 34 | "@typescript-eslint/no-explicit-any": "off", 35 | "@typescript-eslint/no-inferrable-types": "off", 36 | "no-else-return": "error", 37 | "@typescript-eslint/ban-ts-comment": [ 38 | "error", 39 | { 40 | "ts-expect-error": "allow-with-description", 41 | "ts-ignore": true, 42 | "ts-nocheck": true, 43 | "ts-check": false, 44 | minimumDescriptionLength: 5, 45 | }, 46 | ], 47 | "@typescript-eslint/consistent-type-definitions": ["error", "interface"], 48 | //"@typescript-eslint/explicit-function-return-type": "error", 49 | "@typescript-eslint/explicit-member-accessibility": "error", 50 | "@typescript-eslint/explicit-module-boundary-types": "off", 51 | "@typescript-eslint/no-empty-function": "off", 52 | "@typescript-eslint/no-empty-interface": "off", 53 | "@typescript-eslint/no-non-null-assertion": "off", 54 | "@typescript-eslint/no-var-requires": "off", 55 | "@typescript-eslint/prefer-nullish-coalescing": "error", 56 | "@typescript-eslint/prefer-optional-chain": "error", 57 | "@typescript-eslint/unbound-method": "off", 58 | "@typescript-eslint/prefer-as-const": "error", 59 | "@typescript-eslint/no-this-alias": "off", 60 | "@typescript-eslint/no-unused-vars": [ 61 | "warn", 62 | { 63 | varsIgnorePattern: "^_", 64 | argsIgnorePattern: "^_" 65 | }, 66 | ], 67 | 68 | // TODO - enable these new recommended rules 69 | "@typescript-eslint/no-floating-promises": "off", 70 | "@typescript-eslint/no-unsafe-assignment": "off", 71 | "@typescript-eslint/no-unsafe-call": "off", 72 | "@typescript-eslint/no-unsafe-member-access": "off", 73 | "@typescript-eslint/no-unsafe-return": "off", 74 | "@typescript-eslint/restrict-plus-operands": "off", 75 | "@typescript-eslint/restrict-template-expressions": "off", 76 | "@typescript-eslint/naming-convention": [ 77 | "error", 78 | { 79 | selector: "enum", 80 | format: ["UPPER_CASE"] 81 | }, 82 | { 83 | selector: "enumMember", 84 | format: ["UPPER_CASE"], 85 | leadingUnderscore: "forbid", 86 | trailingUnderscore: "forbid" 87 | }, 88 | { 89 | selector: "memberLike", 90 | modifiers: ["private"], 91 | format: ["camelCase"], 92 | leadingUnderscore: "require" 93 | }, 94 | { 95 | selector: "interface", 96 | format: ["PascalCase"], 97 | prefix: ["I"], 98 | }, 99 | { 100 | selector: "variable", 101 | types: ["boolean"], 102 | format: ["PascalCase"], 103 | prefix: ["do", "is", "should", "has", "can", "did", "will"], 104 | }, 105 | ], 106 | curly: "off", 107 | "no-mixed-operators": "error", 108 | "no-console": "warn", 109 | "no-process-exit": "error", 110 | 111 | "eslint-comments/disable-enable-pair": [ 112 | "error", 113 | { 114 | allowWholeFile: true, 115 | }, 116 | ], 117 | // disallow a eslint-enable comment for multiple eslint-disable comments 118 | "eslint-comments/no-aggregating-enable": "error", 119 | // disallow duplicate eslint-disable comments 120 | "eslint-comments/no-duplicate-disable": "error", 121 | // disallow eslint-disable comments without rule names 122 | "eslint-comments/no-unlimited-disable": "error", 123 | // disallow unused eslint-disable comments 124 | "eslint-comments/no-unused-disable": "error", 125 | // disallow unused eslint-enable comments 126 | "eslint-comments/no-unused-enable": "error", 127 | // disallow ESLint directive-comments 128 | "eslint-comments/no-use": [ 129 | "error", 130 | { 131 | allow: [ 132 | "eslint-disable", 133 | "eslint-disable-line", 134 | "eslint-disable-next-line", 135 | "eslint-enable", 136 | ], 137 | }, 138 | ], 139 | } 140 | }; 141 | -------------------------------------------------------------------------------- /src/Ray/index.ts: -------------------------------------------------------------------------------- 1 | import {vec2, vec3, vec4} from "gl-matrix"; 2 | import {AABB} from "../AABB"; 3 | import {AbstractCamera} from "../Camera"; 4 | 5 | export enum TRIANGLE_FACING { 6 | BACK, 7 | FRONT 8 | } 9 | 10 | export interface IRayTriangleIntersection { 11 | facing: TRIANGLE_FACING; 12 | intersection: vec3; 13 | normal: vec3; 14 | t: number; 15 | } 16 | 17 | export interface IAABBIntersection { 18 | t: number; 19 | } 20 | 21 | export interface IRayOptions { 22 | camera: AbstractCamera; 23 | origin?: vec3; 24 | direction?: vec3; 25 | } 26 | 27 | export const RAY_DEFAULT_OPTIONS: IRayOptions = { 28 | camera: null, 29 | origin: null, 30 | direction: null, 31 | }; 32 | 33 | export class Ray { 34 | 35 | private _origin: vec3 = null; 36 | private _direction: vec3 = null; 37 | 38 | private _camera: AbstractCamera = null; 39 | 40 | /** 41 | * @param options - Create options 42 | */ 43 | public constructor(options: IRayOptions) { 44 | // Normalize options 45 | options = Object.assign({}, RAY_DEFAULT_OPTIONS, options); 46 | this._origin = options.origin ? options.origin : vec3.create(); 47 | this._direction = options.direction ? options.direction : vec3.create(); 48 | this._camera = options.camera; 49 | } 50 | 51 | /** 52 | * The ray origin 53 | */ 54 | public getOrigin(): vec3 { return this._origin; } 55 | 56 | /** 57 | * The ray direction 58 | */ 59 | public getDirection(): vec3 { return this._direction; } 60 | 61 | /** 62 | * Create a world-space ray based on the provided mouse position 63 | * @param x - The mouse X coordinate 64 | * @param y - The mouse Y coordinate 65 | */ 66 | public fromMousePosition(x: number, y: number): Ray { 67 | const camera = this._camera; 68 | const deviceCoords = this._getDeviceCoords(x, y); 69 | const clipCoords = this._getClipCoords(deviceCoords); 70 | const cameraCoords = this._getCameraCoords(clipCoords); 71 | const direction = this._getRayCoords(cameraCoords); 72 | const origin = camera.getTranslation(); 73 | this._origin = vec3.copy(vec3.create(), origin); 74 | this._direction = direction; 75 | return this; 76 | } 77 | 78 | /** 79 | * Creates a ray in world-space based on the camera center 80 | * @param x - The mouse X coordinate in screen-space 81 | * @param y - The mouse Y coordinate in screen-space 82 | */ 83 | public fromCameraCenter(): Ray { 84 | const camera = this._camera; 85 | return this.fromMousePosition( 86 | camera.getWidth() * 0.5, 87 | camera.getHeight() * 0.5 88 | ); 89 | } 90 | 91 | /** 92 | * Converts screen-space coordinates into device-space coordinates 93 | * @param x - The X coordinate in screen-space 94 | * @param y - The Y coordinate in screen-space 95 | */ 96 | private _getDeviceCoords(x: number, y: number): vec2 { 97 | const out = vec2.create(); 98 | const camera = this._camera; 99 | const rx = ((2.0 * x) / camera.getWidth()) - 1.0; 100 | const ry = ((2.0 * y) / camera.getHeight()) - 1.0; 101 | vec2.set(out, rx, ry); 102 | return out; 103 | } 104 | 105 | /** 106 | * Converts device-space coordinates into clip-space coordinates 107 | * @param pt - The 2d vector to convert into clip-space 108 | */ 109 | private _getClipCoords(pt: vec2): vec4 { 110 | const out = vec4.create(); 111 | vec4.set(out, pt[0], -pt[1], -1.0, 1.0); 112 | return out; 113 | } 114 | 115 | /** 116 | * Converts clip-space coordinates into camera-space coordinates 117 | * @param pt - The 4d clip-space vector to convert into camera-space 118 | */ 119 | private _getCameraCoords(pt: vec4): vec4 { 120 | const out = vec4.create(); 121 | const camera = this._camera; 122 | const eyeCoords = vec4.create(); 123 | vec4.transformMat4(eyeCoords, pt, camera.getProjectionInverseMatrix()); 124 | vec4.set(out, eyeCoords[0], eyeCoords[1], -1.0, 0.0); 125 | return out; 126 | } 127 | 128 | /** 129 | * Converts camera-space coordinates into a direction 130 | * @param pt - The 4d camera-space vector to convert into a direction 131 | */ 132 | private _getRayCoords(pt: vec4): vec3 { 133 | const out = vec3.create(); 134 | const camera = this._camera; 135 | const worldCoords = vec4.create(); 136 | vec4.transformMat4(worldCoords, pt, camera.getViewInverseMatrix()); 137 | vec3.set(out, worldCoords[0], worldCoords[1], worldCoords[2]); 138 | vec3.normalize(out, out); 139 | return out; 140 | } 141 | 142 | /** 143 | * Returns a ray-triangle intersection point 144 | * @param v0 - Triangle vertex 0 145 | * @param v1 - Triangle vertex 1 146 | * @param v2 - Triangle vertex 2 147 | */ 148 | public intersectsTriangle(v0: vec3, v1: vec3, v2: vec3): IRayTriangleIntersection { 149 | const origin = this.getOrigin(); 150 | const direction = this.getDirection(); 151 | 152 | const e1 = vec3.sub(vec3.create(), v1, v0); 153 | const e2 = vec3.sub(vec3.create(), v2, v0); 154 | const r = vec3.cross(vec3.create(), direction, e2); 155 | 156 | const s = vec3.sub(vec3.create(), origin, v0); 157 | const a = vec3.dot(e1, r); 158 | const q = vec3.cross(vec3.create(), s, e1); 159 | 160 | const u = vec3.dot(s, r); 161 | const v = vec3.dot(direction, q); 162 | 163 | // Front-facing 164 | if (a > 0.000001) { 165 | if (u < 0.0 || u > a) return null; 166 | if (v < 0.0 || u + v > a) return null; 167 | } 168 | // Back-facing 169 | else if (a < -0.000001) { 170 | if (u > 0.0 || u < a) return null; 171 | if (v > 0.0 || u + v < a) return null; 172 | } 173 | // Parallel 174 | else { 175 | return null; 176 | } 177 | 178 | const intersection = vec3.create(); 179 | vec3.scaleAndAdd(intersection, origin, direction, vec3.dot(e2, q) / a); 180 | const isFrontFacing = a > 0.000001; 181 | const facing = ( 182 | isFrontFacing ? 183 | TRIANGLE_FACING.FRONT : 184 | TRIANGLE_FACING.BACK 185 | ); 186 | const dist = vec3.distance(origin, intersection); 187 | 188 | return {facing, intersection, t: dist, normal: null}; 189 | } 190 | 191 | /** 192 | * Returns T of a ray-aabb intersection 193 | * @param aabb - The AABB to check the intersection with 194 | */ 195 | public intersectsAABB(aabb: AABB): IAABBIntersection { 196 | const min = aabb.getMin(); 197 | const max = aabb.getMax(); 198 | const origin = this.getOrigin(); 199 | const direction = this.getDirection(); 200 | const t1 = (min[0] - origin[0]) / direction[0]; 201 | const t2 = (max[0] - origin[0]) / direction[0]; 202 | const t3 = (min[1] - origin[1]) / direction[1]; 203 | const t4 = (max[1] - origin[1]) / direction[1]; 204 | const t5 = (min[2] - origin[2]) / direction[2]; 205 | const t6 = (max[2] - origin[2]) / direction[2]; 206 | const t7 = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6)); 207 | const t8 = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)); 208 | if (t8 < 0 || t7 > t8) return null; 209 | return {t: t7}; 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /src/Frame/index.ts: -------------------------------------------------------------------------------- 1 | import {Container, IContainerOptions} from "../Container"; 2 | import {Renderer} from "../Renderer"; 3 | import {AbstractCamera} from "../Camera"; 4 | import {Texture} from "../Texture"; 5 | import {RenderPipelineGenerator} from "../Material/RenderPipelineGenerator"; 6 | import {FRAME_COMMAND} from "../constants"; 7 | 8 | export interface IFrameAttachment { 9 | attachment: Texture; 10 | clearColor?: ClearColorType; 11 | readCommand?: FRAME_COMMAND; 12 | writeCommand?: FRAME_COMMAND; 13 | } 14 | 15 | export const FRAME_ATTACHMENT_DEFAULT_OPTIONS: IFrameAttachment = { 16 | attachment: null, 17 | clearColor: null, 18 | readCommand: FRAME_COMMAND.READ, 19 | writeCommand: FRAME_COMMAND.WRITE, 20 | }; 21 | 22 | export interface IFrameOptions extends IContainerOptions { 23 | name?: string; 24 | camera?: AbstractCamera; 25 | clearColor?: ClearColorType; 26 | depthAttachment?: IFrameAttachment; 27 | colorAttachments?: IFrameAttachment[]; 28 | } 29 | 30 | export const FRAME_DEFAULT_OPTIONS: IFrameOptions = { 31 | name: null, 32 | camera: null, 33 | clearColor: [0, 0, 0, 0], 34 | depthAttachment: null, 35 | colorAttachments: null 36 | }; 37 | 38 | type ClearColorType = [number, number, number, number]; 39 | 40 | export class Frame extends Container { 41 | 42 | private _camera: AbstractCamera = null; 43 | 44 | private _clearFrame: Frame = null; 45 | private _clearColor: ClearColorType = null; 46 | 47 | private _depthAttachment: IFrameAttachment = null; 48 | private _colorAttachments: IFrameAttachment[] = []; 49 | 50 | /** 51 | * @param options - Create options 52 | * @param createClearFrame - Indicates if a clear frame should be attached automatically 53 | */ 54 | public constructor(options?: IFrameOptions, createClearFrame: boolean = true) { 55 | super(options); 56 | // Normalize options 57 | options = Object.assign({}, FRAME_DEFAULT_OPTIONS, options); 58 | // Process options 59 | this.setName(options.name); 60 | this._camera = options.camera; 61 | this._clearColor = options.clearColor; 62 | this._depthAttachment = options.depthAttachment ? Object.assign({}, FRAME_ATTACHMENT_DEFAULT_OPTIONS, options.depthAttachment) : null; 63 | this._colorAttachments = options.colorAttachments ? options.colorAttachments.map(a => Object.assign({}, FRAME_ATTACHMENT_DEFAULT_OPTIONS, a)) : []; 64 | if (createClearFrame) this._clearFrame = this._createClearFrame(); 65 | } 66 | 67 | /** 68 | * The frame's attached camera 69 | */ 70 | public getAttachedCamera(): AbstractCamera { return this._camera; } 71 | 72 | /** 73 | * The clear color of the frame 74 | */ 75 | public getClearColor(): ClearColorType { return this._clearColor; } 76 | 77 | /** 78 | * The depth attachment output of the frame 79 | */ 80 | public getDepthAttachment(): IFrameAttachment { return this._depthAttachment; } 81 | 82 | /** 83 | * The color attachment output of the frame 84 | */ 85 | public getColorAttachments(): IFrameAttachment[] { return this._colorAttachments; } 86 | 87 | /** 88 | * Attaches a camera which is used to render this frame 89 | * @param camera - The camera to attach 90 | */ 91 | public attachCamera(camera: AbstractCamera): void { 92 | this._camera = camera; 93 | } 94 | 95 | /** 96 | * Update the frame children 97 | */ 98 | public update(renderer: Renderer): void { 99 | const children = this.getChildren(); 100 | // First rebuild childs if necessary 101 | for (let ii = 0; ii < children.length; ++ii) { 102 | const child = children[ii]; 103 | child.update(renderer); 104 | } 105 | super.update(renderer); 106 | } 107 | 108 | /** 109 | * Clear the frame 110 | * @param renderer - The renderer to clear with 111 | */ 112 | public clear(renderer: Renderer): void { 113 | const clearFrame = this._clearFrame; 114 | if (clearFrame !== null) { 115 | clearFrame.draw(renderer); 116 | } 117 | } 118 | 119 | /** 120 | * Draw the frame 121 | * @param renderer - The renderer to draw with 122 | */ 123 | public draw(renderer: Renderer): void { 124 | const children = this.getChildren(); 125 | const device = renderer.getDevice(); 126 | const camera = this.getAttachedCamera(); 127 | // Create depth texture if necessary 128 | const depthFrameAttachment = this.getDepthAttachment(); 129 | if (depthFrameAttachment !== null) { 130 | const depthAttachment = depthFrameAttachment.attachment; 131 | if (depthAttachment !== null && !depthAttachment.getResource()) { 132 | if (!depthAttachment.isRenderable) { 133 | throw new Error(`Depth attachment texture must have property 'isRenderable' enabled`); 134 | } 135 | const descriptor = RenderPipelineGenerator.GenerateTextureDescriptor(depthAttachment); 136 | depthAttachment.create(renderer, descriptor); 137 | } 138 | } 139 | // Create color attachments textures if necessary 140 | const colorFrameAttachments = this.getColorAttachments(); 141 | for (const colorFrameAttachment of colorFrameAttachments) { 142 | const colorAttachment = colorFrameAttachment.attachment; 143 | if (colorAttachment !== null && !colorAttachment.getResource()) { 144 | if (!colorAttachment.isRenderable) { 145 | throw new Error(`Color attachment texture must have property 'isRenderable' enabled`); 146 | } 147 | const descriptor = RenderPipelineGenerator.GenerateTextureDescriptor(colorAttachment); 148 | colorAttachment.create(renderer, descriptor); 149 | } 150 | } 151 | // Update camera if there is one attached 152 | if (camera instanceof AbstractCamera) { 153 | camera.update(); 154 | } 155 | // Render each child 156 | const commandEncoder = device.createCommandEncoder({}); 157 | const renderPassDescriptor = RenderPipelineGenerator.generateRenderPassDescriptor( 158 | depthFrameAttachment, 159 | colorFrameAttachments 160 | ); 161 | const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor); 162 | for (let ii = 0; ii < children.length; ++ii) { 163 | const child = children[ii]; 164 | child.render(renderPass); 165 | } 166 | renderPass.endPass(); 167 | device.defaultQueue.submit([commandEncoder.finish()]); 168 | } 169 | 170 | /** 171 | * Destroy this Object 172 | */ 173 | public destroy(): void { 174 | super.destroy(); 175 | this._camera = null; 176 | this._clearColor = null; 177 | } 178 | 179 | /** 180 | * Creates a frame to clear this frame 181 | */ 182 | private _createClearFrame(): Frame { 183 | const clearDepthAttachment = Object.assign({}, this.getDepthAttachment()); 184 | clearDepthAttachment.readCommand = FRAME_COMMAND.CLEAR; 185 | const clearColorAttachments = this.getColorAttachments().map(a => { 186 | const clearColorAttachment = Object.assign({}, a); 187 | clearColorAttachment.clearColor = a.clearColor || [0, 0, 0, 0]; 188 | clearColorAttachment.readCommand = FRAME_COMMAND.CLEAR; 189 | return clearColorAttachment; 190 | }); 191 | const out = new Frame({ 192 | depthAttachment: clearDepthAttachment, 193 | colorAttachments: clearColorAttachments 194 | }, false); 195 | return out; 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /src/QueueCommander/index.ts: -------------------------------------------------------------------------------- 1 | export type CallbackFunction = ()=> void; 2 | 3 | export interface IDataToBufferTransfer { 4 | buffer: GPUBuffer; 5 | data: ArrayBufferView; 6 | offset: number; 7 | callback: CallbackFunction; 8 | } 9 | 10 | export interface IDataToTextureTransfer { 11 | texture: GPUTexture; 12 | data: ArrayBufferView; 13 | width: number; 14 | height: number; 15 | depth: number; 16 | bytesPerRow: number; 17 | callback: CallbackFunction; 18 | } 19 | 20 | const SRC_COPY_BUFFER_SIZE = 2 ** 18; 21 | 22 | export class QueueCommander { 23 | 24 | private _device: GPUDevice = null; 25 | 26 | private _srcCopyBuffer: GPUBuffer = null; 27 | 28 | private _dataToBufferTransfers: IDataToBufferTransfer[] = []; 29 | private _dataToTextureTransfers: IDataToTextureTransfer[] = []; 30 | 31 | /** 32 | * @param device - Thew GPU device 33 | */ 34 | public constructor(device: GPUDevice) { 35 | this._device = device; 36 | this._srcCopyBuffer = this._createBuffer( 37 | SRC_COPY_BUFFER_SIZE, 38 | GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE 39 | ); 40 | } 41 | 42 | /** 43 | * Create a new GPU buffer 44 | * @param size - The buffer size 45 | * @param usage - The buffer usage 46 | */ 47 | private _createBuffer(size: number, usage: GPUBufferUsageFlags): GPUBuffer { 48 | const buffer = this._device.createBuffer({ 49 | size: size, 50 | usage: usage, 51 | mappedAtCreation: false, 52 | }); 53 | return buffer; 54 | } 55 | 56 | /** 57 | * Transfer CPU data into a GPUBuffer 58 | * @param buffer - The destination buffer to copy the data into 59 | * @param data - The data to copy 60 | * @param byteOffset - The starting byte offset into the destination buffer 61 | */ 62 | public transferDataToBuffer( 63 | buffer: GPUBuffer, 64 | data: ArrayBufferView, 65 | byteOffset: number = 0x0, 66 | callback: CallbackFunction = null 67 | ): void { 68 | this._dataToBufferTransfers.push({ 69 | buffer, data, offset: byteOffset, callback 70 | }); 71 | } 72 | 73 | /** 74 | * Transfer CPU data into a GPUTexture 75 | * @param buffer - The destination texture to copy the data into 76 | * @param data - The data to copy 77 | */ 78 | public transferDataToTexture( 79 | texture: GPUTexture, 80 | data: ArrayBufferView, 81 | width: number = 0, 82 | height: number = 0, 83 | depth: number = 0, 84 | bytesPerRow: number = 0, 85 | callback: CallbackFunction = null 86 | ): void { 87 | this._dataToTextureTransfers.push({ 88 | texture, data, width, height, depth, bytesPerRow, callback 89 | }); 90 | } 91 | 92 | /** 93 | * Flush and execute all queued operations 94 | */ 95 | public flush(): void { 96 | this._flushDataToBufferTransfers(); 97 | this._flushDataToTextureTransfers(); 98 | } 99 | 100 | /** 101 | * Flush all data to buffer transfers 102 | */ 103 | private _flushDataToBufferTransfers(): void { 104 | const device = this._device; 105 | const queue = device.defaultQueue; 106 | 107 | const dataToBufferTransfers = this._dataToBufferTransfers; 108 | // Abort here if there's nothing to do 109 | if (!dataToBufferTransfers.length) return; 110 | 111 | // Flush large buffers 112 | for (let ii = 0; ii < dataToBufferTransfers.length; ++ii) { 113 | const {buffer, data, offset} = dataToBufferTransfers[ii]; 114 | // CPU -> GPU copy 115 | queue.writeBuffer(buffer, offset, data); 116 | // Remove transfer item 117 | dataToBufferTransfers.splice(ii--, 1); 118 | } 119 | 120 | // Trigger callbacks if necessary 121 | for (const {callback} of dataToBufferTransfers) { 122 | if (callback instanceof Function) callback(); 123 | } 124 | return; 125 | 126 | // Buffer for CPU -> GPU copy operations 127 | const srcCopyBuffer = this._srcCopyBuffer; 128 | 129 | // Put source copy buffer into mapped state 130 | //await srcCopyBuffer.mapAsync(GPUMapMode.WRITE, 0x0, SRC_COPY_BUFFER_SIZE); 131 | 132 | // Create view into mapped buffer 133 | const srcCopyBufferView = new Uint8Array(srcCopyBuffer.getMappedRange(0x0, SRC_COPY_BUFFER_SIZE)); 134 | 135 | // Buffer copies which are too large to be chunked together 136 | const rawQueueBufferCopies: IDataToBufferTransfer[] = []; 137 | 138 | // Record enqueued operations 139 | const commandEncoder = device.createCommandEncoder({}); 140 | // Record buffer copy operations 141 | { 142 | let byteOffset = 0x0; 143 | for (let ii = 0; ii < dataToBufferTransfers.length; ++ii) { 144 | const {buffer, data, offset} = dataToBufferTransfers[ii]; 145 | const dataBytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); 146 | // Need resize 147 | if (byteOffset + dataBytes.byteLength > srcCopyBufferView.byteLength) { 148 | //throw new RangeError(`Copy buffer is not large enough to hold data`); 149 | rawQueueBufferCopies.push(dataToBufferTransfers[ii]); 150 | // Don't forget to remove transfer item 151 | dataToBufferTransfers.splice(ii--, 1); 152 | continue; 153 | } 154 | // copy into CPU mapped buffer 155 | srcCopyBufferView.set(dataBytes, byteOffset); 156 | // record CPU -> GPU copy operation 157 | commandEncoder.copyBufferToBuffer( 158 | srcCopyBuffer, byteOffset, buffer, offset, dataBytes.byteLength 159 | ); 160 | byteOffset += dataBytes.byteLength; 161 | // Remove transfer item 162 | dataToBufferTransfers.splice(ii--, 1); 163 | } 164 | } 165 | // Unmap source copy buffer 166 | srcCopyBuffer.unmap(); 167 | // Execute recorded commands 168 | queue.submit([commandEncoder.finish()]); 169 | 170 | // Flush large buffers 171 | for (let ii = 0; ii < rawQueueBufferCopies.length; ++ii) { 172 | const {buffer, data, offset} = rawQueueBufferCopies[ii]; 173 | // CPU -> GPU copy 174 | queue.writeBuffer(buffer, offset, data); 175 | // Remove transfer item 176 | rawQueueBufferCopies.splice(ii--, 1); 177 | } 178 | 179 | // Trigger callbacks if necessary 180 | for (const {callback} of dataToBufferTransfers) { 181 | if (callback instanceof Function) callback(); 182 | } 183 | } 184 | 185 | /** 186 | * Flush all data to texture transfers 187 | */ 188 | private _flushDataToTextureTransfers(): Promise { 189 | const device = this._device; 190 | const queue = device.defaultQueue; 191 | 192 | const dataToTextureTransfers = this._dataToTextureTransfers; 193 | // Abort here if there's nothing to do 194 | if (!dataToTextureTransfers.length) return; 195 | 196 | // Transfer data to textures 197 | for (let ii = 0; ii < dataToTextureTransfers.length; ++ii) { 198 | const {texture, data, bytesPerRow, width, height, depth} = dataToTextureTransfers[ii]; 199 | // CPU -> GPU copy 200 | queue.writeTexture( 201 | {texture, origin: {x: 0, y: 0, z: 0}}, 202 | data, 203 | {offset: 0x0, bytesPerRow, rowsPerImage: 0}, 204 | {width, height, depth} 205 | ); 206 | // Remove transfer item 207 | dataToTextureTransfers.splice(ii--, 1); 208 | } 209 | 210 | // Trigger callbacks if necessary 211 | for (const {callback} of dataToTextureTransfers) { 212 | if (callback instanceof Function) callback(); 213 | } 214 | } 215 | 216 | /** 217 | * Destroy this Object 218 | */ 219 | public destroy(): void { 220 | this._device = null; 221 | this._srcCopyBuffer = null; 222 | this._dataToBufferTransfers = null; 223 | this._dataToTextureTransfers = null; 224 | } 225 | 226 | } 227 | -------------------------------------------------------------------------------- /docs/modules/_mesh_staticmesh_box_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Mesh/StaticMesh/Box" | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Module "Mesh/StaticMesh/Box"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Classes

75 |
    76 |
  • Box
  • 77 |
78 |
79 |
80 |
81 |
82 |
83 | 165 |
166 |
167 |
168 |
169 |

Legend

170 |
171 |
    172 |
  • Object literal
  • 173 |
  • Variable
  • 174 |
  • Function
  • 175 |
  • Type alias
  • 176 |
177 |
    178 |
  • Enumeration
  • 179 |
180 |
    181 |
  • Interface
  • 182 |
183 |
    184 |
  • Class
  • 185 |
186 |
187 |
188 |
189 |
190 |

Generated using TypeDoc

191 |
192 |
193 | 194 | 195 | -------------------------------------------------------------------------------- /docs/modules/_mesh_staticmesh_plane_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Mesh/StaticMesh/Plane" | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Module "Mesh/StaticMesh/Plane"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Classes

75 | 78 |
79 |
80 |
81 |
82 |
83 | 165 |
166 |
167 |
168 |
169 |

Legend

170 |
171 |
    172 |
  • Object literal
  • 173 |
  • Variable
  • 174 |
  • Function
  • 175 |
  • Type alias
  • 176 |
177 |
    178 |
  • Enumeration
  • 179 |
180 |
    181 |
  • Interface
  • 182 |
183 |
    184 |
  • Class
  • 185 |
186 |
187 |
188 |
189 |
190 |

Generated using TypeDoc

191 |
192 |
193 | 194 | 195 | -------------------------------------------------------------------------------- /docs/modules/_camera_sphericalcamera_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Camera/SphericalCamera" | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Module "Camera/SphericalCamera"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Classes

75 | 78 |
79 |
80 |
81 |
82 |
83 | 165 |
166 |
167 |
168 |
169 |

Legend

170 |
171 |
    172 |
  • Object literal
  • 173 |
  • Variable
  • 174 |
  • Function
  • 175 |
  • Type alias
  • 176 |
177 |
    178 |
  • Enumeration
  • 179 |
180 |
    181 |
  • Interface
  • 182 |
183 |
    184 |
  • Class
  • 185 |
186 |
187 |
188 |
189 |
190 |

Generated using TypeDoc

191 |
192 |
193 | 194 | 195 | -------------------------------------------------------------------------------- /docs/modules/_renderer_index_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Renderer/index" | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Module "Renderer/index"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Classes

75 | 78 |
79 |
80 |

Interfaces

81 | 84 |
85 |
86 |
87 |
88 |
89 | 174 |
175 |
176 |
177 |
178 |

Legend

179 |
180 |
    181 |
  • Object literal
  • 182 |
  • Variable
  • 183 |
  • Function
  • 184 |
  • Type alias
  • 185 |
186 |
    187 |
  • Enumeration
  • 188 |
189 |
    190 |
  • Interface
  • 191 |
192 |
    193 |
  • Class
  • 194 |
195 |
196 |
197 |
198 |
199 |

Generated using TypeDoc

200 |
201 |
202 | 203 | 204 | -------------------------------------------------------------------------------- /docs/interfaces/_aabb_index_.iaabboptions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IAABBOptions | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 65 |

Interface IAABBOptions

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |

Hierarchy

74 |
    75 |
  • 76 | IAABBOptions 77 |
  • 78 |
79 |
80 |
81 | 176 |
177 |
178 |
179 |
180 |

Legend

181 |
182 |
    183 |
  • Object literal
  • 184 |
  • Variable
  • 185 |
  • Function
  • 186 |
  • Type alias
  • 187 |
188 |
    189 |
  • Enumeration
  • 190 |
191 |
    192 |
  • Interface
  • 193 |
194 |
    195 |
  • Class
  • 196 |
197 |
198 |
199 |
200 |
201 |

Generated using TypeDoc

202 |
203 |
204 | 205 | 206 | -------------------------------------------------------------------------------- /docs/modules/_mesh_staticmesh_index_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Mesh/StaticMesh/index" | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Module "Mesh/StaticMesh/index"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

References

75 | 79 |
80 |
81 |
82 |
83 |
84 |

References

85 |
86 | 87 |

Box

88 | Re-exports Box 89 |
90 |
91 | 92 |

Plane

93 | Re-exports Plane 94 |
95 |
96 |
97 | 182 |
183 |
184 |
185 |
186 |

Legend

187 |
188 |
    189 |
  • Object literal
  • 190 |
  • Variable
  • 191 |
  • Function
  • 192 |
  • Type alias
  • 193 |
194 |
    195 |
  • Enumeration
  • 196 |
197 |
    198 |
  • Interface
  • 199 |
200 |
    201 |
  • Class
  • 202 |
203 |
204 |
205 |
206 |
207 |

Generated using TypeDoc

208 |
209 |
210 | 211 | 212 | -------------------------------------------------------------------------------- /src/Camera/AbstractCamera.ts: -------------------------------------------------------------------------------- 1 | import {mat4, vec2, vec3, vec4} from "gl-matrix"; 2 | import {AABB} from "../AABB"; 3 | 4 | import {EventEmitter} from "../utils"; 5 | 6 | export interface ICameraOptions { 7 | width?: number; 8 | height?: number; 9 | } 10 | 11 | export const CAMERA_DEFAULT_OPTIONS: ICameraOptions = { 12 | width: 0, 13 | height: 0 14 | }; 15 | 16 | export abstract class AbstractCamera extends EventEmitter { 17 | 18 | private _width: number = 0; 19 | private _height: number = 0; 20 | 21 | private _translation: vec3 = null; 22 | 23 | private _viewMatrix: mat4 = null; 24 | private _projectionMatrix: mat4 = null; 25 | private _viewInverseMatrix: mat4 = null; 26 | private _projectionInverseMatrix: mat4 = null; 27 | private _viewProjectionMatrix: mat4 = null; 28 | private _viewProjectionInverseMatrix: mat4 = null; 29 | 30 | private _frustumPlanes: vec4[] = null; 31 | 32 | /** 33 | * @param options - Create options 34 | */ 35 | public constructor(options?: ICameraOptions) { 36 | super(); 37 | // Normalize options 38 | options = Object.assign({}, CAMERA_DEFAULT_OPTIONS, options); 39 | // Process options 40 | this._width = options.width; 41 | this._height = options.height; 42 | this._translation = vec3.create(); 43 | this._viewMatrix = mat4.create(); 44 | this._projectionMatrix = mat4.create(); 45 | this._viewInverseMatrix = mat4.create(); 46 | this._projectionInverseMatrix = mat4.create(); 47 | this._viewProjectionMatrix = mat4.create(); 48 | this._viewProjectionInverseMatrix = mat4.create(); 49 | this._frustumPlanes = [ 50 | vec4.create(), 51 | vec4.create(), 52 | vec4.create(), 53 | vec4.create(), 54 | vec4.create(), 55 | vec4.create() 56 | ]; 57 | } 58 | 59 | /** 60 | * The camera width 61 | */ 62 | public getWidth(): number { return this._width; } 63 | /** 64 | * Update the camera width 65 | * @param value - The new camera width 66 | */ 67 | public setWidth(value: number): void { this._width = value; } 68 | 69 | /** 70 | * The camera height 71 | */ 72 | public getHeight(): number { return this._height; } 73 | 74 | /** 75 | * Update the camera height 76 | * @param value - The new camera height 77 | */ 78 | public setHeight(value: number): void { this._height = value; } 79 | 80 | /** 81 | * The aspect size of the rendering surface 82 | */ 83 | public getAspect(): number { return this.getWidth() / this.getHeight(); } 84 | 85 | /** 86 | * Returns the translation of the camera 87 | */ 88 | public getTranslation(): vec3 { return this._translation; } 89 | 90 | /** 91 | * Returns the view matrix of the camera 92 | */ 93 | public getViewMatrix(): mat4 { return this._viewMatrix; } 94 | 95 | /** 96 | * Returns the projection matrix of the camera 97 | */ 98 | public getProjectionMatrix(): mat4 { return this._projectionMatrix; } 99 | 100 | /** 101 | * Returns the view inverse matrix of the camera 102 | */ 103 | public getViewInverseMatrix(): mat4 { return this._viewInverseMatrix; } 104 | 105 | /** 106 | * Returns the projection inverse matrix of the camera 107 | */ 108 | public getProjectionInverseMatrix(): mat4 { return this._projectionInverseMatrix; } 109 | 110 | /** 111 | * Returns the view-projection matrix of the camera 112 | */ 113 | public getViewProjectionMatrix(): mat4 { return this._viewProjectionMatrix; } 114 | 115 | /** 116 | * Returns the view-projection inverse matrix of the camera 117 | */ 118 | public getViewProjectionInverseMatrix(): mat4 { return this._viewProjectionInverseMatrix; } 119 | 120 | /** 121 | * Should be called every frame, must be overridden 122 | */ 123 | public abstract update(): void; 124 | 125 | /** 126 | * Updates the internal transforms and matrices 127 | * @param mCameraView - The view matrix of the camera model 128 | * @param mCameraProjection - The projection matrix of the camera model 129 | */ 130 | public updateTransforms(mCameraView: mat4, mCameraProjection: mat4): void { 131 | const mView = this.getViewMatrix(); 132 | const mProjection = this.getProjectionMatrix(); 133 | const mViewInverse = this.getViewInverseMatrix(); 134 | const mProjectionInverse = this.getProjectionInverseMatrix(); 135 | const mViewProjection = this.getViewProjectionMatrix(); 136 | const mViewProjectionInverse = this.getViewProjectionInverseMatrix(); 137 | // Update matrices 138 | mat4.copy(mView, mCameraView); 139 | mat4.copy(mProjection, mCameraProjection); 140 | mat4.multiply(mViewProjection, mCameraProjection, mCameraView); 141 | // Update inverse matrices 142 | mat4.invert(mViewInverse, mCameraView); 143 | mat4.invert(mProjectionInverse, mCameraProjection); 144 | mat4.invert(mViewProjectionInverse, mViewProjection); 145 | } 146 | 147 | /** 148 | * Destroy this Object 149 | */ 150 | public destroy(): void { 151 | this._viewMatrix = null; 152 | this._projectionMatrix = null; 153 | this._viewProjectionMatrix = null; 154 | this._viewProjectionInverseMatrix = null; 155 | this.emit("destroy"); 156 | } 157 | 158 | /** 159 | * Resize the camera 160 | * @param width - The destination width after resize 161 | * @param height - The destination height after resize 162 | */ 163 | public resize(width: number, height: number) { 164 | this._width = width; 165 | this._height = height; 166 | this.emit("resize", width, height); 167 | } 168 | 169 | /** 170 | * Converts the provided point from screen space into world space 171 | * @param pt - A point in screen space 172 | */ 173 | public screenToWorldPoint(pt: vec3): vec3 { 174 | const out = vec3.create(); 175 | const x = (2.0 * pt[0] / this.getWidth()) - 1.0; 176 | const y = (this.getHeight() - (2.0 * pt[1])) / this.getHeight(); 177 | const z = (2.0 * pt[2]) - 1.0; 178 | vec3.set(out, x, y, z); 179 | vec3.transformMat4(out, out, this.getViewProjectionInverseMatrix()); 180 | return out; 181 | } 182 | 183 | /** 184 | * Converts the provided point from world space into screen space 185 | * @param pt - A point in world space 186 | */ 187 | public worldToScreenPoint(pt: vec3): vec2 { 188 | const out = vec2.create(); 189 | const v = vec3.copy(vec3.create(), pt); 190 | vec3.transformMat4(v, v, this.getViewProjectionMatrix()); 191 | out[0] = Math.round(((v[0] + 1.0) * 0.5) * this.getWidth()); 192 | out[1] = Math.round(((1.0 - v[1]) * 0.5) * this.getHeight()); 193 | return out; 194 | } 195 | 196 | /** 197 | * Indicates if the camera's frustum intersects with the provided AABB 198 | * @param aabb - The AABB to check intersection with 199 | */ 200 | public intersectsAABB(aabb: AABB): boolean { 201 | const frustumPlanes = this._frustumPlanes; 202 | for (let ii = 0; ii < 6; ++ii) { 203 | const plane = frustumPlanes[ii]; 204 | const min = aabb.getMin(); 205 | const max = aabb.getMax(); 206 | const px = plane[0] > 0 ? max[0] : min[0]; 207 | const py = plane[1] > 0 ? max[1] : min[1]; 208 | const pz = plane[2] > 0 ? max[2] : min[2]; 209 | if ((plane[0] * px) + (plane[1] * py) + (plane[2] * pz) + (plane[3]) < 0) { 210 | return false; 211 | } 212 | } 213 | return true; 214 | } 215 | 216 | /** 217 | * Update the frustum of the camera 218 | */ 219 | public updateFrustum(): void { 220 | const frustumPlanes = this._frustumPlanes; 221 | const [ 222 | m0, m1, m2, m3, 223 | m4, m5, m6, m7, 224 | m8, m9, m10, m11, 225 | m12, m13, m14, m15 226 | ] = this.getViewProjectionMatrix(); 227 | // Right 228 | frustumPlanes[0][0] = (m3 - m0) / 6; 229 | frustumPlanes[0][1] = (m7 - m4) / 6; 230 | frustumPlanes[0][2] = (m11 - m8) / 6; 231 | frustumPlanes[0][3] = (m15 - m12) / 6; 232 | // Left 233 | frustumPlanes[1][0] = (m3 + m0) / 6; 234 | frustumPlanes[1][1] = (m7 + m4) / 6; 235 | frustumPlanes[1][2] = (m11 + m8) / 6; 236 | frustumPlanes[1][3] = (m15 + m12) / 6; 237 | // Bottom 238 | frustumPlanes[2][0] = (m3 + m1) / 6; 239 | frustumPlanes[2][1] = (m7 + m5) / 6; 240 | frustumPlanes[2][2] = (m11 + m9) / 6; 241 | frustumPlanes[2][3] = (m15 + m13) / 6; 242 | // Top 243 | frustumPlanes[3][0] = (m3 - m1) / 6; 244 | frustumPlanes[3][1] = (m7 - m5) / 6; 245 | frustumPlanes[3][2] = (m11 - m9) / 6; 246 | frustumPlanes[3][3] = (m15 - m13) / 6; 247 | // Top 248 | frustumPlanes[4][0] = (m3 - m2) / 6; 249 | frustumPlanes[4][1] = (m7 - m6) / 6; 250 | frustumPlanes[4][2] = (m11 - m1) / 6; 251 | frustumPlanes[4][3] = (m15 - m14) / 6; 252 | // Z-Far 253 | frustumPlanes[4][0] = (m3 - m2) / 6; 254 | frustumPlanes[4][1] = (m7 - m6) / 6; 255 | frustumPlanes[4][2] = (m11 - m10) / 6; 256 | frustumPlanes[4][3] = (m15 - m14) / 6; 257 | // Z-Near 258 | frustumPlanes[5][0] = (m3 + m2) / 6; 259 | frustumPlanes[5][1] = (m7 + m6) / 6; 260 | frustumPlanes[5][2] = (m11 + m10) / 6; 261 | frustumPlanes[5][3] = (m15 + m14) / 6; 262 | } 263 | 264 | /** 265 | * The distance between the provided point and the camera 266 | * @param pt - A point in world space 267 | */ 268 | public getDistanceToPoint(pt: vec3): number { 269 | return vec3.squaredDistance(this.getTranslation(), pt); 270 | } 271 | 272 | } 273 | -------------------------------------------------------------------------------- /docs/modules/_aabb_index_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "AABB/index" | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Module "AABB/index"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Classes

75 | 78 |
79 |
80 |

Interfaces

81 | 85 |
86 |
87 |

Variables

88 | 91 |
92 |
93 |
94 |
95 |
96 |

Variables

97 |
98 | 99 |

Const AABB_DEFAULT_OPTIONS

100 |
AABB_DEFAULT_OPTIONS: IAABBOptions
101 | 106 |
107 |
108 |
109 | 200 |
201 |
202 |
203 |
204 |

Legend

205 |
206 |
    207 |
  • Object literal
  • 208 |
  • Variable
  • 209 |
  • Function
  • 210 |
  • Type alias
  • 211 |
212 |
    213 |
  • Enumeration
  • 214 |
215 |
    216 |
  • Interface
  • 217 |
218 |
    219 |
  • Class
  • 220 |
221 |
222 |
223 |
224 |
225 |

Generated using TypeDoc

226 |
227 |
228 | 229 | 230 | -------------------------------------------------------------------------------- /docs/modules/_camera_index_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Camera/index" | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Module "Camera/index"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

References

75 | 81 |
82 |
83 |
84 |
85 |
86 |

References

87 |
88 | 89 |

AbstractCamera

90 | Re-exports AbstractCamera 91 |
92 |
93 | 94 |

CAMERA_DEFAULT_OPTIONS

95 | Re-exports CAMERA_DEFAULT_OPTIONS 96 |
97 |
98 | 99 |

ICameraOptions

100 | Re-exports ICameraOptions 101 |
102 |
103 | 104 |

SphericalCamera

105 | Re-exports SphericalCamera 106 |
107 |
108 |
109 | 200 |
201 |
202 |
203 |
204 |

Legend

205 |
206 |
    207 |
  • Object literal
  • 208 |
  • Variable
  • 209 |
  • Function
  • 210 |
  • Type alias
  • 211 |
212 |
    213 |
  • Enumeration
  • 214 |
215 |
    216 |
  • Interface
  • 217 |
218 |
    219 |
  • Class
  • 220 |
221 |
222 |
223 |
224 |
225 |

Generated using TypeDoc

226 |
227 |
228 | 229 | 230 | -------------------------------------------------------------------------------- /docs/interfaces/_ray_index_.iaabbintersection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IAABBIntersection | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 65 |

Interface IAABBIntersection

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |

Hierarchy

74 |
    75 |
  • 76 | IAABBIntersection 77 |
  • 78 |
79 |
80 |
81 |

Index

82 |
83 |
84 |
85 |

Properties

86 |
    87 |
  • t
  • 88 |
89 |
90 |
91 |
92 |
93 |
94 |

Properties

95 |
96 | 97 |

t

98 |
t: number
99 | 104 |
105 |
106 |
107 | 213 |
214 |
215 |
216 |
217 |

Legend

218 |
219 |
    220 |
  • Object literal
  • 221 |
  • Variable
  • 222 |
  • Function
  • 223 |
  • Type alias
  • 224 |
225 |
    226 |
  • Interface
  • 227 |
  • Property
  • 228 |
229 |
    230 |
  • Enumeration
  • 231 |
232 |
    233 |
  • Class
  • 234 |
235 |
236 |
237 |
238 |
239 |

Generated using TypeDoc

240 |
241 |
242 | 243 | 244 | -------------------------------------------------------------------------------- /docs/interfaces/_camera_abstractcamera_.abstractcamera.listenerfn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ListenerFn | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 68 |

Interface ListenerFn<Args>

69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |

Type parameters

77 |
    78 |
  • 79 |

    Args: any[] = any[]

    80 |
  • 81 |
82 |
83 |
84 |

Hierarchy

85 |
    86 |
  • 87 | ListenerFn 88 |
  • 89 |
90 |
91 |
92 |

Callable

93 |
    94 |
  • __call(...args: Args): void
  • 95 |
96 |
    97 |
  • 98 | 103 |

    Parameters

    104 |
      105 |
    • 106 |
      Rest ...args: Args
      107 |
    • 108 |
    109 |

    Returns void

    110 |
  • 111 |
112 |
113 |
114 | 211 |
212 |
213 |
214 |
215 |

Legend

216 |
217 |
    218 |
  • Object literal
  • 219 |
  • Variable
  • 220 |
  • Function
  • 221 |
  • Type alias
  • 222 |
223 |
    224 |
  • Enumeration
  • 225 |
226 |
    227 |
  • Interface
  • 228 |
229 |
    230 |
  • Class
  • 231 |
232 |
233 |
234 |
235 |
236 |

Generated using TypeDoc

237 |
238 |
239 | 240 | 241 | -------------------------------------------------------------------------------- /docs/interfaces/_renderer_abstractrenderer_.irendereroptions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IRendererOptions | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 65 |

Interface IRendererOptions

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |

Hierarchy

74 |
    75 |
  • 76 | IRendererOptions 77 |
  • 78 |
79 |
80 |
81 |

Index

82 |
83 |
84 |
85 |

Properties

86 | 89 |
90 |
91 |
92 |
93 |
94 |

Properties

95 |
96 | 97 |

Optional canvas

98 |
canvas: HTMLCanvasElement
99 | 104 |
105 |
106 |
107 | 210 |
211 |
212 |
213 |
214 |

Legend

215 |
216 |
    217 |
  • Object literal
  • 218 |
  • Variable
  • 219 |
  • Function
  • 220 |
  • Type alias
  • 221 |
222 |
    223 |
  • Interface
  • 224 |
  • Property
  • 225 |
226 |
    227 |
  • Enumeration
  • 228 |
229 |
    230 |
  • Class
  • 231 |
232 |
233 |
234 |
235 |
236 |

Generated using TypeDoc

237 |
238 |
239 | 240 | 241 | -------------------------------------------------------------------------------- /docs/interfaces/_renderer_index_.iuniformupdateentry.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IUniformUpdateEntry | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 65 |

Interface IUniformUpdateEntry

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |

Hierarchy

74 |
    75 |
  • 76 | IUniformUpdateEntry 77 |
  • 78 |
79 |
80 |
81 |

Index

82 |
83 |
84 |
85 |

Properties

86 | 90 |
91 |
92 |
93 |
94 |
95 |

Properties

96 |
97 | 98 |

data

99 |
data: any
100 | 105 |
106 |
107 | 108 |

id

109 |
id: number
110 | 115 |
116 |
117 |
118 | 215 |
216 |
217 |
218 |
219 |

Legend

220 |
221 |
    222 |
  • Object literal
  • 223 |
  • Variable
  • 224 |
  • Function
  • 225 |
  • Type alias
  • 226 |
227 |
    228 |
  • Interface
  • 229 |
  • Property
  • 230 |
231 |
    232 |
  • Enumeration
  • 233 |
234 |
    235 |
  • Class
  • 236 |
237 |
238 |
239 |
240 |
241 |

Generated using TypeDoc

242 |
243 |
244 | 245 | 246 | -------------------------------------------------------------------------------- /docs/globals.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 59 |

yue.js

60 |
61 |
62 |
63 |
64 |
65 | 101 | 180 |
181 |
182 |
183 |
184 |

Legend

185 |
186 |
    187 |
  • Object literal
  • 188 |
  • Variable
  • 189 |
  • Function
  • 190 |
  • Type alias
  • 191 |
192 |
    193 |
  • Enumeration
  • 194 |
195 |
    196 |
  • Interface
  • 197 |
198 |
    199 |
  • Class
  • 200 |
201 |
202 |
203 |
204 |
205 |

Generated using TypeDoc

206 |
207 |
208 | 209 | 210 | -------------------------------------------------------------------------------- /docs/interfaces/_aabb_index_.iaabbbounding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IAABBBounding | yue.js 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 65 |

Interface IAABBBounding

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |

Hierarchy

74 |
    75 |
  • 76 | IAABBBounding 77 |
  • 78 |
79 |
80 |
81 |

Index

82 |
83 |
84 |
85 |

Properties

86 | 90 |
91 |
92 |
93 |
94 |
95 |

Properties

96 |
97 | 98 |

max

99 |
max: vec3
100 | 105 |
106 |
107 | 108 |

min

109 |
min: vec3
110 | 115 |
116 |
117 |
118 | 221 |
222 |
223 |
224 |
225 |

Legend

226 |
227 |
    228 |
  • Object literal
  • 229 |
  • Variable
  • 230 |
  • Function
  • 231 |
  • Type alias
  • 232 |
233 |
    234 |
  • Interface
  • 235 |
  • Property
  • 236 |
237 |
    238 |
  • Enumeration
  • 239 |
240 |
    241 |
  • Class
  • 242 |
243 |
244 |
245 |
246 |
247 |

Generated using TypeDoc

248 |
249 |
250 | 251 | 252 | --------------------------------------------------------------------------------