2018-05-15 02:27:25 +02:00
|
|
|
|
/*
|
|
|
|
|
* This software is licensed under the terms of the MIT-License
|
|
|
|
|
* See COPYING for further information.
|
|
|
|
|
* ---
|
2019-01-23 21:10:43 +01:00
|
|
|
|
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
2019-07-03 20:00:56 +02:00
|
|
|
|
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
2018-05-15 02:27:25 +02:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "taisei.h"
|
|
|
|
|
|
|
|
|
|
#include "geometry.h"
|
|
|
|
|
|
2019-03-26 16:58:38 +01:00
|
|
|
|
static inline void ellipse_bbox(const Ellipse *e, Rect *r) {
|
|
|
|
|
float largest_radius = fmax(creal(e->axes), cimag(e->axes)) * 0.5;
|
|
|
|
|
r->top_left = e->origin - largest_radius - I * largest_radius;
|
|
|
|
|
r->bottom_right = e->origin + largest_radius + I * largest_radius;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-15 02:27:25 +02:00
|
|
|
|
bool point_in_ellipse(complex p, Ellipse e) {
|
|
|
|
|
double Xp = creal(p);
|
|
|
|
|
double Yp = cimag(p);
|
|
|
|
|
double Xe = creal(e.origin);
|
|
|
|
|
double Ye = cimag(e.origin);
|
|
|
|
|
double a = e.angle;
|
|
|
|
|
|
2019-03-26 16:58:38 +01:00
|
|
|
|
Rect e_bbox;
|
|
|
|
|
ellipse_bbox(&e, &e_bbox);
|
|
|
|
|
|
|
|
|
|
return point_in_rect(p, e_bbox) && (
|
2018-05-15 02:27:25 +02:00
|
|
|
|
pow(cos(a) * (Xp - Xe) + sin(a) * (Yp - Ye), 2) / pow(creal(e.axes)/2, 2) +
|
|
|
|
|
pow(sin(a) * (Xp - Xe) - cos(a) * (Yp - Ye), 2) / pow(cimag(e.axes)/2, 2)
|
|
|
|
|
) <= 1;
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-15 17:37:23 +02:00
|
|
|
|
// If segment_ellipse_nonintersection_heuristic returns true, then the
|
|
|
|
|
// segment and ellipse do not intersect. However, **the converse is not true**.
|
|
|
|
|
// Used for quick returning false in real intersection functions.
|
|
|
|
|
static bool segment_ellipse_nonintersection_heuristic(LineSegment seg, Ellipse e) {
|
|
|
|
|
Rect seg_bbox = {
|
|
|
|
|
.top_left = fmin(creal(seg.a), creal(seg.b)) + I * fmin(cimag(seg.a), cimag(seg.b)),
|
|
|
|
|
.bottom_right = fmax(creal(seg.a), creal(seg.b)) + I * fmax(cimag(seg.a), cimag(seg.b))
|
|
|
|
|
};
|
|
|
|
|
|
2019-03-26 16:58:38 +01:00
|
|
|
|
Rect e_bbox;
|
|
|
|
|
ellipse_bbox(&e, &e_bbox);
|
2018-09-15 17:37:23 +02:00
|
|
|
|
|
2019-01-06 01:02:44 +01:00
|
|
|
|
return !rect_rect_intersect(seg_bbox, e_bbox, true, true);
|
2018-09-15 17:37:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-15 02:27:25 +02:00
|
|
|
|
// Is the point of shortest distance between the line through a and b
|
|
|
|
|
// and a point c between a and b and closer than r?
|
|
|
|
|
// if yes, return f so that a+f*(b-a) is that point.
|
|
|
|
|
// otherwise return -1.
|
2018-09-15 17:37:23 +02:00
|
|
|
|
static double lineseg_circle_intersect_fallback(LineSegment seg, Circle c) {
|
2018-05-15 02:27:25 +02:00
|
|
|
|
complex m, v;
|
|
|
|
|
double projection, lv, lm, distance;
|
|
|
|
|
|
|
|
|
|
m = seg.b - seg.a; // vector pointing along the line
|
|
|
|
|
v = seg.a - c.origin; // vector from circle to point A
|
|
|
|
|
|
|
|
|
|
lv = cabs(v);
|
|
|
|
|
lm = cabs(m);
|
|
|
|
|
|
|
|
|
|
if(lv < c.radius) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(lm == 0) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
projection = -creal(v*conj(m)) / lm; // project v onto the line
|
|
|
|
|
|
|
|
|
|
// now the distance can be calculated by Pythagoras
|
2019-03-26 16:58:38 +01:00
|
|
|
|
distance = sqrt(lv*lv - projection*projection);
|
2018-05-15 02:27:25 +02:00
|
|
|
|
|
|
|
|
|
if(distance <= c.radius) {
|
|
|
|
|
double f = projection/lm;
|
|
|
|
|
|
|
|
|
|
if(f >= 0 && f <= 1) { // it’s on the line!
|
|
|
|
|
return f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool lineseg_ellipse_intersect(LineSegment seg, Ellipse e) {
|
2019-01-06 01:02:44 +01:00
|
|
|
|
if(segment_ellipse_nonintersection_heuristic(seg, e)) {
|
|
|
|
|
return false;
|
2018-09-15 17:37:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-15 02:27:25 +02:00
|
|
|
|
// Transform the coordinate system so that the ellipse becomes a circle
|
|
|
|
|
// with origin at (0, 0) and diameter equal to its X axis. Then we can
|
|
|
|
|
// calculate the segment-circle intersection.
|
|
|
|
|
|
2018-09-15 17:37:23 +02:00
|
|
|
|
seg.a -= e.origin;
|
|
|
|
|
seg.b -= e.origin;
|
|
|
|
|
|
2018-05-15 02:27:25 +02:00
|
|
|
|
double ratio = creal(e.axes) / cimag(e.axes);
|
|
|
|
|
complex rotation = cexp(I * -e.angle);
|
|
|
|
|
seg.a *= rotation;
|
|
|
|
|
seg.b *= rotation;
|
|
|
|
|
seg.a = creal(seg.a) + I * ratio * cimag(seg.a);
|
|
|
|
|
seg.b = creal(seg.b) + I * ratio * cimag(seg.b);
|
|
|
|
|
|
|
|
|
|
Circle c = { .radius = creal(e.axes) / 2 };
|
2018-09-15 17:37:23 +02:00
|
|
|
|
return lineseg_circle_intersect_fallback(seg, c) >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double lineseg_circle_intersect(LineSegment seg, Circle c) {
|
|
|
|
|
Ellipse e = { .origin = c.origin, .axes = 2*c.radius + I*2*c.radius };
|
2019-01-06 01:02:44 +01:00
|
|
|
|
if(segment_ellipse_nonintersection_heuristic(seg, e)) {
|
|
|
|
|
return -1;
|
2018-09-15 17:37:23 +02:00
|
|
|
|
}
|
2019-01-06 01:02:44 +01:00
|
|
|
|
return lineseg_circle_intersect_fallback(seg, c);
|
2018-05-15 02:27:25 +02:00
|
|
|
|
}
|
2018-06-29 23:36:51 +02:00
|
|
|
|
|
2019-03-26 16:58:38 +01:00
|
|
|
|
bool point_in_rect(complex p, Rect r) {
|
|
|
|
|
return
|
|
|
|
|
creal(p) >= rect_left(r) &&
|
|
|
|
|
creal(p) <= rect_right(r) &&
|
|
|
|
|
cimag(p) >= rect_top(r) &&
|
|
|
|
|
cimag(p) <= rect_bottom(r);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-29 23:36:51 +02:00
|
|
|
|
bool rect_in_rect(Rect inner, Rect outer) {
|
|
|
|
|
return
|
|
|
|
|
rect_left(inner) >= rect_left(outer) &&
|
|
|
|
|
rect_right(inner) <= rect_right(outer) &&
|
|
|
|
|
rect_top(inner) >= rect_top(outer) &&
|
|
|
|
|
rect_bottom(inner) <= rect_bottom(outer);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-06 01:02:44 +01:00
|
|
|
|
bool rect_rect_intersect(Rect r1, Rect r2, bool edges, bool corners) {
|
2018-06-29 23:36:51 +02:00
|
|
|
|
if(
|
|
|
|
|
rect_bottom(r1) < rect_top(r2) ||
|
|
|
|
|
rect_top(r1) > rect_bottom(r2) ||
|
|
|
|
|
rect_left(r1) > rect_right(r2) ||
|
|
|
|
|
rect_right(r1) < rect_left(r2)
|
|
|
|
|
) {
|
|
|
|
|
// Not even touching
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!edges && (
|
|
|
|
|
rect_bottom(r1) == rect_top(r2) ||
|
|
|
|
|
rect_top(r1) == rect_bottom(r2) ||
|
|
|
|
|
rect_left(r1) == rect_right(r2) ||
|
|
|
|
|
rect_right(r1) == rect_left(r2)
|
|
|
|
|
)) {
|
|
|
|
|
// Discard edge intersects
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-06 01:02:44 +01:00
|
|
|
|
if(!corners && (
|
2018-06-29 23:36:51 +02:00
|
|
|
|
(rect_left(r1) == rect_right(r2) && rect_bottom(r1) == rect_top(r2)) ||
|
|
|
|
|
(rect_left(r1) == rect_right(r2) && rect_bottom(r2) == rect_top(r1)) ||
|
|
|
|
|
(rect_left(r2) == rect_right(r1) && rect_bottom(r1) == rect_top(r2)) ||
|
|
|
|
|
(rect_left(r2) == rect_right(r1) && rect_bottom(r2) == rect_top(r1))
|
2019-01-06 01:02:44 +01:00
|
|
|
|
)) {
|
2018-06-29 23:36:51 +02:00
|
|
|
|
// Discard corner intersects
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-06 01:02:44 +01:00
|
|
|
|
bool rect_rect_intersection(Rect r1, Rect r2, bool edges, bool corners, Rect *out) {
|
|
|
|
|
if(!rect_rect_intersect(r1, r2, edges, corners)) {
|
2018-06-29 23:36:51 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out->top_left = CMPLX(
|
2018-09-14 09:37:20 +02:00
|
|
|
|
fmax(rect_left(r1), rect_left(r2)),
|
2018-06-29 23:36:51 +02:00
|
|
|
|
fmax(rect_top(r1), rect_top(r2))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
out->bottom_right = CMPLX(
|
2018-09-14 09:37:20 +02:00
|
|
|
|
fmin(rect_right(r1), rect_right(r2)),
|
2018-06-29 23:36:51 +02:00
|
|
|
|
fmin(rect_bottom(r1), rect_bottom(r2))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool rect_join(Rect *r1, Rect r2) {
|
|
|
|
|
if(rect_in_rect(r2, *r1)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(rect_in_rect(*r1, r2)) {
|
|
|
|
|
memcpy(r1, &r2, sizeof(r2));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-06 01:02:44 +01:00
|
|
|
|
if(!rect_rect_intersect(*r1, r2, true, false)) {
|
2018-06-29 23:36:51 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(rect_left(*r1) == rect_left(r2) && rect_right(*r1) == rect_right(r2)) {
|
|
|
|
|
// r2 is directly above/below r1
|
|
|
|
|
double y_min = fmin(rect_top(*r1), rect_top(r2));
|
|
|
|
|
double y_max = fmax(rect_bottom(*r1), rect_bottom(r2));
|
|
|
|
|
|
|
|
|
|
r1->top_left = CMPLX(creal(r1->top_left), y_min);
|
|
|
|
|
r1->bottom_right = CMPLX(creal(r1->bottom_right), y_max);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(rect_top(*r1) == rect_top(r2) && rect_bottom(*r1) == rect_bottom(r2)) {
|
|
|
|
|
// r2 is directly left/right to r1
|
|
|
|
|
double x_min = fmin(rect_left(*r1), rect_left(r2));
|
|
|
|
|
double x_max = fmax(rect_right(*r1), rect_right(r2));
|
|
|
|
|
|
|
|
|
|
r1->top_left = CMPLX(x_min, cimag(r1->top_left));
|
|
|
|
|
r1->bottom_right = CMPLX(x_max, cimag(r1->bottom_right));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void rect_set_xywh(Rect *rect, double x, double y, double w, double h) {
|
|
|
|
|
rect->top_left = CMPLX(x, y);
|
|
|
|
|
rect->bottom_right = CMPLX(w, h) + rect->top_left;
|
|
|
|
|
}
|