taisei/src/util/geometry.c
2019-03-26 17:58:38 +02:00

229 lines
6.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/
#include "taisei.h"
#include "geometry.h"
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;
}
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;
Rect e_bbox;
ellipse_bbox(&e, &e_bbox);
return point_in_rect(p, e_bbox) && (
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;
}
// 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))
};
Rect e_bbox;
ellipse_bbox(&e, &e_bbox);
return !rect_rect_intersect(seg_bbox, e_bbox, true, true);
}
// 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.
static double lineseg_circle_intersect_fallback(LineSegment seg, Circle c) {
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
distance = sqrt(lv*lv - projection*projection);
if(distance <= c.radius) {
double f = projection/lm;
if(f >= 0 && f <= 1) { // its on the line!
return f;
}
}
return -1;
}
bool lineseg_ellipse_intersect(LineSegment seg, Ellipse e) {
if(segment_ellipse_nonintersection_heuristic(seg, e)) {
return false;
}
// 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.
seg.a -= e.origin;
seg.b -= e.origin;
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 };
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 };
if(segment_ellipse_nonintersection_heuristic(seg, e)) {
return -1;
}
return lineseg_circle_intersect_fallback(seg, c);
}
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);
}
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);
}
bool rect_rect_intersect(Rect r1, Rect r2, bool edges, bool corners) {
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;
}
if(!corners && (
(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))
)) {
// Discard corner intersects
return false;
}
return true;
}
bool rect_rect_intersection(Rect r1, Rect r2, bool edges, bool corners, Rect *out) {
if(!rect_rect_intersect(r1, r2, edges, corners)) {
return false;
}
out->top_left = CMPLX(
fmax(rect_left(r1), rect_left(r2)),
fmax(rect_top(r1), rect_top(r2))
);
out->bottom_right = CMPLX(
fmin(rect_right(r1), rect_right(r2)),
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;
}
if(!rect_rect_intersect(*r1, r2, true, false)) {
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;
}