├── README.md └── public ├── camera.js ├── draw.js ├── index.html ├── main.js ├── math.js ├── mesh.js ├── models.js ├── render.js └── style.css /README.md: -------------------------------------------------------------------------------- 1 | ## Draws shapes. 2 | 3 | ![Example drawing](https://i.imgur.com/99ahPtG.png "Example") 4 | 5 | Available on [NPM](https://www.npmjs.com/package/@pomle/micro-project). 6 | -------------------------------------------------------------------------------- /public/camera.js: -------------------------------------------------------------------------------- 1 | import {Vec} from './math.js'; 2 | 3 | export class Camera { 4 | constructor() { 5 | this.pos = new Vec(0, 0, 100); 6 | this.zoom = 8; 7 | } 8 | 9 | project(point) { 10 | perspective(point, this.pos.z); 11 | zoom(point, this.zoom); 12 | } 13 | } 14 | 15 | function perspective(point, distance) { 16 | const fov = point.z + distance; 17 | point.x /= fov; 18 | point.y /= fov; 19 | } 20 | 21 | function zoom(point, factor) { 22 | const scale = Math.pow(factor, 2); 23 | point.x *= scale; 24 | point.y *= scale; 25 | } 26 | -------------------------------------------------------------------------------- /public/draw.js: -------------------------------------------------------------------------------- 1 | export function drawMesh(mesh, camera, context) { 2 | context.strokeStyle = mesh.color; 3 | 4 | mesh.polygons.forEach(polygon => { 5 | const projectedPolygon = polygon.map(point => ({...point})); 6 | 7 | projectedPolygon.forEach(point => { 8 | mesh.transform(point); 9 | camera.project(point); 10 | }); 11 | 12 | drawPolygon(projectedPolygon, context); 13 | }); 14 | } 15 | 16 | function drawPolygon(polygon, context) { 17 | polygon.forEach(point => { 18 | offsetToCenter(point, context.canvas); 19 | }); 20 | 21 | context.beginPath(); 22 | 23 | const first = polygon[0]; 24 | context.moveTo(first.x, first.y); 25 | for (const point of polygon) { 26 | context.lineTo(point.x, point.y); 27 | } 28 | context.lineTo(first.x, first.y); 29 | 30 | context.stroke(); 31 | } 32 | 33 | function offsetToCenter(point, canvas) { 34 | point.x += canvas.width / 2; 35 | point.y += canvas.height / 2; 36 | } 37 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | JS Software 3D Rasterizer 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/main.js: -------------------------------------------------------------------------------- 1 | import {square, doubleSquare, cube, pyramid} from './models.js'; 2 | import {Camera} from './camera.js'; 3 | import {createMesh} from './mesh.js'; 4 | import {createWireframeRenderer} from './render.js'; 5 | 6 | const canvas = document.querySelector('canvas'); 7 | 8 | const mesh1 = createMesh(pyramid); 9 | mesh1.color = '#81ff02'; 10 | const mesh2 = createMesh(cube); 11 | mesh2.color = '#ff02f0'; 12 | const mesh3 = createMesh(cube); 13 | mesh3.color = '#ff026d'; 14 | 15 | const scene = [mesh1, mesh2, mesh3]; 16 | 17 | const camera = new Camera(); 18 | camera.pos.z = 200; 19 | camera.zoom = 12; 20 | 21 | const render = createWireframeRenderer(canvas); 22 | 23 | function animate(time) { 24 | mesh1.position.x = Math.sin(time / 1000) * 100; 25 | mesh1.position.z = Math.sin(time / 1200) * 100; 26 | mesh1.rotation.x += 0.01; 27 | mesh1.rotation.y += 0.01; 28 | 29 | mesh2.position.x = Math.sin(time / 500) * 75; 30 | mesh2.position.z = Math.sin(time / 2000) * 120; 31 | mesh2.rotation.x -= 0.01; 32 | mesh2.rotation.y -= 0.01; 33 | 34 | mesh3.position.x = Math.sin(time / 500) * 100; 35 | mesh3.position.y = Math.cos(time / 500) * 100; 36 | mesh3.rotation.y -= 0.005; 37 | 38 | render(scene, camera); 39 | 40 | requestAnimationFrame(animate); 41 | } 42 | 43 | animate(); 44 | -------------------------------------------------------------------------------- /public/math.js: -------------------------------------------------------------------------------- 1 | export class Vec { 2 | constructor(x = 0, y = 0, z = 0) { 3 | this.x = x; 4 | this.y = y; 5 | this.z = z; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /public/mesh.js: -------------------------------------------------------------------------------- 1 | import {Vec} from './math.js'; 2 | 3 | export function createMesh(model) { 4 | return new Mesh(model.map(toPolygon)); 5 | } 6 | 7 | function toPoint([x, y, z]) { 8 | return new Vec(x, y, z); 9 | } 10 | 11 | function toPolygon(shape) { 12 | return shape.map(toPoint); 13 | } 14 | 15 | function offset(point, position) { 16 | point.x += position.x; 17 | point.y += position.y; 18 | point.z += position.z; 19 | } 20 | 21 | function rotate(point, rotation) { 22 | const sin = new Vec( 23 | Math.sin(rotation.x), 24 | Math.sin(rotation.y), 25 | Math.sin(rotation.z)); 26 | 27 | const cos = new Vec( 28 | Math.cos(rotation.x), 29 | Math.cos(rotation.y), 30 | Math.cos(rotation.z)); 31 | 32 | let temp1, temp2; 33 | 34 | temp1 = cos.x * point.y + sin.x * point.z; 35 | temp2 = -sin.x * point.y + cos.x * point.z; 36 | point.y = temp1; 37 | point.z = temp2; 38 | 39 | temp1 = cos.y * point.x + sin.y * point.z; 40 | temp2 = -sin.y * point.x + cos.y * point.z; 41 | point.x = temp1; 42 | point.z = temp2; 43 | 44 | temp1 = cos.z * point.x + sin.z * point.y; 45 | temp2 = -sin.z * point.x + cos.z * point.y; 46 | point.x = temp1; 47 | point.y = temp2; 48 | } 49 | 50 | class Mesh { 51 | constructor(polygons) { 52 | this.polygons = polygons; 53 | this.position = new Vec(); 54 | this.rotation = new Vec(); 55 | } 56 | 57 | *[Symbol.iterator] () { 58 | for (const polygon of this.polygons) { 59 | yield polygon.map(point => ({...point})); 60 | } 61 | } 62 | 63 | transform(point) { 64 | rotate(point, this.rotation); 65 | offset(point, this.position); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/models.js: -------------------------------------------------------------------------------- 1 | export const square = [ 2 | [ 3 | [-50, -50, 0], 4 | [ 50, -50, 0], 5 | [ 50, 50, 0], 6 | [-50, 50, 0], 7 | ], 8 | ]; 9 | 10 | export const doubleSquare = [ 11 | [ 12 | [-50, -50, 0], 13 | [ 50, -50, 0], 14 | [ 50, 50, 0], 15 | [-50, 50, 0], 16 | ], 17 | [ 18 | [-50, -50, -50], 19 | [ 50, -50, -50], 20 | [ 50, 50, -50], 21 | [-50, 50, -50], 22 | ], 23 | ]; 24 | 25 | export const cube = [ 26 | [ 27 | [-50, -50, -50], 28 | [ 50, -50, -50], 29 | [ 50, 50, -50], 30 | [-50, 50, -50] 31 | ], 32 | [ 33 | [-50, -50, 50], 34 | [ 50, -50, 50], 35 | [ 50, 50, 50], 36 | [-50, 50, 50] 37 | ], 38 | [ 39 | [-50, -50, 50], 40 | [-50, -50, -50] 41 | ], 42 | [ 43 | [50, -50, 50], 44 | [50, -50, -50] 45 | ], 46 | [ 47 | [50, 50, 50], 48 | [50, 50, -50] 49 | ], 50 | [ 51 | [-50, 50, 50], 52 | [-50, 50, -50] 53 | ] 54 | ]; 55 | 56 | export const pyramid = [ 57 | [ 58 | [ 50, 0, 50], 59 | [ 0, -50, 0], 60 | [ -50, 0, 50], 61 | ], 62 | [ 63 | [ 50, 0, -50], 64 | [ 0, -50, 0], 65 | [ 50, 0, 50], 66 | ], 67 | [ 68 | [ 50, 0, -50], 69 | [ 0, -50, 0], 70 | [ -50, 0, -50], 71 | ], 72 | [ 73 | [ -50, 0, 50], 74 | [ 0, -50, 0], 75 | [ -50, 0, -50], 76 | ], 77 | ]; 78 | -------------------------------------------------------------------------------- /public/render.js: -------------------------------------------------------------------------------- 1 | import {drawMesh} from './draw.js'; 2 | 3 | export function createWireframeRenderer(canvas) { 4 | const context = canvas.getContext('2d'); 5 | 6 | return function render(scene, camera) { 7 | context.clearRect(0, 0, canvas.width, canvas.height); 8 | 9 | scene.forEach(mesh => { 10 | drawMesh(mesh, camera, context); 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #000; 3 | color: #fff; 4 | font-family: sans-serif; 5 | font-size: 12px; 6 | margin: 0; 7 | } 8 | 9 | fieldset { 10 | border: none; 11 | } 12 | 13 | canvas { 14 | display: block; 15 | image-rendering: pixelated; 16 | width: 100vw; 17 | } 18 | --------------------------------------------------------------------------------