diff --git a/Apps/Sandcastle/gallery/Voxel Picking.html b/Apps/Sandcastle/gallery/Voxel Picking.html
index a10243446139..4877b80625bc 100644
--- a/Apps/Sandcastle/gallery/Voxel Picking.html
+++ b/Apps/Sandcastle/gallery/Voxel Picking.html
@@ -80,15 +80,17 @@
this.names = ["color"];
this.types = [Cesium.MetadataType.VEC4];
this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
- this._levelCount = 3;
+ this.availableLevels = 3;
this.globalTransform = globalTransform;
}
ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
const { tileLevel, tileX, tileY, tileZ } = options;
- if (tileLevel >= this._levelCount) {
- return Promise.reject(`No tiles available beyond level ${this._levelCount}`);
+ if (tileLevel >= this.availableLevels) {
+ return Promise.reject(
+ `No tiles available beyond level ${this.availableLevels - 1}`,
+ );
}
const dimensions = this.dimensions;
@@ -174,6 +176,7 @@
customShader: customShader,
});
voxelPrimitive.nearestSampling = true;
+ voxelPrimitive.stepSize = 0.7;
viewer.scene.primitives.add(voxelPrimitive);
camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, {
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index 428cb7db9bf4..b13a599f3b1c 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -123,14 +123,14 @@
this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
this.globalTransform = globalTransform;
- this._levelCount = 2;
- this._allVoxelData = new Array(this._levelCount);
+ this.availableLevels = 2;
+ this._allVoxelData = new Array(this.availableLevels);
const allVoxelData = this._allVoxelData;
const channelCount = Cesium.MetadataType.getComponentCount(this.types[0]);
const { dimensions } = this;
- for (let level = 0; level < this._levelCount; level++) {
+ for (let level = 0; level < this.availableLevels; level++) {
const dimAtLevel = Math.pow(2, level);
const voxelCountX = dimensions.x * dimAtLevel;
const voxelCountY = dimensions.y * dimAtLevel;
@@ -158,9 +158,9 @@
ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
const { tileLevel, tileX, tileY, tileZ } = options;
- if (tileLevel >= this._levelCount) {
+ if (tileLevel >= this.availableLevels) {
return Promise.reject(
- `No tiles available beyond level ${this._levelCount - 1}`,
+ `No tiles available beyond level ${this.availableLevels - 1}`,
);
}
diff --git a/CHANGES.md b/CHANGES.md
index a9cdb100e06e..e8a7a93e1692 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -6,6 +6,10 @@
### @cesium/engine
+#### Breaking Changes :mega:
+
+- Voxel rendering now requires a WebGL2 context, which is [enabled by default since 1.101](https://github.com/CesiumGS/cesium/pull/10894). For voxel rendering, make sure the `requestWebGl1` flag in `contextOptions` is NOT set to true.
+
#### Fixes :wrench:
- Materials loaded from type now respect submaterials present in the referenced material type. [#10566](https://github.com/CesiumGS/cesium/issues/10566)
@@ -17,6 +21,7 @@
- Prevent runtime errors for certain forms of invalid PNTS files [#12872](https://github.com/CesiumGS/cesium/issues/12872)
- Improved performance of clamped labels. [#12905](https://github.com/CesiumGS/cesium/pull/12905)
- Fixes issue where multiple instances of a Gaussian splat tileset would transform tile positions incorrectly and render out of position. [#12795](https://github.com/CesiumGS/cesium/issues/12795)
+- Converted voxel raymarching to eye coordinates to fix precision issues in large datasets. [#12061](https://github.com/CesiumGS/cesium/issues/12061)
#### Additions :tada:
diff --git a/packages/engine/Source/Scene/ClippingPlaneCollection.js b/packages/engine/Source/Scene/ClippingPlaneCollection.js
index 53faf8d9c2ef..69de5080acb9 100644
--- a/packages/engine/Source/Scene/ClippingPlaneCollection.js
+++ b/packages/engine/Source/Scene/ClippingPlaneCollection.js
@@ -106,7 +106,7 @@ function ClippingPlaneCollection(options) {
* An event triggered when a new clipping plane is added to the collection. Event handlers
* are passed the new plane and the index at which it was added.
* @type {Event}
- * @default Event()
+ * @readonly
*/
this.planeAdded = new Event();
@@ -114,7 +114,7 @@ function ClippingPlaneCollection(options) {
* An event triggered when a new clipping plane is removed from the collection. Event handlers
* are passed the new plane and the index from which it was removed.
* @type {Event}
- * @default Event()
+ * @readonly
*/
this.planeRemoved = new Event();
@@ -472,7 +472,7 @@ ClippingPlaneCollection.prototype.update = function (frameState) {
// Compute texture requirements for current planes
// In RGBA FLOAT, A plane is 4 floats packed to a RGBA.
// In RGBA UNSIGNED_BYTE, A plane is a float in [0, 1) packed to RGBA and an Oct32 quantized normal,
- // so 8 bits or 2 pixels in RGBA.
+ // so 8 bytes or 2 pixels in RGBA.
const pixelsNeeded = useFloatTexture ? this.length : this.length * 2;
if (defined(clippingPlanesTexture)) {
diff --git a/packages/engine/Source/Scene/VoxelBoundsCollection.js b/packages/engine/Source/Scene/VoxelBoundsCollection.js
new file mode 100644
index 000000000000..90c51c9a564a
--- /dev/null
+++ b/packages/engine/Source/Scene/VoxelBoundsCollection.js
@@ -0,0 +1,494 @@
+import Cartesian2 from "../Core/Cartesian2.js";
+import Cartesian3 from "../Core/Cartesian3.js";
+import Cartesian4 from "../Core/Cartesian4.js";
+import Check from "../Core/Check.js";
+import ClippingPlane from "./ClippingPlane.js";
+import ContextLimits from "../Renderer/ContextLimits.js";
+import defined from "../Core/defined.js";
+import destroyObject from "../Core/destroyObject.js";
+import Event from "../Core/Event.js";
+import Frozen from "../Core/Frozen.js";
+import Intersect from "../Core/Intersect.js";
+import Matrix4 from "../Core/Matrix4.js";
+import PixelFormat from "../Core/PixelFormat.js";
+import PixelDatatype from "../Renderer/PixelDatatype.js";
+import Plane from "../Core/Plane.js";
+import Sampler from "../Renderer/Sampler.js";
+import Texture from "../Renderer/Texture.js";
+
+/**
+ * Specifies a set of clipping planes defining rendering bounds for a {@link VoxelPrimitive}.
+ *
+ * @alias VoxelBoundsCollection
+ * @constructor
+ *
+ * @param {object} [options] Object with the following properties:
+ * @param {ClippingPlane[]} [options.planes=[]] An array of {@link ClippingPlane} objects used to selectively disable rendering on the outside of each plane.
+ * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix specifying an additional transform relative to the clipping planes original coordinate system.
+ * @param {boolean} [options.unionClippingRegions=false] If true, a region will be clipped if it is on the outside of any plane in the collection. Otherwise, a region will only be clipped if it is on the outside of every plane.
+ *
+ * @private
+ */
+function VoxelBoundsCollection(options) {
+ const {
+ planes,
+ modelMatrix = Matrix4.IDENTITY,
+ unionClippingRegions = false,
+ } = options ?? Frozen.EMPTY_OBJECT;
+
+ this._planes = [];
+
+ /**
+ * The 4x4 transformation matrix specifying an additional transform relative to the clipping planes
+ * original coordinate system.
+ *
+ * @type {Matrix4}
+ * @default Matrix4.IDENTITY
+ */
+ this.modelMatrix = Matrix4.clone(modelMatrix);
+
+ /**
+ * An event triggered when a new clipping plane is added to the collection. Event handlers
+ * are passed the new plane and the index at which it was added.
+ * @type {Event}
+ * @readonly
+ */
+ this.planeAdded = new Event();
+
+ /**
+ * An event triggered when a new clipping plane is removed from the collection. Event handlers
+ * are passed the new plane and the index from which it was removed.
+ * @type {Event}
+ * @readonly
+ */
+ this.planeRemoved = new Event();
+
+ this._unionClippingRegions = unionClippingRegions;
+ this._testIntersection = unionClippingRegions
+ ? unionIntersectFunction
+ : defaultIntersectFunction;
+
+ this._float32View = undefined;
+
+ this._clippingPlanesTexture = undefined;
+
+ // Add each ClippingPlane object.
+ if (defined(planes)) {
+ for (let i = 0; i < planes.length; ++i) {
+ this.add(planes[i]);
+ }
+ }
+}
+
+function unionIntersectFunction(value) {
+ return value === Intersect.OUTSIDE;
+}
+
+function defaultIntersectFunction(value) {
+ return value === Intersect.INSIDE;
+}
+
+Object.defineProperties(VoxelBoundsCollection.prototype, {
+ /**
+ * Returns the number of planes in this collection. This is commonly used with
+ * {@link VoxelBoundsCollection#get} to iterate over all the planes
+ * in the collection.
+ *
+ * @memberof VoxelBoundsCollection.prototype
+ * @type {number}
+ * @readonly
+ */
+ length: {
+ get: function () {
+ return this._planes.length;
+ },
+ },
+
+ /**
+ * If true, a region will be clipped if it is on the outside of any plane in the
+ * collection. Otherwise, a region will only be clipped if it is on the
+ * outside of every plane.
+ *
+ * @memberof VoxelBoundsCollection.prototype
+ * @type {boolean}
+ * @default false
+ */
+ unionClippingRegions: {
+ get: function () {
+ return this._unionClippingRegions;
+ },
+ set: function (value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("value", value);
+ //>>includeEnd('debug');
+ if (this._unionClippingRegions === value) {
+ return;
+ }
+ this._unionClippingRegions = value;
+ this._testIntersection = value
+ ? unionIntersectFunction
+ : defaultIntersectFunction;
+ },
+ },
+
+ /**
+ * Returns a texture containing packed, untransformed clipping planes.
+ *
+ * @memberof VoxelBoundsCollection.prototype
+ * @type {Texture}
+ * @readonly
+ * @private
+ */
+ texture: {
+ get: function () {
+ return this._clippingPlanesTexture;
+ },
+ },
+
+ /**
+ * Returns a Number encapsulating the state for this VoxelBoundsCollection.
+ *
+ * Clipping mode is encoded in the sign of the number, which is just the plane count.
+ * If this value changes, then shader regeneration is necessary.
+ *
+ * @memberof VoxelBoundsCollection.prototype
+ * @returns {number} A Number that describes the VoxelBoundsCollection's state.
+ * @readonly
+ * @private
+ */
+ clippingPlanesState: {
+ get: function () {
+ return this._unionClippingRegions
+ ? this._planes.length
+ : -this._planes.length;
+ },
+ },
+});
+
+/**
+ * Adds the specified {@link ClippingPlane} to the collection to be used to selectively disable rendering
+ * on the outside of each plane. Use {@link VoxelBoundsCollection#unionClippingRegions} to modify
+ * how modify the clipping behavior of multiple planes.
+ *
+ * @param {ClippingPlane} plane The ClippingPlane to add to the collection.
+ *
+ * @see VoxelBoundsCollection#unionClippingRegions
+ * @see VoxelBoundsCollection#remove
+ * @see VoxelBoundsCollection#removeAll
+ */
+VoxelBoundsCollection.prototype.add = function (plane) {
+ const newPlaneIndex = this._planes.length;
+ plane.index = newPlaneIndex;
+ this._planes.push(plane);
+ this.planeAdded.raiseEvent(plane, newPlaneIndex);
+};
+
+/**
+ * Returns the plane in the collection at the specified index. Indices are zero-based
+ * and increase as planes are added. Removing a plane shifts all planes after
+ * it to the left, changing their indices. This function is commonly used with
+ * {@link VoxelBoundsCollection#length} to iterate over all the planes
+ * in the collection.
+ *
+ * @param {number} index The zero-based index of the plane.
+ * @returns {ClippingPlane} The ClippingPlane at the specified index.
+ *
+ * @see VoxelBoundsCollection#length
+ */
+VoxelBoundsCollection.prototype.get = function (index) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number("index", index);
+ //>>includeEnd('debug');
+
+ return this._planes[index];
+};
+
+function indexOf(planes, plane) {
+ for (let i = 0; i < planes.length; ++i) {
+ if (Plane.equals(planes[i], plane)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * Checks whether this collection contains a ClippingPlane equal to the given ClippingPlane.
+ *
+ * @param {ClippingPlane} [clippingPlane] The ClippingPlane to check for.
+ * @returns {boolean} true if this collection contains the ClippingPlane, false otherwise.
+ *
+ * @see VoxelBoundsCollection#get
+ */
+VoxelBoundsCollection.prototype.contains = function (clippingPlane) {
+ return indexOf(this._planes, clippingPlane) !== -1;
+};
+
+/**
+ * Removes the first occurrence of the given ClippingPlane from the collection.
+ *
+ * @param {ClippingPlane} clippingPlane
+ * @returns {boolean} true
if the plane was removed; false
if the plane was not found in the collection.
+ *
+ * @see VoxelBoundsCollection#add
+ * @see VoxelBoundsCollection#contains
+ * @see VoxelBoundsCollection#removeAll
+ */
+VoxelBoundsCollection.prototype.remove = function (clippingPlane) {
+ const planes = this._planes;
+ const index = indexOf(planes, clippingPlane);
+
+ if (index === -1) {
+ return false;
+ }
+
+ // Unlink this VoxelBoundsCollection from the ClippingPlane
+ if (clippingPlane instanceof ClippingPlane) {
+ clippingPlane.onChangeCallback = undefined;
+ clippingPlane.index = -1;
+ }
+
+ // Shift and update indices
+ const length = planes.length - 1;
+ for (let i = index; i < length; ++i) {
+ const planeToKeep = planes[i + 1];
+ planes[i] = planeToKeep;
+ if (planeToKeep instanceof ClippingPlane) {
+ planeToKeep.index = i;
+ }
+ }
+
+ planes.length = length;
+
+ this.planeRemoved.raiseEvent(clippingPlane, index);
+
+ return true;
+};
+
+/**
+ * Removes all planes from the collection.
+ *
+ * @see VoxelBoundsCollection#add
+ * @see VoxelBoundsCollection#remove
+ */
+VoxelBoundsCollection.prototype.removeAll = function () {
+ // Dereference this VoxelBoundsCollection from all ClippingPlanes
+ const planes = this._planes;
+ for (let i = 0; i < planes.length; ++i) {
+ const plane = planes[i];
+ if (plane instanceof ClippingPlane) {
+ plane.onChangeCallback = undefined;
+ plane.index = -1;
+ }
+ this.planeRemoved.raiseEvent(plane, i);
+ }
+ this._planes = [];
+};
+
+const scratchPlane = new Plane(Cartesian3.fromElements(1.0, 0.0, 0.0), 0.0);
+
+// Pack starting at the beginning of the buffer to allow partial update
+function transformAndPackPlanes(clippingPlaneCollection, transform) {
+ const float32View = clippingPlaneCollection._float32View;
+ const planes = clippingPlaneCollection._planes;
+
+ let floatIndex = 0;
+ for (let i = 0; i < planes.length; ++i) {
+ const { normal, distance } = transformPlane(
+ planes[i],
+ transform,
+ scratchPlane,
+ );
+
+ float32View[floatIndex] = normal.x;
+ float32View[floatIndex + 1] = normal.y;
+ float32View[floatIndex + 2] = normal.z;
+ float32View[floatIndex + 3] = distance;
+
+ floatIndex += 4; // each plane is 4 floats
+ }
+}
+
+const scratchPlaneCartesian4 = new Cartesian4();
+const scratchTransformedNormal = new Cartesian3();
+
+function transformPlane(plane, transform, result) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("plane", plane);
+ Check.typeOf.object("transform", transform);
+ //>>includeEnd('debug');
+
+ const { normal, distance } = plane;
+ const planeAsCartesian4 = Cartesian4.fromElements(
+ normal.x,
+ normal.y,
+ normal.z,
+ distance,
+ scratchPlaneCartesian4,
+ );
+ let transformedPlane = Matrix4.multiplyByVector(
+ transform,
+ planeAsCartesian4,
+ scratchPlaneCartesian4,
+ );
+
+ // Convert the transformed plane to Hessian Normal Form
+ const transformedNormal = Cartesian3.fromCartesian4(
+ transformedPlane,
+ scratchTransformedNormal,
+ );
+ transformedPlane = Cartesian4.divideByScalar(
+ transformedPlane,
+ Cartesian3.magnitude(transformedNormal),
+ scratchPlaneCartesian4,
+ );
+
+ return Plane.fromCartesian4(transformedPlane, result);
+}
+
+function computeTextureResolution(pixelsNeeded, result) {
+ result.x = Math.min(pixelsNeeded, ContextLimits.maximumTextureSize);
+ result.y = Math.ceil(pixelsNeeded / result.x);
+ return result;
+}
+
+const textureResolutionScratch = new Cartesian2();
+/**
+ * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
+ * build the resources for clipping planes.
+ *
+ * Do not call this function directly.
+ *
+ */
+VoxelBoundsCollection.prototype.update = function (frameState, transform) {
+ let clippingPlanesTexture = this._clippingPlanesTexture;
+
+ // Compute texture requirements for current planes
+ // In RGBA FLOAT, a plane is 4 floats packed to a single RGBA pixel.
+ const pixelsNeeded = this.length;
+
+ if (defined(clippingPlanesTexture)) {
+ const currentPixelCount =
+ clippingPlanesTexture.width * clippingPlanesTexture.height;
+ // Recreate the texture to double current requirement if it isn't big enough or is 4 times larger than it needs to be.
+ // Optimization note: this isn't exactly the classic resizeable array algorithm
+ // * not necessarily checking for resize after each add/remove operation
+ // * random-access deletes instead of just pops
+ // * alloc ops likely more expensive than demonstrable via big-O analysis
+ if (
+ currentPixelCount < pixelsNeeded ||
+ pixelsNeeded < 0.25 * currentPixelCount
+ ) {
+ clippingPlanesTexture.destroy();
+ clippingPlanesTexture = undefined;
+ this._clippingPlanesTexture = undefined;
+ }
+ }
+
+ // If there are no bound planes, there's nothing to update.
+ if (this.length === 0) {
+ return;
+ }
+
+ if (!defined(clippingPlanesTexture)) {
+ const requiredResolution = computeTextureResolution(
+ pixelsNeeded,
+ textureResolutionScratch,
+ );
+ // Allocate twice as much space as needed to avoid frequent texture reallocation.
+ // Allocate in the Y direction, since texture may be as wide as context texture support.
+ requiredResolution.y *= 2;
+
+ clippingPlanesTexture = new Texture({
+ context: frameState.context,
+ width: requiredResolution.x,
+ height: requiredResolution.y,
+ pixelFormat: PixelFormat.RGBA,
+ pixelDatatype: PixelDatatype.FLOAT,
+ sampler: Sampler.NEAREST,
+ flipY: false,
+ });
+ this._float32View = new Float32Array(
+ requiredResolution.x * requiredResolution.y * 4,
+ );
+
+ this._clippingPlanesTexture = clippingPlanesTexture;
+ }
+
+ const { width, height } = clippingPlanesTexture;
+ transformAndPackPlanes(this, transform);
+ clippingPlanesTexture.copyFrom({
+ source: {
+ width: width,
+ height: height,
+ arrayBufferView: this._float32View,
+ },
+ });
+};
+
+/**
+ * Function for getting the clipping plane collection's texture resolution.
+ * If the VoxelBoundsCollection hasn't been updated, returns the resolution that will be
+ * allocated based on the current plane count.
+ *
+ * @param {VoxelBoundsCollection} clippingPlaneCollection The clipping plane collection
+ * @param {Context} context The rendering context
+ * @param {Cartesian2} result A Cartesian2 for the result.
+ * @returns {Cartesian2} The required resolution.
+ * @private
+ */
+VoxelBoundsCollection.getTextureResolution = function (
+ clippingPlaneCollection,
+ context,
+ result,
+) {
+ const texture = clippingPlaneCollection.texture;
+ if (defined(texture)) {
+ result.x = texture.width;
+ result.y = texture.height;
+ return result;
+ }
+
+ const pixelsNeeded = clippingPlaneCollection.length;
+ const requiredResolution = computeTextureResolution(pixelsNeeded, result);
+
+ // Allocate twice as much space as needed to avoid frequent texture reallocation.
+ requiredResolution.y *= 2;
+ return requiredResolution;
+};
+
+/**
+ * Returns true if this object was destroyed; otherwise, false.
+ *
+ * If this object was destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception.
+ *
+ * @returns {boolean} true
if this object was destroyed; otherwise, false
.
+ *
+ * @see VoxelBoundsCollection#destroy
+ */
+VoxelBoundsCollection.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.
+ *
+ * Once an object is destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
+ * assign the return value (undefined
) to the object as done in the example.
+ *
+ * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
+ *
+ * @example
+ * voxelBounds = voxelBounds && voxelBounds.destroy();
+ *
+ * @see VoxelBoundsCollection#isDestroyed
+ */
+VoxelBoundsCollection.prototype.destroy = function () {
+ this._clippingPlanesTexture =
+ this._clippingPlanesTexture && this._clippingPlanesTexture.destroy();
+ return destroyObject(this);
+};
+export default VoxelBoundsCollection;
diff --git a/packages/engine/Source/Scene/VoxelBoxShape.js b/packages/engine/Source/Scene/VoxelBoxShape.js
index 9c1b893c6e5a..f39bf3d93a3e 100644
--- a/packages/engine/Source/Scene/VoxelBoxShape.js
+++ b/packages/engine/Source/Scene/VoxelBoxShape.js
@@ -5,6 +5,8 @@ import Check from "../Core/Check.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
+import VoxelBoundsCollection from "./VoxelBoundsCollection.js";
+import ClippingPlane from "./ClippingPlane.js";
/**
* A box {@link VoxelShape}.
@@ -20,100 +22,186 @@ import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
* @private
*/
function VoxelBoxShape() {
+ this._orientedBoundingBox = new OrientedBoundingBox();
+ this._boundingSphere = new BoundingSphere();
+ this._boundTransform = new Matrix4();
+ this._shapeTransform = new Matrix4();
+
/**
- * An oriented bounding box containing the bounded shape.
- * The update function must be called before accessing this value.
+ * The minimum bounds of the shape.
+ * @type {Cartesian3}
* @private
- * @type {OrientedBoundingBox}
- * @readonly
*/
- this.orientedBoundingBox = new OrientedBoundingBox();
+ this._minBounds = VoxelBoxShape.DefaultMinBounds.clone();
/**
- * A bounding sphere containing the bounded shape.
- * The update function must be called before accessing this value.
+ * The maximum bounds of the shape.
+ * @type {Cartesian3}
* @private
- * @type {BoundingSphere}
- * @readonly
*/
- this.boundingSphere = new BoundingSphere();
+ this._maxBounds = VoxelBoxShape.DefaultMaxBounds.clone();
/**
- * A transformation matrix containing the bounded shape.
- * The update function must be called before accessing this value.
+ * The minimum render bounds of the shape.
+ * @type {Cartesian3}
* @private
- * @type {Matrix4}
- * @readonly
*/
- this.boundTransform = new Matrix4();
+ this._renderMinBounds = VoxelBoxShape.DefaultMinBounds.clone();
/**
- * A transformation matrix containing the shape, ignoring the bounds.
- * The update function must be called before accessing this value.
+ * The maximum render bounds of the shape.
+ * @type {Cartesian3}
* @private
- * @type {Matrix4}
+ */
+ this._renderMaxBounds = VoxelBoxShape.DefaultMaxBounds.clone();
+
+ const { DefaultMinBounds, DefaultMaxBounds } = VoxelBoxShape;
+ const boundPlanes = [
+ new ClippingPlane(
+ Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()),
+ DefaultMinBounds.x,
+ ),
+ new ClippingPlane(
+ Cartesian3.negate(Cartesian3.UNIT_Y, new Cartesian3()),
+ DefaultMinBounds.y,
+ ),
+ new ClippingPlane(
+ Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
+ DefaultMinBounds.z,
+ ),
+ new ClippingPlane(Cartesian3.UNIT_X, -DefaultMaxBounds.x),
+ new ClippingPlane(Cartesian3.UNIT_Y, -DefaultMaxBounds.y),
+ new ClippingPlane(Cartesian3.UNIT_Z, -DefaultMaxBounds.z),
+ ];
+
+ this._renderBoundPlanes = new VoxelBoundsCollection({ planes: boundPlanes });
+
+ this._shaderUniforms = {
+ boxEcToXyz: new Matrix3(),
+ boxLocalToShapeUvScale: new Cartesian3(),
+ boxLocalToShapeUvTranslate: new Cartesian3(),
+ };
+
+ this._shaderDefines = {
+ BOX_INTERSECTION_INDEX: undefined,
+ };
+
+ this._shaderMaximumIntersectionsLength = 0; // not known until update
+}
+
+Object.defineProperties(VoxelBoxShape.prototype, {
+ /**
+ * An oriented bounding box containing the bounded shape.
+ *
+ * @memberof VoxelBoxShape.prototype
+ * @type {OrientedBoundingBox}
* @readonly
+ * @private
*/
- this.shapeTransform = new Matrix4();
+ orientedBoundingBox: {
+ get: function () {
+ return this._orientedBoundingBox;
+ },
+ },
/**
- * The minimum bounds of the shape.
- * @type {Cartesian3}
+ * A collection of planes used for the render bounds
+ * @memberof VoxelBoxShape.prototype
+ * @type {VoxelBoundsCollection}
+ * @readonly
* @private
*/
- this._minBounds = VoxelBoxShape.DefaultMinBounds.clone();
+ renderBoundPlanes: {
+ get: function () {
+ return this._renderBoundPlanes;
+ },
+ },
/**
- * The maximum bounds of the shape.
- * @type {Cartesian3}
+ * A bounding sphere containing the bounded shape.
+ *
+ * @memberof VoxelBoxShape.prototype
+ * @type {BoundingSphere}
+ * @readonly
* @private
*/
- this._maxBounds = VoxelBoxShape.DefaultMaxBounds.clone();
+ boundingSphere: {
+ get: function () {
+ return this._boundingSphere;
+ },
+ },
/**
+ * A transformation matrix containing the bounded shape.
+ *
+ * @memberof VoxelBoxShape.prototype
+ * @type {Matrix4}
+ * @readonly
* @private
- * @type {Object}
+ */
+ boundTransform: {
+ get: function () {
+ return this._boundTransform;
+ },
+ },
+
+ /**
+ * A transformation matrix containing the shape, ignoring the bounds.
+ *
+ * @memberof VoxelBoxShape.prototype
+ * @type {Matrix4}
* @readonly
+ * @private
*/
- this.shaderUniforms = {
- renderMinBounds: new Cartesian3(),
- renderMaxBounds: new Cartesian3(),
- boxUvToShapeUvScale: new Cartesian3(),
- boxUvToShapeUvTranslate: new Cartesian3(),
- };
+ shapeTransform: {
+ get: function () {
+ return this._shapeTransform;
+ },
+ },
/**
+ * @memberof VoxelBoxShape.prototype
+ * @type {Object}
+ * @readonly
* @private
+ */
+ shaderUniforms: {
+ get: function () {
+ return this._shaderUniforms;
+ },
+ },
+
+ /**
+ * @memberof VoxelBoxShape.prototype
* @type {Object}
* @readonly
+ * @private
*/
- this.shaderDefines = {
- BOX_INTERSECTION_INDEX: undefined,
- BOX_HAS_SHAPE_BOUNDS: undefined,
- };
+ shaderDefines: {
+ get: function () {
+ return this._shaderDefines;
+ },
+ },
/**
* The maximum number of intersections against the shape for any ray direction.
- * @private
+ * @memberof VoxelBoxShape.prototype
* @type {number}
* @readonly
+ * @private
*/
- this.shaderMaximumIntersectionsLength = 0; // not known until update
-}
+ shaderMaximumIntersectionsLength: {
+ get: function () {
+ return this._shaderMaximumIntersectionsLength;
+ },
+ },
+});
const scratchCenter = new Cartesian3();
const scratchScale = new Cartesian3();
const scratchRotation = new Matrix3();
const scratchClipMinBounds = new Cartesian3();
const scratchClipMaxBounds = new Cartesian3();
-const scratchRenderMinBounds = new Cartesian3();
-const scratchRenderMaxBounds = new Cartesian3();
-
-const transformLocalToUv = Matrix4.fromRotationTranslation(
- Matrix3.fromUniformScale(0.5, new Matrix3()),
- new Cartesian3(0.5, 0.5, 0.5),
- new Matrix4(),
-);
/**
* Update the shape's state.
@@ -148,13 +236,13 @@ VoxelBoxShape.prototype.update = function (
minBounds,
clipMinBounds,
clipMaxBounds,
- scratchRenderMinBounds,
+ this._renderMinBounds,
);
const renderMaxBounds = Cartesian3.clamp(
maxBounds,
clipMinBounds,
clipMaxBounds,
- scratchRenderMaxBounds,
+ this._renderMaxBounds,
);
// Box is not visible if:
@@ -177,29 +265,39 @@ VoxelBoxShape.prototype.update = function (
return false;
}
- this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
+ // Update the render bounds planes
+ const renderBoundPlanes = this._renderBoundPlanes;
+ renderBoundPlanes.get(0).distance = renderMinBounds.x;
+ renderBoundPlanes.get(1).distance = renderMinBounds.y;
+ renderBoundPlanes.get(2).distance = renderMinBounds.z;
+ renderBoundPlanes.get(3).distance = -renderMaxBounds.x;
+ renderBoundPlanes.get(4).distance = -renderMaxBounds.y;
+ renderBoundPlanes.get(5).distance = -renderMaxBounds.z;
- this.orientedBoundingBox = getBoxChunkObb(
+ this._shapeTransform = Matrix4.clone(modelMatrix, this._shapeTransform);
+
+ this._orientedBoundingBox = getBoxChunkObb(
renderMinBounds,
renderMaxBounds,
- this.shapeTransform,
- this.orientedBoundingBox,
+ this._shapeTransform,
+ this._orientedBoundingBox,
);
// All of the box bounds go from -1 to +1, so the model matrix scale can be
// used as the oriented bounding box half axes.
- this.boundTransform = Matrix4.fromRotationTranslation(
- this.orientedBoundingBox.halfAxes,
- this.orientedBoundingBox.center,
- this.boundTransform,
+ this._boundTransform = Matrix4.fromRotationTranslation(
+ this._orientedBoundingBox.halfAxes,
+ this._orientedBoundingBox.center,
+ this._boundTransform,
);
- this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
- this.orientedBoundingBox,
- this.boundingSphere,
+ this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(
+ this._orientedBoundingBox,
+ this._boundingSphere,
);
- const { shaderUniforms, shaderDefines } = this;
+ const shaderUniforms = this._shaderUniforms;
+ const shaderDefines = this._shaderDefines;
// To keep things simple, clear the defines every time
for (const key in shaderDefines) {
@@ -214,49 +312,87 @@ VoxelBoxShape.prototype.update = function (
shaderDefines["BOX_INTERSECTION_INDEX"] = intersectionCount;
intersectionCount += 1;
- shaderUniforms.renderMinBounds = Matrix4.multiplyByPoint(
- transformLocalToUv,
- renderMinBounds,
- shaderUniforms.renderMinBounds,
+ // Compute scale and translation to transform from UV space to bounded UV space
+ const min = minBounds;
+ const max = maxBounds;
+ const boxLocalToShapeUvScale = Cartesian3.fromElements(
+ boundScale(min.x, max.x),
+ boundScale(min.y, max.y),
+ boundScale(min.z, max.z),
+ shaderUniforms.boxLocalToShapeUvScale,
);
- shaderUniforms.renderMaxBounds = Matrix4.multiplyByPoint(
- transformLocalToUv,
- renderMaxBounds,
- shaderUniforms.renderMaxBounds,
+ shaderUniforms.boxLocalToShapeUvTranslate = Cartesian3.negate(
+ Cartesian3.multiplyComponents(
+ boxLocalToShapeUvScale,
+ min,
+ shaderUniforms.boxLocalToShapeUvTranslate,
+ ),
+ shaderUniforms.boxLocalToShapeUvTranslate,
);
- shaderDefines["BOX_HAS_SHAPE_BOUNDS"] = true;
+ this._shaderMaximumIntersectionsLength = intersectionCount;
- const min = minBounds;
- const max = maxBounds;
+ return true;
+};
- // Go from UV space to bounded UV space:
- // delerp(posUv, minBoundsUv, maxBoundsUv)
- // (posUv - minBoundsUv) / (maxBoundsUv - minBoundsUv)
- // posUv / (maxBoundsUv - minBoundsUv) - minBoundsUv / (maxBoundsUv - minBoundsUv)
- // scale = 1.0 / (maxBoundsUv - minBoundsUv)
- // scale = 1.0 / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
- // scale = 2.0 / (maxBounds - minBounds)
- // offset = -minBoundsUv / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
- // offset = -2.0 * (minBounds * 0.5 + 0.5) / (maxBounds - minBounds)
- // offset = -scale * (minBounds * 0.5 + 0.5)
- shaderUniforms.boxUvToShapeUvScale = Cartesian3.fromElements(
- 2.0 / (min.x === max.x ? 1.0 : max.x - min.x),
- 2.0 / (min.y === max.y ? 1.0 : max.y - min.y),
- 2.0 / (min.z === max.z ? 1.0 : max.z - min.z),
- shaderUniforms.boxUvToShapeUvScale,
- );
+function boundScale(minBound, maxBound) {
+ return CesiumMath.equalsEpsilon(minBound, maxBound, CesiumMath.EPSILON7)
+ ? 1.0
+ : 1.0 / (maxBound - minBound);
+}
- shaderUniforms.boxUvToShapeUvTranslate = Cartesian3.fromElements(
- -shaderUniforms.boxUvToShapeUvScale.x * (min.x * 0.5 + 0.5),
- -shaderUniforms.boxUvToShapeUvScale.y * (min.y * 0.5 + 0.5),
- -shaderUniforms.boxUvToShapeUvScale.z * (min.z * 0.5 + 0.5),
- shaderUniforms.boxUvToShapeUvTranslate,
+const scratchTransformPositionWorldToLocal = new Matrix4();
+/**
+ * Update any view-dependent transforms.
+ * @private
+ * @param {FrameState} frameState The frame state.
+ */
+VoxelBoxShape.prototype.updateViewTransforms = function (frameState) {
+ const shaderUniforms = this._shaderUniforms;
+ const transformPositionWorldToLocal = Matrix4.inverse(
+ this._shapeTransform,
+ scratchTransformPositionWorldToLocal,
+ );
+ const transformDirectionWorldToLocal = Matrix4.getMatrix3(
+ transformPositionWorldToLocal,
+ shaderUniforms.boxEcToXyz,
);
+ const rotateViewToWorld = frameState.context.uniformState.inverseViewRotation;
+ Matrix3.multiply(
+ transformDirectionWorldToLocal,
+ rotateViewToWorld,
+ shaderUniforms.boxEcToXyz,
+ );
+};
- this.shaderMaximumIntersectionsLength = intersectionCount;
+/**
+ * Convert a local coordinate to the shape's UV space.
+ * @private
+ * @param {Cartesian3} positionLocal The local coordinate to convert.
+ * @param {Cartesian3} result The Cartesian3 to store the result in.
+ * @returns {Cartesian3} The converted UV coordinate.
+ */
+VoxelBoxShape.prototype.convertLocalToShapeUvSpace = function (
+ positionLocal,
+ result,
+) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("positionLocal", positionLocal);
+ Check.typeOf.object("result", result);
+ //>>includeEnd('debug');
- return true;
+ const { boxLocalToShapeUvScale, boxLocalToShapeUvTranslate } =
+ this._shaderUniforms;
+
+ return Cartesian3.add(
+ Cartesian3.multiplyComponents(
+ positionLocal,
+ boxLocalToShapeUvScale,
+ result,
+ ),
+ boxLocalToShapeUvTranslate,
+ result,
+ );
};
const scratchTileMinBounds = new Cartesian3();
@@ -264,7 +400,6 @@ const scratchTileMaxBounds = new Cartesian3();
/**
* Computes an oriented bounding box for a specified tile.
- * The update function must be called before calling this function.
* @private
* @param {number} tileLevel The tile's level.
* @param {number} tileX The tile's x coordinate.
@@ -309,7 +444,7 @@ VoxelBoxShape.prototype.computeOrientedBoundingBoxForTile = function (
return getBoxChunkObb(
tileMinBounds,
tileMaxBounds,
- this.shapeTransform,
+ this._shapeTransform,
result,
);
};
@@ -318,7 +453,6 @@ const sampleSizeScratch = new Cartesian3();
/**
* Computes an oriented bounding box for a specified sample within a specified tile.
- * The update function must be called before calling this function.
* @private
* @param {SpatialNode} spatialNode The spatial node containing the sample
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
@@ -385,7 +519,7 @@ VoxelBoxShape.prototype.computeOrientedBoundingBoxForSample = function (
return getBoxChunkObb(
sampleMinBounds,
sampleMaxBounds,
- this.shapeTransform,
+ this._shapeTransform,
result,
);
};
@@ -412,6 +546,7 @@ VoxelBoxShape.DefaultMaxBounds = Object.freeze(
new Cartesian3(+1.0, +1.0, +1.0),
);
+const scratchBoxScale = new Cartesian3();
/**
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
*
@@ -437,7 +572,7 @@ function getBoxChunkObb(minimumBounds, maximumBounds, matrix, result) {
result.center = Matrix4.getTranslation(matrix, result.center);
result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
} else {
- let scale = Matrix4.getScale(matrix, scratchScale);
+ let scale = Matrix4.getScale(matrix, scratchBoxScale);
const localCenter = Cartesian3.midpoint(
minimumBounds,
maximumBounds,
@@ -448,7 +583,7 @@ function getBoxChunkObb(minimumBounds, maximumBounds, matrix, result) {
scale.x * 0.5 * (maximumBounds.x - minimumBounds.x),
scale.y * 0.5 * (maximumBounds.y - minimumBounds.y),
scale.z * 0.5 * (maximumBounds.z - minimumBounds.z),
- scratchScale,
+ scratchBoxScale,
);
const rotation = Matrix4.getRotation(matrix, scratchRotation);
result.halfAxes = Matrix3.setScale(rotation, scale, result.halfAxes);
diff --git a/packages/engine/Source/Scene/VoxelCylinderShape.js b/packages/engine/Source/Scene/VoxelCylinderShape.js
index b19759672570..4bb78629802b 100644
--- a/packages/engine/Source/Scene/VoxelCylinderShape.js
+++ b/packages/engine/Source/Scene/VoxelCylinderShape.js
@@ -4,9 +4,11 @@ import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import CesiumMath from "../Core/Math.js";
import Check from "../Core/Check.js";
+import ClippingPlane from "./ClippingPlane.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
+import VoxelBoundsCollection from "./VoxelBoundsCollection.js";
/**
* A cylinder {@link VoxelShape}.
@@ -22,111 +24,179 @@ import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
* @private
*/
function VoxelCylinderShape() {
+ this._orientedBoundingBox = new OrientedBoundingBox();
+ this._boundingSphere = new BoundingSphere();
+ this._boundTransform = new Matrix4();
+ this._shapeTransform = new Matrix4();
+
/**
- * An oriented bounding box containing the bounded shape.
- * The update function must be called before accessing this value.
+ * The minimum bounds of the shape, corresponding to minimum radius, angle, and height.
+ * @type {Cartesian3}
* @private
- * @type {OrientedBoundingBox}
- * @readonly
*/
- this.orientedBoundingBox = new OrientedBoundingBox();
+ this._minBounds = VoxelCylinderShape.DefaultMinBounds.clone();
/**
- * A bounding sphere containing the bounded shape.
- * The update function must be called before accessing this value.
+ * The maximum bounds of the shape, corresponding to maximum radius, angle, and height.
+ * @type {Cartesian3}
* @private
- * @type {BoundingSphere}
- * @readonly
*/
- this.boundingSphere = new BoundingSphere();
+ this._maxBounds = VoxelCylinderShape.DefaultMaxBounds.clone();
+
+ const { DefaultMinBounds, DefaultMaxBounds } = VoxelCylinderShape;
+ const boundPlanes = [
+ new ClippingPlane(
+ Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
+ DefaultMinBounds.z,
+ ),
+ new ClippingPlane(Cartesian3.UNIT_Z, -DefaultMaxBounds.z),
+ ];
+
+ this._renderBoundPlanes = new VoxelBoundsCollection({ planes: boundPlanes });
+
+ this._shaderUniforms = {
+ cameraShapePosition: new Cartesian3(),
+ cylinderEcToRadialTangentUp: new Matrix3(),
+ cylinderRenderRadiusMinMax: new Cartesian2(),
+ cylinderRenderAngleMinMax: new Cartesian2(),
+ cylinderLocalToShapeUvRadius: new Cartesian2(),
+ cylinderLocalToShapeUvAngle: new Cartesian2(),
+ cylinderLocalToShapeUvHeight: new Cartesian2(),
+ cylinderShapeUvAngleRangeOrigin: 0.0,
+ };
+
+ this._shaderDefines = {
+ CYLINDER_HAS_SHAPE_BOUNDS_ANGLE: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_ANGLE: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_ZERO: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF: undefined,
+ CYLINDER_INTERSECTION_INDEX_RADIUS_MAX: undefined,
+ CYLINDER_INTERSECTION_INDEX_RADIUS_MIN: undefined,
+ CYLINDER_INTERSECTION_INDEX_ANGLE: undefined,
+ };
+
+ this._shaderMaximumIntersectionsLength = 0; // not known until update
+}
+Object.defineProperties(VoxelCylinderShape.prototype, {
/**
- * A transformation matrix containing the bounded shape.
- * The update function must be called before accessing this value.
- * @private
- * @type {Matrix4}
+ * An oriented bounding box containing the bounded shape.
+ *
+ * @memberof VoxelCylinderShape.prototype
+ * @type {OrientedBoundingBox}
* @readonly
+ * @private
*/
- this.boundTransform = new Matrix4();
+ orientedBoundingBox: {
+ get: function () {
+ return this._orientedBoundingBox;
+ },
+ },
/**
- * A transformation matrix containing the shape, ignoring the bounds.
- * The update function must be called before accessing this value.
- * @private
- * @type {Matrix4}
+ * A collection of planes used for the render bounds
+ * @memberof VoxelCylinderShape.prototype
+ * @type {VoxelBoundsCollection}
* @readonly
+ * @private
*/
- this.shapeTransform = new Matrix4();
+ renderBoundPlanes: {
+ get: function () {
+ return this._renderBoundPlanes;
+ },
+ },
/**
- * The minimum bounds of the shape, corresponding to minimum radius, angle, and height.
- * @type {Cartesian3}
+ * A bounding sphere containing the bounded shape.
+ *
+ * @memberof VoxelCylinderShape.prototype
+ * @type {BoundingSphere}
+ * @readonly
* @private
*/
- this._minBounds = VoxelCylinderShape.DefaultMinBounds.clone();
+ boundingSphere: {
+ get: function () {
+ return this._boundingSphere;
+ },
+ },
/**
- * The maximum bounds of the shape, corresponding to maximum radius, angle, and height.
- * @type {Cartesian3}
+ * A transformation matrix containing the bounded shape.
+ *
+ * @memberof VoxelCylinderShape.prototype
+ * @type {Matrix4}
+ * @readonly
* @private
*/
- this._maxBounds = VoxelCylinderShape.DefaultMaxBounds.clone();
+ boundTransform: {
+ get: function () {
+ return this._boundTransform;
+ },
+ },
/**
+ * A transformation matrix containing the shape, ignoring the bounds.
+ *
+ * @memberof VoxelCylinderShape.prototype
+ * @type {Matrix4}
+ * @readonly
* @private
+ */
+ shapeTransform: {
+ get: function () {
+ return this._shapeTransform;
+ },
+ },
+
+ /**
+ * @memberof VoxelCylinderShape.prototype
* @type {Object}
* @readonly
+ * @private
*/
- this.shaderUniforms = {
- cylinderRenderRadiusMinMax: new Cartesian2(),
- cylinderRenderAngleMinMax: new Cartesian2(),
- cylinderRenderHeightMinMax: new Cartesian2(),
- cylinderUvToShapeUvRadius: new Cartesian2(),
- cylinderUvToShapeUvAngle: new Cartesian2(),
- cylinderUvToShapeUvHeight: new Cartesian2(),
- cylinderShapeUvAngleMinMax: new Cartesian2(),
- cylinderShapeUvAngleRangeZeroMid: 0.0,
- };
+ shaderUniforms: {
+ get: function () {
+ return this._shaderUniforms;
+ },
+ },
/**
- * @private
+ * @memberof VoxelCylinderShape.prototype
* @type {Object}
* @readonly
+ * @private
*/
- this.shaderDefines = {
- CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN: undefined,
- CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT: undefined,
- CYLINDER_HAS_RENDER_BOUNDS_ANGLE: undefined,
- CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_ZERO: undefined,
- CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF: undefined,
- CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF: undefined,
-
- CYLINDER_HAS_SHAPE_BOUNDS_RADIUS: undefined,
- CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT: undefined,
- CYLINDER_HAS_SHAPE_BOUNDS_ANGLE: undefined,
- CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY: undefined,
- CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY: undefined,
- CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED: undefined,
-
- CYLINDER_INTERSECTION_INDEX_RADIUS_MAX: undefined,
- CYLINDER_INTERSECTION_INDEX_RADIUS_MIN: undefined,
- CYLINDER_INTERSECTION_INDEX_ANGLE: undefined,
- };
+ shaderDefines: {
+ get: function () {
+ return this._shaderDefines;
+ },
+ },
/**
* The maximum number of intersections against the shape for any ray direction.
- * @private
+ * @memberof VoxelCylinderShape.prototype
* @type {number}
* @readonly
+ * @private
*/
- this.shaderMaximumIntersectionsLength = 0; // not known until update
-}
+ shaderMaximumIntersectionsLength: {
+ get: function () {
+ return this._shaderMaximumIntersectionsLength;
+ },
+ },
+});
const scratchScale = new Cartesian3();
const scratchClipMinBounds = new Cartesian3();
const scratchClipMaxBounds = new Cartesian3();
const scratchRenderMinBounds = new Cartesian3();
const scratchRenderMaxBounds = new Cartesian3();
+const scratchTransformPositionWorldToLocal = new Matrix4();
+const scratchCameraPositionLocal = new Cartesian3();
+const scratchCameraRadialPosition = new Cartesian2();
/**
* Update the shape's state.
@@ -158,15 +228,15 @@ VoxelCylinderShape.prototype.update = function (
maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
const { DefaultMinBounds, DefaultMaxBounds } = VoxelCylinderShape;
- const defaultAngleRange = DefaultMaxBounds.y - DefaultMinBounds.y;
- const defaultAngleRangeHalf = 0.5 * defaultAngleRange;
+ const defaultAngleRange = DefaultMaxBounds.y - DefaultMinBounds.y; // == 2 * PI
+ const defaultAngleRangeHalf = 0.5 * defaultAngleRange; // == PI
const epsilonZeroScale = CesiumMath.EPSILON10;
- const epsilonAngleDiscontinuity = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
const epsilonAngle = CesiumMath.EPSILON10;
// Clamp the bounds to the valid range
minBounds.x = Math.max(0.0, minBounds.x);
+ // TODO: require maxBounds.x >= minBounds.x ?
maxBounds.x = Math.max(0.0, maxBounds.x);
minBounds.y = CesiumMath.negativePiToPi(minBounds.y);
maxBounds.y = CesiumMath.negativePiToPi(maxBounds.y);
@@ -174,6 +244,10 @@ VoxelCylinderShape.prototype.update = function (
clipMinBounds.y = CesiumMath.negativePiToPi(clipMinBounds.y);
clipMaxBounds.y = CesiumMath.negativePiToPi(clipMaxBounds.y);
+ // TODO: what does this do with partial volumes crossing the antimeridian?
+ // We could have minBounds.y = +PI/2 and maxBounds.y = -PI/2.
+ // Then clipMinBounds.y = +PI/4 and clipMaxBounds.y = -PI/4.
+ // This maximumByComponent would cancel the clipping.
const renderMinBounds = Cartesian3.maximumByComponent(
minBounds,
clipMinBounds,
@@ -205,57 +279,35 @@ VoxelCylinderShape.prototype.update = function (
return false;
}
- this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
+ // Update the render bounds planes
+ const renderBoundPlanes = this._renderBoundPlanes;
+ renderBoundPlanes.get(0).distance = renderMinBounds.z;
+ renderBoundPlanes.get(1).distance = -renderMaxBounds.z;
- this.orientedBoundingBox = getCylinderChunkObb(
+ this._shapeTransform = Matrix4.clone(modelMatrix, this._shapeTransform);
+
+ this._orientedBoundingBox = getCylinderChunkObb(
renderMinBounds,
renderMaxBounds,
- this.shapeTransform,
- this.orientedBoundingBox,
+ this._shapeTransform,
+ this._orientedBoundingBox,
);
- this.boundTransform = Matrix4.fromRotationTranslation(
- this.orientedBoundingBox.halfAxes,
- this.orientedBoundingBox.center,
- this.boundTransform,
+ this._boundTransform = Matrix4.fromRotationTranslation(
+ this._orientedBoundingBox.halfAxes,
+ this._orientedBoundingBox.center,
+ this._boundTransform,
);
- this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
- this.orientedBoundingBox,
- this.boundingSphere,
+ this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(
+ this._orientedBoundingBox,
+ this._boundingSphere,
);
- const shapeIsDefaultRadius =
- minBounds.x === DefaultMinBounds.x && maxBounds.x === DefaultMaxBounds.x;
const shapeIsAngleReversed = maxBounds.y < minBounds.y;
const shapeAngleRange =
maxBounds.y - minBounds.y + shapeIsAngleReversed * defaultAngleRange;
- const shapeIsAngleRegular =
- shapeAngleRange > defaultAngleRangeHalf + epsilonAngle &&
- shapeAngleRange < defaultAngleRange - epsilonAngle;
- const shapeIsAngleFlipped =
- shapeAngleRange < defaultAngleRangeHalf - epsilonAngle;
- const shapeIsAngleRangeHalf =
- shapeAngleRange >= defaultAngleRangeHalf - epsilonAngle &&
- shapeAngleRange <= defaultAngleRangeHalf + epsilonAngle;
- const shapeHasAngle =
- shapeIsAngleRegular || shapeIsAngleFlipped || shapeIsAngleRangeHalf;
- const shapeIsMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
- minBounds.y,
- DefaultMinBounds.y,
- undefined,
- epsilonAngleDiscontinuity,
- );
- const shapeIsMaxAngleDiscontinuity = CesiumMath.equalsEpsilon(
- maxBounds.y,
- DefaultMaxBounds.y,
- undefined,
- epsilonAngleDiscontinuity,
- );
- const shapeIsDefaultHeight =
- minBounds.z === DefaultMinBounds.z && maxBounds.z === DefaultMaxBounds.z;
- const renderIsDefaultMinRadius = renderMinBounds.x === DefaultMinBounds.x;
const renderIsAngleReversed = renderMaxBounds.y < renderMinBounds.y;
const renderAngleRange =
renderMaxBounds.y -
@@ -271,7 +323,8 @@ VoxelCylinderShape.prototype.update = function (
const renderHasAngle =
renderIsAngleRegular || renderIsAngleFlipped || renderIsAngleRangeZero;
- const { shaderUniforms, shaderDefines } = this;
+ const shaderUniforms = this._shaderUniforms;
+ const shaderDefines = this._shaderDefines;
// To keep things simple, clear the defines every time
for (const key in shaderDefines) {
@@ -286,7 +339,11 @@ VoxelCylinderShape.prototype.update = function (
shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MAX"] = intersectionCount;
intersectionCount += 1;
- if (!renderIsDefaultMinRadius) {
+ if (shapeAngleRange < defaultAngleRange - epsilonAngle) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE"] = true;
+ }
+
+ if (renderMinBounds.x !== DefaultMinBounds.x) {
shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN"] = true;
shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MIN"] = intersectionCount;
intersectionCount += 1;
@@ -300,65 +357,32 @@ VoxelCylinderShape.prototype.update = function (
if (renderMinBounds.x === renderMaxBounds.x) {
shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT"] = true;
}
- if (!shapeIsDefaultRadius) {
- shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_RADIUS"] = true;
-
- // delerp(radius, minRadius, maxRadius)
- // (radius - minRadius) / (maxRadius - minRadius)
- // radius / (maxRadius - minRadius) - minRadius / (maxRadius - minRadius)
- // scale = 1.0 / (maxRadius - minRadius)
- // offset = -minRadius / (maxRadius - minRadius)
- // offset = minRadius / (minRadius - maxRadius)
- const radiusRange = maxBounds.x - minBounds.x;
- let scale = 0.0;
- let offset = 1.0;
- if (radiusRange !== 0.0) {
- scale = 1.0 / radiusRange;
- offset = -minBounds.x / radiusRange;
- }
- shaderUniforms.cylinderUvToShapeUvRadius = Cartesian2.fromElements(
- scale,
- offset,
- shaderUniforms.cylinderUvToShapeUvRadius,
- );
- }
- if (!shapeIsDefaultHeight) {
- shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT"] = true;
-
- // delerp(heightUv, minHeightUv, maxHeightUv)
- // (heightUv - minHeightUv) / (maxHeightUv - minHeightUv)
- // heightUv / (maxHeightUv - minHeightUv) - minHeightUv / (maxHeightUv - minHeightUv)
- // scale = 1.0 / (maxHeightUv - minHeightUv)
- // scale = 1.0 / ((maxHeight * 0.5 + 0.5) - (minHeight * 0.5 + 0.5))
- // scale = 2.0 / (maxHeight - minHeight)
- // offset = -minHeightUv / (maxHeightUv - minHeightUv)
- // offset = -minHeightUv / ((maxHeight * 0.5 + 0.5) - (minHeight * 0.5 + 0.5))
- // offset = -2.0 * (minHeight * 0.5 + 0.5) / (maxHeight - minHeight)
- // offset = -(minHeight + 1.0) / (maxHeight - minHeight)
- // offset = (minHeight + 1.0) / (minHeight - maxHeight)
- const heightRange = maxBounds.z - minBounds.z;
- let scale = 0.0;
- let offset = 1.0;
- if (heightRange !== 0.0) {
- scale = 2.0 / heightRange;
- offset = -(minBounds.z + 1.0) / heightRange;
- }
- shaderUniforms.cylinderUvToShapeUvHeight = Cartesian2.fromElements(
- scale,
- offset,
- shaderUniforms.cylinderUvToShapeUvHeight,
- );
+ const radiusRange = maxBounds.x - minBounds.x;
+ let radialScale = 0.0;
+ let radialOffset = 1.0;
+ if (radiusRange !== 0.0) {
+ radialScale = 1.0 / radiusRange;
+ radialOffset = -minBounds.x * radialScale;
}
- shaderUniforms.cylinderRenderHeightMinMax = Cartesian2.fromElements(
- renderMinBounds.z,
- renderMaxBounds.z,
- shaderUniforms.cylinderRenderHeightMinMax,
+ shaderUniforms.cylinderLocalToShapeUvRadius = Cartesian2.fromElements(
+ radialScale,
+ radialOffset,
+ shaderUniforms.cylinderLocalToShapeUvRadius,
);
- if (shapeIsAngleReversed) {
- shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED"] = true;
+ const heightRange = maxBounds.z - minBounds.z; // Default 2.0
+ let heightScale = 0.0;
+ let heightOffset = 1.0;
+ if (heightRange !== 0.0) {
+ heightScale = 1.0 / heightRange;
+ heightOffset = -minBounds.z * heightScale;
}
+ shaderUniforms.cylinderLocalToShapeUvHeight = Cartesian2.fromElements(
+ heightScale,
+ heightOffset,
+ shaderUniforms.cylinderLocalToShapeUvHeight,
+ );
if (renderHasAngle) {
shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE"] = true;
@@ -382,64 +406,151 @@ VoxelCylinderShape.prototype.update = function (
);
}
- if (shapeHasAngle) {
- shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE"] = true;
- if (shapeIsMinAngleDiscontinuity) {
- shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY"] = true;
- }
- if (shapeIsMaxAngleDiscontinuity) {
- shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY"] = true;
- }
+ const uvMinAngle = (minBounds.y - DefaultMinBounds.y) / defaultAngleRange;
+ const uvMaxAngle = (maxBounds.y - DefaultMinBounds.y) / defaultAngleRange;
+ const uvAngleRangeZero = 1.0 - shapeAngleRange / defaultAngleRange;
- const uvMinAngle = (minBounds.y - DefaultMinBounds.y) / defaultAngleRange;
- const uvMaxAngle = (maxBounds.y - DefaultMinBounds.y) / defaultAngleRange;
- const uvAngleRangeZero = 1.0 - shapeAngleRange / defaultAngleRange;
+ // Translate the origin of UV angles (in [0,1]) to the center of the unoccupied space
+ const uvAngleRangeOrigin = (uvMaxAngle + 0.5 * uvAngleRangeZero) % 1.0;
+ shaderUniforms.cylinderShapeUvAngleRangeOrigin = uvAngleRangeOrigin;
- shaderUniforms.cylinderShapeUvAngleMinMax = Cartesian2.fromElements(
- uvMinAngle,
- uvMaxAngle,
- shaderUniforms.cylinderShapeUvAngleMinMax,
+ if (shapeAngleRange <= epsilonAngle) {
+ shaderUniforms.cylinderLocalToShapeUvAngle = Cartesian2.fromElements(
+ 0.0,
+ 1.0,
+ shaderUniforms.cylinderLocalToShapeUvAngle,
+ );
+ } else {
+ const scale = defaultAngleRange / shapeAngleRange;
+ const shiftedMinAngle = uvMinAngle - uvAngleRangeOrigin;
+ const offset = -scale * (shiftedMinAngle - Math.floor(shiftedMinAngle));
+ shaderUniforms.cylinderLocalToShapeUvAngle = Cartesian2.fromElements(
+ scale,
+ offset,
+ shaderUniforms.cylinderLocalToShapeUvAngle,
);
- shaderUniforms.cylinderShapeUvAngleRangeZeroMid =
- (uvMaxAngle + 0.5 * uvAngleRangeZero) % 1.0;
-
- // delerp(angleUv, uvMinAngle, uvMaxAngle)
- // (angelUv - uvMinAngle) / (uvMaxAngle - uvMinAngle)
- // angleUv / (uvMaxAngle - uvMinAngle) - uvMinAngle / (uvMaxAngle - uvMinAngle)
- // scale = 1.0 / (uvMaxAngle - uvMinAngle)
- // scale = 1.0 / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
- // scale = 2.0 * pi / (maxAngle - minAngle)
- // offset = -uvMinAngle / (uvMaxAngle - uvMinAngle)
- // offset = -((minAngle - pi) / (2.0 * pi)) / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
- // offset = -(minAngle - pi) / (maxAngle - minAngle)
- if (shapeAngleRange <= epsilonAngle) {
- shaderUniforms.cylinderUvToShapeUvAngle = Cartesian2.fromElements(
- 0.0,
- 1.0,
- shaderUniforms.cylinderUvToShapeUvAngle,
- );
- } else {
- const scale = defaultAngleRange / shapeAngleRange;
- const offset = -(minBounds.y - DefaultMinBounds.y) / shapeAngleRange;
- shaderUniforms.cylinderUvToShapeUvAngle = Cartesian2.fromElements(
- scale,
- offset,
- shaderUniforms.cylinderUvToShapeUvAngle,
- );
- }
}
- this.shaderMaximumIntersectionsLength = intersectionCount;
+ this._shaderMaximumIntersectionsLength = intersectionCount;
return true;
};
+const scratchRotateRtuToLocal = new Matrix3();
+const scratchRtuRotation = new Matrix3();
+const scratchTransformPositionViewToLocal = new Matrix4();
+
+/**
+ * Update any view-dependent transforms.
+ * @private
+ * @param {FrameState} frameState The frame state.
+ */
+VoxelCylinderShape.prototype.updateViewTransforms = function (frameState) {
+ const shaderUniforms = this._shaderUniforms;
+ // 1. Update camera position in cylindrical coordinates
+ const transformPositionWorldToLocal = Matrix4.inverse(
+ this._shapeTransform,
+ scratchTransformPositionWorldToLocal,
+ );
+ const cameraPositionLocal = Matrix4.multiplyByPoint(
+ transformPositionWorldToLocal,
+ frameState.camera.positionWC,
+ scratchCameraPositionLocal,
+ );
+ shaderUniforms.cameraShapePosition = Cartesian3.fromElements(
+ Cartesian2.magnitude(cameraPositionLocal),
+ Math.atan2(cameraPositionLocal.y, cameraPositionLocal.x),
+ cameraPositionLocal.z,
+ shaderUniforms.cameraShapePosition,
+ );
+ // 2. Find radial, tangent, and up components at camera position
+ const cameraRadialDirection = Cartesian2.normalize(
+ Cartesian2.fromCartesian3(cameraPositionLocal, scratchCameraRadialPosition),
+ scratchCameraRadialPosition,
+ );
+ // As row vectors, the radial, tangent, and up vectors constitute a rotation matrix from local to RTU.
+ const rotateLocalToRtu = Matrix3.fromRowMajorArray(
+ [
+ cameraRadialDirection.x,
+ cameraRadialDirection.y,
+ 0.0,
+ -cameraRadialDirection.y,
+ cameraRadialDirection.x,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ ],
+ scratchRotateRtuToLocal,
+ );
+ // 3. Get rotation from eye to local coordinates
+ const transformPositionViewToWorld =
+ frameState.context.uniformState.inverseView;
+ const transformPositionViewToLocal = Matrix4.multiplyTransformation(
+ transformPositionWorldToLocal,
+ transformPositionViewToWorld,
+ scratchTransformPositionViewToLocal,
+ );
+ const transformDirectionViewToLocal = Matrix4.getMatrix3(
+ transformPositionViewToLocal,
+ scratchRtuRotation,
+ );
+ // 4. Multiply to get rotation from eye to RTU coordinates
+ shaderUniforms.cylinderEcToRadialTangentUp = Matrix3.multiply(
+ rotateLocalToRtu,
+ transformDirectionViewToLocal,
+ shaderUniforms.cylinderEcToRadialTangentUp,
+ );
+};
+
+/**
+ * Convert a UV coordinate to the shape's UV space.
+ * @private
+ * @param {Cartesian3} positionLocal The local coordinate to convert.
+ * @param {Cartesian3} result The Cartesian3 to store the result in.
+ * @returns {Cartesian3} The converted UV coordinate.
+ */
+VoxelCylinderShape.prototype.convertLocalToShapeUvSpace = function (
+ positionLocal,
+ result,
+) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("positionLocal", positionLocal);
+ Check.typeOf.object("result", result);
+ //>>includeEnd('debug');
+
+ let radius = Math.hypot(positionLocal.x, positionLocal.y);
+ let angle = Math.atan2(positionLocal.y, positionLocal.x);
+ let height = positionLocal.z;
+
+ const {
+ cylinderLocalToShapeUvRadius,
+ cylinderLocalToShapeUvAngle,
+ cylinderShapeUvAngleRangeOrigin,
+ cylinderLocalToShapeUvHeight,
+ } = this._shaderUniforms;
+
+ radius =
+ radius * cylinderLocalToShapeUvRadius.x + cylinderLocalToShapeUvRadius.y;
+
+ // Convert angle to a "UV" in [0,1] with 0 defined at the center of the unoccupied space.
+ angle = (angle + Math.PI) / (2.0 * Math.PI);
+ angle -= cylinderShapeUvAngleRangeOrigin;
+ angle = angle - Math.floor(angle);
+ // Scale and shift so [0,1] covers the occupied space.
+ angle = angle * cylinderLocalToShapeUvAngle.x + cylinderLocalToShapeUvAngle.y;
+
+ height =
+ height * cylinderLocalToShapeUvHeight.x + cylinderLocalToShapeUvHeight.y;
+
+ return Cartesian3.fromElements(radius, angle, height, result);
+};
+
const scratchMinBounds = new Cartesian3();
const scratchMaxBounds = new Cartesian3();
/**
* Computes an oriented bounding box for a specified tile.
- * The update function must be called before calling this function.
* @private
* @param {number} tileLevel The tile's level.
* @param {number} tileX The tile's x coordinate.
@@ -484,7 +595,7 @@ VoxelCylinderShape.prototype.computeOrientedBoundingBoxForTile = function (
return getCylinderChunkObb(
tileMinBounds,
tileMaxBounds,
- this.shapeTransform,
+ this._shapeTransform,
result,
);
};
@@ -493,7 +604,6 @@ const sampleSizeScratch = new Cartesian3();
/**
* Computes an oriented bounding box for a specified sample within a specified tile.
- * The update function must be called before calling this function.
* @private
* @param {SpatialNode} spatialNode The spatial node containing the sample
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
@@ -557,7 +667,7 @@ VoxelCylinderShape.prototype.computeOrientedBoundingBoxForSample = function (
return getCylinderChunkObb(
sampleMinBounds,
sampleMaxBounds,
- this.shapeTransform,
+ this._shapeTransform,
result,
);
};
@@ -639,6 +749,7 @@ function computeLooseOrientedBoundingBox(matrix, result) {
return OrientedBoundingBox.fromPoints(corners, result);
}
+const scratchBoxScale = new Cartesian3();
/**
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
*
@@ -720,7 +831,7 @@ function getCylinderChunkObb(chunkMinBounds, chunkMaxBounds, matrix, result) {
extentX,
extentY,
extentZ,
- scratchScale,
+ scratchBoxScale,
);
const scaleMatrix = Matrix4.fromScale(scale, scratchScaleMatrix);
diff --git a/packages/engine/Source/Scene/VoxelEllipsoidShape.js b/packages/engine/Source/Scene/VoxelEllipsoidShape.js
index afb26af661db..8babd6f3adef 100644
--- a/packages/engine/Source/Scene/VoxelEllipsoidShape.js
+++ b/packages/engine/Source/Scene/VoxelEllipsoidShape.js
@@ -1,6 +1,8 @@
+import defined from "../Core/defined.js";
import BoundingSphere from "../Core/BoundingSphere.js";
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
+import Cartographic from "../Core/Cartographic.js";
import Check from "../Core/Check.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import CesiumMath from "../Core/Math.js";
@@ -8,6 +10,7 @@ import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
import Rectangle from "../Core/Rectangle.js";
+import Transforms from "../Core/Transforms.js";
/**
* An ellipsoid {@link VoxelShape}.
@@ -23,41 +26,10 @@ import Rectangle from "../Core/Rectangle.js";
* @private
*/
function VoxelEllipsoidShape() {
- /**
- * An oriented bounding box containing the bounded shape.
- * The update function must be called before accessing this value.
- * @type {OrientedBoundingBox}
- * @readonly
- * @private
- */
- this.orientedBoundingBox = new OrientedBoundingBox();
-
- /**
- * A bounding sphere containing the bounded shape.
- * The update function must be called before accessing this value.
- * @type {BoundingSphere}
- * @readonly
- * @private
- */
- this.boundingSphere = new BoundingSphere();
-
- /**
- * A transformation matrix containing the bounded shape.
- * The update function must be called before accessing this value.
- * @type {Matrix4}
- * @readonly
- * @private
- */
- this.boundTransform = new Matrix4();
-
- /**
- * A transformation matrix containing the shape, ignoring the bounds.
- * The update function must be called before accessing this value.
- * @type {Matrix4}
- * @readonly
- * @private
- */
- this.shapeTransform = new Matrix4();
+ this._orientedBoundingBox = new OrientedBoundingBox();
+ this._boundingSphere = new BoundingSphere();
+ this._boundTransform = new Matrix4();
+ this._shapeTransform = new Matrix4();
/**
* @type {Rectangle}
@@ -95,31 +67,25 @@ function VoxelEllipsoidShape() {
*/
this._rotation = new Matrix3();
- /**
- * @type {Object}
- * @readonly
- * @private
- */
- this.shaderUniforms = {
- ellipsoidRadiiUv: new Cartesian3(),
+ this._shaderUniforms = {
+ cameraPositionCartographic: new Cartesian3(),
+ ellipsoidEcToEastNorthUp: new Matrix3(),
+ ellipsoidRadii: new Cartesian3(),
eccentricitySquared: 0.0,
evoluteScale: new Cartesian2(),
- ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
+ ellipsoidCurvatureAtLatitude: new Cartesian2(),
+ ellipsoidInverseRadiiSquared: new Cartesian3(),
ellipsoidRenderLongitudeMinMax: new Cartesian2(),
+ ellipsoidShapeUvLongitudeRangeOrigin: 0.0,
ellipsoidShapeUvLongitudeMinMaxMid: new Cartesian3(),
- ellipsoidUvToShapeUvLongitude: new Cartesian2(),
- ellipsoidUvToShapeUvLatitude: new Cartesian2(),
+ ellipsoidLocalToShapeUvLongitude: new Cartesian2(),
+ ellipsoidLocalToShapeUvLatitude: new Cartesian2(),
ellipsoidRenderLatitudeSinMinMax: new Cartesian2(),
- ellipsoidInverseHeightDifferenceUv: 0.0,
+ ellipsoidInverseHeightDifference: 0.0,
clipMinMaxHeight: new Cartesian2(),
};
- /**
- * @type {Object}
- * @readonly
- * @private
- */
- this.shaderDefines = {
+ this._shaderDefines = {
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF: undefined,
@@ -142,14 +108,103 @@ function VoxelEllipsoidShape() {
ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN: undefined,
};
+ this._shaderMaximumIntersectionsLength = 0; // not known until update
+}
+
+Object.defineProperties(VoxelEllipsoidShape.prototype, {
+ /**
+ * An oriented bounding box containing the bounded shape.
+ *
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {OrientedBoundingBox}
+ * @readonly
+ * @private
+ */
+ orientedBoundingBox: {
+ get: function () {
+ return this._orientedBoundingBox;
+ },
+ },
+
+ /**
+ * A bounding sphere containing the bounded shape.
+ *
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {BoundingSphere}
+ * @readonly
+ * @private
+ */
+ boundingSphere: {
+ get: function () {
+ return this._boundingSphere;
+ },
+ },
+
+ /**
+ * A transformation matrix containing the bounded shape.
+ *
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Matrix4}
+ * @readonly
+ * @private
+ */
+ boundTransform: {
+ get: function () {
+ return this._boundTransform;
+ },
+ },
+
+ /**
+ * A transformation matrix containing the shape, ignoring the bounds.
+ *
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Matrix4}
+ * @readonly
+ * @private
+ */
+ shapeTransform: {
+ get: function () {
+ return this._shapeTransform;
+ },
+ },
+
+ /**
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Object}
+ * @readonly
+ * @private
+ */
+ shaderUniforms: {
+ get: function () {
+ return this._shaderUniforms;
+ },
+ },
+
+ /**
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Object}
+ * @readonly
+ * @private
+ */
+ shaderDefines: {
+ get: function () {
+ return this._shaderDefines;
+ },
+ },
+
/**
* The maximum number of intersections against the shape for any ray direction.
+ * @memberof VoxelEllipsoidShape.prototype
* @type {number}
* @readonly
* @private
*/
- this.shaderMaximumIntersectionsLength = 0; // not known until update
-}
+ shaderMaximumIntersectionsLength: {
+ get: function () {
+ return this._shaderMaximumIntersectionsLength;
+ },
+ },
+});
const scratchActualMinBounds = new Cartesian3();
const scratchShapeMinBounds = new Cartesian3();
@@ -159,7 +214,6 @@ const scratchClipMaxBounds = new Cartesian3();
const scratchRenderMinBounds = new Cartesian3();
const scratchRenderMaxBounds = new Cartesian3();
const scratchScale = new Cartesian3();
-const scratchRotationScale = new Matrix3();
const scratchShapeOuterExtent = new Cartesian3();
const scratchRenderOuterExtent = new Cartesian3();
const scratchRenderRectangle = new Rectangle();
@@ -250,7 +304,6 @@ VoxelEllipsoidShape.prototype.update = function (
),
scratchShapeOuterExtent,
);
- const shapeMaxExtent = Cartesian3.maximumComponent(shapeOuterExtent);
const renderOuterExtent = Cartesian3.add(
radii,
@@ -300,31 +353,31 @@ VoxelEllipsoidShape.prototype.update = function (
scratchRenderRectangle,
);
- this.orientedBoundingBox = getEllipsoidChunkObb(
+ this._orientedBoundingBox = getEllipsoidChunkObb(
renderRectangle,
renderMinBounds.z,
renderMaxBounds.z,
this._ellipsoid,
this._translation,
this._rotation,
- this.orientedBoundingBox,
+ this._orientedBoundingBox,
);
- this.shapeTransform = Matrix4.fromRotationTranslation(
- Matrix3.setScale(this._rotation, shapeOuterExtent, scratchRotationScale),
+ this._shapeTransform = Matrix4.fromRotationTranslation(
+ this._rotation,
this._translation,
- this.shapeTransform,
+ this._shapeTransform,
);
- this.boundTransform = Matrix4.fromRotationTranslation(
- this.orientedBoundingBox.halfAxes,
- this.orientedBoundingBox.center,
- this.boundTransform,
+ this._boundTransform = Matrix4.fromRotationTranslation(
+ this._orientedBoundingBox.halfAxes,
+ this._orientedBoundingBox.center,
+ this._boundTransform,
);
- this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
- this.orientedBoundingBox,
- this.boundingSphere,
+ this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(
+ this._orientedBoundingBox,
+ this._boundingSphere,
);
// Longitude
@@ -415,7 +468,8 @@ VoxelEllipsoidShape.prototype.update = function (
shapeIsLatitudeMinOverHalf;
const shapeHasLatitude = shapeHasLatitudeMax || shapeHasLatitudeMin;
- const { shaderUniforms, shaderDefines } = this;
+ const shaderUniforms = this._shaderUniforms;
+ const shaderDefines = this._shaderDefines;
// To keep things simple, clear the defines every time
for (const key in shaderDefines) {
@@ -424,30 +478,28 @@ VoxelEllipsoidShape.prototype.update = function (
}
}
- // The ellipsoid radii scaled to [0,1]. The max ellipsoid radius will be 1.0 and others will be less.
- shaderUniforms.ellipsoidRadiiUv = Cartesian3.divideByScalar(
+ shaderUniforms.ellipsoidRadii = Cartesian3.clone(
shapeOuterExtent,
- shapeMaxExtent,
- shaderUniforms.ellipsoidRadiiUv,
+ shaderUniforms.ellipsoidRadii,
);
- const { x: radiiUvX, z: radiiUvZ } = shaderUniforms.ellipsoidRadiiUv;
- const axisRatio = radiiUvZ / radiiUvX;
+ const { x: radiiX, z: radiiZ } = shaderUniforms.ellipsoidRadii;
+ const axisRatio = radiiZ / radiiX;
shaderUniforms.eccentricitySquared = 1.0 - axisRatio * axisRatio;
shaderUniforms.evoluteScale = Cartesian2.fromElements(
- (radiiUvX * radiiUvX - radiiUvZ * radiiUvZ) / radiiUvX,
- (radiiUvZ * radiiUvZ - radiiUvX * radiiUvX) / radiiUvZ,
+ (radiiX * radiiX - radiiZ * radiiZ) / radiiX,
+ (radiiZ * radiiZ - radiiX * radiiX) / radiiZ,
shaderUniforms.evoluteScale,
);
// Used to compute geodetic surface normal.
- shaderUniforms.ellipsoidInverseRadiiSquaredUv = Cartesian3.divideComponents(
+ shaderUniforms.ellipsoidInverseRadiiSquared = Cartesian3.divideComponents(
Cartesian3.ONE,
Cartesian3.multiplyComponents(
- shaderUniforms.ellipsoidRadiiUv,
- shaderUniforms.ellipsoidRadiiUv,
- shaderUniforms.ellipsoidInverseRadiiSquaredUv,
+ shaderUniforms.ellipsoidRadii,
+ shaderUniforms.ellipsoidRadii,
+ shaderUniforms.ellipsoidInverseRadiiSquared,
),
- shaderUniforms.ellipsoidInverseRadiiSquaredUv,
+ shaderUniforms.ellipsoidInverseRadiiSquared,
);
// Keep track of how many intersections there are going to be.
@@ -460,16 +512,16 @@ VoxelEllipsoidShape.prototype.update = function (
intersectionCount += 1;
shaderUniforms.clipMinMaxHeight = Cartesian2.fromElements(
- (renderMinBounds.z - shapeMaxBounds.z) / shapeMaxExtent,
- (renderMaxBounds.z - shapeMaxBounds.z) / shapeMaxExtent,
+ renderMinBounds.z - shapeMaxBounds.z,
+ renderMaxBounds.z - shapeMaxBounds.z,
shaderUniforms.clipMinMaxHeight,
);
// The percent of space that is between the inner and outer ellipsoid.
- const thickness = (shapeMaxBounds.z - shapeMinBounds.z) / shapeMaxExtent;
- shaderUniforms.ellipsoidInverseHeightDifferenceUv = 1.0 / thickness;
+ const thickness = shapeMaxBounds.z - shapeMinBounds.z;
+ shaderUniforms.ellipsoidInverseHeightDifference = 1.0 / thickness;
if (shapeMinBounds.z === shapeMaxBounds.z) {
- shaderUniforms.ellipsoidInverseHeightDifferenceUv = 0.0;
+ shaderUniforms.ellipsoidInverseHeightDifference = 0.0;
}
// Intersects a wedge for the min and max longitude.
@@ -501,36 +553,39 @@ VoxelEllipsoidShape.prototype.update = function (
if (shapeHasLongitude) {
shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE"] = true;
- const shapeIsLongitudeReversed = shapeMaxBounds.x < shapeMinBounds.x;
+ const uvShapeMinLongitude =
+ (shapeMinBounds.x - DefaultMinBounds.x) / defaultLongitudeRange;
+ const uvShapeMaxLongitude =
+ (shapeMaxBounds.x - DefaultMinBounds.x) / defaultLongitudeRange;
+ const uvLongitudeRangeZero =
+ 1.0 - shapeLongitudeRange / defaultLongitudeRange;
+ // Translate the origin of UV angles (in [0,1]) to the center of the unoccupied space
+ const uvLongitudeRangeOrigin =
+ (uvShapeMaxLongitude + 0.5 * uvLongitudeRangeZero) % 1.0;
+ shaderUniforms.ellipsoidShapeUvLongitudeRangeOrigin =
+ uvLongitudeRangeOrigin;
+ const shapeIsLongitudeReversed = shapeMaxBounds.x < shapeMinBounds.x;
if (shapeIsLongitudeReversed) {
shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED"] =
true;
}
- // delerp(longitudeUv, minLongitudeUv, maxLongitudeUv)
- // (longitudeUv - minLongitudeUv) / (maxLongitudeUv - minLongitudeUv)
- // longitudeUv / (maxLongitudeUv - minLongitudeUv) - minLongitudeUv / (maxLongitudeUv - minLongitudeUv)
- // scale = 1.0 / (maxLongitudeUv - minLongitudeUv)
- // scale = 1.0 / (((maxLongitude - pi) / (2.0 * pi)) - ((minLongitude - pi) / (2.0 * pi)))
- // scale = 2.0 * pi / (maxLongitude - minLongitude)
- // offset = -minLongitudeUv / (maxLongitudeUv - minLongitudeUv)
- // offset = -((minLongitude - pi) / (2.0 * pi)) / (((maxLongitude - pi) / (2.0 * pi)) - ((minLongitude - pi) / (2.0 * pi)))
- // offset = -(minLongitude - pi) / (maxLongitude - minLongitude)
if (shapeLongitudeRange <= epsilonLongitude) {
- shaderUniforms.ellipsoidUvToShapeUvLongitude = Cartesian2.fromElements(
+ shaderUniforms.ellipsoidLocalToShapeUvLongitude = Cartesian2.fromElements(
0.0,
1.0,
- shaderUniforms.ellipsoidUvToShapeUvLongitude,
+ shaderUniforms.ellipsoidLocalToShapeUvLongitude,
);
} else {
const scale = defaultLongitudeRange / shapeLongitudeRange;
+ const shiftedMinLongitude = uvShapeMinLongitude - uvLongitudeRangeOrigin;
const offset =
- -(shapeMinBounds.x - DefaultMinBounds.x) / shapeLongitudeRange;
- shaderUniforms.ellipsoidUvToShapeUvLongitude = Cartesian2.fromElements(
+ -scale * (shiftedMinLongitude - Math.floor(shiftedMinLongitude));
+ shaderUniforms.ellipsoidLocalToShapeUvLongitude = Cartesian2.fromElements(
scale,
offset,
- shaderUniforms.ellipsoidUvToShapeUvLongitude,
+ shaderUniforms.ellipsoidLocalToShapeUvLongitude,
);
}
}
@@ -630,45 +685,92 @@ VoxelEllipsoidShape.prototype.update = function (
if (shapeHasLatitude) {
shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE"] = true;
- // delerp(latitudeUv, minLatitudeUv, maxLatitudeUv)
- // (latitudeUv - minLatitudeUv) / (maxLatitudeUv - minLatitudeUv)
- // latitudeUv / (maxLatitudeUv - minLatitudeUv) - minLatitudeUv / (maxLatitudeUv - minLatitudeUv)
- // scale = 1.0 / (maxLatitudeUv - minLatitudeUv)
- // scale = 1.0 / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
- // scale = 2.0 * pi / (maxLatitude - minLatitude)
- // offset = -minLatitudeUv / (maxLatitudeUv - minLatitudeUv)
- // offset = -((minLatitude - -pi) / (2.0 * pi)) / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
- // offset = -(minLatitude - -pi) / (maxLatitude - minLatitude)
- // offset = (-pi - minLatitude) / (maxLatitude - minLatitude)
if (shapeLatitudeRange < epsilonLatitude) {
- shaderUniforms.ellipsoidUvToShapeUvLatitude = Cartesian2.fromElements(
+ shaderUniforms.ellipsoidLocalToShapeUvLatitude = Cartesian2.fromElements(
0.0,
1.0,
- shaderUniforms.ellipsoidUvToShapeUvLatitude,
+ shaderUniforms.ellipsoidLocalToShapeUvLatitude,
);
} else {
const defaultLatitudeRange = DefaultMaxBounds.y - DefaultMinBounds.y;
const scale = defaultLatitudeRange / shapeLatitudeRange;
const offset =
(DefaultMinBounds.y - shapeMinBounds.y) / shapeLatitudeRange;
- shaderUniforms.ellipsoidUvToShapeUvLatitude = Cartesian2.fromElements(
+ shaderUniforms.ellipsoidLocalToShapeUvLatitude = Cartesian2.fromElements(
scale,
offset,
- shaderUniforms.ellipsoidUvToShapeUvLatitude,
+ shaderUniforms.ellipsoidLocalToShapeUvLatitude,
);
}
}
- this.shaderMaximumIntersectionsLength = intersectionCount;
+ this._shaderMaximumIntersectionsLength = intersectionCount;
return true;
};
+const scratchCameraPositionCartographic = new Cartographic();
+const surfacePositionScratch = new Cartesian3();
+const enuTransformScratch = new Matrix4();
+const enuRotationScratch = new Matrix3();
+/**
+ * Update any view-dependent transforms.
+ * @private
+ * @param {FrameState} frameState The frame state.
+ */
+VoxelEllipsoidShape.prototype.updateViewTransforms = function (frameState) {
+ const shaderUniforms = this._shaderUniforms;
+ const ellipsoid = this._ellipsoid;
+ // TODO: incorporate modelMatrix or shapeTransform here?
+ const cameraWC = frameState.camera.positionWC;
+ const cameraPositionCartographic = ellipsoid.cartesianToCartographic(
+ cameraWC,
+ scratchCameraPositionCartographic,
+ );
+ Cartesian3.fromElements(
+ cameraPositionCartographic.longitude,
+ cameraPositionCartographic.latitude,
+ cameraPositionCartographic.height,
+ shaderUniforms.cameraPositionCartographic,
+ );
+
+ // TODO: incorporate modelMatrix here?
+ const surfacePosition = Cartesian3.fromRadians(
+ cameraPositionCartographic.longitude,
+ cameraPositionCartographic.latitude,
+ 0.0,
+ ellipsoid,
+ surfacePositionScratch,
+ );
+
+ shaderUniforms.ellipsoidCurvatureAtLatitude = ellipsoid.getLocalCurvature(
+ surfacePosition,
+ shaderUniforms.ellipsoidCurvatureAtLatitude,
+ );
+
+ const enuToWorld = Transforms.eastNorthUpToFixedFrame(
+ surfacePosition,
+ ellipsoid,
+ enuTransformScratch,
+ );
+ const rotateEnuToWorld = Matrix4.getRotation(enuToWorld, enuRotationScratch);
+ const rotateWorldToView = frameState.context.uniformState.viewRotation;
+ const rotateEnuToView = Matrix3.multiply(
+ rotateWorldToView,
+ rotateEnuToWorld,
+ enuRotationScratch,
+ );
+ // Inverse is the transpose since it's a pure rotation.
+ shaderUniforms.ellipsoidEcToEastNorthUp = Matrix3.transpose(
+ rotateEnuToView,
+ shaderUniforms.ellipsoidEcToEastNorthUp,
+ );
+};
+
const scratchRectangle = new Rectangle();
/**
* Computes an oriented bounding box for a specified tile.
- * The update function must be called before calling this function.
* @private
* @param {number} tileLevel The tile's level.
* @param {number} tileX The tile's x coordinate.
@@ -732,13 +834,194 @@ VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForTile = function (
);
};
+const scratchQuadrantPosition = new Cartesian2();
+const scratchInverseRadii = new Cartesian2();
+const scratchEllipseTrigs = new Cartesian2();
+const scratchEllipseGuess = new Cartesian2();
+const scratchEvolute = new Cartesian2();
+const scratchQ = new Cartesian2();
+
+/**
+ * Find the nearest point on an ellipse and its radius.
+ * @param {Cartesian2} position
+ * @param {Cartesian2} radii
+ * @param {Cartesian2} evoluteScale
+ * @param {Cartesian3} result The Cartesian3 to store the result in. .x and .y components contain the nearest point on the ellipse, .z contains the local radius of curvature.
+ * @returns {Cartesian3} The nearest point on the ellipse and its radius.
+ * @private
+ */
+function nearestPointAndRadiusOnEllipse(position, radii, evoluteScale, result) {
+ // Map to the first quadrant
+ const p = Cartesian2.abs(position, scratchQuadrantPosition);
+ const inverseRadii = Cartesian2.fromElements(
+ 1.0 / radii.x,
+ 1.0 / radii.y,
+ scratchInverseRadii,
+ );
+ // We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t))
+ // but store the cos and sin of t in a vec2 for efficiency.
+ // Initial guess: t = pi/4
+ let tTrigs = Cartesian2.fromElements(
+ Math.SQRT1_2,
+ Math.SQRT1_2,
+ scratchEllipseTrigs,
+ );
+ // TODO: too much duplication. Move v and evolute declarations inside loop?
+ // Initial guess of point on ellipsoid
+ let v = Cartesian2.multiplyComponents(radii, tTrigs, scratchEllipseGuess);
+ // Center of curvature of the ellipse at v
+ let evolute = Cartesian2.fromElements(
+ evoluteScale.x * tTrigs.x * tTrigs.x * tTrigs.x,
+ evoluteScale.y * tTrigs.y * tTrigs.y * tTrigs.y,
+ scratchEvolute,
+ );
+ for (let i = 0; i < 3; ++i) {
+ // Find the (approximate) intersection of p - evolute with the ellipsoid.
+ const distance = Cartesian2.magnitude(
+ Cartesian2.subtract(v, evolute, scratchQ),
+ );
+ const direction = Cartesian2.normalize(
+ Cartesian2.subtract(p, evolute, scratchQ),
+ scratchQ,
+ );
+ const q = Cartesian2.multiplyByScalar(direction, distance, scratchQ);
+ // Update the estimate of t
+ tTrigs = Cartesian2.multiplyComponents(
+ Cartesian2.add(q, evolute, scratchEllipseTrigs),
+ inverseRadii,
+ scratchEllipseTrigs,
+ );
+ tTrigs = Cartesian2.normalize(
+ Cartesian2.clamp(
+ tTrigs,
+ Cartesian2.ZERO,
+ Cartesian2.ONE,
+ scratchEllipseTrigs,
+ ),
+ scratchEllipseTrigs,
+ );
+ v = Cartesian2.multiplyComponents(radii, tTrigs, scratchEllipseGuess);
+ evolute = Cartesian2.fromElements(
+ evoluteScale.x * tTrigs.x * tTrigs.x * tTrigs.x,
+ evoluteScale.y * tTrigs.y * tTrigs.y * tTrigs.y,
+ scratchEvolute,
+ );
+ }
+
+ // Map back to the original quadrant
+ return Cartesian3.fromElements(
+ Math.sign(position.x) * v.x,
+ Math.sign(position.y) * v.y,
+ Cartesian2.magnitude(Cartesian2.subtract(v, evolute, scratchQ)),
+ result,
+ );
+}
+
+const scratchEllipseRadii = new Cartesian2();
+const scratchEllipsePosition = new Cartesian2();
+const scratchSurfacePointAndRadius = new Cartesian3();
+const scratchNormal2d = new Cartesian2();
+/**
+ * Convert a UV coordinate to the shape's UV space.
+ * @private
+ * @param {Cartesian3} positionLocal The local position to convert.
+ * @param {Cartesian3} result The Cartesian3 to store the result in.
+ * @returns {Cartesian3} The converted UV coordinate.
+ */
+VoxelEllipsoidShape.prototype.convertLocalToShapeUvSpace = function (
+ positionLocal,
+ result,
+) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("positionLocal", positionLocal);
+ Check.typeOf.object("result", result);
+ //>>includeEnd('debug');
+
+ let longitude = Math.atan2(positionLocal.y, positionLocal.x);
+
+ const {
+ ellipsoidRadii,
+ evoluteScale,
+ ellipsoidInverseRadiiSquared,
+ ellipsoidInverseHeightDifference,
+ ellipsoidShapeUvLongitudeRangeOrigin,
+ ellipsoidLocalToShapeUvLongitude,
+ ellipsoidLocalToShapeUvLatitude,
+ } = this._shaderUniforms;
+
+ const distanceFromZAxis = Math.hypot(positionLocal.x, positionLocal.y);
+ const posEllipse = Cartesian2.fromElements(
+ distanceFromZAxis,
+ positionLocal.z,
+ scratchEllipsePosition,
+ );
+ const surfacePointAndRadius = nearestPointAndRadiusOnEllipse(
+ posEllipse,
+ Cartesian2.fromElements(
+ ellipsoidRadii.x,
+ ellipsoidRadii.z,
+ scratchEllipseRadii,
+ ),
+ evoluteScale,
+ scratchSurfacePointAndRadius,
+ );
+
+ const normal2d = Cartesian2.normalize(
+ Cartesian2.fromElements(
+ surfacePointAndRadius.x * ellipsoidInverseRadiiSquared.x,
+ surfacePointAndRadius.y * ellipsoidInverseRadiiSquared.z,
+ scratchNormal2d,
+ ),
+ scratchNormal2d,
+ );
+ let latitude = Math.atan2(normal2d.y, normal2d.x);
+
+ const heightSign =
+ Cartesian2.magnitude(posEllipse) <
+ Cartesian2.magnitude(surfacePointAndRadius)
+ ? -1.0
+ : 1.0;
+ const heightVector = Cartesian2.subtract(
+ posEllipse,
+ surfacePointAndRadius,
+ scratchEllipsePosition,
+ );
+ let height = heightSign * Cartesian2.magnitude(heightVector);
+
+ const {
+ ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE,
+ ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE,
+ } = this._shaderDefines;
+
+ longitude = (longitude + Math.PI) / (2.0 * Math.PI);
+ if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)) {
+ longitude -= ellipsoidShapeUvLongitudeRangeOrigin;
+ longitude = longitude - Math.floor(longitude);
+ // Scale and shift so [0, 1] covers the occupied space.
+ longitude =
+ longitude * ellipsoidLocalToShapeUvLongitude.x +
+ ellipsoidLocalToShapeUvLongitude.y;
+ }
+
+ latitude = (latitude + Math.PI / 2.0) / Math.PI;
+ if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)) {
+ // Scale and shift so [0, 1] covers the occupied space.
+ latitude =
+ latitude * ellipsoidLocalToShapeUvLatitude.x +
+ ellipsoidLocalToShapeUvLatitude.y;
+ }
+
+ height = 1.0 + height * ellipsoidInverseHeightDifference;
+
+ return Cartesian3.fromElements(longitude, latitude, height, result);
+};
+
const sampleSizeScratch = new Cartesian3();
const scratchTileMinBounds = new Cartesian3();
const scratchTileMaxBounds = new Cartesian3();
/**
* Computes an oriented bounding box for a specified sample within a specified tile.
- * The update function must be called before calling this function.
* @private
* @param {SpatialNode} spatialNode The spatial node containing the sample
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js
index c1b7e08b2aa7..1643229602a0 100644
--- a/packages/engine/Source/Scene/VoxelPrimitive.js
+++ b/packages/engine/Source/Scene/VoxelPrimitive.js
@@ -130,6 +130,14 @@ function VoxelPrimitive(options) {
*/
this._paddingAfter = new Cartesian3();
+ /**
+ * This member is not known until the provider is ready.
+ *
+ * @type {number}
+ * @private
+ */
+ this._availableLevels = 1;
+
/**
* This member is not known until the provider is ready.
*
@@ -332,24 +340,24 @@ function VoxelPrimitive(options) {
this._clock = options.clock;
// Transforms and other values that are computed when the shape changes
-
/**
* @type {Matrix4}
* @private
*/
- this._transformPositionWorldToUv = new Matrix4();
+ this._transformPositionLocalToWorld = new Matrix4();
/**
- * @type {Matrix3}
+ * @type {Matrix4}
* @private
*/
- this._transformDirectionWorldToUv = new Matrix3();
+ this._transformPositionWorldToLocal = new Matrix4();
/**
+ * Transforms a plane in Hessian normal form from local space to view space.
* @type {Matrix4}
* @private
*/
- this._transformPositionUvToWorld = new Matrix4();
+ this._transformPlaneLocalToView = new Matrix4();
/**
* @type {Matrix3}
@@ -440,14 +448,16 @@ function VoxelPrimitive(options) {
inputDimensions: new Cartesian3(),
paddingBefore: new Cartesian3(),
paddingAfter: new Cartesian3(),
- transformPositionViewToUv: new Matrix4(),
- transformPositionUvToView: new Matrix4(),
+ transformPositionViewToLocal: new Matrix4(),
transformDirectionViewToLocal: new Matrix3(),
- cameraPositionUv: new Cartesian3(),
- cameraDirectionUv: new Cartesian3(),
+ cameraPositionLocal: new Cartesian3(),
+ cameraDirectionLocal: new Cartesian3(),
+ cameraTileCoordinates: new Cartesian4(),
+ cameraTileUv: new Cartesian3(),
ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
clippingPlanesTexture: undefined,
clippingPlanesMatrix: new Matrix4(),
+ renderBoundPlanesTexture: undefined,
stepSize: 0,
pickColor: new Color(),
};
@@ -631,11 +641,7 @@ function initialize(primitive, provider) {
// Create the shape object, and update it so it is valid for VoxelTraversal
const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
primitive._shape = new ShapeConstructor();
- primitive._shapeVisible = updateShapeAndTransforms(
- primitive,
- primitive._shape,
- provider,
- );
+ primitive._shapeVisible = updateShapeAndTransforms(primitive);
}
Object.defineProperties(VoxelPrimitive.prototype, {
@@ -1137,20 +1143,10 @@ Object.defineProperties(VoxelPrimitive.prototype, {
const scratchIntersect = new Cartesian4();
const scratchNdcAabb = new Cartesian4();
-const scratchTransformPositionWorldToLocal = new Matrix4();
const scratchTransformPositionLocalToWorld = new Matrix4();
const scratchTransformPositionLocalToProjection = new Matrix4();
-
-const transformPositionLocalToUv = Matrix4.fromRotationTranslation(
- Matrix3.fromUniformScale(0.5, new Matrix3()),
- new Cartesian3(0.5, 0.5, 0.5),
- new Matrix4(),
-);
-const transformPositionUvToLocal = Matrix4.fromRotationTranslation(
- Matrix3.fromUniformScale(2.0, new Matrix3()),
- new Cartesian3(-1.0, -1.0, -1.0),
- new Matrix4(),
-);
+const scratchCameraPositionShapeUv = new Cartesian3();
+const scratchCameraTileCoordinates = new Cartesian4();
/**
* Updates the voxel primitive.
@@ -1160,6 +1156,7 @@ const transformPositionUvToLocal = Matrix4.fromRotationTranslation(
*/
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);
@@ -1185,10 +1182,9 @@ VoxelPrimitive.prototype.update = function (frameState) {
// frame because the member variables can be modified externally via the
// getters.
const shapeDirty = checkTransformAndBounds(this, provider);
- const shape = this._shape;
if (shapeDirty) {
- this._shapeVisible = updateShapeAndTransforms(this, shape, provider);
- if (checkShapeDefines(this, shape)) {
+ this._shapeVisible = updateShapeAndTransforms(this);
+ if (checkShapeDefines(this)) {
this._shaderDirty = true;
}
}
@@ -1196,6 +1192,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
return;
}
+ this._shape.updateViewTransforms(frameState);
+
// Update the traversal and prepare for rendering.
const keyframeLocation = getKeyframeLocation(
provider.timeIntervalCollection,
@@ -1243,7 +1241,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
const leafNodeTexture = traversal.leafNodeTexture;
- const uniforms = this._uniforms;
if (defined(leafNodeTexture)) {
uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
uniforms.octreeLeafNodeTexelSizeUv = Cartesian2.clone(
@@ -1262,7 +1259,7 @@ VoxelPrimitive.prototype.update = function (frameState) {
// Calculate the NDC-space AABB to "scissor" the fullscreen quad
const transformPositionWorldToProjection =
context.uniformState.viewProjection;
- const orientedBoundingBox = shape.orientedBoundingBox;
+ const { orientedBoundingBox } = this._shape;
const ndcAabb = orientedBoundingBoxToNdcAabb(
orientedBoundingBox,
transformPositionWorldToProjection,
@@ -1286,17 +1283,17 @@ VoxelPrimitive.prototype.update = function (frameState) {
uniforms.ndcSpaceAxisAlignedBoundingBox,
);
const transformPositionViewToWorld = context.uniformState.inverseView;
- uniforms.transformPositionViewToUv = Matrix4.multiplyTransformation(
- this._transformPositionWorldToUv,
+ const transformPositionViewToLocal = Matrix4.multiplyTransformation(
+ this._transformPositionWorldToLocal,
transformPositionViewToWorld,
- uniforms.transformPositionViewToUv,
+ uniforms.transformPositionViewToLocal,
);
- const transformPositionWorldToView = context.uniformState.view;
- uniforms.transformPositionUvToView = Matrix4.multiplyTransformation(
- transformPositionWorldToView,
- this._transformPositionUvToWorld,
- uniforms.transformPositionUvToView,
+
+ this._transformPlaneLocalToView = Matrix4.transpose(
+ transformPositionViewToLocal,
+ this._transformPlaneLocalToView,
);
+
const transformDirectionViewToWorld =
context.uniformState.inverseViewRotation;
uniforms.transformDirectionViewToLocal = Matrix3.multiply(
@@ -1304,32 +1301,85 @@ VoxelPrimitive.prototype.update = function (frameState) {
transformDirectionViewToWorld,
uniforms.transformDirectionViewToLocal,
);
- uniforms.cameraPositionUv = Matrix4.multiplyByPoint(
- this._transformPositionWorldToUv,
+ uniforms.cameraPositionLocal = Matrix4.multiplyByPoint(
+ this._transformPositionWorldToLocal,
frameState.camera.positionWC,
- uniforms.cameraPositionUv,
+ uniforms.cameraPositionLocal,
);
- uniforms.cameraDirectionUv = Matrix3.multiplyByVector(
- this._transformDirectionWorldToUv,
+ uniforms.cameraDirectionLocal = Matrix3.multiplyByVector(
+ this._transformDirectionWorldToLocal,
frameState.camera.directionWC,
- uniforms.cameraDirectionUv,
+ 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.cameraDirectionUv = Cartesian3.normalize(
- uniforms.cameraDirectionUv,
- uniforms.cameraDirectionUv,
+ 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;
+ updateRenderBoundPlanes(this, frameState);
+
// Render the primitive
const command = frameState.passes.pick
? this._drawCommandPick
: frameState.passes.pickVoxel
? this._drawCommandPickVoxel
: this._drawCommand;
- command.boundingVolume = shape.boundingSphere;
+ command.boundingVolume = this._shape.boundingSphere;
frameState.commandList.push(command);
};
+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();
@@ -1337,7 +1387,6 @@ const scratchExaggerationTranslation = new Cartesian3();
/**
* Update the exaggerated bounds of a primitive to account for vertical exaggeration
- * Currently only applies to Ellipsoid shape type
* @param {VoxelPrimitive} primitive
* @param {FrameState} frameState
* @private
@@ -1513,6 +1562,7 @@ function initFromProvider(primitive, provider, context) {
primitive._inputDimensions,
uniforms.inputDimensions,
);
+ primitive._availableLevels = provider.availableLevels ?? 1;
// Create the VoxelTraversal, and set related uniforms
const keyframeCount = provider.keyframeCount ?? 1;
@@ -1586,12 +1636,11 @@ function updateBound(primitive, newBoundKey, oldBoundKey) {
/**
* Update the shape and related transforms
* @param {VoxelPrimitive} primitive
- * @param {VoxelShape} shape
- * @param {VoxelProvider} provider
* @returns {boolean} True if the shape is visible
* @private
*/
-function updateShapeAndTransforms(primitive, shape, provider) {
+function updateShapeAndTransforms(primitive) {
+ const shape = primitive._shape;
const visible = shape.update(
primitive._compoundModelMatrix,
primitive._exaggeratedMinBounds,
@@ -1603,29 +1652,16 @@ function updateShapeAndTransforms(primitive, shape, provider) {
return false;
}
- const transformPositionLocalToWorld = shape.shapeTransform;
- const transformPositionWorldToLocal = Matrix4.inverse(
- transformPositionLocalToWorld,
- scratchTransformPositionWorldToLocal,
- );
-
- // Set member variables when the shape is dirty
- primitive._transformPositionWorldToUv = Matrix4.multiplyTransformation(
- transformPositionLocalToUv,
- transformPositionWorldToLocal,
- primitive._transformPositionWorldToUv,
+ primitive._transformPositionLocalToWorld = Matrix4.clone(
+ shape.shapeTransform,
+ primitive._transformPositionLocalToWorld,
);
- primitive._transformDirectionWorldToUv = Matrix4.getMatrix3(
- primitive._transformPositionWorldToUv,
- primitive._transformDirectionWorldToUv,
- );
- primitive._transformPositionUvToWorld = Matrix4.multiplyTransformation(
- transformPositionLocalToWorld,
- transformPositionUvToLocal,
- primitive._transformPositionUvToWorld,
+ primitive._transformPositionWorldToLocal = Matrix4.inverse(
+ primitive._transformPositionLocalToWorld,
+ primitive._transformPositionWorldToLocal,
);
primitive._transformDirectionWorldToLocal = Matrix4.getMatrix3(
- transformPositionWorldToLocal,
+ primitive._transformPositionWorldToLocal,
primitive._transformDirectionWorldToLocal,
);
@@ -1679,17 +1715,16 @@ function setTraversalUniforms(traversal, uniforms) {
/**
* Track changes in shape-related shader defines
* @param {VoxelPrimitive} primitive
- * @param {VoxelShape} shape
* @returns {boolean} True if any of the shape defines changed, requiring a shader rebuild
* @private
*/
-function checkShapeDefines(primitive, shape) {
- const shapeDefines = shape.shaderDefines;
- const shapeDefinesChanged = Object.keys(shapeDefines).some(
- (key) => shapeDefines[key] !== primitive._shapeDefinesOld[key],
+function checkShapeDefines(primitive) {
+ const { shaderDefines } = primitive._shape;
+ const shapeDefinesChanged = Object.keys(shaderDefines).some(
+ (key) => shaderDefines[key] !== primitive._shapeDefinesOld[key],
);
if (shapeDefinesChanged) {
- primitive._shapeDefinesOld = clone(shapeDefines, true);
+ primitive._shapeDefinesOld = clone(shaderDefines, true);
}
return shapeDefinesChanged;
}
@@ -1761,12 +1796,12 @@ function updateClippingPlanes(primitive, frameState) {
const uniforms = primitive._uniforms;
uniforms.clippingPlanesTexture = clippingPlanes.texture;
- // Compute the clipping plane's transformation to uv space and then take the inverse
+ // 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(worldToUv * clippingPlaneLocalToWorld))
- // transpose(inverse(clippingPlaneLocalToWorld) * inverse(worldToUv))
- // transpose(inverse(clippingPlaneLocalToWorld) * uvToWorld)
+ // transpose(inverse(worldToLocal * clippingPlaneLocalToWorld))
+ // transpose(inverse(clippingPlaneLocalToWorld) * inverse(worldToLocal))
+ // transpose(inverse(clippingPlaneLocalToWorld) * localToWorld)
uniforms.clippingPlanesMatrix = Matrix4.transpose(
Matrix4.multiplyTransformation(
@@ -1774,7 +1809,7 @@ function updateClippingPlanes(primitive, frameState) {
clippingPlanes.modelMatrix,
uniforms.clippingPlanesMatrix,
),
- primitive._transformPositionUvToWorld,
+ primitive._transformPositionLocalToWorld,
uniforms.clippingPlanesMatrix,
),
uniforms.clippingPlanesMatrix,
diff --git a/packages/engine/Source/Scene/VoxelRenderResources.js b/packages/engine/Source/Scene/VoxelRenderResources.js
index f501f715d19b..ab7b6f46308c 100644
--- a/packages/engine/Source/Scene/VoxelRenderResources.js
+++ b/packages/engine/Source/Scene/VoxelRenderResources.js
@@ -8,15 +8,15 @@ import VoxelFS from "../Shaders/Voxels/VoxelFS.js";
import VoxelVS from "../Shaders/Voxels/VoxelVS.js";
import IntersectionUtils from "../Shaders/Voxels/IntersectionUtils.js";
import IntersectDepth from "../Shaders/Voxels/IntersectDepth.js";
-import IntersectClippingPlanes from "../Shaders/Voxels/IntersectClippingPlanes.js";
+import IntersectPlane from "../Shaders/Voxels/IntersectPlane.js";
import IntersectLongitude from "../Shaders/Voxels/IntersectLongitude.js";
import IntersectBox from "../Shaders/Voxels/IntersectBox.js";
import IntersectCylinder from "../Shaders/Voxels/IntersectCylinder.js";
import IntersectEllipsoid from "../Shaders/Voxels/IntersectEllipsoid.js";
import Intersection from "../Shaders/Voxels/Intersection.js";
-import convertUvToBox from "../Shaders/Voxels/convertUvToBox.js";
-import convertUvToCylinder from "../Shaders/Voxels/convertUvToCylinder.js";
-import convertUvToEllipsoid from "../Shaders/Voxels/convertUvToEllipsoid.js";
+import convertLocalToBoxUv from "../Shaders/Voxels/convertLocalToBoxUv.js";
+import convertLocalToCylinderUv from "../Shaders/Voxels/convertLocalToCylinderUv.js";
+import convertLocalToEllipsoidUv from "../Shaders/Voxels/convertLocalToEllipsoidUv.js";
import Octree from "../Shaders/Voxels/Octree.js";
import Megatexture from "../Shaders/Voxels/Megatexture.js";
import VoxelMetadataOrder from "./VoxelMetadataOrder.js";
@@ -84,6 +84,12 @@ function VoxelRenderResources(primitive) {
this.clippingPlanes = clippingPlanes;
this.clippingPlanesLength = clippingPlanesLength;
+ const renderBoundPlanes = primitive._shape.renderBoundPlanes;
+ const renderBoundPlanesLength = renderBoundPlanes?.length ?? 0;
+
+ this.renderBoundPlanes = renderBoundPlanes;
+ this.renderBoundPlanesLength = renderBoundPlanesLength;
+
// Build shader
shaderBuilder.addVertexLines([VoxelVS]);
@@ -116,8 +122,10 @@ function VoxelRenderResources(primitive) {
"#line 0",
Octree,
VoxelUtils,
- IntersectionUtils,
Megatexture,
+ IntersectionUtils,
+ IntersectPlane,
+ IntersectDepth,
]);
if (clippingPlanesLength > 0) {
@@ -138,9 +146,8 @@ function VoxelRenderResources(primitive) {
ShaderDestination.FRAGMENT,
);
}
- shaderBuilder.addFragmentLines([IntersectClippingPlanes]);
}
- shaderBuilder.addFragmentLines([IntersectDepth]);
+
if (primitive._depthTest) {
shaderBuilder.addDefine(
"DEPTH_TEST",
@@ -151,20 +158,20 @@ function VoxelRenderResources(primitive) {
if (shapeType === "BOX") {
shaderBuilder.addFragmentLines([
- convertUvToBox,
+ convertLocalToBoxUv,
IntersectBox,
Intersection,
]);
} else if (shapeType === "CYLINDER") {
shaderBuilder.addFragmentLines([
- convertUvToCylinder,
+ convertLocalToCylinderUv,
IntersectLongitude,
IntersectCylinder,
Intersection,
]);
} else if (shapeType === "ELLIPSOID") {
shaderBuilder.addFragmentLines([
- convertUvToEllipsoid,
+ convertLocalToEllipsoidUv,
IntersectLongitude,
IntersectEllipsoid,
Intersection,
diff --git a/packages/engine/Source/Scene/VoxelShape.js b/packages/engine/Source/Scene/VoxelShape.js
index 14f647dbc1c7..7947fa7d5720 100644
--- a/packages/engine/Source/Scene/VoxelShape.js
+++ b/packages/engine/Source/Scene/VoxelShape.js
@@ -21,7 +21,6 @@ function VoxelShape() {
Object.defineProperties(VoxelShape.prototype, {
/**
* An oriented bounding box containing the bounded shape.
- * The update function must be called before accessing this value.
*
* @memberof VoxelShape.prototype
* @type {OrientedBoundingBox}
@@ -34,7 +33,6 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* A bounding sphere containing the bounded shape.
- * The update function must be called before accessing this value.
*
* @memberof VoxelShape.prototype
* @type {BoundingSphere}
@@ -47,7 +45,6 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* A transformation matrix containing the bounded shape.
- * The update function must be called before accessing this value.
*
* @memberof VoxelShape.prototype
* @type {Matrix4}
@@ -60,7 +57,6 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* A transformation matrix containing the shape, ignoring the bounds.
- * The update function must be called before accessing this value.
*
* @memberof VoxelShape.prototype
* @type {Matrix4}
@@ -72,6 +68,7 @@ Object.defineProperties(VoxelShape.prototype, {
},
/**
+ * @memberof VoxelShape.prototype
* @type {Object}
* @readonly
* @private
@@ -81,6 +78,7 @@ Object.defineProperties(VoxelShape.prototype, {
},
/**
+ * @memberof VoxelShape.prototype
* @type {Object}
* @readonly
* @private
@@ -91,6 +89,7 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* The maximum number of intersections against the shape for any ray direction.
+ * @memberof VoxelShape.prototype
* @type {number}
* @readonly
* @private
@@ -110,9 +109,26 @@ Object.defineProperties(VoxelShape.prototype, {
*/
VoxelShape.prototype.update = DeveloperError.throwInstantiationError;
+/**
+ * Update any view-dependent transforms.
+ * @private
+ * @param {FrameState} frameState The frame state.
+ */
+VoxelShape.prototype.updateViewTransforms =
+ DeveloperError.throwInstantiationError;
+
+/**
+ * Converts a local coordinate to the shape's UV space.
+ * @private
+ * @param {Cartesian3} positionLocal The local coordinate to convert.
+ * @param {Cartesian3} result The Cartesian3 to store the result in.
+ * @returns {Cartesian3} The converted UV coordinate.
+ */
+VoxelShape.prototype.convertLocalToShapeUvSpace =
+ DeveloperError.throwInstantiationError;
+
/**
* Computes an oriented bounding box for a specified tile.
- * The update function must be called before calling this function.
* @private
* @param {number} tileLevel The tile's level.
* @param {number} tileX The tile's x coordinate.
@@ -126,7 +142,6 @@ VoxelShape.prototype.computeOrientedBoundingBoxForTile =
/**
* Computes an oriented bounding box for a specified sample within a specified tile.
- * The update function must be called before calling this function.
* @private
* @param {SpatialNode} spatialNode The spatial node containing the sample
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
diff --git a/packages/engine/Source/Scene/buildVoxelDrawCommands.js b/packages/engine/Source/Scene/buildVoxelDrawCommands.js
index 19f67111ccca..dbcef201e93a 100644
--- a/packages/engine/Source/Scene/buildVoxelDrawCommands.js
+++ b/packages/engine/Source/Scene/buildVoxelDrawCommands.js
@@ -1,14 +1,18 @@
-import defined from "../Core/defined.js";
-import PrimitiveType from "../Core/PrimitiveType.js";
import BlendingState from "./BlendingState.js";
+import Cartesian2 from "../Core/Cartesian2.js";
+import ClippingPlaneCollection from "./ClippingPlaneCollection.js";
import CullFace from "./CullFace.js";
-import getClippingFunction from "./getClippingFunction.js";
+import defined from "../Core/defined.js";
import DrawCommand from "../Renderer/DrawCommand.js";
import Pass from "../Renderer/Pass.js";
+import PrimitiveType from "../Core/PrimitiveType.js";
+import processVoxelProperties from "./processVoxelProperties.js";
import RenderState from "../Renderer/RenderState.js";
import ShaderDestination from "../Renderer/ShaderDestination.js";
+import VoxelBoundsCollection from "./VoxelBoundsCollection.js";
import VoxelRenderResources from "./VoxelRenderResources.js";
-import processVoxelProperties from "./processVoxelProperties.js";
+
+const textureResolutionScratch = new Cartesian2();
/**
* @function
@@ -23,27 +27,40 @@ function buildVoxelDrawCommands(primitive, context) {
processVoxelProperties(renderResources, primitive);
- const { shaderBuilder, clippingPlanes, clippingPlanesLength } =
- renderResources;
+ const {
+ shaderBuilder,
+ clippingPlanes,
+ clippingPlanesLength,
+ renderBoundPlanes,
+ renderBoundPlanesLength,
+ } = renderResources;
if (clippingPlanesLength > 0) {
- // Extract the getClippingPlane function from the getClippingFunction string.
- // This is a bit of a hack.
const functionId = "getClippingPlane";
- const entireFunction = getClippingFunction(clippingPlanes, context);
- const functionSignatureBegin = 0;
- const functionSignatureEnd = entireFunction.indexOf(")") + 1;
- const functionBodyBegin =
- entireFunction.indexOf("{", functionSignatureEnd) + 1;
- const functionBodyEnd = entireFunction.indexOf("}", functionBodyBegin);
- const functionSignature = entireFunction.slice(
- functionSignatureBegin,
- functionSignatureEnd,
+ const functionSignature = `vec4 ${functionId}(highp sampler2D packedPlanes, int planeNumber)`;
+ const textureResolution = ClippingPlaneCollection.getTextureResolution(
+ clippingPlanes,
+ context,
+ textureResolutionScratch,
+ );
+ const functionBody = getPlaneFunctionBody(textureResolution);
+ shaderBuilder.addFunction(
+ functionId,
+ functionSignature,
+ ShaderDestination.FRAGMENT,
);
- const functionBody = entireFunction.slice(
- functionBodyBegin,
- functionBodyEnd,
+ shaderBuilder.addFunctionLines(functionId, [functionBody]);
+ }
+
+ if (renderBoundPlanesLength > 0) {
+ const functionId = "getBoundPlane";
+ const functionSignature = `vec4 ${functionId}(highp sampler2D packedPlanes, int planeNumber)`;
+ const textureResolution = VoxelBoundsCollection.getTextureResolution(
+ renderBoundPlanes,
+ context,
+ textureResolutionScratch,
);
+ const functionBody = getPlaneFunctionBody(textureResolution);
shaderBuilder.addFunction(
functionId,
functionSignature,
@@ -133,4 +150,28 @@ function buildVoxelDrawCommands(primitive, context) {
primitive._drawCommandPickVoxel = drawCommandPickVoxel;
}
+function getPlaneFunctionBody(textureResolution) {
+ const width = textureResolution.x;
+ const height = textureResolution.y;
+
+ const pixelWidth = 1.0 / width;
+ const pixelHeight = 1.0 / height;
+
+ let pixelWidthString = `${pixelWidth}`;
+ if (pixelWidthString.indexOf(".") === -1) {
+ pixelWidthString += ".0";
+ }
+ let pixelHeightString = `${pixelHeight}`;
+ if (pixelHeightString.indexOf(".") === -1) {
+ pixelHeightString += ".0";
+ }
+
+ return `int pixY = planeNumber / ${width};
+ int pixX = planeNumber - (pixY * ${width});
+ // Sample from center of pixel
+ float u = (float(pixX) + 0.5) * ${pixelWidthString};
+ float v = (float(pixY) + 0.5) * ${pixelHeightString};
+ return texture(packedPlanes, vec2(u, v));`;
+}
+
export default buildVoxelDrawCommands;
diff --git a/packages/engine/Source/Scene/getClippingFunction.js b/packages/engine/Source/Scene/getClippingFunction.js
index 690d542cf4f5..0a283c590ef0 100644
--- a/packages/engine/Source/Scene/getClippingFunction.js
+++ b/packages/engine/Source/Scene/getClippingFunction.js
@@ -38,66 +38,61 @@ function getClippingFunction(clippingPlaneCollection, context) {
}
function clippingFunctionUnion(clippingPlanesLength) {
- const functionString =
- `${
- "float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)\n" +
- "{\n" +
- " vec4 position = czm_windowToEyeCoordinates(fragCoord);\n" +
- " vec3 clipNormal = vec3(0.0);\n" +
- " vec3 clipPosition = vec3(0.0);\n" +
- " float clipAmount;\n" + // For union planes, we want to get the min distance. So we set the initial value to the first plane distance in the loop below.
- " float pixelWidth = czm_metersPerPixel(position);\n" +
- " bool breakAndDiscard = false;\n" +
- " for (int i = 0; i < "
- }${clippingPlanesLength}; ++i)\n` +
- ` {\n` +
- ` vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);\n` +
- ` clipNormal = clippingPlane.xyz;\n` +
- ` clipPosition = -clippingPlane.w * clipNormal;\n` +
- ` float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;\n` +
- ` clipAmount = czm_branchFreeTernary(i == 0, amount, min(amount, clipAmount));\n` +
- ` if (amount <= 0.0)\n` +
- ` {\n` +
- ` breakAndDiscard = true;\n` +
- ` break;\n` + // HLSL compiler bug if we discard here: https://bugs.chromium.org/p/angleproject/issues/detail?id=1945#c6
- ` }\n` +
- ` }\n` +
- ` if (breakAndDiscard) {\n` +
- ` discard;\n` +
- ` }\n` +
- ` return clipAmount;\n` +
- `}\n`;
- return functionString;
+ return `float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)
+{
+ vec4 position = czm_windowToEyeCoordinates(fragCoord);
+ vec3 clipNormal = vec3(0.0);
+ vec3 clipPosition = vec3(0.0);
+ float clipAmount;
+ float pixelWidth = czm_metersPerPixel(position);
+ bool breakAndDiscard = false;
+ for (int i = 0; i < ${clippingPlanesLength}; ++i)
+ {
+ vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);
+ clipNormal = clippingPlane.xyz;
+ clipPosition = -clippingPlane.w * clipNormal;
+ float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;
+ clipAmount = czm_branchFreeTernary(i == 0, amount, min(amount, clipAmount));
+ if (amount <= 0.0)
+ {
+ breakAndDiscard = true;
+ // HLSL compiler bug if we discard here: https://bugs.chromium.org/p/angleproject/issues/detail?id=1945#c6
+ break;
+ }
+ }
+ if (breakAndDiscard) {
+ discard;
+ }
+ return clipAmount;
+}
+`;
}
function clippingFunctionIntersect(clippingPlanesLength) {
- const functionString =
- `${
- "float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)\n" +
- "{\n" +
- " bool clipped = true;\n" +
- " vec4 position = czm_windowToEyeCoordinates(fragCoord);\n" +
- " vec3 clipNormal = vec3(0.0);\n" +
- " vec3 clipPosition = vec3(0.0);\n" +
- " float clipAmount = 0.0;\n" +
- " float pixelWidth = czm_metersPerPixel(position);\n" +
- " for (int i = 0; i < "
- }${clippingPlanesLength}; ++i)\n` +
- ` {\n` +
- ` vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);\n` +
- ` clipNormal = clippingPlane.xyz;\n` +
- ` clipPosition = -clippingPlane.w * clipNormal;\n` +
- ` float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;\n` +
- ` clipAmount = max(amount, clipAmount);\n` +
- ` clipped = clipped && (amount <= 0.0);\n` +
- ` }\n` +
- ` if (clipped)\n` +
- ` {\n` +
- ` discard;\n` +
- ` }\n` +
- ` return clipAmount;\n` +
- `}\n`;
- return functionString;
+ return `float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)
+{
+ bool clipped = true;
+ vec4 position = czm_windowToEyeCoordinates(fragCoord);
+ vec3 clipNormal = vec3(0.0);
+ vec3 clipPosition = vec3(0.0);
+ float clipAmount = 0.0;
+ float pixelWidth = czm_metersPerPixel(position);
+ for (int i = 0; i < ${clippingPlanesLength}; ++i)
+ {
+ vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);
+ clipNormal = clippingPlane.xyz;
+ clipPosition = -clippingPlane.w * clipNormal;
+ float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;
+ clipAmount = max(amount, clipAmount);
+ clipped = clipped && (amount <= 0.0);
+ }
+ if (clipped)
+ {
+ discard;
+ }
+ return clipAmount;
+ }
+`;
}
function getClippingPlaneFloat(width, height) {
@@ -113,19 +108,17 @@ function getClippingPlaneFloat(width, height) {
pixelHeightString += ".0";
}
- const functionString =
- `${
- "vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)\n" +
- "{\n" +
- " int pixY = clippingPlaneNumber / "
- }${width};\n` +
- ` int pixX = clippingPlaneNumber - (pixY * ${width});\n` +
- ` float u = (float(pixX) + 0.5) * ${pixelWidthString};\n` + // sample from center of pixel
- ` float v = (float(pixY) + 0.5) * ${pixelHeightString};\n` +
- ` vec4 plane = texture(packedClippingPlanes, vec2(u, v));\n` +
- ` return czm_transformPlane(plane, transform);\n` +
- `}\n`;
- return functionString;
+ return `vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)
+{
+ int pixY = clippingPlaneNumber / ${width};
+ int pixX = clippingPlaneNumber - (pixY * ${width});
+ // Sample from center of pixel
+ float u = (float(pixX) + 0.5) * ${pixelWidthString};
+ float v = (float(pixY) + 0.5) * ${pixelHeightString};
+ vec4 plane = texture(packedClippingPlanes, vec2(u, v));
+ return czm_transformPlane(plane, transform);
+}
+`;
}
function getClippingPlaneUint8(width, height) {
@@ -141,23 +134,21 @@ function getClippingPlaneUint8(width, height) {
pixelHeightString += ".0";
}
- const functionString =
- `${
- "vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)\n" +
- "{\n" +
- " int clippingPlaneStartIndex = clippingPlaneNumber * 2;\n" + // clipping planes are two pixels each
- " int pixY = clippingPlaneStartIndex / "
- }${width};\n` +
- ` int pixX = clippingPlaneStartIndex - (pixY * ${width});\n` +
- ` float u = (float(pixX) + 0.5) * ${pixelWidthString};\n` + // sample from center of pixel
- ` float v = (float(pixY) + 0.5) * ${pixelHeightString};\n` +
- ` vec4 oct32 = texture(packedClippingPlanes, vec2(u, v)) * 255.0;\n` +
- ` vec2 oct = vec2(oct32.x * 256.0 + oct32.y, oct32.z * 256.0 + oct32.w);\n` +
- ` vec4 plane;\n` +
- ` plane.xyz = czm_octDecode(oct, 65535.0);\n` +
- ` plane.w = czm_unpackFloat(texture(packedClippingPlanes, vec2(u + ${pixelWidthString}, v)));\n` +
- ` return czm_transformPlane(plane, transform);\n` +
- `}\n`;
- return functionString;
+ return `vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)
+{
+ int clippingPlaneStartIndex = clippingPlaneNumber * 2;
+ int pixY = clippingPlaneStartIndex / ${width};
+ int pixX = clippingPlaneStartIndex - (pixY * ${width});
+ // Sample from center of pixel
+ float u = (float(pixX) + 0.5) * ${pixelWidthString};
+ float v = (float(pixY) + 0.5) * ${pixelHeightString};
+ vec4 oct32 = texture(packedClippingPlanes, vec2(u, v)) * 255.0;
+ vec2 oct = vec2(oct32.x * 256.0 + oct32.y, oct32.z * 256.0 + oct32.w);
+ vec4 plane;
+ plane.xyz = czm_octDecode(oct, 65535.0);
+ plane.w = czm_unpackFloat(texture(packedClippingPlanes, vec2(u + ${pixelWidthString}, v)));
+ return czm_transformPlane(plane, transform);
+}
+`;
}
export default getClippingFunction;
diff --git a/packages/engine/Source/Shaders/Voxels/IntersectBox.glsl b/packages/engine/Source/Shaders/Voxels/IntersectBox.glsl
index fd838e2d3335..fc351ddcfe94 100644
--- a/packages/engine/Source/Shaders/Voxels/IntersectBox.glsl
+++ b/packages/engine/Source/Shaders/Voxels/IntersectBox.glsl
@@ -5,43 +5,30 @@
#define BOX_INTERSECTION_INDEX ### // always 0
*/
-uniform vec3 u_renderMinBounds;
-uniform vec3 u_renderMaxBounds;
-
-RayShapeIntersection intersectBox(in Ray ray, in vec3 minBound, in vec3 maxBound)
-{
- // Consider the box as the intersection of the space between 3 pairs of parallel planes
- // Compute the distance along the ray to each plane
- vec3 t0 = (minBound - ray.pos) / ray.dir;
- vec3 t1 = (maxBound - ray.pos) / ray.dir;
-
- // Identify candidate entries/exits based on distance from ray.pos
- vec3 entries = min(t0, t1);
- vec3 exits = max(t0, t1);
-
- vec3 directions = sign(ray.dir);
-
- // The actual intersection points are the furthest entry and the closest exit
- float lastEntry = maxComponent(entries);
- bvec3 isLastEntry = equal(entries, vec3(lastEntry));
- vec3 entryNormal = -1.0 * vec3(isLastEntry) * directions;
- vec4 entry = vec4(entryNormal, lastEntry);
-
- float firstExit = minComponent(exits);
- bvec3 isFirstExit = equal(exits, vec3(firstExit));
- vec3 exitNormal = vec3(isLastEntry) * directions;
- vec4 exit = vec4(exitNormal, firstExit);
-
- if (entry.w > exit.w) {
- entry.w = NO_HIT;
- exit.w = NO_HIT;
+uniform sampler2D u_renderBoundPlanesTexture;
+
+RayShapeIntersection intersectBoundPlanes(in Ray ray) {
+ vec4 lastEntry = vec4(ray.dir, -INF_HIT);
+ vec4 firstExit = vec4(-ray.dir, +INF_HIT);
+ for (int i = 0; i < 6; i++) {
+ vec4 boundPlane = getBoundPlane(u_renderBoundPlanesTexture, i);
+ vec4 intersection = intersectPlane(ray, boundPlane);
+ if (dot(ray.dir, boundPlane.xyz) < 0.0) {
+ lastEntry = intersection.w > lastEntry.w ? intersection : lastEntry;
+ } else {
+ firstExit = intersection.w < firstExit.w ? intersection: firstExit;
+ }
}
- return RayShapeIntersection(entry, exit);
+ if (lastEntry.w < firstExit.w) {
+ return RayShapeIntersection(lastEntry, firstExit);
+ } else {
+ return RayShapeIntersection(vec4(-ray.dir, NO_HIT), vec4(ray.dir, NO_HIT));
+ }
}
-void intersectShape(in Ray ray, inout Intersections ix)
+void intersectShape(in Ray rayUV, in Ray rayEC, inout Intersections ix)
{
- RayShapeIntersection intersection = intersectBox(ray, u_renderMinBounds, u_renderMaxBounds);
+ RayShapeIntersection intersection = intersectBoundPlanes(rayEC);
setShapeIntersection(ix, BOX_INTERSECTION_INDEX, intersection);
}
diff --git a/packages/engine/Source/Shaders/Voxels/IntersectCylinder.glsl b/packages/engine/Source/Shaders/Voxels/IntersectCylinder.glsl
index 009f342f2743..7ac7086b4103 100644
--- a/packages/engine/Source/Shaders/Voxels/IntersectCylinder.glsl
+++ b/packages/engine/Source/Shaders/Voxels/IntersectCylinder.glsl
@@ -19,32 +19,30 @@
// Cylinder uniforms
uniform vec2 u_cylinderRenderRadiusMinMax;
-uniform vec2 u_cylinderRenderHeightMinMax;
#if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE)
uniform vec2 u_cylinderRenderAngleMinMax;
#endif
-/**
- * Find the intersection of a ray with the volume defined by two planes of constant z
- */
-RayShapeIntersection intersectHeightBounds(in Ray ray, in vec2 minMaxHeight, in bool convex)
-{
- float zPosition = ray.pos.z;
- float zDirection = ray.dir.z;
-
- float tmin = (minMaxHeight.x - zPosition) / zDirection;
- float tmax = (minMaxHeight.y - zPosition) / zDirection;
-
- // Normals point outside the volume
- float signFlip = convex ? 1.0 : -1.0;
- vec4 intersectMin = vec4(0.0, 0.0, -1.0 * signFlip, tmin);
- vec4 intersectMax = vec4(0.0, 0.0, 1.0 * signFlip, tmax);
-
- bool topEntry = zDirection < 0.0;
- vec4 entry = topEntry ? intersectMax : intersectMin;
- vec4 exit = topEntry ? intersectMin : intersectMax;
+uniform sampler2D u_renderBoundPlanesTexture;
+
+RayShapeIntersection intersectBoundPlanes(in Ray ray) {
+ vec4 lastEntry = vec4(ray.dir, -INF_HIT);
+ vec4 firstExit = vec4(-ray.dir, +INF_HIT);
+ for (int i = 0; i < 2; i++) {
+ vec4 boundPlane = getBoundPlane(u_renderBoundPlanesTexture, i);
+ vec4 intersection = intersectPlane(ray, boundPlane);
+ if (dot(ray.dir, boundPlane.xyz) < 0.0) {
+ lastEntry = intersection.w > lastEntry.w ? intersection : lastEntry;
+ } else {
+ firstExit = intersection.w < firstExit.w ? intersection: firstExit;
+ }
+ }
- return RayShapeIntersection(entry, exit);
+ if (lastEntry.w < firstExit.w) {
+ return RayShapeIntersection(lastEntry, firstExit);
+ } else {
+ return RayShapeIntersection(vec4(-ray.dir, NO_HIT), vec4(ray.dir, NO_HIT));
+ }
}
/**
@@ -70,8 +68,11 @@ RayShapeIntersection intersectCylinder(in Ray ray, in float radius, in bool conv
float t1 = (-b - determinant) / a;
float t2 = (-b + determinant) / a;
float signFlip = convex ? 1.0 : -1.0;
- vec4 intersect1 = vec4(normalize(position + t1 * direction) * signFlip, 0.0, t1);
- vec4 intersect2 = vec4(normalize(position + t2 * direction) * signFlip, 0.0, t2);
+ vec3 normal1 = vec3((position + t1 * direction) * signFlip, 0.0);
+ vec3 normal2 = vec3((position + t2 * direction) * signFlip, 0.0);
+ // Return normals in eye coordinates
+ vec4 intersect1 = vec4(normalize(czm_normal * normal1), t1);
+ vec4 intersect2 = vec4(normalize(czm_normal * normal2), t2);
return RayShapeIntersection(intersect1, intersect2);
}
@@ -80,21 +81,16 @@ RayShapeIntersection intersectCylinder(in Ray ray, in float radius, in bool conv
* Find the intersection of a ray with a right cylindrical solid of given
* radius and height bounds. NOTE: The shape is assumed to be convex.
*/
-RayShapeIntersection intersectBoundedCylinder(in Ray ray, in float radius, in vec2 minMaxHeight)
+RayShapeIntersection intersectBoundedCylinder(in Ray ray, in Ray rayEC, in float radius)
{
RayShapeIntersection cylinderIntersection = intersectCylinder(ray, radius, true);
- RayShapeIntersection heightBoundsIntersection = intersectHeightBounds(ray, minMaxHeight, true);
+ RayShapeIntersection heightBoundsIntersection = intersectBoundPlanes(rayEC);
return intersectIntersections(ray, cylinderIntersection, heightBoundsIntersection);
}
-void intersectShape(Ray ray, inout Intersections ix)
+void intersectShape(in Ray ray, in Ray rayEC, inout Intersections ix)
{
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- ray.pos = ray.pos * 2.0 - 1.0;
- ray.dir *= 2.0;
-
- RayShapeIntersection outerIntersect = intersectBoundedCylinder(ray, u_cylinderRenderRadiusMinMax.y, u_cylinderRenderHeightMinMax);
+ RayShapeIntersection outerIntersect = intersectBoundedCylinder(ray, rayEC, u_cylinderRenderRadiusMinMax.y);
setShapeIntersection(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MAX, outerIntersect);
diff --git a/packages/engine/Source/Shaders/Voxels/IntersectDepth.glsl b/packages/engine/Source/Shaders/Voxels/IntersectDepth.glsl
index 69c01bd3cd4a..e152f8cac2ae 100644
--- a/packages/engine/Source/Shaders/Voxels/IntersectDepth.glsl
+++ b/packages/engine/Source/Shaders/Voxels/IntersectDepth.glsl
@@ -5,8 +5,6 @@
#define DEPTH_INTERSECTION_INDEX ###
*/
-uniform mat4 u_transformPositionViewToUv;
-
void intersectDepth(in vec2 screenCoord, in Ray ray, inout Intersections ix) {
float logDepthOrDepth = czm_unpackDepth(texture(czm_globeDepthTexture, screenCoord));
float entry;
@@ -15,8 +13,7 @@ void intersectDepth(in vec2 screenCoord, in Ray ray, inout Intersections ix) {
// Calculate how far the ray must travel before it hits the depth buffer.
vec4 eyeCoordinateDepth = czm_screenToEyeCoordinates(screenCoord, logDepthOrDepth);
eyeCoordinateDepth /= eyeCoordinateDepth.w;
- vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
- entry = dot(depthPositionUv - ray.pos, ray.dir);
+ entry = dot(eyeCoordinateDepth.xyz - ray.pos, ray.dir);
exit = +INF_HIT;
} else {
// There's no depth at this location.
diff --git a/packages/engine/Source/Shaders/Voxels/IntersectEllipsoid.glsl b/packages/engine/Source/Shaders/Voxels/IntersectEllipsoid.glsl
index 1dec522b6df7..262f756493d1 100644
--- a/packages/engine/Source/Shaders/Voxels/IntersectEllipsoid.glsl
+++ b/packages/engine/Source/Shaders/Voxels/IntersectEllipsoid.glsl
@@ -26,7 +26,7 @@
#endif
uniform float u_eccentricitySquared;
uniform vec2 u_ellipsoidRenderLatitudeSinMinMax;
-uniform vec2 u_clipMinMaxHeight;
+uniform vec2 u_clipMinMaxHeight; // Values are negative: clipHeight - maxShapeHeight
RayShapeIntersection intersectZPlane(in Ray ray, in float z) {
float t = -ray.pos.z / ray.dir.z;
@@ -44,11 +44,11 @@ RayShapeIntersection intersectZPlane(in Ray ray, in float z) {
}
}
-RayShapeIntersection intersectHeight(in Ray ray, in float relativeHeight, in bool convex)
+RayShapeIntersection intersectHeight(in Ray ray, in float height, in bool convex)
{
// Scale the ray by the ellipsoid axes to make it a unit sphere
// Note: approximating ellipsoid + height as an ellipsoid
- vec3 radiiCorrection = u_ellipsoidRadiiUv / (u_ellipsoidRadiiUv + relativeHeight);
+ vec3 radiiCorrection = vec3(1.0) / (u_ellipsoidRadii + height);
vec3 position = ray.pos * radiiCorrection;
vec3 direction = ray.dir * radiiCorrection;
@@ -74,10 +74,14 @@ RayShapeIntersection intersectHeight(in Ray ray, in float relativeHeight, in boo
float tmax = max(t1, t2);
float directionScale = convex ? 1.0 : -1.0;
- vec3 d1 = directionScale * normalize(position + tmin * direction);
- vec3 d2 = directionScale * normalize(position + tmax * direction);
+ vec3 d1 = directionScale * (position + tmin * direction);
+ vec3 d2 = directionScale * (position + tmax * direction);
- return RayShapeIntersection(vec4(d1, tmin), vec4(d2, tmax));
+ // Return normals in eye coordinates. Use spherical approximation for the normal.
+ vec3 normal1 = normalize(czm_normal * d1);
+ vec3 normal2 = normalize(czm_normal * d2);
+
+ return RayShapeIntersection(vec4(normal1, tmin), vec4(normal2, tmax));
}
/**
@@ -151,16 +155,13 @@ float getLatitudeConeShift(in float sinLatitude) {
// Find prime vertical radius of curvature:
// the distance along the ellipsoid normal to the intersection with the z-axis
float x2 = u_eccentricitySquared * sinLatitude * sinLatitude;
- float primeVerticalRadius = inversesqrt(1.0 - x2);
+ float primeVerticalRadius = u_ellipsoidRadii.x * inversesqrt(1.0 - x2);
// Compute a shift from the origin to the intersection of the cone with the z-axis
return primeVerticalRadius * u_eccentricitySquared * sinLatitude;
}
void intersectFlippedCone(in Ray ray, in float cosHalfAngle, out RayShapeIntersection intersections[2]) {
- // Undo the scaling from ellipsoid to sphere
- ray.pos = ray.pos * u_ellipsoidRadiiUv;
- ray.dir = ray.dir * u_ellipsoidRadiiUv;
// Shift the ray to account for the latitude cone not being centered at the Earth center
ray.pos.z += getLatitudeConeShift(cosHalfAngle);
@@ -206,9 +207,6 @@ void intersectFlippedCone(in Ray ray, in float cosHalfAngle, out RayShapeInterse
}
RayShapeIntersection intersectRegularCone(in Ray ray, in float cosHalfAngle, in bool convex) {
- // Undo the scaling from ellipsoid to sphere
- ray.pos = ray.pos * u_ellipsoidRadiiUv;
- ray.dir = ray.dir * u_ellipsoidRadiiUv;
// Shift the ray to account for the latitude cone not being centered at the Earth center
ray.pos.z += getLatitudeConeShift(cosHalfAngle);
@@ -245,13 +243,7 @@ RayShapeIntersection intersectRegularCone(in Ray ray, in float cosHalfAngle, in
}
}
-void intersectShape(in Ray ray, inout Intersections ix) {
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- ray.pos = ray.pos * 2.0 - 1.0;
- ray.dir *= 2.0;
-
- // Outer ellipsoid
+void intersectShape(in Ray ray, in Ray rayEC, inout Intersections ix) { // Outer ellipsoid
RayShapeIntersection outerIntersect = intersectHeight(ray, u_clipMinMaxHeight.y, true);
setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX, outerIntersect);
diff --git a/packages/engine/Source/Shaders/Voxels/IntersectLongitude.glsl b/packages/engine/Source/Shaders/Voxels/IntersectLongitude.glsl
index e3b628870545..8fb9b9cb4ca1 100644
--- a/packages/engine/Source/Shaders/Voxels/IntersectLongitude.glsl
+++ b/packages/engine/Source/Shaders/Voxels/IntersectLongitude.glsl
@@ -1,6 +1,14 @@
// See IntersectionUtils.glsl for the definitions of Ray, NO_HIT, INF_HIT,
// RayShapeIntersection
+vec4 transformNormalToEC(in vec4 intersection) {
+ return vec4(normalize(czm_normal * intersection.xyz), intersection.w);
+}
+
+RayShapeIntersection transformNormalsToEC(in RayShapeIntersection ix) {
+ return RayShapeIntersection(transformNormalToEC(ix.entry), transformNormalToEC(ix.exit));
+}
+
vec4 intersectLongitude(in Ray ray, in float angle, in bool positiveNormal) {
float normalSign = positiveNormal ? 1.0 : -1.0;
vec2 planeNormal = vec2(-sin(angle), cos(angle)) * normalSign;
@@ -32,8 +40,8 @@ RayShapeIntersection intersectHalfSpace(in Ray ray, in float angle, in bool posi
void intersectFlippedWedge(in Ray ray, in vec2 minMaxAngle, out RayShapeIntersection intersections[2])
{
- intersections[0] = intersectHalfSpace(ray, minMaxAngle.x, false);
- intersections[1] = intersectHalfSpace(ray, minMaxAngle.y, true);
+ intersections[0] = transformNormalsToEC(intersectHalfSpace(ray, minMaxAngle.x, false));
+ intersections[1] = transformNormalsToEC(intersectHalfSpace(ray, minMaxAngle.y, true));
}
bool hitPositiveHalfPlane(in Ray ray, in vec4 intersection, in bool positiveNormal) {
@@ -46,14 +54,18 @@ bool hitPositiveHalfPlane(in Ray ray, in vec4 intersection, in bool positiveNorm
void intersectHalfPlane(in Ray ray, in float angle, out RayShapeIntersection intersections[2]) {
vec4 intersection = intersectLongitude(ray, angle, true);
vec4 farSide = vec4(normalize(ray.dir), INF_HIT);
+ bool hitPositiveSide = hitPositiveHalfPlane(ray, intersection, true);
+
+ farSide = transformNormalToEC(farSide);
- if (hitPositiveHalfPlane(ray, intersection, true)) {
+ if (hitPositiveSide) {
+ intersection = transformNormalToEC(intersection);
intersections[0].entry = -1.0 * farSide;
- intersections[0].exit = vec4(-1.0 * intersection.xy, 0.0, intersection.w);
+ intersections[0].exit = vec4(-1.0 * intersection.xyz, intersection.w);
intersections[1].entry = intersection;
intersections[1].exit = farSide;
} else {
- vec4 miss = vec4(normalize(ray.dir), NO_HIT);
+ vec4 miss = vec4(normalize(czm_normal * ray.dir), NO_HIT);
intersections[0].entry = -1.0 * farSide;
intersections[0].exit = farSide;
intersections[1].entry = miss;
@@ -88,15 +100,15 @@ RayShapeIntersection intersectRegularWedge(in Ray ray, in vec2 minMaxAngle)
if (exitFromInside && enterFromOutside) {
// Ray crosses both faces of negative wedge, exiting then entering the positive shape
- return RayShapeIntersection(first, last);
+ return transformNormalsToEC(RayShapeIntersection(first, last));
} else if (!exitFromInside && enterFromOutside) {
// Ray starts inside wedge. last is in shadow wedge, and first is actually the entry
- return RayShapeIntersection(-1.0 * farSide, first);
+ return transformNormalsToEC(RayShapeIntersection(-1.0 * farSide, first));
} else if (exitFromInside && !enterFromOutside) {
// First intersection was in the shadow wedge, so last is actually the exit
- return RayShapeIntersection(last, farSide);
+ return transformNormalsToEC(RayShapeIntersection(last, farSide));
} else { // !exitFromInside && !enterFromOutside
// Both intersections were in the shadow wedge
- return RayShapeIntersection(miss, miss);
+ return transformNormalsToEC(RayShapeIntersection(miss, miss));
}
}
diff --git a/packages/engine/Source/Shaders/Voxels/IntersectClippingPlanes.glsl b/packages/engine/Source/Shaders/Voxels/IntersectPlane.glsl
similarity index 97%
rename from packages/engine/Source/Shaders/Voxels/IntersectClippingPlanes.glsl
rename to packages/engine/Source/Shaders/Voxels/IntersectPlane.glsl
index 75e4e37f8e3b..4d117de72bcf 100644
--- a/packages/engine/Source/Shaders/Voxels/IntersectClippingPlanes.glsl
+++ b/packages/engine/Source/Shaders/Voxels/IntersectPlane.glsl
@@ -22,6 +22,7 @@ vec4 intersectPlane(in Ray ray, in vec4 plane) {
return vec4(n, t);
}
+#ifdef CLIPPING_PLANES
void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
vec4 backSide = vec4(-ray.dir, -INF_HIT);
vec4 farSide = vec4(ray.dir, +INF_HIT);
@@ -30,7 +31,7 @@ void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
#if (CLIPPING_PLANES_COUNT == 1)
// Union and intersection are the same when there's one clipping plane, and the code
// is more simplified.
- vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, 0, u_clippingPlanesMatrix);
+ vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, 0);
vec4 intersection = intersectPlane(ray, planeUv);
bool reflects = dot(ray.dir, intersection.xyz) < 0.0;
clippingVolume.entry = reflects ? backSide : intersection;
@@ -40,7 +41,7 @@ void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
vec4 firstTransmission = vec4(ray.dir, +INF_HIT);
vec4 lastReflection = vec4(-ray.dir, -INF_HIT);
for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
- vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i, u_clippingPlanesMatrix);
+ vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i);
vec4 intersection = intersectPlane(ray, planeUv);
if (dot(ray.dir, planeUv.xyz) > 0.0) {
firstTransmission = intersection.w <= firstTransmission.w ? intersection : firstTransmission;
@@ -58,7 +59,7 @@ void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
vec4 lastTransmission = vec4(ray.dir, -INF_HIT);
vec4 firstReflection = vec4(-ray.dir, +INF_HIT);
for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
- vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i, u_clippingPlanesMatrix);
+ vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i);
vec4 intersection = intersectPlane(ray, planeUv);
if (dot(ray.dir, planeUv.xyz) > 0.0) {
lastTransmission = intersection.w > lastTransmission.w ? intersection : lastTransmission;
@@ -76,3 +77,4 @@ void intersectClippingPlanes(in Ray ray, inout Intersections ix) {
setShapeIntersection(ix, CLIPPING_PLANES_INTERSECTION_INDEX, clippingVolume);
#endif
}
+#endif
diff --git a/packages/engine/Source/Shaders/Voxels/Intersection.glsl b/packages/engine/Source/Shaders/Voxels/Intersection.glsl
index 5c36a7b70545..da572b61e708 100644
--- a/packages/engine/Source/Shaders/Voxels/Intersection.glsl
+++ b/packages/engine/Source/Shaders/Voxels/Intersection.glsl
@@ -11,9 +11,9 @@
#define INTERSECTION_COUNT ###
*/
-RayShapeIntersection intersectScene(in vec2 screenCoord, in Ray ray, out Intersections ix) {
+RayShapeIntersection intersectScene(in vec2 screenCoord, in Ray ray, in Ray rayEC, out Intersections ix) {
// Do a ray-shape intersection to find the exact starting and ending points.
- intersectShape(ray, ix);
+ intersectShape(ray, rayEC, ix);
// Exit early if the positive shape was completely missed or behind the ray.
RayShapeIntersection intersection = getFirstIntersection(ix);
@@ -28,7 +28,7 @@ RayShapeIntersection intersectScene(in vec2 screenCoord, in Ray ray, out Interse
#endif
// Depth
- intersectDepth(screenCoord, ray, ix);
+ intersectDepth(screenCoord, rayEC, ix);
// Find the first intersection that's in front of the ray
#if (INTERSECTION_COUNT > 1)
diff --git a/packages/engine/Source/Shaders/Voxels/Octree.glsl b/packages/engine/Source/Shaders/Voxels/Octree.glsl
index 387be666b726..a875f9aa9093 100644
--- a/packages/engine/Source/Shaders/Voxels/Octree.glsl
+++ b/packages/engine/Source/Shaders/Voxels/Octree.glsl
@@ -29,6 +29,11 @@ struct TraversalData {
int parentOctreeIndex;
};
+struct TileAndUvCoordinate {
+ ivec4 tileCoords;
+ vec3 tileUv;
+};
+
struct SampleData {
int megatextureIndex;
ivec4 tileCoords;
@@ -79,23 +84,20 @@ int getOctreeParentIndex(in int octreeIndex) {
return parentOctreeIndex;
}
-/**
-* Convert a position in the uv-space of the tileset bounding shape
-* into the uv-space of a tile within the tileset
-*/
-vec3 getTileUv(in vec3 shapePosition, in ivec4 octreeCoords) {
- // PERFORMANCE_IDEA: use bit-shifting (only in WebGL2)
- float dimAtLevel = exp2(float(octreeCoords.w));
- return shapePosition * dimAtLevel - vec3(octreeCoords.xyz);
+vec3 getTileUv(in TileAndUvCoordinate tileAndUv, in ivec4 octreeCoords) {
+ int levelDifference = tileAndUv.tileCoords.w - octreeCoords.w;
+ float scalar = exp2(-1.0 * float(levelDifference));
+ vec3 originShift = vec3(tileAndUv.tileCoords.xyz - (octreeCoords.xyz << levelDifference)) * scalar;
+ return tileAndUv.tileUv * scalar + originShift;
}
-vec3 getClampedTileUv(in vec3 shapePosition, in ivec4 octreeCoords) {
- vec3 tileUv = getTileUv(shapePosition, octreeCoords);
+vec3 getClampedTileUv(in TileAndUvCoordinate tileAndUv, in ivec4 octreeCoords) {
+ vec3 tileUv = getTileUv(tileAndUv, octreeCoords);
return clamp(tileUv, vec3(0.0), vec3(1.0));
}
-void addSampleCoordinates(in vec3 shapePosition, inout SampleData sampleData) {
- vec3 tileUv = getClampedTileUv(shapePosition, sampleData.tileCoords);
+void addSampleCoordinates(in TileAndUvCoordinate tileAndUv, inout SampleData sampleData) {
+ vec3 tileUv = getClampedTileUv(tileAndUv, sampleData.tileCoords);
vec3 inputCoordinate = tileUv * vec3(u_dimensions);
#if defined(PADDING)
@@ -162,32 +164,25 @@ void getOctreeLeafSampleDatas(in OctreeNodeData data, in ivec4 octreeCoords, out
}
#endif
-OctreeNodeData traverseOctreeDownwards(in vec3 shapePosition, inout TraversalData traversalData) {
- float sizeAtLevel = exp2(-1.0 * float(traversalData.octreeCoords.w));
- vec3 start = vec3(traversalData.octreeCoords.xyz) * sizeAtLevel;
- vec3 end = start + vec3(sizeAtLevel);
+OctreeNodeData traverseOctreeDownwards(in ivec4 tileCoordinate, inout TraversalData traversalData) {
OctreeNodeData childData;
for (int i = 0; i < OCTREE_MAX_LEVELS; ++i) {
- // Find out which octree child contains the position
- // 0 if before center, 1 if after
- vec3 center = 0.5 * (start + end);
- vec3 childCoord = step(center, shapePosition);
-
- // Get octree coords for the next level down
- ivec4 octreeCoords = traversalData.octreeCoords;
- traversalData.octreeCoords = ivec4(octreeCoords.xyz * 2 + ivec3(childCoord), octreeCoords.w + 1);
+ // tileCoordinate.xyz is defined at the level of detail tileCoordinate.w.
+ // Find the corresponding coordinate at the level traversalData.octreeCoords.w
+ int level = traversalData.octreeCoords.w + 1;
+ int levelDifference = tileCoordinate.w - level;
+ ivec3 coordinateAtLevel = tileCoordinate.xyz >> levelDifference;
+ traversalData.octreeCoords = ivec4(coordinateAtLevel, level);
- childData = getOctreeChildData(traversalData.parentOctreeIndex, ivec3(childCoord));
+ ivec3 childCoordinate = coordinateAtLevel & 1;
+ childData = getOctreeChildData(traversalData.parentOctreeIndex, childCoordinate);
if (childData.flag != OCTREE_FLAG_INTERNAL) {
// leaf tile - stop traversing
break;
}
- // interior tile - keep going deeper
- start = mix(start, center, childCoord);
- end = mix(center, end, childCoord);
traversalData.parentOctreeIndex = childData.data;
}
@@ -198,50 +193,50 @@ OctreeNodeData traverseOctreeDownwards(in vec3 shapePosition, inout TraversalDat
* Transform a given position to an octree tile coordinate and a position within that tile,
* and find the corresponding megatexture index and texture coordinates
*/
-void traverseOctreeFromBeginning(in vec3 shapePosition, out TraversalData traversalData, out SampleData sampleDatas[SAMPLE_COUNT]) {
+void traverseOctreeFromBeginning(in TileAndUvCoordinate tileAndUv, out TraversalData traversalData, out SampleData sampleDatas[SAMPLE_COUNT]) {
traversalData.octreeCoords = ivec4(0);
traversalData.parentOctreeIndex = 0;
OctreeNodeData nodeData = getOctreeNodeData(vec2(0.0));
if (nodeData.flag != OCTREE_FLAG_LEAF) {
- nodeData = traverseOctreeDownwards(shapePosition, traversalData);
+ nodeData = traverseOctreeDownwards(tileAndUv.tileCoords, traversalData);
}
#if (SAMPLE_COUNT == 1)
getOctreeLeafSampleData(nodeData, traversalData.octreeCoords, sampleDatas[0]);
- addSampleCoordinates(shapePosition, sampleDatas[0]);
+ addSampleCoordinates(tileAndUv, sampleDatas[0]);
#else
getOctreeLeafSampleDatas(nodeData, traversalData.octreeCoords, sampleDatas);
- addSampleCoordinates(shapePosition, sampleDatas[0]);
- addSampleCoordinates(shapePosition, sampleDatas[1]);
+ addSampleCoordinates(tileAndUv, sampleDatas[0]);
+ addSampleCoordinates(tileAndUv, sampleDatas[1]);
#endif
}
-bool inRange(in vec3 v, in vec3 minVal, in vec3 maxVal) {
- return clamp(v, minVal, maxVal) == v;
-}
-
-bool insideTile(in vec3 shapePosition, in ivec4 octreeCoords) {
- vec3 tileUv = getTileUv(shapePosition, octreeCoords);
- bool inside = inRange(tileUv, vec3(0.0), vec3(1.0));
- // Assume (!) the position is always inside the root tile.
- return inside || octreeCoords.w == 0;
+bool insideTile(in ivec4 tileCoordinate, in ivec4 octreeCoords) {
+ int levelDifference = tileCoordinate.w - octreeCoords.w;
+ if (levelDifference < 0) {
+ return false;
+ }
+ ivec3 coordinateAtLevel = tileCoordinate.xyz >> levelDifference;
+ return coordinateAtLevel == octreeCoords.xyz;
}
-void traverseOctreeFromExisting(in vec3 shapePosition, inout TraversalData traversalData, inout SampleData sampleDatas[SAMPLE_COUNT]) {
- if (insideTile(shapePosition, traversalData.octreeCoords)) {
+void traverseOctreeFromExisting(in TileAndUvCoordinate tileAndUv, inout TraversalData traversalData, inout SampleData sampleDatas[SAMPLE_COUNT]) {
+ ivec4 tileCoords = tileAndUv.tileCoords;
+ if (insideTile(tileCoords, traversalData.octreeCoords)) {
for (int i = 0; i < SAMPLE_COUNT; i++) {
- addSampleCoordinates(shapePosition, sampleDatas[i]);
+ addSampleCoordinates(tileAndUv, sampleDatas[i]);
}
return;
}
- // Go up tree until we find a parent tile containing shapePosition
+ // Go up tree until we find a parent tile containing tileCoords.
+ // Assumes all parents are available all they way up to the root.
for (int i = 0; i < OCTREE_MAX_LEVELS; ++i) {
traversalData.octreeCoords.xyz /= 2;
traversalData.octreeCoords.w -= 1;
- if (insideTile(shapePosition, traversalData.octreeCoords)) {
+ if (insideTile(tileCoords, traversalData.octreeCoords)) {
break;
}
@@ -249,14 +244,14 @@ void traverseOctreeFromExisting(in vec3 shapePosition, inout TraversalData trave
}
// Go down tree
- OctreeNodeData nodeData = traverseOctreeDownwards(shapePosition, traversalData);
+ OctreeNodeData nodeData = traverseOctreeDownwards(tileCoords, traversalData);
#if (SAMPLE_COUNT == 1)
getOctreeLeafSampleData(nodeData, traversalData.octreeCoords, sampleDatas[0]);
- addSampleCoordinates(shapePosition, sampleDatas[0]);
+ addSampleCoordinates(tileAndUv, sampleDatas[0]);
#else
getOctreeLeafSampleDatas(nodeData, traversalData.octreeCoords, sampleDatas);
- addSampleCoordinates(shapePosition, sampleDatas[0]);
- addSampleCoordinates(shapePosition, sampleDatas[1]);
+ addSampleCoordinates(tileAndUv, sampleDatas[0]);
+ addSampleCoordinates(tileAndUv, sampleDatas[1]);
#endif
}
diff --git a/packages/engine/Source/Shaders/Voxels/VoxelFS.glsl b/packages/engine/Source/Shaders/Voxels/VoxelFS.glsl
index 1164f80fd8a2..3af0eedbf0ab 100644
--- a/packages/engine/Source/Shaders/Voxels/VoxelFS.glsl
+++ b/packages/engine/Source/Shaders/Voxels/VoxelFS.glsl
@@ -1,9 +1,9 @@
// See Intersection.glsl for the definition of intersectScene
// See IntersectionUtils.glsl for the definition of nextIntersection
-// See convertUvToBox.glsl, convertUvToCylinder.glsl, or convertUvToEllipsoid.glsl
-// for the definition of convertUvToShapeUvSpace. The appropriate function is
-// selected based on the VoxelPrimitive shape type, and added to the shader in
-// Scene/VoxelRenderResources.js.
+// See convertLocalToBoxUv.glsl, convertLocalToCylinderUv.glsl, or convertLocalToEllipsoidUv.glsl
+// for the definitions of convertLocalToShapeSpaceDerivative and getTileAndUvCoordinate.
+// The appropriate functions are selected based on the VoxelPrimitive shape type,
+// and added to the shader in Scene/VoxelRenderResources.js.
// See Octree.glsl for the definitions of TraversalData, SampleData,
// traverseOctreeFromBeginning, and traverseOctreeFromExisting
// See Megatexture.glsl for the definition of accumulatePropertiesFromMegatexture
@@ -15,10 +15,10 @@
#define ALPHA_ACCUM_MAX 0.98 // Must be > 0.0 and <= 1.0
#endif
-uniform mat4 u_transformPositionUvToView;
+uniform mat4 u_transformPositionViewToLocal;
uniform mat3 u_transformDirectionViewToLocal;
-uniform vec3 u_cameraPositionUv;
-uniform vec3 u_cameraDirectionUv;
+uniform vec3 u_cameraPositionLocal;
+uniform vec3 u_cameraDirectionLocal;
uniform float u_stepSize;
#if defined(PICKING)
@@ -63,16 +63,15 @@ RayShapeIntersection getVoxelIntersection(in vec3 tileUv, in vec3 sampleSizeAlon
}
vec4 getStepSize(in SampleData sampleData, in Ray viewRay, in RayShapeIntersection shapeIntersection, in mat3 jacobianT, in float currentT) {
- // The Jacobian is computed in a space where the shape spans [-1, 1].
- // But the ray is marched in a space where the shape fills [0, 1].
- // So we need to scale the Jacobian by 2.
- vec3 gradient = 2.0 * viewRay.rawDir * jacobianT;
+ vec3 gradient = viewRay.dir * jacobianT;
vec3 sampleSizeAlongRay = getSampleSize(sampleData.tileCoords.w) / gradient;
RayShapeIntersection voxelIntersection = getVoxelIntersection(sampleData.tileUv, sampleSizeAlongRay);
- // Transform normal from shape space to Cartesian space
- vec3 voxelNormal = normalize(jacobianT * voxelIntersection.entry.xyz);
+ // Transform normal from shape space to Cartesian space to eye space
+ vec3 voxelNormal = jacobianT * voxelIntersection.entry.xyz;
+ voxelNormal = normalize(czm_normal * voxelNormal);
+
// Compare with the shape intersection, to choose the appropriate normal
vec4 voxelEntry = vec4(voxelNormal, currentT + voxelIntersection.entry.w);
vec4 entry = intersectionMax(shapeIntersection.entry, voxelEntry);
@@ -114,37 +113,40 @@ int getSampleIndex(in SampleData sampleData) {
}
/**
- * Compute the view ray at the current fragment, in the local UV coordinates of the shape.
+ * Compute the view ray at the current fragment, in the local coordinates of the shape.
*/
-Ray getViewRayUv() {
+Ray getViewRayLocal() {
vec4 eyeCoordinates = czm_windowToEyeCoordinates(gl_FragCoord);
- vec3 viewDirUv;
- vec3 viewPosUv;
+ vec3 origin;
+ vec3 direction;
if (czm_orthographicIn3D == 1.0) {
eyeCoordinates.z = 0.0;
- viewPosUv = (u_transformPositionViewToUv * eyeCoordinates).xyz;
- viewDirUv = normalize(u_cameraDirectionUv);
+ origin = (u_transformPositionViewToLocal * eyeCoordinates).xyz;
+ direction = u_cameraDirectionLocal;
} else {
- viewPosUv = u_cameraPositionUv;
- viewDirUv = normalize(u_transformDirectionViewToLocal * eyeCoordinates.xyz);
+ origin = u_cameraPositionLocal;
+ direction = u_transformDirectionViewToLocal * normalize(eyeCoordinates.xyz);
}
- #if defined(SHAPE_ELLIPSOID)
- // viewDirUv has been scaled to a space where the ellipsoid is a sphere.
- // Undo this scaling to get the raw direction.
- vec3 rawDir = viewDirUv * u_ellipsoidRadiiUv;
- return Ray(viewPosUv, viewDirUv, rawDir);
- #else
- return Ray(viewPosUv, viewDirUv, viewDirUv);
- #endif
+ return Ray(origin, direction);
+}
+
+Ray getViewRayEC() {
+ vec4 eyeCoordinates = czm_windowToEyeCoordinates(gl_FragCoord);
+ vec3 viewPosEC = (czm_orthographicIn3D == 1.0)
+ ? vec3(eyeCoordinates.xy, 0.0)
+ : vec3(0.0);
+ vec3 viewDirEC = normalize(eyeCoordinates.xyz);
+ return Ray(viewPosEC, viewDirEC);
}
void main()
{
- Ray viewRayUv = getViewRayUv();
+ Ray viewRayLocal = getViewRayLocal();
+ Ray viewRayEC = getViewRayEC();
Intersections ix;
vec2 screenCoord = (gl_FragCoord.xy - czm_viewport.xy) / czm_viewport.zw; // [0,1]
- RayShapeIntersection shapeIntersection = intersectScene(screenCoord, viewRayUv, ix);
+ RayShapeIntersection shapeIntersection = intersectScene(screenCoord, viewRayLocal, viewRayEC, ix);
// Exit early if the scene was completely missed.
if (shapeIntersection.entry.w == NO_HIT) {
discard;
@@ -152,20 +154,17 @@ void main()
float currentT = shapeIntersection.entry.w;
float endT = shapeIntersection.exit.w;
- vec3 positionUv = viewRayUv.pos + currentT * viewRayUv.dir;
- PointJacobianT pointJacobian = convertUvToShapeUvSpaceDerivative(positionUv);
+
+ vec3 positionEC = viewRayEC.pos + currentT * viewRayEC.dir;
+ TileAndUvCoordinate tileAndUv = getTileAndUvCoordinate(positionEC);
+ vec3 positionLocal = viewRayLocal.pos + currentT * viewRayLocal.dir;
+ mat3 jacobianT = convertLocalToShapeSpaceDerivative(positionLocal);
// Traverse the tree from the start position
TraversalData traversalData;
SampleData sampleDatas[SAMPLE_COUNT];
- traverseOctreeFromBeginning(pointJacobian.point, traversalData, sampleDatas);
- vec4 step = getStepSize(sampleDatas[0], viewRayUv, shapeIntersection, pointJacobian.jacobianT, currentT);
-
- #if defined(JITTER)
- float noise = hash(screenCoord); // [0,1]
- currentT += noise * step.w;
- positionUv += noise * step.w * viewRayUv.dir;
- #endif
+ traverseOctreeFromBeginning(tileAndUv, traversalData, sampleDatas);
+ vec4 step = getStepSize(sampleDatas[0], viewRayLocal, shapeIntersection, jacobianT, currentT);
FragmentInput fragmentInput;
#if defined(STATISTICS)
@@ -182,10 +181,11 @@ void main()
// Prepare the custom shader inputs
copyPropertiesToMetadata(properties, fragmentInput.metadata);
- fragmentInput.attributes.positionEC = vec3(u_transformPositionUvToView * vec4(positionUv, 1.0));
- fragmentInput.attributes.normalEC = normalize(czm_normal * step.xyz);
+ fragmentInput.attributes.positionEC = positionEC;
+ // Re-normalize normals: some shape intersections may have been scaled to encode positive/negative shapes
+ fragmentInput.attributes.normalEC = normalize(step.xyz);
- fragmentInput.voxel.viewDirUv = viewRayUv.dir;
+ fragmentInput.voxel.viewDirUv = viewRayLocal.dir;
fragmentInput.voxel.travelDistance = step.w;
fragmentInput.voxel.stepCount = stepCount;
@@ -233,13 +233,15 @@ void main()
}
#endif
}
- positionUv = viewRayUv.pos + currentT * viewRayUv.dir;
+ positionEC = viewRayEC.pos + currentT * viewRayEC.dir;
+ tileAndUv = getTileAndUvCoordinate(positionEC);
+ positionLocal = viewRayLocal.pos + currentT * viewRayLocal.dir;
+ jacobianT = convertLocalToShapeSpaceDerivative(positionLocal);
// Traverse the tree from the current ray position.
// This is similar to traverseOctreeFromBeginning but is faster when the ray is in the same tile as the previous step.
- pointJacobian = convertUvToShapeUvSpaceDerivative(positionUv);
- traverseOctreeFromExisting(pointJacobian.point, traversalData, sampleDatas);
- step = getStepSize(sampleDatas[0], viewRayUv, shapeIntersection, pointJacobian.jacobianT, currentT);
+ traverseOctreeFromExisting(tileAndUv, traversalData, sampleDatas);
+ step = getStepSize(sampleDatas[0], viewRayLocal, shapeIntersection, jacobianT, currentT);
}
// Convert the alpha from [0,ALPHA_ACCUM_MAX] to [0,1]
diff --git a/packages/engine/Source/Shaders/Voxels/VoxelUtils.glsl b/packages/engine/Source/Shaders/Voxels/VoxelUtils.glsl
index ca2fee5fba96..f712e1fd4b50 100644
--- a/packages/engine/Source/Shaders/Voxels/VoxelUtils.glsl
+++ b/packages/engine/Source/Shaders/Voxels/VoxelUtils.glsl
@@ -1,22 +1,8 @@
struct Ray {
vec3 pos;
vec3 dir;
- vec3 rawDir;
};
-#if defined(JITTER)
-/**
- * Generate a pseudo-random value for a given 2D screen coordinate.
- * Similar to https://www.shadertoy.com/view/4djSRW with a modified hashscale.
- */
-float hash(vec2 p)
-{
- vec3 p3 = fract(vec3(p.xyx) * 50.0);
- p3 += dot(p3, p3.yzx + 19.19);
- return fract((p3.x + p3.y) * p3.z);
-}
-#endif
-
float minComponent(in vec3 v) {
return min(min(v.x, v.y), v.z);
}
@@ -24,8 +10,3 @@ float minComponent(in vec3 v) {
float maxComponent(in vec3 v) {
return max(max(v.x, v.y), v.z);
}
-
-struct PointJacobianT {
- vec3 point;
- mat3 jacobianT;
-};
diff --git a/packages/engine/Source/Shaders/Voxels/convertLocalToBoxUv.glsl b/packages/engine/Source/Shaders/Voxels/convertLocalToBoxUv.glsl
new file mode 100644
index 000000000000..d021d46e07ae
--- /dev/null
+++ b/packages/engine/Source/Shaders/Voxels/convertLocalToBoxUv.glsl
@@ -0,0 +1,30 @@
+uniform vec3 u_boxLocalToShapeUvScale;
+uniform vec3 u_boxLocalToShapeUvTranslate;
+
+uniform ivec4 u_cameraTileCoordinates;
+uniform vec3 u_cameraTileUv;
+uniform mat3 u_boxEcToXyz;
+
+mat3 convertLocalToShapeSpaceDerivative(in vec3 positionLocal) {
+ // For BOX, local space = shape space, so the Jacobian is the identity matrix.
+ return mat3(1.0);
+}
+
+vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
+ return shapeUv / u_boxLocalToShapeUvScale;
+}
+
+vec3 convertEcToDeltaTile(in vec3 positionEC) {
+ vec3 dPosition = u_boxEcToXyz * positionEC;
+ return u_boxLocalToShapeUvScale * dPosition * float(1 << u_cameraTileCoordinates.w);
+}
+
+TileAndUvCoordinate getTileAndUvCoordinate(in vec3 positionEC) {
+ vec3 deltaTileCoordinate = convertEcToDeltaTile(positionEC);
+ vec3 tileUvSum = u_cameraTileUv + deltaTileCoordinate;
+ ivec3 tileCoordinate = u_cameraTileCoordinates.xyz + ivec3(floor(tileUvSum));
+ tileCoordinate = min(max(ivec3(0), tileCoordinate), ivec3((1 << u_cameraTileCoordinates.w) - 1));
+ ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
+ vec3 tileUv = clamp(tileUvSum - vec3(tileCoordinateChange), 0.0, 1.0);
+ return TileAndUvCoordinate(ivec4(tileCoordinate, u_cameraTileCoordinates.w), tileUv);
+}
diff --git a/packages/engine/Source/Shaders/Voxels/convertLocalToCylinderUv.glsl b/packages/engine/Source/Shaders/Voxels/convertLocalToCylinderUv.glsl
new file mode 100644
index 000000000000..30e1039221a8
--- /dev/null
+++ b/packages/engine/Source/Shaders/Voxels/convertLocalToCylinderUv.glsl
@@ -0,0 +1,97 @@
+uniform vec2 u_cylinderLocalToShapeUvRadius; // x = scale, y = offset
+uniform vec2 u_cylinderLocalToShapeUvHeight; // x = scale, y = offset
+uniform vec2 u_cylinderLocalToShapeUvAngle; // x = scale, y = offset
+uniform float u_cylinderShapeUvAngleRangeOrigin;
+uniform mat3 u_cylinderEcToRadialTangentUp;
+uniform ivec4 u_cameraTileCoordinates;
+uniform vec3 u_cameraTileUv;
+uniform vec3 u_cameraShapePosition; // (radial distance, angle, height) of camera in shape space
+
+mat3 convertLocalToShapeSpaceDerivative(in vec3 position) {
+ vec3 radial = normalize(vec3(position.xy, 0.0));
+ vec3 z = vec3(0.0, 0.0, 1.0);
+ vec3 east = normalize(vec3(-position.y, position.x, 0.0));
+ return mat3(radial, east / length(position.xy), z);
+}
+
+vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
+ float radius = shapeUv.x / u_cylinderLocalToShapeUvRadius.x;
+ float angle = shapeUv.y * czm_twoPi / u_cylinderLocalToShapeUvAngle.x;
+ float height = shapeUv.z / u_cylinderLocalToShapeUvHeight.x;
+
+ return vec3(radius, angle, height);
+}
+
+/**
+ * Computes the change in polar coordinates given a change in position.
+ * @param {vec2} dPosition The change in position in Cartesian coordinates.
+ * @param {float} cameraRadialDistance The radial distance of the camera from the origin.
+ * @return {vec2} The change in polar coordinates (radial distance, angle).
+ */
+vec2 computePolarChange(in vec2 dPosition, in float cameraRadialDistance) {
+ float dAngle = atan(dPosition.y, cameraRadialDistance + dPosition.x);
+ // Find the direction of the radial axis at the output angle, in Cartesian coordinates
+ vec2 outputRadialAxis = vec2(cos(dAngle), sin(dAngle));
+ float sinHalfAngle = sin(dAngle / 2.0);
+ float versine = 2.0 * sinHalfAngle * sinHalfAngle;
+ float dRadial = dot(dPosition, outputRadialAxis) - cameraRadialDistance * versine;
+ return vec2(dRadial, dAngle);
+}
+
+vec3 convertEcToDeltaShape(in vec3 positionEC) {
+ // 1. Rotate to radial, tangent, and up coordinates
+ vec3 rtu = u_cylinderEcToRadialTangentUp * positionEC;
+ // 2. Compute change in angular and radial coordinates.
+ vec2 dPolar = computePolarChange(rtu.xy, u_cameraShapePosition.x);
+ return vec3(dPolar.xy, rtu.z);
+}
+
+vec3 convertEcToDeltaTile(in vec3 positionEC) {
+ vec3 deltaShape = convertEcToDeltaShape(positionEC);
+ // Convert to tileset coordinates in [0, 1]
+ float dx = u_cylinderLocalToShapeUvRadius.x * deltaShape.x;
+ float dy = deltaShape.y / czm_twoPi;
+#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
+ // Wrap to ensure dy is not crossing through the unoccupied angle range, where
+ // angle to tile coordinate conversions would be more complicated
+ float cameraUvAngle = (u_cameraShapePosition.y + czm_pi) / czm_twoPi;
+ float cameraUvAngleShift = fract(cameraUvAngle - u_cylinderShapeUvAngleRangeOrigin);
+ float rawOutputUvAngle = cameraUvAngleShift + dy;
+ float rotation = floor(rawOutputUvAngle);
+ dy -= rotation;
+#endif
+ dy *= u_cylinderLocalToShapeUvAngle.x;
+ float dz = u_cylinderLocalToShapeUvHeight.x * deltaShape.z;
+ // Convert to tile coordinate changes
+ return vec3(dx, dy, dz) * float(1 << u_cameraTileCoordinates.w);
+}
+
+TileAndUvCoordinate getTileAndUvCoordinate(in vec3 positionEC) {
+ vec3 deltaTileCoordinate = convertEcToDeltaTile(positionEC);
+ vec3 tileUvSum = u_cameraTileUv + deltaTileCoordinate;
+ ivec3 tileCoordinate = u_cameraTileCoordinates.xyz + ivec3(floor(tileUvSum));
+ int maxTileCoordinate = (1 << u_cameraTileCoordinates.w) - 1;
+ tileCoordinate.x = min(max(0, tileCoordinate.x), maxTileCoordinate);
+ tileCoordinate.z = min(max(0, tileCoordinate.z), maxTileCoordinate);
+#if (!defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE))
+ ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
+ if (tileCoordinate.y < 0) {
+ tileCoordinate.y += (maxTileCoordinate + 1);
+ } else if (tileCoordinate.y > maxTileCoordinate) {
+ tileCoordinate.y -= (maxTileCoordinate + 1);
+ }
+#else
+ tileCoordinate.y = min(max(0, tileCoordinate.y), maxTileCoordinate);
+ ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
+#endif
+ vec3 tileUv = tileUvSum - vec3(tileCoordinateChange);
+ tileUv.x = clamp(tileUv.x, 0.0, 1.0);
+#if (!defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE))
+ // If there is only one tile spanning 2*PI angle, the coordinate wraps around
+ tileUv.y = (u_cameraTileCoordinates.w == 0) ? fract(tileUv.y) : clamp(tileUv.y, 0.0, 1.0);
+#else
+ tileUv.y = clamp(tileUv.y, 0.0, 1.0);
+#endif
+ tileUv.z = clamp(tileUv.z, 0.0, 1.0);
+ return TileAndUvCoordinate(ivec4(tileCoordinate, u_cameraTileCoordinates.w), tileUv);
+}
diff --git a/packages/engine/Source/Shaders/Voxels/convertLocalToEllipsoidUv.glsl b/packages/engine/Source/Shaders/Voxels/convertLocalToEllipsoidUv.glsl
new file mode 100644
index 000000000000..8052c6f354c5
--- /dev/null
+++ b/packages/engine/Source/Shaders/Voxels/convertLocalToEllipsoidUv.glsl
@@ -0,0 +1,193 @@
+/* Ellipsoid defines (set in Scene/VoxelEllipsoidShape.js)
+#define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY
+#define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY
+#define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE
+#define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED
+#define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE
+*/
+
+uniform vec3 u_cameraPositionCartographic; // (longitude, latitude, height) in radians and meters
+uniform vec2 u_ellipsoidCurvatureAtLatitude;
+uniform mat3 u_ellipsoidEcToEastNorthUp;
+uniform vec3 u_ellipsoidRadii;
+uniform vec2 u_evoluteScale; // (radii.x ^ 2 - radii.z ^ 2) * vec2(1.0, -1.0) / radii;
+uniform vec3 u_ellipsoidInverseRadiiSquared;
+#if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY) || defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
+ uniform vec3 u_ellipsoidShapeUvLongitudeMinMaxMid;
+#endif
+#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
+ uniform vec2 u_ellipsoidLocalToShapeUvLongitude; // x = scale, y = offset
+ uniform float u_ellipsoidShapeUvLongitudeRangeOrigin;
+#endif
+#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
+ uniform vec2 u_ellipsoidLocalToShapeUvLatitude; // x = scale, y = offset
+#endif
+uniform float u_ellipsoidInverseHeightDifference;
+
+uniform ivec4 u_cameraTileCoordinates;
+uniform vec3 u_cameraTileUv;
+
+// robust iterative solution without trig functions
+// https://github.com/0xfaded/ellipse_demo/issues/1
+// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse
+// Extended to return radius of curvature along with the point
+vec3 nearestPointAndRadiusOnEllipse(vec2 pos, vec2 radii) {
+ vec2 p = abs(pos);
+ vec2 inverseRadii = 1.0 / radii;
+
+ // We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t))
+ // but store the cos and sin of t in a vec2 for efficiency.
+ // Initial guess: t = pi/4
+ vec2 tTrigs = vec2(0.7071067811865476);
+ // Initial guess of point on ellipsoid
+ vec2 v = radii * tTrigs;
+ // Center of curvature of the ellipse at v
+ vec2 evolute = u_evoluteScale * tTrigs * tTrigs * tTrigs;
+
+ const int iterations = 3;
+ for (int i = 0; i < iterations; ++i) {
+ // Find the (approximate) intersection of p - evolute with the ellipsoid.
+ vec2 q = normalize(p - evolute) * length(v - evolute);
+ // Update the estimate of t.
+ tTrigs = (q + evolute) * inverseRadii;
+ tTrigs = normalize(clamp(tTrigs, 0.0, 1.0));
+ v = radii * tTrigs;
+ evolute = u_evoluteScale * tTrigs * tTrigs * tTrigs;
+ }
+
+ return vec3(v * sign(pos), length(v - evolute));
+}
+
+mat3 convertLocalToShapeSpaceDerivative(in vec3 position) {
+ vec3 east = normalize(vec3(-position.y, position.x, 0.0));
+
+ // Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z)
+ // (assume radii.y == radii.x) and find the nearest point on the ellipse and its normal
+ float distanceFromZAxis = length(position.xy);
+ vec2 posEllipse = vec2(distanceFromZAxis, position.z);
+ vec3 surfacePointAndRadius = nearestPointAndRadiusOnEllipse(posEllipse, u_ellipsoidRadii.xz);
+ vec2 surfacePoint = surfacePointAndRadius.xy;
+
+ vec2 normal2d = normalize(surfacePoint * u_ellipsoidInverseRadiiSquared.xz);
+ vec3 north = vec3(-normal2d.y * normalize(position.xy), abs(normal2d.x));
+
+ float heightSign = length(posEllipse) < length(surfacePoint) ? -1.0 : 1.0;
+ float height = heightSign * length(posEllipse - surfacePoint);
+ vec3 up = normalize(cross(east, north));
+
+ return mat3(east / distanceFromZAxis, north / (surfacePointAndRadius.z + height), up);
+}
+
+vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
+ // Convert from [0, 1] to radians [-pi, pi]
+ float longitude = shapeUv.x * czm_twoPi;
+ #if defined (ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
+ longitude /= u_ellipsoidLocalToShapeUvLongitude.x;
+ #endif
+
+ // Convert from [0, 1] to radians [-pi/2, pi/2]
+ float latitude = shapeUv.y * czm_pi;
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
+ latitude /= u_ellipsoidLocalToShapeUvLatitude.x;
+ #endif
+
+ float height = shapeUv.z / u_ellipsoidInverseHeightDifference;
+
+ return vec3(longitude, latitude, height);
+}
+
+vec3 convertEcToDeltaShape(in vec3 positionEC) {
+ vec3 enu = u_ellipsoidEcToEastNorthUp * positionEC;
+
+ // 1. Compute the change in longitude from the camera to the ENU point
+ // First project the camera and ENU positions to the equatorial XY plane,
+ // positioning the camera on the +x axis, so that enu.x projects along the +y axis
+ float cosLatitude = cos(u_cameraPositionCartographic.y);
+ float sinLatitude = sin(u_cameraPositionCartographic.y);
+ float primeVerticalRadius = 1.0 / u_ellipsoidCurvatureAtLatitude.x;
+ vec2 cameraXY = vec2((primeVerticalRadius + u_cameraPositionCartographic.z) * cosLatitude, 0.0);
+ // Note precision loss in positionXY.x if length(enu) << length(cameraXY)
+ vec2 positionXY = cameraXY + vec2(-enu.y * sinLatitude + enu.z * cosLatitude, enu.x);
+ float dLongitude = atan(positionXY.y, positionXY.x);
+
+ // 2. Find the longitude component of positionXY, by rotating about Z until the y component is zero.
+ // Use the versine to compute the change in x directly from the change in angle:
+ // versine(angle) = 2 * sin^2(angle/2)
+ float sinHalfLongitude = sin(dLongitude / 2.0);
+ float dx = length(positionXY) * 2.0 * sinHalfLongitude * sinHalfLongitude;
+ // Rotate longitude component back to ENU North and Up, and remove from enu
+ enu += vec3(-enu.x, -dx * sinLatitude, dx * cosLatitude);
+
+ // 3. Compute the change in latitude from the camera to the ENU point.
+ // First project the camera and ENU positions to the meridional ZX plane,
+ // positioning the camera on the +Z axis, so that enu.y maps to the +X axis.
+ float meridionalRadius = 1.0 / u_ellipsoidCurvatureAtLatitude.y;
+ vec2 cameraZX = vec2(meridionalRadius + u_cameraPositionCartographic.z, 0.0);
+ vec2 positionZX = cameraZX + vec2(enu.z, enu.y);
+ float dLatitude = atan(positionZX.y, positionZX.x);
+
+ // 4. Compute the change in height above the ellipsoid
+ // Find the change in enu.z associated with rotating the point to the latitude of the camera
+ float sinHalfLatitude = sin(dLatitude / 2.0);
+ float dz = length(positionZX) * 2.0 * sinHalfLatitude * sinHalfLatitude;
+ // The remaining change in enu.z is the change in height above the ellipsoid
+ float dHeight = enu.z + dz;
+
+ return vec3(dLongitude, dLatitude, dHeight);
+}
+
+vec3 convertEcToDeltaTile(in vec3 positionEC) {
+ vec3 deltaShape = convertEcToDeltaShape(positionEC);
+ // Convert to tileset coordinates in [0, 1]
+ float dx = deltaShape.x / czm_twoPi;
+
+#if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE))
+ // Wrap to ensure dx is not crossing through the unoccupied angle range, where
+ // angle to tile coordinate conversions would be more complicated
+ float cameraUvLongitude = (u_cameraPositionCartographic.x + czm_pi) / czm_twoPi;
+ float cameraUvLongitudeShift = fract(cameraUvLongitude - u_ellipsoidShapeUvLongitudeRangeOrigin);
+ float rawOutputUvLongitude = cameraUvLongitudeShift + dx;
+ float rotation = floor(rawOutputUvLongitude);
+ dx -= rotation;
+ dx *= u_ellipsoidLocalToShapeUvLongitude.x;
+#endif
+
+ float dy = deltaShape.y / czm_pi;
+#if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE))
+ dy *= u_ellipsoidLocalToShapeUvLatitude.x;
+#endif
+
+ float dz = u_ellipsoidInverseHeightDifference * deltaShape.z;
+ // Convert to tile coordinate changes
+ return vec3(dx, dy, dz) * float(1 << u_cameraTileCoordinates.w);
+}
+
+TileAndUvCoordinate getTileAndUvCoordinate(in vec3 positionEC) {
+ vec3 deltaTileCoordinate = convertEcToDeltaTile(positionEC);
+ vec3 tileUvSum = u_cameraTileUv + deltaTileCoordinate;
+ ivec3 tileCoordinate = u_cameraTileCoordinates.xyz + ivec3(floor(tileUvSum));
+ int maxTileCoordinate = (1 << u_cameraTileCoordinates.w) - 1;
+ tileCoordinate.y = min(max(0, tileCoordinate.y), maxTileCoordinate);
+ tileCoordinate.z = min(max(0, tileCoordinate.z), maxTileCoordinate);
+#if (!defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE))
+ ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
+ if (tileCoordinate.x < 0) {
+ tileCoordinate.x += (maxTileCoordinate + 1);
+ } else if (tileCoordinate.x > maxTileCoordinate) {
+ tileCoordinate.x -= (maxTileCoordinate + 1);
+ }
+#else
+ tileCoordinate.x = min(max(0, tileCoordinate.x), maxTileCoordinate);
+ ivec3 tileCoordinateChange = tileCoordinate - u_cameraTileCoordinates.xyz;
+#endif
+ vec3 tileUv = tileUvSum - vec3(tileCoordinateChange);
+#if (!defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE))
+ // If there is only one tile spanning 2*PI angle, the coordinate wraps around
+ tileUv.x = (u_cameraTileCoordinates.w == 0) ? fract(tileUv.x) : clamp(tileUv.x, 0.0, 1.0);
+#else
+ tileUv.x = clamp(tileUv.x, 0.0, 1.0);
+#endif
+ tileUv.y = clamp(tileUv.y, 0.0, 1.0);
+ tileUv.z = clamp(tileUv.z, 0.0, 1.0);
+ return TileAndUvCoordinate(ivec4(tileCoordinate, u_cameraTileCoordinates.w), tileUv);
+}
diff --git a/packages/engine/Source/Shaders/Voxels/convertUvToBox.glsl b/packages/engine/Source/Shaders/Voxels/convertUvToBox.glsl
deleted file mode 100644
index e5b078bce28b..000000000000
--- a/packages/engine/Source/Shaders/Voxels/convertUvToBox.glsl
+++ /dev/null
@@ -1,45 +0,0 @@
-/* Box defines (set in Scene/VoxelBoxShape.js)
-#define BOX_HAS_SHAPE_BOUNDS
-*/
-
-#if defined(BOX_HAS_SHAPE_BOUNDS)
- uniform vec3 u_boxUvToShapeUvScale;
- uniform vec3 u_boxUvToShapeUvTranslate;
-#endif
-
-PointJacobianT convertUvToShapeSpaceDerivative(in vec3 positionUv) {
- // For BOX, UV space = shape space, so we can use positionUv as-is,
- // and the Jacobian is the identity matrix, except that a step of 1
- // only spans half the shape space [-1, 1], so the identity is scaled.
- return PointJacobianT(positionUv, mat3(0.5));
-}
-
-vec3 convertShapeToShapeUvSpace(in vec3 positionShape) {
-#if defined(BOX_HAS_SHAPE_BOUNDS)
- return positionShape * u_boxUvToShapeUvScale + u_boxUvToShapeUvTranslate;
-#else
- return positionShape;
-#endif
-}
-
-PointJacobianT convertUvToShapeUvSpaceDerivative(in vec3 positionUv) {
- PointJacobianT pointJacobian = convertUvToShapeSpaceDerivative(positionUv);
- pointJacobian.point = convertShapeToShapeUvSpace(pointJacobian.point);
- return pointJacobian;
-}
-
-vec3 convertShapeUvToUvSpace(in vec3 shapeUv) {
-#if defined(BOX_HAS_SHAPE_BOUNDS)
- return (shapeUv - u_boxUvToShapeUvTranslate) / u_boxUvToShapeUvScale;
-#else
- return shapeUv;
-#endif
-}
-
-vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
-#if defined(BOX_HAS_SHAPE_BOUNDS)
- return shapeUv / u_boxUvToShapeUvScale;
-#else
- return shapeUv;
-#endif
-}
\ No newline at end of file
diff --git a/packages/engine/Source/Shaders/Voxels/convertUvToCylinder.glsl b/packages/engine/Source/Shaders/Voxels/convertUvToCylinder.glsl
deleted file mode 100644
index 9a725785379b..000000000000
--- a/packages/engine/Source/Shaders/Voxels/convertUvToCylinder.glsl
+++ /dev/null
@@ -1,99 +0,0 @@
-/* Cylinder defines (set in Scene/VoxelCylinderShape.js)
-#define CYLINDER_HAS_SHAPE_BOUNDS_RADIUS
-#define CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT
-#define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE
-#define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY
-#define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY
-#define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED
-*/
-
-#if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
- uniform vec2 u_cylinderUvToShapeUvRadius; // x = scale, y = offset
-#endif
-#if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
- uniform vec2 u_cylinderUvToShapeUvHeight; // x = scale, y = offset
-#endif
-#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
- uniform vec2 u_cylinderUvToShapeUvAngle; // x = scale, y = offset
-#endif
-#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
- uniform vec2 u_cylinderShapeUvAngleMinMax;
-#endif
-#if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
- uniform float u_cylinderShapeUvAngleRangeZeroMid;
-#endif
-
-PointJacobianT convertUvToShapeSpaceDerivative(in vec3 positionUv) {
- // Convert from Cartesian UV space [0, 1] to Cartesian local space [-1, 1]
- vec3 position = positionUv * 2.0 - 1.0;
-
- float radius = length(position.xy); // [0, 1]
- vec3 radial = normalize(vec3(position.xy, 0.0));
-
- // Shape space height is defined within [0, 1]
- float height = positionUv.z; // [0, 1]
- vec3 z = vec3(0.0, 0.0, 1.0);
-
- float angle = atan(position.y, position.x);
- vec3 east = normalize(vec3(-position.y, position.x, 0.0));
-
- vec3 point = vec3(radius, angle, height);
- mat3 jacobianT = mat3(radial, east / length(position.xy), z);
- return PointJacobianT(point, jacobianT);
-}
-
-vec3 convertShapeToShapeUvSpace(in vec3 positionShape) {
- float radius = positionShape.x;
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
- radius = radius * u_cylinderUvToShapeUvRadius.x + u_cylinderUvToShapeUvRadius.y;
- #endif
-
- float angle = (positionShape.y + czm_pi) / czm_twoPi;
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
- // Comparing against u_cylinderShapeUvAngleMinMax has precision problems. u_cylinderShapeUvAngleRangeZeroMid is more conservative.
- angle += float(angle < u_cylinderShapeUvAngleRangeZeroMid);
- #endif
-
- // Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY)
- angle = angle > u_cylinderShapeUvAngleRangeZeroMid ? u_cylinderShapeUvAngleMinMax.x : angle;
- #elif defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
- angle = angle < u_cylinderShapeUvAngleRangeZeroMid ? u_cylinderShapeUvAngleMinMax.y : angle;
- #endif
-
- angle = angle * u_cylinderUvToShapeUvAngle.x + u_cylinderUvToShapeUvAngle.y;
- #endif
-
- float height = positionShape.z;
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
- height = height * u_cylinderUvToShapeUvHeight.x + u_cylinderUvToShapeUvHeight.y;
- #endif
-
- return vec3(radius, angle, height);
-}
-
-PointJacobianT convertUvToShapeUvSpaceDerivative(in vec3 positionUv) {
- PointJacobianT pointJacobian = convertUvToShapeSpaceDerivative(positionUv);
- pointJacobian.point = convertShapeToShapeUvSpace(pointJacobian.point);
- return pointJacobian;
-}
-
-vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
- float radius = shapeUv.x;
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
- radius /= u_cylinderUvToShapeUvRadius.x;
- #endif
-
- float angle = shapeUv.y * czm_twoPi;
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
- angle /= u_cylinderUvToShapeUvAngle.x;
- #endif
-
- float height = shapeUv.z;
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
- height /= u_cylinderUvToShapeUvHeight.x;
- #endif
-
- return vec3(radius, angle, height);
-}
diff --git a/packages/engine/Source/Shaders/Voxels/convertUvToEllipsoid.glsl b/packages/engine/Source/Shaders/Voxels/convertUvToEllipsoid.glsl
deleted file mode 100644
index bdacd1778d86..000000000000
--- a/packages/engine/Source/Shaders/Voxels/convertUvToEllipsoid.glsl
+++ /dev/null
@@ -1,139 +0,0 @@
-/* Ellipsoid defines (set in Scene/VoxelEllipsoidShape.js)
-#define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY
-#define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY
-#define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE
-#define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED
-#define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE
-*/
-
-uniform vec3 u_ellipsoidRadiiUv; // [0,1]
-uniform vec2 u_evoluteScale; // (radiiUv.x ^ 2 - radiiUv.z ^ 2) * vec2(1.0, -1.0) / radiiUv;
-uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
-#if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY) || defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
- uniform vec3 u_ellipsoidShapeUvLongitudeMinMaxMid;
-#endif
-#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
- uniform vec2 u_ellipsoidUvToShapeUvLongitude; // x = scale, y = offset
-#endif
-#if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
- uniform vec2 u_ellipsoidUvToShapeUvLatitude; // x = scale, y = offset
-#endif
-uniform float u_ellipsoidInverseHeightDifferenceUv;
-
-// robust iterative solution without trig functions
-// https://github.com/0xfaded/ellipse_demo/issues/1
-// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse
-// Extended to return radius of curvature along with the point
-vec3 nearestPointAndRadiusOnEllipse(vec2 pos, vec2 radii) {
- vec2 p = abs(pos);
- vec2 inverseRadii = 1.0 / radii;
-
- // We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t))
- // but store the cos and sin of t in a vec2 for efficiency.
- // Initial guess: t = pi/4
- vec2 tTrigs = vec2(0.7071067811865476);
- // Initial guess of point on ellipsoid
- vec2 v = radii * tTrigs;
- // Center of curvature of the ellipse at v
- vec2 evolute = u_evoluteScale * tTrigs * tTrigs * tTrigs;
-
- const int iterations = 3;
- for (int i = 0; i < iterations; ++i) {
- // Find the (approximate) intersection of p - evolute with the ellipsoid.
- vec2 q = normalize(p - evolute) * length(v - evolute);
- // Update the estimate of t.
- tTrigs = (q + evolute) * inverseRadii;
- tTrigs = normalize(clamp(tTrigs, 0.0, 1.0));
- v = radii * tTrigs;
- evolute = u_evoluteScale * tTrigs * tTrigs * tTrigs;
- }
-
- return vec3(v * sign(pos), length(v - evolute));
-}
-
-PointJacobianT convertUvToShapeSpaceDerivative(in vec3 positionUv) {
- // Convert from UV space [0, 1] to local space [-1, 1]
- vec3 position = positionUv * 2.0 - 1.0;
- // Undo the scaling from ellipsoid to sphere
- position = position * u_ellipsoidRadiiUv;
-
- float longitude = atan(position.y, position.x);
- vec3 east = normalize(vec3(-position.y, position.x, 0.0));
-
- // Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z)
- // (assume radii.y == radii.x) and find the nearest point on the ellipse and its normal
- float distanceFromZAxis = length(position.xy);
- vec2 posEllipse = vec2(distanceFromZAxis, position.z);
- vec3 surfacePointAndRadius = nearestPointAndRadiusOnEllipse(posEllipse, u_ellipsoidRadiiUv.xz);
- vec2 surfacePoint = surfacePointAndRadius.xy;
-
- vec2 normal2d = normalize(surfacePoint * u_ellipsoidInverseRadiiSquaredUv.xz);
- float latitude = atan(normal2d.y, normal2d.x);
- vec3 north = vec3(-normal2d.y * normalize(position.xy), abs(normal2d.x));
-
- float heightSign = length(posEllipse) < length(surfacePoint) ? -1.0 : 1.0;
- float height = heightSign * length(posEllipse - surfacePoint);
- vec3 up = normalize(cross(east, north));
-
- vec3 point = vec3(longitude, latitude, height);
- mat3 jacobianT = mat3(east / distanceFromZAxis, north / (surfacePointAndRadius.z + height), up);
- return PointJacobianT(point, jacobianT);
-}
-
-vec3 convertShapeToShapeUvSpace(in vec3 positionShape) {
- // Longitude: shift & scale to [0, 1]
- float longitude = (positionShape.x + czm_pi) / czm_twoPi;
-
- // Correct the angle when max < min
- // Technically this should compare against min longitude - but it has precision problems so compare against the middle of empty space.
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
- longitude += float(longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z);
- #endif
-
- // Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY)
- longitude = longitude > u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.x : longitude;
- #endif
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY)
- longitude = longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.y : longitude;
- #endif
-
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
- longitude = longitude * u_ellipsoidUvToShapeUvLongitude.x + u_ellipsoidUvToShapeUvLongitude.y;
- #endif
-
- // Latitude: shift and scale to [0, 1]
- float latitude = (positionShape.y + czm_piOverTwo) / czm_pi;
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
- latitude = latitude * u_ellipsoidUvToShapeUvLatitude.x + u_ellipsoidUvToShapeUvLatitude.y;
- #endif
-
- // Height: scale to the range [0, 1]
- float height = 1.0 + positionShape.z * u_ellipsoidInverseHeightDifferenceUv;
-
- return vec3(longitude, latitude, height);
-}
-
-PointJacobianT convertUvToShapeUvSpaceDerivative(in vec3 positionUv) {
- PointJacobianT pointJacobian = convertUvToShapeSpaceDerivative(positionUv);
- pointJacobian.point = convertShapeToShapeUvSpace(pointJacobian.point);
- return pointJacobian;
-}
-
-vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
- // Convert from [0, 1] to radians [-pi, pi]
- float longitude = shapeUv.x * czm_twoPi;
- #if defined (ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
- longitude /= u_ellipsoidUvToShapeUvLongitude.x;
- #endif
-
- // Convert from [0, 1] to radians [-pi/2, pi/2]
- float latitude = shapeUv.y * czm_pi;
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
- latitude /= u_ellipsoidUvToShapeUvLatitude.x;
- #endif
-
- float height = shapeUv.z / u_ellipsoidInverseHeightDifferenceUv;
-
- return vec3(longitude, latitude, height);
-}
diff --git a/packages/engine/Specs/Scene/VoxelEllipsoidShapeSpec.js b/packages/engine/Specs/Scene/VoxelEllipsoidShapeSpec.js
index bfd80ac66e58..5f6e21457784 100644
--- a/packages/engine/Specs/Scene/VoxelEllipsoidShapeSpec.js
+++ b/packages/engine/Specs/Scene/VoxelEllipsoidShapeSpec.js
@@ -90,17 +90,17 @@ describe("Scene/VoxelEllipsoidShape", function () {
).toEqualEpsilon(expectedOrientedBoundingBox.center, CesiumMath.EPSILON12);
const expectedShapeTransform = Matrix4.fromRowMajorArray([
- (scale.x + maxHeight) * Math.cos(angle),
- -(scale.x + maxHeight) * Math.sin(angle),
+ Math.cos(angle),
+ -Math.sin(angle),
0.0,
expectedOrientedBoundingBox.center.x,
- (scale.y + maxHeight) * Math.sin(angle),
- (scale.y + maxHeight) * Math.cos(angle),
+ Math.sin(angle),
+ Math.cos(angle),
0.0,
expectedOrientedBoundingBox.center.y,
0.0,
0.0,
- scale.z + maxHeight,
+ 1.0,
expectedOrientedBoundingBox.center.z,
0.0,
0.0,
diff --git a/packages/engine/Specs/Scene/buildVoxelDrawCommandsSpec.js b/packages/engine/Specs/Scene/buildVoxelDrawCommandsSpec.js
index d1aea9c50b59..47ae2b39250a 100644
--- a/packages/engine/Specs/Scene/buildVoxelDrawCommandsSpec.js
+++ b/packages/engine/Specs/Scene/buildVoxelDrawCommandsSpec.js
@@ -52,7 +52,7 @@ describe("Scene/buildVoxelDrawCommands", function () {
const { shaderProgram } = primitive._drawCommand;
const fragmentShaderText = shaderProgram._fragmentShaderText;
const clippingFunctionSignature =
- "vec4 getClippingPlane(highp sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)";
+ "vec4 getClippingPlane(highp sampler2D packedPlanes, int planeNumber)";
expect(fragmentShaderText.includes(clippingFunctionSignature)).toBe(true);
});
diff --git a/packages/sandcastle/gallery/voxel-picking/main.js b/packages/sandcastle/gallery/voxel-picking/main.js
index f9d1d584d9a5..f2c643ffdb2e 100644
--- a/packages/sandcastle/gallery/voxel-picking/main.js
+++ b/packages/sandcastle/gallery/voxel-picking/main.js
@@ -35,16 +35,16 @@ function ProceduralMultiTileVoxelProvider(shape) {
this.names = ["color"];
this.types = [Cesium.MetadataType.VEC4];
this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
- this._levelCount = 3;
+ this.availableLevels = 3;
this.globalTransform = globalTransform;
}
ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
const { tileLevel, tileX, tileY, tileZ } = options;
- if (tileLevel >= this._levelCount) {
+ if (tileLevel >= this.availableLevels) {
return Promise.reject(
- `No tiles available beyond level ${this._levelCount}`,
+ `No tiles available beyond level ${this.availableLevels - 1}`,
);
}
@@ -129,6 +129,7 @@ function createPrimitive(provider) {
customShader: customShader,
});
voxelPrimitive.nearestSampling = true;
+ voxelPrimitive.stepSize = 0.7;
viewer.scene.primitives.add(voxelPrimitive);
camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, {
diff --git a/packages/sandcastle/gallery/voxels/main.js b/packages/sandcastle/gallery/voxels/main.js
index 283579416b28..3b88480f81b1 100644
--- a/packages/sandcastle/gallery/voxels/main.js
+++ b/packages/sandcastle/gallery/voxels/main.js
@@ -92,14 +92,14 @@ function ProceduralMultiTileVoxelProvider(shape) {
this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
this.globalTransform = globalTransform;
- this._levelCount = 2;
- this._allVoxelData = new Array(this._levelCount);
+ this.availableLevels = 2;
+ this._allVoxelData = new Array(this.availableLevels);
const allVoxelData = this._allVoxelData;
const channelCount = Cesium.MetadataType.getComponentCount(this.types[0]);
const { dimensions } = this;
- for (let level = 0; level < this._levelCount; level++) {
+ for (let level = 0; level < this.availableLevels; level++) {
const dimAtLevel = Math.pow(2, level);
const voxelCountX = dimensions.x * dimAtLevel;
const voxelCountY = dimensions.y * dimAtLevel;
@@ -127,9 +127,9 @@ function ProceduralMultiTileVoxelProvider(shape) {
ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
const { tileLevel, tileX, tileY, tileZ } = options;
- if (tileLevel >= this._levelCount) {
+ if (tileLevel >= this.availableLevels) {
return Promise.reject(
- `No tiles available beyond level ${this._levelCount - 1}`,
+ `No tiles available beyond level ${this.availableLevels - 1}`,
);
}