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 #4837 - Geometry improvements - Detect incorrectly oriented surfaces in spaces, non convex spaces #4843

Merged
merged 40 commits into from Jun 29, 2023

Conversation

jmarrec
Copy link
Collaborator

@jmarrec jmarrec commented Mar 29, 2023

Pull request overview

Pull Request Author

  • Model API Changes / Additions
  • Any new or modified fields have been implemented in the EnergyPlus ForwardTranslator (and ReverseTranslator as appropriate)
  • Model API methods are tested (in src/model/test)
  • EnergyPlus ForwardTranslator Tests (in src/energyplus/Test)
  • If a new object or method, added a test in NREL/OpenStudio-resources: Add Link
  • If needed, added VersionTranslation rules for the objects (src/osversion/VersionTranslator.cpp)
  • Verified that C# bindings built fine on Windows, partial classes used as needed, etc.
  • All new and existing tests passes
  • If methods have been deprecated, update rest of code to use the new methods

Labels:

  • If change to an IDD file, add the label IDDChange
  • If breaking existing API, add the label APIChange
  • If deemed ready, add label Pull Request - Ready for CI so that CI builds your PR

Review Checklist

This will not be exhaustively relevant to every PR.

  • Perform a Code Review on GitHub
  • Code Style, strip trailing whitespace, etc.
  • All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
  • Labeling is ok
  • If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
  • If feature, test running new feature, try creative ways to break it
  • CI status: all green or justified

@jmarrec jmarrec added component - Utilities Geometry component - Model Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge. labels Mar 29, 2023
@jmarrec jmarrec self-assigned this Mar 29, 2023
@@ -223,6 +223,8 @@ namespace model {
/// Returns any SurfacePropertyConvectionCoefficients associated with this surface, does not return SurfacePropertyConvectionCoefficientsMultipleSurface.
std::vector<SurfacePropertyConvectionCoefficients> surfacePropertyConvectionCoefficients() const;

bool isConvex() const;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

New user-facing method: bool PlanarSurface::isConvex() const

Comment on lines +644 to +661
// Find all surfaces where the outwardNormal does not point towards the outside of the Space
std::vector<Surface> findSurfacesWithIncorrectOrientation() const;

// Checks the outwardNormal of every surface points towards the outside of the Space
bool areAllSurfacesCorrectlyOriented() const;

// Returns true if the orientation of any surface has been changed
bool fixSurfacesWithIncorrectOrientation();

/** This method will check the floorPrint of the space, if that can't be computed, it's not convex. If it can, it checks whether that resulting
* Surface3d is convex. Note: having a floorPrint that's convex isn't sufficied to deem a space to be convex, the walls could still be non
* convex, but it should suit most typical applications */
bool isConvex() const;

/** Checks if every Surface is convex, returns the Concave ones.
* Note: having a non convex surface does not necesarilly mean that the Space is not convex
* eg: a box with a wall that is split into two L s would return false, while the Space is actually still convex. */
std::vector<Surface> findNonConvexSurfaces() const;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

New user-facing methods in Space.

Copy link
Contributor

Choose a reason for hiding this comment

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

Woah these are awesome!

Comment on lines +106 to +107
static boost::optional<Space> fromFloorPrint(const std::vector<Point3d>& floorPrint, double floorHeight, Model& model,
const std::string& spaceName = "");
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

New optional spaceName param here, allow naming the Space plus the surfaces. Very useful for testing purposes in particular.

Comment on lines +40 to +54
// Convenience functions for testing. All them returns point in Counterclockwise order, so if creating a floor, reverse the vertices

UTILITIES_API std::vector<Point3d> convexRegularPolygon(const Point3d& center, size_t num_sides, double side_with = 1.0);

UTILITIES_API std::vector<Point3d> hShapedPolygon(const Point3d& center, double total_length = 10.0);

UTILITIES_API std::vector<Point3d> uShapedPolygon(const Point3d& center, double total_length = 10.0);

UTILITIES_API std::vector<Point3d> tShapedPolygon(const Point3d& center, double total_length = 10.0);

UTILITIES_API std::vector<Point3d> lShapedPolygon(const Point3d& center, double total_length = 10.0);

UTILITIES_API std::vector<Point3d> squaredPolygon(const Point3d& center, double side_with = 10.0);

UTILITIES_API std::vector<Point3d> rectangularPolygon(const Point3d& center, double length_x = 20.0, double length_y = 10.0);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

New convenience functions for creating standard shapes.

Comment on lines +54 to +59
// Copy and move operators are implicitly declared
// Point3d(const Point3d& other) = default;
// Point3d(Point3d&& other) = default;
// Point3d& operator=(const Point3d&) = default;
// Point3d& operator=(Point3d&&) = default;
// ~Point3d() noexcept = default;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Throughout the entire Geometry subfolder, I have made sure to enable move semantics (most of the time that means just deleting the explicitly-defaulted copy constructor)

Comment on lines 167 to 201
bool Surface3d::isConvex() const {

// const auto& a = edges.front().start();
// const auto& b = edges.front().end();
// const auto& c = edges.at(1).end();

auto ab = edges.front().asVector();
auto bc = edges[1].asVector();
auto outwardNormal = ab.cross(bc);
outwardNormal.normalize();

for (auto it = std::next(edges.begin()); it != edges.end(); ++it) {
auto itnext = std::next(it);
if (itnext == std::end(edges)) {
itnext = std::begin(edges);
}
const Point3d& a = it->start();
const Point3d& b = it->end();
const Point3d& c = itnext->end();

auto ab = b - a;
auto ac = c - a;

auto triangleNormal = ab.cross(ac);
auto d = outwardNormal.dot(triangleNormal);
if (d < 0) {
return false;
}
}
return true;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Convexity of a Surface3d: for each triangle of 3 points, we compute the triangleNormal by taking the cross product of AB x AC. We then take the dot product with the initial outwardNormal. If the dot product is negative, this means the triangleNomal is pointing in the opposite direction and we therefore have a non convex / concave surface.

Comment on lines +212 to +222
// We call the volume calculation... if this ends up being negative, we're in the case where ALL surfaces are in the wrong orientation
if (!m_hasAnySurfaceWithIncorrectOrientation) {
m_polyhedronVolume = calcPolyhedronVolume();
if (m_polyhedronVolume < 0) {
LOG(Error, "It seems that ALL surfaces are reversed");
m_hasAnySurfaceWithIncorrectOrientation = true;
m_isCompletelyInsideOut = true;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Catch the case where ALL surfaces are inside out.... If we initially didn't find any orientation issues, but the Polyhedron's volume is negative, then it is inside out.

Comment on lines 246 to 278
void Polyhedron::performEdgeMatching() {

m_hasAnySurfaceWithIncorrectOrientation = false;

for (size_t i = 0; i < m_surfaces.size(); ++i) {
for (size_t j = 0; j < m_surfaces.size(); ++j) {
if (i == j) {
continue;
}
Surface3dEdge thisSurface3dEdge(*it, *itnext, surface, surfNum);
auto itFound = std::find(uniqueSurface3dEdges.begin(), uniqueSurface3dEdges.end(), thisSurface3dEdge);
if (itFound == uniqueSurface3dEdges.end()) {
LOG(Debug, "NOT FOUND: " << thisSurface3dEdge);
uniqueSurface3dEdges.emplace_back(std::move(thisSurface3dEdge));
} else {
LOG(Debug, " FOUND: " << thisSurface3dEdge);
itFound->appendSurface(surface);
auto& surface1 = m_surfaces[i];
auto& surface2 = m_surfaces[j];
for (Surface3dEdge& edge1 : surface1.edges) {
for (Surface3dEdge& edge2 : surface2.edges) {
if (edge1 == edge2) {
if (std::find(edge1.allSurfNums().begin(), edge1.allSurfNums().cend(), edge2.firstSurfNum()) == edge1.allSurfNums().end()) {
edge1.appendSurface(surface2);
edge2.appendSurface(surface1);
if (!edge1.reverseEqual(edge2)) {
edge1.markConflictedOrientation();
edge2.markConflictedOrientation();
m_hasAnySurfaceWithIncorrectOrientation = true;
}
}
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Performing edge matching, in one go we pair the edges (later we can check that edge.count() == 2, any Polyhedron which has edges where count() != 2 is not enclosed), and we also check if that edge matches in reverse order to the other.

In a polyhedron that has a consistent orientation (typically all faces are in counter clockwise order), each edge must be matched by an edge in the opposite direction.

Comment on lines 406 to 461
std::vector<Surface3d> Polyhedron::findSurfacesWithIncorrectOrientation() const {

if (!m_hasAnySurfaceWithIncorrectOrientation) {
return {};
}

if (m_isCompletelyInsideOut) {
LOG(Error, "It seems that ALL surfaces are reversed");
return m_surfaces;
}

if (!m_isEnclosedVolume) {
LOG(Warn, "Polyhedron is not enclosed. Looking surfaces with incorrect orientations can return false negatives.");
// return {};
}

std::vector<Surface3dEdge> uniqueSurface3dEdges = uniqueEdges();

// Remove non-conflicted edges
uniqueSurface3dEdges.erase(
std::remove_if(uniqueSurface3dEdges.begin(), uniqueSurface3dEdges.end(), [](const auto& edge) { return !edge.hasConflictedOrientation(); }),
uniqueSurface3dEdges.end());
if (uniqueSurface3dEdges.empty()) {
return {};
}

std::set<Surface3d> conflictedSurfaces;
for (auto& edge : uniqueSurface3dEdges) {
const auto& surfNums = edge.allSurfNums();
const size_t sfIndex1 = surfNums.front();
const size_t sfIndex2 = surfNums.back();

const auto& sf1 = m_surfaces.at(sfIndex1);
const auto& sf2 = m_surfaces.at(sfIndex2);

const auto c1 = sf1.ratioOfConflictedEdges();
const auto c2 = sf2.ratioOfConflictedEdges();

LOG(Trace, fmt::format("'{}' has {:.2f}% conflicted edges ({}/{}), while '{}' has {:.2f}% conflicted edges ({}/{})", sf1.name, c1,
sf1.numConflictedEdges(), sf1.edges.size(), sf2.name, c2, sf2.numConflictedEdges(), sf2.edges.size()));
if (c1 > c2) {
conflictedSurfaces.insert(sf1);
} else {
conflictedSurfaces.insert(sf2);
}
}

return {conflictedSurfaces.begin(), conflictedSurfaces.end()};
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

When determining surfaces with incorrect orientation, we check each conflicted edge.
For each of the two surfaces that share that edge, we compute the ratio of conflicted edges / total number of edges, and we keep the one with the largest ratio as this is probably the one that's in the wrong order.

Comment on lines +485 to +503
double Polyhedron::polyhedronVolume() const {
if (m_isEnclosedVolume && (m_isCompletelyInsideOut || !m_hasAnySurfaceWithIncorrectOrientation)) {
return m_polyhedronVolume;
}
if (!m_isEnclosedVolume) {
LOG(Error, "Polyhedron volume calculation for a non-enclosed Polyhedron will return bogus values");
} else if (m_hasAnySurfaceWithIncorrectOrientation && !m_isCompletelyInsideOut) {
LOG(Error, "Polyhedron volume calculation for an enclosed Polyhedron but with incorrectly oriented surfaces will return bogus values");
}
return calcPolyhedronVolume();
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Trying to be as loud as possible when calling polyhedronVolume with a condition that will return wrong values.

@jmarrec jmarrec requested a review from tijcolem March 30, 2023 16:52
@jmarrec jmarrec force-pushed the issue-4837 branch 2 times, most recently from 484c29c to 4fa5eb1 Compare March 31, 2023 15:23
jmarrec added a commit to openstudiocoalition/OpenStudioApplication that referenced this pull request Mar 31, 2023
Surface3dEdgeVector edgesNot2;
void resetEdgeMatching();

bool operator<(const Surface3d& rhs) const;
};

class UTILITIES_API Polyhedron
Copy link
Contributor

Choose a reason for hiding this comment

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

Where did this come from, is this yours @jmarrec ?

Copy link
Collaborator Author

@jmarrec jmarrec Apr 1, 2023

Choose a reason for hiding this comment

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

The enclosed checks were initially in E+, I've had to modify that portion last year so after learning about polyhedron geometry I decided to tackle the Space::volume issues that were still there 13 years later.
The work in this PR is original.

Comment on lines +663 to +666
/// @cond
void cacheGeometryDiagnostics();
void resetCachedGeometryDiagnostics();
/// @endcond
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hidden methods to cache... I can't very reliably use signals onChange because it depends on the surfaces... so I'm choosing not to, but making this hidden, so the ThreeJSForwardTranslator can use it.

Comment on lines +319 to +321
// If set to false, the 4 diags below aren't used
bool includeGeometryDiagnostics() const;
void setIncludeGeometryDiagnostics(bool includeGeometryDiagnostics);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Make this optional

Comment on lines +67 to +70
// Check if Surface is Convex, and check if its correctly oriented given the containing space, whether the space itself is Convex, Enclosed
// This is computationally intensive, so it's opt-in
bool includeGeometryDiagnostics() const;
void setIncludeGeometryDiagnostics(bool includeGeometryDiagnostics);
Copy link
Collaborator Author

@jmarrec jmarrec Apr 3, 2023

Choose a reason for hiding this comment

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

Optional include of geometry diagnostics though with caching it's not that bad.

before caching:

Test case Opt-out Opt-out Opt-in Opt-in
Debug Release Debug Release
7_7_Windows_Complete 84.63 11.78 26.76 3.68
ParkUnder_Retail_Office_C2 120.64 18.08 15.92 2.13

After caching, in release:

1/4 Test #2562: ModelFixture.ThreeJSForwardTranslator_RefBldgOutPatientNew2004_Chicago .......................   Passed    1.39 sec
2/4 Test #2563: ModelFixture.ThreeJSForwardTranslator_RefBldgOutPatientNew2004_Chicago_GeometryDiagnostics ...   Passed    1.40 sec
3/4 Test #2560: ModelFixture.ThreeJSForwardTranslator_7_7_Windows_Complete ...................................   Passed    3.79 sec
4/4 Test #2561: ModelFixture.ThreeJSForwardTranslator_7_7_Windows_Complete_GeometryDiagnostics ...............   Passed    3.95 sec

After caching, in debug:

1/4 Test #2562: ModelFixture.ThreeJSForwardTranslator_RefBldgOutPatientNew2004_Chicago .......................   Passed   10.09 sec
2/4 Test #2563: ModelFixture.ThreeJSForwardTranslator_RefBldgOutPatientNew2004_Chicago_GeometryDiagnostics ...   Passed   10.19 sec
3/4 Test #2560: ModelFixture.ThreeJSForwardTranslator_7_7_Windows_Complete ...................................   Passed   27.17 sec
4/4 Test #2561: ModelFixture.ThreeJSForwardTranslator_7_7_Windows_Complete_GeometryDiagnostics ...............   Passed   28.33 sec
Test case Release Release Debug Debug
No Diag With Diag No Diag With Diag
RefBldgOutPatientNew2004_Chicago 1.39 1.4 10.09 10.19
7_7_Windows_Complete 3.79 3.95 27.17 28.33

Comment on lines +507 to +513
std::vector<Space> spaces;
if (m_includeGeometryDiagnostics) {
spaces = model.getConcreteModelObjects<Space>();
for (auto& space : spaces) {
space.cacheGeometryDiagnostics();
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If including, precompute and cache all space info.

Comment on lines +539 to +544
if (m_includeGeometryDiagnostics) {
for (auto& space : spaces) {
space.resetCachedGeometryDiagnostics();
}
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Then reset it

jmarrec added a commit to openstudiocoalition/OpenStudioApplication that referenced this pull request Apr 5, 2023
jmarrec added a commit to openstudiocoalition/OpenStudioApplication that referenced this pull request Apr 5, 2023
@jmarrec jmarrec force-pushed the issue-4837 branch 2 times, most recently from 0feeac6 to 831493f Compare April 11, 2023 14:37
@jmarrec jmarrec force-pushed the issue-4837 branch 2 times, most recently from b2bfe68 to 1c45975 Compare May 16, 2023 09:56
@jmarrec jmarrec added this to the OpenStudio SDK 3.7.0 milestone May 31, 2023
… the same stuff over and over again and clean up the API.

Catch the case where it's also completely inside out
…nsive

```
| Test case                  | Opt-out | Opt-out | Opt-in | Opt-in  |
|----------------------------|---------|---------|--------|---------|
|                            | Debug   | Release | Debug  | Release |
| 7_7_Windows_Complete       | 84.63   | 11.78   | 26.76  | 3.68    |
| ParkUnder_Retail_Office_C2 | 120.64  | 18.08   | 15.92  | 2.13    |
```
```
1/4 Test #2562: ModelFixture.ThreeJSForwardTranslator_RefBldgOutPatientNew2004_Chicago .......................   Passed    1.39 sec
2/4 Test #2563: ModelFixture.ThreeJSForwardTranslator_RefBldgOutPatientNew2004_Chicago_GeometryDiagnostics ...   Passed    1.40 sec
3/4 Test #2560: ModelFixture.ThreeJSForwardTranslator_7_7_Windows_Complete ...................................   Passed    3.79 sec
4/4 Test #2561: ModelFixture.ThreeJSForwardTranslator_7_7_Windows_Complete_GeometryDiagnostics ...............   Passed    3.95 sec
```
@jmarrec jmarrec merged commit 26de116 into develop Jun 29, 2023
5 of 6 checks passed
@jmarrec jmarrec deleted the issue-4837 branch June 29, 2023 14:24
jmarrec added a commit to openstudiocoalition/OpenStudioApplication that referenced this pull request Aug 28, 2023
jmarrec added a commit to openstudiocoalition/OpenStudioApplication that referenced this pull request Aug 28, 2023
jmarrec added a commit to openstudiocoalition/OpenStudioApplication that referenced this pull request Aug 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component - Model component - Utilities Geometry Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge.
Projects
None yet
4 participants