diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index a1f1242a4..3ac973fcb 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -83,7 +83,7 @@ sandbox.makeBatchesUI() await sandbox.loadUrl( // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' // Revit sample house (good for bim-like stuff with many display meshes) - 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' + // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' // 'Super' heavy revit shit // 'https://speckle.xyz/streams/e6f9156405/commits/0694d53bb5' // IFC building (good for a tree based structure) @@ -135,4 +135,5 @@ await sandbox.loadUrl( // 'https://latest.speckle.dev/streams/c1faab5c62/commits/7f0c4d2fc1/' // Classroom // 'https://speckle.xyz/streams/0208ffb67b/commits/a980292728' + 'https://latest.speckle.dev/streams/4658eb53b9/commits/328bd99997' ) diff --git a/packages/viewer/src/modules/SectionBox.js b/packages/viewer/src/modules/SectionBox.js index e785005c3..15133b242 100644 --- a/packages/viewer/src/modules/SectionBox.js +++ b/packages/viewer/src/modules/SectionBox.js @@ -4,9 +4,16 @@ import { TransformControls } from 'three/examples/jsm/controls/TransformControls import { Box3 } from 'three' import { ViewerEvent } from '../IViewer' import { ObjectLayers } from './SpeckleRenderer' +import EventEmitter from './EventEmitter' -export default class SectionBox { +export const SectionBoxEvent = { + DRAG_START: 'section-box-drag-start', + DRAG_END: 'section-box-drag-end' +} + +export default class SectionBox extends EventEmitter { constructor(viewer) { + super() this.viewer = viewer this.viewer.speckleRenderer.renderer.localClippingEnabled = true @@ -144,12 +151,14 @@ export default class SectionBox { if (!this.display.visible) return const val = !!event.value if (val) { + this.emit(SectionBoxEvent.DRAG_START) this.dragging = val //@Dim: Not sure what this needs to do in the new viewer //@Alex: this prevents(?) involuntary selection happening on mobile // this.viewer.interactions.preventSelection = val this.viewer.cameraHandler.enabled = !val } else { + this.emit(SectionBoxEvent.DRAG_END) setTimeout(() => { this.dragging = val //@Dim: Not sure what this needs to do in the new viewer @@ -450,14 +459,14 @@ export default class SectionBox { this.viewer.needsRender = true } - off() { + disable() { this.display.visible = false this.viewer.speckleRenderer.renderer.localClippingEnabled = false this.viewer.emit('section-box-changed', this.getCurrentBox()) this.viewer.needsRender = true } - on() { + enable() { this.display.visible = true this.viewer.speckleRenderer.renderer.localClippingEnabled = true this.viewer.emit('section-box-changed', this.getCurrentBox()) diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index ec5571afc..3d51a76cb 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -10,6 +10,7 @@ import { DirectionalLightHelper, DynamicDrawUsage, Group, + InterleavedBufferAttribute, Intersection, Line3, LineBasicMaterial, @@ -26,6 +27,7 @@ import { Spherical, sRGBEncoding, Texture, + Vector2, Vector3, VSMShadowMap, WebGLRenderer @@ -60,6 +62,9 @@ import { } from './pipeline/Pipeline' import { MeshBVHVisualizer } from 'three-mesh-bvh' import MeshBatch from './batching/MeshBatch' +import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2' +import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry' +import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' export enum ObjectLayers { STREAM_CONTENT = 1, @@ -493,6 +498,165 @@ export default class SpeckleRenderer { this.pipeline.updateClippingPlanes(planes) this.renderer.shadowMap.needsUpdate = true this.resetPipeline() + // console.log('Updated planes -> ', this.viewer.sectionBox.planes[2]) + } + + public onSectionBoxDragStart() { + const clipOutline: Mesh = this.allObjects.getObjectByName('clip-outline') as Mesh + if (clipOutline) { + clipOutline.visible = false + } + } + + public onSectionBoxDragEnd() { + const generate = () => { + let clipOutline: LineSegments2 = this.allObjects.getObjectByName( + 'clip-outline' + ) as LineSegments2 + if (!clipOutline) { + // const lineGeometry = new BufferGeometry() + // const linePosAttr = new BufferAttribute(new Float32Array(50000), 3, false) + const lineGeometry = new LineSegmentsGeometry() + lineGeometry.setPositions(new Float32Array(60000)) + // linePosAttr.setUsage(DynamicDrawUsage) + // lineGeometry.setAttribute('position', linePosAttr) + const material = new LineMaterial({ + color: 0x047efb, + linewidth: 5, // in world units with size attenuation, pixels otherwise + worldUnits: false, + vertexColors: false, + alphaToCoverage: false, + resolution: new Vector2(919, 848) + }) + material.color = new Color(0x047efb) + material.color.convertSRGBToLinear() + material.linewidth = 5 + material.clipIntersection = true + material.worldUnits = false + + clipOutline = new LineSegments2(lineGeometry, material) + clipOutline.name = 'clip-outline' + clipOutline.frustumCulled = false + clipOutline.renderOrder = 1 + clipOutline.layers.set(ObjectLayers.PROPS) + // ;(clipOutline.material as Material).clipIntersection = true + this.allObjects.add(clipOutline) + } + + const tempVector = new Vector3() + const tempVector1 = new Vector3() + const tempVector2 = new Vector3() + const tempVector3 = new Vector3() + const tempLine = new Line3() + + const batches: MeshBatch[] = this.batcher.getBatches( + undefined, + GeometryType.MESH + ) as MeshBatch[] + let index = 0 + for (let b = 0; b < batches.length; b++) { + const posAttr = ( + clipOutline.geometry.attributes['instanceStart'] as InterleavedBufferAttribute + ).data + posAttr.setUsage(DynamicDrawUsage) + const posArray = posAttr.array as number[] + batches[b].boundsTree.shapecast({ + intersectsBounds: (box) => { + const localPlane = this.viewer.sectionBox.planes[2] + return localPlane.intersectsBox(box) + }, + + intersectsTriangle: (tri) => { + // check each triangle edge to see if it intersects with the plane. If so then + // add it to the list of segments. + const localPlane = this.viewer.sectionBox.planes[2] + let count = 0 + + tempLine.start.copy(tri.a) + tempLine.end.copy(tri.b) + if (localPlane.intersectLine(tempLine, tempVector)) { + posArray[index * 3] = tempVector.x + posArray[index * 3 + 1] = tempVector.y + posArray[index * 3 + 2] = tempVector.z + index++ + count++ + } + + tempLine.start.copy(tri.b) + tempLine.end.copy(tri.c) + if (localPlane.intersectLine(tempLine, tempVector)) { + posArray[index * 3] = tempVector.x + posArray[index * 3 + 1] = tempVector.y + posArray[index * 3 + 2] = tempVector.z + count++ + index++ + } + + tempLine.start.copy(tri.c) + tempLine.end.copy(tri.a) + if (localPlane.intersectLine(tempLine, tempVector)) { + posArray[index * 3] = tempVector.x + posArray[index * 3 + 1] = tempVector.y + posArray[index * 3 + 2] = tempVector.z + count++ + index++ + } + + // When the plane passes through a vertex and one of the edges of the triangle, there will be three intersections, two of which must be repeated + if (count === 3) { + // tempVector1.fromBufferAttribute(posAttr, index - 3) + // tempVector2.fromBufferAttribute(posAttr, index - 2) + // tempVector3.fromBufferAttribute(posAttr, index - 1) + tempVector1.set( + posArray[(index - 3) * 3], + posArray[(index - 3) * 3 + 1], + posArray[(index - 3) * 3 + 2] + ) + tempVector2.set( + posArray[(index - 2) * 3], + posArray[(index - 2) * 3 + 1], + posArray[(index - 2) * 3 + 2] + ) + tempVector3.set( + posArray[(index - 1) * 3], + posArray[(index - 1) * 3 + 1], + posArray[(index - 1) * 3 + 2] + ) + // If the last point is a duplicate intersection + if (tempVector3.equals(tempVector1) || tempVector3.equals(tempVector2)) { + count-- + index-- + } else if (tempVector1.equals(tempVector2)) { + // If the last point is not a duplicate intersection + // Set the penultimate point as a distinct point and delete the last point + posArray[(index - 2) * 3] = tempVector.x + posArray[(index - 2) * 3 + 1] = tempVector.y + posArray[(index - 2) * 3 + 2] = tempVector.z + count-- + index-- + } + } + + // If we only intersected with one or three sides then just remove it. This could be handled + // more gracefully. + if (count !== 2) { + index -= count + } + } + }) + posAttr.needsUpdate = true + posAttr.updateRange = { offset: 0, count: posArray.length } + } + clipOutline.visible = true + // clipOutline.geometry.setDrawRange(0, index) + clipOutline.geometry.attributes['instanceStart'].needsUpdate = true + clipOutline.geometry.attributes['instanceEnd'].needsUpdate = true + clipOutline.geometry.computeBoundingBox() + clipOutline.geometry.computeBoundingSphere() + // console.log('Index -> ', index) + this.viewer.removeListener(ViewerEvent.SectionBoxUpdated, generate) + } + this.viewer.on(ViewerEvent.SectionBoxUpdated, generate) } public clipTest() { diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index 20ea70a90..71f0340b9 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -5,7 +5,7 @@ import ViewerObjectLoader from './ViewerObjectLoader' import EventEmitter from './EventEmitter' import CameraHandler from './context/CameraHanlder' -import SectionBox from './SectionBox' +import SectionBox, { SectionBoxEvent } from './SectionBox' import { Clock, Texture } from 'three' import { Assets } from './Assets' import { Optional } from '../helpers/typeHelper' @@ -84,10 +84,18 @@ export class Viewer extends EventEmitter implements IViewer { ;(window as any)._V = this // For debugging! this.sectionBox = new SectionBox(this) - this.sectionBox.off() + this.sectionBox.disable() this.on(ViewerEvent.SectionBoxUpdated, () => { this.speckleRenderer.updateClippingPlanes(this.sectionBox.planes) }) + this.sectionBox.on( + SectionBoxEvent.DRAG_START, + this.speckleRenderer.onSectionBoxDragStart.bind(this.speckleRenderer) + ) + this.sectionBox.on( + SectionBoxEvent.DRAG_END, + this.speckleRenderer.onSectionBoxDragEnd.bind(this.speckleRenderer) + ) this.frame() this.resize() @@ -301,11 +309,11 @@ export class Viewer extends EventEmitter implements IViewer { } public sectionBoxOff() { - this.sectionBox.off() + this.sectionBox.disable() } public sectionBoxOn() { - this.sectionBox.on() + this.sectionBox.enable() } public zoom(objectIds?: string[], fit?: number, transition?: boolean) {