Skip to content

Commit

Permalink
Initial submit for Issue CesiumGS#93, Scale billboards based on dista…
Browse files Browse the repository at this point in the history
…nce. Includes minor fix for setting compiledShaderRotationPick variable.

Signed-off-by: Alex Wood <[email protected]>
  • Loading branch information
abwood committed Aug 23, 2013
1 parent 6c9fe76 commit b47169a
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 8 deletions.
30 changes: 30 additions & 0 deletions Apps/Sandcastle/gallery/Billboards.html
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,26 @@
});
}

function scaleBillboardByDistance(scene, ellipsoid) {
Sandcastle.declare(scaleBillboardByDistance); // For highlighting in Sandcastle.
var image = new Image();
image.onload = function() {
var billboards = new Cesium.BillboardCollection();
var textureAtlas = scene.getContext().createTextureAtlas({
image : image
});
billboards.setTextureAtlas(textureAtlas);

billboards.add({
position : ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-75.59777, 40.03883)),
imageIndex : 0,
scaleByDistance : [1e5, 3.0, 1e7, 0.5]
});
scene.getPrimitives().add(billboards);
};
image.src = '../images/Cesium_Logo_overlay.png';
}

function addPointBillboards(scene, ellipsoid) {
Sandcastle.declare(addPointBillboards); // For highlighting in Sandcastle.
// A white circle is drawn into a 2D canvas. The canvas is used as
Expand Down Expand Up @@ -400,6 +420,16 @@
};
button.textContent = 'Add billboards in reference frame';
toolbar.appendChild(button);

button = document.createElement('button');
button.className = 'button';
button.onclick = function() {
primitives.removeAll();
scaleBillboardByDistance(scene, ellipsoid);
Sandcastle.highlight(scaleBillboardByDistance);
};
button.textContent = 'Scale Billboards based on distance to camera';
toolbar.appendChild(button);
}

var widget = new Cesium.CesiumWidget('cesiumContainer');
Expand Down
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ var geometry = BoxGeometry.createGeometry(box);
* Optimized polyline bounding spheres.
* `Viewer` now automatically sets its clock to that of the first added `DataSource`, regardless of how it was added to the `DataSourceCollection`. Previously, this was only done for dropped files by `viewerDragDropMixin`.
* Upgraded Knockout from version 2.2.1 to 2.3.0.
* Added `Billboard.scaleByDistance` to control Billboard min/max scale based on Camera distance to Billboard.

### b19 - 2013-08-01

Expand Down
54 changes: 53 additions & 1 deletion Source/Scene/Billboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ define([
this._alignedAxis = Cartesian3.clone(defaultValue(description.alignedAxis, Cartesian3.ZERO));
this._width = description.width;
this._height = description.height;
// eyeDistance_min, scale_min, eyeDistance_max, scale_max
this._scaleByDistance = defaultValue(description.scaleByDistance, [5.0e6, 1.0, 2.0e7, 0.0]);

this._pickId = undefined;
this._pickIdThis = description._pickIdThis;
Expand All @@ -94,7 +96,8 @@ define([
var COLOR_INDEX = Billboard.COLOR_INDEX = 8;
var ROTATION_INDEX = Billboard.ROTATION_INDEX = 9;
var ALIGNED_AXIS_INDEX = Billboard.ALIGNED_AXIS_INDEX = 10;
Billboard.NUMBER_OF_PROPERTIES = 11;
var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX = 11;
Billboard.NUMBER_OF_PROPERTIES = 12;

function makeDirty(billboard, propertyChanged) {
var billboardCollection = billboard._billboardCollection;
Expand Down Expand Up @@ -265,6 +268,55 @@ define([
}
};

/**
* Returns the min and max scaling properties of a Billboard based on the billboard's distance from the camera.
*
* @memberof Billboard
*
* @return {float[]} The scaling values based on range packed into an array of floats as follows,
* [minDistance, scaleAtMinDistance, maxDistance, scaleAtMaxDistance].
*
* @see Billboard#setScaleByDistance
*/
Billboard.prototype.getScaleByDistance = function() {
return this._scaleByDistance;
};

/**
* Sets min and max scaling properties of a Billboard based on the billboard's distance from the camera.
* A billboard's scale will interpolate between the min and max scales while the camera distance falls
* within the upper and lower bounds of the specified camera distance cutoffs. Outside of these ranges
* the billboard's scale remains clamped to the nearest scale.
*
* @memberof Billboard
*
* @param {float} minDistance The lower bound of the camera distance threshold (min range of camera to billboard)
* @param {float} scaleAtMinDistance The scale of the billboard at the lower bound of the camera distance threshold.
* @param {float} maxDistance The upper bound of the camera distance threshold (max range of camera to billboard)
* @param {float} scaleAtMaxDistance The scale of the billboard at the upper bound of the camera distance threshold.
*
* @see Billboard#getScaleByDistance
*
* @example
* // Example 1. Set a billboard's scaleByDistance to
* // scale by 3.0 when the camera is 1.0e6 meters from the billboard
* // and disappear as the camera distance approaches 1e7 meters
* b.setScaleByDistance(1.0e6, 3.0, 1e7, 0.0);
*
*/
Billboard.prototype.setScaleByDistance = function(minDistance, scaleAtMinDistance, maxDistance, scaleAtMaxDistance) {
if (!defined(minDistance) || !defined(scaleAtMinDistance) || !defined(maxDistance) || !defined(scaleAtMaxDistance)) {
throw new DeveloperError('Requires minScale, maxScale and minRange, maxRange ranges to be specified.');
}

if (maxDistance < minDistance) {
throw new DeveloperError('maxDistance must be greater than minDistance.');
}

this._scaleByDistance = [minDistance, scaleAtMinDistance, maxDistance, scaleAtMaxDistance];
makeDirty(this, SCALE_BY_DISTANCE_INDEX);
};

/**
* Returns the 3D Cartesian offset applied to this billboard in eye coordinates.
*
Expand Down
64 changes: 57 additions & 7 deletions Source/Scene/BillboardCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ define([
var COLOR_INDEX = Billboard.COLOR_INDEX;
var ROTATION_INDEX = Billboard.ROTATION_INDEX;
var ALIGNED_AXIS_INDEX = Billboard.ALIGNED_AXIS_INDEX;
var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX;
var NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES;

// PERFORMANCE_IDEA: Use vertex compression so we don't run out of
Expand All @@ -74,7 +75,8 @@ define([
direction : 6,
pickColor : 7, // pickColor and color shared an index because pickColor is only used during
color : 7, // the 'pick' pass and 'color' is only used during the 'color' pass.
rotationAndAlignedAxis : 8
rotationAndAlignedAxis : 8,
scaleByDistance : 9
};

// Identifies to the VertexArrayFacade the attributes that are used only for the pick
Expand Down Expand Up @@ -148,6 +150,10 @@ define([
this._compiledShaderRotation = false;
this._compiledShaderRotationPick = false;

this._shaderScaleByDistance = false;
this._compiledShaderScaleByDistance = false;
this._compiledShaderScaleByDistancePick = false;

this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);

this._maxSize = 0.0;
Expand Down Expand Up @@ -204,7 +210,8 @@ define([
BufferUsage.STATIC_DRAW, // IMAGE_INDEX_INDEX
BufferUsage.STATIC_DRAW, // COLOR_INDEX
BufferUsage.STATIC_DRAW, // ROTATION_INDEX
BufferUsage.STATIC_DRAW // ALIGNED_AXIS_INDEX
BufferUsage.STATIC_DRAW, // ALIGNED_AXIS_INDEX
BufferUsage.STATIC_DRAW // SCALE_BY_DISTANCE_INDEX
];

var that = this;
Expand Down Expand Up @@ -246,6 +253,7 @@ define([
* horizontalOrigin : HorizontalOrigin.CENTER,
* verticalOrigin : VerticalOrigin.CENTER,
* scale : 1.0,
* scaleByDistance : [5e6, 1.0, 2e7, 0.0]
* imageIndex : 0,
* color : Color.WHITE
* });
Expand Down Expand Up @@ -670,6 +678,11 @@ define([
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[ROTATION_INDEX] // buffersUsage[ALIGNED_AXIS_INDEX] ignored
}, {
index : attributeIndices.scaleByDistance,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[SCALE_BY_DISTANCE_INDEX]
}], 4 * numberOfBillboards); // 4 vertices per billboard
}

Expand Down Expand Up @@ -849,6 +862,29 @@ define([
writer(i + 3, rotation, x, y, z);
}

function writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {

var i = billboard._index * 4;
var scaleByViewerDistance = billboard.getScaleByDistance();

// if both scales are 1.0 and 1.0, then scaling by distance computation is not necessary
if (scaleByViewerDistance[1] !== 1.0 || scaleByViewerDistance[3] !== 1.0) {
billboardCollection._shaderScaleByDistance = true;
}

var minDistance = scaleByViewerDistance[0];
var scaleAtMinDist = scaleByViewerDistance[1];
var maxDistance = scaleByViewerDistance[2];
var scaleAtMaxDist = scaleByViewerDistance[3];

var allPurposeWriters = vafWriters[allPassPurpose];
var writer = allPurposeWriters[attributeIndices.scaleByDistance];
writer(i + 0, minDistance, scaleAtMinDist, maxDistance, scaleAtMaxDist);
writer(i + 1, minDistance, scaleAtMinDist, maxDistance, scaleAtMaxDist);
writer(i + 2, minDistance, scaleAtMinDist, maxDistance, scaleAtMaxDist);
writer(i + 3, minDistance, scaleAtMinDist, maxDistance, scaleAtMaxDist);
}

function writeBillboard(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
writePosition(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writePixelOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
Expand All @@ -858,6 +894,7 @@ define([
writeOriginAndShow(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writeTextureCoordinatesAndImageSize(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writeRotationAndAlignedAxis(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
}

function recomputeActualPositions(billboardCollection, billboards, length, frameState, modelMatrix, recomputeBoundingVolume) {
Expand Down Expand Up @@ -1043,6 +1080,10 @@ define([
writers.push(writeRotationAndAlignedAxis);
}

if (properties[SCALE_BY_DISTANCE_INDEX]) {
writers.push(writeScaleByDistance);
}

vafWriters = this._vaf.writers;

if ((billboardsToUpdateLength / billboardsLength) > 0.1) {
Expand Down Expand Up @@ -1117,13 +1158,17 @@ define([
});
}

if (!defined(this._sp) || (this._shaderRotation && !this._compiledShaderRotation)) {
if (!defined(this._sp) || (this._shaderRotation && !this._compiledShaderRotation) ||
(this._shaderScaleByDistance && !this._compiledShaderScaleByDistance)) {
this._sp = context.getShaderCache().replaceShaderProgram(
this._sp,
(this._shaderRotation ? '#define ROTATION 1\n' : '') + BillboardCollectionVS,
(this._shaderRotation ? '#define ROTATION 1\n' : '') +
(this._shaderScaleByDistance ? '#define EYE_DISTANCE_SCALING 1\n' : '') +
BillboardCollectionVS,
BillboardCollectionFS,
attributeIndices);
this._compiledShaderRotation = this._shaderRotation;
this._compiledShaderScaleByDistance = this._shaderScaleByDistance;
}

va = this._vaf.vaByPurpose[colorPassPurpose];
Expand Down Expand Up @@ -1151,13 +1196,18 @@ define([
var pickList = this._pickCommands;
commandLists.pickList = pickList;

if (!defined(this._spPick) || (this._shaderRotation && !this._compiledShaderRotationPick)) {
if (!defined(this._spPick) ||
(this._shaderRotation && !this._compiledShaderRotationPick) ||
(this._shaderScaleByDistance && !this._compiledShaderScaleByDistancePick)) {
this._spPick = context.getShaderCache().replaceShaderProgram(
this._spPick,
(this._shaderRotation ? '#define ROTATION 1\n' : '') + '#define RENDER_FOR_PICK 1\n' + BillboardCollectionVS,
(this._shaderRotation ? '#define ROTATION 1\n' : '') +
(this._shaderScaleByDistance ? '#define EYE_DISTANCE_SCALING 1\n' : '') +
'#define RENDER_FOR_PICK 1\n' + BillboardCollectionVS,
'#define RENDER_FOR_PICK 1\n' + BillboardCollectionFS,
attributeIndices);
this._compiledShaderRotation = this._shaderRotationPick;
this._compiledShaderRotationPick = this._shaderRotation;
this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance;
}

va = this._vaf.vaByPurpose[pickPassPurpose];
Expand Down
3 changes: 3 additions & 0 deletions Source/Scene/LabelCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ define([
billboard.setVerticalOrigin(label._verticalOrigin);
billboard.setScale(label._scale);
billboard._pickIdThis = label;
// until text billboard scaling is fixed, disable the scaleByDistance
// (scaling from 1.0 to 1.0)
billboard.setScaleByDistance(1.0, 1.0, 1.0e10, 1.0);
}

glyph.billboard.setImageIndex(glyphTextureInfo.index);
Expand Down
20 changes: 20 additions & 0 deletions Source/Shaders/BillboardCollectionVS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ attribute vec3 originAndShow; // show is 0.0 (false) or 1.0 (t
attribute vec2 pixelOffset;
attribute vec4 eyeOffsetAndScale; // eye offset in meters
attribute vec4 rotationAndAlignedAxis;
attribute vec4 scaleByDistance; // minRangeFromEye, scaleAtMinRange, maxRangeFromEye, scaleAtMaxRange

#ifdef RENDER_FOR_PICK
attribute vec4 pickColor;
Expand Down Expand Up @@ -44,6 +45,25 @@ void main()

///////////////////////////////////////////////////////////////////////////

#ifdef EYE_DISTANCE_SCALING // scale based on eye distance
// transform into 3d eye coordinates. This methodology will even translate for
// 2D mode into a 3D position, but is only valid for the distance calculations below
vec4 position3dInEC = czm_view3D * (vec4(positionHigh, 1) + vec4(positionLow,0));
float lengthSq = dot(position3dInEC.xyz, position3dInEC.xyz);
float scaleAtMin = scaleByDistance.y;
float scaleAtMax = scaleByDistance.w;
float minDistanceFromEyeSq = scaleByDistance.x*scaleByDistance.x;
float maxDistanceFromEyeSq = scaleByDistance.z*scaleByDistance.z;
// ensure that t will fall within the range of [0.0, 1.0]
lengthSq = clamp(lengthSq, minDistanceFromEyeSq, maxDistanceFromEyeSq);
float t = (lengthSq-minDistanceFromEyeSq)/(maxDistanceFromEyeSq-minDistanceFromEyeSq);
// dampen the exponential distance traveled by the camera
// could be interesting to assign this exponent to a uniform for advanced control of interpolation
t = pow(t, 0.5);

scale *= mix(scaleAtMin, scaleAtMax, t);
#endif

vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);

vec2 halfSize = imageSize * scale * czm_highResolutionSnapScale;
Expand Down
22 changes: 22 additions & 0 deletions Specs/Scene/BillboardCollectionSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ defineSuite([
expect(b.getColor().alpha).toEqual(1.0);
expect(b.getRotation()).toEqual(0.0);
expect(b.getAlignedAxis()).toEqual(Cartesian3.ZERO);
expect(b.getScaleByDistance()).toEqual([5.0e6, 1.0, 2.0e7, 0.0]);
expect(b.getWidth()).not.toBeDefined();
expect(b.getHeight()).not.toBeDefined();
});
Expand All @@ -143,6 +144,7 @@ defineSuite([
},
rotation : 1.0,
alignedAxis : new Cartesian3(1.0, 2.0, 3.0),
scaleByDistance : [1.0, 3.0, 1.0e6, 0.0],
width : 300.0,
height : 200.0
});
Expand All @@ -161,6 +163,7 @@ defineSuite([
expect(b.getColor().alpha).toEqual(4.0);
expect(b.getRotation()).toEqual(1.0);
expect(b.getAlignedAxis()).toEqual(new Cartesian3(1.0, 2.0, 3.0));
expect(b.getScaleByDistance()).toEqual([1.0, 3.0, 1.0e6, 0.0]);
expect(b.getWidth()).toEqual(300.0);
expect(b.getHeight()).toEqual(200.0);
});
Expand All @@ -185,6 +188,7 @@ defineSuite([
b.setAlignedAxis(new Cartesian3(1.0, 2.0, 3.0));
b.setWidth(300.0);
b.setHeight(200.0);
b.setScaleByDistance(1.0e6, 3.0, 1.0e8, 0.0);

expect(b.getShow()).toEqual(false);
expect(b.getPosition()).toEqual(new Cartesian3(1.0, 2.0, 3.0));
Expand All @@ -200,10 +204,28 @@ defineSuite([
expect(b.getColor().alpha).toEqual(4.0);
expect(b.getRotation()).toEqual(1.0);
expect(b.getAlignedAxis()).toEqual(new Cartesian3(1.0, 2.0, 3.0));
expect(b.getScaleByDistance()).toEqual([1.0e6, 3.0, 1.0e8, 0.0]);
expect(b.getWidth()).toEqual(300.0);
expect(b.getHeight()).toEqual(200.0);
});

it('disable scale by distance', function() {
var b = billboards.add();
b.setScaleByDistance(1.0e6, 1.0, 1.0e8, 1.0);
render(context, frameState, billboards);
expect(billboards._shaderScaleByDistance).toEqual(false);
expect(billboards._compiledShaderScaleByDistance).toEqual(false);
});

it('throws setScaleByDistance with minRange > maxRange', function() {
var b = billboards.add();

expect(function() {
b.setScaleByDistance(1.0e9, 1.0, 1.0e5, 1.0);
}).toThrow();

});

it('throws with non number Index', function() {
var b = billboards.add();
expect(function() {
Expand Down

0 comments on commit b47169a

Please sign in to comment.