Namespace pick

xeokit Picking System


Select objects and primitives using rays and boundaries


The following class diagrams depict xeokit's picking system architecture.

The SceneObjectsKdTree3 class, a k-d tree that arranges SceneObjects for efficient collision testing with boundaries, rays, and frustums, is positioned in the center of the first diagram. To construct a SceneObjectsKdTree3, use the collision!createSceneObjectsKdTree3 | createSceneObjectsKdTree3 function.

To find SceneObjects in the SceneObjectsKdTree3 that intersect a 3D world-space ray, use Picker.rayPick(), which will generate a RayPickResult. To find SceneObjects that intersect a 2D marquee boundary, use Picker.marqueePick(), which will generate a MarqueePickResult.



A MarqueePickResult provides a list of SceneObjects that intersect the marquee, so unpacking that is trivial.

On the other hand, a RayPickResult includes comprehensive details regarding each ray-SceneObject intersection. This information includes everything necessary to inspect the geometry of the intersecting primitives within each SceneObject, and the coordinates at which each of them intersect the ray.



RayPickResult represents the hierarchical arrangement of Scene components selected by Picker.rayPick(). This structure enables the iteration of the chosen SceneObjects, and the iteration of the selected Meshes, Geometries, and GeometryBuckets within each SceneObject. The structure goes all the way down to the chosen primitives, which can be KdLine3D, KdPoint3D, or KdTriangle3D.

A primitive has one or more vertex indices, and the number of indices is determined by the primitive type. These indices are used to access the compressed vertex coordinates of the primitive within SceneGeometryBucket.positionsCompressed. These coordinates can be decompressed using SceneGeometry.positionsDecompressMatrix and then transformed into the World-space coordinate system using SceneMesh.matrix to obtain the final coordinates of the primitive.

To keep a low memory footprint, while being flexible and extensible, both the RayPickResult and xeokit's scene graph have been designed in such a way that requires some boilerplate code to traverse and unpack them. This includes coordinate decompression and transformation, which we'll demonstrate in the example code below.

Installation

npm install @xeokit/sdk

Usage

import {Scene} from "@xeokit/sdk/scene";
import {SDKError} from "@xeokit/sdk/core";
import {TrianglesPrimitive, LinesPrimitive, PointsPrimitive} from "@xeokit/sdk/constants";
import {KdTree3, searchKdTree3WithAABB} from "@xeokit/sdk/kdtree3";

// Create a scene graph - notice there's not a Viewer in sight

const scene = new Scene();

const sceneModel = scene.createModel({
id: "myModel"
});

if (sceneModel instanceof SDKError) {
console.log(sceneModel.message);

} else {

// Build our standard Table model, with a table top and four legs

sceneModel.createGeometry({
id: "theGeometry", primitive: TrianglesPrimitive,
positions: [10.07, 0, 11.07, 9.58, 3.11, 11.07, 8.15, ..],
indices: [21, 0, 1, 1, 22, 21, 22, 1, 2, 2, 23, 22, 23, ..]
});

sceneModel.createLayerMesh({
id: "redLegMesh", geometryId: "theGeometry",
position: [-4, -6, -4], scale: [1, 3, 1], rotation: [0, 0, 0], color: [1, 0.3, 0.3]
});

sceneModel.createLayerMesh({
id: "greenLegMesh", geometryId: "theGeometry", position: [4, -6, -4], scale: [1, 3, 1],
rotation: [0, 0, 0], color: [0.3, 1.0, 0.3]
});

sceneModel.createLayerMesh({
id: "blueLegMesh", geometryId: "theGeometry", position: [4, -6, 4], scale: [1, 3, 1],
rotation: [0, 0, 0], color: [0.3, 0.3, 1.0]
});

sceneModel.createLayerMesh({
id: "yellowLegMesh", geometryId: "theGeometry", position: [-4, -6, 4], scale: [1, 3, 1],
rotation: [0, 0, 0], color: [1.0, 1.0, 0.0]
});

sceneModel.createLayerMesh({
id: "tableTopMesh", geometryId: "theGeometry", position: [0, -3, 0], scale: [6, 0.5, 6],
rotation: [0, 0, 0], color: [1.0, 0.3, 1.0]
});

sceneModel.createObject({ id: "redLegSceneObject", meshIds: ["redLegMesh"] });
sceneModel.createObject({ id: "greenLegSceneObject", meshIds: ["greenLegMesh"] });
sceneModel.createObject({ id: "blueLegSceneObject", meshIds: ["blueLegMesh"] });
sceneModel.createObject({ id: "yellowLegSceneObject", meshIds: ["yellowLegMesh"] });
sceneModel.createObject({ id: "tableTopSceneObject", meshIds: ["tableTopMesh"] });

sceneModel.build()

.then(() => {

// When our model is finalized, insert all
// its SceneObjects into a SceneObjectsKdTree3

const sceneObjectsKdTree3 = createSceneObjectsKdTree3(Object.values(scene.objects));

// Then we'll try to ray-pick the SceneObjects

const picker = new Picker();

const rayPickResult = picker.rayPick({
sceneObjectsKdTree3,
origin: [0,0,100],
dir: [0,0,-1];
});

// We get a RayPickResults, which contains a
// SceneObjectHit that wraps each SceneObject we picked

for (let i =0, len = rayPickResult.sceneObjectHits.length; i < len; i++) {

const sceneObjectHit = rayPickResult.sceneObjectHits[i];
const sceneObject = sceneObjectHit.sceneObject;

// Within each SceneObjectHit we get a MeshHit that wraps each SceneMesh that
// was picked in this SceneObject

const meshHit = sceneObjectHit.meshHit;
const mesh = meshHit.mesh;

// Within the MeshHit, a single GeometryHit that wraps
// the SceneGeometry that was picked

const geometryHit = meshHit.geometryHit;
const geometry = geometryHit.geometry;

// Within the GeometryHit, we get a GeometryBucketHit for each hit

const geometryBucketHit = geometryHit.geometryBucketHit;
const geometryBucket = geometryBucketHit.geometryBucket;

// And finally within the GeometryBucketHit, a PrimHit
// for each primitive that was hit within the SceneGeometryBucket.

// Each PrimtHit wraps a single a single KdPointPrim, KdLinePrim or KdTrianglePrim,
// which represents a point, line or triangle primitive, respectively.

for (let j = 0, lenj = geometryBucketHit.primHits.length; j < lenj; j++) {

const primHit = geometryBucketHit.primHits[j];
const primitive = primHit.primitive;

// We know the primitive type from the SceneGeometry

switch (geometry.primitive) {

case TrianglesPrimitive:

// Using the vertex indices of each primitive,
// we can then access the compressed coordinates
// for that vertex, then we can decompress them
// and transform them into World-space.

const kdTriangle3D = primHit.prim;

break;

case LinesPrimitive:
const kdTriangle3D = primHit.prim;

break;

case PointsPrimitive:
const kdPoint3D = primHit.prim;

break;
}
}
}
});
}

Classes

Picker

Interfaces

GeometryHit
MarqueePickResult
MeshHit
PrimHit
RayPickResult
SceneObjectHit