Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix wall negative height #9022

Closed
wants to merge 12 commits into from
397 changes: 203 additions & 194 deletions Source/Core/WallGeometry.js
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import PrimitiveType from "./PrimitiveType.js";
import VertexFormat from "./VertexFormat.js";
import WallGeometryLibrary from "./WallGeometryLibrary.js";

var scratchCartesian3Position0 = new Cartesian3();
var scratchCartesian3Position1 = new Cartesian3();
var scratchCartesian3Position2 = new Cartesian3();
var scratchCartesian3Position3 = new Cartesian3();
@@ -347,195 +348,44 @@ WallGeometry.fromConstantHeights = function (options) {
return new WallGeometry(newOptions);
};

/**
* Computes the geometric representation of a wall, including its vertices, indices, and a bounding sphere.
*
* @param {WallGeometry} wallGeometry A description of the wall.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
WallGeometry.createGeometry = function (wallGeometry) {
var wallPositions = wallGeometry._positions;
var minimumHeights = wallGeometry._minimumHeights;
var maximumHeights = wallGeometry._maximumHeights;
var vertexFormat = wallGeometry._vertexFormat;
var granularity = wallGeometry._granularity;
var ellipsoid = wallGeometry._ellipsoid;

var pos = WallGeometryLibrary.computePositions(
ellipsoid,
wallPositions,
maximumHeights,
minimumHeights,
granularity,
true
);
if (!defined(pos)) {
return;
function calculateDirection(p0, p1, result) {
if (Cartesian3.equalsEpsilon(p0, p1, CesiumMath.EPSILON10)) {
return false;
}

var bottomPositions = pos.bottomPositions;
var topPositions = pos.topPositions;
var numCorners = pos.numCorners;

var length = topPositions.length;
var size = length * 2;

var positions = vertexFormat.position ? new Float64Array(size) : undefined;
var normals = vertexFormat.normal ? new Float32Array(size) : undefined;
var tangents = vertexFormat.tangent ? new Float32Array(size) : undefined;
var bitangents = vertexFormat.bitangent ? new Float32Array(size) : undefined;
var textureCoordinates = vertexFormat.st
? new Float32Array((size / 3) * 2)
: undefined;

var positionIndex = 0;
var normalIndex = 0;
var bitangentIndex = 0;
var tangentIndex = 0;
var stIndex = 0;

// add lower and upper points one after the other, lower
// points being even and upper points being odd
var normal = scratchNormal;
var tangent = scratchTangent;
var bitangent = scratchBitangent;
var recomputeNormal = true;
length /= 3;
var i;
var s = 0;
var ds = 1 / (length - wallPositions.length + 1);
for (i = 0; i < length; ++i) {
var i3 = i * 3;
var topPosition = Cartesian3.fromArray(
topPositions,
i3,
scratchCartesian3Position1
);
var bottomPosition = Cartesian3.fromArray(
bottomPositions,
i3,
scratchCartesian3Position2
);
if (vertexFormat.position) {
// insert the lower point
positions[positionIndex++] = bottomPosition.x;
positions[positionIndex++] = bottomPosition.y;
positions[positionIndex++] = bottomPosition.z;

// insert the upper point
positions[positionIndex++] = topPosition.x;
positions[positionIndex++] = topPosition.y;
positions[positionIndex++] = topPosition.z;
}

if (vertexFormat.st) {
textureCoordinates[stIndex++] = s;
textureCoordinates[stIndex++] = 0.0;

textureCoordinates[stIndex++] = s;
textureCoordinates[stIndex++] = 1.0;
}

if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) {
var nextPosition;
var nextTop = Cartesian3.clone(
Cartesian3.ZERO,
scratchCartesian3Position5
);
var groundPosition = ellipsoid.scaleToGeodeticSurface(
Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position2),
scratchCartesian3Position2
);
if (i + 1 < length) {
nextPosition = ellipsoid.scaleToGeodeticSurface(
Cartesian3.fromArray(
topPositions,
i3 + 3,
scratchCartesian3Position3
),
scratchCartesian3Position3
);
nextTop = Cartesian3.fromArray(
topPositions,
i3 + 3,
scratchCartesian3Position5
);
}

if (recomputeNormal) {
var scalednextPosition = Cartesian3.subtract(
nextTop,
topPosition,
scratchCartesian3Position4
);
var scaledGroundPosition = Cartesian3.subtract(
groundPosition,
topPosition,
scratchCartesian3Position1
);
normal = Cartesian3.normalize(
Cartesian3.cross(scaledGroundPosition, scalednextPosition, normal),
normal
);
recomputeNormal = false;
}

if (
Cartesian3.equalsEpsilon(
nextPosition,
groundPosition,
CesiumMath.EPSILON10
)
) {
recomputeNormal = true;
} else {
s += ds;
if (vertexFormat.tangent) {
tangent = Cartesian3.normalize(
Cartesian3.subtract(nextPosition, groundPosition, tangent),
tangent
);
}
if (vertexFormat.bitangent) {
bitangent = Cartesian3.normalize(
Cartesian3.cross(normal, tangent, bitangent),
bitangent
);
}
}

if (vertexFormat.normal) {
normals[normalIndex++] = normal.x;
normals[normalIndex++] = normal.y;
normals[normalIndex++] = normal.z;

normals[normalIndex++] = normal.x;
normals[normalIndex++] = normal.y;
normals[normalIndex++] = normal.z;
}

if (vertexFormat.tangent) {
tangents[tangentIndex++] = tangent.x;
tangents[tangentIndex++] = tangent.y;
tangents[tangentIndex++] = tangent.z;
result = Cartesian3.normalize(Cartesian3.subtract(p0, p1, result), result);
return true;
}

tangents[tangentIndex++] = tangent.x;
tangents[tangentIndex++] = tangent.y;
tangents[tangentIndex++] = tangent.z;
}
function calculateNormal(p0, p1, p2, resultP10, resultP20, result) {
if (!calculateDirection(p1, p0, resultP10)) {
return false;
}

if (vertexFormat.bitangent) {
bitangents[bitangentIndex++] = bitangent.x;
bitangents[bitangentIndex++] = bitangent.y;
bitangents[bitangentIndex++] = bitangent.z;
if (!calculateDirection(p2, p0, resultP20)) {
return false;
}

bitangents[bitangentIndex++] = bitangent.x;
bitangents[bitangentIndex++] = bitangent.y;
bitangents[bitangentIndex++] = bitangent.z;
}
}
var angle = Cartesian3.dot(resultP10, resultP20);
if (CesiumMath.equalsEpsilon(Math.abs(angle), 1.0, CesiumMath.EPSILON10)) {
return false;
}

result = Cartesian3.normalize(
Cartesian3.cross(resultP10, resultP20, result),
result
);
return true;
}

function createWallGeometryAttributes(
vertexFormat,
positions,
normals,
tangents,
bitangents,
textureCoordinates
) {
var attributes = new GeometryAttributes();

if (vertexFormat.position) {
@@ -578,6 +428,10 @@ WallGeometry.createGeometry = function (wallGeometry) {
});
}

return attributes;
}

function createWallGeometryIndices(positions, numCorners) {
// prepare the side walls, two triangles for each wall
//
// A (i+1) B (i+3) E
@@ -591,25 +445,19 @@ WallGeometry.createGeometry = function (wallGeometry) {
// +--------+-------+
// C (i) D (i+2) F
//

var size = positions.length;
var numVertices = size / 3;
size -= 6 * (numCorners + 1);
var indices = IndexDatatype.createTypedArray(numVertices, size);

var scratchPosition1 = new Cartesian3();
var scratchPosition2 = new Cartesian3();
var edgeIndex = 0;
for (i = 0; i < numVertices - 2; i += 2) {
for (var i = 0; i < numVertices - 2; i += 2) {
var LL = i;
var LR = i + 2;
var pl = Cartesian3.fromArray(
positions,
LL * 3,
scratchCartesian3Position1
);
var pr = Cartesian3.fromArray(
positions,
LR * 3,
scratchCartesian3Position2
);
var pl = Cartesian3.fromArray(positions, LL * 3, scratchPosition1);
var pr = Cartesian3.fromArray(positions, LR * 3, scratchPosition2);
if (Cartesian3.equalsEpsilon(pl, pr, CesiumMath.EPSILON10)) {
continue;
}
@@ -624,6 +472,167 @@ WallGeometry.createGeometry = function (wallGeometry) {
indices[edgeIndex++] = LR;
}

return indices;
}

/**
* Computes the geometric representation of a wall, including its vertices, indices, and a bounding sphere.
*
* @param {WallGeometry} wallGeometry A description of the wall.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
WallGeometry.createGeometry = function (wallGeometry) {
var wallPositions = wallGeometry._positions;
var minimumHeights = wallGeometry._minimumHeights;
var maximumHeights = wallGeometry._maximumHeights;
var vertexFormat = wallGeometry._vertexFormat;
var granularity = wallGeometry._granularity;
var ellipsoid = wallGeometry._ellipsoid;

var pos = WallGeometryLibrary.computePositions(
ellipsoid,
wallPositions,
maximumHeights,
minimumHeights,
granularity,
true
);
if (!defined(pos)) {
return;
}

var bottomPositions = pos.bottomPositions;
var topPositions = pos.topPositions;
var numCorners = pos.numCorners;

var length = topPositions.length;
var size = length * 2;

var positions = vertexFormat.position ? new Float64Array(size) : undefined;
var normals = vertexFormat.normal ? new Float32Array(size) : undefined;
var tangents = vertexFormat.tangent ? new Float32Array(size) : undefined;
var bitangents = vertexFormat.bitangent ? new Float32Array(size) : undefined;
var textureCoordinates = vertexFormat.st
? new Float32Array((size / 3) * 2)
: undefined;

var positionIndex = 0;
var normalIndex = 0;
var bitangentIndex = 0;
var tangentIndex = 0;
var stIndex = 0;

// add lower and upper points one after the other, lower
// points being even and upper points being odd
var topPosition = scratchCartesian3Position0;
var bottomPosition = scratchCartesian3Position1;
var nextTopPosition = scratchCartesian3Position2;
var nextBottomPosition = scratchCartesian3Position3;
var topToBottom = scratchCartesian3Position4;
var topToNextTop = scratchCartesian3Position5;

var normal = Cartesian3.clone(Cartesian3.UNIT_Y, scratchNormal);
var tangent = Cartesian3.clone(Cartesian3.UNIT_X, scratchTangent);
var bitangent = Cartesian3.clone(Cartesian3.UNIT_Z, scratchBitangent);

var isWallCorner = false;
var validNormal = false;
var s = 0;
var ds = 1.0 / (length / 3.0 - numCorners - 1.0);
for (var i = 0; i < length; i += 3) {
var curr = i;
var next = i + 3;
Cartesian3.fromArray(topPositions, curr, topPosition);
Cartesian3.fromArray(bottomPositions, curr, bottomPosition);

if (
next < length &&
(vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent)
) {
Cartesian3.fromArray(topPositions, next, nextTopPosition);
Cartesian3.fromArray(bottomPositions, next, nextBottomPosition);
isWallCorner = Cartesian3.equalsEpsilon(
bottomPosition,
nextBottomPosition,
CesiumMath.EPSILON10
);

if (!validNormal || isWallCorner) {
validNormal = calculateNormal(
topPosition,
bottomPosition,
nextTopPosition,
topToBottom,
topToNextTop,
normal
);
}

if (validNormal && !isWallCorner) {
if (vertexFormat.tangent || vertexFormat.bitangent) {
tangent = Cartesian3.normalize(
Cartesian3.subtract(nextBottomPosition, bottomPosition, tangent),
tangent
);
}

if (vertexFormat.bitangent) {
bitangent = Cartesian3.normalize(
Cartesian3.cross(normal, tangent, bitangent),
bitangent
);
}
}
}

if (vertexFormat.position) {
Cartesian3.pack(bottomPosition, positions, positionIndex);
Cartesian3.pack(topPosition, positions, positionIndex + 3);
positionIndex += 6;
}

if (vertexFormat.normal) {
Cartesian3.pack(normal, normals, normalIndex);
Cartesian3.pack(normal, normals, normalIndex + 3);
normalIndex += 6;
}

if (vertexFormat.tangent) {
Cartesian3.pack(tangent, tangents, tangentIndex);
Cartesian3.pack(tangent, tangents, tangentIndex + 3);
tangentIndex += 6;
}

if (vertexFormat.bitangent) {
Cartesian3.pack(bitangent, bitangents, bitangentIndex);
Cartesian3.pack(bitangent, bitangents, bitangentIndex + 3);
bitangentIndex += 6;
}

if (vertexFormat.st) {
textureCoordinates[stIndex++] = s;
textureCoordinates[stIndex++] = 0.0;

textureCoordinates[stIndex++] = s;
textureCoordinates[stIndex++] = 1.0;

if (!isWallCorner) {
s += ds;
}
}
}

var indices = createWallGeometryIndices(positions, numCorners);

var attributes = createWallGeometryAttributes(
vertexFormat,
positions,
normals,
tangents,
bitangents,
textureCoordinates
);

return new Geometry({
attributes: attributes,
indices: indices,
24 changes: 15 additions & 9 deletions Source/Core/WallGeometryLibrary.js
Original file line number Diff line number Diff line change
@@ -26,25 +26,20 @@ function removeDuplicates(ellipsoid, positions, topHeights, bottomHeights) {
if (length < 2) {
return;
}

var hasBottomHeights = defined(bottomHeights);
var hasTopHeights = defined(topHeights);
var hasAllZeroHeights = true;

var cleanedPositions = new Array(length);
var cleanedTopHeights = new Array(length);
var cleanedBottomHeights = new Array(length);

var v0 = positions[0];
cleanedPositions[0] = v0;

var c0 = ellipsoid.cartesianToCartographic(v0, scratchCartographic1);
if (hasTopHeights) {
c0.height = topHeights[0];
}

hasAllZeroHeights = hasAllZeroHeights && c0.height <= 0;

cleanedPositions[0] = v0;
cleanedTopHeights[0] = c0.height;

if (hasBottomHeights) {
@@ -54,13 +49,13 @@ function removeDuplicates(ellipsoid, positions, topHeights, bottomHeights) {
}

var index = 1;
for (var i = 1; i < length; ++i) {
var i;
for (i = 1; i < length; ++i) {
var v1 = positions[i];
var c1 = ellipsoid.cartesianToCartographic(v1, scratchCartographic2);
if (hasTopHeights) {
c1.height = topHeights[i];
}
hasAllZeroHeights = hasAllZeroHeights && c1.height <= 0;

if (!latLonEquals(c0, c1)) {
cleanedPositions[index] = v1; // Shallow copy!
@@ -79,7 +74,18 @@ function removeDuplicates(ellipsoid, positions, topHeights, bottomHeights) {
}
}

if (hasAllZeroHeights || index < 2) {
var zeroHeightCount = 0;
for (i = 0; i < index; ++i) {
var topHeight = cleanedTopHeights[i];
var bottomHeight = cleanedBottomHeights[i];
zeroHeightCount += CesiumMath.equalsEpsilon(
topHeight,
bottomHeight,
CesiumMath.EPSILON10
);
}

if (zeroHeightCount === index || index < 2) {
return;
}

191 changes: 191 additions & 0 deletions Specs/Core/WallGeometrySpec.js
Original file line number Diff line number Diff line change
@@ -82,6 +82,53 @@ describe("Core/WallGeometry", function () {
expect(geometry).toBeUndefined();
});

it("does not create when minimumHeights and maximumHeights are the same", function () {
var geometry = WallGeometry.createGeometry(
new WallGeometry({
positions: Cartesian3.fromDegreesArrayHeights([
-115.0,
44.0,
100000.0,
-90.0,
44.0,
200000.0,
-30.0,
44.0,
200000.0,
]),
maximumHeights: [60000.0, 60000.0, 50000.0],
minimumHeights: [60000.0, 60000.0, 50000.0],
})
);

expect(geometry).toBeUndefined();
});

it("does not create when removing duplicated position results in minimumHeights and maximumHeights are the same", function () {
var geometry = WallGeometry.createGeometry(
new WallGeometry({
positions: Cartesian3.fromDegreesArrayHeights([
-115.0,
44.0,
200000.0,
-115.0,
44.0,
100000.0,
-90.0,
44.0,
200000.0,
-30.0,
44.0,
200000.0,
]),
maximumHeights: [40000.0, 60000.0, 60000.0, 50000.0],
minimumHeights: [60000.0, 60000.0, 60000.0, 50000.0],
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if maximumHeights should always be larger than minimumHeights. This seems to work in the master, so I assume I should test as well

);

expect(geometry).toBeUndefined();
});

it("does not throw when positions are unique but close", function () {
WallGeometry.createGeometry(
new WallGeometry({
@@ -172,6 +219,150 @@ describe("Core/WallGeometry", function () {
expect(cartographic.height).toEqualEpsilon(4000.0, CesiumMath.EPSILON8);
});

it("create positions with negative height", function () {
var w = WallGeometry.createGeometry(
new WallGeometry({
positions: Cartesian3.fromDegreesArrayHeights([
49.0,
18.0,
1000.0,
50.0,
18.0,
1000.0,
]),
maximumHeights: [-300.0, -200.0],
minimumHeights: [-500.0, -600.0],
})
);

var positions = w.attributes.position.values;
var normals = w.attributes.normal.values;
var indices = w.indices;
var numPositions = 4;
var numTriangles = 2;
expect(positions.length).toEqual(numPositions * 3);
expect(normals.length).toEqual(numPositions * 3);
expect(indices.length).toEqual(numTriangles * 3);

var cartographic = ellipsoid.cartesianToCartographic(
Cartesian3.fromArray(positions, 0)
);
expect(cartographic.height).toEqualEpsilon(-500.0, CesiumMath.EPSILON8);

cartographic = ellipsoid.cartesianToCartographic(
Cartesian3.fromArray(positions, 3)
);
expect(cartographic.height).toEqualEpsilon(-300.0, CesiumMath.EPSILON8);

cartographic = ellipsoid.cartesianToCartographic(
Cartesian3.fromArray(positions, 6)
);
expect(cartographic.height).toEqualEpsilon(-600.0, CesiumMath.EPSILON8);

cartographic = ellipsoid.cartesianToCartographic(
Cartesian3.fromArray(positions, 9)
);
expect(cartographic.height).toEqualEpsilon(-200.0, CesiumMath.EPSILON8);

var bottomPosition = Cartesian3.fromArray(positions, 0);
var topPosition = Cartesian3.fromArray(positions, 3);
var nextTopPosition = Cartesian3.fromArray(positions, 9);
var topDiff = Cartesian3.subtract(
nextTopPosition,
topPosition,
new Cartesian3()
);
var bottomDiff = Cartesian3.subtract(
bottomPosition,
topPosition,
new Cartesian3()
);
var expectedNormal = Cartesian3.cross(
bottomDiff,
topDiff,
new Cartesian3()
);
Cartesian3.normalize(expectedNormal, expectedNormal);

var length = normals.length;
for (var i = 0; i < length; i += 3) {
expect(normals[i]).toEqualEpsilon(expectedNormal.x, CesiumMath.EPSILON7);
expect(normals[i + 1]).toEqualEpsilon(
expectedNormal.y,
CesiumMath.EPSILON7
);
expect(normals[i + 2]).toEqualEpsilon(
expectedNormal.z,
CesiumMath.EPSILON7
);
}
});

it("No zero unit normals, tangent, or bitangent created when only part of minimumHeights and maximumHeights array are equal", function () {
var w = WallGeometry.createGeometry(
new WallGeometry({
vertexFormat: VertexFormat.ALL,
positions: Cartesian3.fromDegreesArrayHeights([
49.0,
18.0,
1000.0,
50.0,
18.0,
1000.0,
51.0,
18.0,
1000.0,
52.0,
18.0,
1000.0,
]),
maximumHeights: [0.0, -300.0, 0.0, 10.0],
minimumHeights: [0.0, -300.0, -10.0, -20.0],
})
);

var normals = w.attributes.normal.values;
var tangents = w.attributes.tangent.values;
var bitangents = w.attributes.bitangent.values;
var normal = Cartesian3.fromArray(normals, 0);
var tangent = Cartesian3.fromArray(tangents, 0);
var bitangent = Cartesian3.fromArray(bitangents, 0);
expect(Cartesian3.equalsEpsilon(normal, Cartesian3.UNIT_Y)).toEqual(true);
expect(Cartesian3.equalsEpsilon(tangent, Cartesian3.UNIT_X)).toEqual(true);
expect(Cartesian3.equalsEpsilon(bitangent, Cartesian3.UNIT_Z)).toEqual(
true
);

var length = normals.length;
for (var i = 3; i < length; i += 3) {
normal = Cartesian3.fromArray(normals, i, normal);
tangent = Cartesian3.fromArray(tangents, i, tangent);
bitangent = Cartesian3.fromArray(bitangents, i, bitangents);
var normalLength = Cartesian3.magnitude(normal);
var tangentLength = Cartesian3.magnitude(tangent);
var bitangentLength = Cartesian3.magnitude(bitangent);
var normalTangentAngle = Cartesian3.dot(normal, tangent);
var normalBitangentAngle = Cartesian3.dot(normal, bitangent);
var tangentBitangentAngle = Cartesian3.dot(tangent, bitangent);

expect(
CesiumMath.equalsEpsilon(normalLength, 0.0, CesiumMath.EPSILON7)
).toEqual(false);

expect(
CesiumMath.equalsEpsilon(tangentLength, 0.0, CesiumMath.EPSILON7)
).toEqual(false);

expect(
CesiumMath.equalsEpsilon(bitangentLength, 0.0, CesiumMath.EPSILON7)
).toEqual(false);

expect(normalTangentAngle).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
expect(normalBitangentAngle).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
expect(tangentBitangentAngle).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
}
});

it("cleans positions with duplicates", function () {
var w = WallGeometry.createGeometry(
new WallGeometry({