Browse Source

[Core] Move Rect2 and Transform2D to their own files

Math2D includes Transform2D, which includes Rect2, which includes Vector2.
Aaron Franke 7 years ago
parent
commit
2eb8a9749e
6 changed files with 950 additions and 818 deletions
  1. 0 299
      core/math/math_2d.cpp
  2. 1 519
      core/math/math_2d.h
  3. 240 0
      core/math/rect2.cpp
  4. 236 0
      core/math/rect2.h
  5. 272 0
      core/math/transform_2d.cpp
  6. 201 0
      core/math/transform_2d.h

+ 0 - 299
core/math/math_2d.cpp

@@ -30,64 +30,6 @@
 
 #include "math_2d.h"
 
-bool Rect2::intersects_segment(const Point2 &p_from, const Point2 &p_to, Point2 *r_pos, Point2 *r_normal) const {
-
-	real_t min = 0, max = 1;
-	int axis = 0;
-	real_t sign = 0;
-
-	for (int i = 0; i < 2; i++) {
-		real_t seg_from = p_from[i];
-		real_t seg_to = p_to[i];
-		real_t box_begin = position[i];
-		real_t box_end = box_begin + size[i];
-		real_t cmin, cmax;
-		real_t csign;
-
-		if (seg_from < seg_to) {
-
-			if (seg_from > box_end || seg_to < box_begin)
-				return false;
-			real_t length = seg_to - seg_from;
-			cmin = (seg_from < box_begin) ? ((box_begin - seg_from) / length) : 0;
-			cmax = (seg_to > box_end) ? ((box_end - seg_from) / length) : 1;
-			csign = -1.0;
-
-		} else {
-
-			if (seg_to > box_end || seg_from < box_begin)
-				return false;
-			real_t length = seg_to - seg_from;
-			cmin = (seg_from > box_end) ? (box_end - seg_from) / length : 0;
-			cmax = (seg_to < box_begin) ? (box_begin - seg_from) / length : 1;
-			csign = 1.0;
-		}
-
-		if (cmin > min) {
-			min = cmin;
-			axis = i;
-			sign = csign;
-		}
-		if (cmax < max)
-			max = cmax;
-		if (max < min)
-			return false;
-	}
-
-	Vector2 rel = p_to - p_from;
-
-	if (r_normal) {
-		Vector2 normal;
-		normal[axis] = sign;
-		*r_normal = normal;
-	}
-
-	if (r_pos)
-		*r_pos = p_from + rel * min;
-
-	return true;
-}
-
 /* Point2i */
 
 Point2i Point2i::operator+(const Point2i &p_v) const {
@@ -153,244 +95,3 @@ bool Point2i::operator!=(const Point2i &p_vec2) const {
 
 	return x != p_vec2.x || y != p_vec2.y;
 }
-
-void Transform2D::invert() {
-	// FIXME: this function assumes the basis is a rotation matrix, with no scaling.
-	// Transform2D::affine_inverse can handle matrices with scaling, so GDScript should eventually use that.
-	SWAP(elements[0][1], elements[1][0]);
-	elements[2] = basis_xform(-elements[2]);
-}
-
-Transform2D Transform2D::inverse() const {
-
-	Transform2D inv = *this;
-	inv.invert();
-	return inv;
-}
-
-void Transform2D::affine_invert() {
-
-	real_t det = basis_determinant();
-#ifdef MATH_CHECKS
-	ERR_FAIL_COND(det == 0);
-#endif
-	real_t idet = 1.0 / det;
-
-	SWAP(elements[0][0], elements[1][1]);
-	elements[0] *= Vector2(idet, -idet);
-	elements[1] *= Vector2(-idet, idet);
-
-	elements[2] = basis_xform(-elements[2]);
-}
-
-Transform2D Transform2D::affine_inverse() const {
-
-	Transform2D inv = *this;
-	inv.affine_invert();
-	return inv;
-}
-
-void Transform2D::rotate(real_t p_phi) {
-	*this = Transform2D(p_phi, Vector2()) * (*this);
-}
-
-real_t Transform2D::get_rotation() const {
-	real_t det = basis_determinant();
-	Transform2D m = orthonormalized();
-	if (det < 0) {
-		m.scale_basis(Size2(1, -1)); // convention to separate rotation and reflection for 2D is to absorb a flip along y into scaling.
-	}
-	return Math::atan2(m[0].y, m[0].x);
-}
-
-void Transform2D::set_rotation(real_t p_rot) {
-
-	real_t cr = Math::cos(p_rot);
-	real_t sr = Math::sin(p_rot);
-	elements[0][0] = cr;
-	elements[0][1] = sr;
-	elements[1][0] = -sr;
-	elements[1][1] = cr;
-}
-
-Transform2D::Transform2D(real_t p_rot, const Vector2 &p_pos) {
-
-	real_t cr = Math::cos(p_rot);
-	real_t sr = Math::sin(p_rot);
-	elements[0][0] = cr;
-	elements[0][1] = sr;
-	elements[1][0] = -sr;
-	elements[1][1] = cr;
-	elements[2] = p_pos;
-}
-
-Size2 Transform2D::get_scale() const {
-	real_t det_sign = basis_determinant() > 0 ? 1 : -1;
-	return Size2(elements[0].length(), det_sign * elements[1].length());
-}
-
-void Transform2D::scale(const Size2 &p_scale) {
-	scale_basis(p_scale);
-	elements[2] *= p_scale;
-}
-void Transform2D::scale_basis(const Size2 &p_scale) {
-
-	elements[0][0] *= p_scale.x;
-	elements[0][1] *= p_scale.y;
-	elements[1][0] *= p_scale.x;
-	elements[1][1] *= p_scale.y;
-}
-void Transform2D::translate(real_t p_tx, real_t p_ty) {
-
-	translate(Vector2(p_tx, p_ty));
-}
-void Transform2D::translate(const Vector2 &p_translation) {
-
-	elements[2] += basis_xform(p_translation);
-}
-
-void Transform2D::orthonormalize() {
-
-	// Gram-Schmidt Process
-
-	Vector2 x = elements[0];
-	Vector2 y = elements[1];
-
-	x.normalize();
-	y = (y - x * (x.dot(y)));
-	y.normalize();
-
-	elements[0] = x;
-	elements[1] = y;
-}
-Transform2D Transform2D::orthonormalized() const {
-
-	Transform2D on = *this;
-	on.orthonormalize();
-	return on;
-}
-
-bool Transform2D::operator==(const Transform2D &p_transform) const {
-
-	for (int i = 0; i < 3; i++) {
-		if (elements[i] != p_transform.elements[i])
-			return false;
-	}
-
-	return true;
-}
-
-bool Transform2D::operator!=(const Transform2D &p_transform) const {
-
-	for (int i = 0; i < 3; i++) {
-		if (elements[i] != p_transform.elements[i])
-			return true;
-	}
-
-	return false;
-}
-
-void Transform2D::operator*=(const Transform2D &p_transform) {
-
-	elements[2] = xform(p_transform.elements[2]);
-
-	real_t x0, x1, y0, y1;
-
-	x0 = tdotx(p_transform.elements[0]);
-	x1 = tdoty(p_transform.elements[0]);
-	y0 = tdotx(p_transform.elements[1]);
-	y1 = tdoty(p_transform.elements[1]);
-
-	elements[0][0] = x0;
-	elements[0][1] = x1;
-	elements[1][0] = y0;
-	elements[1][1] = y1;
-}
-
-Transform2D Transform2D::operator*(const Transform2D &p_transform) const {
-
-	Transform2D t = *this;
-	t *= p_transform;
-	return t;
-}
-
-Transform2D Transform2D::scaled(const Size2 &p_scale) const {
-
-	Transform2D copy = *this;
-	copy.scale(p_scale);
-	return copy;
-}
-
-Transform2D Transform2D::basis_scaled(const Size2 &p_scale) const {
-
-	Transform2D copy = *this;
-	copy.scale_basis(p_scale);
-	return copy;
-}
-
-Transform2D Transform2D::untranslated() const {
-
-	Transform2D copy = *this;
-	copy.elements[2] = Vector2();
-	return copy;
-}
-
-Transform2D Transform2D::translated(const Vector2 &p_offset) const {
-
-	Transform2D copy = *this;
-	copy.translate(p_offset);
-	return copy;
-}
-
-Transform2D Transform2D::rotated(real_t p_phi) const {
-
-	Transform2D copy = *this;
-	copy.rotate(p_phi);
-	return copy;
-}
-
-real_t Transform2D::basis_determinant() const {
-
-	return elements[0].x * elements[1].y - elements[0].y * elements[1].x;
-}
-
-Transform2D Transform2D::interpolate_with(const Transform2D &p_transform, real_t p_c) const {
-
-	//extract parameters
-	Vector2 p1 = get_origin();
-	Vector2 p2 = p_transform.get_origin();
-
-	real_t r1 = get_rotation();
-	real_t r2 = p_transform.get_rotation();
-
-	Size2 s1 = get_scale();
-	Size2 s2 = p_transform.get_scale();
-
-	//slerp rotation
-	Vector2 v1(Math::cos(r1), Math::sin(r1));
-	Vector2 v2(Math::cos(r2), Math::sin(r2));
-
-	real_t dot = v1.dot(v2);
-
-	dot = (dot < -1.0) ? -1.0 : ((dot > 1.0) ? 1.0 : dot); //clamp dot to [-1,1]
-
-	Vector2 v;
-
-	if (dot > 0.9995) {
-		v = Vector2::linear_interpolate(v1, v2, p_c).normalized(); //linearly interpolate to avoid numerical precision issues
-	} else {
-		real_t angle = p_c * Math::acos(dot);
-		Vector2 v3 = (v2 - v1 * dot).normalized();
-		v = v1 * Math::cos(angle) + v3 * Math::sin(angle);
-	}
-
-	//construct matrix
-	Transform2D res(Math::atan2(v.y, v.x), Vector2::linear_interpolate(p1, p2, p_c));
-	res.scale_basis(Vector2::linear_interpolate(s1, s2, p_c));
-	return res;
-}
-
-Transform2D::operator String() const {
-
-	return String(String() + elements[0] + ", " + elements[1] + ", " + elements[2]);
-}

+ 1 - 519
core/math/math_2d.h

@@ -31,212 +31,10 @@
 #ifndef MATH_2D_H
 #define MATH_2D_H
 
-#include "math_funcs.h"
-#include "ustring.h"
-#include "vector2.h"
+#include "transform_2d.h"
 /**
 	@author Juan Linietsky <[email protected]>
 */
-struct Transform2D;
-
-struct Rect2 {
-
-	Point2 position;
-	Size2 size;
-
-	const Vector2 &get_position() const { return position; }
-	void set_position(const Vector2 &p_pos) { position = p_pos; }
-	const Vector2 &get_size() const { return size; }
-	void set_size(const Vector2 &p_size) { size = p_size; }
-
-	real_t get_area() const { return size.width * size.height; }
-
-	inline bool intersects(const Rect2 &p_rect) const {
-		if (position.x >= (p_rect.position.x + p_rect.size.width))
-			return false;
-		if ((position.x + size.width) <= p_rect.position.x)
-			return false;
-		if (position.y >= (p_rect.position.y + p_rect.size.height))
-			return false;
-		if ((position.y + size.height) <= p_rect.position.y)
-			return false;
-
-		return true;
-	}
-
-	inline real_t distance_to(const Vector2 &p_point) const {
-
-		real_t dist = 0.0;
-		bool inside = true;
-
-		if (p_point.x < position.x) {
-			real_t d = position.x - p_point.x;
-			dist = inside ? d : MIN(dist, d);
-			inside = false;
-		}
-		if (p_point.y < position.y) {
-			real_t d = position.y - p_point.y;
-			dist = inside ? d : MIN(dist, d);
-			inside = false;
-		}
-		if (p_point.x >= (position.x + size.x)) {
-			real_t d = p_point.x - (position.x + size.x);
-			dist = inside ? d : MIN(dist, d);
-			inside = false;
-		}
-		if (p_point.y >= (position.y + size.y)) {
-			real_t d = p_point.y - (position.y + size.y);
-			dist = inside ? d : MIN(dist, d);
-			inside = false;
-		}
-
-		if (inside)
-			return 0;
-		else
-			return dist;
-	}
-
-	_FORCE_INLINE_ bool intersects_transformed(const Transform2D &p_xform, const Rect2 &p_rect) const;
-
-	bool intersects_segment(const Point2 &p_from, const Point2 &p_to, Point2 *r_pos = NULL, Point2 *r_normal = NULL) const;
-
-	inline bool encloses(const Rect2 &p_rect) const {
-
-		return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) &&
-			   ((p_rect.position.x + p_rect.size.x) < (position.x + size.x)) &&
-			   ((p_rect.position.y + p_rect.size.y) < (position.y + size.y));
-	}
-
-	inline bool has_no_area() const {
-
-		return (size.x <= 0 || size.y <= 0);
-	}
-	inline Rect2 clip(const Rect2 &p_rect) const { /// return a clipped rect
-
-		Rect2 new_rect = p_rect;
-
-		if (!intersects(new_rect))
-			return Rect2();
-
-		new_rect.position.x = MAX(p_rect.position.x, position.x);
-		new_rect.position.y = MAX(p_rect.position.y, position.y);
-
-		Point2 p_rect_end = p_rect.position + p_rect.size;
-		Point2 end = position + size;
-
-		new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x;
-		new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y;
-
-		return new_rect;
-	}
-
-	inline Rect2 merge(const Rect2 &p_rect) const { ///< return a merged rect
-
-		Rect2 new_rect;
-
-		new_rect.position.x = MIN(p_rect.position.x, position.x);
-		new_rect.position.y = MIN(p_rect.position.y, position.y);
-
-		new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x);
-		new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y);
-
-		new_rect.size = new_rect.size - new_rect.position; //make relative again
-
-		return new_rect;
-	};
-	inline bool has_point(const Point2 &p_point) const {
-		if (p_point.x < position.x)
-			return false;
-		if (p_point.y < position.y)
-			return false;
-
-		if (p_point.x >= (position.x + size.x))
-			return false;
-		if (p_point.y >= (position.y + size.y))
-			return false;
-
-		return true;
-	}
-
-	inline bool no_area() const { return (size.width <= 0 || size.height <= 0); }
-
-	bool operator==(const Rect2 &p_rect) const { return position == p_rect.position && size == p_rect.size; }
-	bool operator!=(const Rect2 &p_rect) const { return position != p_rect.position || size != p_rect.size; }
-
-	inline Rect2 grow(real_t p_by) const {
-
-		Rect2 g = *this;
-		g.position.x -= p_by;
-		g.position.y -= p_by;
-		g.size.width += p_by * 2;
-		g.size.height += p_by * 2;
-		return g;
-	}
-
-	inline Rect2 grow_margin(Margin p_margin, real_t p_amount) const {
-		Rect2 g = *this;
-		g = g.grow_individual((MARGIN_LEFT == p_margin) ? p_amount : 0,
-				(MARGIN_TOP == p_margin) ? p_amount : 0,
-				(MARGIN_RIGHT == p_margin) ? p_amount : 0,
-				(MARGIN_BOTTOM == p_margin) ? p_amount : 0);
-		return g;
-	}
-
-	inline Rect2 grow_individual(real_t p_left, real_t p_top, real_t p_right, real_t p_bottom) const {
-
-		Rect2 g = *this;
-		g.position.x -= p_left;
-		g.position.y -= p_top;
-		g.size.width += p_left + p_right;
-		g.size.height += p_top + p_bottom;
-
-		return g;
-	}
-
-	inline Rect2 expand(const Vector2 &p_vector) const {
-
-		Rect2 r = *this;
-		r.expand_to(p_vector);
-		return r;
-	}
-
-	inline void expand_to(const Vector2 &p_vector) { //in place function for speed
-
-		Vector2 begin = position;
-		Vector2 end = position + size;
-
-		if (p_vector.x < begin.x)
-			begin.x = p_vector.x;
-		if (p_vector.y < begin.y)
-			begin.y = p_vector.y;
-
-		if (p_vector.x > end.x)
-			end.x = p_vector.x;
-		if (p_vector.y > end.y)
-			end.y = p_vector.y;
-
-		position = begin;
-		size = end - begin;
-	}
-
-	inline Rect2 abs() const {
-
-		return Rect2(Point2(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs());
-	}
-
-	operator String() const { return String(position) + ", " + String(size); }
-
-	Rect2() {}
-	Rect2(real_t p_x, real_t p_y, real_t p_width, real_t p_height) :
-			position(Point2(p_x, p_y)),
-			size(Size2(p_width, p_height)) {
-	}
-	Rect2(const Point2 &p_pos, const Size2 &p_size) :
-			position(p_pos),
-			size(p_size) {
-	}
-};
-
 /* INTEGER STUFF */
 
 struct Point2i {
@@ -435,320 +233,4 @@ struct Rect2i {
 	}
 };
 
-struct Transform2D {
-	// Warning #1: basis of Transform2D is stored differently from Basis. In terms of elements array, the basis matrix looks like "on paper":
-	// M = (elements[0][0] elements[1][0])
-	//     (elements[0][1] elements[1][1])
-	// This is such that the columns, which can be interpreted as basis vectors of the coordinate system "painted" on the object, can be accessed as elements[i].
-	// Note that this is the opposite of the indices in mathematical texts, meaning: $M_{12}$ in a math book corresponds to elements[1][0] here.
-	// This requires additional care when working with explicit indices.
-	// See https://en.wikipedia.org/wiki/Row-_and_column-major_order for further reading.
-
-	// Warning #2: 2D be aware that unlike 3D code, 2D code uses a left-handed coordinate system: Y-axis points down,
-	// and angle is measure from +X to +Y in a clockwise-fashion.
-
-	Vector2 elements[3];
-
-	_FORCE_INLINE_ real_t tdotx(const Vector2 &v) const { return elements[0][0] * v.x + elements[1][0] * v.y; }
-	_FORCE_INLINE_ real_t tdoty(const Vector2 &v) const { return elements[0][1] * v.x + elements[1][1] * v.y; }
-
-	const Vector2 &operator[](int p_idx) const { return elements[p_idx]; }
-	Vector2 &operator[](int p_idx) { return elements[p_idx]; }
-
-	_FORCE_INLINE_ Vector2 get_axis(int p_axis) const {
-		ERR_FAIL_INDEX_V(p_axis, 3, Vector2());
-		return elements[p_axis];
-	}
-	_FORCE_INLINE_ void set_axis(int p_axis, const Vector2 &p_vec) {
-		ERR_FAIL_INDEX(p_axis, 3);
-		elements[p_axis] = p_vec;
-	}
-
-	void invert();
-	Transform2D inverse() const;
-
-	void affine_invert();
-	Transform2D affine_inverse() const;
-
-	void set_rotation(real_t p_rot);
-	real_t get_rotation() const;
-	_FORCE_INLINE_ void set_rotation_and_scale(real_t p_rot, const Size2 &p_scale);
-	void rotate(real_t p_phi);
-
-	void scale(const Size2 &p_scale);
-	void scale_basis(const Size2 &p_scale);
-	void translate(real_t p_tx, real_t p_ty);
-	void translate(const Vector2 &p_translation);
-
-	real_t basis_determinant() const;
-
-	Size2 get_scale() const;
-
-	_FORCE_INLINE_ const Vector2 &get_origin() const { return elements[2]; }
-	_FORCE_INLINE_ void set_origin(const Vector2 &p_origin) { elements[2] = p_origin; }
-
-	Transform2D scaled(const Size2 &p_scale) const;
-	Transform2D basis_scaled(const Size2 &p_scale) const;
-	Transform2D translated(const Vector2 &p_offset) const;
-	Transform2D rotated(real_t p_phi) const;
-
-	Transform2D untranslated() const;
-
-	void orthonormalize();
-	Transform2D orthonormalized() const;
-
-	bool operator==(const Transform2D &p_transform) const;
-	bool operator!=(const Transform2D &p_transform) const;
-
-	void operator*=(const Transform2D &p_transform);
-	Transform2D operator*(const Transform2D &p_transform) const;
-
-	Transform2D interpolate_with(const Transform2D &p_transform, real_t p_c) const;
-
-	_FORCE_INLINE_ Vector2 basis_xform(const Vector2 &p_vec) const;
-	_FORCE_INLINE_ Vector2 basis_xform_inv(const Vector2 &p_vec) const;
-	_FORCE_INLINE_ Vector2 xform(const Vector2 &p_vec) const;
-	_FORCE_INLINE_ Vector2 xform_inv(const Vector2 &p_vec) const;
-	_FORCE_INLINE_ Rect2 xform(const Rect2 &p_rect) const;
-	_FORCE_INLINE_ Rect2 xform_inv(const Rect2 &p_rect) const;
-
-	operator String() const;
-
-	Transform2D(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy) {
-
-		elements[0][0] = xx;
-		elements[0][1] = xy;
-		elements[1][0] = yx;
-		elements[1][1] = yy;
-		elements[2][0] = ox;
-		elements[2][1] = oy;
-	}
-
-	Transform2D(real_t p_rot, const Vector2 &p_pos);
-	Transform2D() {
-		elements[0][0] = 1.0;
-		elements[1][1] = 1.0;
-	}
-};
-
-bool Rect2::intersects_transformed(const Transform2D &p_xform, const Rect2 &p_rect) const {
-
-	//SAT intersection between local and transformed rect2
-
-	Vector2 xf_points[4] = {
-		p_xform.xform(p_rect.position),
-		p_xform.xform(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y)),
-		p_xform.xform(Vector2(p_rect.position.x, p_rect.position.y + p_rect.size.y)),
-		p_xform.xform(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y + p_rect.size.y)),
-	};
-
-	real_t low_limit;
-
-	//base rect2 first (faster)
-
-	if (xf_points[0].y > position.y)
-		goto next1;
-	if (xf_points[1].y > position.y)
-		goto next1;
-	if (xf_points[2].y > position.y)
-		goto next1;
-	if (xf_points[3].y > position.y)
-		goto next1;
-
-	return false;
-
-next1:
-
-	low_limit = position.y + size.y;
-
-	if (xf_points[0].y < low_limit)
-		goto next2;
-	if (xf_points[1].y < low_limit)
-		goto next2;
-	if (xf_points[2].y < low_limit)
-		goto next2;
-	if (xf_points[3].y < low_limit)
-		goto next2;
-
-	return false;
-
-next2:
-
-	if (xf_points[0].x > position.x)
-		goto next3;
-	if (xf_points[1].x > position.x)
-		goto next3;
-	if (xf_points[2].x > position.x)
-		goto next3;
-	if (xf_points[3].x > position.x)
-		goto next3;
-
-	return false;
-
-next3:
-
-	low_limit = position.x + size.x;
-
-	if (xf_points[0].x < low_limit)
-		goto next4;
-	if (xf_points[1].x < low_limit)
-		goto next4;
-	if (xf_points[2].x < low_limit)
-		goto next4;
-	if (xf_points[3].x < low_limit)
-		goto next4;
-
-	return false;
-
-next4:
-
-	Vector2 xf_points2[4] = {
-		position,
-		Vector2(position.x + size.x, position.y),
-		Vector2(position.x, position.y + size.y),
-		Vector2(position.x + size.x, position.y + size.y),
-	};
-
-	real_t maxa = p_xform.elements[0].dot(xf_points2[0]);
-	real_t mina = maxa;
-
-	real_t dp = p_xform.elements[0].dot(xf_points2[1]);
-	maxa = MAX(dp, maxa);
-	mina = MIN(dp, mina);
-
-	dp = p_xform.elements[0].dot(xf_points2[2]);
-	maxa = MAX(dp, maxa);
-	mina = MIN(dp, mina);
-
-	dp = p_xform.elements[0].dot(xf_points2[3]);
-	maxa = MAX(dp, maxa);
-	mina = MIN(dp, mina);
-
-	real_t maxb = p_xform.elements[0].dot(xf_points[0]);
-	real_t minb = maxb;
-
-	dp = p_xform.elements[0].dot(xf_points[1]);
-	maxb = MAX(dp, maxb);
-	minb = MIN(dp, minb);
-
-	dp = p_xform.elements[0].dot(xf_points[2]);
-	maxb = MAX(dp, maxb);
-	minb = MIN(dp, minb);
-
-	dp = p_xform.elements[0].dot(xf_points[3]);
-	maxb = MAX(dp, maxb);
-	minb = MIN(dp, minb);
-
-	if (mina > maxb)
-		return false;
-	if (minb > maxa)
-		return false;
-
-	maxa = p_xform.elements[1].dot(xf_points2[0]);
-	mina = maxa;
-
-	dp = p_xform.elements[1].dot(xf_points2[1]);
-	maxa = MAX(dp, maxa);
-	mina = MIN(dp, mina);
-
-	dp = p_xform.elements[1].dot(xf_points2[2]);
-	maxa = MAX(dp, maxa);
-	mina = MIN(dp, mina);
-
-	dp = p_xform.elements[1].dot(xf_points2[3]);
-	maxa = MAX(dp, maxa);
-	mina = MIN(dp, mina);
-
-	maxb = p_xform.elements[1].dot(xf_points[0]);
-	minb = maxb;
-
-	dp = p_xform.elements[1].dot(xf_points[1]);
-	maxb = MAX(dp, maxb);
-	minb = MIN(dp, minb);
-
-	dp = p_xform.elements[1].dot(xf_points[2]);
-	maxb = MAX(dp, maxb);
-	minb = MIN(dp, minb);
-
-	dp = p_xform.elements[1].dot(xf_points[3]);
-	maxb = MAX(dp, maxb);
-	minb = MIN(dp, minb);
-
-	if (mina > maxb)
-		return false;
-	if (minb > maxa)
-		return false;
-
-	return true;
-}
-
-Vector2 Transform2D::basis_xform(const Vector2 &p_vec) const {
-
-	return Vector2(
-			tdotx(p_vec),
-			tdoty(p_vec));
-}
-
-Vector2 Transform2D::basis_xform_inv(const Vector2 &p_vec) const {
-
-	return Vector2(
-			elements[0].dot(p_vec),
-			elements[1].dot(p_vec));
-}
-
-Vector2 Transform2D::xform(const Vector2 &p_vec) const {
-
-	return Vector2(
-				   tdotx(p_vec),
-				   tdoty(p_vec)) +
-		   elements[2];
-}
-Vector2 Transform2D::xform_inv(const Vector2 &p_vec) const {
-
-	Vector2 v = p_vec - elements[2];
-
-	return Vector2(
-			elements[0].dot(v),
-			elements[1].dot(v));
-}
-Rect2 Transform2D::xform(const Rect2 &p_rect) const {
-
-	Vector2 x = elements[0] * p_rect.size.x;
-	Vector2 y = elements[1] * p_rect.size.y;
-	Vector2 pos = xform(p_rect.position);
-
-	Rect2 new_rect;
-	new_rect.position = pos;
-	new_rect.expand_to(pos + x);
-	new_rect.expand_to(pos + y);
-	new_rect.expand_to(pos + x + y);
-	return new_rect;
-}
-
-void Transform2D::set_rotation_and_scale(real_t p_rot, const Size2 &p_scale) {
-
-	elements[0][0] = Math::cos(p_rot) * p_scale.x;
-	elements[1][1] = Math::cos(p_rot) * p_scale.y;
-	elements[1][0] = -Math::sin(p_rot) * p_scale.y;
-	elements[0][1] = Math::sin(p_rot) * p_scale.x;
-}
-
-Rect2 Transform2D::xform_inv(const Rect2 &p_rect) const {
-
-	Vector2 ends[4] = {
-		xform_inv(p_rect.position),
-		xform_inv(Vector2(p_rect.position.x, p_rect.position.y + p_rect.size.y)),
-		xform_inv(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y + p_rect.size.y)),
-		xform_inv(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y))
-	};
-
-	Rect2 new_rect;
-	new_rect.position = ends[0];
-	new_rect.expand_to(ends[1]);
-	new_rect.expand_to(ends[2]);
-	new_rect.expand_to(ends[3]);
-
-	return new_rect;
-}
-
 #endif

+ 240 - 0
core/math/rect2.cpp

@@ -0,0 +1,240 @@
+/*************************************************************************/
+/*  rect2.cpp                                                            */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "transform_2d.h" // Includes rect2.h but Rect2 needs Transform2D
+
+bool Rect2::intersects_segment(const Point2 &p_from, const Point2 &p_to, Point2 *r_pos, Point2 *r_normal) const {
+
+	real_t min = 0, max = 1;
+	int axis = 0;
+	real_t sign = 0;
+
+	for (int i = 0; i < 2; i++) {
+		real_t seg_from = p_from[i];
+		real_t seg_to = p_to[i];
+		real_t box_begin = position[i];
+		real_t box_end = box_begin + size[i];
+		real_t cmin, cmax;
+		real_t csign;
+
+		if (seg_from < seg_to) {
+
+			if (seg_from > box_end || seg_to < box_begin)
+				return false;
+			real_t length = seg_to - seg_from;
+			cmin = (seg_from < box_begin) ? ((box_begin - seg_from) / length) : 0;
+			cmax = (seg_to > box_end) ? ((box_end - seg_from) / length) : 1;
+			csign = -1.0;
+
+		} else {
+
+			if (seg_to > box_end || seg_from < box_begin)
+				return false;
+			real_t length = seg_to - seg_from;
+			cmin = (seg_from > box_end) ? (box_end - seg_from) / length : 0;
+			cmax = (seg_to < box_begin) ? (box_begin - seg_from) / length : 1;
+			csign = 1.0;
+		}
+
+		if (cmin > min) {
+			min = cmin;
+			axis = i;
+			sign = csign;
+		}
+		if (cmax < max)
+			max = cmax;
+		if (max < min)
+			return false;
+	}
+
+	Vector2 rel = p_to - p_from;
+
+	if (r_normal) {
+		Vector2 normal;
+		normal[axis] = sign;
+		*r_normal = normal;
+	}
+
+	if (r_pos)
+		*r_pos = p_from + rel * min;
+
+	return true;
+}
+
+bool Rect2::intersects_transformed(const Transform2D &p_xform, const Rect2 &p_rect) const {
+
+	//SAT intersection between local and transformed rect2
+
+	Vector2 xf_points[4] = {
+		p_xform.xform(p_rect.position),
+		p_xform.xform(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y)),
+		p_xform.xform(Vector2(p_rect.position.x, p_rect.position.y + p_rect.size.y)),
+		p_xform.xform(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y + p_rect.size.y)),
+	};
+
+	real_t low_limit;
+
+	//base rect2 first (faster)
+
+	if (xf_points[0].y > position.y)
+		goto next1;
+	if (xf_points[1].y > position.y)
+		goto next1;
+	if (xf_points[2].y > position.y)
+		goto next1;
+	if (xf_points[3].y > position.y)
+		goto next1;
+
+	return false;
+
+next1:
+
+	low_limit = position.y + size.y;
+
+	if (xf_points[0].y < low_limit)
+		goto next2;
+	if (xf_points[1].y < low_limit)
+		goto next2;
+	if (xf_points[2].y < low_limit)
+		goto next2;
+	if (xf_points[3].y < low_limit)
+		goto next2;
+
+	return false;
+
+next2:
+
+	if (xf_points[0].x > position.x)
+		goto next3;
+	if (xf_points[1].x > position.x)
+		goto next3;
+	if (xf_points[2].x > position.x)
+		goto next3;
+	if (xf_points[3].x > position.x)
+		goto next3;
+
+	return false;
+
+next3:
+
+	low_limit = position.x + size.x;
+
+	if (xf_points[0].x < low_limit)
+		goto next4;
+	if (xf_points[1].x < low_limit)
+		goto next4;
+	if (xf_points[2].x < low_limit)
+		goto next4;
+	if (xf_points[3].x < low_limit)
+		goto next4;
+
+	return false;
+
+next4:
+
+	Vector2 xf_points2[4] = {
+		position,
+		Vector2(position.x + size.x, position.y),
+		Vector2(position.x, position.y + size.y),
+		Vector2(position.x + size.x, position.y + size.y),
+	};
+
+	real_t maxa = p_xform.elements[0].dot(xf_points2[0]);
+	real_t mina = maxa;
+
+	real_t dp = p_xform.elements[0].dot(xf_points2[1]);
+	maxa = MAX(dp, maxa);
+	mina = MIN(dp, mina);
+
+	dp = p_xform.elements[0].dot(xf_points2[2]);
+	maxa = MAX(dp, maxa);
+	mina = MIN(dp, mina);
+
+	dp = p_xform.elements[0].dot(xf_points2[3]);
+	maxa = MAX(dp, maxa);
+	mina = MIN(dp, mina);
+
+	real_t maxb = p_xform.elements[0].dot(xf_points[0]);
+	real_t minb = maxb;
+
+	dp = p_xform.elements[0].dot(xf_points[1]);
+	maxb = MAX(dp, maxb);
+	minb = MIN(dp, minb);
+
+	dp = p_xform.elements[0].dot(xf_points[2]);
+	maxb = MAX(dp, maxb);
+	minb = MIN(dp, minb);
+
+	dp = p_xform.elements[0].dot(xf_points[3]);
+	maxb = MAX(dp, maxb);
+	minb = MIN(dp, minb);
+
+	if (mina > maxb)
+		return false;
+	if (minb > maxa)
+		return false;
+
+	maxa = p_xform.elements[1].dot(xf_points2[0]);
+	mina = maxa;
+
+	dp = p_xform.elements[1].dot(xf_points2[1]);
+	maxa = MAX(dp, maxa);
+	mina = MIN(dp, mina);
+
+	dp = p_xform.elements[1].dot(xf_points2[2]);
+	maxa = MAX(dp, maxa);
+	mina = MIN(dp, mina);
+
+	dp = p_xform.elements[1].dot(xf_points2[3]);
+	maxa = MAX(dp, maxa);
+	mina = MIN(dp, mina);
+
+	maxb = p_xform.elements[1].dot(xf_points[0]);
+	minb = maxb;
+
+	dp = p_xform.elements[1].dot(xf_points[1]);
+	maxb = MAX(dp, maxb);
+	minb = MIN(dp, minb);
+
+	dp = p_xform.elements[1].dot(xf_points[2]);
+	maxb = MAX(dp, maxb);
+	minb = MIN(dp, minb);
+
+	dp = p_xform.elements[1].dot(xf_points[3]);
+	maxb = MAX(dp, maxb);
+	minb = MIN(dp, minb);
+
+	if (mina > maxb)
+		return false;
+	if (minb > maxa)
+		return false;
+
+	return true;
+}

+ 236 - 0
core/math/rect2.h

@@ -0,0 +1,236 @@
+/*************************************************************************/
+/*  rect2.h                                                              */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef RECT2_H
+#define RECT2_H
+
+#include "vector2.h" // also includes math_funcs and ustring
+
+struct Transform2D;
+
+struct Rect2 {
+
+	Point2 position;
+	Size2 size;
+
+	const Vector2 &get_position() const { return position; }
+	void set_position(const Vector2 &p_pos) { position = p_pos; }
+	const Vector2 &get_size() const { return size; }
+	void set_size(const Vector2 &p_size) { size = p_size; }
+
+	real_t get_area() const { return size.width * size.height; }
+
+	inline bool intersects(const Rect2 &p_rect) const {
+		if (position.x >= (p_rect.position.x + p_rect.size.width))
+			return false;
+		if ((position.x + size.width) <= p_rect.position.x)
+			return false;
+		if (position.y >= (p_rect.position.y + p_rect.size.height))
+			return false;
+		if ((position.y + size.height) <= p_rect.position.y)
+			return false;
+
+		return true;
+	}
+
+	inline real_t distance_to(const Vector2 &p_point) const {
+
+		real_t dist = 0.0;
+		bool inside = true;
+
+		if (p_point.x < position.x) {
+			real_t d = position.x - p_point.x;
+			dist = inside ? d : MIN(dist, d);
+			inside = false;
+		}
+		if (p_point.y < position.y) {
+			real_t d = position.y - p_point.y;
+			dist = inside ? d : MIN(dist, d);
+			inside = false;
+		}
+		if (p_point.x >= (position.x + size.x)) {
+			real_t d = p_point.x - (position.x + size.x);
+			dist = inside ? d : MIN(dist, d);
+			inside = false;
+		}
+		if (p_point.y >= (position.y + size.y)) {
+			real_t d = p_point.y - (position.y + size.y);
+			dist = inside ? d : MIN(dist, d);
+			inside = false;
+		}
+
+		if (inside)
+			return 0;
+		else
+			return dist;
+	}
+
+	bool intersects_transformed(const Transform2D &p_xform, const Rect2 &p_rect) const;
+
+	bool intersects_segment(const Point2 &p_from, const Point2 &p_to, Point2 *r_pos = NULL, Point2 *r_normal = NULL) const;
+
+	inline bool encloses(const Rect2 &p_rect) const {
+
+		return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) &&
+			   ((p_rect.position.x + p_rect.size.x) < (position.x + size.x)) &&
+			   ((p_rect.position.y + p_rect.size.y) < (position.y + size.y));
+	}
+
+	inline bool has_no_area() const {
+
+		return (size.x <= 0 || size.y <= 0);
+	}
+	inline Rect2 clip(const Rect2 &p_rect) const { /// return a clipped rect
+
+		Rect2 new_rect = p_rect;
+
+		if (!intersects(new_rect))
+			return Rect2();
+
+		new_rect.position.x = MAX(p_rect.position.x, position.x);
+		new_rect.position.y = MAX(p_rect.position.y, position.y);
+
+		Point2 p_rect_end = p_rect.position + p_rect.size;
+		Point2 end = position + size;
+
+		new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x;
+		new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y;
+
+		return new_rect;
+	}
+
+	inline Rect2 merge(const Rect2 &p_rect) const { ///< return a merged rect
+
+		Rect2 new_rect;
+
+		new_rect.position.x = MIN(p_rect.position.x, position.x);
+		new_rect.position.y = MIN(p_rect.position.y, position.y);
+
+		new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x);
+		new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y);
+
+		new_rect.size = new_rect.size - new_rect.position; //make relative again
+
+		return new_rect;
+	};
+	inline bool has_point(const Point2 &p_point) const {
+		if (p_point.x < position.x)
+			return false;
+		if (p_point.y < position.y)
+			return false;
+
+		if (p_point.x >= (position.x + size.x))
+			return false;
+		if (p_point.y >= (position.y + size.y))
+			return false;
+
+		return true;
+	}
+
+	inline bool no_area() const { return (size.width <= 0 || size.height <= 0); }
+
+	bool operator==(const Rect2 &p_rect) const { return position == p_rect.position && size == p_rect.size; }
+	bool operator!=(const Rect2 &p_rect) const { return position != p_rect.position || size != p_rect.size; }
+
+	inline Rect2 grow(real_t p_by) const {
+
+		Rect2 g = *this;
+		g.position.x -= p_by;
+		g.position.y -= p_by;
+		g.size.width += p_by * 2;
+		g.size.height += p_by * 2;
+		return g;
+	}
+
+	inline Rect2 grow_margin(Margin p_margin, real_t p_amount) const {
+		Rect2 g = *this;
+		g = g.grow_individual((MARGIN_LEFT == p_margin) ? p_amount : 0,
+				(MARGIN_TOP == p_margin) ? p_amount : 0,
+				(MARGIN_RIGHT == p_margin) ? p_amount : 0,
+				(MARGIN_BOTTOM == p_margin) ? p_amount : 0);
+		return g;
+	}
+
+	inline Rect2 grow_individual(real_t p_left, real_t p_top, real_t p_right, real_t p_bottom) const {
+
+		Rect2 g = *this;
+		g.position.x -= p_left;
+		g.position.y -= p_top;
+		g.size.width += p_left + p_right;
+		g.size.height += p_top + p_bottom;
+
+		return g;
+	}
+
+	inline Rect2 expand(const Vector2 &p_vector) const {
+
+		Rect2 r = *this;
+		r.expand_to(p_vector);
+		return r;
+	}
+
+	inline void expand_to(const Vector2 &p_vector) { //in place function for speed
+
+		Vector2 begin = position;
+		Vector2 end = position + size;
+
+		if (p_vector.x < begin.x)
+			begin.x = p_vector.x;
+		if (p_vector.y < begin.y)
+			begin.y = p_vector.y;
+
+		if (p_vector.x > end.x)
+			end.x = p_vector.x;
+		if (p_vector.y > end.y)
+			end.y = p_vector.y;
+
+		position = begin;
+		size = end - begin;
+	}
+
+	inline Rect2 abs() const {
+
+		return Rect2(Point2(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs());
+	}
+
+	operator String() const { return String(position) + ", " + String(size); }
+
+	Rect2() {}
+	Rect2(real_t p_x, real_t p_y, real_t p_width, real_t p_height) :
+			position(Point2(p_x, p_y)),
+			size(Size2(p_width, p_height)) {
+	}
+	Rect2(const Point2 &p_pos, const Size2 &p_size) :
+			position(p_pos),
+			size(p_size) {
+	}
+};
+
+#endif // RECT2_H

+ 272 - 0
core/math/transform_2d.cpp

@@ -0,0 +1,272 @@
+/*************************************************************************/
+/*  transform_2d.cpp                                                     */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "transform_2d.h"
+
+void Transform2D::invert() {
+	// FIXME: this function assumes the basis is a rotation matrix, with no scaling.
+	// Transform2D::affine_inverse can handle matrices with scaling, so GDScript should eventually use that.
+	SWAP(elements[0][1], elements[1][0]);
+	elements[2] = basis_xform(-elements[2]);
+}
+
+Transform2D Transform2D::inverse() const {
+
+	Transform2D inv = *this;
+	inv.invert();
+	return inv;
+}
+
+void Transform2D::affine_invert() {
+
+	real_t det = basis_determinant();
+#ifdef MATH_CHECKS
+	ERR_FAIL_COND(det == 0);
+#endif
+	real_t idet = 1.0 / det;
+
+	SWAP(elements[0][0], elements[1][1]);
+	elements[0] *= Vector2(idet, -idet);
+	elements[1] *= Vector2(-idet, idet);
+
+	elements[2] = basis_xform(-elements[2]);
+}
+
+Transform2D Transform2D::affine_inverse() const {
+
+	Transform2D inv = *this;
+	inv.affine_invert();
+	return inv;
+}
+
+void Transform2D::rotate(real_t p_phi) {
+	*this = Transform2D(p_phi, Vector2()) * (*this);
+}
+
+real_t Transform2D::get_rotation() const {
+	real_t det = basis_determinant();
+	Transform2D m = orthonormalized();
+	if (det < 0) {
+		m.scale_basis(Size2(1, -1)); // convention to separate rotation and reflection for 2D is to absorb a flip along y into scaling.
+	}
+	return Math::atan2(m[0].y, m[0].x);
+}
+
+void Transform2D::set_rotation(real_t p_rot) {
+
+	real_t cr = Math::cos(p_rot);
+	real_t sr = Math::sin(p_rot);
+	elements[0][0] = cr;
+	elements[0][1] = sr;
+	elements[1][0] = -sr;
+	elements[1][1] = cr;
+}
+
+Transform2D::Transform2D(real_t p_rot, const Vector2 &p_pos) {
+
+	real_t cr = Math::cos(p_rot);
+	real_t sr = Math::sin(p_rot);
+	elements[0][0] = cr;
+	elements[0][1] = sr;
+	elements[1][0] = -sr;
+	elements[1][1] = cr;
+	elements[2] = p_pos;
+}
+
+Size2 Transform2D::get_scale() const {
+	real_t det_sign = basis_determinant() > 0 ? 1 : -1;
+	return Size2(elements[0].length(), det_sign * elements[1].length());
+}
+
+void Transform2D::scale(const Size2 &p_scale) {
+	scale_basis(p_scale);
+	elements[2] *= p_scale;
+}
+void Transform2D::scale_basis(const Size2 &p_scale) {
+
+	elements[0][0] *= p_scale.x;
+	elements[0][1] *= p_scale.y;
+	elements[1][0] *= p_scale.x;
+	elements[1][1] *= p_scale.y;
+}
+void Transform2D::translate(real_t p_tx, real_t p_ty) {
+
+	translate(Vector2(p_tx, p_ty));
+}
+void Transform2D::translate(const Vector2 &p_translation) {
+
+	elements[2] += basis_xform(p_translation);
+}
+
+void Transform2D::orthonormalize() {
+
+	// Gram-Schmidt Process
+
+	Vector2 x = elements[0];
+	Vector2 y = elements[1];
+
+	x.normalize();
+	y = (y - x * (x.dot(y)));
+	y.normalize();
+
+	elements[0] = x;
+	elements[1] = y;
+}
+Transform2D Transform2D::orthonormalized() const {
+
+	Transform2D on = *this;
+	on.orthonormalize();
+	return on;
+}
+
+bool Transform2D::operator==(const Transform2D &p_transform) const {
+
+	for (int i = 0; i < 3; i++) {
+		if (elements[i] != p_transform.elements[i])
+			return false;
+	}
+
+	return true;
+}
+
+bool Transform2D::operator!=(const Transform2D &p_transform) const {
+
+	for (int i = 0; i < 3; i++) {
+		if (elements[i] != p_transform.elements[i])
+			return true;
+	}
+
+	return false;
+}
+
+void Transform2D::operator*=(const Transform2D &p_transform) {
+
+	elements[2] = xform(p_transform.elements[2]);
+
+	real_t x0, x1, y0, y1;
+
+	x0 = tdotx(p_transform.elements[0]);
+	x1 = tdoty(p_transform.elements[0]);
+	y0 = tdotx(p_transform.elements[1]);
+	y1 = tdoty(p_transform.elements[1]);
+
+	elements[0][0] = x0;
+	elements[0][1] = x1;
+	elements[1][0] = y0;
+	elements[1][1] = y1;
+}
+
+Transform2D Transform2D::operator*(const Transform2D &p_transform) const {
+
+	Transform2D t = *this;
+	t *= p_transform;
+	return t;
+}
+
+Transform2D Transform2D::scaled(const Size2 &p_scale) const {
+
+	Transform2D copy = *this;
+	copy.scale(p_scale);
+	return copy;
+}
+
+Transform2D Transform2D::basis_scaled(const Size2 &p_scale) const {
+
+	Transform2D copy = *this;
+	copy.scale_basis(p_scale);
+	return copy;
+}
+
+Transform2D Transform2D::untranslated() const {
+
+	Transform2D copy = *this;
+	copy.elements[2] = Vector2();
+	return copy;
+}
+
+Transform2D Transform2D::translated(const Vector2 &p_offset) const {
+
+	Transform2D copy = *this;
+	copy.translate(p_offset);
+	return copy;
+}
+
+Transform2D Transform2D::rotated(real_t p_phi) const {
+
+	Transform2D copy = *this;
+	copy.rotate(p_phi);
+	return copy;
+}
+
+real_t Transform2D::basis_determinant() const {
+
+	return elements[0].x * elements[1].y - elements[0].y * elements[1].x;
+}
+
+Transform2D Transform2D::interpolate_with(const Transform2D &p_transform, real_t p_c) const {
+
+	//extract parameters
+	Vector2 p1 = get_origin();
+	Vector2 p2 = p_transform.get_origin();
+
+	real_t r1 = get_rotation();
+	real_t r2 = p_transform.get_rotation();
+
+	Size2 s1 = get_scale();
+	Size2 s2 = p_transform.get_scale();
+
+	//slerp rotation
+	Vector2 v1(Math::cos(r1), Math::sin(r1));
+	Vector2 v2(Math::cos(r2), Math::sin(r2));
+
+	real_t dot = v1.dot(v2);
+
+	dot = (dot < -1.0) ? -1.0 : ((dot > 1.0) ? 1.0 : dot); //clamp dot to [-1,1]
+
+	Vector2 v;
+
+	if (dot > 0.9995) {
+		v = Vector2::linear_interpolate(v1, v2, p_c).normalized(); //linearly interpolate to avoid numerical precision issues
+	} else {
+		real_t angle = p_c * Math::acos(dot);
+		Vector2 v3 = (v2 - v1 * dot).normalized();
+		v = v1 * Math::cos(angle) + v3 * Math::sin(angle);
+	}
+
+	//construct matrix
+	Transform2D res(Math::atan2(v.y, v.x), Vector2::linear_interpolate(p1, p2, p_c));
+	res.scale_basis(Vector2::linear_interpolate(s1, s2, p_c));
+	return res;
+}
+
+Transform2D::operator String() const {
+
+	return String(String() + elements[0] + ", " + elements[1] + ", " + elements[2]);
+}

+ 201 - 0
core/math/transform_2d.h

@@ -0,0 +1,201 @@
+/*************************************************************************/
+/*  transform_2d.h                                                       */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef TRANSFORM_2D_H
+#define TRANSFORM_2D_H
+
+#include "rect2.h" // also includes vector2, math_funcs, and ustring
+
+struct Transform2D {
+	// Warning #1: basis of Transform2D is stored differently from Basis. In terms of elements array, the basis matrix looks like "on paper":
+	// M = (elements[0][0] elements[1][0])
+	//     (elements[0][1] elements[1][1])
+	// This is such that the columns, which can be interpreted as basis vectors of the coordinate system "painted" on the object, can be accessed as elements[i].
+	// Note that this is the opposite of the indices in mathematical texts, meaning: $M_{12}$ in a math book corresponds to elements[1][0] here.
+	// This requires additional care when working with explicit indices.
+	// See https://en.wikipedia.org/wiki/Row-_and_column-major_order for further reading.
+
+	// Warning #2: 2D be aware that unlike 3D code, 2D code uses a left-handed coordinate system: Y-axis points down,
+	// and angle is measure from +X to +Y in a clockwise-fashion.
+
+	Vector2 elements[3];
+
+	_FORCE_INLINE_ real_t tdotx(const Vector2 &v) const { return elements[0][0] * v.x + elements[1][0] * v.y; }
+	_FORCE_INLINE_ real_t tdoty(const Vector2 &v) const { return elements[0][1] * v.x + elements[1][1] * v.y; }
+
+	const Vector2 &operator[](int p_idx) const { return elements[p_idx]; }
+	Vector2 &operator[](int p_idx) { return elements[p_idx]; }
+
+	_FORCE_INLINE_ Vector2 get_axis(int p_axis) const {
+		ERR_FAIL_INDEX_V(p_axis, 3, Vector2());
+		return elements[p_axis];
+	}
+	_FORCE_INLINE_ void set_axis(int p_axis, const Vector2 &p_vec) {
+		ERR_FAIL_INDEX(p_axis, 3);
+		elements[p_axis] = p_vec;
+	}
+
+	void invert();
+	Transform2D inverse() const;
+
+	void affine_invert();
+	Transform2D affine_inverse() const;
+
+	void set_rotation(real_t p_rot);
+	real_t get_rotation() const;
+	_FORCE_INLINE_ void set_rotation_and_scale(real_t p_rot, const Size2 &p_scale);
+	void rotate(real_t p_phi);
+
+	void scale(const Size2 &p_scale);
+	void scale_basis(const Size2 &p_scale);
+	void translate(real_t p_tx, real_t p_ty);
+	void translate(const Vector2 &p_translation);
+
+	real_t basis_determinant() const;
+
+	Size2 get_scale() const;
+
+	_FORCE_INLINE_ const Vector2 &get_origin() const { return elements[2]; }
+	_FORCE_INLINE_ void set_origin(const Vector2 &p_origin) { elements[2] = p_origin; }
+
+	Transform2D scaled(const Size2 &p_scale) const;
+	Transform2D basis_scaled(const Size2 &p_scale) const;
+	Transform2D translated(const Vector2 &p_offset) const;
+	Transform2D rotated(real_t p_phi) const;
+
+	Transform2D untranslated() const;
+
+	void orthonormalize();
+	Transform2D orthonormalized() const;
+
+	bool operator==(const Transform2D &p_transform) const;
+	bool operator!=(const Transform2D &p_transform) const;
+
+	void operator*=(const Transform2D &p_transform);
+	Transform2D operator*(const Transform2D &p_transform) const;
+
+	Transform2D interpolate_with(const Transform2D &p_transform, real_t p_c) const;
+
+	_FORCE_INLINE_ Vector2 basis_xform(const Vector2 &p_vec) const;
+	_FORCE_INLINE_ Vector2 basis_xform_inv(const Vector2 &p_vec) const;
+	_FORCE_INLINE_ Vector2 xform(const Vector2 &p_vec) const;
+	_FORCE_INLINE_ Vector2 xform_inv(const Vector2 &p_vec) const;
+	_FORCE_INLINE_ Rect2 xform(const Rect2 &p_rect) const;
+	_FORCE_INLINE_ Rect2 xform_inv(const Rect2 &p_rect) const;
+
+	operator String() const;
+
+	Transform2D(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy) {
+
+		elements[0][0] = xx;
+		elements[0][1] = xy;
+		elements[1][0] = yx;
+		elements[1][1] = yy;
+		elements[2][0] = ox;
+		elements[2][1] = oy;
+	}
+
+	Transform2D(real_t p_rot, const Vector2 &p_pos);
+	Transform2D() {
+		elements[0][0] = 1.0;
+		elements[1][1] = 1.0;
+	}
+};
+
+Vector2 Transform2D::basis_xform(const Vector2 &p_vec) const {
+
+	return Vector2(
+			tdotx(p_vec),
+			tdoty(p_vec));
+}
+
+Vector2 Transform2D::basis_xform_inv(const Vector2 &p_vec) const {
+
+	return Vector2(
+			elements[0].dot(p_vec),
+			elements[1].dot(p_vec));
+}
+
+Vector2 Transform2D::xform(const Vector2 &p_vec) const {
+
+	return Vector2(
+				   tdotx(p_vec),
+				   tdoty(p_vec)) +
+		   elements[2];
+}
+Vector2 Transform2D::xform_inv(const Vector2 &p_vec) const {
+
+	Vector2 v = p_vec - elements[2];
+
+	return Vector2(
+			elements[0].dot(v),
+			elements[1].dot(v));
+}
+Rect2 Transform2D::xform(const Rect2 &p_rect) const {
+
+	Vector2 x = elements[0] * p_rect.size.x;
+	Vector2 y = elements[1] * p_rect.size.y;
+	Vector2 pos = xform(p_rect.position);
+
+	Rect2 new_rect;
+	new_rect.position = pos;
+	new_rect.expand_to(pos + x);
+	new_rect.expand_to(pos + y);
+	new_rect.expand_to(pos + x + y);
+	return new_rect;
+}
+
+void Transform2D::set_rotation_and_scale(real_t p_rot, const Size2 &p_scale) {
+
+	elements[0][0] = Math::cos(p_rot) * p_scale.x;
+	elements[1][1] = Math::cos(p_rot) * p_scale.y;
+	elements[1][0] = -Math::sin(p_rot) * p_scale.y;
+	elements[0][1] = Math::sin(p_rot) * p_scale.x;
+}
+
+Rect2 Transform2D::xform_inv(const Rect2 &p_rect) const {
+
+	Vector2 ends[4] = {
+		xform_inv(p_rect.position),
+		xform_inv(Vector2(p_rect.position.x, p_rect.position.y + p_rect.size.y)),
+		xform_inv(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y + p_rect.size.y)),
+		xform_inv(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y))
+	};
+
+	Rect2 new_rect;
+	new_rect.position = ends[0];
+	new_rect.expand_to(ends[1]);
+	new_rect.expand_to(ends[2]);
+	new_rect.expand_to(ends[3]);
+
+	return new_rect;
+}
+
+#endif // TRANSFORM_2D_H