#811 Integrated double-click to zoom, and zoom extents with the new viewer. Currently I'm using old existing code just for the sake of less regression, but will revisit it in the future

This commit is contained in:
AlexandruPopovici
2022-06-21 15:47:24 +03:00
parent 499653d983
commit f73384e66f
9 changed files with 198 additions and 64 deletions
@@ -74,8 +74,8 @@ export default class InteractionHandler {
this.selectedObjectsUserData = []
this.selectedRawObjects = []
this.selectionHelper.on('object-doubleclicked', this._handleDoubleClick.bind(this))
this.selectionHelper.on('object-clicked', this._handleSelect.bind(this))
// this.selectionHelper.on('object-doubleclicked', this._handleDoubleClick.bind(this))
// this.selectionHelper.on('object-clicked', this._handleSelect.bind(this))
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.viewer.mouseOverRenderer) {
+19 -38
View File
@@ -1,46 +1,27 @@
import { Raycaster, Scene } from 'three'
import Batcher from './batching/Batcher'
import { WorldTree } from './tree/WorldTree'
import { Camera, Intersection, Raycaster, Scene, Vector2 } from 'three'
export class Intersections {
private scene: Scene
private batcher: Batcher
private lastTimeCall = 0
private raycaster: Raycaster
public constructor(scene: Scene, batcher: Batcher) {
this.scene = scene
this.batcher = batcher
this.batcher
public constructor() {
this.raycaster = new Raycaster()
this.raycaster.params.Line = { threshold: 0.1 }
;(this.raycaster.params as { Line2? }).Line2 = {}
;(this.raycaster.params as { Line2? }).Line2.threshold = 1
}
public intersectScene(raycaster: Raycaster) {
if (performance.now() - this.lastTimeCall < 100) return
this.lastTimeCall = performance.now()
const target = this.scene.getObjectByName('ContentGroup')
const results = raycaster.intersectObjects(target.children)
if (!results.length) {
this.batcher.resetBatchesDrawRanges()
return
}
public intersect(
scene: Scene,
camera: Camera,
point: Vector2,
nearest = true
): Intersection {
this.raycaster.setFromCamera(point, camera)
const target = scene.getObjectByName('ContentGroup')
const results = this.raycaster.intersectObjects(target.children)
results.sort((value) => value.distance)
console.warn(results[0])
const rv = this.batcher.getRenderView(
results[0].object.uuid,
results[0].faceIndex !== undefined ? results[0].faceIndex : results[0].index
)
const hitId = rv.renderData.id
const hitNode = WorldTree.getInstance().findId(hitId)
console.warn(hitNode)
const renderViews = WorldTree.getRenderTree().getRenderViewsForNode(hitNode)
console.warn(renderViews)
this.batcher.selectRenderViews(renderViews)
// this.batcher.selectRenderView(rv)
// this.batcher.isolateRenderViews(renderViews)
// const node1 = WorldTree.getInstance().findId('62942139c0010e51500ee10655ce33a6')
// const node2 = WorldTree.getInstance().findId('c6268101e02c2c5b1b3af25aed1f722b')
// this.batcher.isolateRenderViews([node1.model.renderView, node2.model.renderView])
if (results.length === 0) return null
if (nearest) results.sort((value) => value.distance)
return results[0]
}
}
+139 -3
View File
@@ -1,24 +1,33 @@
import {
AmbientLight,
Box3,
Group,
HemisphereLight,
Intersection,
LinearToneMapping,
PointLight,
Scene,
Sphere,
sRGBEncoding,
Texture,
Vector3,
WebGLRenderer
} from 'three'
import { GeometryType } from './batching/Batch'
import Batcher from './batching/Batcher'
import { SpeckleType } from './converter/GeometryConverter'
import Input, { InputOptionsDefault } from './input/Input'
import { Intersections } from './Intersections'
import { WorldTree } from './tree/WorldTree'
import { Viewer } from './Viewer'
export default class SceneManager {
private _renderer: WebGLRenderer
public scene: Scene
private batcher: Batcher
private intersections: Intersections
private input: Input
public viewer: Viewer // TEMPORARY
public get renderer(): WebGLRenderer {
return this._renderer
@@ -28,11 +37,11 @@ export default class SceneManager {
this.scene.environment = texture
}
public constructor() {
public constructor(viewer: Viewer /** TEMPORARY */) {
this.scene = new Scene()
this.batcher = new Batcher()
this.intersections = new Intersections(this.scene, this.batcher)
this.intersections
this.intersections = new Intersections()
this.viewer = viewer
}
public create(container: HTMLElement) {
@@ -49,6 +58,10 @@ export default class SceneManager {
this._renderer.setSize(container.offsetWidth, container.offsetHeight)
container.appendChild(this._renderer.domElement)
this.input = new Input(this._renderer.domElement, InputOptionsDefault)
this.input.on('object-clicked', this.onObjectClick.bind(this))
this.input.on('object-doubleclicked', this.onObjectDoubleClick.bind(this))
this.addDirectLights()
}
@@ -103,4 +116,127 @@ export default class SceneManager {
hemiLight.up.set(0, 0, 1)
this.scene.add(hemiLight)
}
private onObjectClick(e) {
const result: Intersection = this.intersections.intersect(
this.scene,
this.viewer.cameraHandler.activeCam.camera,
e
)
if (!result) {
this.batcher.resetBatchesDrawRanges()
return
}
// console.warn(result)
const rv = this.batcher.getRenderView(
result.object.uuid,
result.faceIndex !== undefined ? result.faceIndex : result.index
)
const hitId = rv.renderData.id
const hitNode = WorldTree.getInstance().findId(hitId)
// console.warn(hitNode)
const renderViews = WorldTree.getRenderTree().getRenderViewsForNode(hitNode)
// console.warn(renderViews)
this.batcher.selectRenderViews(renderViews)
// this.batcher.selectRenderView(rv)
// this.batcher.isolateRenderViews(renderViews)
}
private onObjectDoubleClick(e) {
const result: Intersection = this.intersections.intersect(
this.scene,
this.viewer.cameraHandler.activeCam.camera,
e
)
let rv = null
if (!result) {
if (this.viewer.sectionBox.display.visible) {
this.zoomToBox(this.viewer.sectionBox.cube, 1.2, true)
} else {
this.zoomExtents()
}
} else {
rv = this.batcher.getRenderView(
result.object.uuid,
result.faceIndex !== undefined ? result.faceIndex : result.index
)
this.zoomToBox(rv.aabb, 1.2, true)
}
this.viewer.needsRender = true
this.viewer.emit(
'object-doubleclicked',
result ? rv.renderData.id : null,
result ? result.point : null
)
}
/** Taken from InteractionsHandler. Will revisit in the future */
zoomExtents(fit = 1.2, transition = true) {
if (this.viewer.sectionBox.display.visible) {
this.zoomToBox(this.viewer.sectionBox.cube, 1.2, true)
return
}
if (this.scene.getObjectByName('ContentGroup').children.length === 0) {
const box = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1))
this.zoomToBox(box, fit, transition)
return
}
const box = new Box3().setFromObject(this.scene.getObjectByName('ContentGroup'))
this.zoomToBox(box, fit, transition)
// this.viewer.controls.setBoundary( box )
}
/** Taken from InteractionsHandler. Will revisit in the future */
zoomToBox(box, fit = 1.2, transition = true) {
if (box.max.x === Infinity || box.max.x === -Infinity) {
box = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1))
}
const fitOffset = fit
const size = box.getSize(new Vector3())
const target = new Sphere()
box.getBoundingSphere(target)
target.radius = target.radius * fitOffset
const maxSize = Math.max(size.x, size.y, size.z)
const camFov = this.viewer.cameraHandler.camera.fov
? this.viewer.cameraHandler.camera.fov
: 55
const camAspect = this.viewer.cameraHandler.camera.aspect
? this.viewer.cameraHandler.camera.aspect
: 1.2
const fitHeightDistance = maxSize / (2 * Math.atan((Math.PI * camFov) / 360))
const fitWidthDistance = fitHeightDistance / camAspect
const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance)
this.viewer.cameraHandler.controls.fitToSphere(target, transition)
this.viewer.cameraHandler.controls.minDistance = distance / 100
this.viewer.cameraHandler.controls.maxDistance = distance * 100
this.viewer.cameraHandler.camera.near = distance / 100
this.viewer.cameraHandler.camera.far = distance * 100
this.viewer.cameraHandler.camera.updateProjectionMatrix()
if (this.viewer.cameraHandler.activeCam.name === 'ortho') {
this.viewer.cameraHandler.orthoCamera.far = distance * 100
this.viewer.cameraHandler.orthoCamera.updateProjectionMatrix()
// fit the camera inside, so we don't have clipping plane issues.
// WIP implementation
const camPos = this.viewer.cameraHandler.orthoCamera.position
let dist = target.distanceToPoint(camPos)
if (dist < 0) {
dist *= -1
this.viewer.cameraHandler.controls.setPosition(
camPos.x + dist,
camPos.y + dist,
camPos.z + dist
)
}
}
}
}
+2 -2
View File
@@ -23,7 +23,7 @@ export class Viewer extends EventEmitter implements IViewer {
private container: HTMLElement
private stats: Optional<Stats>
private loaders: { [id: string]: ViewerObjectLoader } = {}
private needsRender: boolean
public needsRender: boolean
private inProgressOperations: number
public sectionBox: SectionBox
@@ -87,7 +87,7 @@ export class Viewer extends EventEmitter implements IViewer {
this.container = container || document.getElementById('renderer')
this.speckleRenderer = new SpeckleRenderer()
this.speckleRenderer = new SpeckleRenderer(this)
this.speckleRenderer.create(this.container)
Viewer.Assets = new Assets(this.speckleRenderer.renderer)
+20 -16
View File
@@ -1,15 +1,18 @@
import { Vector2 } from 'three'
import EventEmitter from '../EventEmitter'
export interface InputOptions {
hover: boolean
}
export default class SelectionHelper extends EventEmitter {
private pointerDown = false
export const InputOptionsDefault = {
hover: false
}
export default class Input extends EventEmitter {
private tapTimeout
private lastTap = 0
private touchLocation: Touch
private multiSelect = false
private container
constructor(container: HTMLElement, _options: InputOptions) {
@@ -27,7 +30,6 @@ export default class SelectionHelper extends EventEmitter {
this.container.addEventListener('pointerup', (e) => {
e.preventDefault()
const delta = new Date().getTime() - mdTime
this.pointerDown = false
if (delta > 250) return
@@ -65,15 +67,15 @@ export default class SelectionHelper extends EventEmitter {
})
// Handle multiple object selection
document.addEventListener('keydown', (e) => {
if (e.isComposing || e.keyCode === 229) return
if (e.key === 'Shift') this.multiSelect = true
})
// document.addEventListener('keydown', (e) => {
// if (e.isComposing || e.keyCode === 229) return
// if (e.key === 'Shift') this.multiSelect = true
// })
document.addEventListener('keyup', (e) => {
if (e.isComposing || e.keyCode === 229) return
if (e.key === 'Shift') this.multiSelect = false
})
// document.addEventListener('keyup', (e) => {
// if (e.isComposing || e.keyCode === 229) return
// if (e.key === 'Shift') this.multiSelect = false
// })
}
_getNormalisedClickPosition(e) {
@@ -85,10 +87,12 @@ export default class SelectionHelper extends EventEmitter {
x: ((e.clientX - rect.left) * canvas.width) / rect.width,
y: ((e.clientY - rect.top) * canvas.height) / rect.height
}
return {
x: (pos.x / canvas.width) * 2 - 1,
y: (pos.y / canvas.height) * -2 + 1
}
const v = new Vector2(
(pos.x / canvas.width) * 2 - 1,
(pos.y / canvas.height) * -2 + 1
)
console.warn(v)
return v
}
dispose() {
@@ -1,7 +1,7 @@
import * as THREE from 'three'
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils'
import { Geometry } from './converter/Geometry'
import FilteringManager from './FilteringManager'
import { Geometry } from '../converter/Geometry'
import FilteringManager from '../FilteringManager'
/**
* Container for the scene objects, to allow loading/unloading/filtering/coloring/grouping
@@ -129,7 +129,7 @@ export default class _SelectionHelper extends EventEmitter {
normalizedPosition,
this.viewer.cameraHandler.activeCam.camera
)
this.viewer.intersections.intersectScene(this.raycaster)
// this.viewer.intersections.intersectScene(this.raycaster)
/**
* This 'subset' thing is really weird and it's breaking picking. I would gladly
* do something about it, however I'm afraid that it will open up a can of worms,
@@ -1,3 +1,4 @@
import { Box3 } from 'three'
import { GeometryType } from '../batching/Batch'
import { GeometryData } from '../converter/Geometry'
import { SpeckleType } from '../converter/GeometryConverter'
@@ -31,6 +32,8 @@ export class NodeRenderView {
private _materialHash: number
private _geometryType: GeometryType
private _aabb: Box3 = null
public static readonly NullRenderMaterialHash = this.hashCode(
GeometryType.MESH.toString()
)
@@ -83,6 +86,10 @@ export class NodeRenderView {
return this._batchId
}
public get aabb() {
return this._aabb
}
public get needsSegmentConversion() {
return (
this._renderData.speckleType === SpeckleType.Curve ||
@@ -110,6 +117,10 @@ export class NodeRenderView {
this._batchIndexCount = count
}
public computeAABB() {
this._aabb = new Box3().setFromArray(this._renderData.geometry.attributes.POSITION)
}
public getGeometryType(): GeometryType {
switch (this._renderData.speckleType) {
case SpeckleType.Mesh:
@@ -22,7 +22,9 @@ export class RenderTree {
transform.premultiply(rendeNode.geometry.bakeTransform)
}
Geometry.transformGeometryData(rendeNode.geometry, transform)
node.model.renderView.computeAABB()
}
return true
})
}