Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3D Tiles pick improvement: Check intersections with tile bounding volumes #11817

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 149 additions & 7 deletions packages/engine/Source/Core/OrientedBoundingBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import CesiumMath from "./Math.js";
import Matrix3 from "./Matrix3.js";
import Matrix4 from "./Matrix4.js";
import Plane from "./Plane.js";
import Ray from "./Ray.js";
import Rectangle from "./Rectangle.js";

/**
Expand Down Expand Up @@ -670,13 +671,8 @@ OrientedBoundingBox.clone = function (box, result) {
*/
OrientedBoundingBox.intersectPlane = function (box, plane) {
//>>includeStart('debug', pragmas.debug);
if (!defined(box)) {
throw new DeveloperError("box is required.");
}

if (!defined(plane)) {
throw new DeveloperError("plane is required.");
}
Check.defined("box", box);
Check.defined("plane", plane);
//>>includeEnd('debug');

const center = box.center;
Expand Down Expand Up @@ -714,6 +710,141 @@ OrientedBoundingBox.intersectPlane = function (box, plane) {
return Intersect.INTERSECTING;
};

const scratchInverse = new Matrix4();
const scratchNormal = new Cartesian3();
const scratchRay = new Ray();

/**
* Computes the nearest intersection along the ray with the oriented bounding box.
*
* @param {OrientedBoundingBox} box The oriented bounding box to test.
* @param {Ray} ray The ray to test against.
* @param {Cartesian3} [result] The nearest intersection along the provided ray with the oriented bounding box.
* @returns {Cartesian3|undefined} The nearest intersection along the provided ray with the oriented bounding box, or <code>undefined</code> if there is no intersection.
*/
OrientedBoundingBox.intersectRay = function (box, ray, result) {
//>>includeStart('debug', pragmas.debug);
Check.defined("box", box);
Check.defined("ray", ray);
//>>includeEnd('debug');

const transformedRay = scratchRay;
transformedRay.origin = Cartesian3.subtract(
ray.origin,
box.center,
transformedRay.origin
);
transformedRay.direction = Cartesian3.clone(
ray.direction,
transformedRay.direction
);

const transform = Matrix4.multiplyByMatrix3(
Matrix4.IDENTITY,
box.halfAxes,
scratchInverse
);
const inverseHalfAxis = Matrix4.inverse(transform, scratchInverse);
Ray.transform(transformedRay, inverseHalfAxis, transformedRay);

const origin = transformedRay.origin;
const direction = transformedRay.direction;

let tNear = -Infinity,
tFar = Infinity;

// Implementation based on https://education.siggraph.org/static/HyperGraph/raytrace/rtinter3.htm#:~:text=We%20can%20use%20a%20box,bounding%20volume%20or%20a%20box.
let normal = Cartesian3.UNIT_X;
let denominator = Cartesian3.dot(normal, direction);
if (Math.abs(denominator) < CesiumMath.EPSILON15) {
// The ray is paralell to the faces
if (origin.x > 1.0 || origin.x < -1.0) {
return;
}
} else {
const negativeNormal = Cartesian3.negate(normal, scratchNormal);
const negativeDenominator = Cartesian3.dot(negativeNormal, direction);
let tx1 = (-1.0 - Cartesian3.dot(normal, origin)) / denominator;
let tx2 =
(-1.0 - Cartesian3.dot(negativeNormal, origin)) / negativeDenominator;
ggetz marked this conversation as resolved.
Show resolved Hide resolved

if (tx1 > tx2) {
const swap = tx1;
tx1 = tx2;
tx2 = swap;
}

tNear = Math.max(tNear, tx1);
tFar = Math.min(tFar, tx2);

if (tNear > tFar || tFar < 0.0) {
return;
}
}

normal = Cartesian3.UNIT_Y;
denominator = Cartesian3.dot(normal, direction);
if (Math.abs(denominator) < CesiumMath.EPSILON15) {
// The ray is paralell to the faces
if (origin.y > 1.0 || origin.y < -1.0) {
return;
}
} else {
const negativeNormal = Cartesian3.negate(normal, scratchNormal);
const negativeDenominator = Cartesian3.dot(negativeNormal, direction);
let ty1 = (-1.0 - Cartesian3.dot(normal, origin)) / denominator;
let ty2 =
(-1.0 - Cartesian3.dot(negativeNormal, origin)) / negativeDenominator;

if (ty1 > ty2) {
const swap = ty1;
ty1 = ty2;
ty2 = swap;
}

tNear = Math.max(tNear, ty1);
tFar = Math.min(tFar, ty2);

if (tNear > tFar || tFar < 0.0) {
return;
}
}

normal = Cartesian3.UNIT_Z;
denominator = Cartesian3.dot(normal, direction);
if (Math.abs(denominator) < CesiumMath.EPSILON15) {
// The ray is paralell to the faces
if (origin.z > 1.0 || origin.z < -1.0) {
return;
}
} else {
const negativeNormal = Cartesian3.negate(normal, scratchNormal);
const negativeDenominator = Cartesian3.dot(negativeNormal, direction);
let tz1 = (-1.0 - Cartesian3.dot(normal, origin)) / denominator;
let tz2 =
(-1.0 - Cartesian3.dot(negativeNormal, origin)) / negativeDenominator;

if (tz1 > tz2) {
const swap = tz1;
tz1 = tz2;
tz2 = swap;
}

tNear = Math.max(tNear, tz1);
tFar = Math.min(tFar, tz2);

if (tNear > tFar || tFar < 0.0) {
return;
}
}

if (tNear <= 0.0) {
return Ray.getPoint(ray, tFar, result);
}

return Ray.getPoint(ray, tNear, result);
};

const scratchCartesianU = new Cartesian3();
const scratchCartesianV = new Cartesian3();
const scratchCartesianW = new Cartesian3();
Expand Down Expand Up @@ -1161,6 +1292,17 @@ OrientedBoundingBox.prototype.intersectPlane = function (plane) {
return OrientedBoundingBox.intersectPlane(this, plane);
};

/**
* Computes the nearest intersection along the ray with the oriented bounding box.
*
* @param {Ray} ray The ray to test against.
* @param {Cartesian3} [result] The nearest intersection along the provided ray with the oriented bounding box.
* @returns {Cartesian3|undefined} The nearest intersection along the provided ray with the oriented bounding box, or <code>undefined</code> if there is no intersection.
*/
OrientedBoundingBox.prototype.intersectRay = function (ray, result) {
return OrientedBoundingBox.intersectRay(this, ray, result);
};

/**
* Computes the estimated distance squared from the closest point on a bounding box to a point.
*
Expand Down
30 changes: 30 additions & 0 deletions packages/engine/Source/Core/Ray.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Cartesian3 from "./Cartesian3.js";
import Check from "./Check.js";
import defaultValue from "./defaultValue.js";
import defined from "./defined.js";
import Matrix4 from "./Matrix4.js";

/**
* Represents a ray that extends infinitely from the provided origin in the provided direction.
Expand Down Expand Up @@ -77,4 +78,33 @@ Ray.getPoint = function (ray, t, result) {
result = Cartesian3.multiplyByScalar(ray.direction, t, result);
return Cartesian3.add(ray.origin, result, result);
};

/**
* Transforms the ray by the given transformation matrix. The resulting ray's direction is not garunteed to be normalized.
*
* @param {Ray} ray The ray.
* @param {Matrix4} transform The transformation matrix.
* @param {Ray} [result] The transformed ray.
* @returns {Ray} The transformed ray.
*/
Ray.transform = function (ray, transform, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("ray", ray);
Check.typeOf.object("transform", transform);
//>>includeEnd('debug');

if (!defined(result)) {
result = new Ray();
}

result.origin = Matrix4.multiplyByPoint(transform, ray.origin, result.origin);
result.direction = Matrix4.multiplyByPointAsVector(
transform,
ray.direction,
result.direction
);

return result;
};

export default Ray;
22 changes: 10 additions & 12 deletions packages/engine/Source/Scene/Cesium3DTileset.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import ApproximateTerrainHeights from "../Core/ApproximateTerrainHeights.js";
import BoundingSphere from "../Core/BoundingSphere.js";
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartographic from "../Core/Cartographic.js";
Expand All @@ -14,8 +13,6 @@ import destroyObject from "../Core/destroyObject.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import Event from "../Core/Event.js";
import ImageBasedLighting from "./ImageBasedLighting.js";
import Interval from "../Core/Interval.js";
import IntersectionTests from "../Core/IntersectionTests.js";
import IonResource from "../Core/IonResource.js";
import JulianDate from "../Core/JulianDate.js";
import ManagedArray from "../Core/ManagedArray.js";
Expand Down Expand Up @@ -3594,7 +3591,7 @@ Cesium3DTileset.prototype.updateHeight = function (
return removeCallback;
};

const scratchSphereIntersection = new Interval();
const scratchVolumeIntersection = new Cartesian3();
const scratchPickIntersection = new Cartesian3();

/**
Expand All @@ -3618,12 +3615,15 @@ Cesium3DTileset.prototype.pick = function (ray, frameState, result) {

for (let i = 0; i < selectedLength; ++i) {
const tile = selectedTiles[i];
const boundsIntersection = IntersectionTests.raySphere(
if (!defined(tile.content)) {
continue;
}

const boundsIntersection = tile.contentBoundingVolume.intersectRay(
ray,
tile.contentBoundingVolume.boundingSphere,
scratchSphereIntersection
scratchVolumeIntersection
);
if (!defined(boundsIntersection) || !defined(tile.content)) {
if (!defined(boundsIntersection)) {
continue;
}

Expand All @@ -3632,12 +3632,10 @@ Cesium3DTileset.prototype.pick = function (ray, frameState, result) {

const length = candidates.length;
candidates.sort((a, b) => {
const aDist = BoundingSphere.distanceSquaredTo(
a.contentBoundingVolume.boundingSphere,
const aDist = a.contentBoundingVolume.boundingVolume.distanceSquaredTo(
ray.origin
);
const bDist = BoundingSphere.distanceSquaredTo(
b.contentBoundingVolume.boundingSphere,
const bDist = b.contentBoundingVolume.boundingVolume.distanceSquaredTo(
ray.origin
);

Expand Down
11 changes: 11 additions & 0 deletions packages/engine/Source/Scene/TileBoundingRegion.js
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,17 @@ TileBoundingRegion.prototype.intersectPlane = function (plane) {
return this._orientedBoundingBox.intersectPlane(plane);
};

/**
* Computes the nearest intersection along the ray with the bounding volume.
*
* @param {Ray} ray The ray to test against.
* @param {Cartesian3} [result] The nearest intersection along the provided ray with the bounding volume.
* @returns {Cartesian3|undefined} The nearest intersection along the provided ray with the bounding volume, or <code>undefined</code> if there is no intersection.
*/
TileBoundingRegion.prototype.intersectRay = function (ray, result) {
return this._orientedBoundingBox.intersectRay(ray, result);
};

/**
* Creates a debug primitive that shows the outline of the tile bounding region.
*
Expand Down
2 changes: 2 additions & 0 deletions packages/engine/Source/Scene/TileBoundingS2Cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,8 @@ TileBoundingS2Cell.prototype.intersectPlane = function (plane) {
return Intersect.INTERSECTING;
};

// TODO
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"TODO"? 🤔


/**
* Creates a debug primitive that shows the outline of the tile bounding
* volume.
Expand Down
32 changes: 32 additions & 0 deletions packages/engine/Source/Scene/TileBoundingSphere.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import Check from "../Core/Check.js";
import ColorGeometryInstanceAttribute from "../Core/ColorGeometryInstanceAttribute.js";
import GeometryInstance from "../Core/GeometryInstance.js";
import CesiumMath from "../Core/Math.js";
import IntersectionTests from "../Core/IntersectionTests.js";
import Matrix4 from "../Core/Matrix4.js";
import SphereOutlineGeometry from "../Core/SphereOutlineGeometry.js";
import PerInstanceColorAppearance from "./PerInstanceColorAppearance.js";
import Primitive from "./Primitive.js";
import { defined } from "@cesium/engine";
import Ray from "../Core/Ray.js";

/**
* A tile bounding volume specified as a sphere.
Expand Down Expand Up @@ -118,6 +121,35 @@ TileBoundingSphere.prototype.intersectPlane = function (plane) {
return BoundingSphere.intersectPlane(this._boundingSphere, plane);
};

/**
* Computes the nearest intersection along the ray with the bounding volume.
*
* @param {Ray} ray The ray to test against.
* @param {Cartesian3} [result] The nearest intersection along the provided ray with the bounding volume.
* @returns {Cartesian3|undefined} The nearest intersection along the provided ray with the bounding volume, or <code>undefined</code> if there is no intersection.
*/
TileBoundingSphere.prototype.intersectRay = function (ray, result) {
//>>includeStart('debug', pragmas.debug);
Check.defined("ray", ray);
//>>includeEnd('debug');

const interval = IntersectionTests.raySphere(
ray,
this._boundingSphere,
result
);

if (!defined(interval)) {
return;
}

if (interval.start > 0.0) {
return Ray.getPoint(ray, interval.start, result);
}

return Ray.getPoint(ray, interval.stop, result);
};

/**
* Update the bounding sphere after the tile is transformed.
*
Expand Down
11 changes: 11 additions & 0 deletions packages/engine/Source/Scene/TileBoundingVolume.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ TileBoundingVolume.prototype.intersectPlane = function (plane) {
DeveloperError.throwInstantiationError();
};

/**
* Computes the nearest intersection along the ray with the bounding volume.
*
* @param {Ray} ray The ray to test against.
* @param {Cartesian3} [result] The nearest intersection along the provided ray with the bounding volume.
* @returns {Cartesian3|undefined} The nearest intersection along the provided ray with the bounding volume, or <code>undefined</code> if there is no intersection.
*/
TileBoundingVolume.prototype.intersectRay = function (ray, result) {
DeveloperError.throwInstantiationError();
};

/**
* Creates a debug primitive that shows the outline of the tile bounding
* volume.
Expand Down
11 changes: 11 additions & 0 deletions packages/engine/Source/Scene/TileOrientedBoundingBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,17 @@ TileOrientedBoundingBox.prototype.intersectPlane = function (plane) {
return this._orientedBoundingBox.intersectPlane(plane);
};

/**
* Computes the nearest intersection along the ray with the bounding volume.
*
* @param {Ray} ray The ray to test against.
* @param {Cartesian3} [result] The nearest intersection along the provided ray with the bounding volume.
* @returns {Cartesian3|undefined} The nearest intersection along the provided ray with the bounding volume, or <code>undefined</code> if there is no intersection.
*/
TileOrientedBoundingBox.prototype.intersectRay = function (ray, result) {
return this._orientedBoundingBox.intersectRay(ray, result);
};

/**
* Update the bounding box after the tile is transformed.
*
Expand Down
Loading
Loading