diff --git a/Apps/Sandcastle/gallery/development/3D Tiles Picking.html b/Apps/Sandcastle/gallery/development/3D Tiles Picking.html index 0ca0d98ad2e0..843b1d4f30e4 100644 --- a/Apps/Sandcastle/gallery/development/3D Tiles Picking.html +++ b/Apps/Sandcastle/gallery/development/3D Tiles Picking.html @@ -41,36 +41,72 @@ baseLayerPicker: false, globe: false, }); - - // Enable rendering the sky - viewer.scene.skyAtmosphere.show = true; + const scene = viewer.scene; let tileset; - // Add Photorealistic 3D Tiles - try { - tileset = await Cesium.createGooglePhotorealistic3DTileset( - undefined, - { - enableDebugWireframe: true, - } - ); - viewer.scene.primitives.add(tileset); - } catch (error) { - console.log(`Error loading Photorealistic 3D Tiles tileset. - ${error}`); - } + const options = [ + { + text: "Google P3DT", + onselect: async () => { + scene.primitives.remove(tileset); + try { + tileset = await Cesium.createGooglePhotorealistic3DTileset(); + scene.primitives.add(tileset); + } catch (error) { + console.log(error); + } + }, + }, + { + text: "Maxar OWT WFF 1.2", + onselect: async () => { + scene.primitives.remove(tileset); + try { + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(691510, { + maximumScreenSpaceError: 4, + }); + scene.primitives.add(tileset); + } catch (error) { + console.log(error); + } + }, + }, + { + text: "Bentley BIM Model", + onselect: async () => { + scene.primitives.remove(tileset); + try { + tileset = await Cesium.Cesium3DTileset.fromIonAssetId(1240402); + scene.primitives.add(tileset); + viewer.zoomTo(tileset); + } catch (error) { + console.log(error); + } + }, + }, + { + text: "Instanced", + onselect: async () => { + scene.primitives.remove(tileset); + try { + tileset = await Cesium.Cesium3DTileset.fromUrl( + "../../SampleData/Cesium3DTiles/Instanced/InstancedWithBatchTable/tileset.json" + ); + scene.primitives.add(tileset); + viewer.zoomTo(tileset); + } catch (error) { + console.log(error); + } + }, + }, + ]; - viewer.extend(Cesium.viewerCesium3DTilesInspectorMixin); - const inspectorViewModel = viewer.cesium3DTilesInspector.viewModel; - inspectorViewModel.tileset = tileset; - - const scene = viewer.scene; + Sandcastle.addDefaultToolbarMenu(options); const scratchCartesian = new Cesium.Cartesian3(); const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); handler.setInputAction(function (movement) { const pickedPositionResult = scene.pickPosition(movement.position); - console.log(pickedPositionResult); if (Cesium.defined(pickedPositionResult)) { viewer.entities.add({ position: pickedPositionResult, @@ -83,12 +119,7 @@ } const ray = scene.camera.getPickRay(movement.position); - const picked = tileset.pick( - ray, - scene.frameState, - true, - scratchCartesian - ); + const picked = tileset.pick(ray, scene.frameState, scratchCartesian); if (Cesium.defined(picked)) { viewer.entities.add({ diff --git a/packages/engine/Source/Scene/Cesium3DTileContent.js b/packages/engine/Source/Scene/Cesium3DTileContent.js index 76dcdd255b47..945242881b42 100644 --- a/packages/engine/Source/Scene/Cesium3DTileContent.js +++ b/packages/engine/Source/Scene/Cesium3DTileContent.js @@ -355,18 +355,12 @@ Cesium3DTileContent.prototype.update = function (tileset, frameState) { * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Cesium3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Cesium3DTileContent.prototype.pick = function (ray, frameState, result) { DeveloperError.throwInstantiationError(); }; diff --git a/packages/engine/Source/Scene/Cesium3DTileset.js b/packages/engine/Source/Scene/Cesium3DTileset.js index 3322452c427c..ac53d8ffb8b4 100644 --- a/packages/engine/Source/Scene/Cesium3DTileset.js +++ b/packages/engine/Source/Scene/Cesium3DTileset.js @@ -3427,24 +3427,19 @@ Cesium3DTileset.prototype.getHeight = function (cartographic, scene) { }; const scratchSphereIntersection = new Interval(); +const scratchPickIntersection = new Cartesian3(); /** * Find an intersection between a ray and the tileset surface that was rendered. The ray must be given in world coordinates. * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Cesium3DTileset.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Cesium3DTileset.prototype.pick = function (ray, frameState, result) { const selectedTiles = this._selectedTiles; const selectedLength = selectedTiles.length; @@ -3461,7 +3456,11 @@ Cesium3DTileset.prototype.pick = function ( continue; } - const candidate = tile.content.pick(ray, frameState, cullBackFaces, result); + const candidate = tile.content.pick( + ray, + frameState, + scratchPickIntersection + ); if (!defined(candidate)) { continue; @@ -3469,7 +3468,7 @@ Cesium3DTileset.prototype.pick = function ( const distance = Cartesian3.distance(ray.origin, candidate); if (distance < minDistance) { - intersection = candidate; + intersection = Cartesian3.clone(candidate, result); minDistance = distance; } } @@ -3478,8 +3477,7 @@ Cesium3DTileset.prototype.pick = function ( return undefined; } - Cartesian3.clone(intersection, result); - return result; + return intersection; }; /** diff --git a/packages/engine/Source/Scene/Composite3DTileContent.js b/packages/engine/Source/Scene/Composite3DTileContent.js index 0925c389b785..02be6c08ac6c 100644 --- a/packages/engine/Source/Scene/Composite3DTileContent.js +++ b/packages/engine/Source/Scene/Composite3DTileContent.js @@ -353,18 +353,12 @@ Composite3DTileContent.prototype.update = function (tileset, frameState) { * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Composite3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Composite3DTileContent.prototype.pick = function (ray, frameState, result) { if (!this._ready) { return undefined; } @@ -375,7 +369,7 @@ Composite3DTileContent.prototype.pick = function ( const length = contents.length; for (let i = 0; i < length; ++i) { - const candidate = contents[i].pick(ray, frameState, cullBackFaces, result); + const candidate = contents[i].pick(ray, frameState, result); if (!defined(candidate)) { continue; diff --git a/packages/engine/Source/Scene/Empty3DTileContent.js b/packages/engine/Source/Scene/Empty3DTileContent.js index 8c2b22435a5d..0b5c4459da35 100644 --- a/packages/engine/Source/Scene/Empty3DTileContent.js +++ b/packages/engine/Source/Scene/Empty3DTileContent.js @@ -150,12 +150,7 @@ Empty3DTileContent.prototype.applyStyle = function (style) {}; Empty3DTileContent.prototype.update = function (tileset, frameState) {}; -Empty3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Empty3DTileContent.prototype.pick = function (ray, frameState, result) { return undefined; }; diff --git a/packages/engine/Source/Scene/Geometry3DTileContent.js b/packages/engine/Source/Scene/Geometry3DTileContent.js index d13b21be91a0..d89a278eb4b1 100644 --- a/packages/engine/Source/Scene/Geometry3DTileContent.js +++ b/packages/engine/Source/Scene/Geometry3DTileContent.js @@ -525,12 +525,7 @@ Geometry3DTileContent.prototype.update = function (tileset, frameState) { } }; -Geometry3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Geometry3DTileContent.prototype.pick = function (ray, frameState, result) { return undefined; }; diff --git a/packages/engine/Source/Scene/Implicit3DTileContent.js b/packages/engine/Source/Scene/Implicit3DTileContent.js index f3f8e6c9ff2f..96eebae51c1d 100644 --- a/packages/engine/Source/Scene/Implicit3DTileContent.js +++ b/packages/engine/Source/Scene/Implicit3DTileContent.js @@ -1177,12 +1177,7 @@ Implicit3DTileContent.prototype.applyStyle = function (style) {}; Implicit3DTileContent.prototype.update = function (tileset, frameState) {}; -Implicit3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Implicit3DTileContent.prototype.pick = function (ray, frameState, result) { return undefined; }; diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index a781e442e6f7..d9a2f8c596c3 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2489,14 +2489,13 @@ Model.prototype.isClippingEnabled = function () { * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Model.prototype.pick = function (ray, frameState, cullBackFaces, result) { - return pickModel(this, ray, frameState, cullBackFaces, result); +Model.prototype.pick = function (ray, frameState, result) { + return pickModel(this, ray, frameState, result); }; /** diff --git a/packages/engine/Source/Scene/Model/Model3DTileContent.js b/packages/engine/Source/Scene/Model/Model3DTileContent.js index c0e71069a2c4..11146fe92d8e 100644 --- a/packages/engine/Source/Scene/Model/Model3DTileContent.js +++ b/packages/engine/Source/Scene/Model/Model3DTileContent.js @@ -421,23 +421,17 @@ Model3DTileContent.fromGeoJson = async function ( * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Model3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Model3DTileContent.prototype.pick = function (ray, frameState, result) { if (!defined(this._model) || !this._ready) { return undefined; } - return this._model.pick(ray, frameState, cullBackFaces, result); + return this._model.pick(ray, frameState, result); }; function makeModelOptions(tileset, tile, content, additionalOptions) { diff --git a/packages/engine/Source/Scene/Model/pickModel.js b/packages/engine/Source/Scene/Model/pickModel.js index de0be7e8cb98..16a2bb02f94f 100644 --- a/packages/engine/Source/Scene/Model/pickModel.js +++ b/packages/engine/Source/Scene/Model/pickModel.js @@ -1,4 +1,5 @@ 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"; @@ -18,9 +19,11 @@ 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 scratchInstanceMatrix = new Matrix4(); +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. @@ -28,19 +31,12 @@ const scratchInstanceMatrix = new Matrix4(); * @param {Model} model The model to pick. * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -export default function pickModel( - model, - ray, - frameState, - cullBackFaces, - result -) { +export default function pickModel(model, ray, frameState, result) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("model", model); Check.typeOf.object("ray", ray); @@ -59,8 +55,14 @@ export default function pickModel( const runtimeNode = nodes[i]; const node = runtimeNode.node; - let nodeComputedTransform = runtimeNode.computedTransform; - let modelMatrix = sceneGraph.computedModelMatrix; + let nodeComputedTransform = Matrix4.clone( + runtimeNode.computedTransform, + scratchNodeComputedTransform + ); + let modelMatrix = Matrix4.clone( + sceneGraph.computedModelMatrix, + scratchModelMatrix + ); const instances = node.instances; if (defined(instances)) { @@ -77,38 +79,23 @@ export default function pickModel( runtimeNode.computedTransform, nodeComputedTransform ); - } else { - // The node transform should be pre-multiplied with the instancing transform. - modelMatrix = Matrix4.clone( - sceneGraph.computedModelMatrix, - modelMatrix - ); - modelMatrix = Matrix4.multiplyTransformation( - modelMatrix, - runtimeNode.computedTransform, - modelMatrix - ); - - nodeComputedTransform = Matrix4.clone( - Matrix4.IDENTITY, - nodeComputedTransform - ); } } - let computedModelMatrix = Matrix4.multiplyTransformation( - modelMatrix, - nodeComputedTransform, - scratchModelMatrix - ); if (frameState.mode !== SceneMode.SCENE3D) { - computedModelMatrix = Transforms.basisTo2D( + modelMatrix = Transforms.basisTo2D( frameState.mapProjection, - computedModelMatrix, - computedModelMatrix + modelMatrix, + modelMatrix ); } + const computedModelMatrix = Matrix4.multiplyTransformation( + modelMatrix, + nodeComputedTransform, + scratchcomputedModelMatrix + ); + const transforms = []; if (defined(instances)) { const transformsCount = instances.attributes[0].count; @@ -130,26 +117,70 @@ export default function pickModel( if (defined(transformsTypedArray)) { for (let i = 0; i < transformsCount; i++) { - const transform = Matrix4.unpack( - transformsTypedArray, - i * transformElements, - scratchInstanceMatrix + 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 ); - transform[12] = 0.0; - transform[13] = 0.0; - transform[14] = 0.0; - transform[15] = 1.0; + + 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(Matrix4.IDENTITY); + transforms.push(computedModelMatrix); } - for (let j = 0; j < node.primitives.length; j++) { - const primitive = node.primitives[j]; + 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 @@ -165,12 +196,17 @@ export default function pickModel( if (!defined(indices)) { const indicesBuffer = primitive.indices.buffer; const indicesCount = primitive.indices.count; + const indexDatatype = primitive.indices.indexDatatype; if (defined(indicesBuffer) && frameState.context.webgl2) { - const useUint8Array = indicesBuffer.sizeInBytes === indicesCount; - indices = useUint8Array - ? new Uint8Array(indicesCount) - : IndexDatatype.createTypedArray(vertexCount, indicesCount); - indicesBuffer.getBufferData(indices, 0, 0, indicesCount); + 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); } } @@ -230,7 +266,6 @@ export default function pickModel( numComponents, quantization, instanceTransform, - computedModelMatrix, scratchV0 ); const v1 = getVertexPosition( @@ -239,7 +274,6 @@ export default function pickModel( numComponents, quantization, instanceTransform, - computedModelMatrix, scratchV1 ); const v2 = getVertexPosition( @@ -248,7 +282,6 @@ export default function pickModel( numComponents, quantization, instanceTransform, - computedModelMatrix, scratchV2 ); @@ -257,7 +290,7 @@ export default function pickModel( v0, v1, v2, - defaultValue(cullBackFaces, true) + defaultValue(model.backFaceCulling, true) ); if (defined(t)) { @@ -281,8 +314,8 @@ export default function pickModel( const projection = frameState.mapProjection; const ellipsoid = projection.ellipsoid; - const cart = projection.unproject(result, scratchPickCartographic); - ellipsoid.cartographicToCartesian(cart, result); + const cartographic = projection.unproject(result, scratchPickCartographic); + ellipsoid.cartographicToCartesian(cartographic, result); } return result; @@ -294,7 +327,6 @@ function getVertexPosition( numComponents, quantization, instanceTransform, - computedModelMatrix, result ) { const i = index * numComponents; @@ -332,7 +364,6 @@ function getVertexPosition( } result = Matrix4.multiplyByPoint(instanceTransform, result, result); - result = Matrix4.multiplyByPoint(computedModelMatrix, result, result); return result; } diff --git a/packages/engine/Source/Scene/Multiple3DTileContent.js b/packages/engine/Source/Scene/Multiple3DTileContent.js index bef1cb3eb6cb..42ede698a349 100644 --- a/packages/engine/Source/Scene/Multiple3DTileContent.js +++ b/packages/engine/Source/Scene/Multiple3DTileContent.js @@ -656,18 +656,12 @@ Multiple3DTileContent.prototype.update = function (tileset, frameState) { * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. - * @param {boolean} [cullBackFaces=true] If false, back faces are not culled and will return an intersection if picked. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ -Multiple3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Multiple3DTileContent.prototype.pick = function (ray, frameState, result) { if (!this._ready) { return undefined; } @@ -678,7 +672,7 @@ Multiple3DTileContent.prototype.pick = function ( const length = contents.length; for (let i = 0; i < length; ++i) { - const candidate = contents[i].pick(ray, frameState, cullBackFaces, result); + const candidate = contents[i].pick(ray, frameState, result); if (!defined(candidate)) { continue; diff --git a/packages/engine/Source/Scene/Tileset3DTileContent.js b/packages/engine/Source/Scene/Tileset3DTileContent.js index 655f0e675ef1..41d1652453c1 100644 --- a/packages/engine/Source/Scene/Tileset3DTileContent.js +++ b/packages/engine/Source/Scene/Tileset3DTileContent.js @@ -168,6 +168,10 @@ Tileset3DTileContent.prototype.applyStyle = function (style) {}; Tileset3DTileContent.prototype.update = function (tileset, frameState) {}; +Tileset3DTileContent.prototype.pick = function (ray, frameState, result) { + return undefined; +}; + Tileset3DTileContent.prototype.isDestroyed = function () { return false; }; diff --git a/packages/engine/Source/Scene/Vector3DTileContent.js b/packages/engine/Source/Scene/Vector3DTileContent.js index 4d779bbf562b..120a22db5801 100644 --- a/packages/engine/Source/Scene/Vector3DTileContent.js +++ b/packages/engine/Source/Scene/Vector3DTileContent.js @@ -727,12 +727,7 @@ Vector3DTileContent.prototype.update = function (tileset, frameState) { } }; -Vector3DTileContent.prototype.pick = function ( - ray, - frameState, - cullBackFaces, - result -) { +Vector3DTileContent.prototype.pick = function (ray, frameState, result) { return undefined; }; diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js index 2e3381f89d49..6b85a670e228 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js @@ -2414,6 +2414,151 @@ describe( }); }); + it("picks", async function () { + const tileset = await Cesium3DTilesTester.loadTileset(scene, tilesetUrl); + viewRootOnly(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215026.8094312553, + -4736367.339076743, + 4081652.238842398 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picks tileset of tilesets", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + tilesetOfTilesetsUrl + ); + viewRootOnly(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215026.8094312553, + -4736367.339076743, + 4081652.238842398 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picks instanced tileset", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + instancedUrl + ); + viewInstances(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215015.7820120894, + -4736324.352446682, + 4081615.004915994 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picks translucent tileset", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + translucentUrl + ); + viewAllTiles(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215013.1035421563, + -4736313.911345786, + 4081605.96109977 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picks tileset with transforms", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + tilesetWithTransformsUrl + ); + viewAllTiles(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3( + 1215013.8353220497, + -4736316.763939952, + 4081608.4319443353 + ); + expect(tileset.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + + it("picking point cloud tileset returns undefined", async function () { + const tileset = await Cesium3DTilesTester.loadTileset( + scene, + pointCloudUrl + ); + viewAllTiles(); + scene.renderForSpecs(); + + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + expect(tileset.pick(ray, scene.frameState)).toBeUndefined(); + }); + it("destroys", function () { return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function ( tileset diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index df62257eb949..a93f00370367 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -4392,6 +4392,28 @@ describe( }); }); + it("pick returns position of intersection between ray and model surface", async function () { + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + enablePick: !scene.frameState.context.webgl2, + }, + scene + ); + const ray = scene.camera.getPickRay( + new Cartesian2( + scene.drawingBufferWidth / 2.0, + scene.drawingBufferHeight / 2.0 + ) + ); + + const expected = new Cartesian3(0.5, 0, 0.5); + expect(model.pick(ray, scene.frameState)).toEqualEpsilon( + expected, + CesiumMath.EPSILON12 + ); + }); + it("destroy works", function () { spyOn(ShaderProgram.prototype, "destroy").and.callThrough(); return loadAndZoomToModelAsync({ gltf: boxTexturedGlbUrl }, scene).then( diff --git a/packages/engine/Specs/Scene/Model/pickModelSpec.js b/packages/engine/Specs/Scene/Model/pickModelSpec.js index 7aea7e85903d..a3a0d0ac4a2c 100644 --- a/packages/engine/Specs/Scene/Model/pickModelSpec.js +++ b/packages/engine/Specs/Scene/Model/pickModelSpec.js @@ -2,6 +2,7 @@ import { pickModel, Cartesian2, Cartesian3, + HeadingPitchRange, Math as CesiumMath, Model, Ray, @@ -15,7 +16,7 @@ describe("Scene/Model/pickModel", function () { const boxTexturedGltfUrl = "./Data/Models/glTF-2.0/BoxTextured/glTF/BoxTextured.gltf"; const boxInstanced = - "./Data/Models/glTF-2.0/BoxInstanced/glTF/box-instanced.gltf"; + "./Data/Models/glTF-2.0/BoxInstancedNoNormals/glTF/BoxInstancedNoNormals.gltf"; const boxWithOffsetUrl = "./Data/Models/glTF-2.0/BoxWithOffset/glTF/BoxWithOffset.gltf"; const pointCloudUrl = @@ -26,6 +27,8 @@ describe("Scene/Model/pickModel", function () { "./Data/Models/glTF-2.0/BoxWeb3dQuantizedAttributes/glTF/BoxWeb3dQuantizedAttributes.gltf"; const boxCesiumRtcUrl = "./Data/Models/glTF-2.0/BoxCesiumRtc/glTF/BoxCesiumRtc.gltf"; + const boxBackFaceCullingUrl = + "./Data/Models/glTF-2.0/BoxBackFaceCulling/glTF/BoxBackFaceCulling.gltf"; let scene; beforeAll(function () { @@ -223,10 +226,19 @@ describe("Scene/Model/pickModel", function () { }); it("returns position of intersection with instanced model", async function () { + // None of the 4 instanced cubes are in the center of the model's bounding + // sphere, so set up a camera view that focuses in on one of them. + const offset = new HeadingPitchRange( + CesiumMath.PI_OVER_TWO, + -CesiumMath.PI_OVER_FOUR, + 1 + ); + const model = await loadAndZoomToModelAsync( { url: boxInstanced, enablePick: !scene.frameState.context.webgl2, + offset, }, scene ); @@ -237,7 +249,7 @@ describe("Scene/Model/pickModel", function () { ) ); - const expected = new Cartesian3(0.278338500214, 0, 0.278338500214); + const expected = new Cartesian3(0, -0.5, 0.5); expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( expected, CesiumMath.EPSILON12 @@ -281,11 +293,12 @@ describe("Scene/Model/pickModel", function () { expect(pickModel(model, ray, scene.frameState)).toBeUndefined(); }); - it("includes back faces results when cullsBackFaces is false", async function () { + it("includes back faces results when model disbales backface culling", async function () { const model = await loadAndZoomToModelAsync( { - url: boxTexturedGltfUrl, + url: boxBackFaceCullingUrl, enablePick: !scene.frameState.context.webgl2, + backFaceCulling: false, }, scene ); @@ -295,10 +308,15 @@ describe("Scene/Model/pickModel", function () { scene.drawingBufferHeight / 2.0 ) ); + ray.origin = model.boundingSphere.center; - const expected = new Cartesian3(-0.5, 0, -0.5); - expect(pickModel(model, ray, scene.frameState, false)).toEqualEpsilon( + const expected = new Cartesian3( + -0.9999998807907355, + 0, + -0.9999998807907104 + ); + expect(pickModel(model, ray, scene.frameState)).toEqualEpsilon( expected, CesiumMath.EPSILON12 );