Skip to content

Commit 3a20248

Browse files
authored
Raycast Redesign (#19)
* Notes: Raycaster click handler implementation * Better Raycast Click Handler * RaycastService onClick repairs * ReyCastService. Refactor onPointerMove. * raycast refactor: Remove ray intersection from animation loop. Fire ray on demand. * refactors * gardening refactors * gardening
1 parent 38b9da1 commit 3a20248

File tree

6 files changed

+269
-160
lines changed

6 files changed

+269
-160
lines changed

src/app.js

Lines changed: 58 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import CameraManager from './cameraManager.js'
33
import MapControlsFactory from './mapControlsFactory.js'
44
import RendererFactory from './rendererFactory.js'
55
import RayCastService from "./raycastService.js"
6-
import {loadPath} from './utils/utils.js'
6+
import {getWorldDistanceFromPixelDistance, loadPath} from './utils/utils.js'
77
import eventBus from './utils/eventBus.js';
88
import { annotationRenderService } from "./main.js"
99
import lineMaterialResolutionService from "./lineMaterialResolutionService.js"
1010
import materialService from './materialService.js'
11+
import Look from "./look.js"
1112

1213
let xxPre = undefined
1314
let yyPre = undefined
@@ -38,6 +39,55 @@ class App {
3839

3940
this.tooltip = this.createTooltip();
4041

42+
// Register click callback to handle tooltip display
43+
// this.raycastService.registerClickHandler((intersection, event) => {
44+
// const str = intersection ? 'hit' : 'miss'
45+
// console.log(`raycast click handler ${str}`)
46+
//
47+
// if (intersection) {
48+
// const { line:object, point } = intersection
49+
// if ('node' === object.userData?.type) {
50+
// this.showTooltip(object, point, 'node')
51+
// } else if ('edge' === object.userData?.type) {
52+
// this.showTooltip(object, point, 'edge')
53+
// }
54+
// } else {
55+
// this.hideTooltip()
56+
// }
57+
// })
58+
59+
// Register mouse over callback (stationary hover)
60+
this.raycastService.registerMouseOverHandler((intersection, event) => {
61+
if (!intersection) {
62+
this.clearIntersection()
63+
return
64+
}
65+
66+
const { object, point } = intersection
67+
68+
if ('node' === object.userData?.type) {
69+
const { t, nodeName } = intersection
70+
// Publish the vital lineIntersection event using processed intersection
71+
eventBus.publish('lineIntersection', { t, nodeName, nodeLine: object })
72+
this.showTooltip(object, point, 'node')
73+
} else if ('edge' === object.userData?.type) {
74+
this.showTooltip(object, point, 'edge')
75+
}
76+
})
77+
78+
// Register continuous move tracking to publish lineIntersection while over an object
79+
this.raycastService.registerMouseMoveHandler((intersection, event) => {
80+
if (!intersection) {
81+
this.clearIntersection()
82+
return
83+
}
84+
const {object, point} = intersection
85+
if ('node' === object.userData?.type) {
86+
const {t, nodeName} = intersection
87+
eventBus.publish('lineIntersection', {t, nodeName, nodeLine: object})
88+
}
89+
})
90+
4191
window.addEventListener('resize', () => {
4292
const { clientWidth, clientHeight } = this.container
4393
this.cameraManager.windowResizeHelper(clientWidth/clientHeight)
@@ -74,62 +124,17 @@ class App {
74124

75125
animate() {
76126

77-
const scene = this.sceneManager.getActiveScene()
78-
79-
if (true === this.raycastService.isEnabled) {
80-
81-
const nodeMeshGroup = scene.getObjectByName('NodeMeshGroup')
82-
const edgeMeshGroup = scene.getObjectByName('EdgeMeshGroup')
83-
84-
const all = [ ...nodeMeshGroup.children, ...edgeMeshGroup.children ];
85-
const intersections = this.raycastService.intersectObjects(this.cameraManager.camera, all)
86-
87-
this.handleIntersection(intersections)
88-
}
127+
lineMaterialResolutionService.update(this.cameraManager.camera, this.container)
89128

90129
this.mapControl.update()
91130

92-
const deltaTime = this.clock.getDelta()
93-
94131
const look = this.sceneManager.getActiveLook()
95-
look.updateBehavior(deltaTime, scene)
132+
const scene = this.sceneManager.getActiveScene()
133+
look.updateBehavior(this.clock.getDelta(), scene)
96134

97135
this.renderer.render(scene, this.cameraManager.camera)
98136
}
99137

100-
handleIntersection(intersections) {
101-
102-
if (undefined === intersections || 0 === intersections.length) {
103-
this.clearIntersection()
104-
return
105-
}
106-
107-
// Sort by distance to get the closest intersection
108-
intersections.sort((a, b) => a.distance - b.distance);
109-
110-
const { point, object } = intersections[0];
111-
112-
// this.renderer.domElement.style.cursor = 'none';
113-
114-
if (object.userData?.type === 'edge') {
115-
this.raycastService.showVisualFeedback(point, RayCastService.VISUAL_FEEDBACK_NAME_COLOR_THREE_JS);
116-
this.showTooltip(object, point, 'edge');
117-
} else if (object.userData?.type === 'node') {
118-
119-
const { t, nodeName, line } = this.raycastService.handleIntersection(this.geometryManager, intersections[0], RayCastService.DIRECT_LINE_INTERSECTION_STRATEGY)
120-
121-
eventBus.publish('lineIntersection', { t, nodeName, nodeLine:line })
122-
123-
this.showTooltip(object, point, 'node')
124-
125-
}
126-
}
127-
128-
handleEdgeIntersection(edgeObject, point) {
129-
this.raycastService.showVisualFeedback(point, RayCastService.VISUAL_FEEDBACK_NAME_COLOR_THREE_JS);
130-
this.showTooltip(edgeObject, point, 'edge');
131-
}
132-
133138
clearIntersection() {
134139
this.raycastService.clearIntersection()
135140
this.renderer.domElement.style.cursor = '';
@@ -164,7 +169,6 @@ class App {
164169

165170
await this.genomicService.initialize(json, this.pangenomeService)
166171

167-
// Update the population widget with the new data
168172
this.widgetService.updatePopulationWidget(json)
169173

170174
this.widgetService.reset()
@@ -263,11 +267,10 @@ class App {
263267

264268
createTooltip() {
265269

266-
const tooltip = document.createElement('div');
267-
tooltip.className = 'graph-tooltip';
268-
269-
this.container.appendChild(tooltip);
270+
const tooltip = document.createElement('div')
271+
this.container.appendChild(tooltip)
270272

273+
tooltip.className = 'graph-tooltip';
271274
this.enableTooltip()
272275

273276
return tooltip;
@@ -366,16 +369,7 @@ class App {
366369

367370
materialService.clear()
368371

369-
// if (true === this.sceneManager.isActive()) {
370-
// const look = this.sceneManager.getActiveLook()
371-
// look.materialCache.clear()
372-
// }
373-
374-
this.sceneManager.lookManager.clearAllMaterialCaches()
375-
376-
this.sceneManager.clearAllScenes()
377-
378-
this.sceneManager.activeSceneName = null
372+
this.sceneManager.clearCurrentData()
379373

380374
}
381375

src/lineMaterialResolutionService.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import * as THREE from 'three';
2+
import {getWorldDistanceFromPixelDistance} from "./utils/utils.js"
3+
import Look from "./look.js"
24

35
/**
46
* Service to manage LineMaterial resolution updates for worldUnits: false
@@ -96,17 +98,15 @@ class LineMaterialResolutionService {
9698
return this.size.clone();
9799
}
98100

99-
/**
100-
* Update line width for all registered LineMaterials
101-
* @param {number} worldSize - The new line width in world units
102-
*/
103-
updateAllLineWidths(worldSize) {
104-
this.materials.forEach(material => {
101+
update(camera, container){
102+
103+
const worldSize = getWorldDistanceFromPixelDistance(camera, Look.NODE_LINE_WIDTH_PIXELS, container)
104+
for (const material of this.materials){
105105
if (material && typeof material.linewidth !== 'undefined') {
106106
material.linewidth = worldSize;
107107
material.needsUpdate = true;
108108
}
109-
});
109+
}
110110
}
111111

112112
clear(){

src/parametricLine.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,28 +49,30 @@ class ParametricLine extends Line2 {
4949

5050
static getParameter(intersection){
5151

52-
const { faceIndex, point, object:line } = intersection
52+
const { faceIndex, point, object } = intersection
5353

5454
const P = point.clone();
55-
line.worldToLocal(P);
55+
object.worldToLocal(P);
5656

57-
const A = new THREE.Vector3().fromBufferAttribute(line.geometry.attributes.instanceStart, faceIndex);
58-
const B = new THREE.Vector3().fromBufferAttribute(line.geometry.attributes.instanceEnd, faceIndex);
57+
const A = new THREE.Vector3().fromBufferAttribute(object.geometry.attributes.instanceStart, faceIndex);
58+
const B = new THREE.Vector3().fromBufferAttribute(object.geometry.attributes.instanceEnd, faceIndex);
5959

6060
const AB = B.clone().sub(A);
6161

6262
const u = AB.lengthSq() > 0 ? THREE.MathUtils.clamp( AB.dot(P.clone().sub(A)) / AB.lengthSq(), 0, 1 ) : 0;
6363

64-
const { cum, segLen, total } = line.userData.arcLengthTable
64+
const { cum, segLen, total } = object.userData.arcLengthTable
6565

6666
const s = cum[ faceIndex ] + u * segLen[ faceIndex ];
6767

6868
const t = total > 0 ? s / total : 0;
6969

70-
const { userData } = line;
70+
const { userData } = object;
7171
const { nodeName } = userData;
7272

73-
return { t, nodeName, line }
73+
const result = { t, nodeName, ...intersection }
74+
75+
return result
7476

7577
}
7678

0 commit comments

Comments
 (0)