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