├── .gitignore ├── README.md ├── index.html ├── js ├── engine │ ├── Camera │ │ ├── Camera.js │ │ └── FpCamera.js │ ├── Constants.js │ ├── Geometry.js │ ├── Light │ │ └── SpotLight.js │ ├── Matrix.js │ ├── Object3D.js │ ├── Point.js │ ├── Polygon.js │ ├── RenderedPolygon.js │ ├── Renderer.js │ ├── Scene.js │ ├── Utils.js │ └── reader │ │ ├── Generate.js │ │ └── Loader.js ├── geometries │ ├── Cone.js │ ├── Cube.js │ ├── Plane.js │ ├── PyramidSquare.js │ ├── RegularPrism.js │ └── Sphere.js ├── index.js └── scenes │ ├── CastleScene.js │ ├── DefaultScene.js │ ├── SpheresScene.js │ ├── TestLoadScene.js │ └── WavesScene.js ├── resources └── obj │ ├── cube.obj │ ├── diamond.obj │ ├── icosahedron.obj │ ├── shuttle.obj │ └── teapot.obj ├── screenshot.png └── screenshot2.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Javascript-3D-Engine 2 | Javascript 3D Engine - Based in canvas element 3 | 4 | Try it here: 5 | https://juangf.github.io/Javascript-3D-Engine/ 6 | 7 | Image of the engine working 8 | 9 | ## Camera controls 10 | - Key `W`: Front. 11 | - Key `A` or `←`: Left. 12 | - Key `D` or `→`: Right. 13 | - Key `S`: Back. 14 | - Key `↓`: Down. 15 | - Key `↑`: Up. 16 | - Key `C`: Restart camera position. 17 | - Key `1`: Default scene. 18 | - Key `2`: Pyramid scene. 19 | - Mouse Pointer: LookAt. -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Javascript 3D Engine 6 | 7 | 8 | 18 | 19 | 20 | Your browser does not have canvas support :( 21 | 22 | 23 | -------------------------------------------------------------------------------- /js/engine/Camera/Camera.js: -------------------------------------------------------------------------------- 1 | import Point from '../Point.js'; 2 | import Matrix from '../Matrix.js'; 3 | 4 | class Camera { 5 | 6 | constructor(config) { 7 | this.id = config.id; 8 | this.position = new Point(0, 190, -500); 9 | this.up = new Point(0, 1, 0); 10 | this.front = new Point(0, 0, 1); 11 | this.far = 5000; 12 | this.near = -650; 13 | this.right = -2500; 14 | this.left = 2500; 15 | this.top = 2500; 16 | this.bottom = -2500; 17 | 18 | if (config.position) { 19 | this.setPosition(config.position); 20 | } 21 | } 22 | 23 | getId() { 24 | return this.id; 25 | } 26 | 27 | getPosition() { 28 | return this.position; 29 | } 30 | 31 | setPosition(p) { 32 | this.position = p; 33 | return this; 34 | } 35 | 36 | getMatrix() { 37 | return new Matrix(); 38 | } 39 | 40 | getFar() { 41 | return this.far; 42 | } 43 | 44 | getNear() { 45 | return this.near; 46 | } 47 | 48 | getLeft() { 49 | return this.left; 50 | } 51 | 52 | getRight() { 53 | return this.right; 54 | } 55 | 56 | getTop() { 57 | return this.top; 58 | } 59 | 60 | getBottom() { 61 | return this.bottom; 62 | } 63 | 64 | isPointInViewport(p) { 65 | return p.getZ() <= this.getFar() && 66 | p.getZ() >= this.getNear() && 67 | p.getX() >= this.getRight() && 68 | p.getX() <= this.getLeft() && 69 | p.getY() <= this.getTop() && 70 | p.getY() >= this.getBottom(); 71 | } 72 | } 73 | 74 | export default Camera; -------------------------------------------------------------------------------- /js/engine/Camera/FpCamera.js: -------------------------------------------------------------------------------- 1 | import Camera from './Camera.js'; 2 | import Matrix from '../Matrix.js'; 3 | import Point from '../Point.js'; 4 | 5 | class FpCamera extends Camera { 6 | constructor(config) { 7 | super(config); 8 | this.pitch = config.pitch ? config.pitch : 0; 9 | this.yaw = config.yaw ? config.yaw : 0; 10 | } 11 | 12 | setPitch(pitch) { 13 | this.pitch = pitch; 14 | } 15 | 16 | setYaw(yaw) { 17 | this.yaw = yaw; 18 | } 19 | 20 | getMatrix() { 21 | let cosPitch = Math.cos(this.pitch); 22 | let sinPitch = Math.sin(this.pitch); 23 | let cosYaw = Math.cos(this.yaw); 24 | let sinYaw = Math.sin(this.yaw); 25 | 26 | let xaxis = new Point(cosYaw, 0, -sinYaw); 27 | let yaxis = new Point(sinYaw * sinPitch, cosPitch, cosYaw * sinPitch); 28 | let zaxis = new Point(sinYaw * cosPitch, -sinPitch, cosPitch * cosYaw); 29 | 30 | let viewMatrix = new Matrix([ 31 | [xaxis.x, yaxis.x, zaxis.x, 0], 32 | [xaxis.y, yaxis.y, zaxis.y, 0], 33 | [xaxis.z, yaxis.z, zaxis.z, 0], 34 | [-Point.dot(xaxis, this.position), -Point.dot(yaxis, this.position), -Point.dot(zaxis, this.position), 1] 35 | ]); 36 | //console.log(-Point.dot(xaxis, this.position), -Point.dot(yaxis, this.position), -Point.dot(zaxis, this.position)); 37 | 38 | return viewMatrix; 39 | } 40 | } 41 | 42 | export default FpCamera; -------------------------------------------------------------------------------- /js/engine/Constants.js: -------------------------------------------------------------------------------- 1 | // Engine. 2 | export const DEFAULT_MOVE_VELOCITY = 30; 3 | 4 | // Camera. 5 | export const DEFAULT_CAMERA_POINT = {x: 0, y: 540, z: -700}; 6 | 7 | // Keys. 8 | export const KEY_W = 87; 9 | export const KEY_A = 65; 10 | export const KEY_S = 83; 11 | export const KEY_D = 68; 12 | export const KEY_C = 67; 13 | export const KEY_UP = 38; 14 | export const KEY_LEFT = 37; 15 | export const KEY_DOWN = 40; 16 | export const KEY_RIGHT = 39; 17 | 18 | // Scenes. 19 | export const KEY_1 = 49; 20 | export const KEY_2 = 50; 21 | export const KEY_3 = 51; 22 | export const KEY_4 = 52; -------------------------------------------------------------------------------- /js/engine/Geometry.js: -------------------------------------------------------------------------------- 1 | import Point from './Point.js'; 2 | import Utils from './Utils.js'; 3 | class Geometry { 4 | 5 | constructor() { 6 | this.points = []; 7 | this.poligons = []; 8 | } 9 | 10 | addPoint(point) { 11 | this.points.push(point); 12 | return this; 13 | } 14 | 15 | getPoints() { 16 | return this.points; 17 | } 18 | 19 | addPolygon(polygon) { 20 | this.poligons.push(polygon); 21 | return this; 22 | } 23 | 24 | getPolygon() { 25 | return this.poligons; 26 | } 27 | 28 | calcNormals() { 29 | let pointsNormalsList = {}; 30 | 31 | // Calc poligon normal 32 | for (let i = 0; i < this.poligons.length; i++) { 33 | let poly = this.poligons[i], 34 | indexs = poly.getIndexs(), 35 | normal = Utils.getNormal(this.points[indexs[0]], this.points[indexs[1]], this.points[indexs[2]]); 36 | 37 | poly.setNormal(normal); 38 | 39 | for (let j = 0; j < indexs.length; j++) { 40 | if (typeof pointsNormalsList[indexs[j]] === 'undefined') { 41 | pointsNormalsList[indexs[j]] = [normal]; 42 | } else { 43 | pointsNormalsList[indexs[j]].push(normal); 44 | } 45 | } 46 | } 47 | 48 | // Calc vertex normals 49 | for (const index in pointsNormalsList) { 50 | let normals = pointsNormalsList[index], 51 | resultNormal = normals[0]; 52 | 53 | for (let i = 1; i < normals.length; i++) { 54 | resultNormal = Point.add(resultNormal, normals[i]); 55 | } 56 | this.points[index].setNormal(Point.normalize(resultNormal)); 57 | } 58 | } 59 | } 60 | 61 | export default Geometry; -------------------------------------------------------------------------------- /js/engine/Light/SpotLight.js: -------------------------------------------------------------------------------- 1 | import Sphere from "../../geometries/Sphere.js"; 2 | import Object3D from "../../engine/Object3D.js"; 3 | 4 | class SpotLight { 5 | constructor(config) { 6 | this.id = config.id; 7 | this.position = config.position; 8 | this.options = Object.assign({ 9 | rgbaColor: { 10 | r: 255, 11 | g: 255, 12 | b: 255, 13 | a: 0.1 14 | }, 15 | }, config.options); 16 | this.object = new Object3D({ 17 | id: 'spotLight_Sphere_' + this.id, 18 | position: this.position, 19 | geometry: new Sphere(12, 40), 20 | options: { 21 | rgbaColor: { 22 | r: 255, 23 | g: 255, 24 | b: 255, 25 | a: 1 26 | }, 27 | } 28 | }); 29 | } 30 | 31 | getObjects() { 32 | return [this.object]; 33 | } 34 | 35 | getPosition() { 36 | return this.position; 37 | } 38 | 39 | setPosition(p) { 40 | this.position = p; 41 | if (this.object) { 42 | this.object.setPosition(p); 43 | } 44 | return this; 45 | } 46 | 47 | getOptions() { 48 | return this.options; 49 | } 50 | } 51 | 52 | export default SpotLight; -------------------------------------------------------------------------------- /js/engine/Matrix.js: -------------------------------------------------------------------------------- 1 | class Matrix { 2 | /* 3 | 1, 0, 0, X 4 | 0, 1, 0, Y 5 | 0, 0, 1, Z 6 | 0, 0, 0, W 7 | */ 8 | constructor(values = [ 9 | [1, 0, 0, 0], 10 | [0, 1, 0, 0], 11 | [0, 0, 1, 0], 12 | [0, 0, 0, 1], 13 | ]) { 14 | // Create the identity matrix by default 15 | this.m = values; 16 | } 17 | 18 | getSize() { 19 | return this.m.length; 20 | } 21 | 22 | set(values) { 23 | this.m = values; 24 | } 25 | 26 | setRow(index, rowValues) { 27 | this.m[index] = rowValues; 28 | } 29 | 30 | get() { 31 | return this.m; 32 | } 33 | 34 | getValue(row, col) { 35 | return this.m[row][col]; 36 | } 37 | 38 | setValue(row, col, value) { 39 | this.m[row][col] = value; 40 | return this; 41 | } 42 | 43 | toString() { 44 | console.log(`${this.m[0][0]}, ${this.m[0][1]}, ${this.m[0][2]}, ${this.m[0][3]} 45 | ${this.m[1][0]}, ${this.m[1][1]}, ${this.m[1][2]}, ${this.m[1][3]} 46 | ${this.m[2][0]}, ${this.m[2][1]}, ${this.m[2][2]}, ${this.m[2][3]} 47 | ${this.m[3][0]}, ${this.m[3][1]}, ${this.m[3][2]}, ${this.m[3][3]}`); 48 | } 49 | 50 | static multiply_iterative(m1, m2) { 51 | let m = new Matrix(); 52 | let size = m1.getSize(); 53 | 54 | for (let i = 0; i < size; i++) { 55 | for (let j = 0; j < size; j++) { 56 | let sum = 0; 57 | for (let k = 0; k < size; k++) { 58 | sum += m1.getValue(i, k) * m2.getValue(j, k); 59 | } 60 | m.setValue(i, j, sum); 61 | } 62 | } 63 | return m; 64 | } 65 | 66 | static multiply(m1, m2) { 67 | let m1v = m1.get(); 68 | let m2v = m2.get(); 69 | 70 | return new Matrix([ 71 | [ 72 | m1v[0][0] * m2v[0][0] + m1v[0][1] * m2v[0][1] + m1v[0][2] * m2v[0][2] + m1v[0][3] * m2v[0][3], 73 | m1v[0][0] * m2v[1][0] + m1v[0][1] * m2v[1][1] + m1v[0][2] * m2v[1][2] + m1v[0][3] * m2v[1][3], 74 | m1v[0][0] * m2v[2][0] + m1v[0][1] * m2v[2][1] + m1v[0][2] * m2v[2][2] + m1v[0][3] * m2v[2][3], 75 | m1v[0][0] * m2v[3][0] + m1v[0][1] * m2v[3][1] + m1v[0][2] * m2v[3][2] + m1v[0][3] * m2v[3][3] 76 | ], 77 | [ 78 | m1v[1][0] * m2v[0][0] + m1v[1][1] * m2v[0][1] + m1v[1][2] * m2v[0][2] + m1v[1][3] * m2v[0][3], 79 | m1v[1][0] * m2v[1][0] + m1v[1][1] * m2v[1][1] + m1v[1][2] * m2v[1][2] + m1v[1][3] * m2v[1][3], 80 | m1v[1][0] * m2v[2][0] + m1v[1][1] * m2v[2][1] + m1v[1][2] * m2v[2][2] + m1v[1][3] * m2v[2][3], 81 | m1v[1][0] * m2v[3][0] + m1v[1][1] * m2v[3][1] + m1v[1][2] * m2v[3][2] + m1v[1][3] * m2v[3][3] 82 | ], 83 | [ 84 | m1v[2][0] * m2v[0][0] + m1v[2][1] * m2v[0][1] + m1v[2][2] * m2v[0][2] + m1v[2][3] * m2v[0][3], 85 | m1v[2][0] * m2v[1][0] + m1v[2][1] * m2v[1][1] + m1v[2][2] * m2v[1][2] + m1v[2][3] * m2v[1][3], 86 | m1v[2][0] * m2v[2][0] + m1v[2][1] * m2v[2][1] + m1v[2][2] * m2v[2][2] + m1v[2][3] * m2v[2][3], 87 | m1v[2][0] * m2v[3][0] + m1v[2][1] * m2v[3][1] + m1v[2][2] * m2v[3][2] + m1v[2][3] * m2v[3][3] 88 | ], 89 | [ 90 | m1v[3][0] * m2v[0][0] + m1v[3][1] * m2v[0][1] + m1v[3][2] * m2v[0][2] + m1v[3][3] * m2v[0][3], 91 | m1v[3][0] * m2v[1][0] + m1v[3][1] * m2v[1][1] + m1v[3][2] * m2v[1][2] + m1v[3][3] * m2v[1][3], 92 | m1v[3][0] * m2v[2][0] + m1v[3][1] * m2v[2][1] + m1v[3][2] * m2v[2][2] + m1v[3][3] * m2v[2][3], 93 | m1v[3][0] * m2v[3][0] + m1v[3][1] * m2v[3][1] + m1v[3][2] * m2v[3][2] + m1v[3][3] * m2v[3][3] 94 | ] 95 | ]); 96 | } 97 | 98 | /** 99 | * Based on: 100 | * https://github.com/jcoglan/sylvester/blob/master/src/matrix.js 101 | */ 102 | static toRightTriangular(m) { 103 | let M = m, els; 104 | let n = 4, i, j, np = 8, p; 105 | for (i = 0; i < n; i++) { 106 | if (M.getValue(i, i) === 0) { 107 | for (j = i + 1; j < n; j++) { 108 | if (M.getValue(j, i) !== 0) { 109 | els = []; 110 | for (p = 0; p < np; p++) { els.push(M.getValue(i, p) + M.getValue(j, p)); } 111 | M.setRow(i, els); 112 | break; 113 | } 114 | } 115 | } 116 | if (M.getValue(i, i) !== 0) { 117 | for (j = i + 1; j < n; j++) { 118 | var multiplier = M.getValue(j, i) / M.getValue(i, i); 119 | els = []; 120 | for (p = 0; p < np; p++) { 121 | // Elements with column numbers up to an including the number of the 122 | // row that we're subtracting can safely be set straight to zero, 123 | // since that's the point of this routine and it avoids having to 124 | // loop over and correct rounding errors later 125 | els.push(p <= i ? 0 : M.getValue(j, p) - M.getValue(i, p) * multiplier); 126 | } 127 | M.setRow(j, els); 128 | } 129 | } 130 | } 131 | return M; 132 | }; 133 | 134 | static duplicate(m) { 135 | return new Matrix(m.get()); 136 | } 137 | 138 | static augment(m1, m2) { 139 | let M = m2; 140 | let T = m1, cols = 4; 141 | let i = 4, nj = 4, j; 142 | if (i !== 4) { return null; } 143 | while (i--) { j = nj; 144 | while (j--) { 145 | T.setValue(i, cols + j, M.getValue(i, j)); 146 | } 147 | } 148 | return T; 149 | } 150 | 151 | /** 152 | * Based on: 153 | * https://github.com/jcoglan/sylvester/blob/master/src/matrix.js 154 | */ 155 | static inverse(m) { 156 | let n = 4, i = n, j; 157 | let M = Matrix.toRightTriangular(Matrix.augment(m, new Matrix())); 158 | let np = 8, p, els, divisor; 159 | let inverse_elements = []; 160 | // Sylvester.Matrix is non-singular so there will be no zeros on the 161 | // diagonal. Cycle through rows from last to first. 162 | while (i--) { 163 | // First, normalise diagonal elements to 1 164 | els = []; 165 | inverse_elements[i] = []; 166 | divisor = M.getValue(i, i); 167 | 168 | for (p = 0; p < np; p++) { 169 | let new_element = M.getValue(i, p) / divisor; 170 | els.push(new_element); 171 | // Shuffle off the current row of the right hand side into the results 172 | // array as it will not be modified by later runs through this loop 173 | if (p >= n) { 174 | inverse_elements[i].push(new_element); 175 | } 176 | } 177 | M.setRow(i, els); 178 | // Then, subtract this row from those above it to give the identity matrix 179 | // on the left hand side 180 | j = i; 181 | while (j--) { 182 | els = []; 183 | for (p = 0; p < np; p++) { 184 | els.push(M.getValue(j, p) - M.getValue(i, p) * M.getValue(j, i)); 185 | } 186 | M.setRow(j, els); 187 | } 188 | } 189 | return new Matrix(inverse_elements); 190 | } 191 | 192 | } 193 | 194 | export default Matrix; -------------------------------------------------------------------------------- /js/engine/Object3D.js: -------------------------------------------------------------------------------- 1 | import Matrix from './Matrix.js'; 2 | import Utils from './Utils.js'; 3 | 4 | class Object3D { 5 | 6 | constructor(config) { 7 | this.id = config.id; 8 | this.geometry = config.geometry; 9 | this.position = config.position; 10 | this.options = Object.assign({ 11 | drawPoints: false, 12 | backfaceCulling: true, 13 | drawNormals: false, 14 | drawVertexNormals: false, 15 | wireFrame: false, 16 | rgbaColor: { 17 | r: 180, 18 | g: 180, 19 | b: 180, 20 | a: 1 21 | } 22 | }, config.options); 23 | this.transforms = { 24 | translation: new Matrix(), 25 | scale: new Matrix(), 26 | rotations: { 27 | x: { 28 | MRotation: null, 29 | MInverse: null, 30 | MReverse: null 31 | }, 32 | y: { 33 | MRotation: null, 34 | MInverse: null, 35 | MReverse: null 36 | }, 37 | z: { 38 | MRotation: null, 39 | MInverse: null, 40 | MReverse: null 41 | } 42 | } 43 | }; 44 | } 45 | 46 | getId() { 47 | return this.id; 48 | } 49 | 50 | getPosition() { 51 | return this.position; 52 | } 53 | 54 | setPosition(p) { 55 | this.position = p; 56 | return this; 57 | } 58 | 59 | getGeometry() { 60 | return this.geometry; 61 | } 62 | 63 | getOptions() { 64 | return this.options; 65 | } 66 | 67 | setOptions(options) { 68 | this.options = Object.assign(this.options, options); 69 | return this; 70 | } 71 | 72 | getTransforms() { 73 | return this.transforms; 74 | } 75 | 76 | scale(value) { 77 | this.transforms.scale = new Matrix(4, value); 78 | } 79 | 80 | rotate(axis, alpha, pointAround = this.position) { 81 | let axisTRot = this.transforms.rotations[axis]; 82 | 83 | if (alpha !== 0) { 84 | alpha = Utils.degToRad(alpha); 85 | 86 | switch (axis) { 87 | case 'x': 88 | /* 89 | 1, 0, 0, 0 90 | 0, cos(a), -sin(a), 0 91 | 0, sin(a), cos(a), 0 92 | 0, 0, 0, 1 93 | */ 94 | axisTRot.MRotation = new Matrix(); 95 | axisTRot.MRotation.setValue(1, 1, Math.cos(alpha)); 96 | axisTRot.MRotation.setValue(1, 2, -Math.sin(alpha)); 97 | axisTRot.MRotation.setValue(2, 1, Math.sin(alpha)); 98 | axisTRot.MRotation.setValue(2, 2, Math.cos(alpha)); 99 | break; 100 | 101 | case 'y': 102 | /* 103 | cos(a), 0, sin(a), 0 104 | 0, 0, 0 , 0 105 | 0, 0, 0 , 0 106 | -sin(1), 0, cos(a), 1 107 | */ 108 | axisTRot.MRotation = new Matrix(); 109 | axisTRot.MRotation.setValue(0, 0, Math.cos(alpha)); 110 | axisTRot.MRotation.setValue(2, 0, -Math.sin(alpha)); 111 | axisTRot.MRotation.setValue(0, 2, Math.sin(alpha)); 112 | axisTRot.MRotation.setValue(2, 2, Math.cos(alpha)); 113 | break; 114 | 115 | case 'z': 116 | /* 117 | cos(a), -sin(a), 0, 0 118 | sin(a), cos(a), 0, 0 119 | 0, 0, 1, 0 120 | 0, 0, 0, 1 121 | */ 122 | axisTRot.MRotation = new Matrix(); 123 | axisTRot.MRotation.setValue(0, 0, Math.cos(alpha)); 124 | axisTRot.MRotation.setValue(0, 1, -Math.sin(alpha)); 125 | axisTRot.MRotation.setValue(1, 0, Math.sin(alpha)); 126 | axisTRot.MRotation.setValue(1, 1, Math.cos(alpha)); 127 | break; 128 | } 129 | 130 | 131 | axisTRot.MInverse = new Matrix(); 132 | axisTRot.MInverse.setValue(0, 3, -pointAround.getX()), 133 | axisTRot.MInverse.setValue(1, 3, -pointAround.getY()); 134 | axisTRot.MInverse.setValue(2, 3, -pointAround.getZ()); 135 | 136 | axisTRot.MReverse = new Matrix(); 137 | axisTRot.MReverse.setValue(0, 3, pointAround.getX()), 138 | axisTRot.MReverse.setValue(1, 3, pointAround.getY()); 139 | axisTRot.MReverse.setValue(2, 3, pointAround.getZ()); 140 | } else { 141 | axisTRot.MRotation = null; 142 | } 143 | return this; 144 | } 145 | } 146 | 147 | export default Object3D; -------------------------------------------------------------------------------- /js/engine/Point.js: -------------------------------------------------------------------------------- 1 | class Point { 2 | 3 | constructor(x, y, z, w = 1) { 4 | this.x = x; 5 | this.y = y; 6 | this.z = z; 7 | this.w = w; // If the Vector is director, w = 0 8 | this.normal = null; // If it is a geometry vertex, it stores the calculated vertex normal 9 | } 10 | 11 | setCoords(x, y, z, w = 1) { 12 | this.x = x; 13 | this.y = y; 14 | this.z = z; 15 | this.w = w; 16 | return this; 17 | } 18 | 19 | getCoords() { 20 | return [this.x, this.y, this.z, this.w]; 21 | } 22 | 23 | getX() { 24 | return this.x; 25 | } 26 | 27 | getY() { 28 | return this.y; 29 | } 30 | 31 | getZ() { 32 | return this.z; 33 | } 34 | 35 | setNormal(normal) { 36 | this.normal = normal; 37 | return this; 38 | } 39 | 40 | getNormal() { 41 | return this.normal; 42 | } 43 | 44 | static add(p1, p2) { 45 | return new Point(p2.getX() + p1.getX(), p2.getY() + p1.getY(), p2.getZ() + p1.getZ()); 46 | } 47 | 48 | static substract(p1, p2) { 49 | return new Point(p2.getX() - p1.getX(), p2.getY() - p1.getY(), p2.getZ() - p1.getZ()); 50 | } 51 | 52 | static multiply(p1, v) { 53 | return new Point(p1.getX() * v, p1.getY() * v, p1.getZ() * v); 54 | } 55 | 56 | static dotProduct(p1, p2) { 57 | return new Point( 58 | p1.getY() * p2.getZ() - p1.getZ() * p2.getY(), 59 | p1.getZ() * p2.getX() - p1.getX() * p2.getZ(), 60 | p1.getX() * p2.getY() - p1.getY() * p2.getX() 61 | ); 62 | } 63 | 64 | static dot(p1, p2) { 65 | return p1.getX() * p2.getX() + p1.getY() * p2.getY() + p1.getZ() * p2.getZ(); 66 | } 67 | 68 | static length(p) { 69 | return Math.sqrt(this.dot(p, p)); 70 | } 71 | 72 | static normalize(p) { 73 | let m = this.length(p); 74 | if (m > 0) { 75 | return new Point(p.getX() / m, p.getY() / m, p.getZ() / m); 76 | } else { 77 | return p; 78 | } 79 | } 80 | 81 | static multiplyMatrix(p, m) { 82 | let size = m.getSize(); 83 | let coords = p.getCoords(); 84 | let res = [0, 0, 0]; 85 | 86 | for (let i = 0; i < size; i++) { 87 | let sum = 0; 88 | for (let j = 0; j < size; j++) { 89 | sum += m.getValue(i, j) * coords[j]; 90 | } 91 | res[i] = sum; 92 | } 93 | 94 | return new Point(res[0], res[1], res[2], res[3]); 95 | } 96 | 97 | static angleBetween(v1, v2) { 98 | var dot = this.dot(v1, v2); 99 | var mod1 = this.length(v1); 100 | var mod2 = this.length(v2); 101 | 102 | if (!mod2 || !mod1) { 103 | return 0; 104 | } 105 | 106 | var alpha = dot / (mod1 * mod2); 107 | 108 | if (alpha < -1) alpha = -1; 109 | else if (alpha > 1) alpha = 1; 110 | 111 | return Math.acos(alpha); 112 | } 113 | } 114 | 115 | export default Point; -------------------------------------------------------------------------------- /js/engine/Polygon.js: -------------------------------------------------------------------------------- 1 | class Polygon { 2 | 3 | constructor(indexs = [], normal = null) { 4 | this.indexs = indexs; 5 | this.normal = normal; // Normal vector 6 | } 7 | 8 | getIndexs() { 9 | return this.indexs; 10 | } 11 | 12 | setNormal(normal) { 13 | this.normal = normal; 14 | return this; 15 | } 16 | 17 | getNormal() { 18 | return this.normal; 19 | } 20 | 21 | hasIndex(index) { 22 | return this.indexs.indexOf(index) > -1; 23 | } 24 | } 25 | 26 | export default Polygon; -------------------------------------------------------------------------------- /js/engine/RenderedPolygon.js: -------------------------------------------------------------------------------- 1 | class RenderedPolygon { 2 | constructor(points = [], options = {}) { 3 | this.points = points; 4 | this.pointsIndexs = []; 5 | this.normal = null; 6 | this.options = Object.assign({ 7 | drawPoints: false, 8 | drawNormals: false, 9 | drawVertexNormals: false, 10 | wireFrame: false, 11 | rgbaColor: '' 12 | }, options); 13 | } 14 | 15 | getOptions() { 16 | return this.options; 17 | } 18 | 19 | getPoints() { 20 | return this.points; 21 | } 22 | 23 | addPoint(point) { 24 | this.points.push(point); 25 | return this; 26 | } 27 | 28 | getPointIndexs() { 29 | return this.pointsIndexs; 30 | } 31 | 32 | addPointIndex(index) { 33 | this.pointsIndexs.push(index); 34 | return this; 35 | } 36 | 37 | setOptions(options) { 38 | this.options = Object.assign(this.options, options); 39 | return this; 40 | } 41 | 42 | setNormal(normal) { 43 | this.normal = normal; 44 | return this; 45 | } 46 | 47 | getNormal() { 48 | return this.normal; 49 | } 50 | } 51 | 52 | export default RenderedPolygon; -------------------------------------------------------------------------------- /js/engine/Renderer.js: -------------------------------------------------------------------------------- 1 | import Point from './Point.js'; 2 | import Matrix from './Matrix.js'; 3 | import RenderedPolygon from './RenderedPolygon.js' 4 | 5 | class Renderer { 6 | constructor(canvas, camera, scene) { 7 | this.canvas = canvas; 8 | this.camera = camera; 9 | this.scene = scene 10 | 11 | this.ctx = this.canvas.getContext('2d'); 12 | this.worldMatrix = new Matrix([ 13 | [1, 0, 0, 0], 14 | [0, -1, 0, 0], 15 | [0, 0, 1, 0], 16 | [0, 0, 0, 1] 17 | ]); 18 | 19 | this.objectsLoaded = false; 20 | this.objects = {}; 21 | this.lights = {}; 22 | } 23 | 24 | setScene(scene) { 25 | this.scene = scene; 26 | this.objectsLoaded = false; 27 | return this; 28 | } 29 | 30 | isVisible(p1, p2, p3) { 31 | return ( 32 | (this.projection('x', p2.getX(), p2.getZ()) - this.projection('x', p1.getX(), p1.getZ())) * (this.projection('y', p3.getY(), p3.getZ()) - this.projection('y', p1.getY(), p1.getZ())) < 33 | (this.projection('x', p3.getX(), p3.getZ()) - this.projection('x', p1.getX(), p1.getZ())) * (this.projection('y', p2.getY(), p2.getZ()) - this.projection('y', p1.getY(), p1.getZ())) 34 | ); 35 | } 36 | 37 | projection(axis, value, z) { 38 | return (value * 700) / (z + 700) + (axis === 'x' ? this.canvas.width : this.canvas.height) / 2; 39 | } 40 | 41 | clearCanvas() { 42 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 43 | return this; 44 | } 45 | 46 | drawPoint(point, text = '') { 47 | let x = this.projection('x', point.getX(), point.getZ()); 48 | let y = this.projection('y', point.getY(), point.getZ()); 49 | 50 | this.ctx.beginPath(); 51 | this.ctx.strokeStyle = '#000000'; 52 | this.ctx.arc(x, y, 4, 0, Math.PI * 2, true); 53 | this.ctx.stroke(); 54 | 55 | if (text !== '') { 56 | this.ctx.font = '10px Verdana'; 57 | this.ctx.strokeText(text, x + 4, y - 4); 58 | } 59 | return this; 60 | } 61 | 62 | drawNormal(poly) { 63 | let pDir = Point.multiply(poly.getNormal(), 30); 64 | let points = poly.getPoints(); 65 | let p = Point.add(Point.add(points[0], points[1]), points[2]); 66 | 67 | if (points.length === 4) { 68 | p = Point.multiply(Point.add(p, points[3]), 0.25); 69 | } else { 70 | p = Point.multiply(p, 0.33333); 71 | } 72 | 73 | this.ctx.beginPath(); 74 | this.ctx.moveTo( 75 | this.projection('x', p.getX(), p.getZ()), 76 | this.projection('y', p.getY(), p.getZ()) 77 | ); 78 | this.ctx.strokeStyle = '#FF0000'; 79 | this.ctx.lineTo( 80 | this.projection('x', p.getX() + pDir.getX(), p.getZ() + pDir.getZ()), 81 | this.projection('y', p.getY() + pDir.getY(), p.getZ() + pDir.getZ()) 82 | ); 83 | this.ctx.stroke(); 84 | this.ctx.strokeStyle = '#000000'; 85 | return this; 86 | } 87 | 88 | drawVertexNormal(p, normal) { 89 | let pDir = Point.multiply(normal, 30); 90 | 91 | this.ctx.beginPath(); 92 | this.ctx.moveTo( 93 | this.projection('x', p.getX(), p.getZ()), 94 | this.projection('y', p.getY(), p.getZ()) 95 | ); 96 | this.ctx.strokeStyle = '#0000FF'; 97 | this.ctx.lineTo( 98 | this.projection('x', p.getX() + pDir.getX(), p.getZ() + pDir.getZ()), 99 | this.projection('y', p.getY() + pDir.getY(), p.getZ() + pDir.getZ()) 100 | ); 101 | this.ctx.stroke(); 102 | this.ctx.strokeStyle = '#000000'; 103 | return this; 104 | } 105 | 106 | drawPolygons(renderedPolygons) { 107 | renderedPolygons.sort((poly1, poly2) => { 108 | let points1 = poly1.getPoints(); 109 | let points2 = poly2.getPoints(); 110 | return (points2[0].getZ() + points2[1].getZ() + points2[2].getZ()) / 3 - (points1[0].getZ() + points1[1].getZ() + points1[2].getZ()) / 3; 111 | }); 112 | 113 | renderedPolygons.forEach(poly => { 114 | let options = poly.getOptions(); 115 | let points = poly.getPoints(); 116 | 117 | points.forEach((p, i) => { 118 | if (i === 0) { 119 | this.ctx.beginPath(); 120 | this.ctx.moveTo( 121 | this.projection('x', p.getX(), p.getZ()), 122 | this.projection('y', p.getY(), p.getZ()) 123 | ); 124 | } 125 | this.ctx.lineTo( 126 | this.projection('x', p.getX(), p.getZ()), 127 | this.projection('y', p.getY(), p.getZ()) 128 | ); 129 | }); 130 | 131 | this.ctx.closePath(); 132 | this.ctx.strokeStyle = options.rgbaColor; 133 | this.ctx.stroke(); 134 | 135 | if (!options.wireFrame) { 136 | this.ctx.fillStyle = options.rgbaColor; 137 | this.ctx.fill(); 138 | } 139 | 140 | if (options.drawPoints) { 141 | let pointIndexs = poly.getPointIndexs(); 142 | points.forEach((p, i) => { 143 | this.drawPoint(p, pointIndexs[i]); 144 | }); 145 | } 146 | 147 | if (options.drawNormals) { 148 | this.drawNormal(poly); 149 | } 150 | 151 | if (options.drawVertexNormals) { 152 | points.forEach(p => { 153 | this.drawVertexNormal(p, p.getNormal()); 154 | }); 155 | } 156 | }); 157 | return this; 158 | } 159 | 160 | renderPolygon(camera, polygon, position, points, transforms, transformsMatrix, options) { 161 | let indexs = polygon.getIndexs(); 162 | let numindexs = indexs.length; 163 | let transformedPoints = {}; 164 | let renderedPoly = new RenderedPolygon(); 165 | 166 | /** 167 | * @todo refactorize the transformed points system 168 | */ 169 | if (options.backfaceCulling) { 170 | for (let i = 0; i < 3; i++) { 171 | let p = Point.add(points[indexs[i]], position); 172 | transformedPoints[indexs[i]] = Point.multiplyMatrix(p, transformsMatrix); 173 | } 174 | if (!this.isVisible(transformedPoints[indexs[0]], transformedPoints[indexs[1]], transformedPoints[indexs[2]])) { 175 | return null; 176 | } 177 | } 178 | 179 | for (let i = 0; i < numindexs; i++) { 180 | let p = Point.add(points[indexs[i]], position); 181 | p = Point.multiplyMatrix(p, transformsMatrix); 182 | 183 | if (!camera.isPointInViewport(p)) { 184 | return null; 185 | } 186 | 187 | p.setNormal(Point.multiplyMatrix(Point.substract(points[indexs[i]].getNormal(), camera.getPosition()), transformsMatrix)); 188 | 189 | renderedPoly.addPoint(p); 190 | 191 | if (options.drawPoints) { 192 | renderedPoly.addPointIndex(indexs[i]); 193 | } 194 | } 195 | 196 | renderedPoly.setNormal(Point.multiplyMatrix(Point.substract(polygon.getNormal(), camera.getPosition()), transformsMatrix)); 197 | 198 | return renderedPoly; 199 | } 200 | 201 | lightFn(normal, p0, objPos, pL){ 202 | let vOL = Point.substract(Point.add(p0, objPos), pL); 203 | let alpha = Point.angleBetween(vOL, normal) * 180 / Math.PI; 204 | 205 | return alpha * 255 / 90; 206 | } 207 | 208 | renderObjectPolygons(object, camera, lights) { 209 | let pos = object.getPosition(); 210 | let options = object.getOptions(); 211 | let geometry = object.getGeometry(); 212 | let polygons = geometry.getPolygon(); 213 | let points = geometry.getPoints(); 214 | let transforms = object.getTransforms(); 215 | 216 | // Note: TransformedPoint = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalPoint 217 | let transformsMatrix = Matrix.multiply(this.worldMatrix, camera.getMatrix()); 218 | let renderedPolygons = []; 219 | let ambient = 190; 220 | 221 | polygons.forEach(p => { 222 | let renderedPoly = this.renderPolygon(camera, p, pos, points, transforms, transformsMatrix, options); 223 | if (renderedPoly) { 224 | if (options.drawNormals || options.drawVertexNormals || options.drawPoints) { 225 | renderedPoly.setOptions({ 226 | drawNormals: options.drawNormals, 227 | drawVertexNormals: options.drawVertexNormals, 228 | drawPoints: options.drawPoints 229 | }); 230 | } 231 | 232 | if (options.wireFrame) { 233 | renderedPoly.setOptions({wireFrame: options.wireFrame}); 234 | } 235 | 236 | for (let [id, light] of Object.entries(lights)) { 237 | let indexs = p.getIndexs(); 238 | let temp = this.lightFn( 239 | p.getNormal(), 240 | Point.multiply(Point.add(Point.add(points[indexs[0]], points[indexs[1]]), points[indexs[2]]), 0.33333), 241 | pos, 242 | light.getPosition() 243 | ); 244 | 245 | if (temp < ambient) { 246 | temp = ambient; 247 | } 248 | 249 | let color = this.mixRgbaColors(options.rgbaColor, light.getOptions()['rgbaColor'], temp / 255); 250 | 251 | renderedPoly.setOptions({ 252 | rgbaColor: `rgba(${color.r}, ${color.g}, ${color.b}, 1)` 253 | }); 254 | } 255 | 256 | renderedPolygons.push(renderedPoly); 257 | } 258 | }); 259 | 260 | return renderedPolygons; 261 | } 262 | 263 | // Fast and easy way to combine (additive mode) two RGBA colors with JavaScript. 264 | // https://gist.github.com/JordanDelcros/518396da1c13f75ee057 265 | mixRgbaColors(base, added, ratio = 1) { 266 | const alpha = 1 - (1 - added.a) * (1 - base.a); 267 | return { 268 | 'r' : Math.round((added.r * added.a / alpha) + (base.r * base.a * (1 - added.a) / alpha)) * ratio, 269 | 'g' : Math.round((added.g * added.a / alpha) + (base.g * base.a * (1 - added.a) / alpha)) * ratio, 270 | 'b' : Math.round((added.b * added.a / alpha) + (base.b * base.a * (1 - added.a) / alpha)) * ratio, 271 | 'a' : alpha 272 | }; 273 | } 274 | 275 | render() { 276 | this.clearCanvas(); 277 | 278 | // Call before render scene method 279 | this.scene.beforeRender(); 280 | 281 | if (!this.objectsLoaded) { 282 | this.objects = this.scene.getObjects(); 283 | this.lights = this.scene.getLights(); 284 | 285 | let lightObjects = []; 286 | for (let [id, light] of Object.entries(this.lights)) { 287 | lightObjects = lightObjects.concat(light.getObjects()); 288 | } 289 | this.objects = {...this.objects, ...lightObjects}; 290 | this.objectsLoaded = true; 291 | } 292 | 293 | let renderedPolygons = []; 294 | for (let [id, object] of Object.entries(this.objects)) { 295 | renderedPolygons = renderedPolygons.concat(this.renderObjectPolygons(object, this.camera, this.lights)); 296 | } 297 | 298 | this.drawPolygons(renderedPolygons); 299 | return this; 300 | } 301 | } 302 | 303 | export default Renderer; -------------------------------------------------------------------------------- /js/engine/Scene.js: -------------------------------------------------------------------------------- 1 | class Scene { 2 | 3 | constructor(config) { 4 | this.id = config.id; 5 | this.name = config.name; 6 | this.objects = {}; 7 | this.lights = {}; 8 | } 9 | 10 | addObject(obj) { 11 | this.objects[obj.id] = obj; 12 | return this; 13 | } 14 | 15 | removeObject(objectId) { 16 | delete this.objects[objectId]; 17 | return this; 18 | } 19 | 20 | getObject(objectId) { 21 | return this.objects[objectId]; 22 | } 23 | 24 | getObjects() { 25 | return this.objects; 26 | } 27 | 28 | addLight(light) { 29 | this.lights[light.id] = light; 30 | return this; 31 | } 32 | 33 | removeLight(lightId) { 34 | delete this.lights[lightId]; 35 | return this; 36 | } 37 | 38 | getLight(lightId) { 39 | return this.lights[lightId]; 40 | } 41 | 42 | getLights() { 43 | return this.lights; 44 | } 45 | 46 | beforeRender() { 47 | return this; 48 | } 49 | } 50 | 51 | export default Scene; -------------------------------------------------------------------------------- /js/engine/Utils.js: -------------------------------------------------------------------------------- 1 | import Point from './Point.js'; 2 | 3 | class Utils { 4 | static degToRad(alpha) { 5 | return alpha * Math.PI / 180; 6 | } 7 | static radToDeg(radians) { 8 | return radians * 180 / Math.PI; 9 | } 10 | static getNormal(p0, p1, p2) { 11 | let ab1 = Point.substract(p1, p2); 12 | let ab2 = Point.substract(p1, p0); 13 | let ab1xab2 = Point.dotProduct(ab1, ab2); 14 | return Point.normalize(ab1xab2); 15 | } 16 | } 17 | 18 | export default Utils; -------------------------------------------------------------------------------- /js/engine/reader/Generate.js: -------------------------------------------------------------------------------- 1 | import Loader from "./Loader.js"; 2 | import Object3D from "../Object3D.js"; 3 | import Point from "../Point.js"; 4 | import Polygon from "../Polygon.js"; 5 | import Geometry from "../../engine/Geometry.js"; 6 | 7 | class Generate { 8 | 9 | /** 10 | * 11 | * @param {number} x 12 | * @param {number} y 13 | * @param {number} z 14 | * @param {string} file 15 | * @param {number} size 16 | * @returns {Object3D} 17 | */ 18 | constructor(x, y, z, file, size = 100, options = {}) { 19 | this.point = {x: x, y: y, z: z}; 20 | this.size = size; 21 | this.file = file; 22 | 23 | let loader = new Loader(); 24 | this.raw = loader.load(this.file); 25 | this.options = Object.assign({ 26 | drawPoints: false, 27 | drawNormals: false 28 | }, options); 29 | 30 | return this.parse(); 31 | } 32 | 33 | parse() { 34 | let obj = {}; 35 | let vertexMatches = this.raw.match(/^v( -?\d+(\.\d+)?){3}$/gm); 36 | if (vertexMatches) { 37 | obj.vertices = vertexMatches.map(function(vertex) { 38 | let vertices = vertex.split(" "); 39 | vertices.shift(); 40 | return vertices; 41 | }); 42 | } 43 | 44 | let facesMatches = this.raw.match(/^f(.*)([^\n]*\n+)/gm); 45 | if (facesMatches) { 46 | obj.faces = facesMatches.map(function(face) { 47 | let faces = face.split(" "); 48 | faces.shift(); 49 | faces.forEach(function (e, i) { 50 | if (e.indexOf("/") !== -1) { 51 | faces[i] = (e.split('/')[0] - 1); 52 | } else { 53 | faces[i] = e - 1; 54 | } 55 | }); 56 | 57 | return faces; 58 | }); 59 | } 60 | 61 | let name = this.raw.match(/^o (\S+)/gm); 62 | if (name) { 63 | obj.name = name[0].split(" ")[1]; 64 | } 65 | 66 | this.obj = obj; 67 | this.createObject(); 68 | this.object.calcNormals(); 69 | 70 | return new Object3D({ 71 | id: this.obj.name, 72 | position: new Point(this.point.x, this.point.y, this.point.z), 73 | geometry: this.object, 74 | options: this.options 75 | }); 76 | } 77 | 78 | createObject() { 79 | this.object = new Geometry(); 80 | if (this.obj !== {}) { 81 | this.obj.vertices.forEach(function (element) { 82 | this.object.addPoint(new Point(element[0] * this.size, element[1] * this.size, element[2] * this.size)); 83 | }, this); 84 | this.obj.faces.forEach(function (element) { 85 | this.object.addPolygon(new Polygon(element.reverse())); 86 | }, this); 87 | } 88 | } 89 | } 90 | 91 | export default Generate; -------------------------------------------------------------------------------- /js/engine/reader/Loader.js: -------------------------------------------------------------------------------- 1 | class Loader { 2 | constructor() {} 3 | 4 | load(file) { 5 | let data = null; 6 | 7 | if (file !== null) { 8 | let rawFile = new XMLHttpRequest(); 9 | rawFile.open("GET", file, false); 10 | rawFile.onreadystatechange = function () { 11 | if (rawFile.readyState === 4) { 12 | if (rawFile.status === 200 || rawFile.status === 0) { 13 | data = rawFile.responseText; 14 | } 15 | } 16 | }; 17 | rawFile.send(null); 18 | } 19 | 20 | return data; 21 | } 22 | } 23 | 24 | export default Loader; -------------------------------------------------------------------------------- /js/geometries/Cone.js: -------------------------------------------------------------------------------- 1 | import Point from '../engine/Point.js'; 2 | import Polygon from '../engine/Polygon.js'; 3 | import Geometry from '../engine/Geometry.js'; 4 | 5 | class Cone extends Geometry { 6 | constructor(radius, height, space) { 7 | super(); 8 | let index = 0; 9 | 10 | this.addPoint(new Point(0, 0, 0)); 11 | this.addPoint(new Point(0, height, 0)); 12 | 13 | for (let i = 0, b = space; i < 360 / space; i++, b += space) { 14 | this.addPoint(new Point( 15 | radius * Math.sin(b / 180 * Math.PI), 16 | 0, 17 | radius * Math.cos(b / 180 * Math.PI) 18 | )); 19 | if (index > 0) { 20 | this.addPolygon(new Polygon([0, index + 1, index + 2])); 21 | } 22 | this.addPolygon(new Polygon([1, index + 2, index + 1])); 23 | index++; 24 | } 25 | 26 | this.addPolygon(new Polygon([0, index + 1, 2])); 27 | this.addPolygon(new Polygon([1, 2, index + 1])); 28 | 29 | this.calcNormals(); 30 | } 31 | } 32 | 33 | export default Cone; -------------------------------------------------------------------------------- /js/geometries/Cube.js: -------------------------------------------------------------------------------- 1 | import RegularPrism from './RegularPrism.js'; 2 | 3 | /** 4 | * Perfect regular prism. 5 | * 6 | * 5________4 7 | * / | /| 8 | * 0________1 | 9 | * | 6_____|_7 10 | * | / | / 11 | * 3________2/ 12 | * 13 | */ 14 | class Cube extends RegularPrism { 15 | 16 | /** 17 | * Constructor. 18 | * (All sides are the same size). 19 | * 20 | * @param {number} size 21 | */ 22 | constructor(size, allTriangles = true) { 23 | super(size, size, size, allTriangles); 24 | } 25 | } 26 | 27 | export default Cube; -------------------------------------------------------------------------------- /js/geometries/Plane.js: -------------------------------------------------------------------------------- 1 | import Point from '../engine/Point.js'; 2 | import Polygon from '../engine/Polygon.js'; 3 | import Geometry from '../engine/Geometry.js'; 4 | 5 | class Plane extends Geometry { 6 | constructor(size, space = 1) { 7 | super(); 8 | size = size / space; 9 | 10 | for (let i = 0, b = 0; i <= space; i++, b += space + 1) { 11 | for (let j = 0; j <= space; j++) { 12 | this.addPoint(new Point(j * size, 0, i * size)); 13 | if (j < space && i < space) { 14 | let bj = b + j; 15 | this.addPolygon(new Polygon([bj + 1, bj + space + 2, bj + space + 1, bj])); 16 | } 17 | } 18 | } 19 | 20 | this.calcNormals(); 21 | } 22 | } 23 | 24 | export default Plane; -------------------------------------------------------------------------------- /js/geometries/PyramidSquare.js: -------------------------------------------------------------------------------- 1 | import Point from '../engine/Point.js'; 2 | import Polygon from '../engine/Polygon.js'; 3 | import Geometry from '../engine/Geometry.js'; 4 | 5 | /** 6 | * Square base pyramid definition. 7 | */ 8 | class PyramidSquare extends Geometry { 9 | 10 | /** 11 | * Constructor. 12 | * 13 | * @param {number} h high 14 | * @param {number} w width 15 | * @param {number} d dept 16 | */ 17 | constructor(h, w, d) { 18 | super(); 19 | this 20 | .addPoint(new Point(0, h / 2, 0)) 21 | .addPoint(new Point(w / 2, -h / 2, d / 2)) 22 | .addPoint(new Point(w / 2, -h / 2, -d / 2)) 23 | .addPoint(new Point(-w / 2, -h / 2, -d / 2)) 24 | .addPoint(new Point(-w / 2, -h / 2, d / 2)); 25 | 26 | this 27 | .addPolygon(new Polygon([3, 4, 1, 2])) 28 | .addPolygon(new Polygon([0, 2, 1])) 29 | .addPolygon(new Polygon([0, 3, 2])) 30 | .addPolygon(new Polygon([0, 4, 3])) 31 | .addPolygon(new Polygon([0, 1, 4])); 32 | 33 | this.calcNormals(); 34 | } 35 | } 36 | 37 | export default PyramidSquare; -------------------------------------------------------------------------------- /js/geometries/RegularPrism.js: -------------------------------------------------------------------------------- 1 | import Point from '../engine/Point.js'; 2 | import Polygon from '../engine/Polygon.js'; 3 | import Geometry from '../engine/Geometry.js'; 4 | 5 | /** 6 | * Object with 6 faces, 12 edges, and 8 vertices. 7 | */ 8 | class RegularPrism extends Geometry { 9 | 10 | /** 11 | * Constructor. 12 | * 13 | * @param {number} w high 14 | * @param {number} h width 15 | * @param {number} d dept 16 | */ 17 | constructor(w, h, d, allTriangles = true) { 18 | super(); 19 | h = h / 2; 20 | w = w / 2; 21 | d = d / 2; 22 | 23 | this 24 | .addPoint(new Point(-h, -w, -d)) 25 | .addPoint(new Point(h, -w, -d)) 26 | .addPoint(new Point(h, w, -d)) 27 | .addPoint(new Point(-h, w, -d)) 28 | .addPoint(new Point(h, -w, d)) 29 | .addPoint(new Point(-h, -w, d)) 30 | .addPoint(new Point(-h, w, d)) 31 | .addPoint(new Point(h, w, d)); 32 | 33 | if (allTriangles) { 34 | this 35 | .addPolygon(new Polygon([0, 1, 2, 0])) 36 | .addPolygon(new Polygon([0, 2, 3, 0])) 37 | .addPolygon(new Polygon([3, 5, 0, 3])) 38 | .addPolygon(new Polygon([3, 6, 5, 3])) 39 | .addPolygon(new Polygon([4, 5, 6, 4])) 40 | .addPolygon(new Polygon([4, 6, 7, 4])) 41 | .addPolygon(new Polygon([1, 4, 7, 1])) 42 | .addPolygon(new Polygon([1, 7, 2, 1])) 43 | .addPolygon(new Polygon([5, 4, 1, 5])) 44 | .addPolygon(new Polygon([5, 1, 0, 5])) 45 | .addPolygon(new Polygon([3, 2, 7, 3])) 46 | .addPolygon(new Polygon([3, 7, 6, 3])); 47 | } else { 48 | this 49 | .addPolygon(new Polygon([0, 1, 2, 3])) 50 | .addPolygon(new Polygon([0, 3, 6, 5])) 51 | .addPolygon(new Polygon([4, 5, 6, 7])) 52 | .addPolygon(new Polygon([1, 4, 7, 2])) 53 | .addPolygon(new Polygon([5, 4, 1, 0])) 54 | .addPolygon(new Polygon([3, 2, 7, 6])); 55 | } 56 | 57 | this.calcNormals(); 58 | } 59 | } 60 | 61 | export default RegularPrism; -------------------------------------------------------------------------------- /js/geometries/Sphere.js: -------------------------------------------------------------------------------- 1 | import Point from '../engine/Point.js'; 2 | import Polygon from '../engine/Polygon.js'; 3 | import Geometry from '../engine/Geometry.js'; 4 | 5 | class Sphere extends Geometry { 6 | constructor(radius, space, allTriangles = false) { 7 | super(); 8 | let index = 0; 9 | let startInd = 0; 10 | 11 | this.addPoint(new Point(0, radius, 0)); 12 | 13 | for (let j = 0; j < 360 / space - 1; j++) { 14 | this.addPolygon(new Polygon([0, index + 1, index + 2])); 15 | index++; 16 | } 17 | 18 | this.addPolygon(new Polygon([0, index + 1, 1])); 19 | 20 | index = 1; 21 | 22 | for (let i = 0, b = space; i < 180 / space - 1; i++, b += space) { 23 | //Assign our a loop to go through 360 degrees in intervals of our variable space 24 | for (let j = 0, a = 0; j < 360 / space; j++, a += space) { 25 | if (j === 0) { 26 | startInd = index; 27 | } 28 | this.addPoint(new Point( 29 | radius * Math.sin(a / 180 * Math.PI) * Math.sin(b / 180 * Math.PI), 30 | radius * Math.cos(b / 180 * Math.PI), 31 | -radius * Math.cos(a / 180 * Math.PI) * Math.sin(b / 180 * Math.PI) 32 | )); 33 | 34 | if (i > 0) { 35 | if (j < 360 / space - 1) { 36 | if (allTriangles) { 37 | this.addPolygon(new Polygon([ 38 | index, 39 | index + 360 / space, 40 | index + 1 41 | ])); 42 | this.addPolygon(new Polygon([ 43 | index + 360 / space, 44 | index + 360 / space + 1, 45 | index + 1 46 | ])); 47 | } else { 48 | this.addPolygon(new Polygon([ 49 | index, 50 | index + 360 / space, 51 | index + 360 / space + 1, 52 | index + 1 53 | ])); 54 | } 55 | } else { 56 | if (allTriangles) { 57 | this.addPolygon(new Polygon([ 58 | index, 59 | index + 360 / space, 60 | startInd 61 | ])); 62 | this.addPolygon(new Polygon([ 63 | index + 360 / space, 64 | startInd + 360 / space, 65 | startInd 66 | ])); 67 | } else { 68 | this.addPolygon(new Polygon([ 69 | index, 70 | index + 360 / space, 71 | startInd + 360 / space, 72 | startInd 73 | ])); 74 | } 75 | } 76 | index++; 77 | } 78 | } 79 | } 80 | 81 | this.addPoint(new Point(0, -radius, 0)); 82 | 83 | index = this.getPoints().length - 1; 84 | startInd = index; 85 | 86 | for (let j = 0; j < 360 / space - 1; j++) { 87 | this.addPolygon(new Polygon([startInd, index - 1, index - 2])); 88 | index--; 89 | } 90 | 91 | this.addPolygon(new Polygon([startInd, index - 1, startInd - 1])); 92 | 93 | this.calcNormals(); 94 | } 95 | } 96 | 97 | export default Sphere; -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | // Core. 2 | import Renderer from './engine/Renderer.js'; 3 | import Point from './engine/Point.js'; 4 | import FpCamera from './engine/Camera/FpCamera.js'; 5 | import Utils from './engine/Utils.js' 6 | 7 | // Constants. 8 | import {DEFAULT_MOVE_VELOCITY, DEFAULT_CAMERA_POINT} from './engine/Constants.js'; 9 | import {KEY_W, KEY_A, KEY_S, KEY_D, KEY_UP, KEY_C, KEY_LEFT, KEY_DOWN, KEY_RIGHT} from './engine/Constants.js'; 10 | import {KEY_1, KEY_2, KEY_3, KEY_4} from './engine/Constants.js'; 11 | 12 | // Scenes. 13 | import DefaultScene from './scenes/DefaultScene.js'; 14 | import CastleScene from './scenes/CastleScene.js'; 15 | import WavesScene from './scenes/WavesScene.js'; 16 | import TestLoadScene from './scenes/TestLoadScene.js'; 17 | 18 | let canvas = document.getElementById('canvas'); 19 | canvas.width = window.innerWidth; 20 | canvas.height = window.innerHeight; 21 | 22 | let homeScene = new DefaultScene(); 23 | let castleScene = new CastleScene(0, 0, 0); 24 | let wavesScene = new WavesScene(); 25 | let testLoadScene = new TestLoadScene(); 26 | 27 | let cameraPosition = new Point( 28 | DEFAULT_CAMERA_POINT.x, 29 | DEFAULT_CAMERA_POINT.y, 30 | DEFAULT_CAMERA_POINT.z 31 | ); 32 | 33 | let camera = new FpCamera({ 34 | id: 'cam1', 35 | position: cameraPosition 36 | }); 37 | 38 | let renderer = new Renderer(canvas, camera, homeScene); 39 | 40 | let renderFn = timestamp => { 41 | requestAnimationFrame(renderFn); 42 | renderer.render(); 43 | }; 44 | renderer.render(); 45 | 46 | window.onkeydown = (e => { 47 | let updateScene = cameraPosition => { 48 | camera.setPosition(cameraPosition); 49 | renderer.render(); 50 | }; 51 | 52 | switch (e.keyCode) { 53 | case KEY_LEFT: 54 | case KEY_A: 55 | cameraPosition.setCoords(cameraPosition.getX() - DEFAULT_MOVE_VELOCITY, cameraPosition.getY(), cameraPosition.getZ()); 56 | updateScene(cameraPosition); 57 | break; 58 | case KEY_RIGHT: 59 | case KEY_D: 60 | cameraPosition.setCoords(cameraPosition.getX() + DEFAULT_MOVE_VELOCITY, cameraPosition.getY(), cameraPosition.getZ()); 61 | updateScene(cameraPosition); 62 | break; 63 | case KEY_DOWN: 64 | cameraPosition.setCoords(cameraPosition.getX(), cameraPosition.getY() + DEFAULT_MOVE_VELOCITY, cameraPosition.getZ()); 65 | updateScene(cameraPosition); 66 | break; 67 | case KEY_UP: 68 | cameraPosition.setCoords(cameraPosition.getX(), cameraPosition.getY() - DEFAULT_MOVE_VELOCITY, cameraPosition.getZ()); 69 | updateScene(cameraPosition); 70 | break; 71 | case KEY_W: 72 | cameraPosition.setCoords(cameraPosition.getX(), cameraPosition.getY(), cameraPosition.getZ() - DEFAULT_MOVE_VELOCITY); 73 | updateScene(cameraPosition); 74 | break; 75 | case KEY_S: 76 | cameraPosition.setCoords(cameraPosition.getX(), cameraPosition.getY(), cameraPosition.getZ() + DEFAULT_MOVE_VELOCITY); 77 | updateScene(cameraPosition); 78 | break; 79 | case KEY_C: 80 | cameraPosition.setCoords(DEFAULT_CAMERA_POINT.x, DEFAULT_CAMERA_POINT.y, DEFAULT_CAMERA_POINT.z); 81 | updateScene(cameraPosition); 82 | break; 83 | case KEY_1: 84 | cameraPosition.setCoords(DEFAULT_CAMERA_POINT.x, DEFAULT_CAMERA_POINT.y, DEFAULT_CAMERA_POINT.z); 85 | camera.setYaw(0); 86 | camera.setPitch(0); 87 | renderer.setScene(homeScene).render(); 88 | break; 89 | case KEY_2: 90 | cameraPosition.setCoords(390, 300, -400); 91 | camera.setYaw(-2.18); 92 | camera.setPitch(0.5); 93 | renderer.setScene(wavesScene).render(); 94 | break; 95 | case KEY_3: 96 | cameraPosition.setCoords(810, 1080, -1990); 97 | camera.setYaw(-0.37); 98 | camera.setPitch(0.38); 99 | renderer.setScene(castleScene).render(); 100 | break; 101 | case KEY_4: 102 | cameraPosition.setCoords(0, 300, -300); 103 | camera.setYaw(0); 104 | camera.setPitch(0.38); 105 | renderer.setScene(testLoadScene).render(); 106 | break; 107 | } 108 | }); 109 | 110 | document.onmousemove =(e => { 111 | let posX = e.clientX; 112 | let posY = e.clientY; 113 | let percX = posX / window.innerWidth; 114 | let percY = posY / window.innerHeight; 115 | 116 | let alpha = Utils.degToRad((percX - 0.5) * 360); 117 | let beta = Utils.degToRad((percY - 0.5) * 360); 118 | camera.setYaw(alpha); 119 | camera.setPitch(beta); 120 | }); 121 | 122 | renderFn(); -------------------------------------------------------------------------------- /js/scenes/CastleScene.js: -------------------------------------------------------------------------------- 1 | import Scene from "../engine/Scene.js"; 2 | import Object3D from "../engine/Object3D.js"; 3 | import Point from "../engine/Point.js"; 4 | import PyramidSquare from "../geometries/PyramidSquare.js"; 5 | import RegularPrism from "../geometries/RegularPrism.js"; 6 | import SpotLight from "../engine/Light/SpotLight.js"; 7 | 8 | /** 9 | * Scene with a castle. 10 | */ 11 | class CastleScene extends Scene { 12 | 13 | /** 14 | * Constructor 15 | * 16 | * @param {number} x 17 | * @param {number} y 18 | * @param {number} z 19 | * @param {Camera} camera 20 | * @param {object} options 21 | */ 22 | constructor(x, y, z, options = {}) { 23 | super({ 24 | id: 'castle_scene', 25 | name: 'Castle' 26 | }); 27 | 28 | let size = 1000; 29 | let high = size * 0.7; 30 | 31 | if (options !== null) { 32 | if ('size' in options && options.size > 0) { 33 | size = options.size; 34 | } 35 | 36 | if ('high' in options && options.high > 0) { 37 | high = options.high; 38 | } 39 | } 40 | 41 | let towerDimension = { 42 | h: high, 43 | w: size * 0.6, 44 | d: size * 0.6 45 | }; 46 | 47 | let towerPositions = { 48 | tower_1: { 49 | id: 1, 50 | position: { 51 | x: - (x + size / 2), 52 | y: y, 53 | z: - (z + size / 2) 54 | }, 55 | dimension: towerDimension 56 | }, 57 | tower_2: { 58 | id: 2, 59 | position: { 60 | x: x + size / 2, 61 | y: y, 62 | z: - (z + size / 2) 63 | }, 64 | dimension: towerDimension 65 | }, 66 | tower_3: { 67 | id: 3, 68 | position: { 69 | x: - (x + size / 2), 70 | y: y, 71 | z: z + size / 2 72 | }, 73 | dimension: towerDimension 74 | }, 75 | tower_4: { 76 | id: 4, 77 | position: { 78 | x: x + size / 2, 79 | y: y, 80 | z: z + size / 2 81 | }, 82 | dimension: towerDimension 83 | } 84 | }; 85 | let wallDimension = { 86 | h: towerDimension.h - towerDimension.h * 0.4, 87 | w: size - towerDimension.w, 88 | d: towerDimension.d - towerDimension.d * 0.9 89 | }; 90 | 91 | for (let key in towerPositions) { 92 | this.makeTower(towerPositions[key].id, towerPositions[key].position, towerPositions[key].dimension); 93 | } 94 | 95 | this 96 | .addObject(new Object3D({ 97 | id: 'wall_tower_1_2', 98 | position: new Point( 99 | + towerPositions.tower_1.position.x / 2 + towerPositions.tower_2.position.x / 2, 100 | wallDimension.h / 2 - towerDimension.h / 2 + y, 101 | (towerPositions.tower_1.position.z) + (wallDimension.d / 2) - (towerDimension.w / 2) 102 | ), 103 | geometry: new RegularPrism( 104 | wallDimension.h, 105 | wallDimension.w + x * 2, 106 | wallDimension.d 107 | ), 108 | options: { 109 | drawPoints: false, 110 | drawNormals: false, 111 | rgbaColor: { 112 | r: 255, 113 | g: 175, 114 | b: 67, 115 | a: 1 116 | } 117 | } 118 | })) 119 | .addObject(new Object3D({ 120 | id: 'wall_tower_1_3', 121 | position: new Point( 122 | (towerPositions.tower_1.position.x) + (wallDimension.d / 2) - (towerDimension.w / 2), 123 | wallDimension.h / 2 - towerDimension.h / 2 + y, 124 | 0, 125 | ), 126 | geometry: new RegularPrism( 127 | wallDimension.h, 128 | wallDimension.d, 129 | wallDimension.w + z * 2 130 | ), 131 | options: { 132 | drawPoints: false, 133 | drawNormals: false, 134 | rgbaColor: { 135 | r: 255, 136 | g: 175, 137 | b: 67, 138 | a: 1 139 | } 140 | } 141 | })) 142 | .addObject(new Object3D({ 143 | id: 'wall_tower_3_4', 144 | position: new Point( 145 | + towerPositions.tower_3.position.x / 2 + towerPositions.tower_4.position.x / 2, 146 | wallDimension.h / 2 - towerDimension.h / 2 + y, 147 | (towerPositions.tower_3.position.z) - (wallDimension.d / 2) + (towerDimension.w / 2) 148 | ), 149 | geometry: new RegularPrism( 150 | wallDimension.h, 151 | wallDimension.w + x * 2, 152 | wallDimension.d 153 | ), 154 | options: { 155 | drawPoints: false, 156 | drawNormals: false, 157 | rgbaColor: { 158 | r: 255, 159 | g: 175, 160 | b: 67, 161 | a: 1 162 | } 163 | } 164 | })) 165 | .addObject(new Object3D({ 166 | id: 'wall_tower_2_4', 167 | position: new Point( 168 | (towerPositions.tower_2.position.x) - (wallDimension.d / 2) + (towerDimension.w / 2), 169 | wallDimension.h / 2 - towerDimension.h / 2 + y, 170 | 0 171 | ), 172 | geometry: new RegularPrism( 173 | wallDimension.h, 174 | wallDimension.d, 175 | wallDimension.w + z * 2 176 | ), 177 | options: { 178 | drawPoints: false, 179 | drawNormals: false, 180 | rgbaColor: { 181 | r: 255, 182 | g: 175, 183 | b: 67, 184 | a: 1 185 | } 186 | } 187 | })) 188 | .addLight(new SpotLight({ 189 | id: 'light1', 190 | position: new Point(0, 1500, -1400) 191 | })); 192 | } 193 | 194 | makeTower(id, position, dimension) { 195 | this 196 | .addObject(new Object3D({ 197 | id: 'tower_' + id, 198 | position: new Point(position.x, position.y, position.z), 199 | geometry: new RegularPrism(dimension.h, dimension.w, dimension.d), 200 | options: { 201 | drawPoints: false, 202 | drawNormals: false, 203 | rgbaColor: { 204 | r: 255, 205 | g: 193, 206 | b: 7, 207 | a: 1 208 | } 209 | } 210 | })) 211 | .addObject(new Object3D({ 212 | id: 'tower_roof_' + id, 213 | position: new Point(position.x, position.y + dimension.h, position.z), 214 | geometry: new PyramidSquare(dimension.h, dimension.w, dimension.d), 215 | options: { 216 | drawPoints: false, 217 | drawNormals: false, 218 | rgbaColor: { 219 | r: 244, 220 | g: 67, 221 | b: 54, 222 | a: 1 223 | } 224 | } 225 | })); 226 | 227 | return this; 228 | } 229 | 230 | } 231 | 232 | export default CastleScene; -------------------------------------------------------------------------------- /js/scenes/DefaultScene.js: -------------------------------------------------------------------------------- 1 | import Scene from "../engine/Scene.js"; 2 | import Object3D from "../engine/Object3D.js"; 3 | import SpotLight from "../engine/Light/SpotLight.js"; 4 | import Point from "../engine/Point.js"; 5 | import Cube from "../geometries/Cube.js"; 6 | import Plane from "../geometries/Plane.js"; 7 | import Sphere from "../geometries/Sphere.js"; 8 | import PyramidSquare from "../geometries/PyramidSquare.js"; 9 | import Cone from "../geometries/Cone.js"; 10 | import Generate from '../engine/reader/Generate.js'; 11 | 12 | class DefaultScene extends Scene 13 | { 14 | constructor() { 15 | super({ 16 | id: 'scn1', 17 | name: 'Testing Scene' 18 | }); 19 | 20 | this 21 | .addObject(new Object3D({ 22 | id: 'plane1', 23 | position: new Point(-1000, -50, -500), 24 | geometry: new Plane(2000, 20), 25 | options: { 26 | rgbaColor: { 27 | r: 100, 28 | g: 104, 29 | b: 139, 30 | a: 1 31 | }, 32 | backfaceCulling: false 33 | } 34 | })) 35 | .addObject(new Object3D({ 36 | id: 'cube1', 37 | position: new Point(0, 150, 300), 38 | geometry: new Cube(300, false), 39 | options: { 40 | rgbaColor: { 41 | r: 255, 42 | g: 131, 43 | b: 131, 44 | a: 1 45 | }, 46 | drawPoints: false 47 | } 48 | })) 49 | .addObject(new Object3D({ 50 | id: 'sphere1', 51 | position: new Point(440, 900, 500), 52 | geometry: new Sphere(200, 20, false), 53 | options: { 54 | rgbaColor: { 55 | r: 100, 56 | g: 10, 57 | b: 131, 58 | a: 1 59 | }, 60 | drawNormals: false 61 | } 62 | })) 63 | .addObject(new Object3D({ 64 | id: 'sphere2', 65 | position: new Point(-380, 400, 100), 66 | geometry: new Sphere(100, 20, false), 67 | options: { 68 | rgbaColor: { 69 | r: 255, 70 | g: 0, 71 | b: 0, 72 | a: 1 73 | } 74 | } 75 | })) 76 | .addObject(new Object3D({ 77 | id: 'pyramid_square', 78 | position: new Point(450, 150, 190), 79 | geometry: new PyramidSquare(300, 300, 300), 80 | options: { 81 | rgbaColor: { 82 | r: 100, 83 | g: 100, 84 | b: 200, 85 | a: 1 86 | } 87 | } 88 | })) 89 | .addObject(new Object3D({ 90 | id: 'cone1', 91 | position: new Point(400, 0, 700), 92 | geometry: new Cone(200, 600, 20) 93 | })) 94 | .addObject(new Object3D({ 95 | id: 'cone2', 96 | position: new Point(400, 0, -100), 97 | geometry: new Cone(100, 200, 20) 98 | })) 99 | .addObject(new Generate(-450, 0, -100, 'resources/obj/teapot.obj', 100, { 100 | rgbaColor: { 101 | r: 255, 102 | g: 193, 103 | b: 7, 104 | a: 1 105 | } 106 | })) 107 | .addLight(new SpotLight({ 108 | id: 'light1', 109 | position: new Point(0, 500, 0) 110 | })) 111 | 112 | this.alpha = 0; 113 | this.radius = 600; 114 | this.light = this.getLight('light1'); 115 | } 116 | beforeRender() { 117 | this.alpha += 15; 118 | if (this.alpha > 360) { 119 | this.alpha -= 360; 120 | } 121 | this.light.setPosition(new Point(Math.cos(this.alpha * Math.PI / 180) * this.radius, 500, Math.sin(this.alpha * Math.PI / 180) * this.radius)); 122 | return this; 123 | } 124 | } 125 | 126 | export default DefaultScene; -------------------------------------------------------------------------------- /js/scenes/SpheresScene.js: -------------------------------------------------------------------------------- 1 | import Scene from "../engine/Scene.js"; 2 | import Object3D from "../engine/Object3D.js"; 3 | import Point from "../engine/Point.js"; 4 | import Sphere from "../geometries/Sphere.js"; 5 | import SpotLight from "../engine/Light/SpotLight.js"; 6 | 7 | /** 8 | * Scene with spheres. 9 | */ 10 | class SpheresScene extends Scene { 11 | 12 | /** 13 | * Constructor 14 | * 15 | * @param {number} x 16 | * @param {number} y 17 | * @param {number} z 18 | * @param {Camera} camera 19 | * @param {object} options 20 | */ 21 | constructor() { 22 | super({ 23 | id: 'spheres_scene', 24 | name: 'Spheres' 25 | }); 26 | 27 | this 28 | .addObject(new Object3D({ 29 | id: 'sphere1', 30 | position: new Point(250, 400, -200), 31 | geometry: new Sphere(200, 18), 32 | options: { 33 | rgbaColor: { 34 | r: 100, 35 | g: 10, 36 | b: 131, 37 | a: 1 38 | }, 39 | drawNormals: true 40 | } 41 | })) 42 | .addObject(new Object3D({ 43 | id: 'sphere2', 44 | position: new Point(-250, 400, -200), 45 | geometry: new Sphere(200, 30), 46 | options: { 47 | rgbaColor: { 48 | r: 100, 49 | g: 130, 50 | b: 1, 51 | a: 1 52 | }, 53 | drawNormals: true 54 | } 55 | })) 56 | .addLight(new SpotLight({ 57 | id: 'light1', 58 | position: new Point(0, 600, -500) 59 | })) 60 | 61 | return this; 62 | } 63 | 64 | } 65 | 66 | export default SpheresScene; -------------------------------------------------------------------------------- /js/scenes/TestLoadScene.js: -------------------------------------------------------------------------------- 1 | import Scene from "../engine/Scene.js"; 2 | import Point from "../engine/Point.js"; 3 | import SpotLight from "../engine/Light/SpotLight.js"; 4 | 5 | // Objects. 6 | import Generate from '../engine/reader/Generate.js'; 7 | 8 | /** 9 | * Scene with a castle. 10 | */ 11 | class TestLoadScene extends Scene { 12 | constructor() { 13 | super({ 14 | id: 'test_load_obj', 15 | name: 'load_obj' 16 | }); 17 | let cube = new Generate(0, 0, 0, 'resources/obj/cube.obj'); 18 | let diamond = new Generate(150, 150, 150, 'resources/obj/diamond.obj'); 19 | let icosahedron = new Generate(-150, -150, -150, 'resources/obj/icosahedron.obj'); 20 | let teapot = new Generate(-200, 200, -200, 'resources/obj/teapot.obj', 50); 21 | let shuttle = new Generate(200, 200, -200, 'resources/obj/shuttle.obj', 10); 22 | 23 | this.addObject(cube); 24 | this.addObject(diamond); 25 | this.addObject(icosahedron); 26 | this.addObject(teapot); 27 | this.addObject(shuttle); 28 | this.addLight(new SpotLight({ 29 | id: 'light1', 30 | position: new Point(0, 500, 0) 31 | })); 32 | } 33 | } 34 | 35 | export default TestLoadScene; -------------------------------------------------------------------------------- /js/scenes/WavesScene.js: -------------------------------------------------------------------------------- 1 | import Scene from "../engine/Scene.js"; 2 | import Object3D from "../engine/Object3D.js"; 3 | import Point from "../engine/Point.js"; 4 | import Utils from "../engine/Utils.js"; 5 | import Plane from "../geometries/Plane.js"; 6 | import SpotLight from "../engine/Light/SpotLight.js"; 7 | 8 | /** 9 | * Scene with a castle. 10 | */ 11 | class WavesScene extends Scene { 12 | constructor() { 13 | super({ 14 | id: 'waves', 15 | name: 'Waves' 16 | }); 17 | 18 | this.spaces = 30; 19 | 20 | let size = 1550; 21 | 22 | this.addObject(new Object3D({ 23 | id: 'plane1', 24 | position: new Point(-size / 2, 200, -1500), 25 | geometry: new Plane(size, this.spaces), 26 | options: { 27 | rgbaColor: { 28 | r: 100, 29 | g: 100, 30 | b: 255, 31 | a: 1 32 | }, 33 | drawPoints: false, 34 | drawNormals: false 35 | } 36 | })) 37 | .addLight(new SpotLight({ 38 | id: 'light1', 39 | position: new Point(0, 300, -800), 40 | options: { 41 | rgbaColor: { 42 | r: 255, 43 | g: 255, 44 | b: 255, 45 | a: 0.3 46 | } 47 | } 48 | })); 49 | 50 | this.alpha = 0; 51 | let obj = this.getObject('plane1'); 52 | this.points = obj.getGeometry().getPoints(); 53 | 54 | obj.rotate('y', 90, new Point(0, 200, size / 2)) 55 | } 56 | beforeRender() { 57 | if (this.alpha > 360) { 58 | this.alpha -= 360; 59 | } else { 60 | this.alpha += 4; 61 | } 62 | 63 | for (let i = 0; i <= this.spaces; i++) { 64 | for (let j = 0; j <= this.spaces; j++) { 65 | let p = this.points[i * (this.spaces + 1) + j]; 66 | p.setCoords(p.getX(), Math.cos(Utils.degToRad(this.alpha - i * 20)) * 50, p.getZ()); 67 | } 68 | } 69 | } 70 | } 71 | export default WavesScene; -------------------------------------------------------------------------------- /resources/obj/cube.obj: -------------------------------------------------------------------------------- 1 | 2 | # cube.obj 3 | # 4 | 5 | o cube 6 | 7 | v -0.500000 -0.500000 0.500000 8 | v 0.500000 -0.500000 0.500000 9 | v -0.500000 0.500000 0.500000 10 | v 0.500000 0.500000 0.500000 11 | v -0.500000 0.500000 -0.500000 12 | v 0.500000 0.500000 -0.500000 13 | v -0.500000 -0.500000 -0.500000 14 | v 0.500000 -0.500000 -0.500000 15 | 16 | vt 0.000000 0.000000 17 | vt 1.000000 0.000000 18 | vt 0.000000 1.000000 19 | vt 1.000000 1.000000 20 | 21 | vn 0.000000 0.000000 1.000000 22 | vn 0.000000 1.000000 0.000000 23 | vn 0.000000 0.000000 -1.000000 24 | vn 0.000000 -1.000000 0.000000 25 | vn 1.000000 0.000000 0.000000 26 | vn -1.000000 0.000000 0.000000 27 | 28 | g cube 29 | 30 | s 1 31 | f 1/1/1 2/2/1 3/3/1 32 | f 3/3/1 2/2/1 4/4/1 33 | s 2 34 | f 3/1/2 4/2/2 5/3/2 35 | f 5/3/2 4/2/2 6/4/2 36 | s 3 37 | f 5/4/3 6/3/3 7/2/3 38 | f 7/2/3 6/3/3 8/1/3 39 | s 4 40 | f 7/1/4 8/2/4 1/3/4 41 | f 1/3/4 8/2/4 2/4/4 42 | s 5 43 | f 2/1/5 8/2/5 4/3/5 44 | f 4/3/5 8/2/5 6/4/5 45 | s 6 46 | f 7/1/6 1/2/6 5/3/6 47 | f 5/3/6 1/2/6 3/4/6 48 | -------------------------------------------------------------------------------- /resources/obj/diamond.obj: -------------------------------------------------------------------------------- 1 | # diamond.obj 2 | 3 | o diamond 4 | 5 | v 0.000000 0.000000 0.780000 6 | v 0.450000 0.450000 0.000000 7 | v 0.450000 -0.450000 0.000000 8 | v -0.450000 -0.450000 0.000000 9 | v -0.450000 0.450000 0.000000 10 | v 0.000000 0.000000 -0.780000 11 | 12 | f 1 2 3 13 | f 1 3 4 14 | f 1 4 5 15 | f 1 5 2 16 | f 6 5 4 17 | f 6 4 3 18 | f 6 3 2 19 | f 6 2 1 20 | f 6 1 5 21 | -------------------------------------------------------------------------------- /resources/obj/icosahedron.obj: -------------------------------------------------------------------------------- 1 | 2 | g icosahedron 3 | 4 | v 0 -0.525731 0.850651 5 | v 0.850651 0 0.525731 6 | v 0.850651 0 -0.525731 7 | v -0.850651 0 -0.525731 8 | v -0.850651 0 0.525731 9 | v -0.525731 0.850651 0 10 | v 0.525731 0.850651 0 11 | v 0.525731 -0.850651 0 12 | v -0.525731 -0.850651 0 13 | v 0 -0.525731 -0.850651 14 | v 0 0.525731 -0.850651 15 | v 0 0.525731 0.850651 16 | 17 | f 2 3 7 18 | f 2 8 3 19 | f 4 5 6 20 | f 5 4 9 21 | f 7 6 12 22 | f 6 7 11 23 | f 10 11 3 24 | f 11 10 4 25 | f 8 9 10 26 | f 9 8 1 27 | f 12 1 2 28 | f 1 12 5 29 | f 7 3 11 30 | f 2 7 12 31 | f 4 6 11 32 | f 6 5 12 33 | f 3 8 10 34 | f 8 2 1 35 | f 4 10 9 36 | f 5 9 1 37 | -------------------------------------------------------------------------------- /resources/obj/shuttle.obj: -------------------------------------------------------------------------------- 1 | # Viewpoint Datalabs International, Inc. Copyright 1996 2 | 3 | o shuttle 4 | 5 | g 6 | v 3.070224 -0.119728 0.996443 7 | v 5.942016 -0.012019 4.157199 8 | v 6.614015 -0.063428 4.157199 9 | v 5.759114 0.000000 1.664500 10 | v 3.070224 -0.449143 0.929434 11 | v 5.000295 -0.539011 1.315104 12 | v 3.070224 -0.604752 0.872464 13 | v 3.070224 -0.866525 0.730690 14 | v 3.070224 -0.959007 0.650256 15 | v 3.070224 -1.053631 0.163277 16 | v 2.983248 -1.080021 -0.880639 17 | v 6.130317 -1.100022 -1.106943 18 | v 3.739287 -4.334102 -0.876958 19 | v 4.400283 -4.682100 -0.952940 20 | v 3.038248 -4.334102 -0.811319 21 | v 3.180259 -4.550090 -0.921939 22 | v 2.700250 -4.334102 -0.947940 23 | v 0.840214 -2.480049 -1.050312 24 | v 1.208789 -1.060728 0.203820 25 | v 1.208789 -1.054148 0.411073 26 | v 1.208789 -0.958092 0.610367 27 | v 1.208789 -0.875165 0.685964 28 | v 1.208789 -0.621528 0.854704 29 | v 1.208789 -0.467365 0.922276 30 | v -4.649089 -1.039587 0.209476 31 | v -4.649345 -0.922345 0.432259 32 | v -4.649708 -0.652575 0.753550 33 | v -4.999902 -1.012545 0.094530 34 | v -4.999240 -0.870266 0.347384 35 | v -4.999321 -0.802315 0.416133 36 | v -4.906714 -0.620194 0.686502 37 | v -4.999759 -0.491153 0.805206 38 | v -5.568033 -0.119200 0.568687 39 | v -5.349121 -0.814175 0.247113 40 | v -5.348800 -0.938377 -0.030175 41 | v -6.499984 -0.676000 -0.433500 42 | v -6.499984 -0.610000 -0.164800 43 | v -6.499984 -0.240000 0.109600 44 | v -7.649984 0.000000 -0.620000 45 | v 1.209237 -1.080021 -1.321617 46 | v 3.070224 0.119728 0.996443 47 | v 3.093016 0.040804 1.276300 48 | v 6.614015 0.063428 4.157199 49 | v 3.070224 0.449143 0.929434 50 | v 5.000295 0.539011 1.315104 51 | v 3.070224 0.604752 0.872464 52 | v 3.070224 0.866525 0.730690 53 | v 5.000295 1.149023 1.260104 54 | v 3.070224 0.959007 0.650256 55 | v 3.070224 1.053627 0.449897 56 | v 5.000295 1.428028 0.442095 57 | v 3.070224 1.053631 0.163277 58 | v 2.983248 1.080021 -0.880639 59 | v 5.000295 1.302926 -1.259946 60 | v 3.739287 4.334102 -0.876958 61 | v 4.400283 4.682100 -0.952940 62 | v 3.038248 4.334102 -0.811319 63 | v 3.180259 4.550090 -0.921939 64 | v 1.209237 1.080021 -0.636414 65 | v 2.700250 4.334102 -0.947940 66 | v 0.169216 1.990039 -1.063281 67 | v 1.208789 1.060728 0.203820 68 | v 1.208789 1.054148 0.411073 69 | v 1.208789 0.958092 0.610367 70 | v 1.208789 0.875165 0.685964 71 | v 1.208789 0.621528 0.854704 72 | v 1.208789 0.467365 0.922276 73 | v -4.649089 1.039587 0.209476 74 | v -4.649345 0.922345 0.432259 75 | v -4.649708 0.652575 0.753550 76 | v -4.649856 0.514670 0.885149 77 | v -4.649964 0.160748 0.994500 78 | v -4.999902 1.012545 0.094530 79 | v -4.999240 0.870266 0.347384 80 | v -4.999321 0.802315 0.416133 81 | v -4.999759 0.491153 0.805206 82 | v -4.999948 0.160720 0.980689 83 | v -5.299752 0.147914 0.811038 84 | v -5.349121 0.814175 0.247113 85 | v -5.348800 0.938377 -0.030175 86 | v -6.499984 0.676000 -0.433500 87 | v -6.499931 0.693962 -0.748535 88 | v -6.499984 0.610000 -0.164800 89 | v -6.499984 0.523000 -0.048800 90 | v -6.499984 0.240000 0.109600 91 | v 1.209237 1.080021 -1.321617 92 | v -5.568033 0.119200 0.568687 93 | v -5.299752 -0.147914 0.811038 94 | v -4.999948 -0.160720 0.980689 95 | v -4.649964 -0.160748 0.994500 96 | v 1.208789 -0.130179 0.996071 97 | v 1.208789 0.130179 0.996071 98 | v 3.093016 -0.040804 1.276300 99 | v 5.942016 0.012019 4.157199 100 | v 7.043714 0.000000 4.157199 101 | v 4.998233 -0.130896 1.193100 102 | v 5.171283 -1.310384 -1.055942 103 | v 6.130317 1.100022 -1.106943 104 | v 2.983248 -1.080021 -1.351649 105 | v 2.983248 1.080021 -1.351649 106 | v -6.499931 -0.693962 -0.748535 107 | v -4.999902 -1.000020 -0.943979 108 | v 0.169216 -1.990039 -1.063281 109 | v 5.000295 -1.510030 0.750093 110 | v 5.000295 -0.874017 1.399122 111 | v 5.000295 -1.149023 1.260104 112 | v 5.000295 0.874017 1.399122 113 | v -7.074984 -0.304058 -0.264426 114 | v -7.074984 0.139529 -0.169387 115 | v -7.074984 0.304058 -0.264426 116 | v -7.074957 0.403450 -0.684268 117 | v -7.074984 0.393008 -0.495246 118 | v -7.074984 0.354637 -0.334026 119 | v -7.074984 0.057454 -0.155083 120 | v -7.074984 -0.354637 -0.334026 121 | v -7.074984 -0.393008 -0.495246 122 | v -7.074957 -0.403450 -0.684268 123 | v -7.074984 -0.139529 -0.169387 124 | v -7.074984 -0.057454 -0.155083 125 | v 5.257180 -0.244260 -0.448877 126 | v 5.275361 -0.389797 -0.446328 127 | v 5.534085 -0.255527 -0.410058 128 | v 5.858724 -0.171973 -0.364548 129 | v 6.246687 -0.127423 -0.310161 130 | v 6.245811 -0.209802 -0.310283 131 | v 5.957494 -0.242908 -0.350702 132 | v 5.684797 -0.367023 -0.388930 133 | v 5.030259 -0.310424 -0.039389 134 | v 5.218888 -0.403501 -0.175729 135 | v 5.254566 -0.476272 -0.297997 136 | v 5.497149 -0.409135 -0.146573 137 | v 5.811742 -0.367356 -0.029404 138 | v 6.194348 -0.345081 0.063191 139 | v 6.203377 -0.386271 -0.007583 140 | v 5.919040 -0.402825 -0.076394 141 | v 5.661265 -0.464884 -0.221067 142 | v 5.030257 -0.815056 -0.039376 143 | v 5.218887 -0.721987 -0.175721 144 | v 5.254566 -0.649223 -0.297993 145 | v 5.497147 -0.716354 -0.146565 146 | v 5.811740 -0.758129 -0.029394 147 | v 6.194347 -0.780403 0.063202 148 | v 6.203376 -0.739216 -0.007574 149 | v 5.919039 -0.722663 -0.076386 150 | v 5.661264 -0.660610 -0.221062 151 | v 5.533661 -0.562752 -0.410117 152 | v 5.257178 -0.881243 -0.448860 153 | v 5.275359 -0.735706 -0.446319 154 | v 5.534083 -0.869976 -0.410042 155 | v 5.858722 -0.953530 -0.364528 156 | v 6.246684 -0.998080 -0.310138 157 | v 6.245809 -0.915701 -0.310265 158 | v 5.957492 -0.882595 -0.350685 159 | v 5.684796 -0.758480 -0.388920 160 | v 5.151601 -0.815102 -0.904963 161 | v 5.295470 -0.722016 -0.722016 162 | v 5.296154 -0.649239 -0.594654 163 | v 5.571022 -0.716382 -0.673535 164 | v 5.905705 -0.758165 -0.699682 165 | v 6.299025 -0.780442 -0.683500 166 | v 6.288245 -0.739248 -0.612975 167 | v 5.995947 -0.722692 -0.625000 168 | v 5.708329 -0.660628 -0.556788 169 | v 5.295474 -0.403530 -0.722041 170 | v 5.296155 -0.476288 -0.594668 171 | v 5.571025 -0.409163 -0.673559 172 | v 5.905710 -0.367392 -0.699712 173 | v 6.299029 -0.345120 -0.683534 174 | v 6.288249 -0.386303 -0.613002 175 | v 5.995951 -0.402854 -0.625025 176 | v 5.708331 -0.464902 -0.556803 177 | v 5.218888 0.403501 -0.175729 178 | v 5.257180 0.244260 -0.448877 179 | v 5.254566 0.476272 -0.297997 180 | v 5.275361 0.389797 -0.446328 181 | v 5.497149 0.409135 -0.146573 182 | v 5.534085 0.255527 -0.410058 183 | v 5.811742 0.367356 -0.029404 184 | v 5.858724 0.171973 -0.364548 185 | v 6.194348 0.345081 0.063191 186 | v 6.246687 0.127423 -0.310161 187 | v 6.203377 0.386271 -0.007583 188 | v 6.245811 0.209802 -0.310283 189 | v 5.919040 0.402825 -0.076394 190 | v 5.957494 0.242908 -0.350702 191 | v 5.661265 0.464884 -0.221067 192 | v 5.684797 0.367023 -0.388930 193 | v 5.218887 0.721987 -0.175721 194 | v 5.254566 0.649223 -0.297993 195 | v 5.497147 0.716354 -0.146565 196 | v 5.811740 0.758129 -0.029394 197 | v 6.194347 0.780403 0.063202 198 | v 6.203376 0.739216 -0.007574 199 | v 5.919039 0.722663 -0.076386 200 | v 5.661264 0.660610 -0.221062 201 | v 5.257178 0.881243 -0.448860 202 | v 5.275359 0.735706 -0.446319 203 | v 5.534083 0.869976 -0.410042 204 | v 5.858722 0.953530 -0.364528 205 | v 6.246684 0.998080 -0.310138 206 | v 6.245809 0.915701 -0.310265 207 | v 5.957492 0.882595 -0.350685 208 | v 5.684796 0.758480 -0.388920 209 | v 5.533661 0.562752 -0.410117 210 | v 5.295470 0.722016 -0.722016 211 | v 5.296154 0.649239 -0.594654 212 | v 5.571022 0.716382 -0.673535 213 | v 5.905705 0.758165 -0.699682 214 | v 6.299025 0.780442 -0.683500 215 | v 6.288245 0.739248 -0.612975 216 | v 5.995947 0.722692 -0.625000 217 | v 5.708329 0.660628 -0.556788 218 | v 5.295474 0.403530 -0.722041 219 | v 5.296155 0.476288 -0.594668 220 | v 5.571025 0.409163 -0.673559 221 | v 5.905710 0.367392 -0.699712 222 | v 6.299029 0.345120 -0.683534 223 | v 6.288249 0.386303 -0.613002 224 | v 5.995951 0.402854 -0.625025 225 | v 5.708331 0.464902 -0.556803 226 | v 5.165639 -0.318491 0.637328 227 | v 5.166101 -0.159250 0.913146 228 | v 4.998497 -0.252327 1.074635 229 | v 5.183997 -0.172954 0.637297 230 | v 5.184248 -0.086480 0.787078 231 | v 5.445252 -0.307224 0.636859 232 | v 5.445698 -0.153617 0.902920 233 | v 5.773065 -0.390779 0.636310 234 | v 5.773632 -0.195395 0.974730 235 | v 6.164821 -0.435329 0.635652 236 | v 6.165453 -0.217671 1.012654 237 | v 6.163937 -0.352950 0.635654 238 | v 6.164450 -0.176480 0.941314 239 | v 5.872800 -0.319843 0.636142 240 | v 5.873264 -0.159926 0.913131 241 | v 5.597437 -0.195729 0.636604 242 | v 5.597722 -0.097867 0.806108 243 | v 5.444824 0.000000 0.636860 244 | v 5.166102 0.159236 0.913155 245 | v 5.184248 0.086472 0.787083 246 | v 5.445698 0.153603 0.902928 247 | v 5.773632 0.195378 0.974740 248 | v 6.165453 0.217651 1.012665 249 | v 6.164450 0.176464 0.941323 250 | v 5.873265 0.159912 0.913140 251 | v 5.597722 0.097858 0.806113 252 | v 5.165639 0.318491 0.637345 253 | v 4.997765 0.504639 0.637636 254 | v 5.183997 0.172954 0.637307 255 | v 5.445252 0.307224 0.636875 256 | v 5.773065 0.390779 0.636330 257 | v 6.164821 0.435329 0.635675 258 | v 6.163937 0.352950 0.635673 259 | v 5.872800 0.319843 0.636159 260 | v 5.597437 0.195729 0.636614 261 | v 5.165176 0.159265 0.361518 262 | v 4.997031 0.252350 0.200598 263 | v 5.183746 0.086488 0.487521 264 | v 5.444806 0.153631 0.370806 265 | v 5.772497 0.195413 0.297899 266 | v 6.164188 0.217691 0.258662 267 | v 6.163424 0.176496 0.330003 268 | v 5.872335 0.159941 0.359162 269 | v 5.597153 0.097876 0.467105 270 | v 5.165176 -0.159221 0.361493 271 | v 4.997031 -0.252281 0.200558 272 | v 5.183746 -0.086464 0.487507 273 | v 5.444806 -0.153589 0.370782 274 | v 5.772497 -0.195360 0.297868 275 | v 6.164188 -0.217631 0.258628 276 | v 6.163424 -0.176448 0.329975 277 | v 5.872335 -0.159897 0.359136 278 | v 5.597153 -0.097850 0.467090 279 | v 5.090927 -1.067391 -0.472156 280 | v 5.171283 1.310384 -1.055942 281 | v 5.151606 0.310470 -0.905003 282 | v 5.151606 -0.310470 -0.905003 283 | v 5.030257 0.815056 -0.039376 284 | v 5.030259 0.310424 -0.039389 285 | v 5.090930 -0.058113 -0.472183 286 | v 5.090930 0.058113 -0.472183 287 | v 5.000295 -1.210004 0.173074 288 | v 5.000295 1.210004 0.173074 289 | v 5.000295 -1.428028 0.442095 290 | v 4.997764 -0.504639 0.637610 291 | v 4.998497 0.252304 1.074648 292 | v 4.998233 0.130896 1.193100 293 | v 5.000295 1.510030 0.750093 294 | v 5.151601 0.815102 -0.904963 295 | v 5.090927 1.067391 -0.472156 296 | v 3.070224 -1.053627 0.449897 297 | v -5.349205 0.737229 0.323968 298 | v -5.349205 -0.737229 0.323968 299 | v -5.349476 -0.470935 0.566062 300 | v -6.499984 -0.098825 0.133439 301 | v -6.499984 0.098825 0.133439 302 | v -6.499984 -0.523000 -0.048800 303 | v -5.349476 0.470935 0.566062 304 | v -4.999902 1.000020 -0.943979 305 | v 0.840214 2.480049 -1.050312 306 | v 1.209237 -1.080021 -0.636414 307 | v 3.804262 4.682100 -0.938960 308 | v 5.000295 -1.302926 -1.259946 309 | v 3.804262 -4.682100 -0.938960 310 | v -4.649856 -0.514670 0.885149 311 | v -4.999492 0.681710 0.569242 312 | v -4.649417 0.860391 0.497003 313 | v -4.906714 0.620194 0.686502 314 | v -4.649417 -0.860391 0.497003 315 | v -4.999492 -0.681710 0.569242 316 | # 310 vertices 317 | 318 | # 0 vertex parms 319 | 320 | # 0 texture vertices 321 | 322 | # 0 normals 323 | 324 | g windows 325 | usemtl glass 326 | s 1 327 | f 310 32 294 328 | f 76 308 306 329 | f 294 88 33 330 | f 310 31 32 331 | f 88 294 32 332 | f 87 33 88 78 333 | f 87 78 298 334 | f 298 76 306 335 | f 298 78 76 336 | g tail 337 | usemtl bone 338 | s 4 339 | f 95 3 96 4 340 | f 4 287 43 95 341 | f 94 2 3 43 342 | f 3 2 93 96 343 | f 41 1 93 42 344 | f 41 42 287 345 | f 43 3 95 346 | f 287 42 94 43 347 | f 42 93 2 94 348 | f 96 93 1 349 | g rearbody 350 | s 6 351 | f 275 98 54 352 | f 96 223 286 287 353 | f 97 277 155 354 | f 276 281 280 277 355 | f 276 275 289 356 | f 283 282 128 279 357 | f 283 290 275 358 | f 257 51 248 359 | f 282 303 97 360 | f 96 6 106 361 | f 303 12 97 362 | f 104 285 223 363 | f 97 155 274 364 | f 284 282 266 365 | f 286 288 287 366 | f 137 128 282 367 | f 283 279 278 368 | f 248 288 286 369 | f 6 105 106 370 | f 275 54 283 371 | f 284 266 285 372 | f 96 287 4 373 | f 284 285 104 374 | f 248 51 288 375 | f 283 278 290 376 | f 274 137 282 377 | f 289 275 290 378 | f 97 12 98 275 379 | f 48 107 45 380 | f 96 106 104 381 | f 282 283 257 266 382 | f 97 275 276 277 383 | f 104 223 96 384 | f 257 283 51 385 | f 97 274 282 386 | f 128 280 281 279 387 | f 287 288 48 388 | f 287 48 45 389 | g body 390 | s 7 391 | f 309 31 310 392 | f 294 33 295 393 | f 108 118 39 394 | f 80 79 74 73 395 | f 49 47 48 396 | f 5 1 91 24 397 | f 10 291 20 19 398 | f 294 295 38 399 | f 78 77 76 400 | f 81 82 111 112 401 | f 65 66 46 47 402 | f 30 309 310 403 | f 5 105 6 404 | f 30 29 26 309 405 | f 68 62 59 299 406 | f 78 88 89 77 407 | f 118 38 295 119 408 | f 83 81 112 113 409 | f 64 65 47 49 410 | f 35 37 36 411 | f 23 8 7 412 | f 24 91 90 305 413 | f 62 52 53 59 414 | f 296 85 109 114 415 | f 79 292 75 74 416 | f 50 49 288 417 | f 22 23 27 418 | f 282 10 11 303 419 | f 293 294 297 420 | f 71 72 92 67 421 | f 112 39 113 422 | f 310 294 293 423 | f 305 90 89 424 | f 308 70 307 425 | f 296 87 298 426 | f 114 39 119 427 | f 71 77 72 428 | f 45 107 44 429 | f 8 23 22 430 | f 7 5 24 23 431 | f 287 44 41 432 | f 307 69 74 75 433 | f 92 91 1 41 434 | f 63 62 68 435 | f 28 29 34 35 436 | f 105 7 8 106 437 | f 32 89 88 438 | f 49 48 288 439 | f 82 81 299 440 | f 115 37 297 108 441 | f 113 39 110 442 | f 73 74 69 68 443 | f 29 30 293 34 444 | f 291 104 9 445 | f 22 27 309 446 | f 54 53 52 283 447 | f 83 79 80 448 | f 83 80 81 449 | f 48 47 46 107 450 | f 25 20 21 26 451 | f 301 11 10 19 452 | f 39 115 108 453 | f 306 307 75 454 | f 110 39 109 455 | f 292 298 306 456 | f 306 308 307 457 | f 70 66 65 458 | f 294 38 297 459 | f 5 6 96 460 | f 85 84 110 109 461 | f 62 63 50 52 462 | f 102 25 28 463 | f 9 106 8 464 | f 310 293 30 465 | f 70 71 66 466 | f 77 89 90 72 467 | f 66 71 67 468 | f 297 37 34 293 469 | f 106 9 104 470 | f 25 19 20 471 | f 44 107 46 472 | f 85 296 298 473 | f 117 101 36 116 474 | f 111 39 112 475 | f 307 70 65 476 | f 35 34 37 477 | f 23 305 27 478 | f 102 301 19 25 479 | f 50 288 51 480 | f 80 73 299 481 | f 84 298 292 482 | f 49 50 63 64 483 | f 32 305 89 484 | f 1 5 96 485 | f 32 31 27 305 486 | f 66 67 44 46 487 | f 296 295 33 87 488 | f 291 10 282 489 | f 81 80 299 490 | f 309 27 31 491 | f 84 85 298 492 | f 116 36 37 115 493 | f 292 79 83 84 494 | f 283 52 51 495 | f 309 26 21 22 496 | f 284 291 282 497 | f 102 36 101 498 | f 65 64 69 307 499 | f 295 296 114 119 500 | f 73 68 299 501 | f 39 116 115 502 | f 105 5 7 503 | f 23 24 305 504 | f 39 117 116 505 | f 77 71 76 506 | f 109 39 114 507 | f 297 38 118 108 508 | f 75 292 306 509 | f 39 118 119 510 | f 21 20 291 9 511 | f 9 8 22 21 512 | f 287 45 44 513 | f 71 70 308 76 514 | f 84 83 113 110 515 | f 67 92 41 44 516 | f 25 26 29 28 517 | f 104 291 284 518 | f 102 28 35 519 | f 69 64 63 68 520 | f 72 90 91 92 521 | f 52 50 51 522 | f 102 35 36 523 | g wings 524 | s 5 525 | f 16 15 17 526 | f 304 15 16 527 | f 300 57 60 528 | f 14 13 304 529 | f 59 53 55 57 530 | f 60 57 58 531 | f 18 301 103 532 | f 300 59 57 533 | f 304 13 15 534 | f 56 55 53 54 535 | f 15 13 11 301 536 | f 61 59 300 537 | f 57 55 302 538 | f 103 301 102 539 | f 17 15 301 540 | f 303 11 13 14 541 | f 58 57 302 542 | f 302 55 56 543 | f 17 301 18 544 | f 299 59 61 545 | g tiles 546 | usemtl fldkdkgrey 547 | s 3 548 | f 302 56 54 549 | f 18 103 40 550 | f 16 17 99 551 | f 86 61 300 552 | f 99 304 16 553 | f 303 14 304 554 | f 99 303 304 555 | f 17 18 99 556 | f 302 54 100 557 | f 58 302 100 558 | f 100 86 300 559 | f 18 40 99 560 | f 100 60 58 561 | f 100 300 60 562 | f 101 117 111 82 563 | f 102 101 82 299 564 | f 117 39 111 565 | f 99 100 54 303 566 | f 303 54 98 12 567 | f 86 100 99 40 568 | f 40 103 61 86 569 | f 299 61 103 102 570 | g enginside 571 | usemtl redbrick 572 | s 9 573 | f 238 255 246 574 | f 194 202 201 193 575 | f 153 162 163 154 576 | f 144 153 154 145 577 | f 184 194 193 182 578 | f 238 246 237 579 | f 272 234 232 271 580 | f 236 237 235 234 581 | f 204 195 186 582 | f 134 143 144 135 583 | f 143 152 153 144 584 | f 204 203 195 585 | f 237 246 245 235 586 | f 273 236 234 272 587 | f 238 237 236 588 | f 185 184 182 183 589 | f 135 144 145 136 590 | f 154 163 146 591 | f 195 203 202 194 592 | f 235 245 244 233 593 | f 264 273 272 263 594 | f 219 185 183 218 595 | f 187 186 184 185 596 | f 136 145 146 597 | f 161 169 170 162 598 | f 204 220 212 599 | f 255 264 263 254 600 | f 234 235 233 232 601 | f 186 195 194 184 602 | f 145 154 146 603 | f 152 161 162 153 604 | f 204 212 203 605 | f 246 255 254 245 606 | f 238 236 273 607 | f 204 187 220 608 | f 169 125 126 170 609 | f 126 135 136 127 610 | f 163 171 146 611 | f 203 212 211 202 612 | f 245 254 253 244 613 | f 238 273 264 614 | f 211 219 218 210 615 | f 170 126 127 171 616 | f 127 136 146 617 | f 162 170 171 163 618 | f 202 211 210 201 619 | f 238 264 255 620 | f 254 263 262 253 621 | f 212 220 219 211 622 | f 171 127 146 623 | f 125 134 135 126 624 | f 204 186 187 625 | f 220 187 185 219 626 | f 263 272 271 262 627 | g engout 628 | usemtl black 629 | f 251 260 259 250 630 | f 209 217 216 208 631 | f 157 165 166 158 632 | f 132 141 142 133 633 | f 179 178 176 177 634 | f 215 177 175 214 635 | f 270 230 228 269 636 | f 227 241 240 225 637 | f 191 199 198 190 638 | f 150 159 160 151 639 | f 131 140 141 132 640 | f 177 176 174 175 641 | f 230 231 229 228 642 | f 269 228 226 268 643 | f 229 242 241 227 644 | f 192 200 199 191 645 | f 139 148 149 140 646 | f 130 139 140 131 647 | f 180 192 191 178 648 | f 228 229 227 226 649 | f 268 226 224 267 650 | f 231 243 242 229 651 | f 176 190 189 174 652 | f 140 149 150 141 653 | f 149 158 159 150 654 | f 190 198 197 189 655 | f 243 252 251 242 656 | f 259 268 267 258 657 | f 216 179 177 215 658 | f 181 180 178 179 659 | f 121 130 131 122 660 | f 167 123 124 168 661 | f 208 216 215 207 662 | f 250 259 258 249 663 | f 252 261 260 251 664 | f 198 207 206 197 665 | f 158 166 167 159 666 | f 123 132 133 124 667 | f 166 122 123 167 668 | f 207 215 214 206 669 | f 261 270 269 260 670 | f 241 250 249 240 671 | f 199 208 207 198 672 | f 159 167 168 160 673 | f 122 131 132 123 674 | f 165 121 122 166 675 | f 217 181 179 216 676 | f 260 269 268 259 677 | f 242 251 250 241 678 | f 200 209 208 199 679 | f 148 157 158 149 680 | f 141 150 151 142 681 | f 178 191 190 176 682 | f 226 227 225 224 683 | g engmount 684 | usemtl brass 685 | s 11 686 | f 225 240 239 222 687 | f 164 120 121 165 688 | f 128 137 138 129 689 | f 196 205 289 290 690 | f 265 221 285 266 691 | f 206 214 213 205 692 | f 138 147 148 139 693 | f 174 189 188 172 694 | f 249 258 256 247 695 | f 221 222 223 285 696 | f 155 277 164 156 697 | f 274 155 156 147 698 | f 213 173 281 276 699 | f 258 267 265 256 700 | f 189 197 196 188 701 | f 120 129 130 121 702 | f 173 172 279 281 703 | f 239 247 248 286 704 | f 205 213 276 289 705 | f 137 274 147 138 706 | f 156 164 165 157 707 | f 224 225 222 221 708 | f 247 256 257 248 709 | f 172 188 278 279 710 | f 280 128 129 120 711 | f 188 196 290 278 712 | f 256 265 266 257 713 | f 214 175 173 213 714 | f 147 156 157 148 715 | f 175 174 172 173 716 | f 240 249 247 239 717 | f 222 239 286 223 718 | f 277 280 120 164 719 | f 129 138 139 130 720 | f 197 206 205 196 721 | f 267 224 221 265 722 | g engrim 723 | usemtl dkdkgrey 724 | s off 725 | f 233 244 243 231 726 | f 124 133 134 125 727 | f 262 271 270 261 728 | f 142 151 152 143 729 | f 253 262 261 252 730 | f 151 160 161 152 731 | f 244 253 252 243 732 | f 160 168 169 161 733 | f 201 210 209 200 734 | f 271 232 230 270 735 | f 133 142 143 134 736 | f 232 233 231 230 737 | f 183 182 180 181 738 | f 218 183 181 217 739 | f 182 193 192 180 740 | f 210 218 217 209 741 | f 193 201 200 192 742 | f 168 124 125 169 743 | # 393 elements -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangf/Javascript-3D-Engine/a93e377448440a1e726db3fb2e27ed962ffd8859/screenshot.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangf/Javascript-3D-Engine/a93e377448440a1e726db3fb2e27ed962ffd8859/screenshot2.png --------------------------------------------------------------------------------