204 lines
5.6 KiB
Go
204 lines
5.6 KiB
Go
package math
|
|
|
|
import (
|
|
"fmt"
|
|
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
|
|
"gonum.org/v1/gonum/mat"
|
|
"math"
|
|
)
|
|
|
|
// MatrixTransform The transformation matrix used by Flash display objects.
|
|
// The matrix is a 2x3 affine transformation matrix. A Vector2(x, y) is transformed by the matrix in the following way:
|
|
//
|
|
// [a c tx] * [x] = [a*x + c*y + tx]
|
|
// [b d ty] [y] [b*x + d*y + ty]
|
|
// [0 0 1 ] [1] [1 ]
|
|
//
|
|
// Objects in Flash can only move in units of types.Twip, or 1/20 pixels.
|
|
//
|
|
// [SWF19 pp.22-24](https://web.archive.org/web/20220205011833if_/https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=22)
|
|
type MatrixTransform struct {
|
|
matrix *mat.Dense
|
|
}
|
|
|
|
var DefaultScale = Vector2[float64]{
|
|
X: 1,
|
|
Y: 1,
|
|
}
|
|
|
|
var DefaultRotateSkew = Vector2[float64]{
|
|
X: 0,
|
|
Y: 0,
|
|
}
|
|
|
|
var DefaultTranslation = Vector2[float64]{
|
|
X: 0,
|
|
Y: 0,
|
|
}
|
|
|
|
func NewMatrixTransform(scale, rotateSkew, translation Vector2[float64]) MatrixTransform {
|
|
return MatrixTransform{
|
|
matrix: mat.NewDense(3, 3, []float64{
|
|
/* a */ /* c */ /* tx */
|
|
scale.X, rotateSkew.Y, translation.X,
|
|
/* b */ /* d */ /* ty */
|
|
rotateSkew.X, scale.Y, translation.Y,
|
|
0, 0, 1,
|
|
}),
|
|
}
|
|
}
|
|
|
|
func ScaleTransform(scale Vector2[float64]) MatrixTransform {
|
|
return NewMatrixTransform(scale, DefaultRotateSkew, DefaultTranslation)
|
|
}
|
|
|
|
func RotateTransform(angle float64) MatrixTransform {
|
|
//TODO: check sin sign location
|
|
sin, cos := math.Sincos(angle)
|
|
return NewMatrixTransform(NewVector2(cos, cos), NewVector2(-sin, sin), DefaultTranslation)
|
|
}
|
|
|
|
func TranslateTransform[T ~int64 | ~float64](translate Vector2[T]) MatrixTransform {
|
|
return NewMatrixTransform(DefaultScale, DefaultRotateSkew, translate.Float64())
|
|
}
|
|
|
|
func IdentityTransform() MatrixTransform {
|
|
return NewMatrixTransform(DefaultScale, DefaultRotateSkew, DefaultTranslation)
|
|
}
|
|
|
|
func SkewXTransform(angle float64) MatrixTransform {
|
|
return NewMatrixTransform(DefaultScale, NewVector2(math.Tan(angle), 0), DefaultTranslation)
|
|
}
|
|
|
|
func SkewYTransform(angle float64) MatrixTransform {
|
|
return NewMatrixTransform(DefaultScale, NewVector2(0, math.Tan(angle)), DefaultTranslation)
|
|
}
|
|
|
|
func (m MatrixTransform) Combine(o MatrixTransform) MatrixTransform {
|
|
return m.Multiply(o)
|
|
}
|
|
|
|
func (m MatrixTransform) Multiply(o MatrixTransform) MatrixTransform {
|
|
var r mat.Dense
|
|
r.Mul(m.matrix, o.matrix)
|
|
return MatrixTransform{
|
|
matrix: &r,
|
|
}
|
|
}
|
|
|
|
var identityTransform = IdentityTransform()
|
|
|
|
func (m MatrixTransform) IsIdentity() bool {
|
|
return m.EqualsExact(identityTransform)
|
|
}
|
|
|
|
// GetA Gets the ScaleX factor
|
|
func (m MatrixTransform) GetA() float64 {
|
|
return m.matrix.At(0, 0)
|
|
}
|
|
|
|
// GetB Gets the RotateSkewX factor
|
|
func (m MatrixTransform) GetB() float64 {
|
|
return m.matrix.At(1, 0)
|
|
}
|
|
|
|
// GetC Gets the RotateSkewY factor
|
|
func (m MatrixTransform) GetC() float64 {
|
|
return m.matrix.At(0, 1)
|
|
}
|
|
|
|
// GetD Gets the ScaleY factor
|
|
func (m MatrixTransform) GetD() float64 {
|
|
return m.matrix.At(1, 1)
|
|
}
|
|
|
|
func (m MatrixTransform) GetTX() float64 {
|
|
return m.matrix.At(0, 2)
|
|
}
|
|
|
|
func (m MatrixTransform) GetTY() float64 {
|
|
return m.matrix.At(1, 2)
|
|
}
|
|
|
|
func (m MatrixTransform) GetMatrix() mat.Matrix {
|
|
return m.matrix
|
|
}
|
|
|
|
// MinimumStrokeWidth
|
|
// Given a matrix, calculates the scale for stroke widths.
|
|
// TODO: Verify the actual behavior; I think it's more like the average between scaleX and scaleY.
|
|
// Does not yet support vertical/horizontal stroke scaling flags.
|
|
func (m MatrixTransform) MinimumStrokeWidth() float64 {
|
|
sx := math.Sqrt(m.GetA()*m.GetA() + m.GetB()*m.GetB())
|
|
sy := math.Sqrt(m.GetC()*m.GetC() + m.GetD()*m.GetD())
|
|
return max(sx, sy)
|
|
}
|
|
|
|
func (m MatrixTransform) GetMatrixWithoutTranslation() mat.Matrix {
|
|
return m.matrix.Slice(0, 2, 0, 2)
|
|
}
|
|
|
|
func (m MatrixTransform) GetTranslation() Vector2[float64] {
|
|
return m.ApplyToVector(NewVector2[float64](0, 0), true)
|
|
}
|
|
|
|
func (m MatrixTransform) Inverse() *MatrixTransform {
|
|
var r mat.Dense
|
|
err := r.Inverse(m.matrix)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &MatrixTransform{
|
|
matrix: &r,
|
|
}
|
|
}
|
|
|
|
func MatrixTransformApplyToVector[T ~int64 | ~float64](m MatrixTransform, v Vector2[T], applyTranslation bool) Vector2[T] {
|
|
return Vector2ToType[float64, T](m.ApplyToVector(v.Float64(), applyTranslation))
|
|
}
|
|
|
|
func (m MatrixTransform) ApplyToVector(v Vector2[float64], applyTranslation bool) Vector2[float64] {
|
|
var r mat.VecDense
|
|
if applyTranslation {
|
|
/*
|
|
[a c tx] * [x] = [a*x + c*y + tx]
|
|
[b d ty] [y] [b*x + d*y + ty]
|
|
[0 0 1 ] [1] [1 ]
|
|
*/
|
|
r.MulVec(m.matrix, mat.NewVecDense(3, []float64{v.X, v.Y, 1}))
|
|
} else {
|
|
/*
|
|
[a c] * [x] = [a*x + c*y]
|
|
[b d] [y] [b*x + d*y]
|
|
*/
|
|
r.MulVec(m.GetMatrixWithoutTranslation(), mat.NewVecDense(2, []float64{v.X, v.Y}))
|
|
}
|
|
return NewVector2[float64](r.AtVec(0), r.AtVec(1))
|
|
}
|
|
|
|
func (m MatrixTransform) EqualsExact(o MatrixTransform) bool {
|
|
return mat.Equal(m.matrix, o.matrix)
|
|
}
|
|
|
|
const TransformCompareEpsilon = 1e-12
|
|
|
|
func (m MatrixTransform) Equals(o MatrixTransform, epsilon float64) bool {
|
|
return mat.EqualApprox(m.matrix, o.matrix, epsilon)
|
|
}
|
|
|
|
func (m MatrixTransform) EqualsWithoutTranslation(o MatrixTransform, epsilon float64) bool {
|
|
return mat.EqualApprox(m.GetMatrixWithoutTranslation(), o.GetMatrixWithoutTranslation(), epsilon)
|
|
}
|
|
|
|
func (m MatrixTransform) String() string {
|
|
return fmt.Sprintf("%#v", mat.Formatted(m.matrix, mat.FormatPython()))
|
|
}
|
|
|
|
func MatrixTransformFromSWF(m types.MATRIX, scale float64) MatrixTransform {
|
|
return NewMatrixTransform(
|
|
NewVector2(m.ScaleX.Float64(), m.ScaleY.Float64()),
|
|
NewVector2(m.RotateSkew0.Float64(), m.RotateSkew1.Float64()),
|
|
NewVector2(m.TranslateX.Float64(), m.TranslateY.Float64()),
|
|
).Multiply(ScaleTransform(NewVector2(scale, scale)))
|
|
}
|