tela.js 1.0.4 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +37 -26
  2. package/package.json +3 -5
  3. package/src/Camera/Camera.js +69 -21
  4. package/src/Camera/common.js +36 -0
  5. package/src/Camera/parallel.js +74 -0
  6. package/src/Camera/raster.js +9 -39
  7. package/src/Camera/rayTrace.js +84 -0
  8. package/src/Camera/rayTraceWorker.js +59 -0
  9. package/src/Color/Color.js +1 -1
  10. package/src/Geometry/Box.js +8 -0
  11. package/src/Geometry/Geometry.js +16 -0
  12. package/src/Geometry/Line.js +19 -2
  13. package/src/Geometry/Mesh.js +13 -4
  14. package/src/Geometry/Path.js +3 -0
  15. package/src/Geometry/{Point.js → Sphere.js} +39 -8
  16. package/src/Geometry/Triangle.js +33 -4
  17. package/src/IO/IO.js +21 -37
  18. package/src/Material/Material.js +18 -1
  19. package/src/Scene/BScene.js +44 -27
  20. package/src/Scene/KScene.js +54 -37
  21. package/src/Scene/NaiveScene.js +21 -2
  22. package/src/Scene/RandomScene.js +3 -4
  23. package/src/Scene/VoxelScene.js +32 -29
  24. package/src/Scene/utils.js +27 -0
  25. package/src/Tela/Canvas.js +76 -236
  26. package/src/Tela/Image.js +88 -187
  27. package/src/Tela/Tela.js +365 -0
  28. package/src/Tela/Window.js +111 -190
  29. package/src/Tela/utils.js +17 -0
  30. package/src/Utils/Constants.js +4 -31
  31. package/src/Utils/Math.js +1 -122
  32. package/src/Utils/Stream.js +7 -5
  33. package/src/Utils/Utils.js +39 -11
  34. package/src/Utils/Utils3D.js +22 -4
  35. package/src/Vector/Vector.js +2 -3
  36. package/src/index.js +5 -9
  37. package/dist/node/index.js +0 -4859
  38. package/dist/web/index.js +0 -4396
  39. package/src/Camera/raytrace.js +0 -40
  40. package/src/Scene/Scene.js +0 -284
  41. package/src/Utils/Animation.js +0 -88
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tela.js
2
2
 
3
- Experimental graphic library from scratch, with reference implementation of computer graphics algorithms.
3
+ Experimental graphic library from scratch(software-only), with reference implementation of computer graphics algorithms.
4
4
 
5
5
  ## Purpose
6
6
 
@@ -29,25 +29,23 @@ Playground usage:
29
29
 
30
30
  </body>
31
31
  <script type="module">
32
- import { Canvas, Animation, Color } from "https://cdn.jsdelivr.net/npm/tela.js/dist/web/index.js";
32
+ import { Canvas, Color, loop } from "https://cdn.jsdelivr.net/npm/tela.js/src/index.js";
33
33
 
34
34
  // You can also import from local file
35
- // import { Canvas, Animation, Color} from "./node_modules/tela.js/dist/web/index.js";
35
+ // import { Canvas, Color, loop } from "./node_modules/tela.js/src/index.js";
36
36
 
37
37
  const width = 640;
38
38
  const height = 480;
39
39
  const canvas = Canvas.ofSize(640, 480);
40
- Animation
41
- .loop(({ time, dt }) => {
42
- document.title = `FPS: ${(Math.floor(1 / dt))}`;
43
- canvas.map((x, y) => {
44
- return Color.ofRGB(
45
- ((x * time) / width) % 1,
46
- ((y * time) / height) % 1
47
- )
48
- })
40
+ loop(({ time, dt }) => {
41
+ document.title = `FPS: ${(Math.floor(1 / dt))}`;
42
+ canvas.map((x, y) => {
43
+ return Color.ofRGB(
44
+ ((x * time) / width) % 1,
45
+ ((y * time) / height) % 1
46
+ )
49
47
  })
50
- .play();
48
+ }).play();
51
49
  document.body.appendChild(canvas.DOM);
52
50
 
53
51
  </script>
@@ -59,23 +57,22 @@ Playground usage:
59
57
  Install `tela.js` it using `npm install tela.js` / `bun add tela.js`.
60
58
 
61
59
  ```js
62
- import { Animation, Color } from "tela.js/dist/node/index.js";
60
+ import { loop, Color } from "tela.js/src/index.node.js";
63
61
  import Window from "tela.js/src/Tela/Window.js";
64
62
 
65
63
  const width = 640;
66
64
  const height = 480;
67
65
  const window = Window.ofSize(640, 480);
68
- Animation
69
- .loop(({ time, dt }) => {
70
- window.setTitle(`FPS: ${Math.floor(1 / dt)}`);
71
- window.map((x, y) => {
72
- return Color.ofRGB(
73
- ((x * time) / width) % 1,
74
- ((y * time) / height) % 1
75
- )
76
- })
66
+ loop(({ time, dt }) => {
67
+ window.setTitle(`FPS: ${Math.floor(1 / dt)}`);
68
+ window.map((x, y) => {
69
+ return Color.ofRGB(
70
+ ((x * time) / width) % 1,
71
+ ((y * time) / height) % 1
72
+ )
77
73
  })
78
- .play();
74
+ })
75
+ .play();
79
76
  ```
80
77
 
81
78
  And run it: `node index.mjs` / `bun index.js`
@@ -90,7 +87,7 @@ Install `tela.js` it using `npm install tela.js` / `bun add tela.js`.
90
87
  Create a file:
91
88
  ```js
92
89
  // index.js
93
- import { Color, video } from "tela.js/dist/node/index.js";
90
+ import { Color, video } from "tela.js/src/index.node.js";
94
91
 
95
92
  const width = 640;
96
93
  const height = 480;
@@ -111,7 +108,7 @@ video(
111
108
  animation,
112
109
  { width, height, FPS }
113
110
  )
114
- .until(({ time }) => time < maxVideoTime);
111
+ .while(({ time }) => time < maxVideoTime);
115
112
  ```
116
113
 
117
114
  And run it: `node index.mjs` / `bun index.js`
@@ -127,6 +124,13 @@ ffmpeg -version
127
124
 
128
125
  ```
129
126
 
127
+ ## More examples
128
+
129
+ You can find more examples of usage in:
130
+ - [Playground (for web examples)](https://pedroth.github.io/tela.js)
131
+ - [Test folder (for desktop)](/test/node/)
132
+
133
+
130
134
  # Dependencies
131
135
 
132
136
  - [`bun`][bun]/[`node`][node]
@@ -136,6 +140,13 @@ ffmpeg -version
136
140
  [Node][node] is preferred when running the demos (it is faster, [opened a bug in bun](https://github.com/oven-sh/bun/issues/9218)), [bun][bun] is needed to build the library.
137
141
 
138
142
 
143
+ # TODOs
144
+
145
+ - Fix parallel ray trace refresh bug
146
+ - Parallel ray map
147
+ - Serialize meshes not only triangles
148
+ - Optimize data serialization in parallel ray tracer
149
+
139
150
 
140
151
  [ffmpeg]: https://ffmpeg.org/
141
152
  [bun]: https://bun.sh/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tela.js",
3
- "version": "1.0.4",
3
+ "version": "1.1.1",
4
4
  "author": "Pedroth",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,11 +37,9 @@
37
37
  ],
38
38
  "license": "Apache-2.0",
39
39
  "scripts": {
40
- "build": "bun run clean; bun i; bun bundle.js",
41
- "buildDev": "bun bundle.js --watch",
42
40
  "serve": "bunx http-server",
43
- "clean": "rm -fr node_modules; rm -fr dist/; rm *.png *.jpeg *.ppm *.webm *.mp4",
44
- "pub": "npm version patch; bun run build; npm publish"
41
+ "clean": "rm -fr node_modules; rm *.png *.jpeg *.ppm *.webm *.mp4",
42
+ "pub": "npm version patch; npm publish"
45
43
  },
46
44
  "type": "module"
47
45
  }
@@ -1,29 +1,22 @@
1
- import { Vec2, Vec3 } from "../Vector/Vector.js"
1
+ import Vec, { Vec2, Vec3 } from "../Vector/Vector.js"
2
2
  import Ray from "../Ray/Ray.js";
3
- import { rayTrace } from "./raytrace.js";
3
+ import { getRayTracer } from "./rayTrace.js";
4
4
  import { rasterGraphics } from "./raster.js";
5
5
  import { sdfTrace } from "./sdf.js";
6
6
  import { normalTrace } from "./normal.js";
7
-
8
-
7
+ import { parallelWorkers } from "./parallel.js";
9
8
 
10
9
  export default class Camera {
11
10
  constructor(props = {}) {
12
- const { lookAt, distanceToPlane, position } = props;
11
+ const { lookAt, distanceToPlane, position, orientCoords, orbitCoords } = props;
13
12
  this.lookAt = lookAt ?? Vec3(0, 0, 0);
14
13
  this.distanceToPlane = distanceToPlane ?? 1;
15
14
  this.position = position ?? Vec3(3, 0, 0);
16
- this._orientCoords = Vec2();
17
- this._orbitCoords = Vec3(this.position.length(), 0, 0);
18
- this.orient();
19
- }
20
-
21
- clone() {
22
- return new Camera({
23
- lookAt: this.lookAt,
24
- position: this.position,
25
- distanceToPlane: this.distanceToPlane,
26
- })
15
+ this._orientCoords = orientCoords ?? Vec2();
16
+ this._orbitCoords = orbitCoords;
17
+ if (this._orbitCoords) this.orbit(...this._orbitCoords.toArray());
18
+ else this.orient(...this._orientCoords.toArray());
19
+ this._orbitCoords = this._orbitCoords ?? Vec3(this.position.length());
27
20
  }
28
21
 
29
22
  look(at, up = Vec3(0, 0, 1)) {
@@ -91,11 +84,13 @@ export default class Camera {
91
84
  return {
92
85
  to: canvas => {
93
86
  const w = canvas.width;
87
+ const invW = 1 / w;
94
88
  const h = canvas.height;
89
+ const invH = 1 / h;
95
90
  const ans = canvas.map((x, y) => {
96
91
  const dirInLocal = [
97
- (x / w - 0.5),
98
- (y / h - 0.5),
92
+ (x * invW - 0.5),
93
+ (y * invH - 0.5),
99
94
  this.distanceToPlane
100
95
  ]
101
96
  const dir = Vec3(
@@ -112,11 +107,11 @@ export default class Camera {
112
107
  }
113
108
  }
114
109
 
115
- sceneShot(scene, params = {}) {
116
- return this.rayMap(rayTrace(scene, params));
110
+ sceneShot(scene, params) {
111
+ return this.rayMap(getRayTracer(scene, params));
117
112
  }
118
113
 
119
- reverseShot(scene, params = {}) {
114
+ reverseShot(scene, params) {
120
115
  return {
121
116
  to: rasterGraphics(scene, this, params)
122
117
  }
@@ -130,6 +125,18 @@ export default class Camera {
130
125
  return this.rayMap(normalTrace(scene));
131
126
  }
132
127
 
128
+ parallelShot(scene, params) {
129
+ return {
130
+ to: canvas => {
131
+ return Promise
132
+ .all(parallelWorkers(this, scene, canvas, params))
133
+ .then(() => {
134
+ canvas.paint();
135
+ })
136
+ }
137
+ }
138
+ }
139
+
133
140
  toCameraCoord(x) {
134
141
  let pointInCamCoord = x.sub(this.position);
135
142
  pointInCamCoord = Vec3(
@@ -147,4 +154,45 @@ export default class Camera {
147
154
  }
148
155
  return x;
149
156
  }
157
+
158
+ rayFromImage(width, height) {
159
+ const w = width;
160
+ const invW = 1 / w;
161
+ const h = height;
162
+ const invH = 1 / h;
163
+ return (x, y) => {
164
+ const dirInLocal = [
165
+ (x * invW - 0.5),
166
+ (y * invH - 0.5),
167
+ this.distanceToPlane
168
+ ]
169
+ const dir = Vec3(
170
+ this.basis[0].x * dirInLocal[0] + this.basis[1].x * dirInLocal[1] + this.basis[2].x * dirInLocal[2],
171
+ this.basis[0].y * dirInLocal[0] + this.basis[1].y * dirInLocal[1] + this.basis[2].y * dirInLocal[2],
172
+ this.basis[0].z * dirInLocal[0] + this.basis[1].z * dirInLocal[1] + this.basis[2].z * dirInLocal[2]
173
+ )
174
+ .normalize()
175
+ return Ray(this.position, dir);
176
+ }
177
+ }
178
+
179
+ serialize() {
180
+ return {
181
+ lookAt: this.lookAt.toArray(),
182
+ distanceToPlane: this.distanceToPlane,
183
+ position: this.position.toArray(),
184
+ orientCoords: this._orientCoords.toArray(),
185
+ orbitCoords: this._orbitCoords.toArray(),
186
+ }
187
+ }
188
+
189
+ static deserialize(json) {
190
+ return new Camera({
191
+ lookAt: Vec.fromArray(json.lookAt),
192
+ distanceToPlane: json.distanceToPlane,
193
+ position: Vec.fromArray(json.position),
194
+ orientCoords: Vec.fromArray(json.orientCoords),
195
+ orbitCoords: Vec.fromArray(json.orbitCoords)
196
+ })
197
+ }
150
198
  }
@@ -0,0 +1,36 @@
1
+ import Color from "../Color/Color.js";
2
+ import { lerp } from "../Utils/Math.js";
3
+ import { Vec2 } from "../Vector/Vector.js";
4
+
5
+ export function getDefaultTexColor(texUV) {
6
+ texUV = texUV.scale(16).map(x => x % 1)
7
+ return texUV.x < 0.5 && texUV.y < 0.5 ?
8
+ Color.BLACK :
9
+ texUV.x > 0.5 && texUV.y > 0.5 ?
10
+ Color.BLACK :
11
+ Color.PURPLE;
12
+ }
13
+
14
+ export function getBiLinearTexColor(texUV, texture) {
15
+ const size = Vec2(texture.width, texture.height);
16
+ const texInt = texUV.mul(size);
17
+
18
+ const texInt0 = texInt.map(Math.floor);
19
+ const texInt1 = texInt0.add(Vec2(1, 0));
20
+ const texInt2 = texInt0.add(Vec2(0, 1));
21
+ const texInt3 = texInt0.add(Vec2(1, 1));
22
+
23
+ const color0 = texture.getPxl(...texInt0.toArray());
24
+ const color1 = texture.getPxl(...texInt1.toArray());
25
+ const color2 = texture.getPxl(...texInt2.toArray());
26
+ const color3 = texture.getPxl(...texInt3.toArray());
27
+
28
+ const x = texInt.sub(texInt0);
29
+ const bottomX = lerp(color0, color1)(x.x);
30
+ const topX = lerp(color2, color3)(x.x);
31
+ return lerp(bottomX, topX)(x.y);
32
+ }
33
+
34
+ export function getTexColor(texUV, texture) {
35
+ return texture.getPxl(texUV.x * texture.width, texUV.y * texture.height);
36
+ }
@@ -0,0 +1,74 @@
1
+ import { IS_NODE, NUMBER_OF_CORES } from "../Utils/Constants.js";
2
+ import Color from "../Color/Color.js";
3
+ import { CHANNELS } from "../Tela/Tela.js";
4
+
5
+ //========================================================================================
6
+ /* *
7
+ * UTILS *
8
+ * */
9
+ //========================================================================================
10
+
11
+ const __Worker = IS_NODE ? (await import("node:worker_threads")).Worker : Worker;
12
+ class MyWorker {
13
+ constructor(path) {
14
+ this.path = path;
15
+ this.worker = new __Worker(path, { type: 'module' });
16
+ }
17
+
18
+ onMessage(lambda) {
19
+ if (IS_NODE) {
20
+ this.worker.removeAllListeners('message');
21
+ this.worker.on("message", lambda);
22
+ } else {
23
+ this.worker.onmessage = message => lambda(message.data);
24
+ }
25
+ }
26
+
27
+ postMessage(message) {
28
+ return this.worker.postMessage(message);
29
+ }
30
+ }
31
+
32
+ let WORKERS = [];
33
+ let prevSceneHash = undefined;
34
+
35
+ //========================================================================================
36
+ /* *
37
+ * MAIN *
38
+ * */
39
+ //========================================================================================
40
+
41
+ export function parallelWorkers(camera, scene, canvas, params = {}) {
42
+ // lazy loading workers
43
+ if (WORKERS.length === 0) WORKERS = [...Array(NUMBER_OF_CORES)].map(() => new MyWorker(`/src/Camera/rayTraceWorker.js`));
44
+ const w = canvas.width;
45
+ const h = canvas.height;
46
+ const isNewScene = prevSceneHash !== scene.hash;
47
+ if (isNewScene) prevSceneHash = scene.hash;
48
+ return WORKERS.map((worker, k) => {
49
+ return new Promise((resolve) => {
50
+ worker.onMessage(message => {
51
+ const { image, startRow, endRow, } = message;
52
+ let index = 0;
53
+ const startIndex = CHANNELS * w * startRow;
54
+ const endIndex = CHANNELS * w * endRow;
55
+ for (let i = startIndex; i < endIndex; i += CHANNELS) {
56
+ canvas.setPxlData(i, Color.ofRGB(image[index++], image[index++], image[index++], image[index++]));
57
+ }
58
+ resolve();
59
+ })
60
+ const ratio = Math.floor(h / WORKERS.length);
61
+
62
+ const message = {
63
+ width: w,
64
+ height: h,
65
+ params: params,
66
+ startRow: k * ratio,
67
+ endRow: Math.min(h, (k + 1) * ratio),
68
+ camera: camera.serialize(),
69
+ scene: isNewScene ? scene.serialize() : undefined
70
+ };
71
+ worker.postMessage(message);
72
+ });
73
+ })
74
+ }
@@ -1,14 +1,14 @@
1
1
  import Color from "../Color/Color.js";
2
2
  import Line from "../Geometry/Line.js";
3
- import Point from "../Geometry/Point.js";
3
+ import Sphere from "../Geometry/Sphere.js";
4
4
  import Mesh from "../Geometry/Mesh.js";
5
5
  import Triangle from "../Geometry/Triangle.js";
6
- import { lerp } from "../Utils/Math.js";
7
6
  import { Vec2 } from "../Vector/Vector.js";
7
+ import { getBiLinearTexColor, getDefaultTexColor, getTexColor } from "./common.js";
8
8
 
9
- export function rasterGraphics(scene, camera, params) {
9
+ export function rasterGraphics(scene, camera, params = {}) {
10
10
  const type2render = {
11
- [Point.name]: rasterPoint,
11
+ [Sphere.name]: rasterSphere,
12
12
  [Line.name]: rasterLine,
13
13
  [Triangle.name]: rasterTriangle,
14
14
  [Mesh.name]: rasterMesh,
@@ -52,7 +52,7 @@ export function rasterGraphics(scene, camera, params) {
52
52
  }
53
53
 
54
54
 
55
- function rasterPoint({ canvas, camera, elem, w, h, zBuffer }) {
55
+ function rasterSphere({ canvas, camera, elem, w, h, zBuffer }) {
56
56
  const point = elem;
57
57
  const { distanceToPlane } = camera;
58
58
  const { texCoord, texture, position, color, radius } = point;
@@ -71,6 +71,7 @@ function rasterPoint({ canvas, camera, elem, w, h, zBuffer }) {
71
71
  y = Math.floor(y);
72
72
  if (x < 0 || x >= w || y < 0 || y >= h) return;
73
73
  const intRadius = Math.ceil((radius) * (distanceToPlane / z) * w);
74
+ const intRadiusSquare = intRadius * intRadius;
74
75
  let finalColor = color;
75
76
  if (
76
77
  texture &&
@@ -83,6 +84,8 @@ function rasterPoint({ canvas, camera, elem, w, h, zBuffer }) {
83
84
  for (let l = -intRadius; l < intRadius; l++) {
84
85
  const xl = Math.max(0, Math.min(w - 1, x + k));
85
86
  const yl = Math.floor(y + l);
87
+ const squareLength = k * k + l * l;
88
+ if (squareLength > intRadiusSquare) continue;
86
89
  const [i, j] = canvas.canvas2grid(xl, yl);
87
90
  const zBufferIndex = Math.floor(w * i + j);
88
91
  if (z < zBuffer[zBufferIndex]) {
@@ -227,7 +230,7 @@ function rasterTriangle({ canvas, camera, elem, w, h, zBuffer, params }) {
227
230
  params.bilinearTexture ?
228
231
  getBiLinearTexColor(texUV, texture) :
229
232
  getTexColor(texUV, texture) :
230
- getDefaultTexColor(texUV);
233
+ c ? c : getDefaultTexColor(texUV); // TODO: review this
231
234
  c = texColor;
232
235
  }
233
236
  const [i, j] = canvas.canvas2grid(x, y);
@@ -253,37 +256,4 @@ function lineCameraPlaneIntersection(vertexOut, vertexIn, camera) {
253
256
  const alpha = (distanceToPlane - vertexOut.z) / v.z;
254
257
  const p = vertexOut.add(v.scale(alpha));
255
258
  return p;
256
- }
257
-
258
- function getDefaultTexColor(texUV) {
259
- texUV = texUV.scale(16).map(x => x % 1)
260
- return texUV.x < 0.5 && texUV.y < 0.5 ?
261
- Color.BLACK :
262
- texUV.x > 0.5 && texUV.y > 0.5 ?
263
- Color.BLACK :
264
- Color.PURPLE;
265
- }
266
-
267
- function getBiLinearTexColor(texUV, texture) {
268
- const size = Vec2(texture.width, texture.height);
269
- const texInt = texUV.mul(size);
270
-
271
- const texInt0 = texInt.map(Math.floor);
272
- const texInt1 = texInt0.add(Vec2(1, 0));
273
- const texInt2 = texInt0.add(Vec2(0, 1));
274
- const texInt3 = texInt0.add(Vec2(1, 1));
275
-
276
- const color0 = texture.getPxl(...texInt0.toArray());
277
- const color1 = texture.getPxl(...texInt1.toArray());
278
- const color2 = texture.getPxl(...texInt2.toArray());
279
- const color3 = texture.getPxl(...texInt3.toArray());
280
-
281
- const x = texInt.sub(texInt0);
282
- const bottomX = lerp(color0, color1)(x.x);
283
- const topX = lerp(color2, color3)(x.x);
284
- return lerp(bottomX, topX)(x.y);
285
- }
286
-
287
- function getTexColor(texUV, texture) {
288
- return texture.getPxl(texUV.x * texture.width, texUV.y * texture.height);
289
259
  }
@@ -0,0 +1,84 @@
1
+ import Color from "../Color/Color.js";
2
+ import Triangle from "../Geometry/Triangle.js";
3
+ import Ray from "../Ray/Ray.js";
4
+ import Vec from "../Vector/Vector.js";
5
+ import { getBiLinearTexColor, getDefaultTexColor, getTexColor } from "./common.js";
6
+
7
+ export function rayTrace(ray, scene, params) {
8
+ let { samplesPerPxl, bounces, variance, gamma, bilinearTexture } = params;
9
+ bounces = bounces ?? 10;
10
+ variance = variance ?? 0.001;
11
+ samplesPerPxl = samplesPerPxl ?? 1;
12
+ gamma = gamma ?? 0.5;
13
+ bilinearTexture = bilinearTexture ?? false;
14
+ const invSamples = (bounces ?? 1) / samplesPerPxl
15
+ let c = Color.BLACK;
16
+ for (let i = 0; i < samplesPerPxl; i++) {
17
+ const epsilon = Vec.RANDOM(3).scale(variance);
18
+ const epsilonOrtho = epsilon.sub(ray.dir.scale(epsilon.dot(ray.dir)));
19
+ const r = Ray(ray.init, ray.dir.add(epsilonOrtho).normalize());
20
+ c = c.add(trace(r, scene, { bounces, bilinearTexture }));
21
+ }
22
+ return c.scale(invSamples).toGamma(gamma);
23
+ }
24
+
25
+ export function getRayTracer(scene, params = {}) {
26
+ const lambda = ray => rayTrace(ray, scene, params);
27
+ return lambda;
28
+ }
29
+
30
+ export function trace(ray, scene, options) {
31
+ const { bounces, bilinearTexture } = options;
32
+ if (bounces < 0) return Color.BLACK;
33
+ const hit = scene.interceptWithRay(ray);
34
+ if (!hit) return Color.BLACK;
35
+ const [, p, e] = hit;
36
+ const color = getColorFromElement(e, ray, { bilinearTexture });
37
+ const mat = e.material;
38
+ let r = mat.scatter(ray, p, e);
39
+ let finalC = trace(
40
+ r,
41
+ scene,
42
+ { bounces: bounces - 1, bilinearTexture }
43
+ );
44
+ return e.emissive ? color.add(color.mul(finalC)) : color.mul(finalC);
45
+ }
46
+
47
+ function getColorFromElement(e, ray, params) {
48
+ if (Triangle.name === e.constructor.name) {
49
+ return getTriangleColor(e, ray, params);
50
+ }
51
+ return e.color ?? e.colors[0]
52
+ }
53
+
54
+ function getTriangleColor(triangle, ray, params) {
55
+ const { tangents, positions, texCoords, texture, colors } = triangle;
56
+
57
+ const haveTextures = texture &&
58
+ texCoords &&
59
+ texCoords.length > 0 &&
60
+ !texCoords.some(x => x === undefined);
61
+
62
+ const v = ray.init.sub(positions[0]);
63
+ const u1 = tangents[0];
64
+ const u2 = tangents[1];
65
+ const r = ray.dir;
66
+ const detInv = 1 / u1.cross(u2).dot(r);
67
+ const alpha = v.cross(u2).dot(r) * detInv
68
+ const beta = u1.cross(v).dot(r) * detInv;
69
+ if (haveTextures) {
70
+ const texUV = texCoords[0].scale(1 - alpha - beta)
71
+ .add(texCoords[1].scale(alpha))
72
+ .add(texCoords[2].scale(beta));
73
+ const texColor = texture ?
74
+ params.bilinearTexture ?
75
+ getBiLinearTexColor(texUV, texture) :
76
+ getTexColor(texUV, texture) :
77
+ getDefaultTexColor(texUV);
78
+ return texColor;
79
+ }
80
+ return colors[0]
81
+ .scale(alpha)
82
+ .add(colors[1].scale(beta))
83
+ .add(colors[2].scale(1 - alpha - beta))
84
+ }
@@ -0,0 +1,59 @@
1
+ import Camera from "./Camera.js";
2
+ import { rayTrace } from "./rayTrace.js";
3
+ import { CHANNELS } from "../Tela/Tela.js";
4
+ import { IS_NODE } from "../Utils/Constants.js";
5
+ import { deserializeScene } from "../Scene/utils.js";
6
+ import Color from "../Color/Color.js";
7
+
8
+ const parentPort = IS_NODE ? (await import("node:worker_threads")).parentPort : undefined;
9
+
10
+ let scene = undefined;
11
+
12
+ function getScene(serializedScene) {
13
+ return deserializeScene(serializedScene).then(s => s.rebuild());
14
+ }
15
+
16
+ async function main(inputs) {
17
+ const {
18
+ startRow,
19
+ endRow,
20
+ width,
21
+ height,
22
+ params,
23
+ scene: serializedScene,
24
+ camera: serializedCamera,
25
+ } = inputs;
26
+ scene = serializedScene ? await getScene(serializedScene) : scene;
27
+ const camera = Camera.deserialize(serializedCamera);
28
+ const rayGen = camera.rayFromImage(width, height);
29
+ const bufferSize = width * (endRow - startRow + 1) * CHANNELS;
30
+ const image = new Float32Array(bufferSize);
31
+ let index = 0;
32
+ // the order does matter
33
+ for (let y = startRow; y < endRow; y++) {
34
+ for (let x = 0; x < width; x++) {
35
+ const ray = rayGen(x, height - 1 - y);
36
+ const color = scene ? rayTrace(ray, scene, params) : Color.random();
37
+ image[index++] = color.red;
38
+ image[index++] = color.green;
39
+ image[index++] = color.blue;
40
+ image[index++] = color.alpha;
41
+ }
42
+ }
43
+ return { image, startRow, endRow };
44
+ }
45
+
46
+
47
+ if (IS_NODE) {
48
+ parentPort.on("message", async message => {
49
+ const input = message;
50
+ const output = await main(input);
51
+ parentPort.postMessage(output);
52
+ });
53
+ } else {
54
+ onmessage = async message => {
55
+ const input = message.data;
56
+ const output = await main(input);
57
+ postMessage(output);
58
+ };
59
+ }
@@ -12,7 +12,7 @@ export default class Color {
12
12
  }
13
13
 
14
14
  toArray() {
15
- return [this.red, this.green, this.blue, this.alpha];
15
+ return [this.rgb[0], this.rgb[1], this.rgb[2], this.alpha];
16
16
  }
17
17
 
18
18
  get red() {
@@ -141,6 +141,14 @@ export default class Box {
141
141
  return this.min.add(Vec.RANDOM(this.dim).mul(this.diagonal));
142
142
  }
143
143
 
144
+ serialize() {
145
+ // TODO
146
+ }
147
+
148
+ deserialize() {
149
+ // TODO
150
+ }
151
+
144
152
  static EMPTY = new Box();
145
153
  }
146
154
 
@@ -35,4 +35,20 @@ export default class Geometry {
35
35
  interceptWithRay(ray) {
36
36
  throw Error("Not implemented");
37
37
  }
38
+
39
+ sample() {
40
+ throw Error("Not implemented");
41
+ }
42
+
43
+ equals(obj) {
44
+ return this === obj;
45
+ }
46
+
47
+ serialize() {
48
+ throw Error("Not implemented");
49
+ }
50
+
51
+ static deserialize(json) {
52
+ throw Error("Not implemented");
53
+ }
38
54
  }