UNPKG

@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
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; }