Notifications
Clear all

[Closed] Matrix3 to string and back

Hi there,

I want to save an object’s transforms into a file and load them back to the object, but it seems that something is going wrong with the conversion. The problem is more obvious if your objects are far away form origin and if their pivots are not in the same place with their parents.

In the following sample code I don’t save anything to a file, I just convert values from Matrix3 to float and back. I’ve used the simplest way to do it, by “executing” the string in order to get the Matrix3 value. I’ve also tried to save all the components separately into float values and combine them again into a Matrix3 value using the “as float” method but I got the exact same results.

The whole thing gets even more weird when I compare the two Row4 parts (last line of the code) because even though they look identical, they don’t seem to be equal.

So here is the code


--Range
start = 0.0
end = 100.0
samples = .5
--Objects
ObjM = $MatrixBakeCap
ObjS = $StringBakeCap
--SAVE
tmsM = for t=start to end by samples collect at time t ( ObjM.transform )	 --Collect TMs
tmsS = for t=start to end by samples collect at time t ( ObjS.transform as string )  --Collect TMs as string
--LOAD
ObjM.parent = undefined
ObjS.parent = undefined
iM=0
iS = 0
for t=start to end by samples do with animate on at time t ( iM += 1; ObjM.transform = tmsM[iM] )
for t=start to end by samples do with animate on at time t ( iS += 1; ObjS.transform = execute tmsS[iS] )
--Compare positions
for i=1 to tmsM.count do ( tmsSvalue = execute tmsS[i]; format "% % = %
" tmsM[i].row4 tmsSvalue.row4 ( tmsM[i].row4 == tmsSvalue.row4 ) )

And I have also attached a scene file. For the green object I collect the Transforms and load them back without any conversion and it works fine, and the for red object I try to collect and load the transforms by converting them to String and back. When you execute the script, check the red object’s X position axis.

Thanks,
Nick

30 Replies
 lo1

You are making 2 assumptions in your code:

  1. The two objects initially have identical transforms.
  2. Matrices can make a round-trip to a string.

Both of these assumptions are false.

1) The objects have different initial transforms.

Thus, the comparison in the last line of code will always be false, even if the serialization was accurate.

2) A string representation of a matrix can NOT be converted back to that matrix.

This is because a matrix is stored as float values, which themselves are not guaranteed to be identical to their string representation.

To prove this, run the following code:

a = 0.1234567
b = a as string
c = execute b
a == c --false

In order to accurately represent a float value in serialization, we must get its raw bytes, not its string representation.

These are functions you can use to get accurate string representation of matrices. They convert each float value to a corresponding int value, which CAN be serialized as a string without data loss.

fn serializeMatrix3 mat3 =
(
	local ss = stringstream ""
	format "%,%,%,%,%,%,%,%,%,%,%,%" \
		(bit.floatAsInt mat3.row1.x) \
		(bit.floatAsInt mat3.row1.y) \
		(bit.floatAsInt mat3.row1.z) \
		(bit.floatAsInt mat3.row2.x) \
		(bit.floatAsInt mat3.row2.y) \
		(bit.floatAsInt mat3.row2.z) \
		(bit.floatAsInt mat3.row3.x) \
		(bit.floatAsInt mat3.row3.y) \
		(bit.floatAsInt mat3.row3.z) \
		(bit.floatAsInt mat3.row4.x) \
		(bit.floatAsInt mat3.row4.y) \
		(bit.floatAsInt mat3.row4.z) \
		to:ss
	return ss as string
)

fn strToFloat str =
(
	return (bit.IntAsFloat (str as integer))
)

fn deserializeMatrix3 str =
(
	local p = filterString str ","
	local row1 = [strToFloat p[1], strToFloat p[2], strToFloat p[3]]
	local row2 = [strToFloat p[4], strToFloat p[5], strToFloat p[6]]
	local row3 = [strToFloat p[7], strToFloat p[8], strToFloat p[9]]
	local row4 = [strToFloat p[10], strToFloat p[11], strToFloat p[12]]
	return matrix3 row1 row2 row3 row4
)

A more performant solution would be to do away with string representations altogether and just write the raw float bytes to a binary stream. I’ll leave you with writing the code for that.

 lo1

And this is a function you can use for comparing almost identical matrices, as most similarly looking matrices will be.


fn compareMatrices a b thresh:0.001 =
(
	for r = 1 to 4 do
	(
		for v = 1 to 3 do
		(
			if abs (a[r][v] - b[r][v]) > thresh do return false
		)
	)
	return true
)

It can also be optimized further, but this is the most readable form of it.

Hi lo,

Thank you very much for your detailed reply.

I haven’t really worked with bits so I’ll try read/understand how to use them.
In terms of the my first assumption though, sorry I just saw that I’ve mistakenly moved the top object. In my tests (with the wrong serialization) they where perfectly aligned I was still getting false.

Thanks again,
Nick

Hi again,

I think I understood how your code works. You convert the value from float to an integer bit value so when you save it, you don’t lose any info. I have one question though, I’ve tried to do the same just for floats and I’m getting the following result.


fn deserializeFloat str = (
 bit.IntAsFloat (str as integer)
)
 
fn serializeFloat flt = (
 local ss = stringstream ""
 format "%" (bit.floatAsInt flt) to:ss
 ss as string
)
 
deserializeFloat (serializeFloat 205.0125)
205.012

Is it normal that I’m loosing the last decimal point?

Thanks,
Nick

1 Reply
 lo1
(@lo1)
Joined: 11 months ago

Posts: 0

Yes. The string representation of your value is truncated even before you serialize it.
See the following code:


a = 205.0125
205.012

The printed value is not the value stored, if you want to test it, do it this way:

x = 205.0125
  y = deserializeFloat (serializeFloat x)
  x == y --> true

(you can also use formattedPrint to get some longer representations for pretty printing; even the default formattedPrint x will give you “205.0125”)

Cool, thanks guys!

 lo1

Again, I must warn – if you need this for a reliable and fast solution, you’d be wise to stay away from string representations entirely.

So, I suppose you advice me to find a way to save my data in Binary format?

(using something like this? http://docs.autodesk.com/3DSMAX/15/ENU/MAXScript-Help/index.html?url=files/GUID-423269A5-B395-4A90-8058-F96C0A24115F.htm,topicNumber=d30e169175 )

 lo1

Yes, using exactly that
Specifically, the writeFloat and readFloat methods will be helpful.

Your read/write matrix functions would look something like this:

fn writeMatrix stream mat3 =
(
	for r = 1 to 4 do for v = 1 to 3 do writeFloat stream mat3[r][v]
)

fn readMatrix stream =
(
	local rows = for r = 1 to 4 collect [readFloat stream, readFloat stream, readFloat stream]
	return matrix3 rows[1] rows[2] rows[3] rows[4]	
)

You would be wise to also start with a small file header. At the least, the first 4 bytes should be an integer describing how many matrices are in the rest of the file.

Page 1 / 3