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