diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 2370160..3ed52f1 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,7 +1,26 @@ - + + + + + + + + + + + + + + + + + + + + + + @@ -88,13 +112,20 @@ - - - - - + - + + + + + + + + + + + + \ No newline at end of file diff --git a/build/bundle.js b/build/bundle.js index e5e6c5e..a535078 100644 --- a/build/bundle.js +++ b/build/bundle.js @@ -1 +1 @@ -!function(e){var t=window.webpackHotUpdate;window.webpackHotUpdate=function(e,r){!function(e,t){if(!O[e]||!w[e])return;for(var r in w[e]=!1,t)Object.prototype.hasOwnProperty.call(t,r)&&(d[r]=t[r]);0==--m&&0===g&&S()}(e,r),t&&t(e,r)};var r,n=!0,o="f2c88108809235be8897",i={},a=[],u=[];function c(e){var t=j[e];if(!t)return R;var n=function(n){return t.hot.active?(j[n]?-1===j[n].parents.indexOf(e)&&j[n].parents.push(e):(a=[e],r=n),-1===t.children.indexOf(n)&&t.children.push(n)):(console.warn("[HMR] unexpected require("+n+") from disposed module "+e),a=[]),R(n)},o=function(e){return{configurable:!0,enumerable:!0,get:function(){return R[e]},set:function(t){R[e]=t}}};for(var i in R)Object.prototype.hasOwnProperty.call(R,i)&&"e"!==i&&"t"!==i&&Object.defineProperty(n,i,o(i));return n.e=function(e){return"ready"===f&&p("prepare"),g++,R.e(e).then(t,(function(e){throw t(),e}));function t(){g--,"prepare"===f&&(b[e]||P(e),0===g&&0===m&&S())}},n.t=function(e,t){return 1&t&&(e=n(e)),R.t(e,-2&t)},n}function l(t){var n={_acceptedDependencies:{},_declinedDependencies:{},_selfAccepted:!1,_selfDeclined:!1,_selfInvalidated:!1,_disposeHandlers:[],_main:r!==t,active:!0,accept:function(e,t){if(void 0===e)n._selfAccepted=!0;else if("function"==typeof e)n._selfAccepted=e;else if("object"==typeof e)for(var r=0;r=0&&n._disposeHandlers.splice(t,1)},invalidate:function(){switch(this._selfInvalidated=!0,f){case"idle":(d={})[t]=e[t],p("ready");break;case"ready":x(t);break;case"prepare":case"check":case"dispose":case"apply":(v=v||[]).push(t)}},check:k,apply:E,status:function(e){if(!e)return f;s.push(e)},addStatusHandler:function(e){s.push(e)},removeStatusHandler:function(e){var t=s.indexOf(e);t>=0&&s.splice(t,1)},data:i[t]};return r=void 0,n}var s=[],f="idle";function p(e){f=e;for(var t=0;t0;){var o=n.pop(),i=o.id,a=o.chain;if((s=j[i])&&(!s.hot._selfAccepted||s.hot._selfInvalidated)){if(s.hot._selfDeclined)return{type:"self-declined",chain:a,moduleId:i};if(s.hot._main)return{type:"unaccepted",chain:a,moduleId:i};for(var u=0;u ")),S.type){case"self-declined":n.onDeclined&&n.onDeclined(S),n.ignoreDeclined||(E=new Error("Aborted because of self decline: "+S.moduleId+M));break;case"declined":n.onDeclined&&n.onDeclined(S),n.ignoreDeclined||(E=new Error("Aborted because of declined dependency: "+S.moduleId+" in "+S.parentId+M));break;case"unaccepted":n.onUnaccepted&&n.onUnaccepted(S),n.ignoreUnaccepted||(E=new Error("Aborted because "+f+" is not accepted"+M));break;case"accepted":n.onAccepted&&n.onAccepted(S),x=!0;break;case"disposed":n.onDisposed&&n.onDisposed(S),T=!0;break;default:throw new Error("Unexception type "+S.type)}if(E)return p("abort"),Promise.reject(E);if(x)for(f in w[f]=d[f],m(b,S.outdatedModules),S.outdatedDependencies)Object.prototype.hasOwnProperty.call(S.outdatedDependencies,f)&&(g[f]||(g[f]=[]),m(g[f],S.outdatedDependencies[f]));T&&(m(b,[S.moduleId]),w[f]=k)}var I,D=[];for(c=0;c0;)if(f=F.pop(),s=j[f]){var B={},U=s.hot._disposeHandlers;for(l=0;l=0&&H.parents.splice(I,1))}}for(f in g)if(Object.prototype.hasOwnProperty.call(g,f)&&(s=j[f]))for(L=g[f],l=0;l=0&&s.children.splice(I,1);p("apply"),void 0!==y&&(o=y,y=void 0);for(f in d=void 0,w)Object.prototype.hasOwnProperty.call(w,f)&&(e[f]=w[f]);var V=null;for(f in g)if(Object.prototype.hasOwnProperty.call(g,f)&&(s=j[f])){L=g[f];var q=[];for(c=0;ce.length)&&(t=e.length);for(var r=0,n=new Array(t);re.length)&&(t=e.length);for(var r=0,n=new Array(t);r=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,a=!0,u=!1;return{s:function(){r=e[Symbol.iterator]()},n:function(){var e=r.next();return a=e.done,e},e:function(e){u=!0,i=e},f:function(){try{a||null==r.return||r.return()}finally{if(u)throw i}}}}function q(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r=this.tris.length&&(this.sinceLastTri+=e,this.sinceLastTri>100&&(this.tris.push(this.createRandomTriangle()),this.sinceLastTri=0));var t,r=V(this.tris);try{for(r.s();!(t=r.n()).done;){var n=t.value;this.renderTriangle(n)}}catch(e){r.e(e)}finally{r.f()}}},{key:"animate",value:function(e){var t=this,r=Date.now(),n=r-e;this.animateRandomTrisPopin(n),window.requestAnimationFrame((function(){return t.animate(r)}))}},{key:"run",value:function(){var e=this;this.initViewport(),this.useDefaultProgram(),window.requestAnimationFrame((function(){return e.animate(Date.now())}))}}])&&G(t.prototype,r),n&&G(t,n),e}();function W(e,t){var r=document.createElement("div");r.id=e.id,r.innerText=t,e.replaceWith(r)}window.onload=function(){var e=function(e,t){var r=document.querySelector("#root"),n=document.createElement("canvas");return n.id="main-stage",n.width=e,n.height=t,r.appendChild(n),n}(1024,720),t=e.getContext("webgl");if(t){var r=new z(t,e);r.onShutdown((function(t){return function(e,t){t===B.QUIT?W(e,"We're done here."):t===B.ERROR&&W(e,"Something unexpected happened and forced the program to shut down.")}(e,t)})),r.run()}else W(e,"Unable to initialize WebGL. Your browser or machine may not support it.")}}]); \ No newline at end of file +!function(e){var t=window.webpackHotUpdate;window.webpackHotUpdate=function(e,n){!function(e,t){if(!_[e]||!w[e])return;for(var n in w[e]=!1,t)Object.prototype.hasOwnProperty.call(t,n)&&(d[n]=t[n]);0==--g&&0===m&&S()}(e,n),t&&t(e,n)};var n,r=!0,o="96d6984be2e376a14bf8",i={},a=[],u=[];function c(e){var t=j[e];if(!t)return B;var r=function(r){return t.hot.active?(j[r]?-1===j[r].parents.indexOf(e)&&j[r].parents.push(e):(a=[e],n=r),-1===t.children.indexOf(r)&&t.children.push(r)):(console.warn("[HMR] unexpected require("+r+") from disposed module "+e),a=[]),B(r)},o=function(e){return{configurable:!0,enumerable:!0,get:function(){return B[e]},set:function(t){B[e]=t}}};for(var i in B)Object.prototype.hasOwnProperty.call(B,i)&&"e"!==i&&"t"!==i&&Object.defineProperty(r,i,o(i));return r.e=function(e){return"ready"===f&&h("prepare"),m++,B.e(e).then(t,(function(e){throw t(),e}));function t(){m--,"prepare"===f&&(b[e]||E(e),0===m&&0===g&&S())}},r.t=function(e,t){return 1&t&&(e=r(e)),B.t(e,-2&t)},r}function l(t){var r={_acceptedDependencies:{},_declinedDependencies:{},_selfAccepted:!1,_selfDeclined:!1,_selfInvalidated:!1,_disposeHandlers:[],_main:n!==t,active:!0,accept:function(e,t){if(void 0===e)r._selfAccepted=!0;else if("function"==typeof e)r._selfAccepted=e;else if("object"==typeof e)for(var n=0;n=0&&r._disposeHandlers.splice(t,1)},invalidate:function(){switch(this._selfInvalidated=!0,f){case"idle":(d={})[t]=e[t],h("ready");break;case"ready":A(t);break;case"prepare":case"check":case"dispose":case"apply":(v=v||[]).push(t)}},check:k,apply:P,status:function(e){if(!e)return f;s.push(e)},addStatusHandler:function(e){s.push(e)},removeStatusHandler:function(e){var t=s.indexOf(e);t>=0&&s.splice(t,1)},data:i[t]};return n=void 0,r}var s=[],f="idle";function h(e){f=e;for(var t=0;t0;){var o=r.pop(),i=o.id,a=o.chain;if((s=j[i])&&(!s.hot._selfAccepted||s.hot._selfInvalidated)){if(s.hot._selfDeclined)return{type:"self-declined",chain:a,moduleId:i};if(s.hot._main)return{type:"unaccepted",chain:a,moduleId:i};for(var u=0;u ")),S.type){case"self-declined":r.onDeclined&&r.onDeclined(S),r.ignoreDeclined||(P=new Error("Aborted because of self decline: "+S.moduleId+T));break;case"declined":r.onDeclined&&r.onDeclined(S),r.ignoreDeclined||(P=new Error("Aborted because of declined dependency: "+S.moduleId+" in "+S.parentId+T));break;case"unaccepted":r.onUnaccepted&&r.onUnaccepted(S),r.ignoreUnaccepted||(P=new Error("Aborted because "+f+" is not accepted"+T));break;case"accepted":r.onAccepted&&r.onAccepted(S),A=!0;break;case"disposed":r.onDisposed&&r.onDisposed(S),R=!0;break;default:throw new Error("Unexception type "+S.type)}if(P)return h("abort"),Promise.reject(P);if(A)for(f in w[f]=d[f],g(b,S.outdatedModules),S.outdatedDependencies)Object.prototype.hasOwnProperty.call(S.outdatedDependencies,f)&&(m[f]||(m[f]=[]),g(m[f],S.outdatedDependencies[f]));R&&(g(b,[S.moduleId]),w[f]=k)}var D,M=[];for(c=0;c0;)if(f=F.pop(),s=j[f]){var U={},L=s.hot._disposeHandlers;for(l=0;l=0&&H.parents.splice(D,1))}}for(f in m)if(Object.prototype.hasOwnProperty.call(m,f)&&(s=j[f]))for(C=m[f],l=0;l=0&&s.children.splice(D,1);h("apply"),void 0!==y&&(o=y,y=void 0);for(f in d=void 0,w)Object.prototype.hasOwnProperty.call(w,f)&&(e[f]=w[f]);var z=null;for(f in m)if(Object.prototype.hasOwnProperty.call(m,f)&&(s=j[f])){C=m[f];var G=[];for(c=0;c1e-5?this.scaled(1/e):c.Zeros()}},{key:"scaled",value:function(e){return new c(this.x()*e,this.y()*e,this.z()*e)}},{key:"length",value:function(){return this.lengthCacheDirty&&(this.len=Math.sqrt(this.dot(this)),this.lengthCacheDirty=!1),this.len}}])&&i(t.prototype,n),r&&i(t,r),c}(r);function p(e){return(p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function d(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function y(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,a=!0,u=!1;return{s:function(){n=e[Symbol.iterator]()},n:function(){var e=n.next();return a=e.done,e},e:function(e){u=!0,i=e},f:function(){try{a||null==n.return||n.return()}finally{if(u)throw i}}}}function he(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n any = () => {}; constructor(gl: WebGLRenderingContext, canvas: HTMLCanvasElement) { this.gl = gl; this.stage = canvas; this.currentProgram = this.createDefaultProgram(); - this.perspMat = perspMat(90, this.stage.width / this.stage.height, 1.0, 2000.0); this.setupGl(); } private setupGl(): void { this.gl.enable(this.gl.DEPTH_TEST); - this.gl.enable(this.gl.CULL_FACE); + //this.gl.enable(this.gl.CULL_FACE); + //this.gl.frontFace(this.gl.CCW); } onShutdown(callback: (exitCode: ExitCode) => any): void { @@ -45,6 +55,7 @@ class WebGlApp { } private shutdown(exitCode: ExitCode) { + this.running = false; this.onShutdownCallback(exitCode); } @@ -52,13 +63,14 @@ class WebGlApp { const program = this.createProgram( this.createShaderFromString(this.gl.VERTEX_SHADER, vs_src), this.createShaderFromString(this.gl.FRAGMENT_SHADER, fs_src)); - this.positionAttributeLocation = this.gl.getAttribLocation(program, "a_position"); + this.a_position = this.gl.getAttribLocation(program, "a_position"); + this.a_normal = this.gl.getAttribLocation(program, "a_normal"); this.u_colorLoc = this.gl.getUniformLocation(program, "u_color") as number; this.u_trsMatrixLoc = this.gl.getUniformLocation(program, "u_trsMatrix") as number; return program; } - private createProgram(vertexShader: WebGLShader, fragmentShader: WebGLShader) { + private createProgram(vertexShader: WebGLShader, fragmentShader: WebGLShader): WebGLProgram { const program: WebGLProgram = this.gl.createProgram() as WebGLProgram; this.gl.attachShader(program, vertexShader); this.gl.attachShader(program, fragmentShader); @@ -89,12 +101,27 @@ class WebGlApp { } } - private renderTriangle(triangle: Triangle2D) { - this.gl.bufferData(this.gl.ARRAY_BUFFER, triangle.pointBuffer(), this.gl.STATIC_DRAW); - this.gl.uniform4fv(this.u_colorLoc, triangle.colorVec()); + private renderObject(object: Object) { + this.gl.enableVertexAttribArray(this.a_position); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.pointBuffer!); + this.gl.bufferData(this.gl.ARRAY_BUFFER, object.pointBuffer(), this.gl.STATIC_DRAW); + this.gl.vertexAttribPointer( + this.a_position, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.enableVertexAttribArray(this.a_normal); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.normalBuffer!); + this.gl.bufferData(this.gl.ARRAY_BUFFER, object.normalBuffer(), this.gl.STATIC_DRAW); + this.gl.vertexAttribPointer( + this.a_normal, 3, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.elementBuffer!); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, object.elementBuffer(), this.gl.STATIC_DRAW); + + this.gl.uniform4fv(this.u_colorLoc, object.getColor().vec()); this.gl.uniformMatrix4fv(this.u_trsMatrixLoc, false, - triangle.getTrsMatrix().buffer()); - this.gl.drawArrays(this.gl.TRIANGLES, 0, 3); + this.useCamera(object.getTrsMatrix()).buffer()); + + this.gl.drawElements(this.gl.TRIANGLES, object.elementBuffer().length, this.gl.UNSIGNED_SHORT, 0); } private initViewport(): void { @@ -105,70 +132,53 @@ class WebGlApp { private useDefaultProgram(): void { this.gl.useProgram(this.currentProgram); - const positionBuffer = this.gl.createBuffer(); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer); - this.gl.enableVertexAttribArray(this.positionAttributeLocation); - this.gl.vertexAttribPointer( - this.positionAttributeLocation, 3, this.gl.FLOAT, false, 0, 0); + this.pointBuffer = this.gl.createBuffer()!; + this.elementBuffer = this.gl.createBuffer()!; + this.normalBuffer = this.gl.createBuffer()!; } - private createRandomTriangle(): Triangle2D { - return new Triangle2D( - this.rand2DClipspacePoint(), - this.rand2DClipspacePoint(), - this.rand2DClipspacePoint(), - this.randTrsMat(), - this.randBasicColor() - ) + private useCamera(inputTrs: TrsMatrix): MatrixFour { + const projectionMatrix = MatrixFour.Perspective( + Math.PI, + this.stage.clientWidth / this.stage.clientHeight, + 1, + 2000); + const viewProjectionMatrix = projectionMatrix.times(this.camera.viewMat()); + return viewProjectionMatrix.times(inputTrs); } - private randTrsMat(): TrsMatrix { - return new TrsMatrix() - .translateBy(this.randClipspaceInt() / 2, this.randClipspaceInt() / 2, this.randClipspaceInt() / 2) - .rotateBy(this.randAngle(), this.randAngle(), this.randAngle()) - } - - private rand2DClipspacePoint(): Point2D { - return new Point2D(this.randClipspaceInt() / 2, this.randClipspaceInt() / 2); - } - - private randBasicColor(): BasicColor { - return new BasicColor(Math.random(), Math.random(), Math.random()); - } - - private randAngle(): number { - return Math.random() * Math.PI / 10; - } - - private randClipspaceInt(): number { - return Math.random() * 2 - 1; - } - - private animateRandomTrisPopin(delta: number): void { - if (this.maxTris >= this.tris.length) { - this.sinceLastTri += delta; - if (this.sinceLastTri > 100) { - this.tris.push(this.createRandomTriangle()); - this.sinceLastTri = 0; - } + private mainLoop(delta: number): void { + for (const object of this.objects) { + object.update(delta); } - for (const tri of this.tris) { - this.renderTriangle(tri); + for (const object of this.objects) { + this.renderObject(object); } } - private animate(then: number): void { + private frame(then: number): void { const now = Date.now(); const delta = now - then; - this.animateRandomTrisPopin(delta); - window.requestAnimationFrame(() => this.animate(now)); + this.mainLoop(delta); + if (this.running) { + window.requestAnimationFrame(() => this.frame(now)); + } + } + + private setupScene(): void { + const cone = new Cone(4, 100); + cone.setColor(new BasicColor(1.0, 0, 0, 1.0)); + cone.scaleBy(100); + this.objects.push(cone); + this.camera.translateBy(100, 100, 100); + console.log(this.useCamera(cone.getTrsMatrix())); } run() { this.initViewport(); this.useDefaultProgram(); - window.requestAnimationFrame(() => this.animate(Date.now())); - //this.shutdown(ExitCode.QUIT); + this.setupScene(); + window.requestAnimationFrame(() => this.frame(Date.now())); } } diff --git a/src/BasicColor.ts b/src/BasicColor.ts index fa6d199..681f579 100644 --- a/src/BasicColor.ts +++ b/src/BasicColor.ts @@ -1,9 +1,14 @@ +import {VectorSizeError} from "./Errors"; +import {Color, Vector} from "./types"; + class BasicColor implements Vector, Color { - private rep: Float32Array; - constructor(r: number, g: number, b: number, a?: number) { - this.rep = new Float32Array([r, g, b, a ?? 1.0]); + private readonly rep: Float32Array; + constructor(); + constructor(r: number, g: number, b: number, a?: number); + constructor(...args: any[]) { + this.rep = new Float32Array([args[0] ?? 0.5, args[1] ?? 0.5, args[2] ?? 0.5, args[3] ?? 1.0]); } - static fromVec(vec: Float32Array): Color { + static fromVec(vec: ArrayLike): Color { if (vec.length === 4) { return new BasicColor(vec[0], vec[1], vec[2], vec[3]); } diff --git a/src/Camera.ts b/src/Camera.ts new file mode 100644 index 0000000..941c53f --- /dev/null +++ b/src/Camera.ts @@ -0,0 +1,66 @@ +import TrsMatrix from "./TrsMatrix"; +import Point3D from "./Point3D"; +import {Transformable} from "./types"; +import MatrixFour from "./MatrixFour"; + +class Camera implements Transformable { + private trs: TrsMatrix; + constructor(); + constructor(position: Point3D, lookAt: Point3D, up: Point3D); + constructor(radius: number, rotX: number, rotY: number, rotZ: number); + constructor(...args: any[]) { + this.trs = new TrsMatrix(); + if (args[0] !== undefined) { + if (args[0] instanceof Point3D) { + this.trs.setPosition(args[0]); + this.lookAt(args[1], args[2]); + } + else { + this.rotateBy(args[1], args[2], args[3]); + this.translateBy(0, 0, args[0]); + } + } + } + + translateBy(tX: number, tY: number, tZ: number): Camera { + this.trs.translateBy(tX, tY, tZ); + return this; + } + + rotateBy(degX: number, degY: number, degZ: number): Camera { + this.trs.rotateBy(degX, degY, degZ); + return this; + } + + scaleBy(sX: number, sY: number, sZ: number): Camera { + this.trs.scaleBy(sX, sY, sZ); + return this; + } + + getTrsMatrix(): TrsMatrix { + return this.trs; + } + + position(): Point3D { + return this.trs.position(); + }; + + lookAt(other: Point3D, up: Point3D) { + const selfPosition = this.position(); + const zAxis = selfPosition.minus(other).normalised(); + const xAxis = up.cross(zAxis).normalised(); + const yAxis = zAxis.cross(xAxis).normalised(); + this.trs.setSlice([ + xAxis.x(), xAxis.y(), xAxis.z(), 0, + yAxis.x(), yAxis.y(), yAxis.z(), 0, + zAxis.x(), zAxis.y(), zAxis.z(), 0, + selfPosition.x(), selfPosition.y(), selfPosition.z(), 1, + ], 0); + } + + viewMat(): MatrixFour { + return this.trs.inverse(); + } +} + +export default Camera; \ No newline at end of file diff --git a/src/Cone.ts b/src/Cone.ts new file mode 100644 index 0000000..27740f3 --- /dev/null +++ b/src/Cone.ts @@ -0,0 +1,106 @@ +import TrsMatrix from "./TrsMatrix"; +import BasicColor from "./BasicColor"; +import TransformableGeometry from "./TransformableGeometry"; +import {triNormal} from "./utils"; +import Point3D from "./Point3D"; +import {Color, Colored, GeometryParams, Transformable} from "./types"; +import {Updatable} from "./App"; + +class Cone extends TransformableGeometry implements Transformable, Colored, Updatable { + private color: Color; + private height?: number; + private basePoints?: number; + private static DEFAULT_BASE_POINTS: number = 10; + private static DEFAULT_HEIGHT: number = 1; + constructor(geometry: GeometryParams, trsMatrix?: TrsMatrix, color?: Color); + constructor(height?: number, basePoints?: number, trsMatrix?: TrsMatrix, color?: Color | ArrayLike); + constructor(...args: any[]) { + let color; + if (typeof args[0] !== "number") { + super(args[0], args[1]); + color = args[2]; + } + else { + super(Cone.generateGeometry(args[0], args[1]), args[2]); + this.height = args[0] ?? Cone.DEFAULT_HEIGHT; + this.basePoints = args[1] ?? Cone.DEFAULT_BASE_POINTS; + color = args[4] ?? new BasicColor(); + } + if (color.hasOwnProperty("length")) { + this.color = BasicColor.fromVec(color as ArrayLike); + } + else { + this.color = color as Color; + } + } + + private static generateGeometry(height: number, basePoints: number): GeometryParams { + basePoints = Math.abs(Math.ceil(basePoints)); + const basePointsAngle = 2 * Math.PI / basePoints; + const pointBuffer = [ + 0.0, 0.0, 0.0, + 0.0, 0.0, height, + 0.0, 1.0, 0.0 + ]; + const normalBuffer = []; + const elementBuffer = []; + for (let i = 1; i < basePoints; i++) { + pointBuffer.push( + Math.sin(basePointsAngle * i), + Math.cos(basePointsAngle * i), + 0.0 + ); + elementBuffer.push(0, 2 + i, 1 + i); + normalBuffer.push( + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + ); + elementBuffer.push(1, 1 + i, 2 + i); + normalBuffer.push( + ...triNormal( + new Point3D(pointBuffer.slice(1, 4)), + new Point3D(pointBuffer.slice(1 + i, 4 + i)), + new Point3D(pointBuffer.slice(2 + i, 5 + i)), + ).vec() + ); + } + elementBuffer.push(0, 2, 1 + basePoints); + elementBuffer.push(1, 1 + basePoints, 2); + return { + pointBuffer: new Float32Array(pointBuffer), + normalBuffer: new Float32Array(normalBuffer), + elementBuffer: new Uint16Array(elementBuffer) + }; + } + + update(delta: number): void { + const secs = delta / 1000.0; + this.rotateBy(0, 0.05 * secs, 0); + } + + setHeight(height: number) { + this.changeHeight(height - (this.height ?? 0)); + } + + changeHeight(deltaHeight: number) { + this.height = this.height ?? Cone.DEFAULT_HEIGHT + deltaHeight; + this.geometry = Cone.generateGeometry(this.height, this.basePoints ?? Cone.DEFAULT_BASE_POINTS); + } + + setBasePoints(basePoints: number) { + this.height = basePoints; + this.geometry = Cone.generateGeometry(this.height, this.basePoints ?? Cone.DEFAULT_BASE_POINTS); + } + + setColor(color: Color) { + this.color = color; + } + + getColor(): Color { + return this.color; + } +} + + +export default Cone; \ No newline at end of file diff --git a/src/Matrix4.ts b/src/Matrix4.ts deleted file mode 100644 index 236e8d9..0000000 --- a/src/Matrix4.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {identityMatrix} from "./utils"; - -class Matrix4 { - protected readonly matrix: Float32Array; - constructor(matrix: Float32Array | ArrayLike); - constructor(...args: any[]) { - if (args[0].length === 16) { - if (!(args[0] instanceof Float32Array)) { - args[0] = new Float32Array(args[0]); - } - this.matrix = args[0]; - } - else { - throw new VectorSizeError(16, args[0].length); - } - } - times(other: Matrix4) { - const result = new Matrix4(Array(16)); - const a = this.matrix; - const b = other.matrix; - const c = result.matrix; - c[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; - c[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; - c[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; - c[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; - c[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; - c[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; - c[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; - c[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; - c[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; - c[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; - c[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; - c[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; - c[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; - c[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; - c[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; - c[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; - return result; - } - buffer(): Float32Array { - return this.matrix.slice(); - } - set(m: number, n: number, val: number): void { - this.matrix[m * 4 + n] = val; - } - setRel(m: number, n: number, val: number): void { - this.matrix[m * 4 + n] += val; - } - static Identity(): Matrix4 { - return new Matrix4(identityMatrix(4)); - } -} - -export default Matrix4; \ No newline at end of file diff --git a/src/MatrixFour.ts b/src/MatrixFour.ts new file mode 100644 index 0000000..4607d9f --- /dev/null +++ b/src/MatrixFour.ts @@ -0,0 +1,155 @@ +import {identityMatrix} from "./utils"; +import {VectorSizeError} from "./Errors"; + +class MatrixFour { + protected matrix: Float32Array; + constructor(); + constructor(matrix: Float32Array | ArrayLike); + constructor(...args: any[]) { + if (args[0]) { + if (args[0].length === 16) { + if (!(args[0] instanceof Float32Array)) { + args[0] = new Float32Array(args[0]); + } + this.matrix = args[0]; + } + else { + throw new VectorSizeError(16, args[0].length); + } + } + else { + this.matrix = identityMatrix(4); + } + } + + times(other: MatrixFour) { + const result = new MatrixFour(Array(16)); + const a = this.matrix; + const b = other.matrix; + const c = result.matrix; + c[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + c[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + c[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + c[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + c[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + c[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + c[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + c[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + c[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + c[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + c[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + c[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + c[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + c[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + c[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + c[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + return result; + } + + buffer(start?: number, stop?: number): Float32Array { + return this.matrix.slice(start, stop); + } + + set(m: number, n: number, val: number): void { + this.matrix[m * 4 + n] = val; + } + + setSlice(vals: number[], offset: number): void { + this.matrix.set(vals, offset); + } + + setRel(m: number, n: number, val: number): void { + this.matrix[m * 4 + n] += val; + } + + inverse(): MatrixFour { + const m = this.matrix; + const m00 = m[0]; + const m01 = m[1]; + const m02 = m[2]; + const m03 = m[3]; + const m10 = m[4]; + const m11 = m[5]; + const m12 = m[6]; + const m13 = m[7]; + const m20 = m[8]; + const m21 = m[9]; + const m22 = m[10]; + const m23 = m[11]; + const m30 = m[12]; + const m31 = m[13]; + const m32 = m[14]; + const m33 = m[15]; + const tmp_0 = m22 * m33; + const tmp_1 = m32 * m23; + const tmp_2 = m12 * m33; + const tmp_3 = m32 * m13; + const tmp_4 = m12 * m23; + const tmp_5 = m22 * m13; + const tmp_6 = m02 * m33; + const tmp_7 = m32 * m03; + const tmp_8 = m02 * m23; + const tmp_9 = m22 * m03; + const tmp_10 = m02 * m13; + const tmp_11 = m12 * m03; + const tmp_12 = m20 * m31; + const tmp_13 = m30 * m21; + const tmp_14 = m10 * m31; + const tmp_15 = m30 * m11; + const tmp_16 = m10 * m21; + const tmp_17 = m20 * m11; + const tmp_18 = m00 * m31; + const tmp_19 = m30 * m01; + const tmp_20 = m00 * m21; + const tmp_21 = m20 * m01; + const tmp_22 = m00 * m11; + const tmp_23 = m10 * m01; + + const t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31); + const t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31); + const t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31); + const t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21); + + const d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3); + + const result = new MatrixFour([ + d * t0, + d * t1, + d * t2, + d * t3, + + d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)), + d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)), + d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)), + d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)), + + d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)), + d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)), + d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)), + d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)), + + d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)), + d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)), + d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)), + d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)) + ]); + return result; + } + + static Perspective(fov: number, aspect: number, near: number, far: number): MatrixFour { + const f = Math.tan(Math.PI / 2 - fov / 2); + const rangeInv = 1.0 / (near - far); + return new MatrixFour(new Float32Array([ + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (near + far) * rangeInv, -1, + 0, 0, near * far * rangeInv * 2, 0 + ])); + } + + static Identity(): MatrixFour { + return new MatrixFour(identityMatrix(4)); + } +} + +export default MatrixFour; \ No newline at end of file diff --git a/src/Point2D.ts b/src/Point2D.ts index b80bb8e..f4743bd 100644 --- a/src/Point2D.ts +++ b/src/Point2D.ts @@ -1,4 +1,5 @@ import PointVec from "./PointVec"; +import {VectorSizeError} from "./Errors"; class Point2D extends PointVec { constructor(vec: Float32Array | ArrayLike); diff --git a/src/Point3D.ts b/src/Point3D.ts index f699da1..6c78240 100644 --- a/src/Point3D.ts +++ b/src/Point3D.ts @@ -1,32 +1,102 @@ import PointVec from "./PointVec"; +import {VectorSizeError} from "./Errors"; class Point3D extends PointVec { + private len: number; + private lengthCacheDirty: boolean; constructor(vec: Float32Array | ArrayLike); constructor(x: number, y: number, z: number); - constructor(first: any, second?: any, third?: any) { - super(first, second, third); + constructor(...args: any[]) { + if (typeof args[0] === "number") { + super(args[0], args[1], args[2]); + } + else { + if (args[0].length !== 3) { + throw new VectorSizeError(3, args[0].length); + } + else { + super(args[0][0], args[0][1], args[0][2]); + } + } + this.len = 0; + this.lengthCacheDirty = true; } - xOrd(): number { + x(): number { return this.rep[0]; } - yOrd(): number { + y(): number { return this.rep[1]; } - zOrd(): number { + z(): number { return this.rep[2]; } setX(x: number): void { this.rep[0] = x; + this.lengthCacheDirty = false; } setY(y: number): void { this.rep[1] = y; + this.lengthCacheDirty = false; } setZ(z: number): void { this.rep[2] = z; + this.lengthCacheDirty = false; + } + set(point: Point3D): void { + this.setX(point.x()); + this.setY(point.y()); + this.setZ(point.z()); } vec(): Float32Array { return this.rep.slice(); } + cross(other: Point3D): Point3D { + return new Point3D( + this.y() * other.z() - this.z() * other.y(), + this.z() * other.x() - this.x() * other.z(), + this.x() * other.y() - this.y() * other.x(), + ); + } + dot(other: Point3D): number { + return this.x() * other.x() + this.y() * other.y() + this.z() * other.z(); + } + minus(other: Point3D): Point3D { + return new Point3D( + this.x() - other.x(), + this.y() - other.y(), + this.z() - other.z(), + ) + } + plus(other: Point3D): Point3D { + return new Point3D( + this.x() + other.x(), + this.y() + other.y(), + this.z() + other.z(), + ); + } + normalised(): Point3D { + const len = this.length(); + if (len > 0.00001) { + return this.scaled(1 / len); + } + else { + return Point3D.Zeros(); + } + } + scaled(scalar: number) { + return new Point3D( + this.x() * scalar, + this.y() * scalar, + this.z() * scalar, + ); + } + length(): number { + if (this.lengthCacheDirty) { + this.len = Math.sqrt(this.dot(this)); + this.lengthCacheDirty = false; + } + return this.len; + } static Zeros(): Point3D { return new Point3D(0.0, 0.0, 0.0); } diff --git a/src/PointVec.ts b/src/PointVec.ts index 8668df0..2f258b7 100644 --- a/src/PointVec.ts +++ b/src/PointVec.ts @@ -1,22 +1,12 @@ +import {Vector} from "./types"; + abstract class PointVec implements Vector { protected rep: Float32Array; - protected constructor(vec: Float32Array | ArrayLike); - protected constructor(x: number, y: number, z: number); - protected constructor(first: number | Float32Array | ArrayLike, second?: number, third?: number) { - if (typeof first === "number") { - if (typeof second !== "number" || typeof third !== "number"){ - throw new TypeError("Incorrect constructor call for Point3D"); - } - else { - this.rep = new Float32Array(3); - this.rep[0] = first; - this.rep[1] = second; - this.rep[2] = third; - } - } - else { - this.rep = new Float32Array(first[2]); - } + protected constructor(first: number, second: number, third: number) { + this.rep = new Float32Array(3); + this.rep[0] = first; + this.rep[1] = second; + this.rep[2] = third; } abstract vec(): Float32Array; } diff --git a/src/TransformableGeometry.ts b/src/TransformableGeometry.ts new file mode 100644 index 0000000..fbc01f0 --- /dev/null +++ b/src/TransformableGeometry.ts @@ -0,0 +1,76 @@ +import TrsMatrix from "./TrsMatrix"; +import {VectorSizeError} from "./Errors"; +import {Geometry, GeometryParams, Transformable} from "./types"; +import Point3D from "./Point3D"; + +class TransformableGeometry implements Transformable, Geometry { + protected geometry: GeometryParams; + protected readonly trsMatrix: TrsMatrix; + constructor(geometry: { + pointBuffer: Float32Array, + normalBuffer: Float32Array, + elementBuffer?: ArrayLike, + }, trsMatrix?: TrsMatrix) { + let elBuffer; + if (geometry.elementBuffer) { + elBuffer = new Uint16Array(geometry.elementBuffer); + } + else { + if (geometry.pointBuffer.length % 3 !== 0) { + throw new VectorSizeError("Multiple of 3 (no vertexOrder supplied)", + geometry.pointBuffer.length); + } + else { + elBuffer = new Uint16Array(Array(geometry.pointBuffer.length / 3).keys()); + } + } + this.geometry = { + pointBuffer: geometry.pointBuffer, + normalBuffer: geometry.normalBuffer, + elementBuffer: elBuffer, + }; + this.trsMatrix = trsMatrix ?? new TrsMatrix(); + } + + getTrsMatrix(): TrsMatrix { + return this.trsMatrix; + } + + pointBuffer(): Float32Array { + return this.geometry.pointBuffer; + } + + normalBuffer(): Float32Array { + return this.geometry.normalBuffer; + } + + elementBuffer(): Uint16Array { + return this.geometry.elementBuffer; + } + + position(): Point3D { + return this.trsMatrix.position(); + } + + translateBy(tX: number, tY: number, tZ: number): this { + this.trsMatrix.translateBy(tX, tY, tZ); + return this; + } + + rotateBy(degX: number, degY: number, degZ: number): this { + this.trsMatrix.rotateBy(degX, degY, degZ); + return this; + } + + scaleBy(xOrAll: number, sY?: number, sZ?: number): this { + if (sY && sZ) { + this.trsMatrix.scaleBy(xOrAll, sY, sZ); + } + else { + this.trsMatrix.scaleBy(xOrAll); + } + return this; + } +} + +export default TransformableGeometry; \ No newline at end of file diff --git a/src/Triangle2D.ts b/src/Triangle2D.ts index fff3494..ab20087 100644 --- a/src/Triangle2D.ts +++ b/src/Triangle2D.ts @@ -1,39 +1,56 @@ import TrsMatrix from "./TrsMatrix"; import Point2D from "./Point2D"; +import BasicColor from "./BasicColor"; +import {VectorSizeError} from "./Errors"; +import TransformableGeometry from "./TransformableGeometry"; +import {triNormal} from "./utils"; +import Point3D from "./Point3D"; +import {Color, Colored, Transformable} from "./types"; -class Triangle2D implements Transformable{ - private geometryRep: Float32Array; +class Triangle2D extends TransformableGeometry implements Transformable, Colored { private color: Color; - private readonly trsMatrix: TrsMatrix; - constructor(vec: Float32Array, trsMatrix?: TrsMatrix, color?: Color); - constructor(p1: Point2D, p2: Point2D, p3: Point2D, trsMatrix?: TrsMatrix, color?: Color); + constructor(pointBuffer: Float32Array, trsMatrix?: TrsMatrix, color?: Color); + constructor(p1: Point2D, p2: Point2D, p3: Point2D, trsMatrix?: TrsMatrix, color?: Color | ArrayLike); constructor(...args: any[]) { + let trsMatrix: TrsMatrix, pointBuff: Float32Array, color: Color | ArrayLike; if (args[0] instanceof Float32Array) { if (args[0].length !== 6 && args[0].length !== 8) { throw new VectorSizeError("6 or 9", args[0].length) } else if (args[0].length === 6) { - this.geometryRep = new Float32Array([ + pointBuff = new Float32Array([ ...args[0].slice(0, 2), 0.0, ...args[0].slice(2, 4), 0.0, ...args[0].slice(4, 6), 0.0 ]); } else { - this.geometryRep = args[0]; + pointBuff = args[0]; } - this.trsMatrix = args[1] ?? new TrsMatrix(); - this.color = args[2] ?? new BasicColor(0, 0, 0, 1.0); + trsMatrix = args[1] ?? new TrsMatrix(); + color = args[2] ?? new BasicColor(); } else { - this.geometryRep = new Float32Array([...args[0].vec(), ...args[1].vec(), ...args[2].vec()]); - this.trsMatrix = args[3] ?? new TrsMatrix(); - this.color = args[4] ?? new BasicColor(0, 0, 0, 1.0); + pointBuff = new Float32Array([...args[0].vec(), ...args[1].vec(), ...args[2].vec()]); + trsMatrix = args[3] ?? new TrsMatrix(); + color = args[4] ?? new BasicColor(); + } + super({ + pointBuffer: pointBuff, + normalBuffer: new Float32Array( + triNormal( + new Point3D(pointBuff.slice(0, 3)), + new Point3D(pointBuff.slice(3, 6)), + new Point3D(pointBuff.slice(6, 9)) + ).vec() + ), + }, trsMatrix); + if (color.hasOwnProperty("length")) { + this.color = BasicColor.fromVec(color as ArrayLike); + } + else { + this.color = color as Color; } - } - - colorVec(): Float32Array { - return this.color.vec(); } setColor(color: Color) { @@ -44,18 +61,14 @@ class Triangle2D implements Transformable{ return this.color; } - getTrsMatrix(): TrsMatrix { - return this.trsMatrix; - } - private mapPointTo(point: Point2D | ArrayLike, x: number) { if (point instanceof Point2D) { - this.geometryRep[x] = point.xOrd(); - this.geometryRep[x + 1] = point.yOrd(); + this.geometry.pointBuffer[x] = point.xOrd(); + this.geometry.pointBuffer[x + 1] = point.yOrd(); } else { - this.geometryRep[x] = point[0]; - this.geometryRep[x + 1] = point[1]; + this.geometry.pointBuffer[x] = point[0]; + this.geometry.pointBuffer[x + 1] = point[1]; } } @@ -72,34 +85,6 @@ class Triangle2D implements Transformable{ this.mapPointTo(p3, 6); } } - - updatePointsFromVec(vec: Float32Array) { - if (vec.length === 6) { - this.geometryRep = vec; - } - else { - throw new VectorSizeError(6, vec.length); - } - } - - pointBuffer(): Float32Array { - return this.geometryRep; - } - - translateBy(tX: number, tY: number, tZ: number): Triangle2D { - this.trsMatrix.translateBy(tX, tY, tZ); - return this; - } - - rotateBy(degX: number, degY: number, degZ: number): Triangle2D { - this.trsMatrix.rotateBy(degX, degY, degZ); - return this; - } - - scaleBy(sX: number, sY: number, sZ: number): Triangle2D { - this.trsMatrix.scaleBy(sX, sY, sZ); - return this; - } } export default Triangle2D; \ No newline at end of file diff --git a/src/TrsMatrix.ts b/src/TrsMatrix.ts index a89a0f5..1f71115 100644 --- a/src/TrsMatrix.ts +++ b/src/TrsMatrix.ts @@ -1,34 +1,44 @@ -import Matrix4 from "./Matrix4"; +import MatrixFour from "./MatrixFour"; +import Point3D from "./Point3D"; -class TrsMatrix implements Transformable { - protected trs: Matrix4 = Matrix4.Identity(); +class TrsMatrix extends MatrixFour { constructor(); - constructor(translation: Matrix4, rotX: Matrix4, rotY: Matrix4, rotZ: Matrix4, scale: Matrix4); + constructor(translation: MatrixFour, rotX: MatrixFour, rotY: MatrixFour, rotZ: MatrixFour, scale: MatrixFour); constructor(mat: TrsMatrix); constructor(...args: any[]) { + super(); if (args[0]) { if (args[0] instanceof TrsMatrix) { - this.trs = new Matrix4(args[0].buffer()); + this.matrix = args[0].buffer(); } else { - this.trs = args[0].times(args[1]).times(args[2]).times(args[3]).times(args[4]) + this.matrix = args[0].times(args[1]).times(args[2]).times(args[3]).times(args[4]) } } } - toMatrix4(): Matrix4 { - return this.trs; + + position(): Point3D { + return new Point3D(this.buffer(12, 15)); } - buffer(): Float32Array { - return this.trs.buffer(); + + setPosition(xOrPoint: number | Point3D, y?: number, z?: number): void { + if (xOrPoint instanceof Point3D) { + this.setSlice([xOrPoint.x(), xOrPoint.y(), xOrPoint.z()], 12); + } + else { + this.setSlice([xOrPoint, y ?? 0, z ?? 0], 12); + } } + translateBy(tX: number, tY: number, tZ: number): TrsMatrix { - this.trs = this.trs.times(new Matrix4([ + this.matrix = this.times(new MatrixFour([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tX, tY, tZ, 1, - ])); + ])).buffer(); return this; } + rotateBy(degX: number, degY: number, degZ: number): TrsMatrix { const sx = Math.sin(degX); const cx = Math.cos(degX); @@ -36,34 +46,39 @@ class TrsMatrix implements Transformable { const cy = Math.cos(degY); const sz = Math.sin(degZ); const cz = Math.cos(degZ); - this.trs = this.trs - .times(new Matrix4([ + this.matrix = this + .times(new MatrixFour([ 1, 0, 0, 0, 0, cx, sx, 0, 0, -sx, cx, 0, 0, 0, 0, 1, ])) - .times(new Matrix4([ + .times(new MatrixFour([ cy, 0, -sy, 0, 0, 1, 0, 0, sy, 0, cy, 0, 0, 0, 0, 1, ])) - .times(new Matrix4([ + .times(new MatrixFour([ cz, sz, 0, 0, -sz, cz, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, - ])); + ])).buffer(); return this; } - scaleBy(sX: number, sY: number, sZ: number): TrsMatrix { - this.trs = this.trs.times(new Matrix4([ - sX, 0, 0, 0, - 0, sY, 0, 0, - 0, 0, sZ, 0, - 0, 0, 0, 1, - ])); + + scaleBy(xOrAll: number, sY?: number, sZ?: number): TrsMatrix { + if (sY === undefined || sZ === undefined) { + sY = xOrAll; + sZ = xOrAll; + } + this.matrix = this.times(new MatrixFour([ + xOrAll, 0, 0, 0, + 0, sY, 0, 0, + 0, 0, sZ, 0, + 0, 0, 0, 1, + ])).buffer(); return this; } } diff --git a/src/fragmentShader.glsl b/src/fragmentShader.glsl index f79676f..93e3b1b 100644 --- a/src/fragmentShader.glsl +++ b/src/fragmentShader.glsl @@ -1,9 +1,12 @@ precision mediump float; uniform vec4 u_color; - varying float v_depth; +varying vec3 v_light; +varying vec3 v_normal; void main() { - gl_FragColor = vec4(exp(v_depth), exp(v_depth), exp(v_depth), 1.0); + vec3 normal = normalize(v_normal); + float light = dot(normal, v_light); + gl_FragColor = u_color * (1.0 + light - light); } \ No newline at end of file diff --git a/src/types.d.ts b/src/types.d.ts index c61691f..e7bb457 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,3 +1,6 @@ +import TrsMatrix from "./TrsMatrix"; +import Point3D from "./Point3D"; + declare module '*.glsl' { const value: string; export = value; @@ -8,9 +11,16 @@ declare interface Vector { } declare interface Transformable { - translateBy(tX: number, tY: number, tZ: number): any; - rotateBy(degX: number, degY: number, degZ: number): any; - scaleBy(sX: number, sY: number, sZ: number): any; + translateBy(tX: number, tY: number, tZ: number): Transformable; + rotateBy(degX: number, degY: number, degZ: number): Transformable; + scaleBy(sX: number, sY: number, sZ: number): Transformable; + getTrsMatrix(): TrsMatrix; + position(): Point3D; +} + +declare interface Colored { + setColor(color: Color); + getColor(): Color; } declare interface Color extends Vector { @@ -18,4 +28,16 @@ declare interface Color extends Vector { green(): number; blue(): number; alpha(): number; +} + +declare interface GeometryParams { + pointBuffer: Float32Array; + normalBuffer: Float32Array; + elementBuffer: Uint16Array; +} + +declare interface Geometry { + pointBuffer(): Float32Array; + normalBuffer(): Float32Array; + elementBuffer(): Uint16Array; } \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 05da464..a6cc604 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,9 @@ -import Matrix4 from "./Matrix4"; +import MatrixFour from "./MatrixFour"; +import Triangle2D from "./Triangle2D"; +import TrsMatrix from "./TrsMatrix"; +import Point2D from "./Point2D"; +import BasicColor from "./BasicColor"; +import Point3D from "./Point3D"; export function identityMatrix(size: number): Float32Array { const mat = Array(size*size).fill(0); @@ -8,14 +13,6 @@ export function identityMatrix(size: number): Float32Array { )); } -export function perspMat(fovDeg: number, aspect: number, near: number, far: number): Matrix4 { - const f = Math.tan(Math.PI * 0.5 - 0.5 * fovDeg); - const rangeInv = 1.0 / (near - far); - - return new Matrix4([ - f / aspect, 0, 0, 0, - 0, f, 0, 0, - 0, 0, (near + far) * rangeInv, -1, - 0, 0, near * far * rangeInv * 2, 0 - ]); +export function triNormal(p1: Point3D, p2: Point3D, p3: Point3D): Point3D { + return p2.minus(p1).cross(p3.minus(p1)); } \ No newline at end of file diff --git a/src/vertexShader.glsl b/src/vertexShader.glsl index 5ca5b62..9110ced 100644 --- a/src/vertexShader.glsl +++ b/src/vertexShader.glsl @@ -1,11 +1,14 @@ attribute vec4 a_position; - +attribute vec4 a_normal; uniform mat4 u_trsMatrix; +varying vec3 v_light; varying float v_depth; - +varying vec3 v_normal; void main() { + v_light = vec3(0.0, 0.0, 1.0); + v_normal = (u_trsMatrix * a_normal).xyz; vec4 pos = u_trsMatrix * a_position; v_depth = pos.z; gl_Position = pos;