2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module iv
.vmath2d
.vxstore
;
20 import iv
.vmath2d
.math2d
;
23 // ////////////////////////////////////////////////////////////////////////// //
24 public template IsGoodVertexStorage(T
, VT
) if ((is(T
== struct) ||
is(T
== class)) && IsVectorDim
!(VT
, 2)) {
25 enum IsGoodVertexStorage
= is(typeof((inout int=0) {
31 const(VT
)[] vs
= o
[0..2];
34 // sadly, we can't check for `opIndex()` wrapping here
36 // sadly, we can't check for `swap()` wrapping here
38 bool ins = o
.inside(VT(0, 0));
43 public template IsGoodVertexStorage(T
) if (is(T
== struct) ||
is(T
== class)) {
44 static import std
.traits
;
45 static if (is(std
.traits
.Unqual
!(typeof(T
.init
.centroid
)))) {
46 enum IsGoodVertexStorage
= IsGoodVertexStorage
!(T
, std
.traits
.Unqual
!(typeof(T
.init
.centroid
)));
48 enum IsGoodVertexStorage
= false;
52 public template VertexStorageVT(T
) if (IsGoodVertexStorage
!T
) {
53 static import std
.traits
;
54 alias VertexStorageVT
= std
.traits
.Unqual
!(typeof(T
.init
.centroid
));
57 //static assert(IsGoodVertexStorage!(VertexStorage!vec2));
58 //pragma(msg, VertexStorageVT!(VertexStorage!vec2));
61 // ////////////////////////////////////////////////////////////////////////// //
62 public enum VertexStorageCommon
= q
{
63 void moveBy() (VT
.Float dx
, VT
.Float dy
) { moveBy(VT(dx
, dy
)); }
65 void moveTo() (in auto ref VT pos
) { moveBy(pos
-centroid
); }
66 void moveTo() (VT
.Float x
, VT
.Float y
) { moveBy(VT(x
, y
)-centroid
); }
68 bool inside() (VT
.Float x
, VT
.Float y
) { return inside(VT(x
, y
)); }
72 // ////////////////////////////////////////////////////////////////////////// //
73 public struct VertexStorage(VT
) if (IsVectorDim
!(VT
, 2)) {
80 mixin(VertexStorageCommon
);
82 // WARNING! all slices will become UB!
83 void clear(bool complete
=false) () {
85 static if (complete
) {
86 if (vtx
!is null) delete vtx
;
91 @property bool empty () const pure nothrow @safe @nogc { pragma(inline
, true); return (vtxUsed
== 0); }
93 @property int length () const pure nothrow @safe @nogc { pragma(inline
, true); return vtxUsed
; }
94 alias opDollar
= length
;
96 @property VT
centroid () nothrow @safe @nogc {
97 //pragma(inline, true);
98 if (!center
.isFinite
) {
101 foreach (const ref v
; vtx
[0..vtxUsed
]) center
+= v
;
102 center
/= cast(VT
.Float
)vtxUsed
;
108 bool sameIndex (int i0
, int i1
) const pure nothrow @safe @nogc {
109 pragma(inline
, true);
110 return (((i0
%vtxUsed
)+vtxUsed
)%vtxUsed
== ((i1
%vtxUsed
)+vtxUsed
)%vtxUsed
);
113 VT
opIndex (int idx
) const pure nothrow @trusted @nogc {
114 pragma(inline
, true);
115 if (vtxUsed
== 0) assert(0, "no vertices in storage");
116 return vtx
.ptr
[((idx
%vtxUsed
)+vtxUsed
)%vtxUsed
];
119 void opIndexAssign() (in auto ref VT v
, int idx
) nothrow @trusted @nogc {
120 pragma(inline
, true);
121 if (vtxUsed
== 0) assert(0, "no vertices in storage");
122 vtx
.ptr
[((idx
%vtxUsed
)+vtxUsed
)%vtxUsed
] = v
;
126 void swap (int i0
, int i1
) pure nothrow @trusted @nogc {
127 if (vtxUsed
== 0) assert(0, "no vertices in storage");
128 if (i0
== i1
) return;
129 i0
= ((i0
%vtxUsed
)+vtxUsed
)%vtxUsed
;
130 i1
= ((i1
%vtxUsed
)+vtxUsed
)%vtxUsed
;
131 auto tmp
= vtx
.ptr
[i0
];
132 vtx
.ptr
[i0
] = vtx
.ptr
[i1
];
136 // WILL become UB if storage will go out of scope when the result is still alive
137 const(VT
)[] opSlice (int lo
, int hi
) inout pure nothrow @trusted @nogc {
138 pragma(inline
, true);
139 if (lo
< 0 || hi
< 0 || lo
> hi || lo
> vtxUsed || hi
> vtxUsed
) assert(0, "invalid slicing");
143 // WILL become UB if storage will go out of scope when the result is still alive
144 const(VT
)[] opSlice () inout pure nothrow @trusted @nogc {
145 pragma(inline
, true);
146 return vtx
[0..vtxUsed
];
149 void opOpAssign(string op
:"~") (in auto ref VT v
) nothrow @trusted {
150 if (vtxUsed
>= int.max
/2) assert(0, "out of memory for vertices");
151 if (vtxUsed
< vtx
.length
) {
152 vtx
.ptr
[vtxUsed
] = v
;
154 assert(vtxUsed
== vtx
.length
);
157 if (vtx
.ptr
!is optr
) {
158 import core
.memory
: GC
;
160 if (optr
is GC
.addrOf(optr
)) GC
.setAttr(optr
, GC
.BlkAttr
.NO_INTERIOR
);
167 void remove (int idx
) nothrow @trusted {
168 import core
.stdc
.string
: memmove
;
169 if (vtxUsed
== 0) assert(0, "no vertices in storage");
170 auto i
= ((idx
%vtxUsed
)+vtxUsed
)%vtxUsed
;
171 if (i
< vtxUsed
-1) memmove(vtx
.ptr
+i
, vtx
.ptr
+i
+1, (vtxUsed
-i
-1)*vtx
[0].sizeof
);
176 void moveBy() (in auto ref VT ofs
) nothrow @trusted {
177 foreach (ref v
; vtx
[0..vtxUsed
]) v
+= ofs
;
178 if (center
.isFinite
) center
+= ofs
;
181 bool inside() (in auto ref VT p
) { return insideConvexHelper(this, p
); }
184 //static assert(IsGoodVertexStorage!(VertexStorage!vec2, vec2));
187 VertexStorage!vec2 vs;
188 vs.moveTo(vec2(0, 0));
192 auto c = vs.centroid;
199 // ////////////////////////////////////////////////////////////////////////// //
200 private int sign(T
) (T v
) { pragma(inline
, true); return (v
< 0 ?
-1 : v
> 0 ?
1 : 0); }
203 // ////////////////////////////////////////////////////////////////////////// //
204 // dumb O(n) support function, just brute force check all points
205 public VT
supportDumb(VS
, VT
) (ref VS vstore
, in auto ref VT dir
) if (IsGoodVertexStorage
!(VS
, VT
)) {
206 if (vstore
.length
== 0) return VT
.Invalid
; // dunno, something
208 auto maxDot
= sup
.dot(dir
);
209 foreach (const ref v
; vstore
[1..$]) {
210 immutable d
= v
.dot(dir
);
211 if (d
> maxDot
) { maxDot
= d
; sup
= v
; }
217 // support function using hill climbing
218 public VT
support(VS
, VT
) (ref VS vstore
, in auto ref VT dir
) if (IsGoodVertexStorage
!(VS
, VT
)) {
219 if (vstore
.length
<= 4) return supportDumb(vstore
, dir
); // we will check all vertices in those cases anyway
221 int vidx
= 1, vtxdir
= 1;
222 auto maxDot
= vstore
[1].dot(dir
);
224 // check backward direction
225 immutable dot0delta
= vstore
[0].dot(dir
)-maxDot
;
226 // if dot0delta is negative, there is no reason to go backwards
228 // check forward direction
229 immutable dot2delta
= vstore
[2].dot(dir
)-maxDot
;
230 vtxdir
= (dot2delta
> dot0delta ?
1 : -1);
233 // this loop is guaranteed to stop (obviously), but only for good convexes, so limit iteration count
234 foreach (immutable itrcount
; 0..vstore
.length
) {
235 immutable d
= vstore
[vidx
+vtxdir
].dot(dir
);
236 if (d
< maxDot
) return vstore
[vidx
]; // i found her!
247 // gets the signed area
248 // if the area is less than 0, it indicates that the polygon is clockwise winded
249 public auto signedArea(VS
) (ref VS vstore
) if (IsGoodVertexStorage
!VS
) {
250 alias VT
= VertexStorageVT
!VS
;
251 if (vstore
.length
< 3) return 0;
253 foreach (immutable i
; 0..vstore
.length
) {
254 area
+= vstore
[i
].x
*vstore
[i
+1].y
;
255 area
-= vstore
[i
].y
*vstore
[i
+1].x
;
257 return area
/cast(VT
.Float
)2;
261 // indicates if the vertices are in counter clockwise order
262 // warning: If the area of the polygon is 0, it is unable to determine the winding
263 public bool isCCW(VS
) (ref VS vstore
) if (IsGoodVertexStorage
!VS
) {
264 return (vstore
.length
> 2 && signedArea(vstore
) > 0);
268 // forces the vertices to be counter clock wise order
269 public void forceCCW(VS
) (ref VS vstore
) if (IsGoodVertexStorage
!VS
) {
270 if (vstore
.length
< 3) return;
271 if (!isCCW(vstore
)) {
272 foreach (immutable idx
; 0..vstore
.length
/2) vstore
.swap(idx
, vstore
.length
-idx
-1);
273 assert(isCCW(vstore
));
278 // removes all collinear points
279 public void collinearSimplify(VS
, FT
:double) (ref VS vstore
, FT tolerance
=0) if (IsGoodVertexStorage
!VS
) {
280 alias VT
= VertexStorageVT
!VS
;
282 while (vstore
.length
> 3 && idx
< vstore
.length
) {
283 if (Math2D
.isCollinear(vstore
[idx
-1], vstore
[idx
], vstore
[idx
+1], cast(VT
.Float
)tolerance
)) {
292 // shapes with collinear/identical points aren't "convex"
293 public bool isConvex(VS
) (ref VS vstore
) if (IsGoodVertexStorage
!VS
) {
294 if (vstore
.length
< 3) return false;
295 if (vstore
.length
== 3) return true; // nothing to check here
297 foreach (immutable idx
, const ref v0
; vstore
[]) {
298 //immutable v0 = vstore[idx];
299 immutable v1
= vstore
[idx
+1]-v0
;
300 immutable v2
= vstore
[idx
+2];
301 int d
= sign(v2
.x
*v1
.y
-v2
.y
*v1
.x
+v1
.x
*v0
.y
-v1
.y
*v0
.x
);
302 if (d
== 0) return false;
304 if (dir
!= d
) return false;
313 public bool insideConvexHelper(VS
, VT
) (ref VS vstore
, in auto ref VT p
) /*if (IsGoodVertexStorage!(VS, VT))*/ {
314 if (vstore
.length
< 3) return false;
316 foreach (immutable idx
, const ref v
; vstore
[]) {
317 int d
= sign((vstore
[idx
+1]-v
).cross(p
-v
));
319 if (side
== 0) side
= d
; else if (side
!= d
) return false;
328 public void moveTo(VS, VT) (ref VS vstore, in auto ref VT pos) if (IsGoodVertexStorage!(VS, VT)) {
329 static if (is(typeof(&vstore.moveTo))) {
332 immutable ofs = pos-vstore.centroid;
333 foreach (immutable idx, const ref v; vstore[]) vstore[idx] = v+ofs;
338 public void moveTo(VS, FT:double) (ref VS vstore, FT x, FT y) if (IsGoodVertexStorage!VS) {
339 alias VT = VertexStorageVT!VS;
340 moveTo!VS(vstore, VT(cast(VT.Float)x, cast(VT.Float)y));
344 public void moveByHelper(VS, VT) (ref VS vstore, in auto ref VT ofs) if (IsGoodVertexStorage!(VS, VT)) {
345 static if (is(typeof(&vstore.moveBy))) {
348 foreach (immutable idx, const ref v; vstore[]) vstore[idx] = v+ofs;
353 public void moveByHelper(VS, FT:double) (ref VS vstore, FT x, FT y) if (IsGoodVertexStorage!VS) {
354 alias VT = VertexStorageVT!VS;
355 .moveByHelper!VS(vstore, VT(cast(VT.Float)x, cast(VT.Float)y));
360 public void scaleBy(VS
, VT
) (ref VS vstore
, in auto ref VT scale
) if (IsGoodVertexStorage
!(VS
, VT
)) {
361 foreach (immutable idx
, const ref v
; vstore
[]) vstore
[idx
] = VT(v
.x
*scale
.x
, v
.y
*scale
.y
);
365 public void scaleBy(VS
, FT
:double) (ref VS vstore
, FT scale
) if (IsGoodVertexStorage
!VS
) {
366 alias VT
= VertexStorageVT
!VS
;
367 scaleBy(vstore
, VT(cast(VT
.Float
)scale
, cast(VT
.Float
)scale
));
371 // replace vstore points with convex hull (or nothing if it is not possible to build convex hull)
372 // returns `false` if convex hull cannot be built
373 public bool buildConvex(VS
) (ref VS vstore
) if (IsGoodVertexStorage
!VS
) {
374 alias VT
= VertexStorageVT
!VS
;
376 // no hulls with less than 3 vertices (ensure actual polygon)
377 if (vstore
.length
< 3) { vstore
.clear(); return false; }
379 // copy original vertices
381 if (averts
.length
< vstore
.length
) {
382 auto optr
= averts
.ptr
;
383 averts
.length
= vstore
.length
;
384 if (averts
.ptr
!is optr
) {
385 import core
.memory
: GC
;
387 if (optr
is GC
.addrOf(optr
)) GC
.setAttr(optr
, GC
.BlkAttr
.NO_INTERIOR
);
390 assert(averts
.length
>= vstore
.length
);
391 foreach (immutable idx
, const ref v
; vstore
[]) averts
.ptr
[idx
] = v
;
393 // find the right most point on the hull
395 VT
.Float highestXCoord
= averts
[0].x
;
396 foreach (immutable i
; 1..averts
.length
) {
397 VT
.Float x
= averts
[i
].x
;
398 if (x
> highestXCoord
) {
400 rightMost
= cast(int)i
;
401 } else if (x
== highestXCoord
) {
402 // if matching x then take farthest negative y
403 if (averts
[i
].y
< averts
[rightMost
].y
) rightMost
= cast(int)i
;
408 if (hull
.length
< averts
.length
) {
409 auto optr
= hull
.ptr
;
410 hull
.length
= averts
.length
;
411 if (hull
.ptr
!is optr
) {
412 import core
.memory
: GC
;
414 if (optr
is GC
.addrOf(optr
)) GC
.setAttr(optr
, GC
.BlkAttr
.NO_INTERIOR
);
417 hull
.ptr
[0..averts
.length
] = 0; // just in case, lol
420 int indexHull
= rightMost
;
424 hull
[outCount
] = indexHull
;
426 // search for next index that wraps around the hull by computing cross products to
427 // find the most counter-clockwise vertex in the set, given the previos hull index
428 int nextHullIndex
= 0;
429 foreach (immutable i
; 1..averts
.length
) {
430 // skip if same coordinate as we need three unique points in the set to perform a cross product
431 if (nextHullIndex
== indexHull
) { nextHullIndex
= i
; continue; }
432 // cross every set of three unique vertices
433 // record each counter clockwise third vertex and add to the output hull
434 immutable e1
= averts
[nextHullIndex
]-averts
[hull
[outCount
]];
435 immutable e2
= averts
[i
]-averts
[hull
[outCount
]];
436 immutable c
= e1
.cross(e2
);
437 if (c
< 0) nextHullIndex
= i
;
438 // cross product is zero then e vectors are on same line
439 // therefore want to record vertex farthest along that line
440 if (c
== 0 && e2
.lengthSquared
> e1
.lengthSquared
) nextHullIndex
= i
;
443 indexHull
= nextHullIndex
;
444 // conclude algorithm upon wrap-around
445 if (nextHullIndex
== rightMost
) { vcount
= outCount
; break; }
447 if (vcount
< 3) { vstore
.clear(); return false; }
449 // copy vertices into shape's vertices
451 foreach (immutable hidx
; hull
[0..vcount
]) vstore
~= averts
[hidx
];
452 if (!isConvex(vstore
)) { vstore
.clear(); return false; }