Skip to content

Commit

Permalink
Merge pull request #3 from dnfield/geom
Browse files Browse the repository at this point in the history
Add geometry classes
  • Loading branch information
dnfield authored Mar 2, 2022
2 parents f3b5c35 + b872ed1 commit 2dd3360
Show file tree
Hide file tree
Showing 11 changed files with 1,174 additions and 1 deletion.

This file was deleted.

122 changes: 122 additions & 0 deletions packages/vector_graphics_compiler/lib/src/geometry/basic_types.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import 'dart:math' as math;
import 'package:meta/meta.dart';

/// An immutable position in two-dimensional space.
///
/// This class is roughly compatible with dart:ui's Offset.
@immutable
class Point {
/// Creates a point object with x,y coordinates.
const Point(this.x, this.y);

static const Point zero = Point(0, 0);

/// The offset along the x-axis of this point.
final double x;

/// The offset along the y-axis of this point.
final double y;

@override
int get hashCode => Object.hash(x, y);

@override
bool operator ==(Object other) {
return other is Point && other.x == x && other.y == y;
}

Point operator /(double divisor) {
return Point(x / divisor, y / divisor);
}

Point operator *(double multiplicand) {
return Point(x * multiplicand, y * multiplicand);
}

@override
String toString() => 'Point($x, $y)';
}

/// An immutable, 2D, axis-aligned, floating-point rectangle whose coordinates
/// are relative to a given origin.
@immutable
class Rect {
/// Creates a rectangle from the specified left, top, right, and bottom
/// positions.
const Rect.fromLTRB(this.left, this.top, this.right, this.bottom);

/// Creates a rectangle from the specified left and top positions with width
/// and height dimensions.
const Rect.fromLTWH(double left, double top, double width, double height)
: this.fromLTRB(left, top, left + width, top + height);

/// Creates a rectangle representing a circle with centerpoint `x,`y` and
/// radius `r`.
const Rect.fromCircle(double x, double y, double r)
: this.fromLTRB(x - r, y - r, x + r, y + r);

/// A rectangle covering the entire coordinate space, equal to dart:ui's
/// definition.
static const Rect largest = Rect.fromLTRB(-1e9, -1e9, 1e9, 1e9);

/// A rectangle with the top, left, right, and bottom edges all at zero.
static const Rect zero = Rect.fromLTRB(0, 0, 0, 0);

/// The x-axis offset of left edge.
final double left;

/// The y-axis offset of the top edge.
final double top;

/// The x-axis offset of the right edge.
final double right;

/// The y-axis offset of the bottom edge.
final double bottom;

/// The width of the rectangle.
double get width => right - left;

/// The height of the rectangle.
double get height => bottom - top;

/// The top left corner of the rect.
Point get topLeft => Point(left, top);

/// The top right corner of the rect.
Point get topRight => Point(right, top);

/// The bottom left corner of the rect.
Point get bottomLeft => Point(bottom, left);

/// The bottom right corner of the rect.
Point get bottomRight => Point(bottom, right);

/// The size of the rectangle, expressed as a [Point].
Point get size => Point(width, height);

/// Creates the smallest rectangle that covers the edges of this and `other`.
Rect expanded(Rect other) {
return Rect.fromLTRB(
math.min(left, other.left),
math.min(top, other.top),
math.max(right, other.right),
math.max(bottom, other.bottom),
);
}

@override
String toString() => 'Rect.fromLTRB($left, $top, $right, $bottom)';

@override
int get hashCode => Object.hash(left, top, right, bottom);

@override
bool operator ==(Object other) {
return other is Rect &&
other.left == left &&
other.top == top &&
other.right == right &&
other.bottom == bottom;
}
}
193 changes: 193 additions & 0 deletions packages/vector_graphics_compiler/lib/src/geometry/matrix.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import 'dart:math' as math;
import 'dart:typed_data';

import 'package:meta/meta.dart';

import 'basic_types.dart';

/// An immutable affine matrix, a 3x3 column-major-order matrix in which the
/// last row is always set to the identity values, i.e. `0 0 1`.
@immutable
class AffineMatrix {
/// Creates an immutable affine matrix. To work with the identity matrix, use
/// the [identity] property.
const AffineMatrix(
this.a,
this.b,
this.c,
this.d,
this.e,
this.f, [
this._m4_10 = 1.0,
]);

/// The identity affine matrix.
static const AffineMatrix identity = AffineMatrix(1, 0, 0, 1, 0, 0);

/// The 0,0 position of the matrix.
final double a;

/// The 1,0 position of the matrix.
final double b;

/// The 0,1 position of the matrix.
final double c;

/// The 1,1 position of the matrix.
final double d;

/// The 2,0 position of the matrix.
final double e;

/// The 2,1 position of the matrix.
final double f;

/// Translations can affect this value, so we have to track it.
final double _m4_10;

/// Creates a new affine matrix rotated by `radians`.
AffineMatrix rotated(double radians) {
if (radians == 0) {
return this;
}
final double cosAngle = math.cos(radians);
final double sinAngle = math.sin(radians);
return AffineMatrix(
(a * cosAngle) + (c * sinAngle),
(b * cosAngle) + (d * sinAngle),
(a * -sinAngle) + (c * cosAngle),
(b * -sinAngle) + (d * cosAngle),
e,
f,
_m4_10,
);
}

/// Creates a new affine matrix rotated by `x` and `y`.
///
/// If `y` is not specified, it is defaulted to the same value as `x`.
AffineMatrix scaled(double x, [double? y]) {
y ??= x;
if (x == 1 && y == 1) {
return this;
}
return AffineMatrix(
a * x,
b * x,
c * y,
d * y,
e,
f,
_m4_10 * x,
);
}

/// Creates a new affine matrix, translated along the x and y axis.
AffineMatrix translated(double x, double y) {
return AffineMatrix(
a,
b,
c,
d,
(a * x) + (c * y) + e,
(b * x) + (d * y) + f,
_m4_10,
);
}

/// Creates a new affine matrix of this concatenated with `other`.
AffineMatrix multiplied(AffineMatrix other) {
return AffineMatrix(
(a * other.a) + (c * other.b),
(b * other.a) + (d * other.b),
(a * other.c) + (c * other.d),
(b * other.c) + (d * other.d),
(a * other.e) + (c * other.f) + e,
(b * other.e) + (d * other.f) + f,
_m4_10,
);
}

/// Maps `point` using the values of this matrix.
Point transformPoint(Point point) {
return Point(
(a * point.x) + (c * point.y) + e,
(b * point.x) + (d * point.y) + f,
);
}

/// Maps `rect` using the values of this matrix.
Rect transformRect(Rect rect) {
final double x = rect.left;
final double y = rect.top;
final double w = rect.width;
final double h = rect.height;

final double wx = a * w;
final double hx = c * h;
final double rx = a * x + c * y;

final double wy = b * w;
final double hy = d * h;
final double ry = b * x + d * y;

double left = rx;
double right = rx;
if (wx < 0) {
left += wx;
} else {
right += wx;
}
if (hx < 0) {
left += hx;
} else {
right += hx;
}

double top = ry;
double bottom = ry;
if (wy < 0) {
top += wy;
} else {
bottom += wy;
}
if (hy < 0) {
top += hy;
} else {
bottom += hy;
}

return Rect.fromLTRB(left, top, right, bottom);
}

/// Creates a typed data representatino of this matrix suitable for use with
/// `package:vector_math_64` (and, by extension, Flutter/dart:ui).
Float64List toMatrix4() {
return Float64List.fromList(<double>[
a, b, 0, 0, //
c, d, 0, 0, //
0, 0, _m4_10, 0, //
e, f, 0, 1.0, //
]);
}

@override
int get hashCode => Object.hash(a, b, c, d, e, f, _m4_10);

@override
bool operator ==(Object other) {
return other is AffineMatrix &&
other.a == a &&
other.b == b &&
other.d == d &&
other.e == e &&
other._m4_10 == _m4_10;
}

@override
String toString() => '''
[ $a, $c, $e ]
[ $b, $d, $f ]
[ 0.0, 0.0, 1.0 ]
''';
}
Loading

0 comments on commit 2dd3360

Please sign in to comment.