#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:
@@ -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) {
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user