Skip to content

Commit 04d5381

Browse files
authored
Refactor 3D view (#341)
* Create main view model * Move spinner out of 3d view * Finish rebase
1 parent c0219d7 commit 04d5381

File tree

12 files changed

+1445
-1223
lines changed

12 files changed

+1445
-1223
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as React from 'react';
2+
import { User } from '@jupyterlab/services';
3+
interface IProps {
4+
remoteUser: User.IIdentity | null | undefined;
5+
}
6+
export function FollowIndicator(props: IProps) {
7+
return props.remoteUser?.display_name ? (
8+
<div
9+
style={{
10+
position: 'absolute',
11+
top: 1,
12+
right: 3,
13+
background: props.remoteUser.color
14+
}}
15+
>
16+
{`Following ${props.remoteUser.display_name}`}
17+
</div>
18+
) : null;
19+
}

packages/base/src/3dview/helpers.ts

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import { IDict, IParsedShape } from '@jupytercad/schema';
2+
import * as THREE from 'three';
3+
import {
4+
acceleratedRaycast,
5+
computeBoundsTree,
6+
disposeBoundsTree
7+
} from 'three-mesh-bvh';
8+
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
9+
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
10+
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js';
11+
12+
import { getCSSVariableColor } from '../tools';
13+
14+
export const DEFAULT_LINEWIDTH = 4;
15+
export const SELECTED_LINEWIDTH = 12;
16+
17+
// Apply the BVH extension
18+
19+
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
20+
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
21+
THREE.Mesh.prototype.raycast = acceleratedRaycast;
22+
23+
export const DEFAULT_MESH_COLOR_CSS = '--jp-inverse-layout-color4';
24+
export const DEFAULT_EDGE_COLOR_CSS = '--jp-inverse-layout-color2';
25+
export const SELECTED_MESH_COLOR_CSS = '--jp-brand-color0';
26+
27+
export const DEFAULT_MESH_COLOR = new THREE.Color(
28+
getCSSVariableColor(DEFAULT_MESH_COLOR_CSS)
29+
);
30+
export const DEFAULT_EDGE_COLOR = new THREE.Color(
31+
getCSSVariableColor(DEFAULT_EDGE_COLOR_CSS)
32+
);
33+
export const SELECTED_MESH_COLOR = new THREE.Color(
34+
getCSSVariableColor(SELECTED_MESH_COLOR_CSS)
35+
);
36+
37+
export type BasicMesh = THREE.Mesh<
38+
THREE.BufferGeometry,
39+
THREE.MeshBasicMaterial
40+
>;
41+
42+
/**
43+
* The interface for a 3D pointer
44+
*/
45+
export interface IPointer {
46+
parent: BasicMesh;
47+
readonly mesh: BasicMesh;
48+
}
49+
50+
/**
51+
* The result of mesh picking, contains the picked mesh and the 3D position of the pointer.
52+
*/
53+
export interface IPickedResult {
54+
mesh: BasicMesh;
55+
position: THREE.Vector3;
56+
}
57+
58+
export function projectVector(options: {
59+
vector: THREE.Vector3;
60+
camera: THREE.Camera;
61+
width: number;
62+
height: number;
63+
}): THREE.Vector2 {
64+
const { vector, camera, width, height } = options;
65+
const copy = new THREE.Vector3().copy(vector);
66+
67+
copy.project(camera);
68+
69+
return new THREE.Vector2(
70+
(0.5 + copy.x / 2) * width,
71+
(0.5 - copy.y / 2) * height
72+
);
73+
}
74+
75+
export function computeExplodedState(options: {
76+
mesh: BasicMesh;
77+
boundingGroup: THREE.Box3;
78+
factor: number;
79+
}) {
80+
const { mesh, boundingGroup, factor } = options;
81+
const center = new THREE.Vector3();
82+
boundingGroup.getCenter(center);
83+
84+
const oldGeometryCenter = new THREE.Vector3();
85+
mesh.geometry.boundingBox?.getCenter(oldGeometryCenter);
86+
87+
const centerToMesh = new THREE.Vector3(
88+
oldGeometryCenter.x - center.x,
89+
oldGeometryCenter.y - center.y,
90+
oldGeometryCenter.z - center.z
91+
);
92+
const distance = centerToMesh.length() * factor;
93+
94+
centerToMesh.normalize();
95+
96+
const newGeometryCenter = new THREE.Vector3(
97+
oldGeometryCenter.x + distance * centerToMesh.x,
98+
oldGeometryCenter.y + distance * centerToMesh.y,
99+
oldGeometryCenter.z + distance * centerToMesh.z
100+
);
101+
102+
return {
103+
oldGeometryCenter,
104+
newGeometryCenter,
105+
vector: centerToMesh,
106+
distance
107+
};
108+
}
109+
110+
export function buildShape(options: {
111+
objName: string;
112+
data: IParsedShape;
113+
clippingPlanes: THREE.Plane[];
114+
selected: boolean;
115+
guidata?: IDict;
116+
}): {
117+
meshGroup: THREE.Group;
118+
mainMesh: THREE.Mesh<THREE.BufferGeometry, THREE.MeshPhongMaterial>;
119+
edgesMeshes: LineSegments2[];
120+
} | null {
121+
const { objName, data, guidata, clippingPlanes, selected } = options;
122+
const { faceList, edgeList, jcObject } = data;
123+
124+
const vertices: Array<number> = [];
125+
const normals: Array<number> = [];
126+
const triangles: Array<number> = [];
127+
128+
let vInd = 0;
129+
if (faceList.length === 0 && edgeList.length === 0) {
130+
return null;
131+
}
132+
faceList.forEach(face => {
133+
// Copy Vertices into three.js Vector3 List
134+
vertices.push(...face.vertexCoord);
135+
normals.push(...face.normalCoord);
136+
137+
// Sort Triangles into a three.js Face List
138+
for (let i = 0; i < face.triIndexes.length; i += 3) {
139+
triangles.push(
140+
face.triIndexes[i + 0] + vInd,
141+
face.triIndexes[i + 1] + vInd,
142+
face.triIndexes[i + 2] + vInd
143+
);
144+
}
145+
146+
vInd += face.vertexCoord.length / 3;
147+
});
148+
149+
let color = DEFAULT_MESH_COLOR;
150+
let visible = jcObject.visible;
151+
if (guidata && guidata[objName]) {
152+
const objdata = guidata[objName];
153+
154+
if (Object.prototype.hasOwnProperty.call(objdata, 'color')) {
155+
const rgba = objdata['color'] as number[];
156+
color = new THREE.Color(rgba[0], rgba[1], rgba[2]);
157+
}
158+
159+
if (Object.prototype.hasOwnProperty.call(objdata, 'visibility')) {
160+
visible = guidata[objName]['visibility'];
161+
}
162+
}
163+
164+
// Compile the connected vertices and faces into a model
165+
// And add to the scene
166+
// We need one material per-mesh because we will set the uniform color independently later
167+
// it's too bad Three.js does not easily allow setting uniforms independently per-mesh
168+
const material = new THREE.MeshPhongMaterial({
169+
color,
170+
side: THREE.DoubleSide,
171+
wireframe: false,
172+
flatShading: false,
173+
clippingPlanes,
174+
shininess: 0
175+
});
176+
177+
const geometry = new THREE.BufferGeometry();
178+
geometry.setIndex(triangles);
179+
geometry.setAttribute(
180+
'position',
181+
new THREE.Float32BufferAttribute(vertices, 3)
182+
);
183+
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
184+
geometry.computeBoundingBox();
185+
if (vertices.length > 0) {
186+
geometry.computeBoundsTree();
187+
}
188+
189+
const meshGroup = new THREE.Group();
190+
meshGroup.name = `${objName}-group`;
191+
meshGroup.visible = visible;
192+
193+
const baseMat = new THREE.MeshBasicMaterial();
194+
baseMat.depthWrite = false;
195+
baseMat.depthTest = false;
196+
baseMat.colorWrite = false;
197+
baseMat.stencilWrite = true;
198+
baseMat.stencilFunc = THREE.AlwaysStencilFunc;
199+
200+
// back faces
201+
const mat0 = baseMat.clone();
202+
mat0.side = THREE.BackSide;
203+
mat0.clippingPlanes = clippingPlanes;
204+
mat0.stencilFail = THREE.IncrementWrapStencilOp;
205+
mat0.stencilZFail = THREE.IncrementWrapStencilOp;
206+
mat0.stencilZPass = THREE.IncrementWrapStencilOp;
207+
const backFaces = new THREE.Mesh(geometry, mat0);
208+
backFaces.name = `${objName}-back`;
209+
meshGroup.add(backFaces);
210+
211+
// front faces
212+
const mat1 = baseMat.clone();
213+
mat1.side = THREE.FrontSide;
214+
mat1.clippingPlanes = clippingPlanes;
215+
mat1.stencilFail = THREE.DecrementWrapStencilOp;
216+
mat1.stencilZFail = THREE.DecrementWrapStencilOp;
217+
mat1.stencilZPass = THREE.DecrementWrapStencilOp;
218+
const frontFaces = new THREE.Mesh(geometry, mat1);
219+
frontFaces.name = `${objName}-front`;
220+
meshGroup.add(frontFaces);
221+
222+
const mainMesh = new THREE.Mesh(geometry, material);
223+
mainMesh.name = objName;
224+
mainMesh.userData = {
225+
type: 'shape'
226+
};
227+
228+
if (selected) {
229+
mainMesh.material.color = SELECTED_MESH_COLOR;
230+
}
231+
232+
let edgeIdx = 0;
233+
const edgesMeshes: LineSegments2[] = [];
234+
edgeList.forEach(edge => {
235+
const edgeMaterial = new LineMaterial({
236+
linewidth: DEFAULT_LINEWIDTH,
237+
// @ts-ignore Missing typing in ThreeJS
238+
color: DEFAULT_EDGE_COLOR,
239+
clippingPlanes,
240+
// Depth offset so that lines are most always on top of faces
241+
polygonOffset: true,
242+
polygonOffsetFactor: -5,
243+
polygonOffsetUnits: -5
244+
});
245+
246+
const edgeGeometry = new LineGeometry();
247+
edgeGeometry.setPositions(edge.vertexCoord);
248+
const edgesMesh = new LineSegments2(edgeGeometry, edgeMaterial);
249+
edgesMesh.name = `edge-${objName}-${edgeIdx}`;
250+
edgesMesh.userData = {
251+
type: 'edge',
252+
edgeIndex: edgeIdx,
253+
parent: objName
254+
};
255+
256+
edgesMeshes.push(edgesMesh);
257+
258+
edgeIdx++;
259+
});
260+
meshGroup.add(...edgesMeshes);
261+
meshGroup.add(mainMesh);
262+
263+
return { meshGroup, mainMesh, edgesMeshes };
264+
}

packages/base/src/3dview/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './mainview';

0 commit comments

Comments
 (0)