1 package org.djutils.draw.surface;
2
3 import java.util.Arrays;
4 import java.util.Iterator;
5 import java.util.LinkedHashMap;
6 import java.util.Locale;
7 import java.util.Map;
8 import java.util.NoSuchElementException;
9
10 import org.djutils.draw.DrawRuntimeException;
11 import org.djutils.draw.Drawable2d;
12 import org.djutils.draw.Drawable3d;
13 import org.djutils.draw.bounds.Bounds3d;
14 import org.djutils.draw.point.Point3d;
15 import org.djutils.exceptions.Throw;
16
17
18
19
20
21
22
23
24
25
26 public class Surface3d implements Drawable3d
27 {
28
29 private static final long serialVersionUID = 20210706L;
30
31
32 private final double[] x;
33
34
35 private final double[] y;
36
37
38 private final double[] z;
39
40
41 private final int[] indices;
42
43
44 private final Bounds3d bounds;
45
46
47
48
49
50
51
52
53 public Surface3d(final Point3d[][] points) throws NullPointerException, DrawRuntimeException
54 {
55 Throw.whenNull(points, "points may not be null");
56 Throw.when(points.length == 0, DrawRuntimeException.class, "points must have at least one element");
57 this.indices = new int[points.length * 3];
58
59 Map<Point3d, Integer> indexMap = new LinkedHashMap<>();
60 for (int triangle = 0; triangle < points.length; triangle++)
61 {
62 Point3d[] trianglePoints = points[triangle];
63 Throw.whenNull(trianglePoints, "Element in points may not be null");
64 Throw.when(trianglePoints.length != 3, DrawRuntimeException.class,
65 "Triangle %d contain wrong number of points (should be 3, got %d", triangle, trianglePoints.length);
66 Point3d prevPoint = trianglePoints[2];
67 for (int i = 0; i < 3; i++)
68 {
69 Point3d point = trianglePoints[i];
70
71 Throw.when(point.equals(prevPoint), DrawRuntimeException.class, "Triangle %d contains duplicate point",
72 triangle);
73 Integer currentIndex = indexMap.get(point);
74 if (currentIndex == null)
75 {
76 indexMap.put(point, indexMap.size());
77 }
78 prevPoint = point;
79 }
80 }
81 this.x = new double[indexMap.size()];
82 this.y = new double[indexMap.size()];
83 this.z = new double[indexMap.size()];
84 for (int triangle = 0; triangle < points.length; triangle++)
85 {
86 Point3d[] trianglePoints = points[triangle];
87 for (int i = 0; i < 3; i++)
88 {
89 Point3d point3d = trianglePoints[i];
90 Integer index = indexMap.get(point3d);
91 this.indices[triangle * 3 + i] = index;
92 this.x[index] = point3d.x;
93 this.y[index] = point3d.y;
94 this.z[index] = point3d.z;
95 }
96 }
97 this.bounds = new Bounds3d(getPoints());
98 }
99
100
101 @Override
102 public Iterator<? extends Point3d> getPoints()
103 {
104 return new Iterator<Point3d>()
105 {
106 private int current = 0;
107
108
109 @Override
110 public boolean hasNext()
111 {
112 return this.current < size();
113 }
114
115 @SuppressWarnings("synthetic-access")
116
117 @Override
118 public Point3d next()
119 {
120 Throw.when(this.current >= size(), NoSuchElementException.class, "Iterator has exhausted the input");
121 int index = this.current++;
122 return new Point3d(Surface3d.this.x[Surface3d.this.indices[index]],
123 Surface3d.this.y[Surface3d.this.indices[index]], Surface3d.this.z[Surface3d.this.indices[index]]);
124 }
125 };
126 }
127
128
129 @Override
130 public int size()
131 {
132 return this.indices.length;
133 }
134
135
136 @Override
137 public Bounds3d getBounds()
138 {
139 return this.bounds;
140 }
141
142
143 @Override
144 public Drawable2d project() throws DrawRuntimeException
145 {
146 throw new DrawRuntimeException("Project not implemented because we do not have a class that represents a multitude "
147 + "of triangles in the 2D plane");
148 }
149
150
151 @Override
152 public String toString()
153 {
154 return toString("%f", false);
155 }
156
157
158 @Override
159 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
160 {
161 StringBuilder result = new StringBuilder();
162 if (!doNotIncludeClassName)
163 {
164 result.append("Surface3d ");
165 }
166 String format = String.format("%%sx=%1$s, y=%1$s, z=%1$s", doubleFormat);
167 for (int index = 0; index < this.indices.length; index++)
168 {
169 if (index % 3 == 0)
170 {
171 result.append("[");
172 }
173 int i = this.indices[index];
174 result.append(String.format(Locale.US, format, index == 0 ? "[" : ", ", this.x[i], this.y[i], this.z[i]));
175 if (index % 3 == 2)
176 {
177 result.append("]");
178 }
179 }
180 result.append("]");
181 return result.toString();
182 }
183
184
185 @SuppressWarnings("checkstyle:designforextension")
186 @Override
187 public int hashCode()
188 {
189 final int prime = 31;
190 int result = 1;
191 result = prime * result + Arrays.hashCode(this.indices);
192 result = prime * result + Arrays.hashCode(this.x);
193 result = prime * result + Arrays.hashCode(this.y);
194 result = prime * result + Arrays.hashCode(this.z);
195 return result;
196 }
197
198
199 @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
200 @Override
201 public boolean equals(final Object obj)
202 {
203 if (this == obj)
204 return true;
205 if (obj == null)
206 return false;
207 if (getClass() != obj.getClass())
208 return false;
209 Surface3d other = (Surface3d) obj;
210 if (!Arrays.equals(this.indices, other.indices))
211 return false;
212 if (!Arrays.equals(this.x, other.x))
213 return false;
214 if (!Arrays.equals(this.y, other.y))
215 return false;
216 if (!Arrays.equals(this.z, other.z))
217 return false;
218 return true;
219 }
220
221 }