├── 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 | 
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 |
--------------------------------------------------------------------------------