@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
426 lines (378 loc) • 13.2 kB
JavaScript
import AttributeCompression from "../../Core/AttributeCompression.js";
import BoundingSphere from "../../Core/BoundingSphere.js";
import Cartesian3 from "../../Core/Cartesian3.js";
import Cartographic from "../../Core/Cartographic.js";
import Check from "../../Core/Check.js";
import ComponentDatatype from "../../Core/ComponentDatatype.js";
import defined from "../../Core/defined.js";
import Ellipsoid from "../../Core/Ellipsoid.js";
import IndexDatatype from "../../Core/IndexDatatype.js";
import IntersectionTests from "../../Core/IntersectionTests.js";
import Ray from "../../Core/Ray.js";
import Matrix4 from "../../Core/Matrix4.js";
import Transforms from "../../Core/Transforms.js";
import VerticalExaggeration from "../../Core/VerticalExaggeration.js";
import AttributeType from "../AttributeType.js";
import SceneMode from "../SceneMode.js";
import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
import ModelUtility from "./ModelUtility.js";
const scratchV0 = new Cartesian3();
const scratchV1 = new Cartesian3();
const scratchV2 = new Cartesian3();
const scratchNodeComputedTransform = new Matrix4();
const scratchModelMatrix = new Matrix4();
const scratchcomputedModelMatrix = new Matrix4();
const scratchPickCartographic = new Cartographic();
const scratchBoundingSphere = new BoundingSphere();
/**
* Find an intersection between a ray and the model surface that was rendered. The ray must be given in world coordinates.
*
* @param {Model} model The model to pick.
* @param {Ray} ray The ray to test for intersection.
* @param {FrameState} frameState The frame state.
* @param {number} [verticalExaggeration=1.0] A scalar used to exaggerate the height of a position relative to the ellipsoid. If the value is 1.0 there will be no effect.
* @param {number} [relativeHeight=0.0] The ellipsoid height relative to which a position is exaggerated. If the value is 0.0 the position will be exaggerated relative to the ellipsoid surface.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.default] The ellipsoid to which the exaggerated position is relative.
* @param {Cartesian3|undefined} [result] The intersection or <code>undefined</code> if none was found.
* @returns {Cartesian3|undefined} The intersection or <code>undefined</code> if none was found.
*
* @private
*/
export default function pickModel(
model,
ray,
frameState,
verticalExaggeration,
relativeHeight,
ellipsoid,
result,
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("model", model);
Check.typeOf.object("ray", ray);
Check.typeOf.object("frameState", frameState);
//>>includeEnd('debug');
if (!model._ready || frameState.mode === SceneMode.MORPHING) {
return;
}
let minT = Number.MAX_VALUE;
const sceneGraph = model.sceneGraph;
const nodes = sceneGraph._runtimeNodes;
for (let i = 0; i < nodes.length; i++) {
const runtimeNode = nodes[i];
const node = runtimeNode.node;
let nodeComputedTransform = Matrix4.clone(
runtimeNode.computedTransform,
scratchNodeComputedTransform,
);
let modelMatrix = Matrix4.clone(
sceneGraph.computedModelMatrix,
scratchModelMatrix,
);
const instances = node.instances;
if (defined(instances)) {
if (instances.transformInWorldSpace) {
// Replicate the multiplication order in LegacyInstancingStageVS.
modelMatrix = Matrix4.multiplyTransformation(
model.modelMatrix,
sceneGraph.components.transform,
modelMatrix,
);
nodeComputedTransform = Matrix4.multiplyTransformation(
sceneGraph.axisCorrectionMatrix,
runtimeNode.computedTransform,
nodeComputedTransform,
);
}
}
let computedModelMatrix = Matrix4.multiplyTransformation(
modelMatrix,
nodeComputedTransform,
scratchcomputedModelMatrix,
);
if (frameState.mode !== SceneMode.SCENE3D) {
computedModelMatrix = Transforms.basisTo2D(
frameState.mapProjection,
computedModelMatrix,
computedModelMatrix,
);
}
const transforms = [];
if (defined(instances)) {
const transformsCount = instances.attributes[0].count;
const instanceComponentDatatype =
instances.attributes[0].componentDatatype;
const transformElements = 12;
let transformsTypedArray = runtimeNode.transformsTypedArray;
if (!defined(transformsTypedArray)) {
const instanceTransformsBuffer = runtimeNode.instancingTransformsBuffer;
if (defined(instanceTransformsBuffer) && frameState.context.webgl2) {
transformsTypedArray = ComponentDatatype.createTypedArray(
instanceComponentDatatype,
transformsCount * transformElements,
);
instanceTransformsBuffer.getBufferData(transformsTypedArray);
}
}
if (defined(transformsTypedArray)) {
for (let i = 0; i < transformsCount; i++) {
const index = i * transformElements;
const transform = new Matrix4(
transformsTypedArray[index],
transformsTypedArray[index + 1],
transformsTypedArray[index + 2],
transformsTypedArray[index + 3],
transformsTypedArray[index + 4],
transformsTypedArray[index + 5],
transformsTypedArray[index + 6],
transformsTypedArray[index + 7],
transformsTypedArray[index + 8],
transformsTypedArray[index + 9],
transformsTypedArray[index + 10],
transformsTypedArray[index + 11],
0,
0,
0,
1,
);
if (instances.transformInWorldSpace) {
Matrix4.multiplyTransformation(
transform,
nodeComputedTransform,
transform,
);
Matrix4.multiplyTransformation(modelMatrix, transform, transform);
} else {
Matrix4.multiplyTransformation(
transform,
computedModelMatrix,
transform,
);
}
transforms.push(transform);
}
}
}
if (transforms.length === 0) {
transforms.push(computedModelMatrix);
}
const primitivesLength = runtimeNode.runtimePrimitives.length;
for (let j = 0; j < primitivesLength; j++) {
const runtimePrimitive = runtimeNode.runtimePrimitives[j];
const primitive = runtimePrimitive.primitive;
if (defined(runtimePrimitive.boundingSphere) && !defined(instances)) {
const boundingSphere = BoundingSphere.transform(
runtimePrimitive.boundingSphere,
computedModelMatrix,
scratchBoundingSphere,
);
const boundsIntersection = IntersectionTests.raySphere(
ray,
boundingSphere,
);
if (!defined(boundsIntersection)) {
continue;
}
}
const positionAttribute = ModelUtility.getAttributeBySemantic(
primitive,
VertexAttributeSemantic.POSITION,
);
const byteOffset = positionAttribute.byteOffset;
const byteStride = positionAttribute.byteStride;
const vertexCount = positionAttribute.count;
if (!defined(primitive.indices)) {
// Point clouds
continue;
}
let indices = primitive.indices.typedArray;
if (!defined(indices)) {
const indicesBuffer = primitive.indices.buffer;
const indicesCount = primitive.indices.count;
const indexDatatype = primitive.indices.indexDatatype;
if (defined(indicesBuffer) && frameState.context.webgl2) {
if (indexDatatype === IndexDatatype.UNSIGNED_BYTE) {
indices = new Uint8Array(indicesCount);
} else if (indexDatatype === IndexDatatype.UNSIGNED_SHORT) {
indices = new Uint16Array(indicesCount);
} else if (indexDatatype === IndexDatatype.UNSIGNED_INT) {
indices = new Uint32Array(indicesCount);
}
indicesBuffer.getBufferData(indices);
}
}
let vertices = positionAttribute.typedArray;
let componentDatatype = positionAttribute.componentDatatype;
let attributeType = positionAttribute.type;
const quantization = positionAttribute.quantization;
if (defined(quantization)) {
componentDatatype = positionAttribute.quantization.componentDatatype;
attributeType = positionAttribute.quantization.type;
}
const numComponents = AttributeType.getNumberOfComponents(attributeType);
const bytes = ComponentDatatype.getSizeInBytes(componentDatatype);
const isInterleaved =
!defined(vertices) &&
defined(byteStride) &&
byteStride !== numComponents * bytes;
let elementStride = numComponents;
let offset = 0;
if (isInterleaved) {
elementStride = byteStride / bytes;
offset = byteOffset / bytes;
}
const elementCount = vertexCount * elementStride;
if (!defined(vertices)) {
const verticesBuffer = positionAttribute.buffer;
if (defined(verticesBuffer) && frameState.context.webgl2) {
vertices = ComponentDatatype.createTypedArray(
componentDatatype,
elementCount,
);
verticesBuffer.getBufferData(
vertices,
isInterleaved ? 0 : byteOffset,
0,
elementCount,
);
}
if (quantization && positionAttribute.normalized) {
vertices = AttributeCompression.dequantize(
vertices,
componentDatatype,
attributeType,
vertexCount,
);
}
}
if (!defined(indices) || !defined(vertices)) {
return;
}
ellipsoid = ellipsoid ?? Ellipsoid.default;
verticalExaggeration = verticalExaggeration ?? 1.0;
relativeHeight = relativeHeight ?? 0.0;
const indicesLength = indices.length;
for (let i = 0; i < indicesLength; i += 3) {
const i0 = indices[i];
const i1 = indices[i + 1];
const i2 = indices[i + 2];
for (const instanceTransform of transforms) {
const v0 = getVertexPosition(
vertices,
i0,
offset,
elementStride,
quantization,
instanceTransform,
verticalExaggeration,
relativeHeight,
ellipsoid,
scratchV0,
);
const v1 = getVertexPosition(
vertices,
i1,
offset,
elementStride,
quantization,
instanceTransform,
verticalExaggeration,
relativeHeight,
ellipsoid,
scratchV1,
);
const v2 = getVertexPosition(
vertices,
i2,
offset,
elementStride,
quantization,
instanceTransform,
verticalExaggeration,
relativeHeight,
ellipsoid,
scratchV2,
);
const t = IntersectionTests.rayTriangleParametric(
ray,
v0,
v1,
v2,
model.backFaceCulling ?? true,
);
if (defined(t)) {
if (t < minT && t >= 0.0) {
minT = t;
}
}
}
}
}
}
if (minT === Number.MAX_VALUE) {
return undefined;
}
result = Ray.getPoint(ray, minT, result);
if (frameState.mode !== SceneMode.SCENE3D) {
Cartesian3.fromElements(result.y, result.z, result.x, result);
const projection = frameState.mapProjection;
const ellipsoid = projection.ellipsoid;
const cartographic = projection.unproject(result, scratchPickCartographic);
ellipsoid.cartographicToCartesian(cartographic, result);
}
return result;
}
function getVertexPosition(
vertices,
index,
offset,
numElements,
quantization,
instanceTransform,
verticalExaggeration,
relativeHeight,
ellipsoid,
result,
) {
const i = offset + index * numElements;
result.x = vertices[i];
result.y = vertices[i + 1];
result.z = vertices[i + 2];
if (defined(quantization)) {
if (quantization.octEncoded) {
result = AttributeCompression.octDecodeInRange(
result,
quantization.normalizationRange,
result,
);
if (quantization.octEncodedZXY) {
const x = result.x;
result.x = result.z;
result.z = result.y;
result.y = x;
}
} else {
result = Cartesian3.multiplyComponents(
result,
quantization.quantizedVolumeStepSize,
result,
);
result = Cartesian3.add(
result,
quantization.quantizedVolumeOffset,
result,
);
}
}
result = Matrix4.multiplyByPoint(instanceTransform, result, result);
if (verticalExaggeration !== 1.0) {
VerticalExaggeration.getPosition(
result,
ellipsoid,
verticalExaggeration,
relativeHeight,
result,
);
}
return result;
}