Integrating section box variable thikness outlines in the viewer
This commit is contained in:
@@ -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'
|
||||
)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user