three.cad/src/Scene.js

419 lines
10 KiB
JavaScript
Raw Normal View History

2021-03-31 07:20:24 +08:00
2021-04-01 06:03:35 +08:00
import * as THREE from '../node_modules/three/src/Three';
2021-04-11 07:50:12 +08:00
import { TrackballControls } from '../lib/trackball'
2021-04-07 03:46:16 +08:00
import { Sketch } from './Sketch'
2021-04-11 07:50:12 +08:00
import Stats from '../lib/stats.module.js';
2021-03-31 07:20:24 +08:00
2021-04-16 08:05:40 +08:00
import { extrude, flipBufferGeometryNormals } from './extrude'
2021-04-15 10:11:55 +08:00
import { onHover, onPick } from './mouseEvents';
import { _vec2, _vec3, color, awaitSelection, ptObj, setHover } from './shared'
2021-03-31 07:20:24 +08:00
2021-04-11 07:16:08 +08:00
import { AxesHelper } from './axes'
2021-04-08 15:33:18 +08:00
2021-04-07 03:46:16 +08:00
2021-04-11 07:50:12 +08:00
import CSG from "../lib/three-csg"
2021-03-31 16:07:34 +08:00
2021-04-03 03:33:09 +08:00
2021-04-16 16:12:10 +08:00
2021-03-31 07:20:24 +08:00
2021-04-01 13:35:08 +08:00
window.loader = new THREE.ObjectLoader();
2021-04-07 05:10:07 +08:00
window.id = 0
2021-04-16 08:05:40 +08:00
window.sid = 1
window.mid = 1
const pointMaterial = new THREE.PointsMaterial({
color: color.selpoint,
size: 4,
})
2021-03-31 07:20:24 +08:00
2021-04-01 08:04:14 +08:00
export class Scene {
2021-03-31 07:20:24 +08:00
constructor(store) {
2021-04-16 08:05:40 +08:00
this.sid = 1
this.mid = 1
2021-03-31 07:20:24 +08:00
2021-04-01 06:03:35 +08:00
this.store = store;
2021-03-31 07:20:24 +08:00
this.canvas = document.querySelector('#c');
2021-04-08 06:50:53 +08:00
this.rect = this.canvas.getBoundingClientRect().toJSON()
2021-04-11 07:16:08 +08:00
this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas });
2021-03-31 07:20:24 +08:00
const size = 1;
const near = 0;
const far = 100;
this.camera = new THREE.OrthographicCamera(-size, size, size, -size, near, far);
this.camera.zoom = 0.1;
2021-04-07 02:31:14 +08:00
const cameraDist = 50
const xzAngle = 30 * Math.PI / 180
this.camera.position.set(
cameraDist * Math.sin(xzAngle),
cameraDist * Math.tan(30 * Math.PI / 180),
cameraDist * Math.cos(xzAngle)
);
2021-03-31 07:20:24 +08:00
const controls = new TrackballControls(this.camera, this.canvas);
controls.target.set(0, 0, 0);
controls.update();
2021-04-01 13:35:08 +08:00
this.obj3d = new THREE.Scene()
2021-03-31 07:20:24 +08:00
2021-04-11 07:16:08 +08:00
// this.obj3d.background = new THREE.Color(color.background);
2021-03-31 07:20:24 +08:00
const helpersGroup = new THREE.Group();
2021-04-11 07:16:08 +08:00
helpersGroup.name = "helpers";
2021-04-01 13:35:08 +08:00
this.obj3d.add(helpersGroup);
2021-03-31 07:20:24 +08:00
2021-04-07 02:31:14 +08:00
2021-04-11 07:16:08 +08:00
for (let i = 0; i < 4; i++) {
2021-04-16 08:05:40 +08:00
const freePt = new THREE.Points(
new THREE.BufferGeometry().setAttribute('position',
new THREE.Float32BufferAttribute(3, 3)
),
pointMaterial.clone()
)
2021-04-10 16:45:15 +08:00
freePt.matrixAutoUpdate = false
freePt.visible = false
freePt.depthTest = false
2021-04-11 07:16:08 +08:00
freePt.userData.type = 'selpoint'
2021-04-10 16:45:15 +08:00
helpersGroup.add(freePt);
}
2021-04-15 10:11:55 +08:00
this.selpoints = this.obj3d.children[0].children
2021-04-10 16:45:15 +08:00
this.fptIdx = 0;
this.fptObj = {}
2021-04-07 02:31:14 +08:00
const planeGeom = new THREE.PlaneGeometry(5, 5)
2021-03-31 07:20:24 +08:00
const pxy = new THREE.Mesh(
2021-04-07 02:31:14 +08:00
planeGeom,
2021-03-31 07:20:24 +08:00
new THREE.MeshBasicMaterial({
2021-04-05 11:52:17 +08:00
color: color.plane,
2021-04-11 07:16:08 +08:00
opacity: 0.02,
2021-03-31 07:20:24 +08:00
side: THREE.DoubleSide,
transparent: true,
depthWrite: false,
toneMapped: false
})
);
2021-04-07 02:31:14 +08:00
pxy.add(
new THREE.LineSegments(
new THREE.EdgesGeometry(planeGeom),
new THREE.LineBasicMaterial({ color: color.planeBorder })
)
)
2021-04-11 07:16:08 +08:00
pxy.userData.type = 'plane'
pxy.layers.enable(1)
pxy.children[0].layers.disable(1)
2021-03-31 07:20:24 +08:00
const pyz = pxy.clone().rotateY(Math.PI / 2);
const pxz = pxy.clone().rotateX(-Math.PI / 2);
2021-04-07 02:31:14 +08:00
helpersGroup.add(pxy);
2021-04-11 07:16:08 +08:00
[pxz, pyz].forEach(e => {
e.traverse(f => f.material = f.material.clone())
helpersGroup.add(e);
});
2021-03-31 07:20:24 +08:00
2021-04-11 07:16:08 +08:00
this.axes = new AxesHelper(this.camera.zoom)
this.axes.visible = false
helpersGroup.add(this.axes);
2021-03-31 07:20:24 +08:00
2021-04-13 09:37:16 +08:00
const dist = 15
2021-04-13 20:39:20 +08:00
const light1 = new THREE.PointLight(color.lighting, 0.7);
2021-04-11 07:16:08 +08:00
light1.position.set(dist, dist, dist);
2021-04-11 07:50:12 +08:00
helpersGroup.add(light1);
2021-04-13 20:39:20 +08:00
const light2 = new THREE.PointLight(color.lighting, 0.7);
2021-04-11 07:16:08 +08:00
light2.position.set(-dist, -dist, -dist);
2021-04-11 07:50:12 +08:00
helpersGroup.add(light2);
2021-03-31 07:20:24 +08:00
this.render = render.bind(this);
this.addSketch = addSketch.bind(this);
this.extrude = extrude.bind(this);
this.onHover = onHover.bind(this);
this.onPick = onPick.bind(this);
2021-04-11 15:57:39 +08:00
this.setHover = setHover.bind(this);
2021-04-09 07:05:52 +08:00
this.awaitSelection = awaitSelection.bind(this);
2021-03-31 07:20:24 +08:00
2021-04-01 13:35:08 +08:00
this.obj3d.addEventListener('change', this.render);
2021-03-31 07:20:24 +08:00
controls.addEventListener('change', this.render);
controls.addEventListener('start', this.render);
window.addEventListener('resize', this.render);
this.stats = new Stats();
this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
document.getElementById('stats').appendChild(this.stats.dom);
this.hovered = [];
this.selected = [];
2021-04-05 11:52:17 +08:00
this.activeSketch = null;
2021-03-31 07:20:24 +08:00
this.render();
}
2021-04-01 06:03:35 +08:00
resizeCanvas(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
saveState() {
localStorage.setItem(
2021-04-16 08:05:40 +08:00
'sv2', JSON.stringify([id, this.sid, this.mid, this.store.getState().treeEntries])
2021-04-01 06:03:35 +08:00
)
}
2021-04-01 13:35:08 +08:00
loadState() { //uglyyy
2021-04-16 08:05:40 +08:00
const [curid, cursid, curmid, state] = JSON.parse(
localStorage.getItem('sv2')
2021-04-01 06:03:35 +08:00
)
2021-04-07 05:10:07 +08:00
window.id = curid
2021-04-16 08:05:40 +08:00
this.sid = cursid
this.mid = curmid
2021-04-01 06:03:35 +08:00
2021-04-16 08:05:40 +08:00
const entries = state.byId
2021-04-01 13:35:08 +08:00
for (let k in entries) {
2021-04-01 06:03:35 +08:00
2021-04-04 12:54:27 +08:00
if (k[0] == 's') {
2021-04-01 13:35:08 +08:00
entries[k].obj3d = loader.parse(entries[k].obj3d)
this.obj3d.add(entries[k].obj3d)
2021-04-17 13:58:54 +08:00
entries[k] = new Sketch(this, entries[k])
2021-04-01 13:35:08 +08:00
entries[k].obj3d.addEventListener('change', this.render) // !! took 3 hours to realize
2021-04-16 08:05:40 +08:00
} else if (k[0] == 'e') {
entries[k] = loader.parse(state.byId[k])
2021-04-17 07:03:30 +08:00
2021-04-16 08:05:40 +08:00
if (entries[k].userData.inverted) {
flipBufferGeometryNormals(entries[k].geometry)
2021-04-17 07:03:30 +08:00
}
2021-04-16 08:05:40 +08:00
this.obj3d.add(entries[k])
} else {
entries[k] = loader.parse(state.byId[k])
2021-04-01 06:03:35 +08:00
2021-04-01 13:35:08 +08:00
this.obj3d.add(entries[k])
2021-04-01 06:03:35 +08:00
2021-04-01 13:35:08 +08:00
}
}
this.store.dispatch({ type: 'restore-state', state })
2021-04-16 08:05:40 +08:00
return state
2021-04-01 06:03:35 +08:00
}
2021-04-17 13:58:54 +08:00
loadSketch(string) {
let entry = JSON.parse(string)
entry.obj3d = loader.parse(entry.obj3d)
this.obj3d.add(entry.obj3d)
entry = new Sketch(this, entry)
entry.obj3d.addEventListener('change', this.render)
return entry
}
2021-04-07 02:31:14 +08:00
clearSelection() {
2021-04-15 06:46:30 +08:00
for (let x = 0, obj; x < this.selected.length; x++) {
obj = this.selected[x]
2021-04-15 10:11:55 +08:00
if (obj.userData.type == 'selpoint') {
obj.visible = false
2021-04-11 07:16:08 +08:00
} else {
2021-04-15 10:11:55 +08:00
setHover(obj, 0)
2021-04-11 07:16:08 +08:00
}
2021-04-07 02:31:14 +08:00
}
2021-04-10 18:53:44 +08:00
this.selected = []
2021-04-07 02:31:14 +08:00
for (let x = 0; x < this.hovered.length; x++) {
2021-04-15 10:11:55 +08:00
2021-04-11 15:57:39 +08:00
const obj = this.hovered[x]
2021-04-15 10:11:55 +08:00
setHover(obj, 0)
2021-04-07 02:31:14 +08:00
}
2021-04-10 18:53:44 +08:00
2021-04-07 02:31:14 +08:00
}
2021-04-10 16:45:15 +08:00
2021-04-17 07:03:30 +08:00
boolOp(m1, m2, op, refresh = false) {
2021-04-10 16:45:15 +08:00
let bspA = CSG.fromMesh(m1)
let bspB = CSG.fromMesh(m2)
2021-04-11 15:57:39 +08:00
m1.visible = false
m2.visible = false
m1.traverse(e => e.layers.disable(1))
m2.traverse(e => e.layers.disable(1))
2021-04-11 07:16:08 +08:00
2021-04-13 09:37:16 +08:00
let bspResult, opChar;
switch (op) {
case 's':
bspResult = bspA.subtract(bspB)
opChar = "-"
break;
case 'u':
bspResult = bspA.union(bspB)
opChar = "\u222a"
break;
case 'i':
bspResult = bspA.intersect(bspB)
opChar = "\u2229"
break;
default:
break;
}
2021-04-11 07:16:08 +08:00
2021-04-10 16:45:15 +08:00
let mesh = CSG.toMesh(bspResult, m1.matrix, m1.material)
mesh.userData.type = 'mesh'
2021-04-16 08:05:40 +08:00
mesh.userData.featureInfo = [m1.name, m2.name, op]
2021-04-13 09:37:16 +08:00
2021-04-16 08:05:40 +08:00
mesh.name = `(${m1.name} ${opChar} ${m2.name})`
2021-04-10 18:53:44 +08:00
mesh.layers.enable(1)
2021-04-11 07:16:08 +08:00
2021-04-11 15:57:39 +08:00
const vertices = new THREE.Points(mesh.geometry, new THREE.PointsMaterial({ size: 0 }));
2021-04-10 16:45:15 +08:00
vertices.userData.type = 'point'
vertices.layers.enable(1)
2021-04-11 07:16:08 +08:00
2021-04-10 16:45:15 +08:00
mesh.add(vertices)
2021-04-11 07:16:08 +08:00
2021-04-17 07:03:30 +08:00
if (!refresh) {
sc.obj3d.add(mesh)
this.store.dispatch({
type: 'set-entry-visibility', obj: {
[m1.name]: false,
[m2.name]: false,
[mesh.name]: true,
}
})
this.store.dispatch({
type: 'rx-boolean', mesh, deps: [m1.name, m2.name]
})
} else {
return mesh
}
}
2021-04-11 15:57:39 +08:00
2021-04-17 07:03:30 +08:00
refreshNode(id) {
let curId
let que = [id]
let idx = 0
const { byId, tree } = this.store.getState().treeEntries
while (idx < que.length) {
curId = que[idx++]
const info = byId[curId].userData.featureInfo
let newNode
if (info.length == 2) {
newNode = this.extrude(byId[info[0]], info[1], true)
} else if (info.length == 3) {
newNode = this.boolOp(byId[info[0]], byId[info[1]], info[2], true)
2021-04-11 15:57:39 +08:00
}
2021-04-17 07:03:30 +08:00
byId[curId].geometry.copy(newNode.geometry)
for (let k in tree[curId]) {
que.push(k)
}
}
2021-04-10 16:45:15 +08:00
}
2021-04-17 07:03:30 +08:00
2021-03-31 07:20:24 +08:00
}
2021-04-05 11:52:17 +08:00
let idx, x, y, ele, pos, dims, matrix;
2021-03-31 07:20:24 +08:00
function render() {
this.stats.begin();
if (this.resizeCanvas(this.renderer)) {
const canvas = this.renderer.domElement;
this.camera.left = -canvas.clientWidth / canvas.clientHeight;
this.camera.right = canvas.clientWidth / canvas.clientHeight;
this.camera.updateProjectionMatrix();
2021-04-08 06:50:53 +08:00
Object.assign(this.rect, this.canvas.getBoundingClientRect().toJSON())
2021-03-31 07:20:24 +08:00
}
2021-04-08 15:33:18 +08:00
2021-04-11 07:16:08 +08:00
if (this.axes) this.axes.resize(this.camera.zoom)
2021-04-08 15:33:18 +08:00
2021-04-01 13:35:08 +08:00
this.renderer.render(this.obj3d, this.camera);
2021-04-05 11:52:17 +08:00
if (this.activeSketch) {
2021-04-16 08:05:40 +08:00
dims = this.activeSketch.dimGroup.children
2021-04-05 11:52:17 +08:00
matrix = this.activeSketch.obj3d.matrix
for (idx = 1; idx < dims.length; idx += 2) {
ele = dims[idx]
pos = _vec3.set(
...ele.geometry.attributes.position.array
).applyMatrix4(matrix).project(this.camera)
2021-04-09 07:05:52 +08:00
x = (pos.x * .5 + .5) * this.canvas.clientWidth + 10 + this.rect.left;
y = (pos.y * -.5 + .5) * this.canvas.clientHeight + this.rect.top;
2021-04-07 02:31:14 +08:00
2021-04-05 11:52:17 +08:00
ele.label.style.transform = `translate(0%, -50%) translate(${x}px,${y}px)`;
}
}
2021-03-31 07:20:24 +08:00
this.stats.end();
}
async function addSketch() {
2021-04-03 03:33:09 +08:00
2021-04-04 12:54:27 +08:00
let sketch;
2021-03-31 07:20:24 +08:00
2021-04-11 07:16:08 +08:00
const references = await this.awaitSelection({ selpoint: 3 }, { plane: 1 });
2021-04-04 17:36:41 +08:00
if (!references) return;
2021-04-05 11:52:17 +08:00
if (references[0].userData.type == 'plane') {
2021-04-08 06:50:53 +08:00
sketch = new Sketch(this)
2021-04-04 17:36:41 +08:00
sketch.obj3d.matrix = references[0].matrix
2021-04-01 13:35:08 +08:00
sketch.plane.applyMatrix4(sketch.obj3d.matrix)
sketch.obj3d.inverse = sketch.obj3d.matrix.clone().invert()
2021-04-04 17:36:41 +08:00
this.obj3d.add(sketch.obj3d)
2021-04-04 12:54:27 +08:00
} else {
2021-04-08 06:50:53 +08:00
sketch = new Sketch(this)
2021-04-04 17:36:41 +08:00
this.obj3d.add(sketch.obj3d)
2021-04-01 13:35:08 +08:00
sketch.align(
2021-03-31 07:20:24 +08:00
...references.map(
2021-04-07 03:46:16 +08:00
el => new THREE.Vector3(...el.geometry.attributes.position.array).applyMatrix4(el.matrixWorld)
2021-03-31 07:20:24 +08:00
)
)
}
2021-04-04 12:54:27 +08:00
2021-04-07 02:31:14 +08:00
this.clearSelection()
2021-04-04 12:54:27 +08:00
2021-04-01 13:35:08 +08:00
sketch.obj3d.addEventListener('change', this.render);
this.store.dispatch({ type: 'rx-sketch', obj: sketch })
2021-03-31 07:20:24 +08:00
2021-04-11 15:57:39 +08:00
sketch.activate()
this.render()
2021-03-31 07:20:24 +08:00
}
2021-04-02 08:19:14 +08:00
window.sc = new Scene(store)
2021-04-16 08:05:40 +08:00
sc.loadState()
2021-03-31 07:20:24 +08:00
2021-04-10 16:45:15 +08:00
// sc.camera.layers.enable(1)
// rc.layers.set(1)