Fork me on GitHub

立体solid.ts

1 import {chunk} from '@mathigon/core';
2 import {$html, $N, Browser, CustomElementView, register, slide} from '@mathigon/boost';
3 import {create3D, Graphics3D} from './webgl';
4 
5 const STROKE_COLOR = 0x666666;
6 const LINE_RADIUS = 0.012;
7 const LINE_SEGMENTS = 4;
8 const POINT_RADIUS = 0.08;
import

 

 

 

 1 // Utilities
 2 
 3 type Vector = [number, number, number];
 4 
 5 // Custom methods on the THREE.Object3D class
 6 interface Object3D extends THREE.Object3D {
 7   setClipPlanes?: (planes: THREE.Plane[]) => void;
 8   updateGeometry?: (gep: THREE.Geometry) => void;
 9   updateEnds?: (f: Vector, t: Vector) => void;
10 }
11 
12 function rotate($solid: Solid, animate = true, speed = 1) {
13   // TODO Damping after mouse movement
14   // TODO Better mouse-to-point mapping
15 
16   // Only Chrome is fast enough to support auto-rotation.
17   const autoRotate = animate && Browser.isChrome && !Browser.isMobile;
18   $solid.css('cursor', 'grab');
19 
20   let dragging = false;
21   let visible = false;
22 
23   function frame() {
24     if (visible && autoRotate) requestAnimationFrame(frame);
25     $solid.scene.draw();
26     if (!dragging) $solid.object.rotation.y += speed * 0.012;
27   }
28 
29   if (autoRotate) {
30     $solid.scene.$canvas.on('enterViewport', () => { visible = true; frame(); });
31     $solid.scene.$canvas.on('exitViewport', () => { visible = false; });
32   } else {
33     setTimeout(frame);
34   }
35 
36   // The 1.1 creates rotations that are slightly faster than the mouse/finger.
37   const s = Math.PI / 2 / $solid.scene.$canvas.width * 1.1;
38 
39   slide($solid.scene.$canvas, {
40     start() {
41       dragging = true;
42       $html.addClass('grabbing');
43     },
44     move(posn, start, last) {
45       const d = posn.subtract(last).scale(s);
46       const q = new THREE.Quaternion().setFromEuler(new THREE.Euler(d.y, d.x));
47       $solid.object.quaternion.multiplyQuaternions(q, $solid.object.quaternion);
48       $solid.trigger('rotate', {quaternion: $solid.object.quaternion});
49       if (!autoRotate) frame();
50     },
51     end() {
52       dragging = false;
53       $html.removeClass('grabbing');
54     }
55   });
56 }
57 
58 function createEdges(geometry: THREE.Geometry, material: THREE.Material, maxAngle?: number) {
59   const obj = new THREE.Object3D();
60   if (!maxAngle) return obj;
61 
62   const edges = new THREE.EdgesGeometry(geometry, maxAngle);
63   const edgeData = edges.attributes.position.array as number[];
64   const points = chunk(chunk(edgeData, 3).map(p => new THREE.Vector3(...p)), 2);
65 
66   for (const edge of points) {
67     const curve = new THREE.LineCurve3(edge[0], edge[1]);
68     const geometry = new THREE.TubeGeometry(curve, 1, LINE_RADIUS, LINE_SEGMENTS);
69     obj.add(new THREE.Mesh(geometry, material));
70   }
71 
72   return obj;
73 }
Utilities

 

 

 

 

 

 1 // Custom Element
 2 
 3 @register('x-solid')
 4 export class Solid extends CustomElementView {
 5   private isReady = false;
 6   object!: THREE.Object3D;
 7   scene!: Graphics3D;
 8 
 9   async ready() {
10     const size = this.attr('size').split(',');
11     const width = +size[0];
12     const height = size.length > 1 ? +size[1] : width;
13 
14     this.css({width: width + 'px', height: height + 'px'});
15 
16     this.scene = await create3D(this, 35, 2 * width, 2 * height);
17     this.scene.camera.position.set(0, 3, 6);
18     this.scene.camera.up = new THREE.Vector3(0, 1, 0);
19     this.scene.camera.lookAt(new THREE.Vector3(0, 0, 0));
20 
21     const light1 = new THREE.AmbientLight(0x555555);
22     this.scene.add(light1);
23 
24     const light2 = new THREE.PointLight(0xffffff);
25     light2.position.set(3, 4.5, 6);
26     this.scene.add(light2);
27 
28     this.object = new THREE.Object3D();
29     this.scene.add(this.object);
30 
31     this.trigger('loaded');
32     this.isReady = true;
33   }
34 
35   addMesh(fn: (scene: Graphics3D) => THREE.Object3D[]|void) {
36     if (this.isReady) {
37       this.addMeshCallback(fn);
38     } else {
39       this.one('loaded', () => this.addMeshCallback(fn));
40     }
41   }
42 
43   addMeshCallback(fn: (scene: Graphics3D) => THREE.Object3D[]|void) {
44     const items = fn(this.scene) || [];
45     for (const i of items)  this.object.add(i);
46 
47     if (!this.hasAttr('static')) {
48       const speed = +this.attr('rotate') || 1;
49       rotate(this, this.hasAttr('rotate'), speed);
50     }
51 
52     this.scene.draw();
53   }
54 
55   rotate(q: THREE.Quaternion) {
56     this.object.quaternion.set(q.x, q.y, q.z, q.w);
57     this.scene.draw();
58   }
Custom Element

 

 

 

 

 

  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 = '\nvec3 transformed = position + vec3(normal) * 0.02;\n';
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   }
Element Creation Utilities

 

 

 

 

 

 1 // Materials
 2 
 3   static solidMaterial(color: number, flatShading = false) {
 4     return new THREE.MeshPhongMaterial({
 5       side: THREE.DoubleSide,
 6       transparent: true,
 7       opacity: 0.9,
 8       specular: 0x222222,
 9       // depthWrite: false,
10       color, flatShading
11     });
12   }
13 
14   static translucentMaterial(color: number, opacity = 0.1) {
15     return new THREE.MeshLambertMaterial({
16       side: THREE.DoubleSide,
17       transparent: true,
18       depthWrite: false,
19       opacity, color
20     });
21   }
22 }
Materials

 

posted @ 2020-03-03 23:13  小奔奔  阅读(154)  评论(0编辑  收藏  举报