-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Safeguards project from undefined cartographic value #12671
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
base: main
Are you sure you want to change the base?
Changes from all commits
95ec0fe
78be243
8cc5c04
468c1a1
4b478d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -872,11 +872,11 @@ function projectNormal( | |
); | ||
} | ||
|
||
normalEndpointCartographic.height = 0.0; | ||
const normalEndpointProjected = projection.project( | ||
normalEndpointCartographic, | ||
result, | ||
); | ||
const normalEndpointProjected = defined(normalEndpointCartographic) | ||
? projection.project(normalEndpointCartographic, result) | ||
: Cartesian3.clone(Cartesian3.ZERO, result); | ||
normalEndpointProjected.height = 0.0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order to use a ternary statement (consistent with other changes in this PR), I changed the "set height to 0" statement to occur after the projection step. This order change should not make a functional difference since projection does not depend on height, and simply sets the result height to the input height. Open to push back here if this seems unnecessarily risky. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That should be fine, but let's make sure we're not normalizing (0, 0, 0) right below this. |
||
|
||
result = Cartesian3.subtract( | ||
normalEndpointProjected, | ||
projectedPosition, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1521,10 +1521,11 @@ function updateBatchTableBoundingSpheres(primitive, frameState) { | |
center, | ||
scratchBoundingSphereCartographic, | ||
); | ||
const center2D = projection.project( | ||
cartographic, | ||
scratchBoundingSphereCenter2D, | ||
); | ||
const center2D = defined(cartographic) | ||
? projection.project(cartographic, scratchBoundingSphereCenter2D) | ||
: Cartesian3.clone( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this intentionally cloning a clone? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, thought I caught that but seems I forgot to commit it. |
||
Cartesian3.clone(Cartesian3.ZERO, scratchBoundingSphereCenter2D), | ||
); | ||
encodedCenter = EncodedCartesian3.fromCartesian( | ||
center2D, | ||
scratchBoundingSphereCenterEncoded, | ||
|
@@ -1589,18 +1590,17 @@ function updateBatchTableOffsets(primitive, frameState) { | |
center, | ||
scratchBoundingSphereCartographic, | ||
); | ||
const center2D = projection.project( | ||
cartographic, | ||
scratchBoundingSphereCenter2D, | ||
); | ||
|
||
const center2D = defined(cartographic) | ||
? projection.project(cartographic, scratchBoundingSphereCenter2D) | ||
: Cartesian3.clone(Cartesian3.ZERO, scratchBoundingSphereCenter2D); | ||
|
||
const newPoint = Cartesian3.add(offset, center, offsetScratchCartesian); | ||
cartographic = ellipsoid.cartesianToCartographic(newPoint, cartographic); | ||
|
||
const newPointProjected = projection.project( | ||
cartographic, | ||
offsetScratchCartesian, | ||
); | ||
const newPointProjected = defined(cartographic) | ||
? projection.project(cartographic, offsetScratchCartesian) | ||
: Cartesian3.clone(Cartesian3.ZERO, scratchBoundingSphereCenter2D); | ||
|
||
const newVector = Cartesian3.subtract( | ||
newPointProjected, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1367,6 +1367,40 @@ describe( | |
primitive.destroy(); | ||
expect(primitive.isDestroyed()).toEqual(true); | ||
}); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I only authored this one test for now, so I can get feedback before potentially going in the wrong direction. Questions:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, also, forgot to remove this line:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like a solid approach to test the issue you are addressing, locating primitives at the origin. As to whether you need to add unit tests for the other areas you added the fix, I think the initial questions to ask are 1) do existing unit tests break and (if yes then fix) 2) without the fix are we able to reproduce the error by placing something at the origin. If yes to 2 I think a unit test makes sense. |
||
it("does not throw an error when rendering a primitive at the origin", function () { | ||
const modelMat = Matrix4.fromTranslation(Cartesian3.ZERO); | ||
|
||
const boxInstance = new GeometryInstance({ | ||
geometry: BoxGeometry.fromDimensions({ | ||
vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT, | ||
dimensions: new Cartesian3(500000.0, 500000.0, 500000.0), | ||
}), | ||
id: "box", | ||
attributes: { | ||
color: new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 0.5), | ||
// This triggers batching to run, which is the code path where an origin-centered object needs special treatment. | ||
distanceDisplayCondition: | ||
new DistanceDisplayConditionGeometryInstanceAttribute( | ||
0, | ||
10000000.0, | ||
), | ||
}, | ||
modelMatrix: modelMat, | ||
}); | ||
|
||
primitive = new Primitive({ | ||
geometryInstances: boxInstance, | ||
appearance: new PerInstanceColorAppearance({ | ||
closed: true, | ||
}), | ||
asynchronous: false, | ||
}); | ||
|
||
expect(function () { | ||
primitive.update(frameState); | ||
}).not.toThrow(); | ||
}); | ||
}, | ||
"WebGL", | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Computing the surface normal can run into division by zero errors when the position in question is the origin.
I'd suggest restructuring the flow, both here and in those other similar cases in this file, to use a function something like the following (it also removes some redundant work):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be helpful at this point to add test cases where the input is the origin to the unit tests for the fixes throughout this PR, assuming they're straightforward to create.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point... in fact, it's made me questions my overall approach. As I took a look through the other changed files for instances where we might want a
getProjectedPositionAndNormal
function, I realized that it's pretty hard to tell what happens downstream in general.What I mean is, in some cases (like this one), it's easy to see the value gets normalized immediately. In others, however, it's just a function that returns a value, and who knows how it gets consumed. I did some digging but it would be a non-trivial effort to trace all code paths for every file I changed here (some of which are very core, like
Primitive
).Maybe this PR should be more focused towards the case that actually causes the bug reported? Taking an even bigger step back - is there ever even a use case for spawning things at (0, 0, 0)? If we really want to support it, maybe we can just jitter objects spawned at the origin by some epsilon?