| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136 |
- import buildVoxelCustomShader from "./buildVoxelCustomShader.js";
- import buildVoxelDrawCommands from "./buildVoxelDrawCommands.js";
- import Cartesian2 from "../Core/Cartesian2.js";
- import Cartesian3 from "../Core/Cartesian3.js";
- import Cartesian4 from "../Core/Cartesian4.js";
- import Cartographic from "../Core/Cartographic.js";
- import Cesium3DTilesetStatistics from "./Cesium3DTilesetStatistics.js";
- import CesiumMath from "../Core/Math.js";
- import Check from "../Core/Check.js";
- import Color from "../Core/Color.js";
- import ClippingPlaneCollection from "./ClippingPlaneCollection.js";
- import clone from "../Core/clone.js";
- import CustomShader from "./Model/CustomShader.js";
- import Frozen from "../Core/Frozen.js";
- import defined from "../Core/defined.js";
- import destroyObject from "../Core/destroyObject.js";
- import Ellipsoid from "../Core/Ellipsoid.js";
- import Event from "../Core/Event.js";
- import JulianDate from "../Core/JulianDate.js";
- import Material from "./Material.js";
- import Matrix3 from "../Core/Matrix3.js";
- import Matrix4 from "../Core/Matrix4.js";
- import MetadataComponentType from "./MetadataComponentType.js";
- import MetadataType from "./MetadataType.js";
- import oneTimeWarning from "../Core/oneTimeWarning.js";
- import PolylineCollection from "./PolylineCollection.js";
- import VerticalExaggeration from "../Core/VerticalExaggeration.js";
- import VoxelContent from "./VoxelContent.js";
- import VoxelShapeType from "./VoxelShapeType.js";
- import VoxelTraversal from "./VoxelTraversal.js";
- import VoxelMetadataOrder from "./VoxelMetadataOrder.js";
-
- /**
- * A primitive that renders voxel data from a {@link VoxelProvider}.
- *
- * @alias VoxelPrimitive
- * @constructor
- *
- * @param {object} [options] Object with the following properties:
- * @param {VoxelProvider} [options.provider] The voxel provider that supplies the primitive with tile data.
- * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix used to transform the primitive.
- * @param {CustomShader} [options.customShader] The custom shader used to style the primitive.
- * @param {Clock} [options.clock] The clock used to control time dynamic behavior.
- * @param {boolean} [options.calculateStatistics] Generate statistics for performance profile.
- *
- * @see VoxelProvider
- * @see Cesium3DTilesVoxelProvider
- * @see VoxelShapeType
- * @see {@link https://github.com/CesiumGS/cesium/tree/main/Documentation/CustomShaderGuide|Custom Shader Guide}
- *
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- */
- function VoxelPrimitive(options) {
- options = options ?? Frozen.EMPTY_OBJECT;
- const {
- provider = VoxelPrimitive.DefaultProvider,
- modelMatrix = Matrix4.IDENTITY,
- customShader = buildVoxelCustomShader(provider),
- clock,
- calculateStatistics = false,
- } = options;
-
- /**
- * @type {boolean}
- * @private
- */
- this._ready = false;
-
- /**
- * @type {VoxelProvider}
- * @private
- */
- this._provider = provider;
-
- /**
- * This member is not created until the first update loop.
- *
- * @type {VoxelTraversal}
- * @private
- */
- this._traversal = undefined;
-
- /**
- * @type {Cesium3DTilesetStatistics}
- * @private
- */
- this._statistics = new Cesium3DTilesetStatistics();
-
- /**
- * @type {boolean}
- * @private
- */
- this._calculateStatistics = calculateStatistics;
-
- const {
- shape: shapeType,
- minBounds = VoxelShapeType.getMinBounds(shapeType),
- maxBounds = VoxelShapeType.getMaxBounds(shapeType),
- dimensions,
- paddingBefore = Cartesian3.ZERO,
- paddingAfter = Cartesian3.ZERO,
- metadataOrder,
- availableLevels = 1,
- } = provider;
-
- /**
- * @type {Cartesian3}
- * @readonly
- * @constant
- * @private
- */
- this._dimensions = Cartesian3.clone(dimensions);
-
- /**
- * @type {Cartesian3}
- * @private
- */
- this._paddingBefore = Cartesian3.clone(paddingBefore);
-
- /**
- * @type {Cartesian3}
- * @private
- */
- this._paddingAfter = Cartesian3.clone(paddingAfter);
-
- /**
- * @type {Cartesian3}
- * @private
- */
- this._inputDimensions = computeInputDimensions(
- dimensions,
- paddingBefore,
- paddingAfter,
- metadataOrder,
- );
-
- /**
- * @type {number}
- * @private
- */
- this._availableLevels = availableLevels;
-
- /**
- * @type {Cartesian3}
- * @private
- */
- this._minBounds = minBounds.clone();
-
- /**
- * Used to detect if the shape is dirty.
- *
- * @type {Cartesian3}
- * @private
- */
- this._minBoundsOld = new Cartesian3();
-
- /**
- * @type {Cartesian3}
- * @private
- */
- this._maxBounds = maxBounds.clone();
-
- /**
- * Used to detect if the shape is dirty.
- *
- * @type {Cartesian3}
- * @private
- */
- this._maxBoundsOld = new Cartesian3();
-
- /**
- * @type {Cartesian3}
- * @private
- */
- this._minClippingBounds = minBounds.clone();
-
- /**
- * Used to detect if the clipping is dirty.
- *
- * @type {Cartesian3}
- * @private
- */
- this._minClippingBoundsOld = new Cartesian3();
-
- /**
- * @type {Cartesian3}
- * @private
- */
- this._maxClippingBounds = maxBounds.clone();
-
- /**
- * Used to detect if the clipping is dirty.
- *
- * @type {Cartesian3}
- * @private
- */
- this._maxClippingBoundsOld = new Cartesian3();
-
- /**
- * Vertical exaggeration applied to the voxel shape
- *
- * @type {number}
- * @private
- */
- this._verticalExaggeration = 1.0;
-
- /**
- * The height relative to which the shape is exaggerated.
- *
- * @type {number}
- * @private
- */
- this._verticalExaggerationRelativeHeight = 0.0;
-
- /**
- * Clipping planes on the primitive
- *
- * @type {ClippingPlaneCollection}
- * @private
- */
- this._clippingPlanes = undefined;
-
- /**
- * Keeps track of when the clipping planes change
- *
- * @type {number}
- * @private
- */
- this._clippingPlanesState = 0;
-
- /**
- * Keeps track of when the clipping planes are enabled / disabled
- *
- * @type {boolean}
- * @private
- */
- this._clippingPlanesEnabled = false;
-
- /**
- * The primitive's model matrix.
- *
- * @type {Matrix4}
- * @private
- */
- this._modelMatrix = Matrix4.clone(modelMatrix);
-
- /**
- * Used to detect if the model matrix is dirty.
- *
- * @type {Matrix4}
- * @private
- */
- this._modelMatrixOld = Matrix4.clone(this._modelMatrix);
-
- /**
- * @type {CustomShader}
- * @private
- */
- this._customShader = customShader ?? VoxelPrimitive.DefaultCustomShader;
-
- /**
- * @type {Event}
- * @private
- */
- this._customShaderCompilationEvent = new Event();
-
- /**
- * @type {boolean}
- * @private
- */
- this._shaderDirty = true;
-
- /**
- * @type {DrawCommand}
- * @private
- */
- this._drawCommand = undefined;
-
- /**
- * @type {DrawCommand}
- * @private
- */
- this._drawCommandPick = undefined;
-
- /**
- * @type {object}
- * @private
- */
- this._pickId = undefined;
-
- /**
- * @type {Clock}
- * @private
- */
- this._clock = clock;
-
- // Transforms and other values that are computed when the shape changes
- /**
- * @type {Matrix4}
- * @private
- */
- this._transformPositionLocalToWorld = new Matrix4();
-
- /**
- * @type {Matrix4}
- * @private
- */
- this._transformPositionWorldToLocal = new Matrix4();
-
- /**
- * Transforms a plane in Hessian normal form from local space to view space.
- * @type {Matrix4}
- * @private
- */
- this._transformPlaneLocalToView = new Matrix4();
-
- /**
- * @type {Matrix3}
- * @private
- */
- this._transformDirectionWorldToLocal = new Matrix3();
-
- // Rendering
- /**
- * @type {boolean}
- * @private
- */
- this._nearestSampling = false;
-
- /**
- * @type {number}
- * @private
- */
- this._levelBlendFactor = 0.0;
-
- /**
- * @type {number}
- * @private
- */
- this._stepSizeMultiplier = 1.0;
-
- /**
- * @type {boolean}
- * @private
- */
- this._depthTest = true;
-
- /**
- * @type {boolean}
- * @private
- */
- this._useLogDepth = undefined;
-
- /**
- * @type {number}
- * @private
- */
- this._screenSpaceError = 4.0; // in pixels
-
- // Debug / statistics
- /**
- * @type {PolylineCollection}
- * @private
- */
- this._debugPolylines = new PolylineCollection();
-
- /**
- * @type {boolean}
- * @private
- */
- this._debugDraw = false;
-
- /**
- * @type {boolean}
- * @private
- */
- this._disableRender = false;
-
- /**
- * @type {boolean}
- * @private
- */
- this._disableUpdate = false;
-
- const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
-
- /**
- * @type {VoxelShape}
- * @private
- */
- this._shape = new ShapeConstructor();
-
- checkTransformAndBounds(this);
-
- /**
- * @type {boolean}
- * @private
- */
- this._shapeVisible = updateShapeAndTransforms(this);
-
- /**
- * @type {Object<string, any>}
- * @private
- */
- this._uniforms = {
- octreeInternalNodeTexture: undefined,
- octreeInternalNodeTilesPerRow: 0,
- octreeInternalNodeTexelSizeUv: new Cartesian2(),
- octreeLeafNodeTexture: undefined,
- octreeLeafNodeTilesPerRow: 0,
- octreeLeafNodeTexelSizeUv: new Cartesian2(),
- megatextureTextures: [],
- megatextureTileCounts: new Cartesian3(),
- dimensions: this._dimensions,
- inputDimensions: this._inputDimensions,
- paddingBefore: this._paddingBefore,
- paddingAfter: this._paddingAfter,
- transformPositionViewToLocal: new Matrix4(),
- transformDirectionViewToLocal: new Matrix3(),
- cameraPositionLocal: new Cartesian3(),
- cameraDirectionLocal: new Cartesian3(),
- cameraTileCoordinates: new Cartesian4(),
- cameraTileUv: new Cartesian3(),
- ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
- clippingPlanesTexture: undefined,
- clippingPlanesMatrix: new Matrix4(),
- renderBoundPlanesTexture: undefined,
- stepSize: this._stepSizeMultiplier,
- pickColor: new Color(),
- };
-
- /**
- * Shape specific shader defines from the previous shape update. Used to detect if the shader needs to be rebuilt.
- * @type {Object<string, any>}
- * @private
- */
- this._shapeDefinesOld = {};
-
- /**
- * Map uniform names to functions that return the uniform values.
- * @type {Object<string, function():any>}
- * @private
- */
- this._uniformMap = {};
-
- const uniforms = this._uniforms;
- const uniformMap = this._uniformMap;
- for (const key in uniforms) {
- if (uniforms.hasOwnProperty(key)) {
- const name = `u_${key}`;
- uniformMap[name] = function () {
- return uniforms[key];
- };
- }
- }
-
- setupShapeUniformsAndDefines(this, this._shape);
-
- /**
- * The event fired to indicate that a tile's content was loaded.
- * <p>
- * This event is fired during the tileset traversal while the frame is being rendered
- * so that updates to the tile take effect in the same frame. Do not create or modify
- * Cesium entities or primitives during the event listener.
- * </p>
- *
- * @type {Event}
- *
- * @example
- * voxelPrimitive.tileLoad.addEventListener(function() {
- * console.log('A tile was loaded.');
- * });
- */
- this.tileLoad = new Event();
-
- /**
- * This event fires once for each visible tile in a frame.
- * <p>
- * This event is fired during the traversal while the frame is being rendered.
- *
- * @type {Event}
- *
- * @example
- * voxelPrimitive.tileVisible.addEventListener(function() {
- * console.log('A tile is visible.');
- * });
- *
- */
- this.tileVisible = new Event();
-
- /**
- * The event fired to indicate that a tile's content failed to load.
- *
- * @type {Event}
- *
- * @example
- * voxelPrimitive.tileFailed.addEventListener(function() {
- * console.log('An error occurred loading tile.');
- * });
- */
- this.tileFailed = new Event();
-
- /**
- * The event fired to indicate that a tile's content was unloaded.
- *
- * @type {Event}
- *
- * @example
- * voxelPrimitive.tileUnload.addEventListener(function() {
- * console.log('A tile was unloaded from the cache.');
- * });
- *
- */
- this.tileUnload = new Event();
-
- /**
- * The event fired to indicate progress of loading new tiles. This event is fired when a new tile
- * is requested, when a requested tile is finished downloading, and when a downloaded tile has been
- * processed and is ready to render.
- * <p>
- * The number of pending tile requests, <code>numberOfPendingRequests</code>, and number of tiles
- * processing, <code>numberOfTilesProcessing</code> are passed to the event listener.
- * </p>
- * <p>
- * This event is fired at the end of the frame after the scene is rendered.
- * </p>
- *
- * @type {Event}
- *
- * @example
- * voxelPrimitive.loadProgress.addEventListener(function(numberOfPendingRequests, numberOfTilesProcessing) {
- * if ((numberOfPendingRequests === 0) && (numberOfTilesProcessing === 0)) {
- * console.log('Finished loading');
- * return;
- * }
- *
- * console.log(`Loading: requests: ${numberOfPendingRequests}, processing: ${numberOfTilesProcessing}`);
- * });
- */
- this.loadProgress = new Event();
-
- /**
- * The event fired to indicate that all tiles that meet the screen space error this frame are loaded. The voxel
- * primitive is completely loaded for this view.
- * <p>
- * This event is fired at the end of the frame after the scene is rendered.
- * </p>
- *
- * @type {Event}
- *
- * @example
- * voxelPrimitive.allTilesLoaded.addEventListener(function() {
- * console.log('All tiles are loaded');
- * });
- */
- this.allTilesLoaded = new Event();
-
- /**
- * The event fired to indicate that all tiles that meet the screen space error this frame are loaded. This event
- * is fired once when all tiles in the initial view are loaded.
- * <p>
- * This event is fired at the end of the frame after the scene is rendered.
- * </p>
- *
- * @type {Event}
- *
- * @example
- * voxelPrimitive.initialTilesLoaded.addEventListener(function() {
- * console.log('Initial tiles are loaded');
- * });
- *
- * @see Cesium3DTileset#allTilesLoaded
- */
- this.initialTilesLoaded = new Event();
- }
-
- /**
- * Computes the dimensions of the input voxel data, including padding and in the input orientation.
- *
- * @param {Cartesian3} dimensions The dimensions of the voxel data, not including padding, in z-up orientation.
- * @param {Cartesian3} paddingBefore The padding before the voxel data.
- * @param {Cartesian3} paddingAfter The padding after the voxel data.
- * @param {VoxelMetadataOrder} metadataOrder The ordering of the input metadata dimensions.
- *
- * @private
- */
- function computeInputDimensions(
- dimensions,
- paddingBefore,
- paddingAfter,
- metadataOrder,
- ) {
- const inputDimensions = Cartesian3.add(
- dimensions,
- paddingBefore,
- new Cartesian3(),
- );
- Cartesian3.add(inputDimensions, paddingAfter, inputDimensions);
- if (metadataOrder === VoxelMetadataOrder.Y_UP) {
- const inputDimensionsY = inputDimensions.y;
- inputDimensions.y = inputDimensions.z;
- inputDimensions.z = inputDimensionsY;
- }
- return inputDimensions;
- }
-
- /**
- * Combine uniforms from the shape with the primitive uniform map, and
- * setup change tracking for shape defines to know when to rebuild the shader.
- *
- * @param {VoxelPrimitive} primitive The primitive with which the shape uniforms are associated.
- * @param {VoxelShape} shape The shape from which to pull the shader uniforms and defines.
- *
- * @private
- */
- function setupShapeUniformsAndDefines(primitive, shape) {
- const uniformMap = primitive._uniformMap;
- const { shaderUniforms, shaderDefines } = shape;
- for (const uniformName in shaderUniforms) {
- if (shaderUniforms.hasOwnProperty(uniformName)) {
- const name = `u_${uniformName}`;
-
- //>>includeStart('debug', pragmas.debug);
- if (defined(uniformMap[name])) {
- oneTimeWarning(
- `VoxelPrimitive: Uniform name "${name}" is already defined`,
- );
- }
- //>>includeEnd('debug');
-
- uniformMap[name] = function () {
- return shaderUniforms[uniformName];
- };
- }
- }
- primitive._shapeDefinesOld = clone(shaderDefines, true);
- }
-
- Object.defineProperties(VoxelPrimitive.prototype, {
- /**
- * Gets a value indicating whether or not the primitive is ready for use.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {boolean}
- * @readonly
- */
- ready: {
- get: function () {
- return this._ready;
- },
- },
-
- /**
- * Gets the {@link VoxelProvider} associated with this primitive.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {VoxelProvider}
- * @readonly
- */
- provider: {
- get: function () {
- return this._provider;
- },
- },
-
- /**
- * Gets the bounding sphere.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {BoundingSphere}
- * @readonly
- */
- boundingSphere: {
- get: function () {
- return this._shape.boundingSphere;
- },
- },
-
- /**
- * Gets the oriented bounding box.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {OrientedBoundingBox}
- * @readonly
- */
- orientedBoundingBox: {
- get: function () {
- return this._shape.orientedBoundingBox;
- },
- },
-
- /**
- * Gets the model matrix.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Matrix4}
- * @readonly
- */
- modelMatrix: {
- get: function () {
- return this._modelMatrix;
- },
- set: function (modelMatrix) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.object("modelMatrix", modelMatrix);
- //>>includeEnd('debug');
-
- this._modelMatrix = Matrix4.clone(modelMatrix, this._modelMatrix);
- },
- },
-
- /**
- * Gets the shape type.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {VoxelShapeType}
- * @readonly
- */
- shape: {
- get: function () {
- return this._provider.shape;
- },
- },
-
- /**
- * Gets the dimensions of each voxel tile, in z-up orientation.
- * Does not include padding.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Cartesian3}
- * @readonly
- * @constant
- */
- dimensions: {
- get: function () {
- return this._dimensions;
- },
- },
-
- /**
- * Gets the dimensions of one tile of the input voxel data, in the input orientation.
- * Includes padding.
- * @memberof VoxelPrimitive.prototype
- * @type {Cartesian3}
- * @readonly
- * @constant
- */
- inputDimensions: {
- get: function () {
- return this._inputDimensions;
- },
- },
-
- /**
- * Gets the padding before the voxel data.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Cartesian3}
- * @readonly
- * @constant
- */
- paddingBefore: {
- get: function () {
- return this._paddingBefore;
- },
- },
-
- /**
- * Gets the padding after the voxel data.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Cartesian3}
- * @readonly
- * @constant
- */
- paddingAfter: {
- get: function () {
- return this._paddingAfter;
- },
- },
-
- /**
- * Gets the minimum value per channel of the voxel data.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {number[][]}
- * @readonly
- * @constant
- */
- minimumValues: {
- get: function () {
- return this._provider.minimumValues;
- },
- },
-
- /**
- * Gets the maximum value per channel of the voxel data.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {number[][]}
- * @readonly
- * @constant
- */
- maximumValues: {
- get: function () {
- return this._provider.maximumValues;
- },
- },
-
- /**
- * Gets or sets whether or not this primitive should be displayed.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {boolean}
- */
- show: {
- get: function () {
- return !this._disableRender;
- },
- set: function (show) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.bool("show", show);
- //>>includeEnd('debug');
-
- this._disableRender = !show;
- },
- },
-
- /**
- * Gets or sets whether or not the primitive should update when the view changes.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {boolean}
- */
- disableUpdate: {
- get: function () {
- return this._disableUpdate;
- },
- set: function (disableUpdate) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.bool("disableUpdate", disableUpdate);
- //>>includeEnd('debug');
-
- this._disableUpdate = disableUpdate;
- },
- },
-
- /**
- * Gets or sets whether or not to render debug visualizations.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {boolean}
- */
- debugDraw: {
- get: function () {
- return this._debugDraw;
- },
- set: function (debugDraw) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.bool("debugDraw", debugDraw);
- //>>includeEnd('debug');
-
- this._debugDraw = debugDraw;
- },
- },
-
- /**
- * Gets or sets whether or not to test against depth when rendering.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {boolean}
- */
- depthTest: {
- get: function () {
- return this._depthTest;
- },
- set: function (depthTest) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.bool("depthTest", depthTest);
- //>>includeEnd('debug');
-
- if (this._depthTest !== depthTest) {
- this._depthTest = depthTest;
- this._shaderDirty = true;
- }
- },
- },
-
- /**
- * Gets or sets the nearest sampling.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {boolean}
- */
- nearestSampling: {
- get: function () {
- return this._nearestSampling;
- },
- set: function (nearestSampling) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.bool("nearestSampling", nearestSampling);
- //>>includeEnd('debug');
-
- this._nearestSampling = nearestSampling;
- },
- },
-
- /**
- * Controls how quickly to blend between different levels of the tree.
- * 0.0 means an instantaneous pop.
- * 1.0 means a full linear blend.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {number}
- * @private
- */
- levelBlendFactor: {
- get: function () {
- return this._levelBlendFactor;
- },
- set: function (levelBlendFactor) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.number("levelBlendFactor", levelBlendFactor);
- //>>includeEnd('debug');
-
- this._levelBlendFactor = CesiumMath.clamp(levelBlendFactor, 0.0, 1.0);
- },
- },
-
- /**
- * Gets or sets the screen space error in pixels. If the screen space size
- * of a voxel is greater than the screen space error, the tile is subdivided.
- * Lower screen space error corresponds with higher detail rendering, but could
- * result in worse performance and higher memory consumption.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {number}
- */
- screenSpaceError: {
- get: function () {
- return this._screenSpaceError;
- },
- set: function (screenSpaceError) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.number("screenSpaceError", screenSpaceError);
- //>>includeEnd('debug');
-
- this._screenSpaceError = screenSpaceError;
- },
- },
-
- /**
- * Gets or sets the step size multiplier used during raymarching.
- * The lower the value, the higher the rendering quality, but
- * also the worse the performance.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {number}
- */
- stepSize: {
- get: function () {
- return this._stepSizeMultiplier;
- },
- set: function (stepSize) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.number("stepSize", stepSize);
- //>>includeEnd('debug');
-
- this._stepSizeMultiplier = stepSize;
- },
- },
-
- /**
- * Gets or sets the minimum bounds in the shape's local coordinate system.
- * Voxel data is stretched or squashed to fit the bounds.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Cartesian3}
- */
- minBounds: {
- get: function () {
- return this._minBounds;
- },
- set: function (minBounds) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("minBounds", minBounds);
- //>>includeEnd('debug');
-
- this._minBounds = Cartesian3.clone(minBounds, this._minBounds);
- },
- },
-
- /**
- * Gets or sets the maximum bounds in the shape's local coordinate system.
- * Voxel data is stretched or squashed to fit the bounds.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Cartesian3}
- */
- maxBounds: {
- get: function () {
- return this._maxBounds;
- },
- set: function (maxBounds) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("maxBounds", maxBounds);
- //>>includeEnd('debug');
-
- this._maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
- },
- },
-
- /**
- * Gets or sets the minimum clipping location in the shape's local coordinate system.
- * Any voxel content outside the range is clipped.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Cartesian3}
- */
- minClippingBounds: {
- get: function () {
- return this._minClippingBounds;
- },
- set: function (minClippingBounds) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("minClippingBounds", minClippingBounds);
- //>>includeEnd('debug');
-
- this._minClippingBounds = Cartesian3.clone(
- minClippingBounds,
- this._minClippingBounds,
- );
- },
- },
-
- /**
- * Gets or sets the maximum clipping location in the shape's local coordinate system.
- * Any voxel content outside the range is clipped.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Cartesian3}
- */
- maxClippingBounds: {
- get: function () {
- return this._maxClippingBounds;
- },
- set: function (maxClippingBounds) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("maxClippingBounds", maxClippingBounds);
- //>>includeEnd('debug');
-
- this._maxClippingBounds = Cartesian3.clone(
- maxClippingBounds,
- this._maxClippingBounds,
- );
- },
- },
-
- /**
- * The {@link ClippingPlaneCollection} used to selectively disable rendering the primitive.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {ClippingPlaneCollection}
- */
- clippingPlanes: {
- get: function () {
- return this._clippingPlanes;
- },
- set: function (clippingPlanes) {
- // Don't need to check if undefined, it's handled in the setOwner function
- ClippingPlaneCollection.setOwner(clippingPlanes, this, "_clippingPlanes");
- },
- },
-
- /**
- * Gets or sets the custom shader. If undefined, attempt to build a default custom shader
- * appropriate to the metadata type. If that fails, use {@link VoxelPrimitive.DefaultCustomShader}.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {CustomShader}
- * @see {@link https://github.com/CesiumGS/cesium/tree/main/Documentation/CustomShaderGuide|Custom Shader Guide}
- */
- customShader: {
- get: function () {
- return this._customShader;
- },
- set: function (customShader) {
- if (customShader === this._customShader) {
- return;
- }
- // Delete old custom shader entries from the uniform map
- // (they were added in VoxelRenderResources when the shader was built)
- const uniformMap = this._uniformMap;
- const oldCustomShader = this._customShader;
- const oldCustomShaderUniformMap = oldCustomShader.uniformMap;
- for (const uniformName in oldCustomShaderUniformMap) {
- if (oldCustomShaderUniformMap.hasOwnProperty(uniformName)) {
- delete uniformMap[uniformName];
- }
- }
-
- if (!defined(customShader)) {
- const defaultShader = buildVoxelCustomShader(this._provider);
- this._customShader =
- defaultShader ?? VoxelPrimitive.DefaultCustomShader;
- } else {
- this._customShader = customShader;
- }
- this._shaderDirty = true;
- },
- },
-
- /**
- * Gets an event that is raised whenever a custom shader is compiled.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Event}
- * @readonly
- */
- customShaderCompilationEvent: {
- get: function () {
- return this._customShaderCompilationEvent;
- },
- },
-
- /**
- * Loading and rendering information for requested content.
- * To use `visited` and `numberOfTilesWithContentReady` statistics, set options.calculateStatistics` to `true` in the constructor.
- * @type {Cesium3DTilesetStatistics}
- * @readonly
- * @private
- */
- statistics: {
- get: function () {
- return this._statistics;
- },
- },
- });
-
- const scratchIntersect = new Cartesian4();
- const scratchNdcAabb = new Cartesian4();
- const scratchTransformPositionLocalToWorld = new Matrix4();
- const scratchTransformPositionLocalToProjection = new Matrix4();
- const scratchCameraPositionShapeUv = new Cartesian3();
- const scratchCameraTileCoordinates = new Cartesian4();
-
- /**
- * Updates the voxel primitive.
- *
- * @param {FrameState} frameState
- * @private
- */
- VoxelPrimitive.prototype.update = function (frameState) {
- const provider = this._provider;
- const uniforms = this._uniforms;
-
- // Update the custom shader in case it has texture uniforms.
- this._customShader.update(frameState);
-
- // Initialize from the provider. This only happens once.
- const context = frameState.context;
- if (!this._ready) {
- initializeFromContext(this, provider, context);
- // Set the primitive as ready after the first frame render since
- // the user might set up events subscribed to the post render event,
- // and the primitive may not be ready for those past the first frame.
- frameState.afterRender.push(() => {
- this._ready = true;
- return true;
- });
-
- // Don't render until the next frame after ready is set to true
- return;
- }
-
- // Check if the shape is dirty before updating it. This needs to happen every
- // frame because the member variables can be modified externally via the
- // getters.
- const shapeDirty = checkTransformAndBounds(this);
- const exaggerationChanged = updateVerticalExaggeration(this, frameState);
- if (shapeDirty || exaggerationChanged) {
- this._shapeVisible = updateShapeAndTransforms(this);
- if (checkShapeDefines(this)) {
- this._shaderDirty = true;
- }
- }
- if (!this._shapeVisible) {
- return;
- }
-
- this._shape.updateViewTransforms(frameState);
-
- // Update the traversal and prepare for rendering.
- const keyframeLocation = getKeyframeLocation(
- provider.timeIntervalCollection,
- this._clock,
- );
-
- const traversal = this._traversal;
- const sampleCountOld = traversal._sampleCount;
-
- traversal.update(
- frameState,
- keyframeLocation,
- shapeDirty, // recomputeBoundingVolumes
- this._disableUpdate, // pauseUpdate
- );
-
- if (sampleCountOld !== traversal._sampleCount) {
- this._shaderDirty = true;
- }
-
- if (!traversal.isRenderable(traversal.rootNode)) {
- return;
- }
-
- if (this._debugDraw) {
- // Debug draw bounding boxes and other things. Must go after traversal update
- // because that's what updates the tile bounding boxes.
- debugDraw(this, frameState);
- }
-
- if (this._disableRender) {
- return;
- }
-
- // Check if log depth changed
- if (this._useLogDepth !== frameState.useLogDepth) {
- this._useLogDepth = frameState.useLogDepth;
- this._shaderDirty = true;
- }
-
- // Check if clipping planes changed
- const clippingPlanesChanged = updateClippingPlanes(this, frameState);
- if (clippingPlanesChanged) {
- this._shaderDirty = true;
- }
-
- const leafNodeTexture = traversal.leafNodeTexture;
- if (defined(leafNodeTexture)) {
- uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
- uniforms.octreeLeafNodeTexelSizeUv = Cartesian2.clone(
- traversal.leafNodeTexelSizeUv,
- uniforms.octreeLeafNodeTexelSizeUv,
- );
- uniforms.octreeLeafNodeTilesPerRow = traversal.leafNodeTilesPerRow;
- }
-
- // Rebuild shaders
- if (this._shaderDirty) {
- buildVoxelDrawCommands(this, context);
- this._shaderDirty = false;
- }
-
- // Calculate the NDC-space AABB to "scissor" the fullscreen quad
- const transformPositionWorldToProjection =
- context.uniformState.viewProjection;
- const { orientedBoundingBox } = this._shape;
- const ndcAabb = orientedBoundingBoxToNdcAabb(
- orientedBoundingBox,
- transformPositionWorldToProjection,
- scratchNdcAabb,
- );
-
- // If the object is offscreen, don't render it.
- const offscreen =
- ndcAabb.x === +1.0 ||
- ndcAabb.y === +1.0 ||
- ndcAabb.z === -1.0 ||
- ndcAabb.w === -1.0;
- if (offscreen) {
- return;
- }
-
- // Prepare to render: update uniforms that can change every frame
- // Using a uniform instead of going through RenderState's scissor because the viewport is not accessible here, and the scissor command needs pixel coordinates.
- uniforms.ndcSpaceAxisAlignedBoundingBox = Cartesian4.clone(
- ndcAabb,
- uniforms.ndcSpaceAxisAlignedBoundingBox,
- );
- const transformPositionViewToWorld = context.uniformState.inverseView;
- const transformPositionViewToLocal = Matrix4.multiplyTransformation(
- this._transformPositionWorldToLocal,
- transformPositionViewToWorld,
- uniforms.transformPositionViewToLocal,
- );
-
- this._transformPlaneLocalToView = Matrix4.transpose(
- transformPositionViewToLocal,
- this._transformPlaneLocalToView,
- );
-
- const transformDirectionViewToWorld =
- context.uniformState.inverseViewRotation;
- uniforms.transformDirectionViewToLocal = Matrix3.multiply(
- this._transformDirectionWorldToLocal,
- transformDirectionViewToWorld,
- uniforms.transformDirectionViewToLocal,
- );
- uniforms.cameraPositionLocal = Matrix4.multiplyByPoint(
- this._transformPositionWorldToLocal,
- frameState.camera.positionWC,
- uniforms.cameraPositionLocal,
- );
- uniforms.cameraDirectionLocal = Matrix3.multiplyByVector(
- this._transformDirectionWorldToLocal,
- frameState.camera.directionWC,
- uniforms.cameraDirectionLocal,
- );
- const cameraTileCoordinates = getTileCoordinates(
- this,
- uniforms.cameraPositionLocal,
- scratchCameraTileCoordinates,
- );
- uniforms.cameraTileCoordinates = Cartesian4.fromElements(
- Math.floor(cameraTileCoordinates.x),
- Math.floor(cameraTileCoordinates.y),
- Math.floor(cameraTileCoordinates.z),
- cameraTileCoordinates.w,
- uniforms.cameraTileCoordinates,
- );
- uniforms.cameraTileUv = Cartesian3.fromElements(
- cameraTileCoordinates.x - Math.floor(cameraTileCoordinates.x),
- cameraTileCoordinates.y - Math.floor(cameraTileCoordinates.y),
- cameraTileCoordinates.z - Math.floor(cameraTileCoordinates.z),
- uniforms.cameraTileUv,
- );
- uniforms.stepSize = this._stepSizeMultiplier;
-
- updateNearestSampling(this);
- updateRenderBoundPlanes(this, frameState);
-
- // Render the primitive
- const command = frameState.passes.pick
- ? this._drawCommandPick
- : frameState.passes.pickVoxel
- ? this._drawCommandPickVoxel
- : this._drawCommand;
- command.boundingVolume = this._shape.boundingSphere;
- frameState.commandList.push(command);
- };
-
- function updateNearestSampling(primitive) {
- const { megatextures } = primitive._traversal;
- for (let i = 0; i < megatextures.length; ++i) {
- megatextures[i].nearestSampling = primitive._nearestSampling;
- }
- }
-
- function updateRenderBoundPlanes(primitive, frameState) {
- const uniforms = primitive._uniforms;
- const { renderBoundPlanes } = primitive._shape;
- if (!defined(renderBoundPlanes)) {
- return;
- }
- renderBoundPlanes.update(frameState, primitive._transformPlaneLocalToView);
- uniforms.renderBoundPlanesTexture = renderBoundPlanes.texture;
- }
-
- /**
- * Converts a position in local space to tile coordinates.
- *
- * @param {VoxelPrimitive} primitive The primitive to get the tile coordinates for.
- * @param {Cartesian3} positionLocal The position in local space to convert to tile coordinates.
- * @param {Cartesian4} result The result object to store the tile coordinates.
- * @returns {Cartesian4} The tile coordinates of the supplied position.
- * @private
- */
- function getTileCoordinates(primitive, positionLocal, result) {
- const shapeUv = primitive._shape.convertLocalToShapeUvSpace(
- positionLocal,
- scratchCameraPositionShapeUv,
- );
-
- const availableLevels = primitive._availableLevels;
- const numTiles = 2 ** (availableLevels - 1);
-
- return Cartesian4.fromElements(
- shapeUv.x * numTiles,
- shapeUv.y * numTiles,
- shapeUv.z * numTiles,
- availableLevels - 1,
- result,
- );
- }
-
- const scratchExaggerationScale = new Cartesian3();
- const scratchExaggerationCenter = new Cartesian3();
- const scratchCartographicCenter = new Cartographic();
-
- /**
- * Check for changes in the vertical exaggeration of the primitive
- * @param {VoxelPrimitive} primitive The primitive to update
- * @param {FrameState} frameState The current frame state
- * @returns {boolean} <code>true</code> if the exaggeration was changed
- * @private
- */
- function updateVerticalExaggeration(primitive, frameState) {
- const { verticalExaggeration, verticalExaggerationRelativeHeight } =
- frameState;
-
- if (
- primitive._verticalExaggeration === verticalExaggeration &&
- primitive._verticalExaggerationRelativeHeight ===
- verticalExaggerationRelativeHeight
- ) {
- return false;
- }
-
- primitive._verticalExaggeration = verticalExaggeration;
- primitive._verticalExaggerationRelativeHeight =
- verticalExaggerationRelativeHeight;
- return true;
- }
-
- /**
- * Initialize primitive properties that are derived from the voxel provider
- * @param {VoxelPrimitive} primitive
- * @param {VoxelProvider} provider
- * @param {Context} context
- * @private
- */
- function initializeFromContext(primitive, provider, context) {
- const uniforms = primitive._uniforms;
-
- primitive._pickId = context.createPickId({ primitive });
- uniforms.pickColor = Color.clone(primitive._pickId.color, uniforms.pickColor);
-
- // Create the VoxelTraversal, and set related uniforms
- const keyframeCount = provider.keyframeCount ?? 1;
- primitive._traversal = new VoxelTraversal(primitive, context, keyframeCount);
- primitive.statistics.texturesByteLength =
- primitive._traversal.textureMemoryByteLength;
- setTraversalUniforms(primitive._traversal, uniforms);
- }
-
- /**
- * Track changes in provider transform and primitive bounds
- * @param {VoxelPrimitive} primitive
- * @returns {boolean} Whether any of the transform or bounds changed
- * @private
- */
- function checkTransformAndBounds(primitive) {
- const numChanges =
- updateBound(primitive, "_modelMatrix", "_modelMatrixOld") +
- updateBound(primitive, "_minBounds", "_minBoundsOld") +
- updateBound(primitive, "_maxBounds", "_maxBoundsOld") +
- updateBound(primitive, "_minClippingBounds", "_minClippingBoundsOld") +
- updateBound(primitive, "_maxClippingBounds", "_maxClippingBoundsOld");
- return numChanges > 0;
- }
-
- /**
- * Compare old and new values of a bound and update the old if it is different.
- * @param {VoxelPrimitive} primitive The primitive with bounds properties
- * @param {string} newBoundKey A key pointing to a bounds property of type Cartesian3 or Matrix4
- * @param {string} oldBoundKey A key pointing to a bounds property of the same type as the property at newBoundKey
- * @returns {number} 1 if the bound value changed, 0 otherwise
- *
- * @private
- */
- function updateBound(primitive, newBoundKey, oldBoundKey) {
- const newBound = primitive[newBoundKey];
- const oldBound = primitive[oldBoundKey];
-
- const changed = !newBound.equals(oldBound);
- if (changed) {
- newBound.clone(oldBound);
- }
- return changed ? 1 : 0;
- }
-
- const scratchExaggeratedMinBounds = new Cartesian3();
- const scratchExaggeratedMaxBounds = new Cartesian3();
- const scratchExaggeratedMinClippingBounds = new Cartesian3();
- const scratchExaggeratedMaxClippingBounds = new Cartesian3();
- const scratchExaggeratedModelMatrix = new Matrix4();
- const scratchCompoundModelMatrix = new Matrix4();
-
- /**
- * Update the shape and related transforms
- * @param {VoxelPrimitive} primitive
- * @returns {boolean} True if the shape is visible
- * @private
- */
- function updateShapeAndTransforms(primitive) {
- const verticalExaggeration = primitive._verticalExaggeration;
- const verticalExaggerationRelativeHeight =
- primitive._verticalExaggerationRelativeHeight;
- const exaggeratedMinBounds = Cartesian3.clone(
- primitive._minBounds,
- scratchExaggeratedMinBounds,
- );
- const exaggeratedMaxBounds = Cartesian3.clone(
- primitive._maxBounds,
- scratchExaggeratedMaxBounds,
- );
- const exaggeratedMinClippingBounds = Cartesian3.clone(
- primitive._minClippingBounds,
- scratchExaggeratedMinClippingBounds,
- );
- const exaggeratedMaxClippingBounds = Cartesian3.clone(
- primitive._maxClippingBounds,
- scratchExaggeratedMaxClippingBounds,
- );
- const exaggeratedModelMatrix = Matrix4.clone(
- primitive._modelMatrix,
- scratchExaggeratedModelMatrix,
- );
-
- if (primitive.shape === VoxelShapeType.ELLIPSOID) {
- // Apply the exaggeration by stretching the height bounds
- exaggeratedMinBounds.z = VerticalExaggeration.getHeight(
- primitive._minBounds.z,
- verticalExaggeration,
- verticalExaggerationRelativeHeight,
- );
- exaggeratedMaxBounds.z = VerticalExaggeration.getHeight(
- primitive._maxBounds.z,
- verticalExaggeration,
- verticalExaggerationRelativeHeight,
- );
- exaggeratedMinClippingBounds.z = VerticalExaggeration.getHeight(
- primitive._minClippingBounds.z,
- verticalExaggeration,
- verticalExaggerationRelativeHeight,
- );
- exaggeratedMaxClippingBounds.z = VerticalExaggeration.getHeight(
- primitive._maxClippingBounds.z,
- verticalExaggeration,
- verticalExaggerationRelativeHeight,
- );
- } else {
- // Apply the exaggeration via the model matrix
- const exaggerationScale = Cartesian3.fromElements(
- 1.0,
- 1.0,
- verticalExaggeration,
- scratchExaggerationScale,
- );
- Matrix4.multiplyByScale(
- exaggeratedModelMatrix,
- exaggerationScale,
- exaggeratedModelMatrix,
- );
- Matrix4.multiplyByTranslation(
- exaggeratedModelMatrix,
- computeBoxExaggerationTranslation(primitive),
- exaggeratedModelMatrix,
- );
- }
-
- const provider = primitive._provider;
- const shapeTransform = provider.shapeTransform ?? Matrix4.IDENTITY;
- const globalTransform = provider.globalTransform ?? Matrix4.IDENTITY;
-
- // Compound model matrix = global transform * model matrix * shape transform
- const compoundModelMatrix = Matrix4.multiplyTransformation(
- globalTransform,
- exaggeratedModelMatrix,
- scratchCompoundModelMatrix,
- );
- Matrix4.multiplyTransformation(
- compoundModelMatrix,
- shapeTransform,
- compoundModelMatrix,
- );
-
- const shape = primitive._shape;
- const visible = shape.update(
- compoundModelMatrix,
- exaggeratedMinBounds,
- exaggeratedMaxBounds,
- exaggeratedMinClippingBounds,
- exaggeratedMaxClippingBounds,
- );
- if (!visible) {
- return false;
- }
-
- primitive._transformPositionLocalToWorld = Matrix4.clone(
- shape.shapeTransform,
- primitive._transformPositionLocalToWorld,
- );
- primitive._transformPositionWorldToLocal = Matrix4.inverse(
- primitive._transformPositionLocalToWorld,
- primitive._transformPositionWorldToLocal,
- );
- primitive._transformDirectionWorldToLocal = Matrix4.getMatrix3(
- primitive._transformPositionWorldToLocal,
- primitive._transformDirectionWorldToLocal,
- );
-
- return true;
- }
-
- const scratchExaggerationTranslation = new Cartesian3();
-
- /**
- * Compute the translation to apply to box shapes to account for vertical exaggeration
- *
- * @param {VoxelPrimitive} primitive
- * @returns {Cartesian3} The translation to apply to the box to account for vertical exaggeration
- * @private
- */
- function computeBoxExaggerationTranslation(primitive) {
- const verticalExaggeration = primitive._verticalExaggeration;
- const verticalExaggerationRelativeHeight =
- primitive._verticalExaggerationRelativeHeight;
-
- // Compute translation based on box center, relative height, and exaggeration
- const {
- shapeTransform = Matrix4.IDENTITY,
- globalTransform = Matrix4.IDENTITY,
- } = primitive._provider;
-
- // Find the Cartesian position of the center of the OBB
- const initialCenter = Matrix4.getTranslation(
- shapeTransform,
- scratchExaggerationCenter,
- );
- const intermediateCenter = Matrix4.multiplyByPoint(
- primitive._modelMatrix,
- initialCenter,
- scratchExaggerationCenter,
- );
- const transformedCenter = Matrix4.multiplyByPoint(
- globalTransform,
- intermediateCenter,
- scratchExaggerationCenter,
- );
-
- // Find the cartographic height
- const ellipsoid = Ellipsoid.WGS84;
- const centerCartographic = ellipsoid.cartesianToCartographic(
- transformedCenter,
- scratchCartographicCenter,
- );
-
- let centerHeight = 0.0;
- if (defined(centerCartographic)) {
- centerHeight = centerCartographic.height;
- }
-
- // Find the shift that will put the center in the right position relative
- // to relativeHeight, after it is scaled by verticalExaggeration
- const exaggeratedHeight = VerticalExaggeration.getHeight(
- centerHeight,
- verticalExaggeration,
- verticalExaggerationRelativeHeight,
- );
-
- return Cartesian3.fromElements(
- 0.0,
- 0.0,
- (exaggeratedHeight - centerHeight) / verticalExaggeration,
- scratchExaggerationTranslation,
- );
- }
-
- /**
- * Set uniforms that come from the traversal.
- * @param {VoxelTraversal} traversal
- * @param {object} uniforms
- * @private
- */
- function setTraversalUniforms(traversal, uniforms) {
- uniforms.octreeInternalNodeTexture = traversal.internalNodeTexture;
- uniforms.octreeInternalNodeTexelSizeUv = Cartesian2.clone(
- traversal.internalNodeTexelSizeUv,
- uniforms.octreeInternalNodeTexelSizeUv,
- );
- uniforms.octreeInternalNodeTilesPerRow = traversal.internalNodeTilesPerRow;
-
- const { megatextures } = traversal;
- const megatexture = megatextures[0];
- uniforms.megatextureTextures = new Array(megatextures.length);
- for (let i = 0; i < megatextures.length; i++) {
- uniforms.megatextureTextures[i] = megatextures[i].texture;
- }
- uniforms.megatextureTileCounts = Cartesian3.clone(
- megatexture.tileCounts,
- uniforms.megatextureTileCounts,
- );
- }
-
- /**
- * Track changes in shape-related shader defines
- * @param {VoxelPrimitive} primitive
- * @returns {boolean} True if any of the shape defines changed, requiring a shader rebuild
- * @private
- */
- function checkShapeDefines(primitive) {
- const { shaderDefines } = primitive._shape;
- const shapeDefinesChanged = Object.keys(shaderDefines).some(
- (key) => shaderDefines[key] !== primitive._shapeDefinesOld[key],
- );
- if (shapeDefinesChanged) {
- primitive._shapeDefinesOld = clone(shaderDefines, true);
- }
- return shapeDefinesChanged;
- }
-
- /**
- * Find the keyframe location to render at. Doesn't need to be a whole number.
- * @param {TimeIntervalCollection} timeIntervalCollection
- * @param {Clock} clock
- * @returns {number}
- *
- * @private
- */
- function getKeyframeLocation(timeIntervalCollection, clock) {
- if (!defined(timeIntervalCollection) || !defined(clock)) {
- return 0.0;
- }
- let date = clock.currentTime;
- let timeInterval;
- let timeIntervalIndex = timeIntervalCollection.indexOf(date);
- if (timeIntervalIndex >= 0) {
- timeInterval = timeIntervalCollection.get(timeIntervalIndex);
- } else {
- // Date fell outside the range
- timeIntervalIndex = ~timeIntervalIndex;
- if (timeIntervalIndex === timeIntervalCollection.length) {
- // Date past range
- timeIntervalIndex = timeIntervalCollection.length - 1;
- timeInterval = timeIntervalCollection.get(timeIntervalIndex);
- date = timeInterval.stop;
- } else {
- // Date before range
- timeInterval = timeIntervalCollection.get(timeIntervalIndex);
- date = timeInterval.start;
- }
- }
- // De-lerp between the start and end of the interval
- const totalSeconds = JulianDate.secondsDifference(
- timeInterval.stop,
- timeInterval.start,
- );
- const secondsDifferenceStart = JulianDate.secondsDifference(
- date,
- timeInterval.start,
- );
- const t = secondsDifferenceStart / totalSeconds;
-
- return timeIntervalIndex + t;
- }
-
- /**
- * Update the clipping planes state and associated uniforms
- *
- * @param {VoxelPrimitive} primitive
- * @param {FrameState} frameState
- * @returns {boolean} Whether the clipping planes changed, requiring a shader rebuild
- * @private
- */
- function updateClippingPlanes(primitive, frameState) {
- const clippingPlanes = primitive.clippingPlanes;
- if (!defined(clippingPlanes)) {
- return false;
- }
-
- clippingPlanes.update(frameState);
-
- const { clippingPlanesState, enabled } = clippingPlanes;
-
- if (enabled) {
- const uniforms = primitive._uniforms;
- uniforms.clippingPlanesTexture = clippingPlanes.texture;
-
- // Compute the clipping plane's transformation to local space and then take the inverse
- // transpose to properly transform the hessian normal form of the plane.
-
- // transpose(inverse(worldToLocal * clippingPlaneLocalToWorld))
- // transpose(inverse(clippingPlaneLocalToWorld) * inverse(worldToLocal))
- // transpose(inverse(clippingPlaneLocalToWorld) * localToWorld)
-
- uniforms.clippingPlanesMatrix = Matrix4.transpose(
- Matrix4.multiplyTransformation(
- Matrix4.inverse(
- clippingPlanes.modelMatrix,
- uniforms.clippingPlanesMatrix,
- ),
- primitive._transformPositionLocalToWorld,
- uniforms.clippingPlanesMatrix,
- ),
- uniforms.clippingPlanesMatrix,
- );
- }
-
- if (
- primitive._clippingPlanesState === clippingPlanesState &&
- primitive._clippingPlanesEnabled === enabled
- ) {
- return false;
- }
- primitive._clippingPlanesState = clippingPlanesState;
- primitive._clippingPlanesEnabled = enabled;
-
- return true;
- }
-
- /**
- * Returns true if this object was destroyed; otherwise, false.
- * <br /><br />
- * If this object was destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
- *
- * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
- *
- * @see VoxelPrimitive#destroy
- */
- VoxelPrimitive.prototype.isDestroyed = function () {
- return false;
- };
-
- /**
- * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
- * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
- * <br /><br />
- * Once an object is destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
- * assign the return value (<code>undefined</code>) to the object as done in the example.
- *
- * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
- *
- * @see VoxelPrimitive#isDestroyed
- *
- * @example
- * voxelPrimitive = voxelPrimitive && voxelPrimitive.destroy();
- */
- VoxelPrimitive.prototype.destroy = function () {
- const drawCommand = this._drawCommand;
- if (defined(drawCommand)) {
- drawCommand.shaderProgram =
- drawCommand.shaderProgram && drawCommand.shaderProgram.destroy();
- }
- const drawCommandPick = this._drawCommandPick;
- if (defined(drawCommandPick)) {
- drawCommandPick.shaderProgram =
- drawCommandPick.shaderProgram && drawCommandPick.shaderProgram.destroy();
- }
-
- this._pickId = this._pickId && this._pickId.destroy();
- this._traversal = this._traversal && this._traversal.destroy();
- this.statistics.texturesByteLength = 0;
- this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy();
-
- return destroyObject(this);
- };
-
- const corners = new Array(
- new Cartesian4(-1.0, -1.0, -1.0, 1.0),
- new Cartesian4(+1.0, -1.0, -1.0, 1.0),
- new Cartesian4(-1.0, +1.0, -1.0, 1.0),
- new Cartesian4(+1.0, +1.0, -1.0, 1.0),
- new Cartesian4(-1.0, -1.0, +1.0, 1.0),
- new Cartesian4(+1.0, -1.0, +1.0, 1.0),
- new Cartesian4(-1.0, +1.0, +1.0, 1.0),
- new Cartesian4(+1.0, +1.0, +1.0, 1.0),
- );
- const vertexNeighborIndices = new Array(
- 1,
- 2,
- 4,
- 0,
- 3,
- 5,
- 0,
- 3,
- 6,
- 1,
- 2,
- 7,
- 0,
- 5,
- 6,
- 1,
- 4,
- 7,
- 2,
- 4,
- 7,
- 3,
- 5,
- 6,
- );
-
- const scratchCornersClipSpace = new Array(
- new Cartesian4(),
- new Cartesian4(),
- new Cartesian4(),
- new Cartesian4(),
- new Cartesian4(),
- new Cartesian4(),
- new Cartesian4(),
- new Cartesian4(),
- );
-
- /**
- * Projects all 8 corners of the oriented bounding box to NDC space and finds the
- * resulting NDC axis aligned bounding box. To avoid projecting a vertex that is
- * behind the near plane, it uses the intersection point of each of the vertex's
- * edges against the near plane as part of the AABB calculation. This is done in
- * clip space prior to perspective division.
- *
- * @function
- *
- * @param {OrientedBoundingBox} orientedBoundingBox
- * @param {Matrix4} worldToProjection
- * @param {Cartesian4} result
- * @returns {Cartesian4}
- *
- * @private
- */
- function orientedBoundingBoxToNdcAabb(
- orientedBoundingBox,
- worldToProjection,
- result,
- ) {
- const transformPositionLocalToWorld = Matrix4.fromRotationTranslation(
- orientedBoundingBox.halfAxes,
- orientedBoundingBox.center,
- scratchTransformPositionLocalToWorld,
- );
- const transformPositionLocalToProjection = Matrix4.multiply(
- worldToProjection,
- transformPositionLocalToWorld,
- scratchTransformPositionLocalToProjection,
- );
-
- let ndcMinX = +Number.MAX_VALUE;
- let ndcMaxX = -Number.MAX_VALUE;
- let ndcMinY = +Number.MAX_VALUE;
- let ndcMaxY = -Number.MAX_VALUE;
- let cornerIndex;
-
- // Convert all points to clip space
- const cornersClipSpace = scratchCornersClipSpace;
- const cornersLength = corners.length;
- for (cornerIndex = 0; cornerIndex < cornersLength; cornerIndex++) {
- Matrix4.multiplyByVector(
- transformPositionLocalToProjection,
- corners[cornerIndex],
- cornersClipSpace[cornerIndex],
- );
- }
-
- for (cornerIndex = 0; cornerIndex < cornersLength; cornerIndex++) {
- const position = cornersClipSpace[cornerIndex];
- if (position.z >= -position.w) {
- // Position is past near plane, so there's no need to clip.
- const ndcX = position.x / position.w;
- const ndcY = position.y / position.w;
- ndcMinX = Math.min(ndcMinX, ndcX);
- ndcMaxX = Math.max(ndcMaxX, ndcX);
- ndcMinY = Math.min(ndcMinY, ndcY);
- ndcMaxY = Math.max(ndcMaxY, ndcY);
- } else {
- for (let neighborIndex = 0; neighborIndex < 3; neighborIndex++) {
- const neighborVertexIndex =
- vertexNeighborIndices[cornerIndex * 3 + neighborIndex];
- const neighborPosition = cornersClipSpace[neighborVertexIndex];
- if (neighborPosition.z >= -neighborPosition.w) {
- // Position is behind the near plane and neighbor is after, so get intersection point on the near plane.
- const distanceToPlaneFromPosition = position.z + position.w;
- const distanceToPlaneFromNeighbor =
- neighborPosition.z + neighborPosition.w;
- const t =
- distanceToPlaneFromPosition /
- (distanceToPlaneFromPosition - distanceToPlaneFromNeighbor);
-
- const intersect = Cartesian4.lerp(
- position,
- neighborPosition,
- t,
- scratchIntersect,
- );
- const intersectNdcX = intersect.x / intersect.w;
- const intersectNdcY = intersect.y / intersect.w;
- ndcMinX = Math.min(ndcMinX, intersectNdcX);
- ndcMaxX = Math.max(ndcMaxX, intersectNdcX);
- ndcMinY = Math.min(ndcMinY, intersectNdcY);
- ndcMaxY = Math.max(ndcMaxY, intersectNdcY);
- }
- }
- }
- }
-
- // Clamp the NDC values to -1 to +1 range even if they extend much further.
- ndcMinX = CesiumMath.clamp(ndcMinX, -1.0, +1.0);
- ndcMinY = CesiumMath.clamp(ndcMinY, -1.0, +1.0);
- ndcMaxX = CesiumMath.clamp(ndcMaxX, -1.0, +1.0);
- ndcMaxY = CesiumMath.clamp(ndcMaxY, -1.0, +1.0);
- result = Cartesian4.fromElements(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, result);
-
- return result;
- }
-
- const polylineAxisDistance = 30000000.0;
- const polylineXAxis = new Cartesian3(polylineAxisDistance, 0.0, 0.0);
- const polylineYAxis = new Cartesian3(0.0, polylineAxisDistance, 0.0);
- const polylineZAxis = new Cartesian3(0.0, 0.0, polylineAxisDistance);
-
- /**
- * Draws the tile bounding boxes and axes.
- *
- * @function
- *
- * @param {VoxelPrimitive} that
- * @param {FrameState} frameState
- *
- * @private
- */
- function debugDraw(that, frameState) {
- const traversal = that._traversal;
- const polylines = that._debugPolylines;
- polylines.removeAll();
-
- function makePolylineLineSegment(startPos, endPos, color, thickness) {
- polylines.add({
- positions: [startPos, endPos],
- width: thickness,
- material: Material.fromType("Color", {
- color: color,
- }),
- });
- }
-
- function makePolylineBox(orientedBoundingBox, color, thickness) {
- // Normally would want to use a scratch variable to store the corners, but
- // polylines don't clone the positions.
- const corners = orientedBoundingBox.computeCorners();
- makePolylineLineSegment(corners[0], corners[1], color, thickness);
- makePolylineLineSegment(corners[2], corners[3], color, thickness);
- makePolylineLineSegment(corners[4], corners[5], color, thickness);
- makePolylineLineSegment(corners[6], corners[7], color, thickness);
- makePolylineLineSegment(corners[0], corners[2], color, thickness);
- makePolylineLineSegment(corners[4], corners[6], color, thickness);
- makePolylineLineSegment(corners[1], corners[3], color, thickness);
- makePolylineLineSegment(corners[5], corners[7], color, thickness);
- makePolylineLineSegment(corners[0], corners[4], color, thickness);
- makePolylineLineSegment(corners[2], corners[6], color, thickness);
- makePolylineLineSegment(corners[1], corners[5], color, thickness);
- makePolylineLineSegment(corners[3], corners[7], color, thickness);
- }
-
- function drawTile(tile) {
- if (!traversal.isRenderable(tile)) {
- return;
- }
-
- const level = tile.level;
- const startThickness = 5.0;
- const thickness = Math.max(1.0, startThickness / Math.pow(2.0, level));
- const colors = [Color.RED, Color.LIME, Color.BLUE];
- const color = colors[level % 3];
-
- makePolylineBox(tile.orientedBoundingBox, color, thickness);
-
- if (defined(tile.children)) {
- for (let i = 0; i < 8; i++) {
- drawTile(tile.children[i]);
- }
- }
- }
-
- makePolylineBox(that._shape.orientedBoundingBox, Color.WHITE, 5.0);
-
- drawTile(traversal.rootNode);
-
- const axisThickness = 10.0;
- makePolylineLineSegment(
- Cartesian3.ZERO,
- polylineXAxis,
- Color.RED,
- axisThickness,
- );
- makePolylineLineSegment(
- Cartesian3.ZERO,
- polylineYAxis,
- Color.LIME,
- axisThickness,
- );
- makePolylineLineSegment(
- Cartesian3.ZERO,
- polylineZAxis,
- Color.BLUE,
- axisThickness,
- );
-
- polylines.update(frameState);
- }
-
- /**
- * The default custom shader used by the primitive.
- *
- * @type {CustomShader}
- * @constant
- * @readonly
- *
- * @private
- */
- VoxelPrimitive.DefaultCustomShader = new CustomShader({
- fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
- {
- vec3 voxelNormal = fsInput.attributes.normalEC;
- float diffuse = max(0.0, dot(voxelNormal, czm_lightDirectionEC));
- float lighting = 0.5 + 0.5 * diffuse;
- material.diffuse = vec3(lighting);
- material.alpha = 1.0;
- }`,
- });
-
- function DefaultVoxelProvider() {
- this.shape = VoxelShapeType.BOX;
- this.dimensions = new Cartesian3(1, 1, 1);
- this.names = ["data"];
- this.types = [MetadataType.SCALAR];
- this.componentTypes = [MetadataComponentType.FLOAT32];
- this.maximumTileCount = 1;
- }
-
- DefaultVoxelProvider.prototype.requestData = function (options) {
- const tileLevel = defined(options) ? (options.tileLevel ?? 0) : 0;
- if (tileLevel >= 1) {
- return undefined;
- }
-
- const content = new VoxelContent({ metadata: [new Float32Array(1)] });
- return Promise.resolve(content);
- };
-
- VoxelPrimitive.DefaultProvider = new DefaultVoxelProvider();
-
- export default VoxelPrimitive;
|