Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
Merge 845286a into 99b41a3
Browse files Browse the repository at this point in the history
  • Loading branch information
atomicsulfate committed Feb 1, 2021
2 parents 99b41a3 + 845286a commit 31f6b68
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 118 deletions.
5 changes: 2 additions & 3 deletions @here/harp-mapview/lib/MapObjectAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under Apache 2.0, see full license in LICENSE
* SPDX-License-Identifier: Apache-2.0
*/
import { GeometryKind, MapEnv, Pickability, Technique } from "@here/harp-datasource-protocol";
import { GeometryKind, Pickability, Technique } from "@here/harp-datasource-protocol";
import * as THREE from "three";

import { DataSource } from "./DataSource";
Expand Down Expand Up @@ -135,9 +135,8 @@ export class MapObjectAdapter {

/**
* Whether underlying `THREE.Object3D` should be pickable by {@link PickHandler}.
* @param env - Property lookup environment.
*/
isPickable(env: MapEnv) {
isPickable() {
// An object is pickable only if it's visible and Pickabilty.onlyVisible or
// Pickabililty.all set.
return (
Expand Down
30 changes: 6 additions & 24 deletions @here/harp-mapview/lib/MapView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ import { MapViewFog } from "./MapViewFog";
import { MapViewTaskScheduler } from "./MapViewTaskScheduler";
import { MapViewThemeManager } from "./MapViewThemeManager";
import { PickHandler, PickResult } from "./PickHandler";
import { PickingRaycaster } from "./PickingRaycaster";
import { PoiManager } from "./poi/PoiManager";
import { PoiTableManager } from "./poi/PoiTableManager";
import { PolarTileDataSource } from "./PolarTileDataSource";
Expand Down Expand Up @@ -869,7 +868,7 @@ export class MapView extends EventDispatcher {
private readonly m_enablePolarDataSource: boolean = true;

// gestures
private readonly m_raycaster: PickingRaycaster;
private readonly m_raycaster = new THREE.Raycaster();
private readonly m_plane = new THREE.Plane(new THREE.Vector3(0, 0, 1));
private readonly m_sphere = new THREE.Sphere(undefined, EarthConstants.EQUATORIAL_RADIUS);

Expand Down Expand Up @@ -1007,11 +1006,6 @@ export class MapView extends EventDispatcher {
this.m_politicalView = this.m_options.politicalView;

this.handleRequestAnimationFrame = this.renderLoop.bind(this);
this.m_pickHandler = new PickHandler(
this,
this.m_rteCamera,
this.m_options.enablePickTechnique === true
);

if (this.m_options.tileWrappingEnabled !== undefined) {
this.m_tileWrappingEnabled = this.m_options.tileWrappingEnabled;
Expand Down Expand Up @@ -1087,7 +1081,11 @@ export class MapView extends EventDispatcher {
// setup camera with initial position
this.setupCamera();

this.m_raycaster = new PickingRaycaster(width, height, this.m_env);
this.m_pickHandler = new PickHandler(
this,
this.m_rteCamera,
this.m_options.enablePickTechnique === true
);

this.m_movementDetector = new CameraMovementDetector(
this.m_options.movementThrottleTimeout,
Expand Down Expand Up @@ -2569,22 +2567,6 @@ export class MapView extends EventDispatcher {
return p;
}

/**
* Returns a ray caster using the supplied screen positions.
*
* @param x - The X position in css/client coordinates (without applied display ratio).
* @param y - The Y position in css/client coordinates (without applied display ratio).
*
* @alpha
*
* @return Raycaster with origin at the camera and direction based on the supplied x / y screen
* points.
*/
raycasterFromScreenPoint(x: number, y: number): THREE.Raycaster {
this.m_raycaster.setFromCamera(this.getNormalizedScreenCoordinates(x, y), this.m_rteCamera);
return this.m_raycaster;
}

getWorldPositionAt(x: number, y: number, fallback: true): THREE.Vector3;
getWorldPositionAt(x: number, y: number, fallback?: boolean): THREE.Vector3 | null;

Expand Down
84 changes: 30 additions & 54 deletions @here/harp-mapview/lib/MapViewPoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,25 @@ export abstract class MapViewPoints extends THREE.Points {

const geometry = this.geometry;
const matrixWorld = this.matrixWorld;
const screenCoords = raycaster.ray.origin
const ndc = raycaster.ray.origin
.clone()
.add(raycaster.ray.direction)
.project(raycaster.camera);
const mouseCoords = new THREE.Vector2(
Math.ceil(((screenCoords.x + 1) / 2) * raycaster.width),
Math.ceil(((1 - screenCoords.y) / 2) * raycaster.height)
);
const mouseCoords = ndcToScreen(ndc, raycaster);

const testPoint = (point: THREE.Vector3, index: number) => {
const pointInfo = getPointInfo(point, matrixWorld, raycaster);
if (pointInfo.pointIsOnScreen) {
this.testPoint(
point,
pointInfo.absoluteScreenPosition!,
mouseCoords,
index,
pointInfo.distance!,
intersects
);
}
};

if (geometry instanceof THREE.BufferGeometry) {
const point = new THREE.Vector3();
Expand All @@ -85,56 +96,29 @@ export abstract class MapViewPoints extends THREE.Points {
if (index !== null) {
const indices = index.array;
for (let i = 0, il = indices.length; i < il; i++) {
const a = indices[i];
point.fromArray(positions as number[], a * 3);
const pointInfo = getPointInfo(point, matrixWorld, raycaster);
if (pointInfo.pointIsOnScreen) {
this.testPoint(
point,
pointInfo.absoluteScreenPosition!,
mouseCoords,
i,
pointInfo.distance!,
intersects
);
}
testPoint(point.fromArray(positions as number[], indices[i] * 3), i);
}
} else {
for (let i = 0, l = positions.length / 3; i < l; i++) {
point.fromArray(positions as number[], i * 3);
const pointInfo = getPointInfo(point, matrixWorld, raycaster);
if (pointInfo.pointIsOnScreen) {
this.testPoint(
point,
pointInfo.absoluteScreenPosition!,
mouseCoords,
i,
pointInfo.distance!,
intersects
);
}
testPoint(point.fromArray(positions as number[], i * 3), i);
}
}
} else {
const vertices = geometry.vertices;
for (let index = 0; index < vertices.length; index++) {
const point = vertices[index];
const pointInfo = getPointInfo(point, matrixWorld, raycaster);
if (pointInfo.pointIsOnScreen) {
this.testPoint(
point,
pointInfo.absoluteScreenPosition!,
mouseCoords,
index,
pointInfo.distance!,
intersects
);
}
testPoint(vertices[index], index);
}
}
}
}

function ndcToScreen(ndc: THREE.Vector3, raycaster: PickingRaycaster): THREE.Vector2 {
return new THREE.Vector2(ndc.x + 1, 1 - ndc.y)
.divideScalar(2)
.multiply(raycaster.canvasSize)
.ceil();
}

function getPointInfo(
point: THREE.Vector3,
matrixWorld: THREE.Matrix4,
Expand All @@ -144,20 +128,12 @@ function getPointInfo(
absoluteScreenPosition?: THREE.Vector2;
distance?: number;
} {
const worldPosition = point.clone();
worldPosition.applyMatrix4(matrixWorld);
const worldPosition = point.clone().applyMatrix4(matrixWorld);
const distance = worldPosition.distanceTo(raycaster.ray.origin);
worldPosition.project(raycaster.camera);
const relativeScreenPosition = new THREE.Vector2(worldPosition.x, worldPosition.y);
const pointIsOnScreen =
relativeScreenPosition.x < 1 &&
relativeScreenPosition.x > -1 &&
relativeScreenPosition.y < 1 &&
relativeScreenPosition.y > -1;
const ndc = worldPosition.project(raycaster.camera);
const pointIsOnScreen = ndc.x < 1 && ndc.x > -1 && ndc.y < 1 && ndc.y > -1;
if (pointIsOnScreen) {
worldPosition.x = ((worldPosition.x + 1) / 2) * raycaster.width;
worldPosition.y = ((1 - worldPosition.y) / 2) * raycaster.height;
const absoluteScreenPosition = new THREE.Vector2(worldPosition.x, worldPosition.y);
const absoluteScreenPosition = ndcToScreen(ndc, raycaster);
return {
absoluteScreenPosition,
pointIsOnScreen,
Expand Down
63 changes: 54 additions & 9 deletions @here/harp-mapview/lib/PickHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import * as THREE from "three";
import { IntersectParams } from "./IntersectParams";
import { MapView } from "./MapView";
import { MapViewPoints } from "./MapViewPoints";
import { PickingRaycaster } from "./PickingRaycaster";
import { PickListener } from "./PickListener";
import { Tile, TileFeatureData } from "./Tile";
import { MapViewUtils } from "./Utils";

/**
* Describes the general type of a picked object.
Expand Down Expand Up @@ -108,6 +110,7 @@ export interface PickResult {
userData?: any;
}

const tmpV3 = new THREE.Vector3();
const tmpOBB = new OrientedBox3();

// Intersects the dependent tile objects using the supplied raycaster. Note, because multiple
Expand Down Expand Up @@ -143,16 +146,20 @@ function intersectDependentObjects(
* @internal
*/
export class PickHandler {
private readonly m_pickingRaycaster: PickingRaycaster;

constructor(
readonly mapView: MapView,
readonly camera: THREE.Camera,
public enablePickTechnique = false
) {}
) {
this.m_pickingRaycaster = new PickingRaycaster(
mapView.renderer.getSize(new THREE.Vector2())
);
}

/**
* Does a raycast on all objects in the scene; useful for picking. This function is Limited to
* objects that THREE.js can raycast. However, any solid lines that have their geometry in the
* shader cannot be tested for intersection.
* Does a raycast on all objects in the scene; useful for picking.
*
* @param x - The X position in CSS/client coordinates, without the applied display ratio.
* @param y - The Y position in CSS/client coordinates, without the applied display ratio.
Expand All @@ -161,15 +168,14 @@ export class PickHandler {
* @returns the list of intersection results.
*/
intersectMapObjects(x: number, y: number, parameters?: IntersectParams): PickResult[] {
const worldPos = this.mapView.getNormalizedScreenCoordinates(x, y);
const rayCaster = this.mapView.raycasterFromScreenPoint(x, y);

const ndc = this.mapView.getNormalizedScreenCoordinates(x, y);
const rayCaster = this.setupRaycaster(x, y);
const pickListener = new PickListener(parameters);

if (this.mapView.textElementsRenderer !== undefined) {
const { clientWidth, clientHeight } = this.mapView.canvas;
const screenX = worldPos.x * clientWidth * 0.5;
const screenY = worldPos.y * clientHeight * 0.5;
const screenX = ndc.x * clientWidth * 0.5;
const screenY = ndc.y * clientHeight * 0.5;
const scenePosition = new THREE.Vector2(screenX, screenY);
this.mapView.textElementsRenderer.pickTextElements(scenePosition, pickListener);
}
Expand Down Expand Up @@ -217,6 +223,25 @@ export class PickHandler {
return pickListener.results;
}

/**
* Returns a ray caster using the supplied screen positions.
*
* @param x - The X position in css/client coordinates (without applied display ratio).
* @param y - The Y position in css/client coordinates (without applied display ratio).
*
* @return Raycaster with origin at the camera and direction based on the supplied x / y screen
* points.
*/
raycasterFromScreenPoint(x: number, y: number): THREE.Raycaster {
this.m_pickingRaycaster.setFromCamera(
this.mapView.getNormalizedScreenCoordinates(x, y),
this.camera
);

this.mapView.renderer.getSize(this.m_pickingRaycaster.canvasSize);
return this.m_pickingRaycaster;
}

private createResult(intersection: THREE.Intersection): PickResult {
const pickResult: PickResult = {
type: PickObjectType.Unspecified,
Expand Down Expand Up @@ -350,4 +375,24 @@ export class PickHandler {
}
pickResult.userData = featureData.objInfos[objInfosIndex - 1];
}

private setupRaycaster(x: number, y: number): THREE.Raycaster {
const camera = this.mapView.camera;
const rayCaster = this.raycasterFromScreenPoint(x, y);

// A threshold must be set for picking of line and line segments, indicating the maximum
// distance in world units from the ray to a line to consider it as picked. Use the world
// units equivalent to one pixel at the furthest intersection (i.e. intersection with ground
// or far plane).
const furthestIntersection = this.mapView.getWorldPositionAt(x, y, true);
const furthestDistance =
camera.position.distanceTo(furthestIntersection) /
this.mapView.camera.getWorldDirection(tmpV3).dot(rayCaster.ray.direction);
rayCaster.params.Line!.threshold = MapViewUtils.calculateWorldSizeByFocalLength(
this.mapView.focalLength,
furthestDistance,
1
);
return rayCaster;
}
}
16 changes: 6 additions & 10 deletions @here/harp-mapview/lib/PickingRaycaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,26 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { MapEnv } from "@here/harp-datasource-protocol";
import * as THREE from "three";

import { MapObjectAdapter } from "./MapObjectAdapter";

function intersectObject(
object: THREE.Object3D,
raycaster: PickingRaycaster,
env: MapEnv,
intersects: THREE.Intersection[],
recursive?: boolean
) {
if (object.layers.test(raycaster.layers) && object.visible) {
const mapObjectAdapter = MapObjectAdapter.get(object);
if (!mapObjectAdapter || mapObjectAdapter.isPickable(env)) {
if (!mapObjectAdapter || mapObjectAdapter.isPickable()) {
object.raycast(raycaster, intersects);
}
}

if (recursive === true) {
for (const child of object.children) {
intersectObject(child, raycaster, env, intersects, true);
intersectObject(child, raycaster, intersects, true);
}
}
}
Expand All @@ -41,11 +39,9 @@ export class PickingRaycaster extends THREE.Raycaster {
/**
* Constructor.
*
* @param width - the canvas width.
* @param height - the canvas height.
* @param m_env - the view enviroment.
* @param canvasSize - the canvas width and height.
*/
constructor(public width: number, public height: number, private readonly m_env: MapEnv) {
constructor(readonly canvasSize: THREE.Vector2) {
super();
}

Expand All @@ -58,7 +54,7 @@ export class PickingRaycaster extends THREE.Raycaster {
): THREE.Intersection[] {
const intersects: THREE.Intersection[] = optionalTarget ?? [];

intersectObject(object, this, this.m_env, intersects, recursive);
intersectObject(object, this, intersects, recursive);

return intersects;
}
Expand All @@ -73,7 +69,7 @@ export class PickingRaycaster extends THREE.Raycaster {
const intersects: THREE.Intersection[] = optionalTarget ?? [];

for (const object of objects) {
intersectObject(object, this, this.m_env, intersects, recursive);
intersectObject(object, this, intersects, recursive);
}

return intersects;
Expand Down
Loading

0 comments on commit 31f6b68

Please sign in to comment.