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.
- package/.eslintrc.yml +8 -0
- package/LICENSE +201 -0
- package/README.md +54 -0
- package/assets/bunny.obj +7474 -0
- package/assets/expand.svg +1 -0
- package/assets/great.jpg +0 -0
- package/assets/kakashi.jpg +0 -0
- package/assets/spot.obj +11999 -0
- package/assets/spot.png +0 -0
- package/assets/torus.obj +3137 -0
- package/assets/x.svg +1 -0
- package/bun.lockb +0 -0
- package/bundle.js +39 -0
- package/dist/node/index.js +37048 -0
- package/dist/web/index.js +1511 -0
- package/index.css +129 -0
- package/index.html +19 -0
- package/index.js +534 -0
- package/package.json +40 -0
- package/src/Animation/Animation.js +61 -0
- package/src/Box/Box.js +105 -0
- package/src/Camera/Camera.js +133 -0
- package/src/Canvas/Canvas.js +203 -0
- package/src/Canvas/README.md +83 -0
- package/src/Color/Color.js +77 -0
- package/src/DomBuilder/DomBuilder.js +126 -0
- package/src/IO/IO.js +120 -0
- package/src/Image/Image.js +138 -0
- package/src/Image/README.md +33 -0
- package/src/Monads/Monads.js +28 -0
- package/src/Ray/Ray.js +7 -0
- package/src/Scene/Mesh.js +103 -0
- package/src/Scene/NaiveScene.js +77 -0
- package/src/Scene/Point.js +109 -0
- package/src/Scene/PointCloud.js +51 -0
- package/src/Scene/Scene.js +200 -0
- package/src/Stream/Stream.js +10 -0
- package/src/Utils/Constants.js +1 -0
- package/src/Utils/Math.js +65 -0
- package/src/Utils/Utils.js +39 -0
- package/src/Vector/Vector.js +495 -0
- package/src/index.js +32 -0
- package/src/index.node.js +5 -0
- package/test/node/amazing_shader.js +56 -0
- package/test/node/bunny.js +55 -0
- package/test/node/bunny_parallel.js +102 -0
- package/test/node/image2rgb.js +57 -0
- package/test/node/image_test.js +38 -0
- package/test/node/lorentz.js +0 -0
- package/test/web/amazing_shader.js +60 -0
- package/test/web/amazing_shader_2.js +54 -0
- package/test/web/bunny.js +72 -0
- package/test/web/image2rgb.js +77 -0
- package/test/web/interactive_wave.js +108 -0
- package/test/web/lorenz.js +60 -0
- package/test/web/mandelbrot.js +59 -0
- package/test/web/rotating_grid.js +62 -0
- package/test/web/signed_bunny.js +139 -0
- package/test/web/signed_distance.js +95 -0
- package/test/web/simple_animation.js +39 -0
- package/test/web/simple_shader.js +14 -0
- package/test/web/six_spheres.js +102 -0
- package/test/web/wave_equation.js +93 -0
- package/vs-monaco/package/LICENSE +21 -0
- package/vs-monaco/package/ThirdPartyNotices.txt +448 -0
- package/vs-monaco/package/min/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.de.js +8 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.es.js +8 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.fr.js +8 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.it.js +8 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.ja.js +8 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.js +8 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.ko.js +8 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.ru.js +8 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.zh-cn.js +8 -0
- package/vs-monaco/package/min/vs/base/common/worker/simpleWorker.nls.zh-tw.js +8 -0
- package/vs-monaco/package/min/vs/base/worker/workerMain.js +27 -0
- package/vs-monaco/package/min/vs/basic-languages/abap/abap.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/apex/apex.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/azcli/azcli.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/bat/bat.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/bicep/bicep.js +11 -0
- package/vs-monaco/package/min/vs/basic-languages/cameligo/cameligo.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/clojure/clojure.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/coffee/coffee.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/cpp/cpp.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/csharp/csharp.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/csp/csp.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/css/css.js +12 -0
- package/vs-monaco/package/min/vs/basic-languages/cypher/cypher.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/dart/dart.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/dockerfile/dockerfile.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/ecl/ecl.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/elixir/elixir.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/flow9/flow9.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/freemarker2/freemarker2.js +12 -0
- package/vs-monaco/package/min/vs/basic-languages/fsharp/fsharp.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/go/go.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/graphql/graphql.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/handlebars/handlebars.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/hcl/hcl.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/html/html.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/ini/ini.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/java/java.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/javascript/javascript.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/julia/julia.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/kotlin/kotlin.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/less/less.js +11 -0
- package/vs-monaco/package/min/vs/basic-languages/lexon/lexon.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/liquid/liquid.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/lua/lua.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/m3/m3.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/markdown/markdown.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/mdx/mdx.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/mips/mips.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/msdax/msdax.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/mysql/mysql.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/objective-c/objective-c.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/pascal/pascal.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/pascaligo/pascaligo.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/perl/perl.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/pgsql/pgsql.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/php/php.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/pla/pla.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/postiats/postiats.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/powerquery/powerquery.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/powershell/powershell.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/protobuf/protobuf.js +11 -0
- package/vs-monaco/package/min/vs/basic-languages/pug/pug.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/python/python.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/qsharp/qsharp.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/r/r.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/razor/razor.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/redis/redis.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/redshift/redshift.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/ruby/ruby.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/rust/rust.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/sb/sb.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/scala/scala.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/scheme/scheme.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/scss/scss.js +12 -0
- package/vs-monaco/package/min/vs/basic-languages/shell/shell.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/solidity/solidity.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/sophia/sophia.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/sparql/sparql.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/sql/sql.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/st/st.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/swift/swift.js +13 -0
- package/vs-monaco/package/min/vs/basic-languages/systemverilog/systemverilog.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/tcl/tcl.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/twig/twig.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/typescript/typescript.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/vb/vb.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/wgsl/wgsl.js +307 -0
- package/vs-monaco/package/min/vs/basic-languages/xml/xml.js +10 -0
- package/vs-monaco/package/min/vs/basic-languages/yaml/yaml.js +10 -0
- package/vs-monaco/package/min/vs/editor/editor.main.css +6 -0
- package/vs-monaco/package/min/vs/editor/editor.main.js +745 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.de.js +31 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.es.js +31 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.fr.js +29 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.it.js +29 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.ja.js +31 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.js +29 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.ko.js +29 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.ru.js +31 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.zh-cn.js +31 -0
- package/vs-monaco/package/min/vs/editor/editor.main.nls.zh-tw.js +29 -0
- package/vs-monaco/package/min/vs/language/css/cssMode.js +13 -0
- package/vs-monaco/package/min/vs/language/css/cssWorker.js +81 -0
- package/vs-monaco/package/min/vs/language/html/htmlMode.js +13 -0
- package/vs-monaco/package/min/vs/language/html/htmlWorker.js +453 -0
- package/vs-monaco/package/min/vs/language/json/jsonMode.js +15 -0
- package/vs-monaco/package/min/vs/language/json/jsonWorker.js +36 -0
- package/vs-monaco/package/min/vs/language/typescript/tsMode.js +20 -0
- package/vs-monaco/package/min/vs/language/typescript/tsWorker.js +37016 -0
- 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
|
+
}
|