SUAPI-CppWrapper
C++WrapperforSketchUpCAPI
Loop.cpp
1 //
2 // Loop.cpp
3 //
4 // Sketchup C++ Wrapper for C API
5 // MIT License
6 //
7 // Copyright (c) 2017 Tom Kaneko
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining a copy
10 // of this software and associated documentation files (the "Software"), to deal
11 // in the Software without restriction, including without limitation the rights
12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 // copies of the Software, and to permit persons to whom the Software is
14 // furnished to do so, subject to the following conditions:
15 
16 // The above copyright notice and this permission notice shall be included in all
17 // copies or substantial portions of the Software.
18 
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 // SOFTWARE.
26 //
27 
28 // Macro for getting rid of unused variables commonly for assert checking
29 #define _unused(x) ((void)(x))
30 
31 #include "SUAPI-CppWrapper/model/Loop.hpp"
32 
33 #include "SUAPI-CppWrapper/model/LoopInput.hpp"
34 #include "SUAPI-CppWrapper/model/Vertex.hpp"
35 #include "SUAPI-CppWrapper/model/Edge.hpp"
36 #include "SUAPI-CppWrapper/model/Material.hpp"
37 #include "SUAPI-CppWrapper/model/Layer.hpp"
38 
39 #include <cassert>
40 #include <stdexcept>
41 
42 #include <math.h>
43 
44 namespace CW {
45 
47  Entity()
48 {}
49 
50 
51 Loop::Loop(SULoopRef loop):
52  Entity(SULoopToEntity(loop), true)
53 {}
54 
55 
56 Loop::Loop(const Loop& other):
57  Entity(other.m_entity, true)
58 {}
59 
60 
61 Loop& Loop::operator=(const Loop& other) {
62  m_entity = other.m_entity;
63  m_attached = true;
64  return (*this);
65 }
66 
67 
68 SULoopRef Loop::ref() const {
69  return SULoopFromEntity(m_entity);
70 }
71 
72 
73 LoopInput Loop::loop_input() const {
74  if(!(*this)) {
75  throw std::logic_error("CW::Loop::loop_input(): Loop is null");
76  }
77  std::vector<Edge> edges = this->edges();
78  std::vector<InputEdgeProperties> edge_properties;
79  edge_properties.reserve(edges.size());
80  for (size_t i=0; i < edges.size(); i++) {
81  InputEdgeProperties edge_prop;
82  edge_prop.hidden = edges[i].hidden();
83  edge_prop.soft = edges[i].soft();
84  edge_prop.smooth = edges[i].smooth();
85  edge_prop.material = edges[i].material();
86  edge_prop.layer = edges[i].layer();
87  edge_properties.push_back(edge_prop);
88  }
89  return LoopInput(edge_properties);
90 }
91 
92 
93 std::vector<Edge> Loop::edges() const {
94  if(!(*this)) {
95  throw std::logic_error("CW::Loop::edges(): Loop is null");
96  }
97  size_t count = 0;
98  SUResult res = SULoopGetNumVertices(this->ref(), &count);
99  assert(res == SU_ERROR_NONE);
100  std::vector<SUEdgeRef> edge_refs(count, SU_INVALID);
101  res = SULoopGetEdges(this->ref(), count, edge_refs.data(), &count);
102  assert(res == SU_ERROR_NONE); _unused(res);
103  std::vector<Edge> edges(count);
104  std::transform(edge_refs.begin(), edge_refs.end(), edges.begin(),
105  [](const SUEdgeRef& value){
106  return Edge(value);
107  });
108  return edges;
109 }
110 
111 
112 std::vector<Vertex> Loop::vertices() const {
113  if(!(*this)) {
114  throw std::logic_error("CW::Loop::vertices(): Loop is null");
115  }
116  size_t count = 0;
117  SUResult res = SULoopGetNumVertices(this->ref(), &count);
118  assert(res == SU_ERROR_NONE);
119  std::vector<SUVertexRef> verts_array(count, SU_INVALID);
120  res = SULoopGetVertices(this->ref(), count, verts_array.data(), &count);
121  assert(res == SU_ERROR_NONE); _unused(res);
122  std::vector<Vertex> vertices(count);
123  std::transform(verts_array.begin(), verts_array.end(), vertices.begin(),
124  [](const SUVertexRef& value){
125  return Vertex(value);
126  });
127  return vertices;
128 }
129 
130 
131 std::vector<Point3D> Loop::points() const {
132  if(!(*this)) {
133  throw std::logic_error("CW::Loop::points(): Loop is null");
134  }
135  std::vector<Vertex> verts = vertices();
136  std::vector<Point3D> points;
137  points.reserve(verts.size());
138  for (size_t i=0; i < verts.size(); ++i) {
139  points.push_back(verts[i].position());
140  }
141  return points;
142 }
143 
144 
145 PointLoopClassify Loop::classify_point(const Point3D& point) const {
146  if(!(*this)) {
147  throw std::logic_error("CW::Loop::classify_point(): Loop is null");
148  }
149  if(!point) {
150  throw std::invalid_argument("CW::Loop::classify_point(): Point3D given is null");
151  }
152  return classify_point(this->points(), point);
153 }
154 
155 
156 size_t Loop::size() const {
157  if(!(*this)) {
158  throw std::logic_error("CW::Loop::size(): Loop is null");
159  }
160  size_t count = 0;
161  SUResult res = SULoopGetNumVertices(this->ref(), &count);
162  assert(res == SU_ERROR_NONE); _unused(res);
163  return count;
164 }
165 
166 
167 PointLoopClassify Loop::classify_point(const std::vector<Point3D>& loop_points, const Point3D& test_point) {
168  if(loop_points.size() < 3) {
169  throw std::invalid_argument("CW::Loop::classify_point(): Fewer than 3 points given - not a valid loop.");
170  }
171  if(!test_point) {
172  throw std::invalid_argument("CW::Loop::classify_point(): Point3D given is null");
173  }
174  // http://www.geeksforgeeks.org/how-to-check-if-a-given-point-lies-inside-a-polygon/
175  // First check that the test point is on the plane
176  Plane3D loop_plane = Plane3D::plane_from_loop(loop_points);
177  if (!loop_plane) {
178  throw std::invalid_argument("CW::Loop::classify_point(): Points given does not form a valid loop.");
179  }
180  else if (!loop_plane.on_plane(test_point)) {
181  return PointLoopClassify::PointNotOnPlane;
182  }
183  // Now check if it is on the vertices
184  for (size_t i=0; i < loop_points.size(); i++) {
185  if (loop_points[i] == test_point) {
186  return PointLoopClassify::PointOnVertex;
187  }
188  }
189  // Now check if it is on the edges
190  for (size_t i=0; i < loop_points.size(); i++) {
191  Point3D next_point;
192  if (i == loop_points.size()-1) {
193  next_point = loop_points[0];
194  }
195  else {
196  next_point = loop_points[i+1];
197  }
198  if (Line3D::on_line_segment(loop_points[i], next_point, test_point)) {
199  return PointLoopClassify::PointOnEdge;
200  }
201  }
202  // Now check if it is inside or outside, given that we know that the point is (a) on the same plane of the loop, and (b) not on the edge, and (c) not on the vertices.
203  // From: http://www.geeksforgeeks.org/how-to-check-if-a-given-point-lies-inside-a-polygon/
204  // We draw a line from the point, and if it intersects with the loop an even number of times, then it is outside, if it intersects an odd number of times, it is inside.
205  // The line to draw can be any vector that is coplanar to the loop's plane - the simple one is get a vector of two points within the loop.
206  assert(loop_points[0] != loop_points[1]);
207  Vector3D ray = Vector3D(loop_points[1] - loop_points[0]).unit();
208  // Colinear line segments create problems for the algorith below, so we ignore them by creating a new list of points to calculate that exclude colinear edges.
209  struct LoopPoint {
210  Point3D point;
211  Vector3D vector_to_next;
212  };
213  std::vector<LoopPoint> red_loop_points;
214  red_loop_points.reserve(loop_points.size());
215  for (size_t i=0; i < loop_points.size(); i++) {
216  Point3D next_point;
217  if (i == loop_points.size()-1) {
218  next_point = loop_points[0];
219  } else {
220  next_point = loop_points[i+1];
221  }
222  Vector3D next_vector = next_point - loop_points[i];
223  if (next_vector.unit() != ray.unit() && next_vector.unit() != -ray.unit()) {
224  red_loop_points.push_back(LoopPoint{loop_points[i], next_vector});
225  }
226  }
227  // Now we have a list of points with colinear points removed, find intersections with the line segments
228  size_t num_intersections = 0;
229  for (size_t i=0; i < red_loop_points.size(); i++) {
230  Point3D intersection = Point3D::ray_line_intersection(red_loop_points[i].point, red_loop_points[i].vector_to_next, test_point, ray, false);
231  if (!!intersection) {
232  // There is a case where the ray intersection lies on a vertex of the loop. Whether this should be counted as an intersection of both lines connected to the loop or just once through depends on the orientation of the ray relative to the lines connected to that vertex
233  if (intersection == red_loop_points[i].point) {
234  // Get the directions of the lines connected to the vertex
235  LoopPoint prev_point;
236  if (i == 0) {
237  prev_point = red_loop_points[red_loop_points.size()-1];
238  }
239  else {
240  prev_point = red_loop_points[i-1];
241  }
242  Vector3D prev_cross = ray.cross(prev_point.vector_to_next);
243  Vector3D next_cross = ray.cross(red_loop_points[i].vector_to_next);
244  assert(fabs(next_cross.length()) > Vector3D::EPSILON);
245  if (prev_cross.unit() != next_cross.unit()) {
246  // The cross product direction is in opposite directions - ie the lines connected to the vertex are on the same side of the ray, therefore, count this as an additional intersection
247  num_intersections++;
248  }
249  }
250  else {
251  num_intersections++;
252  }
253  }
254  }
255  if (num_intersections % 2 == 0) {
256  // Even number of intersections
257  return PointLoopClassify::PointOutside;
258  }
259  else {
260  return PointLoopClassify::PointInside;
261  }
262 }
263 
264 bool Loop::is_outer_loop() const {
265  bool is_outer;
266  SUResult res = SULoopIsOuterLoop(this->ref(), &is_outer);
267  assert(res == SU_ERROR_NONE); _unused(res);
268  return is_outer;
269 }
270 
271 
272 } /* namespace CW */
bool m_attached
Indicates whether the Entity has been attached to a model.
Definition: Entity.hpp:64
SULoopRef ref() const
Definition: Loop.cpp:68
static Point3D ray_line_intersection(const Point3D &point_a, const Vector3D &vector_a, const Point3D &point_b, const Vector3D &ray_b, bool return_colinear=false)
Definition: Geometry.cpp:575
std::vector< Edge > edges() const
Definition: Loop.cpp:93
Entity()
Constructor representing a null objject.
Definition: Entity.cpp:48
static bool on_line_segment(const Point3D &point_a, const Point3D &point_b, const Point3D &test_point)
Definition: Geometry.cpp:1213
Loop & operator=(const Loop &other)
Definition: Loop.cpp:61
SUEntityRef m_entity
The C SUEntityRef that this class wraps.
Definition: Entity.hpp:59
std::vector< Point3D > points() const
Definition: Loop.cpp:131
size_t size() const
Definition: Loop.cpp:156
Loop()
Definition: Loop.cpp:46
PointLoopClassify classify_point(const Point3D &point) const
Definition: Loop.cpp:145
Definition: Color.hpp:34
bool on_plane(const Point3D &point) const
Definition: Geometry.cpp:842
Vector3D cross(const Vector3D &vector2) const
Definition: Geometry.cpp:311
std::vector< Vertex > vertices() const
Definition: Loop.cpp:112
bool is_outer_loop() const
Definition: Loop.cpp:264
static Plane3D plane_from_loop(const std::vector< Point3D > &loop_points)
Definition: Geometry.cpp:892