swf2ass-go/types/math/matrix.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)))
}