affine-transform.dhover×163all
#!/usr/bin/env dub
/+ dub.sdl:
    name "manim_affine_transform"
    targetPath "build"
+/
/**
 * 2D affine transforms as 3×3 homogeneous matrices: compose translate ·
 * rotate · scale into ONE matrix, apply it to a Bézier control polygon, and
 * verify that the single composed matrix equals applying the three transforms
 * sequentially.
 *
 * The *object & scene model* axis (coordinate space) of the analysis spine.
 * Every mobject carries a model transform; `Transform`/`ApplyMatrix` and the
 * camera all reduce to affine maps on control points. The load-bearing fact a
 * reimplementation must respect is composition order: `M = T·R·S` applied once
 * is identical to scaling, then rotating, then translating each point in turn
 * — matrix multiply *is* function composition, right-to-left. This probe
 * proves it numerically (max deviation 0 within fp epsilon), which is why the
 * proposal builds a single `Affine2` compose primitive rather than mutating
 * points per operation.
 *
 * It also exercises exactly the `Matrix`/`Affine2` primitive M1 of the
 * proposal adds to `libs/math` on top of the existing `Vector` type.
 *
 * Companion to docs/research/manim/concepts.md § "Affine transform" and
 *   docs/research/manim/manim-community/scene-graph.md § "Coordinate space".
 * Run with: dub run --single affine-transform.d
 *
 * Portability: pure computation, no external dependencies — runs everywhere.
 */
module 
(module) manim_affine_transform

2D affine transforms as 3×3 homogeneous matrices: compose translate · rotate · scale into ONE matrix, apply it to a Bézier control polygon, and verify that the single composed matrix equals applying the three transforms sequentially.

The object & scene model axis (coordinate space) of the analysis spine. Every mobject carries a model transform; Transform/ApplyMatrix and the camera all reduce to affine maps on control points. The load-bearing fact a reimplementation must respect is composition order: M = T·R·S applied once is identical to scaling, then rotating, then translating each point in turn — matrix multiply is function composition, right-to-left. This probe proves it numerically (max deviation 0 within fp epsilon), which is why the proposal builds a single Affine2 compose primitive rather than mutating points per operation.

It also exercises exactly the Matrix/Affine2 primitive M1 of the proposal adds to libs/math on top of the existing Vector type.

Companion to docs/research/manim/concepts.md § "Affine transform" and docs/research/manim/manim-community/scene-graph.md § "Coordinate space". Run with: dub run --single affine-transform.d

Portability

pure computation, no external dependencies — runs everywhere.

manim_affine_transform
;
import
(package) std
std
.
(module) std.math

Contains the elementary mathematical functions (powers, roots, and trigonometric functions), and low-level floating-point operations. Mathematical special functions are available in std.mathspecial.

Category Members
Constants E PI PI_2 PI4 M1_PI M2_PI M2_SQRTPI LN10 LN2 LOG2 LOG2E LOG2T LOG10E SQRT2 SQRT1_2
Algebraic abs fabs sqrt cbrt hypot poly nextPow2 truncPow2
Trigonometry sin cos tan asin acos atan atan2 sinh cosh tanh asinh acosh atanh
Rounding ceil floor round lround trunc rint lrint nearbyint rndtol quantize
Exponentiation & Logarithms pow powmod exp exp2 expm1 ldexp frexp log log2 log10 logb ilogb log1p scalbn
Remainder fmod modf remainder remquo
Floating-point operations approxEqual feqrel fdim fmax fmin fma isClose nextDown nextUp nextafter NaN getNaNPayload cmp
Introspection isFinite isIdentical isInfinity isNaN isNormal isSubnormal signbit sgn copysign isPowerOf2
Hardware Control IeeeFlags ieeeFlags resetIeeeFlags FloatingPointControl

The functionality closely follows the IEEE754-2008 standard for floating-point arithmetic, including the use of camelCase names rather than C99-style lower case names. All of these functions behave correctly when presented with an infinity or NaN.

The following IEEE 'real' formats are currently supported:

  • 64 bit Big-endian 'double' (eg PowerPC)

  • 128 bit Big-endian 'quadruple' (eg SPARC)

  • 64 bit Little-endian 'double' (eg x86-SSE2)

  • 80 bit Little-endian, with implied bit 'real80' (eg x87, Itanium)

  • 128 bit Little-endian 'quadruple' (not implemented on any known processor!)

  • Non-IEEE 128 bit Big-endian 'doubledouble' (eg PowerPC) has partial support

Unlike C, there is no global 'errno' variable. Consequently, almost all of these functions are pure nothrow.

Source

std/math/package.d

@copyrightCopyright The D Language Foundation 2000 - 2011. D implementations of tan, atan, atan2, exp, expm1, exp2, log, log10, log1p, log2, floor, ceil and lrint functions are based on the CEPHES math library, which is Copyright (C) 2001 Stephen L. Moshier <steve@moshier.net> and are incorporated herein by permission of the author. The author reserves the right to distribute this material elsewhere under different copying permissions. These modifications are distributed here under the following terms:@licenseBoost License 1.0.@authorsWalter Bright, Don Clugston, Conversion of CEPHES math library to D by Iain Buclaw and David Nadlinger
math
:
(alias) manim_affine_transform.cos = real std.math.trigonometry.cos(real x) pure nothrow @nogc @safe

Returns cosine of x. x is in radians.

x cos(x) invalid?
yes
yes
@bugsResults are undefined if |x| >= 2,64.
cos
,
(alias constant) manim_affine_transform.PI = real std.math.constants.PI = 3.14159L

&pi; = 3.141592...

PI
,
(alias) manim_affine_transform.sin = real std.math.trigonometry.sin(real x) pure nothrow @nogc @safe

Returns sine of x. x is in radians.

x , sin(x) , invalid? , , yes 0.0, 0.0, no , , yes

@paramx angle in radians (not degrees)@returnssine of x@seecos, tan, asin@bugsResults are undefined if |x| >= 2,64.
sin
,
(alias) manim_affine_transform.sqrt = float std.math.algebraic.sqrt(float x) pure nothrow @nogc @safe

Compute square root of x.

x sqrt(x) invalid?
-0.0 -0.0 no
<0.0 yes
+ + no
sqrt
;
import
(package) std
std
.
(module) std.stdio
Category Symbols
File handles _popen File isFileHandle openNetwork stderr stdin stdout
Reading chunks lines readf readfln readln
Writing toFile write writef writefln writeln
Misc KeepTerminator LockType StdioException

Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio is publically imported when importing std.stdio.

There are three layers of I/O:

  1. The lowest layer is the operating system layer. The two main schemes are Windows and Posix.

  2. C's stdio.h which unifies the two operating system schemes.

  3. std.stdio, this module, unifies the various stdio.h implementations into a high level package for D programs.

Source

std/stdio.d

@copyrightCopyright The D Language Foundation 2007-.@licenseBoost License 1.0.@authorsWalter Bright, Andrei Alexandrescu, Alex Rønne Petersen
stdio
:
(alias template) manim_affine_transform.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))

Equivalent to writef(fmt, args, '\n').

writefln
,
(alias template) manim_affine_transform.writeln = std.stdio.writeln(T...)(T args)

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
;
/// Row-major 3×3 affine matrix. struct
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
{ double[3][3]
(field) double[3][3] manim_affine_transform.Mat3.m
m
;
static
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
manim_affine_transform.Mat3 manim_affine_transform.Mat3.identity() pure nothrow @nogc @safe
identity
() @safe pure nothrow @nogc
=>
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
([[1.0, 0, 0], [0.0, 1, 0], [0.0, 0, 1]]);
static
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
manim_affine_transform.Mat3 manim_affine_transform.Mat3.translate(double tx, double ty) pure nothrow @nogc @safe
translate
(double
(parameter) double tx
tx
, double
(parameter) double ty
ty
) @safe pure nothrow @nogc
=>
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
([[1.0, 0,
(parameter) double tx
tx
], [0.0, 1,
(parameter) double ty
ty
], [0.0, 0, 1]]);
static
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
manim_affine_transform.Mat3 manim_affine_transform.Mat3.scale(double sx, double sy) pure nothrow @nogc @safe
scale
(double
(parameter) double sx
sx
, double
(parameter) double sy
sy
) @safe pure nothrow @nogc
=>
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
([[
(parameter) double sx
sx
, 0, 0], [0.0,
(parameter) double sy
sy
, 0], [0.0, 0, 1]]);
static
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
manim_affine_transform.Mat3 manim_affine_transform.Mat3.rotate(double rad) pure nothrow @nogc @safe
rotate
(double
(parameter) double rad
rad
) @safe pure nothrow @nogc
{ const
(local variable) const(double) c
c
=
double std.math.trigonometry.cos(double x) pure nothrow @nogc @safe

Returns cosine of x. x is in radians.

x cos(x) invalid?
yes
yes
@bugsResults are undefined if |x| >= 2,64.
cos
(
(parameter) double rad
rad
),
(local variable) const(double) s
s
=
double std.math.trigonometry.sin(double x) pure nothrow @nogc @safe

Returns sine of x. x is in radians.

x , sin(x) , invalid? , , yes 0.0, 0.0, no , , yes

@paramx angle in radians (not degrees)@returnssine of x@seecos, tan, asin@bugsResults are undefined if |x| >= 2,64.
sin
(
(parameter) double rad
rad
);
return
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
([[
(local variable) const(double) c
c
, -
(local variable) const(double) s
s
, 0], [
(local variable) const(double) s
s
,
(local variable) const(double) c
c
, 0], [0.0, 0, 1]]);
} /// Matrix product `this · rhs`.
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
manim_affine_transform.Mat3 manim_affine_transform.Mat3.mul(in manim_affine_transform.Mat3 rhs) const pure nothrow @nogc @safe

Matrix product this · rhs``.

mul
(in
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
(parameter) const(manim_affine_transform.Mat3) rhs
rhs
) const @safe pure nothrow @nogc
{
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
(local variable) manim_affine_transform.Mat3 r
r
;
foreach (
(local variable) int i
i
; 0 .. 3)
foreach (
(local variable) int j
j
; 0 .. 3)
{ double
(local variable) double acc
acc
= 0;
foreach (
(local variable) int k
k
; 0 .. 3)
(local variable) double acc
acc
+=
(field) double[3][3] manim_affine_transform.Mat3.m
m
[
(local variable) int i
i
][
(local variable) int k
k
] *
(parameter) const(manim_affine_transform.Mat3) rhs
rhs
.
(field) double[3][3] manim_affine_transform.Mat3.m
m
[
(local variable) int k
k
][
(local variable) int j
j
];
(local variable) manim_affine_transform.Mat3 r
r
.
(field) double[3][3] manim_affine_transform.Mat3.m
m
[
(local variable) int i
i
][
(local variable) int j
j
] =
(local variable) double acc
acc
;
} return
(local variable) manim_affine_transform.Mat3 r
r
;
} /// Apply to a 2D point (homogeneous w = 1). double[2]
double[2] manim_affine_transform.Mat3.apply(in double[2] p) const pure nothrow @nogc @safe

Apply to a 2D point (homogeneous w = 1).

apply
(in double[2]
(parameter) const(double[2]) p
p
) const @safe pure nothrow @nogc
=> [
(field) double[3][3] manim_affine_transform.Mat3.m
m
[0][0] *
(parameter) const(double[2]) p
p
[0] +
(field) double[3][3] manim_affine_transform.Mat3.m
m
[0][1] *
(parameter) const(double[2]) p
p
[1] +
(field) double[3][3] manim_affine_transform.Mat3.m
m
[0][2],
(field) double[3][3] manim_affine_transform.Mat3.m
m
[1][0] *
(parameter) const(double[2]) p
p
[0] +
(field) double[3][3] manim_affine_transform.Mat3.m
m
[1][1] *
(parameter) const(double[2]) p
p
[1] +
(field) double[3][3] manim_affine_transform.Mat3.m
m
[1][2]];
} double
double manim_affine_transform.dist(in double[2] a, in double[2] b) pure nothrow @nogc @safe
dist
(in double[2]
(parameter) const(double[2]) a
a
, in double[2]
(parameter) const(double[2]) b
b
) @safe pure nothrow @nogc
=>
double std.math.algebraic.sqrt(double x) pure nothrow @nogc @safe

Compute square root of x.

x sqrt(x) invalid?
-0.0 -0.0 no
<0.0 yes
+ + no
sqrt
((
(parameter) const(double[2]) a
a
[0] -
(parameter) const(double[2]) b
b
[0]) ^^ 2 + (
(parameter) const(double[2]) a
a
[1] -
(parameter) const(double[2]) b
b
[1]) ^^ 2);
int
int D main() @safe
main
() @safe
{ const
(local variable) const(manim_affine_transform.Mat3) T
T
=
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
.
manim_affine_transform.Mat3 manim_affine_transform.Mat3.translate(double tx, double ty) pure nothrow @nogc @safe
translate
(2.0, -1.0);
const
(local variable) const(manim_affine_transform.Mat3) R
R
=
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
.
manim_affine_transform.Mat3 manim_affine_transform.Mat3.rotate(double rad) pure nothrow @nogc @safe
rotate
(
(constant) real std.math.constants.PI = 3.14159L

&pi; = 3.141592...

PI
/ 6); // 30°
const
(local variable) const(manim_affine_transform.Mat3) S
S
=
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
.
manim_affine_transform.Mat3 manim_affine_transform.Mat3.scale(double sx, double sy) pure nothrow @nogc @safe
scale
(1.5, 0.5);
const
(local variable) const(manim_affine_transform.Mat3) M
M
=
(local variable) const(manim_affine_transform.Mat3) T
T
.
manim_affine_transform.Mat3 manim_affine_transform.Mat3.mul(in manim_affine_transform.Mat3 rhs) const pure nothrow @nogc @safe

Matrix product this · rhs``.

mul
(
(local variable) const(manim_affine_transform.Mat3) R
R
).
manim_affine_transform.Mat3 manim_affine_transform.Mat3.mul(in manim_affine_transform.Mat3 rhs) const pure nothrow @nogc @safe

Matrix product this · rhs``.

mul
(
(local variable) const(manim_affine_transform.Mat3) S
S
); // T·R·S — scale first, then rotate, then translate
// A cubic control polygon. const double[2][4]
(local variable) const(double[2][4]) poly
poly
= [[0.0, 0], [1.0, 2], [2.0, -1], [3.0, 0]];
void std.stdio.writeln!string(string __param_0) @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
("== M = T·R·S applied once vs S→R→T applied in sequence ==");
void std.stdio.writefln!char(in char[] fmt) @safe

Equivalent to writef(fmt, args, '\n').

writefln
(" control point via M via sequence |Δ|");
double
(local variable) double maxDev
maxDev
= 0;
foreach (
(parameter) const(double[2]) p
p
;
(local variable) const(double[2][4]) poly
poly
)
{ const
(local variable) const(double[2]) viaM
viaM
=
(local variable) const(manim_affine_transform.Mat3) M
M
.
double[2] manim_affine_transform.Mat3.apply(in double[2] p) const pure nothrow @nogc @safe

Apply to a 2D point (homogeneous w = 1).

apply
(
(local variable) const(double[2]) p
p
);
const
(local variable) const(double[2]) viaSeq
viaSeq
=
(local variable) const(manim_affine_transform.Mat3) T
T
.
double[2] manim_affine_transform.Mat3.apply(in double[2] p) const pure nothrow @nogc @safe

Apply to a 2D point (homogeneous w = 1).

apply
(
(local variable) const(manim_affine_transform.Mat3) R
R
.
double[2] manim_affine_transform.Mat3.apply(in double[2] p) const pure nothrow @nogc @safe

Apply to a 2D point (homogeneous w = 1).

apply
(
(local variable) const(manim_affine_transform.Mat3) S
S
.
double[2] manim_affine_transform.Mat3.apply(in double[2] p) const pure nothrow @nogc @safe

Apply to a 2D point (homogeneous w = 1).

apply
(
(local variable) const(double[2]) p
p
)));
const
(local variable) const(double) d
d
=
double manim_affine_transform.dist(in double[2] a, in double[2] b) pure nothrow @nogc @safe
dist
(
(local variable) const(double[2]) viaM
viaM
,
(local variable) const(double[2]) viaSeq
viaSeq
);
if (
(local variable) const(double) d
d
>
(local variable) double maxDev
maxDev
)
(local variable) double maxDev
maxDev
=
(local variable) const(double) d
d
;
void std.stdio.writefln!(char, const(double), const(double), const(double), const(double), const(double), const(double), const(double))(in char[] fmt, const(double) __param_1, const(double) __param_2, const(double) __param_3, const(double) __param_4, const(double) __param_5, const(double) __param_6, const(double) __param_7) @safe

Equivalent to writef(fmt, args, '\n').

writefln
(" (%4.1f,%4.1f) (%7.4f,%7.4f) (%7.4f,%7.4f) %.2e",
(local variable) const(double[2]) p
p
[0],
(local variable) const(double[2]) p
p
[1],
(local variable) const(double[2]) viaM
viaM
[0],
(local variable) const(double[2]) viaM
viaM
[1],
(local variable) const(double[2]) viaSeq
viaSeq
[0],
(local variable) const(double[2]) viaSeq
viaSeq
[1],
(local variable) const(double) d
d
);
}
void std.stdio.writefln!(char, double)(in char[] fmt, double __param_1) @safe

Equivalent to writef(fmt, args, '\n').

writefln
(" max deviation: %.2e (matrix product = right-to-left composition)",
(local variable) double maxDev
maxDev
);
// Order matters: T·S != S·T. const
(local variable) const(double[2]) ts
ts
=
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
.
manim_affine_transform.Mat3 manim_affine_transform.Mat3.translate(double tx, double ty) pure nothrow @nogc @safe
translate
(2, 0).
manim_affine_transform.Mat3 manim_affine_transform.Mat3.mul(in manim_affine_transform.Mat3 rhs) const pure nothrow @nogc @safe

Matrix product this · rhs``.

mul
(
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
.
manim_affine_transform.Mat3 manim_affine_transform.Mat3.scale(double sx, double sy) pure nothrow @nogc @safe
scale
(3, 3)).
double[2] manim_affine_transform.Mat3.apply(in double[2] p) const pure nothrow @nogc @safe

Apply to a 2D point (homogeneous w = 1).

apply
([1.0, 0]);
const
(local variable) const(double[2]) st
st
=
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
.
manim_affine_transform.Mat3 manim_affine_transform.Mat3.scale(double sx, double sy) pure nothrow @nogc @safe
scale
(3, 3).
manim_affine_transform.Mat3 manim_affine_transform.Mat3.mul(in manim_affine_transform.Mat3 rhs) const pure nothrow @nogc @safe

Matrix product this · rhs``.

mul
(
(struct) manim_affine_transform.Mat3

Row-major 3×3 affine matrix.

Mat3
.
manim_affine_transform.Mat3 manim_affine_transform.Mat3.translate(double tx, double ty) pure nothrow @nogc @safe
translate
(2, 0)).
double[2] manim_affine_transform.Mat3.apply(in double[2] p) const pure nothrow @nogc @safe

Apply to a 2D point (homogeneous w = 1).

apply
([1.0, 0]);
void std.stdio.writeln!string(string __param_0) @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
("\n== non-commutativity: translate·scale vs scale·translate on (1,0) ==");
void std.stdio.writefln!(char, const(double), const(double))(in char[] fmt, const(double) __param_1, const(double) __param_2) @safe

Equivalent to writef(fmt, args, '\n').

writefln
(" T·S (scale then translate): (%.1f, %.1f)",
(local variable) const(double[2]) ts
ts
[0],
(local variable) const(double[2]) ts
ts
[1]);
void std.stdio.writefln!(char, const(double), const(double))(in char[] fmt, const(double) __param_1, const(double) __param_2) @safe

Equivalent to writef(fmt, args, '\n').

writefln
(" S·T (translate then scale): (%.1f, %.1f)",
(local variable) const(double[2]) st
st
[0],
(local variable) const(double[2]) st
st
[1]);
void std.stdio.writeln!string(string __param_0) @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
(" (they differ — order is part of the transform, not incidental)");
return 0; }