1 // Element Creation Utilities
2
3 addLabel(text: string, posn: Vector, color = STROKE_COLOR, margin = '') {
4 const $label = $N('div', {text, class: 'label3d'});
5 $label.css('color', '#' + color.toString(16).padStart(6, '0'));
6 if (margin) $label.css('margin', margin);
7
8 let posn1 = new THREE.Vector3(...posn);
9 this.scene.$canvas.insertAfter($label);
10
11 this.scene.onDraw(() => {
12 const p = posn1.clone().applyQuaternion(this.object.quaternion)
13 .add(this.object.position).project(this.scene.camera);
14 $label.css('left', (1 + p.x) * this.scene.$canvas.width / 2 + 'px');
15 $label.css('top', (1 - p.y) * this.scene.$canvas.height / 2 + 'px');
16 });
17
18 return {
19 updatePosition(posn: Vector) {
20 posn1 = new THREE.Vector3(...posn);
21 }
22 };
23 }
24
25 addArrow(from: Vector, to: Vector, color = STROKE_COLOR) {
26 const material = new THREE.MeshBasicMaterial({color});
27 const obj = new THREE.Object3D() as Object3D;
28
29 const height = new THREE.Vector3(...from).distanceTo(new THREE.Vector3(...to));
30 const line = new THREE.CylinderGeometry(0.02, 0.02, height - 0.3, 8, 1, true);
31 obj.add(new THREE.Mesh(line, material));
32
33 const start = new THREE.ConeGeometry(0.1, 0.15, 16, 1);
34 start.translate(0, height/2 - 0.1, 0);
35 obj.add(new THREE.Mesh(start, material));
36
37 const end = new THREE.ConeGeometry(0.1, 0.15, 16, 1);
38 end.rotateX(Math.PI);
39 end.translate(0, -height/2 + 0.1, 0);
40 obj.add(new THREE.Mesh(end, material));
41
42 obj.updateEnds = function(f: Vector, t: Vector) {
43 // TODO Support changing the height of the arrow.
44 const q = new THREE.Quaternion();
45 const v = new THREE.Vector3(t[0]-f[0], t[1]-f[1], t[2]-f[2]).normalize();
46 q.setFromUnitVectors(new THREE.Vector3(0, 1, 0), v);
47 obj.setRotationFromQuaternion(q);
48 obj.position.set((f[0]+t[0])/2, (f[1]+t[1])/2, (f[2]+t[2])/2);
49 };
50
51 obj.updateEnds(from, to);
52 this.object.add(obj);
53 return obj;
54 }
55
56 addCircle(radius: number, color = STROKE_COLOR, segments = 64) {
57 const path = new THREE.Curve<THREE.Vector3>();
58 path.getPoint = function(t) {
59 const a = 2 * Math.PI * t;
60 return new THREE.Vector3(radius * Math.cos(a), radius * Math.sin(a), 0);
61 };
62
63 const material = new THREE.MeshBasicMaterial({color});
64 const geometry = new THREE.TubeGeometry(path, segments, LINE_RADIUS, LINE_SEGMENTS);
65
66 const mesh = new THREE.Mesh(geometry, material);
67 this.object.add(mesh);
68 return mesh;
69 }
70
71 addPoint(position: Vector, color = STROKE_COLOR) {
72 const material = new THREE.MeshBasicMaterial({color});
73 const geometry = new THREE.SphereGeometry(POINT_RADIUS, 16, 16);
74
75 const mesh = new THREE.Mesh(geometry, material);
76 mesh.position.set(...position);
77 this.object.add(mesh);
78 }
79
80 addSolid(geo: THREE.Geometry, color: number, maxAngle = 5, flatShading = false) {
81 const edgeMaterial = new THREE.LineBasicMaterial({color: 0xffffff});
82 const edges = new THREE.EdgesGeometry(geo, maxAngle);
83
84 const obj = new THREE.Object3D();
85 obj.add(new THREE.LineSegments(edges, edgeMaterial));
86 obj.add(new THREE.Mesh(geo, Solid.solidMaterial(color, flatShading)));
87
88 this.object.add(obj);
89 return obj;
90 }
91
92 // TODO merge addOutlined() and addWireframe(), by looking at
93 // geometry.isConeGeometry etc.
94
95 // A translucent material with a solid border.
96 addOutlined(geo: THREE.Geometry, color = 0xaaaaaa, maxAngle = 5, opacity = 0.1, strokeColor?: number) {
97 const solidMaterial = Solid.translucentMaterial(color, opacity);
98 const solid = new THREE.Mesh(geo, solidMaterial);
99
100 const edgeMaterial = new THREE.MeshBasicMaterial({color: strokeColor || STROKE_COLOR});
101 let edges = createEdges(geo, edgeMaterial, maxAngle);
102
103 const obj = new THREE.Object3D() as Object3D;
104 obj.add(solid, edges);
105
106 obj.setClipPlanes = function(planes: THREE.Plane[]) {
107 solidMaterial.clippingPlanes = planes;
108 };
109
110 obj.updateGeometry = function(geo: THREE.Geometry) {
111 solid.geometry.dispose();
112 solid.geometry = geo;
113 obj.remove(edges);
114 edges = createEdges(geo, edgeMaterial, maxAngle);
115 obj.add(edges);
116 };
117
118 this.object.add(obj);
119 return obj;
120 }
121
122 // Like .addOutlined, but we also add outlines for curved edges (e.g. of
123 // a sphere or cylinder).
124 addWireframe(geometry: THREE.Geometry, color = 0xaaaaaa, maxAngle = 5, opacity = 0.1) {
125 const solid = this.addOutlined(geometry, color, maxAngle, opacity);
126
127 const outlineMaterial = new THREE.MeshBasicMaterial({
128 color: STROKE_COLOR,
129 side: THREE.BackSide
130 });
131 outlineMaterial.onBeforeCompile = function(shader) {
132 const token = '#include <begin_vertex>';
133 const customTransform = '
vec3 transformed = position + vec3(normal) * 0.02;
';
134 shader.vertexShader = shader.vertexShader.replace(token,customTransform)
135 };
136 const outline = new THREE.Mesh(geometry, outlineMaterial);
137
138 const knockoutMaterial = new THREE.MeshBasicMaterial({
139 color: 0xffffff,
140 side: THREE.BackSide
141 });
142 const knockout = new THREE.Mesh(geometry, knockoutMaterial);
143
144 const obj = new THREE.Object3D() as Object3D;
145 obj.add(solid, outline, knockout);
146
147 obj.setClipPlanes = function(planes: THREE.Plane[]) {
148 if (solid.setClipPlanes) solid.setClipPlanes(planes);
149 for (const m of [outlineMaterial, knockoutMaterial])
150 m.clippingPlanes = planes;
151 };
152
153 obj.updateGeometry = function(geo: THREE.Geometry) {
154 if (solid.updateGeometry) solid.updateGeometry(geo);
155 for (const mesh of [outline, knockout]) {
156 mesh.geometry.dispose();
157 mesh.geometry = geo;
158 }
159 };
160
161 this.object.add(obj);
162 return obj;
163 }