tela.js 1.0.0

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 (178) hide show
  1. package/.eslintrc.yml +8 -0
  2. package/LICENSE +201 -0
  3. package/README.md +54 -0
  4. package/assets/bunny.obj +7474 -0
  5. package/assets/expand.svg +1 -0
  6. package/assets/great.jpg +0 -0
  7. package/assets/kakashi.jpg +0 -0
  8. package/assets/spot.obj +11999 -0
  9. package/assets/spot.png +0 -0
  10. package/assets/torus.obj +3137 -0
  11. package/assets/x.svg +1 -0
  12. package/bun.lockb +0 -0
  13. package/bundle.js +39 -0
  14. package/dist/node/index.js +37048 -0
  15. package/dist/web/index.js +1511 -0
  16. package/index.css +129 -0
  17. package/index.html +19 -0
  18. package/index.js +534 -0
  19. package/package.json +40 -0
  20. package/src/Animation/Animation.js +61 -0
  21. package/src/Box/Box.js +105 -0
  22. package/src/Camera/Camera.js +133 -0
  23. package/src/Canvas/Canvas.js +203 -0
  24. package/src/Canvas/README.md +83 -0
  25. package/src/Color/Color.js +77 -0
  26. package/src/DomBuilder/DomBuilder.js +126 -0
  27. package/src/IO/IO.js +120 -0
  28. package/src/Image/Image.js +138 -0
  29. package/src/Image/README.md +33 -0
  30. package/src/Monads/Monads.js +28 -0
  31. package/src/Ray/Ray.js +7 -0
  32. package/src/Scene/Mesh.js +103 -0
  33. package/src/Scene/NaiveScene.js +77 -0
  34. package/src/Scene/Point.js +109 -0
  35. package/src/Scene/PointCloud.js +51 -0
  36. package/src/Scene/Scene.js +200 -0
  37. package/src/Stream/Stream.js +10 -0
  38. package/src/Utils/Constants.js +1 -0
  39. package/src/Utils/Math.js +65 -0
  40. package/src/Utils/Utils.js +39 -0
  41. package/src/Vector/Vector.js +495 -0
  42. package/src/index.js +32 -0
  43. package/src/index.node.js +5 -0
  44. package/test/node/amazing_shader.js +56 -0
  45. package/test/node/bunny.js +55 -0
  46. package/test/node/bunny_parallel.js +102 -0
  47. package/test/node/image2rgb.js +57 -0
  48. package/test/node/image_test.js +38 -0
  49. package/test/node/lorentz.js +0 -0
  50. package/test/web/amazing_shader.js +60 -0
  51. package/test/web/amazing_shader_2.js +54 -0
  52. package/test/web/bunny.js +72 -0
  53. package/test/web/image2rgb.js +77 -0
  54. package/test/web/interactive_wave.js +108 -0
  55. package/test/web/lorenz.js +60 -0
  56. package/test/web/mandelbrot.js +59 -0
  57. package/test/web/rotating_grid.js +62 -0
  58. package/test/web/signed_bunny.js +139 -0
  59. package/test/web/signed_distance.js +95 -0
  60. package/test/web/simple_animation.js +39 -0
  61. package/test/web/simple_shader.js +14 -0
  62. package/test/web/six_spheres.js +102 -0
  63. package/test/web/wave_equation.js +93 -0
  64. package/vs-monaco/package/LICENSE +21 -0
  65. package/vs-monaco/package/ThirdPartyNotices.txt +448 -0
  66. package/vs-monaco/package/min/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  67. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.de.js +8 -0
  68. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.es.js +8 -0
  69. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.fr.js +8 -0
  70. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.it.js +8 -0
  71. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.ja.js +8 -0
  72. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.js +8 -0
  73. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.ko.js +8 -0
  74. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.ru.js +8 -0
  75. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.zh-cn.js +8 -0
  76. package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.zh-tw.js +8 -0
  77. package/vs-monaco/package/min/vs/base/worker/workerMain.js +27 -0
  78. package/vs-monaco/package/min/vs/basic-languages/abap/abap.js +10 -0
  79. package/vs-monaco/package/min/vs/basic-languages/apex/apex.js +10 -0
  80. package/vs-monaco/package/min/vs/basic-languages/azcli/azcli.js +10 -0
  81. package/vs-monaco/package/min/vs/basic-languages/bat/bat.js +10 -0
  82. package/vs-monaco/package/min/vs/basic-languages/bicep/bicep.js +11 -0
  83. package/vs-monaco/package/min/vs/basic-languages/cameligo/cameligo.js +10 -0
  84. package/vs-monaco/package/min/vs/basic-languages/clojure/clojure.js +10 -0
  85. package/vs-monaco/package/min/vs/basic-languages/coffee/coffee.js +10 -0
  86. package/vs-monaco/package/min/vs/basic-languages/cpp/cpp.js +10 -0
  87. package/vs-monaco/package/min/vs/basic-languages/csharp/csharp.js +10 -0
  88. package/vs-monaco/package/min/vs/basic-languages/csp/csp.js +10 -0
  89. package/vs-monaco/package/min/vs/basic-languages/css/css.js +12 -0
  90. package/vs-monaco/package/min/vs/basic-languages/cypher/cypher.js +10 -0
  91. package/vs-monaco/package/min/vs/basic-languages/dart/dart.js +10 -0
  92. package/vs-monaco/package/min/vs/basic-languages/dockerfile/dockerfile.js +10 -0
  93. package/vs-monaco/package/min/vs/basic-languages/ecl/ecl.js +10 -0
  94. package/vs-monaco/package/min/vs/basic-languages/elixir/elixir.js +10 -0
  95. package/vs-monaco/package/min/vs/basic-languages/flow9/flow9.js +10 -0
  96. package/vs-monaco/package/min/vs/basic-languages/freemarker2/freemarker2.js +12 -0
  97. package/vs-monaco/package/min/vs/basic-languages/fsharp/fsharp.js +10 -0
  98. package/vs-monaco/package/min/vs/basic-languages/go/go.js +10 -0
  99. package/vs-monaco/package/min/vs/basic-languages/graphql/graphql.js +10 -0
  100. package/vs-monaco/package/min/vs/basic-languages/handlebars/handlebars.js +10 -0
  101. package/vs-monaco/package/min/vs/basic-languages/hcl/hcl.js +10 -0
  102. package/vs-monaco/package/min/vs/basic-languages/html/html.js +10 -0
  103. package/vs-monaco/package/min/vs/basic-languages/ini/ini.js +10 -0
  104. package/vs-monaco/package/min/vs/basic-languages/java/java.js +10 -0
  105. package/vs-monaco/package/min/vs/basic-languages/javascript/javascript.js +10 -0
  106. package/vs-monaco/package/min/vs/basic-languages/julia/julia.js +10 -0
  107. package/vs-monaco/package/min/vs/basic-languages/kotlin/kotlin.js +10 -0
  108. package/vs-monaco/package/min/vs/basic-languages/less/less.js +11 -0
  109. package/vs-monaco/package/min/vs/basic-languages/lexon/lexon.js +10 -0
  110. package/vs-monaco/package/min/vs/basic-languages/liquid/liquid.js +10 -0
  111. package/vs-monaco/package/min/vs/basic-languages/lua/lua.js +10 -0
  112. package/vs-monaco/package/min/vs/basic-languages/m3/m3.js +10 -0
  113. package/vs-monaco/package/min/vs/basic-languages/markdown/markdown.js +10 -0
  114. package/vs-monaco/package/min/vs/basic-languages/mdx/mdx.js +10 -0
  115. package/vs-monaco/package/min/vs/basic-languages/mips/mips.js +10 -0
  116. package/vs-monaco/package/min/vs/basic-languages/msdax/msdax.js +10 -0
  117. package/vs-monaco/package/min/vs/basic-languages/mysql/mysql.js +10 -0
  118. package/vs-monaco/package/min/vs/basic-languages/objective-c/objective-c.js +10 -0
  119. package/vs-monaco/package/min/vs/basic-languages/pascal/pascal.js +10 -0
  120. package/vs-monaco/package/min/vs/basic-languages/pascaligo/pascaligo.js +10 -0
  121. package/vs-monaco/package/min/vs/basic-languages/perl/perl.js +10 -0
  122. package/vs-monaco/package/min/vs/basic-languages/pgsql/pgsql.js +10 -0
  123. package/vs-monaco/package/min/vs/basic-languages/php/php.js +10 -0
  124. package/vs-monaco/package/min/vs/basic-languages/pla/pla.js +10 -0
  125. package/vs-monaco/package/min/vs/basic-languages/postiats/postiats.js +10 -0
  126. package/vs-monaco/package/min/vs/basic-languages/powerquery/powerquery.js +10 -0
  127. package/vs-monaco/package/min/vs/basic-languages/powershell/powershell.js +10 -0
  128. package/vs-monaco/package/min/vs/basic-languages/protobuf/protobuf.js +11 -0
  129. package/vs-monaco/package/min/vs/basic-languages/pug/pug.js +10 -0
  130. package/vs-monaco/package/min/vs/basic-languages/python/python.js +10 -0
  131. package/vs-monaco/package/min/vs/basic-languages/qsharp/qsharp.js +10 -0
  132. package/vs-monaco/package/min/vs/basic-languages/r/r.js +10 -0
  133. package/vs-monaco/package/min/vs/basic-languages/razor/razor.js +10 -0
  134. package/vs-monaco/package/min/vs/basic-languages/redis/redis.js +10 -0
  135. package/vs-monaco/package/min/vs/basic-languages/redshift/redshift.js +10 -0
  136. package/vs-monaco/package/min/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
  137. package/vs-monaco/package/min/vs/basic-languages/ruby/ruby.js +10 -0
  138. package/vs-monaco/package/min/vs/basic-languages/rust/rust.js +10 -0
  139. package/vs-monaco/package/min/vs/basic-languages/sb/sb.js +10 -0
  140. package/vs-monaco/package/min/vs/basic-languages/scala/scala.js +10 -0
  141. package/vs-monaco/package/min/vs/basic-languages/scheme/scheme.js +10 -0
  142. package/vs-monaco/package/min/vs/basic-languages/scss/scss.js +12 -0
  143. package/vs-monaco/package/min/vs/basic-languages/shell/shell.js +10 -0
  144. package/vs-monaco/package/min/vs/basic-languages/solidity/solidity.js +10 -0
  145. package/vs-monaco/package/min/vs/basic-languages/sophia/sophia.js +10 -0
  146. package/vs-monaco/package/min/vs/basic-languages/sparql/sparql.js +10 -0
  147. package/vs-monaco/package/min/vs/basic-languages/sql/sql.js +10 -0
  148. package/vs-monaco/package/min/vs/basic-languages/st/st.js +10 -0
  149. package/vs-monaco/package/min/vs/basic-languages/swift/swift.js +13 -0
  150. package/vs-monaco/package/min/vs/basic-languages/systemverilog/systemverilog.js +10 -0
  151. package/vs-monaco/package/min/vs/basic-languages/tcl/tcl.js +10 -0
  152. package/vs-monaco/package/min/vs/basic-languages/twig/twig.js +10 -0
  153. package/vs-monaco/package/min/vs/basic-languages/typescript/typescript.js +10 -0
  154. package/vs-monaco/package/min/vs/basic-languages/vb/vb.js +10 -0
  155. package/vs-monaco/package/min/vs/basic-languages/wgsl/wgsl.js +307 -0
  156. package/vs-monaco/package/min/vs/basic-languages/xml/xml.js +10 -0
  157. package/vs-monaco/package/min/vs/basic-languages/yaml/yaml.js +10 -0
  158. package/vs-monaco/package/min/vs/editor/editor.main.css +6 -0
  159. package/vs-monaco/package/min/vs/editor/editor.main.js +745 -0
  160. package/vs-monaco/package/min/vs/editor/editor.main.nls.de.js +31 -0
  161. package/vs-monaco/package/min/vs/editor/editor.main.nls.es.js +31 -0
  162. package/vs-monaco/package/min/vs/editor/editor.main.nls.fr.js +29 -0
  163. package/vs-monaco/package/min/vs/editor/editor.main.nls.it.js +29 -0
  164. package/vs-monaco/package/min/vs/editor/editor.main.nls.ja.js +31 -0
  165. package/vs-monaco/package/min/vs/editor/editor.main.nls.js +29 -0
  166. package/vs-monaco/package/min/vs/editor/editor.main.nls.ko.js +29 -0
  167. package/vs-monaco/package/min/vs/editor/editor.main.nls.ru.js +31 -0
  168. package/vs-monaco/package/min/vs/editor/editor.main.nls.zh-cn.js +31 -0
  169. package/vs-monaco/package/min/vs/editor/editor.main.nls.zh-tw.js +29 -0
  170. package/vs-monaco/package/min/vs/language/css/cssMode.js +13 -0
  171. package/vs-monaco/package/min/vs/language/css/cssWorker.js +81 -0
  172. package/vs-monaco/package/min/vs/language/html/htmlMode.js +13 -0
  173. package/vs-monaco/package/min/vs/language/html/htmlWorker.js +453 -0
  174. package/vs-monaco/package/min/vs/language/json/jsonMode.js +15 -0
  175. package/vs-monaco/package/min/vs/language/json/jsonWorker.js +36 -0
  176. package/vs-monaco/package/min/vs/language/typescript/tsMode.js +20 -0
  177. package/vs-monaco/package/min/vs/language/typescript/tsWorker.js +37016 -0
  178. package/vs-monaco/package/min/vs/loader.js +11 -0
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "tela.js",
3
+ "version": "1.0.0",
4
+ "description": "Graphic library from scratch",
5
+ "module": "./src/index.js",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/pedroth/tela.js.git"
10
+ },
11
+ "keywords": [
12
+ "javascript",
13
+ "computer graphics",
14
+ "algorithms",
15
+ "mathematics",
16
+ "minimal dependencies"
17
+ ],
18
+ "author": "Pedroth",
19
+ "license": "Apache-2.0",
20
+ "bugs": {
21
+ "url": "https://github.com/pedroth/tela.js/issues"
22
+ },
23
+ "homepage": "https://github.com/pedroth/tela.js#readme",
24
+ "scripts": {
25
+ "build": "bun run clean; bun i; bun bundle.js",
26
+ "buildDev": "bun bundle.js --watch",
27
+ "serve": "bunx http-server",
28
+ "clean": "rm -fr node_modules; rm -fr dist/; rm *.png *.jpeg *.ppm *.webm *.mp4",
29
+ "pub": "npm version patch; bun run build; npm publish"
30
+ },
31
+ "devDependencies": {
32
+ "bun-types": "latest",
33
+ "child_process": "^1.0.2",
34
+ "eslint": "^8.55.0",
35
+ "node-watch": "^0.7.4"
36
+ },
37
+ "dependencies": {
38
+ "jimp": "^0.22.10"
39
+ }
40
+ }
@@ -0,0 +1,61 @@
1
+ import Stream from "../Stream/Stream.js";
2
+
3
+ export default class Animation {
4
+ constructor(state, next, doWhile) {
5
+ this.animation = new Stream(state, next);
6
+ this.while = doWhile;
7
+ this.requestAnimeId = null;
8
+ }
9
+
10
+ play(stream = this.animation) {
11
+ this.requestAnimeId = requestAnimationFrame(() => {
12
+ if (!this.while(stream.head)) return this.stop();
13
+ this.play(stream.tail);
14
+ });
15
+ Animation.globalAnimationIds.push(this.requestAnimeId);
16
+ return this;
17
+ }
18
+
19
+ stop() {
20
+ cancelAnimationFrame(this.requestAnimeId);
21
+ return this;
22
+ }
23
+
24
+ static globalAnimationIds = [];
25
+
26
+ static builder() {
27
+ return new AnimationBuilder();
28
+ }
29
+ }
30
+
31
+ class AnimationBuilder {
32
+ constructor() {
33
+ this._state = null;
34
+ this._next = null;
35
+ this._end = null;
36
+ }
37
+
38
+ initialState(state) {
39
+ this._state = state;
40
+ return this;
41
+ }
42
+
43
+ // next: currentState => NextState
44
+ nextState(next) {
45
+ this._next = next;
46
+ return this;
47
+ }
48
+
49
+ while(end) {
50
+ this._end = end;
51
+ return this;
52
+ }
53
+
54
+ build() {
55
+ const someAreEmpty = [this._state, this._next, this._end].some(
56
+ (x) => x === null || x === undefined
57
+ );
58
+ if (someAreEmpty) throw new Error("Animation properties are missing");
59
+ return new Animation(this._state, this._next, this._end);
60
+ }
61
+ }
package/src/Box/Box.js ADDED
@@ -0,0 +1,105 @@
1
+ import { none, some } from "../Monads/Monads";
2
+ import Vec from "../Vector/Vector";
3
+
4
+
5
+ export default class Box {
6
+ constructor(min, max) {
7
+ this.isEmpty = min === undefined || max === undefined;
8
+ if (this.isEmpty) return this;
9
+ this.min = min.op(max, Math.min);
10
+ this.max = max.op(min, Math.max);
11
+ this.center = min.add(max).scale(1 / 2);
12
+ this.diagonal = max.sub(min);
13
+ }
14
+
15
+ add(box) {
16
+ if (this.isEmpty) return box;
17
+ const { min, max } = this;
18
+ return new Box(min.op(box.min, Math.min), max.op(box.max, Math.max));
19
+ }
20
+
21
+ union = this.add;
22
+
23
+ sub(box) {
24
+ if (this.isEmpty) return Box.EMPTY;
25
+ const { min, max } = this;
26
+ const newMin = min.op(box.min, Math.max);
27
+ const newMax = max.op(box.max, Math.min);
28
+ const newDiag = newMax.sub(newMin);
29
+ const isAllPositive = newDiag.data.every((x) => x >= 0);
30
+ return !isAllPositive ? Box.EMPTY : new Box(newMin, newMax);
31
+ }
32
+
33
+ intersection = this.sub;
34
+
35
+ interceptWith(ray) {
36
+ const maxIte = 100;
37
+ const epsilon = 1e-3;
38
+ let p = ray.init;
39
+ let t = this.distanceToPoint(p);
40
+ let minT = t;
41
+ for (let i = 0; i < maxIte; i++) {
42
+ p = ray.trace(t);
43
+ const d = this.distanceToPoint(p);
44
+ t += d;
45
+ if (d < epsilon) {
46
+ return some(p);
47
+ }
48
+ if (d > minT) {
49
+ break;
50
+ };
51
+ minT = d;
52
+ }
53
+ return none();
54
+ }
55
+
56
+ scale(r) {
57
+ return new Box(this.min.sub(this.center).scale(r), this.max.sub(this.center).scale(r)).move(this.center);
58
+ }
59
+
60
+ move(v) {
61
+ return new Box(this.min.add(v), this.max.add(v));
62
+ }
63
+
64
+ equals(box) {
65
+ if (!(box instanceof Box)) return false;
66
+ if (this == Box.EMPTY) return true;
67
+ return this.min.equals(box.min) && this.max.equals(box.max);
68
+ }
69
+
70
+ distanceToBox(box) {
71
+ // return this.center.sub(box.center).length;
72
+ return this.min.sub(box.min).length() + this.max.sub(box.max).length();
73
+ }
74
+
75
+ distanceToPoint(pointVec) {
76
+ const p = pointVec.sub(this.center);
77
+ const r = this.max.sub(this.center);
78
+ const q = p.map(Math.abs).sub(r);
79
+ return q.map(x => Math.max(x, 0)).length() + Math.min(0, maxComp(q));
80
+ }
81
+
82
+ estimateNormal(pointVec) {
83
+ const epsilon = 1e-3;
84
+ const n = pointVec.dim;
85
+ const grad = [];
86
+ const d = this.distanceToPoint(pointVec);
87
+ for (let i = 0; i < n; i++) {
88
+ grad.push(this.distanceToPoint(pointVec.add(Vec.e(n)(i).scale(epsilon))) - d)
89
+ }
90
+ return Vec.fromArray(grad).scale(Math.sign(d)).normalize();
91
+ }
92
+
93
+ toString() {
94
+ return `{
95
+ min:${this.min.toString()},
96
+ max:${this.max.toString()}
97
+ }`
98
+ }
99
+
100
+ static EMPTY = new Box();
101
+ }
102
+
103
+ function maxComp(u) {
104
+ return u.fold((e, x) => Math.max(e, x), -Number.MAX_VALUE);
105
+ }
@@ -0,0 +1,133 @@
1
+ import { Vec3 } from "../Vector/Vector.js"
2
+ import Color from "../Color/Color.js"
3
+ import Ray from "../Ray/Ray.js";
4
+
5
+ export default class Camera {
6
+ constructor(props = {
7
+ sphericalCoords: Vec3(2, 0, 0),
8
+ focalPoint: Vec3(0, 0, 0),
9
+ distanceToPlane: 1
10
+ }) {
11
+ const { sphericalCoords, focalPoint, distanceToPlane } = props;
12
+ this.sphericalCoords = sphericalCoords || Vec3(2, 0, 0);
13
+ this.focalPoint = focalPoint || Vec3(0, 0, 0);
14
+ this.distanceToPlane = distanceToPlane || 1;
15
+ this.orbit();
16
+ }
17
+
18
+ orbit() {
19
+ const [rho, theta, phi] = this.sphericalCoords.toArray();
20
+ const cosT = Math.cos(theta);
21
+ const sinT = Math.sin(theta);
22
+ const cosP = Math.cos(phi);
23
+ const sinP = Math.sin(phi);
24
+
25
+ this.basis = [];
26
+ // z - axis
27
+ this.basis[2] = Vec3(-cosP * cosT, -cosP * sinT, -sinP);
28
+ // y - axis
29
+ this.basis[1] = Vec3(-sinP * cosT, -sinP * sinT, cosP);
30
+ // x -axis
31
+ this.basis[0] = Vec3(-sinT, cosT, 0);
32
+
33
+ const sphereCoordinates = Vec3(
34
+ rho * cosP * cosT,
35
+ rho * cosP * sinT,
36
+ rho * sinP
37
+ );
38
+ this.eye = sphereCoordinates.add(this.focalPoint);
39
+ return this;
40
+ }
41
+
42
+ rayShot(lambdaWithRays) {
43
+ return {
44
+ to: canvas => {
45
+ const w = canvas.width;
46
+ const h = canvas.height;
47
+ return canvas.map((x, y) => {
48
+ const dirInLocal = [
49
+ 2 * (x / w) - 1,
50
+ 2 * (y / h) - 1,
51
+ 1
52
+ ]
53
+ const dir = this.basis[0].scale(dirInLocal[0])
54
+ .add(this.basis[1].scale(dirInLocal[1]))
55
+ .add(this.basis[2].scale(dirInLocal[2]))
56
+ .normalize()
57
+ return lambdaWithRays(Ray(this.eye, dir));
58
+ });
59
+ }
60
+ }
61
+ }
62
+
63
+ sceneShot(scene) {
64
+ const lambda = ray => {
65
+ return scene.interceptWith(ray)
66
+ .map(([pos, normal]) => {
67
+ return Color.ofRGB(
68
+ (normal.get(0) + 1) / 2,
69
+ (normal.get(1) + 1) / 2,
70
+ (normal.get(2) + 1) / 2
71
+ )
72
+ })
73
+ .orElse(() => {
74
+ return Color.BLACK;
75
+ })
76
+ }
77
+ return this.rayShot(lambda);
78
+ }
79
+
80
+ reverseShot(scene) {
81
+ return {
82
+ to: canvas => {
83
+ canvas.fill(Color.BLACK);
84
+ const w = canvas.width;
85
+ const h = canvas.height;
86
+ const zBuffer = new Float64Array(w * h)
87
+ .fill(Number.MAX_VALUE);
88
+ scene.getElements().forEach((point) => {
89
+ let pointInCamCoord = point.position.sub(this.eye);
90
+ pointInCamCoord = Vec3(
91
+ this.basis[0].dot(pointInCamCoord),
92
+ this.basis[1].dot(pointInCamCoord),
93
+ this.basis[2].dot(pointInCamCoord)
94
+ )
95
+ //frustum culling
96
+ const z = pointInCamCoord.get(2);
97
+ if (z < this.distanceToPlane) return;
98
+
99
+ //project
100
+ const projectedPoint = pointInCamCoord
101
+ .scale(this.distanceToPlane / z);
102
+
103
+ // canvas coords
104
+ let x = w / 2 + projectedPoint.get(0) * w;
105
+ let y = h / 2 + projectedPoint.get(1) * h;
106
+ x = Math.floor(x);
107
+ y = Math.floor(y);
108
+ if (x < 0 || x >= w || y < 0 || y >= h) return;
109
+ const radius = Math.floor(Math.max(1, Math.min(10, 10 - z)));
110
+ for (let k = -radius; k < radius; k++) {
111
+ for (let l = -radius; l < radius; l++) {
112
+ const xl = Math.max(0, Math.min(w - 1, x + k));
113
+ const yl = Math.floor(y + l);
114
+ const j = xl;
115
+ const i = h - 1 - yl;
116
+ const zBufferIndex = Math.floor(w * i + j);
117
+ if (z < zBuffer[zBufferIndex]) {
118
+ zBuffer[zBufferIndex] = z;
119
+ canvas.setPxl(
120
+ xl,
121
+ yl,
122
+ point.color
123
+ )
124
+ }
125
+ }
126
+ }
127
+ });
128
+ canvas.paint();
129
+ return canvas;
130
+ }
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,203 @@
1
+ import Color from "../Color/Color";
2
+ import { MAX_8BIT } from "../Utils/Constants";
3
+ import { clipLine } from "../Utils/Math";
4
+
5
+ export default class Canvas {
6
+
7
+ constructor(canvas) {
8
+ this._canvas = canvas;
9
+ this._width = canvas.width;
10
+ this._height = canvas.height;
11
+ this._ctx = this._canvas.getContext("2d", { willReadFrequently: true });
12
+ this._imageData = this._ctx.getImageData(0, 0, this._width, this._height);
13
+ this._image = this._imageData.data;
14
+ }
15
+
16
+ get width() {
17
+ return this._canvas.width;
18
+ }
19
+
20
+ get height() {
21
+ return this._canvas.height;
22
+ }
23
+
24
+ get DOM() {
25
+ return this._canvas;
26
+ }
27
+
28
+ /**
29
+ * color: Color
30
+ */
31
+ fill(color) {
32
+ return this.map(() => color);
33
+ }
34
+
35
+ /**
36
+ * lambda: (x: Number, y: Number, c: color) => Color
37
+ */
38
+ map(lambda) {
39
+ const n = this._image.length;
40
+ const w = this._width;
41
+ const h = this._height;
42
+ for (let k = 0; k < n; k += 4) {
43
+ const i = Math.floor(k / (4 * w));
44
+ const j = Math.floor((k / 4) % w);
45
+ const x = j;
46
+ const y = h - 1 - i;
47
+ const color = lambda(x, y);
48
+ this._image[k] = color.red * MAX_8BIT;
49
+ this._image[k + 1] = color.green * MAX_8BIT;
50
+ this._image[k + 2] = color.blue * MAX_8BIT;
51
+ this._image[k + 3] = MAX_8BIT;
52
+ }
53
+ return this.paint();
54
+ }
55
+
56
+ setPxl(x, y, color) {
57
+ const w = this._width;
58
+ const h = this._height;
59
+ const i = h - 1 - y;
60
+ const j = x;
61
+ let index = 4 * (w * i + j);
62
+ this._image[index] = color.red * MAX_8BIT;
63
+ this._image[index + 1] = color.green * MAX_8BIT;
64
+ this._image[index + 2] = color.blue * MAX_8BIT;
65
+ this._image[index + 3] = MAX_8BIT;
66
+ return this;
67
+ }
68
+
69
+ getPxl(x, y) {
70
+ const w = this._width;
71
+ const h = this._height;
72
+ const i = h - 1 - y;
73
+ const j = x;
74
+ let index = 4 * (w * i + j);
75
+ return Color.ofRGBRaw(this._image[index], this._image[index + 1], this._image[index + 2]);
76
+ }
77
+
78
+ // drawLine(p1, p2, shader) {
79
+ // const { width, height } = this.canvas;
80
+ // const line = clipLine(p1, p2, new Box(Vec2(0, 0), Vec2(width, height)));
81
+ // if (line.length === 0) return;
82
+ // const [p0, p1] = line;
83
+ // const v = p1.sub(p0);
84
+ // const n = v.map(Math.abs).fold((e, x) => e + x);
85
+ // for (let k = 0; k < n; k++) {
86
+ // const s = k / n;
87
+ // const x = p0.add(v.scale(s)).map(Math.floor);
88
+ // const [i, j] = x.toArray();
89
+ // const index = 4 * (i * width + j);
90
+ // const color = shader(i, j);
91
+ // this.data[index] = color.red;
92
+ // this.data[index + 1] = color.green;
93
+ // this.data[index + 2] = color.blue;
94
+ // this.data[index + 3] = 255;
95
+ // }
96
+ // return this;
97
+ // }
98
+
99
+ paint() {
100
+ this._ctx.putImageData(this._imageData, 0, 0);
101
+ return this;
102
+ }
103
+
104
+ onMouseDown(lambda) {
105
+ this._canvas.addEventListener("mousedown", handleMouse(this, lambda), false);
106
+ this._canvas.addEventListener("touchstart", handleMouse(this, lambda), false);
107
+ return this;
108
+ }
109
+
110
+ onMouseUp(lambda) {
111
+ this._canvas.addEventListener("mouseup", handleMouse(this, lambda), false);
112
+ this._canvas.addEventListener("touchend", handleMouse(this, lambda), false);
113
+ return this;
114
+ }
115
+
116
+ onMouseMove(lambda) {
117
+ this._canvas.addEventListener("mousemove", handleMouse(this, lambda), false);
118
+ this._canvas.addEventListener("touchmove", handleMouse(this, lambda), false);
119
+ return this;
120
+ }
121
+
122
+ onMouseWheel(lambda) {
123
+ this._canvas.addEventListener("wheel", lambda, false)
124
+ }
125
+
126
+ resize(width, height) {
127
+ this._canvas.width = width;
128
+ this._canvas.height = height;
129
+ this._width = this._canvas.width;
130
+ this._height = this._canvas.height;
131
+ this._ctx = this._canvas.getContext("2d", { willReadFrequently: true });
132
+ this._imageData = this._ctx.getImageData(0, 0, this._width, this._height);
133
+ this._image = this._imageData.data;
134
+ }
135
+
136
+ startVideoRecorder() {
137
+ let responseBlob;
138
+ const canvasSnapshots = [];
139
+ const stream = this._canvas.captureStream();
140
+ const recorder = new MediaRecorder(stream);
141
+ recorder.addEventListener("dataavailable", e => canvasSnapshots.push(e.data));
142
+ recorder.start();
143
+ recorder.onstop = () => (responseBlob = new Blob(canvasSnapshots, { type: 'video/webm' }));
144
+ return {
145
+ stop: () => new Promise((re) => {
146
+ recorder.stop();
147
+ setTimeout(() => re(responseBlob));
148
+ })
149
+ };
150
+ }
151
+
152
+ static ofSize(width, height) {
153
+ const canvas = document.createElement('canvas');
154
+ canvas.setAttribute('width', width);
155
+ canvas.setAttribute('height', height);
156
+ return new Canvas(canvas);
157
+ }
158
+
159
+ static ofDOM(canvasDOM) {
160
+ return new Canvas(canvasDOM);
161
+ }
162
+
163
+ static ofCanvas(canvas) {
164
+ return new Canvas(canvas._canvas);
165
+ }
166
+
167
+ static ofUrl(url) {
168
+ return new Promise((resolve) => {
169
+ const img = document.createElement("img");
170
+ img.src = url;
171
+ img.onload = function () {
172
+ const canvas = document.createElement("canvas");
173
+ canvas.width = img.width;
174
+ canvas.height = img.height;
175
+ const ctx = canvas.getContext("2d");
176
+ ctx.drawImage(img, 0, 0);
177
+ resolve(Canvas.ofDOM(canvas));
178
+ };
179
+ });
180
+ }
181
+
182
+ static ofImage(image) {
183
+ const w = image.width;
184
+ const h = image.height;
185
+ return Canvas.ofSize(w, h)
186
+ .map((x, y) => {
187
+ return image.get(x, y);
188
+ })
189
+ }
190
+ }
191
+
192
+ function handleMouse(canvas, lambda) {
193
+ return event => {
194
+ const h = canvas.height;
195
+ const w = canvas.width;
196
+ const rect = canvas._canvas.getBoundingClientRect();
197
+ // different coordinates from canvas DOM image data
198
+ const mx = (event.clientX - rect.left) / rect.width, my = (event.clientY - rect.top) / rect.height;
199
+ const x = Math.floor(mx * w);
200
+ const y = Math.floor(h - 1 - my * h);
201
+ return lambda(x, y);
202
+ }
203
+ }
@@ -0,0 +1,83 @@
1
+ # Canvas
2
+
3
+ Abstraction for DOM canvas element. Canvas, can be though as a function from $\text{canvas}: \text{position} \rightarrow \text{color}$.
4
+
5
+ - $\text{position}$ is a product of two sets: $[0, W-1] \times [0, H-1]$
6
+ - $\text{color}$ is a product of three sets: $\text{red} \times \text{green} \times \text{blue} \times \text{alpha} = [0,1]^C$
7
+ - Where $W$ is the width, $H$ is the height and $C$ is the number of color channels.
8
+
9
+ ```
10
+ y H-1
11
+ ^
12
+ |
13
+ |
14
+ |
15
+ |
16
+ | W-1
17
+ +-------------> x
18
+ 0
19
+
20
+ ```
21
+
22
+ ## Details
23
+
24
+ While the Canvas API, tries to be user friendly, the reality of the DOM Canvas is different.
25
+
26
+ ```
27
+ 0 W-1
28
+ +-------------> y
29
+ |
30
+ |
31
+ | *
32
+ |
33
+ |
34
+ v x
35
+
36
+ H-1
37
+
38
+ ```
39
+
40
+ In reality the DOM canvas stores its data in a simple 1-dimensional array of length: $ \text{width}(W) \times \text{height}(H) \times \text{colors}(C)$. Hence, DOM canvas is more of a function $\text{index}: [0,W \times H \times C - 1] \rightarrow \text{(red | green | blue): }[0, 255]$.
41
+
42
+
43
+ Having the coordinate $\mathbf{x} = xe_1 + ye_2 + z e_3$. Where $x \in [0, H-1]$ and $y \in [0, W-1]$ represent the position and $z\in[0, C-1]$ represents the channel color, we can find its $\text{index}$, by computing the following equation:
44
+
45
+ $$\text{index}(x, y, z) = C W x + Cy + z = C(Wx + y) + z$$
46
+
47
+ > Note that $\text{index}(H - 1, W - 1, C - 1) = C * W * H - 1$.
48
+
49
+ Code to retrieve the color from the canvas
50
+
51
+ ```js
52
+ function retrieveColor(canvasDataArray, x, y) {
53
+ const index = C * (W * x + y);
54
+ const red = canvasDataArray[index];
55
+ const green = canvasDataArray[index + 1];
56
+ const blue = canvasDataArray[index + 2];
57
+ const alpha = canvasDataArray[index + 3];
58
+ return [red, green, blue, alpha]
59
+ }
60
+ ```
61
+
62
+ Code to retrieve coordinates $(x,y,z)$ based of $\text{index}$, we need to solve:
63
+
64
+ $$\text{index} = C(Wx + y) + z$$
65
+
66
+ Seems impossible to solve this because there is only one equation and we have 3 variables, but looking more closely we found the solution:
67
+
68
+ Finding $z \in [0, C-1]$:
69
+ $$\text{index} \mod C = (C(Wx+y)+z) \mod C $$
70
+ $$\text{index} \mod C = z $$
71
+
72
+ Finding $y \in [0, W-1]$
73
+
74
+ $$\lfloor \text{index} / C \rfloor = \lfloor(C(Wx+y)+z) / C \rfloor$$
75
+ $$\lfloor \text{index} / C \rfloor \mod W = Wx+y \mod W$$
76
+ $$\lfloor \text{index} / C \rfloor \mod W = y$$
77
+
78
+ Finding $x \in [0, H-1]$
79
+
80
+ $$\lfloor \text{index} / CW \rfloor = \lfloor(C(Wx+y)+z) / CW \rfloor$$
81
+
82
+ $$\lfloor \text{index} / CW \rfloor = x$$
83
+
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Class that abstracts colors.
3
+ *
4
+ * Here colors are represented as [0,1]^3 vector.
5
+ */
6
+
7
+ const MAX_8BIT = 255;
8
+ export default class Color {
9
+ constructor(rbg) {
10
+ this.rgb = rbg;
11
+ }
12
+
13
+ getRGB() {
14
+ return this.rgb;
15
+ }
16
+
17
+ get red() {
18
+ return this.rgb[0];
19
+ }
20
+
21
+ get green() {
22
+ return this.rgb[1];
23
+ }
24
+
25
+ get blue() {
26
+ return this.rgb[2];
27
+ }
28
+
29
+ add(color) {
30
+ return Color.ofRGB(this.rgb[0] + color.red, this.rgb[1] + color.green, this.rgb[2] + color.blue );
31
+ }
32
+
33
+ scale(r) {
34
+ const ans = this.rgb.map(c => Math.min(1, Math.max(0, c * r)));
35
+ return new Color(ans);
36
+ }
37
+
38
+ /**
39
+ *
40
+ * @param {Color} color
41
+ * @returns {Boolean}
42
+ */
43
+ equals(color) {
44
+ return (
45
+ this.rgb[0] === color.rgb[0] &&
46
+ this.rgb[1] === color.rgb[1] &&
47
+ this.rgb[2] === color.rgb[2]
48
+ );
49
+ }
50
+
51
+ static ofRGB(red = 0, green = 0, blue = 0) {
52
+ const rgb = new Float64Array(3);
53
+ rgb[0] = red;
54
+ rgb[1] = green;
55
+ rgb[2] = blue;
56
+ return new Color(rgb);
57
+ }
58
+
59
+ static ofRGBRaw(red = 0, green = 0, blue = 0) {
60
+ const rgb = new Float64Array(3);
61
+ rgb[0] = red / MAX_8BIT;
62
+ rgb[1] = green / MAX_8BIT;
63
+ rgb[2] = blue / MAX_8BIT;
64
+ return new Color(rgb);
65
+ }
66
+
67
+ static random() {
68
+ const r = () => Math.random();
69
+ return Color.ofRGB(r(), r(), r());
70
+ }
71
+
72
+ static RED = Color.ofRGB(1, 0, 0);
73
+ static GREEN = Color.ofRGB(0, 1, 0);
74
+ static BLUE = Color.ofRGB(0, 0, 1);
75
+ static BLACK = Color.ofRGB(0, 0, 0);
76
+ static WHITE = Color.ofRGB(1, 1, 1);
77
+ }