# Math help needed: 2D transformation matrix

This is a screenshot next to a picture of a color measurement device app I am developing.
There are thee random reference points on the scan template, visible in the screenshot as the blue one to the upper left (selected Ref. point A) and the two black squares.

User can take the instrument’s arm and collect the “real” coordinate values as the template can be tilted or translated.
And this is where I need help. I would need to calculate a transformation matrix based on the vectors AB/BC or AB/AC and their measured values, and then apply this to the color patches to recalculate their “live” positions.
But the tutorials I found don’t nail that point. Any hints how I can achieve this in Xojo?

Thanks a lot!

OK, I found a JavaScript application I could use. But its usage troubles me. It is not compiled into a node.js, but a collection of source files. Would be nice to have it run in JavaScriptEngineMBS.
Any hints how to set it up? @Christian_Schmitz ?

It shouldn’t be hard to convert the relevant parts into Xojo. You can use an array to hold the matrix values.

I thought so too. Structures would also do.
And some of the code looks pretty straightforward. For other pieces I had to realise I don’t understand Javascript. I mean, what is something like this supposed to do?

``````export function fromTriangles (t1, t2) {
// point p = first point of the triangle
const px1 = t1[0].x != null ? t1[0].x : t1[0][0]
``````

This one?

Its doing this:

``````If t1[0].x is not null
px1 = t1[0].x
else
px1 = t1[0][0]
``````

As long as you keep things simple and don’t allow values to be nil (null) then you can simplify the code.

I would also not get bogged down with converting stuff you don’t need.

!= is <>
? x:y is IIf

i made a few matrix methods in xojo but later i continue in c#
because the matrix library is more complex and ready to use there.

if you want to rotate around a point x,y you move all to 0,0
means translate vectors via subtract, turn it via rotation matrix and push them the same distance back.
you need a angle in radians, and a distance in compare to scale something.

Xojo
Vec3 Class

``````Public Shared Function Point(x As Double, y As Double, z As Double) As Vec3
Return New Vec3(x,y,z,1.0)

End Function

Public Shared Function Vector(x As Double, y As Double, z As Double) As Vec3
Return New Vec3(x,y,z,0.0)

End Function

Public Shared Function Add(a1 as Vec3, a2 as Vec3) As Vec3

Return New Vec3(a1.x + a2.x, a1.y + a2.y, a1.z + a2.z, a1.w + a2.w)

End Function

Public Shared Function Subtract(a1 as Vec3, a2 as Vec3) As Vec3

Return New Vec3(a1.x - a2.x, a1.y - a2.y, a1.z - a2.z, a1.w - a2.w)

End Function

Public Shared Function Negating(a as Vec3) As Vec3

Return New Vec3(-a.x, -a.y, -a.z, -a.w)

End Function

Public Shared Function MultiplyingByScalar(a As Vec3, scalar As Double) As Vec3

Return New Vec3(a.x * scalar, a.y * scalar, a.z * scalar, a.w * scalar)

End Function

Public Shared Function DividingByScalar(a As Vec3, scalar As Double) As Vec3

Return New Vec3(a.x / scalar, a.y / scalar, a.z / scalar, a.w / scalar)

End Function

Public Shared Function Magnitute(a as Vec3) As Double

Return Sqrt((a.x * a.x) + (a.y * a.y) + (a.z * a.z) + (a.w * a.w))

End Function

Public Shared Function Normalize(a As Vec3) As Vec3
Var magnitute As Double = Magnitute(a)

If magnitute = 0.0 Then
Return New Vec3(0.0, 0.0, 0.0, 0.0)
Else
Return New Vec3(a.x / magnitute, a.y / magnitute, a.z / magnitute, a.w / magnitute)
End If

End Function

Public Shared Function DotProduct(a as Vec3, b as Vec3) As Double

'auch als Scalar Produkt genannt

Return (a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w)

End Function

Public Shared Function CrossProduct(a as Vec3, b as Vec3) As Vec3

Return Vec3.Vector(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x)

End Function

Public Shared Function Length(a as Vec3) As Double

Return Magnitute(a)

End Function

Public Shared Function Reflect(v As Vec3, n As Vec3, bounce As Double) As Vec3

'https://www.3dkingdoms.com/weekly/weekly.php?a=2

' Vnew = bounce * ( -2*(V dot N)*N + V )

' where bounce=0.0 means no bounce, and bounce=1.0 means no loss of speed.

Var dotproduct As Double = DotProduct(v, n)

Return Vec3.Add(Vec3.MultiplyingByScalar(Vec3.MultiplyingByScalar(n, -2.0 * dotproduct), bounce),v)

End Function

Public Sub Constructor()
Self.x = 0.0
Self.y = 0.0
Self.z = 0.0
Self.w = 0.0

End Sub

Public Sub Constructor(x As Double, y As Double, z As Double, w As Double)
Self.x = x
Self.y = y
Self.z = z
Self.w = w

End Sub

Public Sub Debug()
System.DebugLog "X = " + x.ToString + " Y = " + y.ToString + " Z = " + z.ToString
End Sub

Public Property x As Double = 0

Public Property y As Double = 0

Public Property w As Double = 0

Public Property z As Double = 0

``````

Xojo
Ray Class

``````Private Sub Constructor()

End Sub

Public Sub Constructor(origin As Vec3, direction As Vec3)

Self.Origin = Vec3.Point(origin.x, origin.y, origin.z) 'a point
Self.Direction = Vec3.Vector(direction.x, direction.y, direction.z) 'a vector

End Sub

Public Property Origin As Vec3

Public Property Direction As Vec3

Public Shared Function Position(ray As Ray, t As Double) As Vec3

' t = time

Var totalDistance As Vec3 = Vec3.MultiplyingByScalar(ray.Direction, t)

End Function

Public Shared Function Transform(ray As Ray, m(, ) As Double) As Ray

Var ray2 As New Ray(Vec3.Point(0,0,0),Vec3.Vector(0,0,0))

ray2.Origin = MatrixMultiplyTuple(m, ray.Origin)
ray2.Direction = MatrixMultiplyTuple(m, ray.Direction)

Return ray2

End Function

``````

Xojo
MatrixModule

``````Public Function Matrix4x4() As double(,)
'Row, Column
'0-3, 0-3

Var m(3,3) As Double

Return m

'eine 3x5 Matrix hat 3 Reihen und 5 Spalten
End Function

Public Function MatrixCustomized(rows As Integer, columns As Integer) As double(,)
'Row, Column

Var m(-1, -1) As Double
m.ResizeTo(rows-1, columns-1)

Return m

'eine 3x5 Matrix hat 3 Reihen und 5 Spalten
End Function

Public Sub MatrixDebug(m(, ) As Double)
System.DebugLog "Debug Matrix"
For row As Integer = 0 To 3
Var rowStr As String = "row (" + row.ToString + ")"
For col As Integer = 0 To 3
rowStr = rowStr + " , " + Str(m(row,col))
Next
System.DebugLog rowStr
Next

End Sub

Public Function MatrixEquality(a(, ) As Double, b(, ) As Double) As Boolean

'Gleichheit prüfen

Var equality As Boolean = True

For row As Integer = 0 To 3
For col As Integer = 0 To 3
If Equal(a(row, col), b(row, col)) Then
Else
equality = False
Exit
End If
Next
If equality = False Then Exit
Next

Return equality
End Function

Public Sub MatrixIdentity(m(, ) As Double)
For row As Integer = 0 To 3
For col As Integer = 0 To 3
m(row,col) = 0.0
Next
Next

'diagonal 1
m(0, 0) = 1.0
m(1, 1) = 1.0
m(2, 2) = 1.0
m(3, 3) = 1.0

End Sub

Public Function MatrixMultiply(a(, ) As Double, b(, ) As Double) As Double(,)
Var c(3,3) As Double

For row As Integer = 0 To 3
For col As Integer = 0 To 3
c(row,col) = a(row, 0) * b(0, col) + a(row, 1) * b(1, col) + a(row, 2) * b(2, col) + a(row, 3) * b(3, col)
Next
Next

Return c

End Function

Public Function MatrixMultiplyTuple(m(, ) As Double, v As Vec3) As Vec3

Var vout As New Vec3

' immer eine Reihe der Matrix mit einer Spalte des Tuple

vout.x = Vec3.DotProduct(New vec3(m(0,0),m(0,1),m(0,2),m(0,3)), v)
vout.y = Vec3.DotProduct(New vec3(m(1,0),m(1,1),m(1,2),m(1,3)), v)
vout.z = Vec3.DotProduct(New vec3(m(2,0),m(2,1),m(2,2),m(2,3)), v)
vout.w = Vec3.DotProduct(New vec3(m(3,0),m(3,1),m(3,2),m(3,3)), v)

Return vout

End Function

Public Function MatrixRotationX(radians As Double) As Double(,)

' drehen um die X Achse

Var m(-1,-1) As Double = Matrix4x4()

MatrixIdentity(m)

Return m
End Function

Public Function MatrixRotationY(radians As Double) As Double(,)

' drehen um die Y Achse

Var m(-1,-1) As Double = Matrix4x4()

MatrixIdentity(m)

Return m
End Function

Public Function MatrixRotationZ(radians As Double) As Double(,)

' drehen um die Z Achse

Var m(-1,-1) As Double = Matrix4x4()

MatrixIdentity(m)

Return m
End Function

Public Function MatrixScaling(x As Double, y As Double, z As Double) As double(,)

' zum scalieren

Var m(-1,-1) As Double = Matrix4x4()

MatrixIdentity(m)

m(0,0)=x
m(1,1)=y
m(2,2)=z

Return m
End Function

Public Function MatrixShearing(xy As Double, xz As Double, yx As Double, yz As Double, zx As Double, zy As Double) As Double(,)
Var m(-1,-1) As Double = Matrix4x4()

MatrixIdentity(m)

m(0,1)= xy
m(0,2)= xz

m(1,0)= yx
m(1,2)= yz

m(2,0)= zx
m(2,1)= zy

Return m
End Function

Public Function MatrixTranslation(x As Double, y As Double, z As Double) As double(,)

' zum verschieben

Var m(-1,-1) As Double = Matrix4x4()

MatrixIdentity(m)

m(0,3)=x
m(1,3)=y
m(2,3)=z

Return m
End Function

Public Function MatrixTranspose(a(, ) As Double) As Double(,)

'rows into columns and columns into rows

Var m(3,3) As Double

For row As Integer = 0 To 3
For col As Integer = 0 To 3
m(col, row) = a(row, col)
Next
Next

Return m
End Function

``````
``````Protected Property EPSILON As Double = 0.00001

Public Function Clamp(x As double, _min As Double, _max As Double) As Double
If (x < _min) Then Return _min
If (x > _max) Then Return _max
Return x

End Function

Public Function DegreesToRadians(degrees As Double) As Double

Return degrees * Math.PI / 180.0
End Function

Public Function Equal(a As Double, b As Double) As Boolean
If Abs(a-b)<EPSILON Then
Return True
Else
Return False
End If

End Function

``````

Math Module

``````Public Property PI As Double = 3.14159265359
``````

Thanks a lot, all!
I started to rewrite the JS library in Xojo (it has exactly the features I need, plus a few more) and am partially successful. Some unit tests perform like expected, some others fail and I don’t see the reason currently.
Maybe it lies in the weird parameter handling for a Matrix depending on

``````declare module 'transformation-matrix' {
type Matrix = {
a: number;
b: number;
c: number;
d: number;
e: number;
f: number;
};
``````

Which I read as a structure with the double parameters in alphabetical order.

Many functions export this like here, with a weird mixture of double parameters:

``````export function shear (shx, shy) {
return {
a: 1,
c: shx,
e: 0,
b: shy,
d: 1,
f: 0
}
``````

Is that order of importance – will it set {1, shx, 0, shy, 1, 0} or do I have to take the parameter names into account, writing {1, shy, shx, 1, 0,0}?

i think this a: is like a property name.

Thanks again. Turned out it was me, comparing double values too exactly. Now only combined transforms do not work. Here I must be getting something wrong about the recursion the Javascript developer uses for chained matrix multiplies.

Still have the combined transform error, but besides that everything else looks nice. Thanks a lot again!
Think I’ll add a graphic display to see the results in 2D – let me know if someone should need this stuff in pure Xojo too.