From bd0c043d180183d82413d196cda1b8e1f5186213 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Sun, 21 Jun 2020 16:40:45 -0400 Subject: [PATCH 1/3] Add backFaceCulling option to model --- Source/Scene/Model.js | 80 ++++++++++++-- .../Box-Back-Face-Culling.gltf | 100 ++++++++++++++++++ Specs/Scene/ModelSpec.js | 31 ++++++ 3 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 Specs/Data/Models/Box-Back-Face-Culling/Box-Back-Face-Culling.gltf diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index b3a2f3772ff5..431755aa5bf1 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -217,6 +217,7 @@ var uriToGuid = {}; * @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting. * @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps. * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas. + * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the material's doubleSided property; when false, back face culling is disabled. Back faces are not culled if {@link Model#color} is translucent or {@link Model#silhouetteSize} is greater than 0.0. * * @see Model.fromGltf * @@ -493,6 +494,18 @@ function Model(options) { // to the root tile. this.clippingPlanesOriginMatrix = undefined; + /** + * Whether to cull back-facing geometry. When true, back face culling is + * determined by the material's doubleSided property; when false, back face + * culling is disabled. Back faces are not culled if {@link Model#color} is + * translucent or {@link Model#silhouetteSize} is greater than 0.0. + * + * @type {Boolean} + * + * @default true + */ + this.backFaceCulling = defaultValue(options.backFaceCulling, true); + /** * This property is for debugging only; it is not for production use nor is it optimized. *

@@ -1371,6 +1384,7 @@ function containsGltfMagic(uint8Array) { * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model. * @param {Boolean} [options.dequantizeInShader=true] Determines if a {@link https://github.com/google/draco|Draco} encoded model is dequantized on the GPU. This decreases total memory usage for encoded models. * @param {Credit|String} [options.credit] A credit for the model, which is displayed on the canvas. + * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the material's doubleSided property; when false, back face culling is disabled. Back faces are not culled if {@link Model#color} is translucent or {@link Model#silhouetteSize} is greater than 0.0. * * @returns {Model} The newly created model. * @@ -3947,6 +3961,9 @@ function createCommand(model, gltfNode, runtimeNode, context, scene3DOnly) { // Generated on demand when color alpha is less than 1.0 translucentCommand: undefined, translucentCommand2D: undefined, + // Generated on demand when back face culling is false + disableCullingCommand: undefined, + disableCullingCommand2D: undefined, // For updating node commands on shader reconstruction programId: programId, }; @@ -4516,6 +4533,44 @@ function updateColor(model, frameState, forceDerive) { } } +function getDisableCullingRenderState(renderState) { + var rs = clone(renderState, true); + rs.cull.enabled = false; + return RenderState.fromCache(rs); +} + +function deriveDisableCullingCommand(command) { + var disableCullingCommand = DrawCommand.shallowClone(command); + disableCullingCommand.renderState = getDisableCullingRenderState( + command.renderState + ); + return disableCullingCommand; +} + +function updateBackFaceCulling(model, frameState, forceDerive) { + var scene3DOnly = frameState.scene3DOnly; + var backFaceCulling = model.backFaceCulling; + if (!backFaceCulling) { + var nodeCommands = model._nodeCommands; + var length = nodeCommands.length; + if (!defined(nodeCommands[0].disableCullingCommand) || forceDerive) { + for (var i = 0; i < length; ++i) { + var nodeCommand = nodeCommands[i]; + var command = nodeCommand.command; + nodeCommand.disableCullingCommand = deriveDisableCullingCommand( + command + ); + if (!scene3DOnly) { + var command2D = nodeCommand.command2D; + nodeCommand.disableCullingCommand2D = deriveDisableCullingCommand( + command2D + ); + } + } + } + } +} + function getProgramId(model, program) { var programs = model._rendererResources.programs; for (var id in programs) { @@ -5347,6 +5402,7 @@ Model.prototype.update = function (frameState) { var silhouette = hasSilhouette(this, frameState); var translucent = isTranslucent(this); var invisible = isInvisible(this); + var backFaceCulling = this.backFaceCulling; var displayConditionPassed = defined(this.distanceDisplayCondition) ? distanceDisplayConditionVisible(this, frameState) : true; @@ -5491,6 +5547,7 @@ Model.prototype.update = function (frameState) { regenerateShaders(this, frameState); } else { updateColor(this, frameState, false); + updateBackFaceCulling(this, frameState, false); updateSilhouette(this, frameState, false); } } @@ -5525,8 +5582,14 @@ Model.prototype.update = function (frameState) { for (i = 0; i < length; ++i) { nc = nodeCommands[i]; if (nc.show) { - var command = translucent ? nc.translucentCommand : nc.command; - command = silhouette ? nc.silhouetteModelCommand : command; + var command = nc.command; + if (silhouette) { + command = nc.silhouetteModelCommand; + } else if (translucent) { + command = nc.translucentCommand; + } else if (!backFaceCulling) { + command = nc.disableCullingCommand; + } commandList.push(command); boundingVolume = nc.command.boundingVolume; if ( @@ -5534,10 +5597,14 @@ Model.prototype.update = function (frameState) { (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D) ) { - var command2D = translucent - ? nc.translucentCommand2D - : nc.command2D; - command2D = silhouette ? nc.silhouetteModelCommand2D : command2D; + var command2D = nc.command; + if (silhouette) { + command2D = nc.silhouetteModelCommand2D; + } else if (translucent) { + command2D = nc.translucentCommand2D; + } else if (!backFaceCulling) { + command2D = nc.disableCullingCommand2D; + } commandList.push(command2D); } } @@ -5664,6 +5731,7 @@ function regenerateShaders(model, frameState) { // Force update silhouette commands/shaders updateColor(model, frameState, true); + updateBackFaceCulling(model, frameState, true); updateSilhouette(model, frameState, true); } diff --git a/Specs/Data/Models/Box-Back-Face-Culling/Box-Back-Face-Culling.gltf b/Specs/Data/Models/Box-Back-Face-Culling/Box-Back-Face-Culling.gltf new file mode 100644 index 000000000000..5c278ddb2141 --- /dev/null +++ b/Specs/Data/Models/Box-Back-Face-Culling/Box-Back-Face-Culling.gltf @@ -0,0 +1,100 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v1.1.46", + "version" : "2.0" + }, + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0 + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "Cube" + } + ], + "meshes" : [ + { + "name" : "Cube.001", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3 + } + ] + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 20, + "max" : [ + 1, + 0.9999998807907104, + 1 + ], + "min" : [ + -1, + -0.9999998807907104, + -1 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 20, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 20, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5123, + "count" : 30, + "type" : "SCALAR" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 240, + "byteOffset" : 0 + }, + { + "buffer" : 0, + "byteLength" : 240, + "byteOffset" : 240 + }, + { + "buffer" : 0, + "byteLength" : 160, + "byteOffset" : 480 + }, + { + "buffer" : 0, + "byteLength" : 60, + "byteOffset" : 640 + } + ], + "buffers" : [ + { + "byteLength" : 700, + "uri" : "data:application/octet-stream;base64,/v9/P/7/fz8AAIA//v9/P/7/f78AAIA/AACAP/7/f7/+/3+/AACAP/7/fz/+/3+/AACAv/7/fz/+/38/AACAv/7/f7/+/38//v9/P/7/f78AAIA//v9/P/7/fz8AAIA/AACAP/7/fz/+/3+/AACAP/7/f7/+/3+//v9/v/7/f78AAIC//v9/v/7/fz8AAIC/AACAv/7/f7/+/38//v9/v/7/f78AAIC/AACAP/7/f7/+/3+//v9/P/7/f78AAIA/AACAP/7/fz/+/3+//v9/v/7/fz8AAIC/AACAv/7/fz/+/38//v9/P/7/fz8AAIA/AACAPwAAAAABAIAzAACAPwAAAAABAIAzAACAPwAAAAABAIAzAACAPwAAAAABAIAzAQCAswAAAAAAAIA/AQCAswAAAAAAAIA/AQCAswAAAAAAAIA/AQCAswAAAAAAAIA/AQCAMwAAAAAAAIC/AQCAMwAAAAAAAIC/AQCAMwAAAAAAAIC/AQCAMwAAAAAAAIC/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAgPwAAQD8AAMA+AABAPwAAwD4AAIA/AAAgPwAAgD8AACA/AAAAPwAAwD4AAAA/AADAPgAAQD8AACA/AABAPwAAID8AAAAAAADAPgAAAAAAAMA+AACAPgAAID8AAIA+AADAPgAAAD8AAMA+AACAPgAAAD4AAIA+AAAAPgAAAD8AAGA/AACAPgAAID8AAIA+AAAgPwAAAD8AAGA/AAAAPwAAAQACAAAAAgADAAQABQAGAAQABgAHAAgACQAKAAgACgALAAwADQAOAAwADgAPABAAEQASABMAEAASAA==" + } + ] +} diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js index b2db67d54604..01b20f31acf6 100644 --- a/Specs/Scene/ModelSpec.js +++ b/Specs/Scene/ModelSpec.js @@ -148,6 +148,9 @@ describe( var boxGltf2WithTechniquesUrl = "./Data/Models/Box-Gltf-2-Techniques/Box.gltf"; + var boxBackFaceCullingUrl = + "./Data/Models/Box-Back-Face-Culling/Box-Back-Face-Culling.gltf"; + var texturedBoxModel; var cesiumAirModel; var animBoxesModel; @@ -3857,6 +3860,34 @@ describe( }); }); + it("renders box when back face culling is disabled", function () { + return loadModel(boxBackFaceCullingUrl).then(function (model) { + expect(model.ready).toBe(true); + model.show = true; + + // Look at the model + model.zoomTo(); + + expect({ + scene: scene, + time: JulianDate.fromDate(new Date("January 1, 2014 12:00:00 UTC")), + }).toRenderAndCall(function (rgba) { + expect(rgba).toEqual([0, 0, 0, 255]); + }); + + model.backFaceCulling = false; + + expect({ + scene: scene, + time: JulianDate.fromDate(new Date("January 1, 2014 12:00:00 UTC")), + }).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + }); + + primitives.remove(model); + }); + }); + describe("height referenced model", function () { function createMockGlobe() { var globe = { From 69ab6bd79dc1cf3fe5f0be1b88374615898d593d Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Sun, 21 Jun 2020 20:47:51 -0400 Subject: [PATCH 2/3] Add back face culling for tilesets --- Source/Scene/Batched3DModel3DTileContent.js | 2 + Source/Scene/Cesium3DTileset.js | 10 +++ Source/Scene/Instanced3DModel3DTileContent.js | 2 + Source/Scene/Model.js | 2 +- Source/Scene/ModelInstanceCollection.js | 72 +++++++++++++++---- Specs/Scene/Cesium3DTilesetSpec.js | 56 +++++++++++++++ 6 files changed, 131 insertions(+), 13 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 9c0586953e5d..c333c09890ca 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -431,6 +431,7 @@ function initialize(content, arrayBuffer, byteOffset) { luminanceAtZenith: tileset.luminanceAtZenith, sphericalHarmonicCoefficients: tileset.sphericalHarmonicCoefficients, specularEnvironmentMaps: tileset.specularEnvironmentMaps, + backFaceCulling: tileset.backFaceCulling, }); } else { // This transcodes glTF to an internal representation for geometry so we can take advantage of the re-batching of vector data. @@ -533,6 +534,7 @@ Batched3DModel3DTileContent.prototype.update = function (tileset, frameState) { this._model.luminanceAtZenith = this._tileset.luminanceAtZenith; this._model.sphericalHarmonicCoefficients = this._tileset.sphericalHarmonicCoefficients; this._model.specularEnvironmentMaps = this._tileset.specularEnvironmentMaps; + this._model.backFaceCulling = this._tileset.backFaceCulling; this._model.debugWireframe = this._tileset.debugWireframe; // Update clipping planes diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 45bfd2c01da9..e62d700a96d1 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -89,6 +89,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js"; * @param {Number} [options.luminanceAtZenith=0.2] The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map. * @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting. * @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps. + * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled. * @param {String} [options.debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value. * @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. * @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. @@ -750,6 +751,15 @@ function Cesium3DTileset(options) { */ this.specularEnvironmentMaps = options.specularEnvironmentMaps; + /** + * Whether to cull back-facing geometry. When true, back face culling is determined + * by the glTF material's doubleSided property; when false, back face culling is disabled. + * + * @type {Boolean} + * @default true + */ + this.backFaceCulling = defaultValue(options.backFaceCulling, true); + /** * This property is for debugging only; it is not optimized for production use. *

diff --git a/Source/Scene/Instanced3DModel3DTileContent.js b/Source/Scene/Instanced3DModel3DTileContent.js index 804e3d9eacfa..cfcf28f15b6b 100644 --- a/Source/Scene/Instanced3DModel3DTileContent.js +++ b/Source/Scene/Instanced3DModel3DTileContent.js @@ -303,6 +303,7 @@ function initialize(content, arrayBuffer, byteOffset) { luminanceAtZenith: tileset.luminanceAtZenith, sphericalHarmonicCoefficients: tileset.sphericalHarmonicCoefficients, specularEnvironmentMaps: tileset.specularEnvironmentMaps, + backFaceCulling: tileset.backFaceCulling, }; if (gltfFormat === 0) { @@ -609,6 +610,7 @@ Instanced3DModel3DTileContent.prototype.update = function ( this._modelInstanceCollection.luminanceAtZenith = this._tileset.luminanceAtZenith; this._modelInstanceCollection.sphericalHarmonicCoefficients = this._tileset.sphericalHarmonicCoefficients; this._modelInstanceCollection.specularEnvironmentMaps = this._tileset.specularEnvironmentMaps; + this._modelInstanceCollection.backFaceCulling = this._tileset.backFaceCulling; this._modelInstanceCollection.debugWireframe = this._tileset.debugWireframe; var model = this._modelInstanceCollection._model; diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 431755aa5bf1..b89fd91a944a 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -5597,7 +5597,7 @@ Model.prototype.update = function (frameState) { (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D) ) { - var command2D = nc.command; + var command2D = nc.command2D; if (silhouette) { command2D = nc.silhouetteModelCommand2D; } else if (translucent) { diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 120f77cb4fed..a94956523945 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -18,6 +18,7 @@ import Buffer from "../Renderer/Buffer.js"; import BufferUsage from "../Renderer/BufferUsage.js"; import DrawCommand from "../Renderer/DrawCommand.js"; import Pass from "../Renderer/Pass.js"; +import RenderState from "../Renderer/RenderState.js"; import ShaderSource from "../Renderer/ShaderSource.js"; import ForEach from "../ThirdParty/GltfPipeline/ForEach.js"; import when from "../ThirdParty/when.js"; @@ -62,6 +63,7 @@ var LoadState = { * @param {Number} [options.luminanceAtZenith=0.2] The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map. * @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting. * @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps. + * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe. * @@ -114,6 +116,9 @@ function ModelInstanceCollection(options) { this._drawCommands = []; this._modelCommands = undefined; + this._renderStates = undefined; + this._disableCullingRenderStates = undefined; + this._boundingSphere = createBoundingSphere(this); this._center = Cartesian3.clone(this._boundingSphere.center); this._rtcTransform = new Matrix4(); @@ -157,6 +162,8 @@ function ModelInstanceCollection(options) { this.luminanceAtZenith = options.luminanceAtZenith; this.sphericalHarmonicCoefficients = options.sphericalHarmonicCoefficients; this.specularEnvironmentMaps = options.specularEnvironmentMaps; + this.backFaceCulling = defaultValue(options.backFaceCulling, true); + this._backFaceCulling = this.backFaceCulling; } Object.defineProperties(ModelInstanceCollection.prototype, { @@ -787,8 +794,8 @@ function createModel(collection, context) { } } -function updateWireframe(collection) { - if (collection._debugWireframe !== collection.debugWireframe) { +function updateWireframe(collection, force) { + if (collection._debugWireframe !== collection.debugWireframe || force) { collection._debugWireframe = collection.debugWireframe; // This assumes the original primitive was TRIANGLES and that the triangles @@ -803,9 +810,45 @@ function updateWireframe(collection) { } } } -function updateShowBoundingVolume(collection) { + +function getDisableCullingRenderState(renderState) { + var rs = clone(renderState, true); + rs.cull.enabled = false; + return RenderState.fromCache(rs); +} + +function updateBackFaceCulling(collection, force) { + if (collection._backFaceCulling !== collection.backFaceCulling || force) { + collection._backFaceCulling = collection.backFaceCulling; + + var commands = collection._drawCommands; + var length = commands.length; + var i; + + if (!defined(collection._disableCullingRenderStates)) { + collection._disableCullingRenderStates = new Array(length); + collection._renderStates = new Array(length); + for (i = 0; i < length; ++i) { + var renderState = commands[i].renderState; + var derivedRenderState = getDisableCullingRenderState(renderState); + collection._disableCullingRenderStates[i] = derivedRenderState; + collection._renderStates[i] = renderState; + } + } + + for (i = 0; i < length; ++i) { + commands[i].renderState = collection._backFaceCulling + ? collection._renderStates[i] + : collection._disableCullingRenderStates[i]; + } + } +} + +function updateShowBoundingVolume(collection, force) { if ( - collection.debugShowBoundingVolume !== collection._debugShowBoundingVolume + collection.debugShowBoundingVolume !== + collection._debugShowBoundingVolume || + force ) { collection._debugShowBoundingVolume = collection.debugShowBoundingVolume; @@ -939,13 +982,16 @@ function commandsDirty(model) { var nodeCommands = model._nodeCommands; var length = nodeCommands.length; + var commandsDirty = false; + for (var i = 0; i < length; i++) { var nc = nodeCommands[i]; if (nc.command.dirty) { - return true; + nc.command.dirty = false; + commandsDirty = true; } } - return false; + return commandsDirty; } function generateModelCommands(modelInstanceCollection, instancingSupported) { @@ -960,8 +1006,8 @@ function generateModelCommands(modelInstanceCollection, instancingSupported) { } } -function updateShadows(collection) { - if (collection.shadows !== collection._shadows) { +function updateShadows(collection, force) { + if (collection.shadows !== collection._shadows || force) { collection._shadows = collection.shadows; var castShadows = ShadowMode.castShadows(collection.shadows); @@ -1067,7 +1113,8 @@ ModelInstanceCollection.prototype.update = function (frameState) { } // If the model was set to rebuild shaders during update, rebuild instanced commands. - if (commandsDirty(model)) { + var modelCommandsDirty = commandsDirty(model); + if (modelCommandsDirty) { generateModelCommands(this, instancingSupported); } @@ -1081,9 +1128,10 @@ ModelInstanceCollection.prototype.update = function (frameState) { updateCommandsNonInstanced(this); } - updateShadows(this); - updateWireframe(this); - updateShowBoundingVolume(this); + updateShadows(this, modelCommandsDirty); + updateWireframe(this, modelCommandsDirty); + updateBackFaceCulling(this, modelCommandsDirty); + updateShowBoundingVolume(this, modelCommandsDirty); var passes = frameState.passes; if (!passes.render && !passes.pick) { diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index c18da72e413b..716a296e2c63 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -7,6 +7,7 @@ import { defined } from "../../Source/Cesium.js"; import { getAbsoluteUri } from "../../Source/Cesium.js"; import { getStringFromTypedArray } from "../../Source/Cesium.js"; import { HeadingPitchRange } from "../../Source/Cesium.js"; +import { HeadingPitchRoll } from "../../Source/Cesium.js"; import { Intersect } from "../../Source/Cesium.js"; import { JulianDate } from "../../Source/Cesium.js"; import { Math as CesiumMath } from "../../Source/Cesium.js"; @@ -2434,6 +2435,61 @@ describe( ); }); + function testBackFaceCulling(url, setViewOptions) { + var renderOptions = { + scene: scene, + time: new JulianDate(2457522.154792), + }; + return Cesium3DTilesTester.loadTileset(scene, url).then(function ( + tileset + ) { + scene.camera.setView(setViewOptions); + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).toEqual([0, 0, 0, 255]); + tileset.backFaceCulling = false; + expect(renderOptions).toRenderAndCall(function (rgba2) { + expect(rgba2).not.toEqual(rgba); + }); + }); + }); + } + + it("renders b3dm tileset when back face culling is disabled", function () { + var setViewOptions = { + destination: new Cartesian3( + 1215012.6853779217, + -4736313.101374343, + 4081603.4657718465 + ), + orientation: new HeadingPitchRoll( + 6.283185307179584, + -0.49999825387267993, + 6.283185307179586 + ), + endTransform: Matrix4.IDENTITY, + }; + + return testBackFaceCulling(withoutBatchTableUrl, setViewOptions); + }); + + it("renders i3dm tileset when back face culling is disabled", function () { + var setViewOptions = { + destination: new Cartesian3( + 1215015.8599828142, + -4736324.65638894, + 4081609.967056947 + ), + orientation: new HeadingPitchRoll( + 6.283185307179585, + -0.5000006393986758, + 6.283185307179586 + ), + endTransform: Matrix4.IDENTITY, + }; + + return testBackFaceCulling(instancedUrl, setViewOptions); + }); + /////////////////////////////////////////////////////////////////////////// // Styling tests From 074d963af075fd5d387bace1c7709acb2ea7db3c Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Sun, 21 Jun 2020 21:12:40 -0400 Subject: [PATCH 3/3] Updated CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 098711f0a489..64ed0fad4f25 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ ##### Additions :tada: - Added support for PolylineVolume in CZML [#8841](https://github.com/CesiumGS/cesium/pull/8841) +- Added `backFaceCulling` property to `Cesium3DTileset` and `Model` to support viewing the underside or interior of a tileset or model. [#8981](https://github.com/CesiumGS/cesium/pull/8981) ##### Fixes :wrench: