Why 2 identical code pieces give diffirent results? (in terms of input data)

Hello guys. I’m trying to learn quaternions, and combine them with buffers. But while doing so, I got 1 problem: in some cases, 6th and 10th components of CFrame are inversed, aka was 1 and becomes -1. Can someone tell me, why this occurs, and how I can prevent this?
Example of such CFrame:
-63.350399, 4.56043625, 23.2463398, -5.39922257e-06, -0.00118275138, -0.999999285, -0.000830398756, 0.999989152, -0.00118275813, 0.999999642, 0.000830383622, -6.38138181e-06 becomes

-63.3505859, 4.56030273, 23.2463379, 4.00296358e-07, 0.000215795357, 1.00000012, 0.00461801607, 0.999989331, -0.000215795357, -0.99998945, 0.00461801607, -5.96046448e-07
(Shorter variant, with rounding up to 0.01):
-63.35, 4.56, 23.25, 0, 0, -1, 0, 1, 0, 1, 0, 0 and
-63.35, 4.56, 23.25, 0, 0, 1, 0, 1, 0, -1, 0, 0

Quaternion code:

local QuaternionMeta = {}
QuaternionMeta.__index = QuaternionMeta
QuaternionMeta.__tostring = function(self)
	return tostring(self.w) .. ", " .. tostring(self.x) .. ", " .. tostring(self.y) .. ", " .. tostring(self.z)
end

function QuaternionMeta.new(w, x, y, z)
	local magnitude = (x^2 + y^2 + z^2 + w^2)^0.5
	local self = {
		w = w / magnitude,
		x = x / magnitude,
		y = y / magnitude,
		z = z / magnitude,
	}
	return setmetatable(self, QuaternionMeta)
end

function QuaternionMeta.fromOrientation(x, y, z):{}
	local c1 = math.cos(y/2)
	local c2 = math.cos(z/2)
	local c3 = math.cos(x/2)
	local s1 = math.sin(y/2)
	local s2 = math.sin(z/2)
	local s3 = math.sin(x/2)

	local w = c1 * c2 * c3 - s1 * s2 * s3
	local x = s1 * s2 * c3 + c1 * c2 * s3
	local y = s1 * c2 * c3 + c1 * s2 * s3
	local z = c1 * s2 * c3 - s1 * c2 * s3

	return QuaternionMeta.new(w, x, y, z)
end

function QuaternionMeta.fromCFrame(CF)
	local _, _, _, R11, _, _, _, R22, _, _, _, R33 = CF:GetComponents()

	local qx = (1 + R11 - R22 - R33)^0.5 / 2
	local qy = (1 - R11 + R22 - R33)^0.5 / 2
	local qz = (1 - R11 - R22 + R33)^0.5 / 2
	local qw = (1 + R11 + R22 + R33)^0.5 / 2

	local magnitude = (qx^2 + qy^2 + qz^2 + qw^2)^0.5
	return QuaternionMeta.new(qw / magnitude, qx / magnitude, qy / magnitude, qz / magnitude)
end

function QuaternionMeta:ToCFrame(x, y, z)
	return CFrame.new(x or 0, y or 0, z or 0, self.x, self.y, self.z, self.w)
end

return QuaternionMeta

Code which uses quaternions and somewhy in this 2 places it gives diffirent results:

--1st part, correct CFrame

local FOV = script.FieldOfView.Value
	local HalfFovRadians = math.rad(FOV / 2)
	local RayDirection = CameraStart.WorldCFrame.LookVector
	local RayRight = CameraStart.WorldCFrame.RightVector * math.tan(HalfFovRadians)
	local RayUp = -CameraStart.WorldCFrame.UpVector * math.tan(HalfFovRadians)
	
	local CamStart = CameraStart.WorldPosition
	
	local CameraData = buffer.create(21) --1+4*3+2*4 (FOV (0-255), Position, Quaternion data)
	buffer.writeu8(CameraData, 0, math.clamp(math.floor(FOV), 0, 255))
	buffer.writei32(CameraData, 1, math.clamp(math.floor(CamStart.X * PosRounding), -2147483648, 2147483647))
	buffer.writei32(CameraData, 5, math.clamp(math.floor(CamStart.Y * PosRounding), -2147483648, 2147483647))
	buffer.writei32(CameraData, 9, math.clamp(math.floor(CamStart.Z * PosRounding), -2147483648, 2147483647))
	
	local CameraQuaternion = Quaternion.fromCFrame(CameraStart.WorldCFrame)
	buffer.writei16(CameraData, 13, math.clamp(math.floor(CameraQuaternion.w * QuatRounding), -32768, 32767))
	buffer.writei16(CameraData, 15, math.clamp(math.floor(CameraQuaternion.x * QuatRounding), -32768, 32767))
	buffer.writei16(CameraData, 17, math.clamp(math.floor(CameraQuaternion.y * QuatRounding), -32768, 32767))
	buffer.writei16(CameraData, 19, math.clamp(math.floor(CameraQuaternion.z * QuatRounding), -32768, 32767))
	
	warn(CFrame.new(0, 0, 0, math.floor(CameraQuaternion.x * QuatRounding) / QuatRounding, math.floor(CameraQuaternion.y * QuatRounding) / QuatRounding, math.floor(CameraQuaternion.z * QuatRounding) / QuatRounding, math.floor(CameraQuaternion.w * QuatRounding) / QuatRounding):FuzzyEq(CameraQuaternion:ToCFrame(), 0.01))
	print(tostring(CameraQuaternion))
	print(CameraStart.WorldCFrame)
	--local CameraData = {FOV, CameraStart.WorldCFrame}
	self.CameraData[#self.CameraData + 1] = CameraData





--2nd part, inverted result

FrameIndex = FrameIndex or #self.ColorData
	local ColorData = self.ColorData[FrameIndex]
	local DistanceData = self.DistanceData[FrameIndex]
	local AttractionData = self.AttractionData[FrameIndex]
	local NormalsData = self.NormalsData[FrameIndex]
	local CameraData = self.CameraData[FrameIndex]
	
	local FOV = buffer.readu8(CameraData, 0)
	local PosX, PosY, PosZ = buffer.readi32(CameraData, 1) / PosRounding, buffer.readi32(CameraData, 5) / PosRounding, buffer.readi32(CameraData, 9) / PosRounding
	local QuatW, QuatX, QuatY, QuatZ = buffer.readi16(CameraData, 13) / QuatRounding, buffer.readi16(CameraData, 15) / QuatRounding, buffer.readi16(CameraData, 17) / QuatRounding, buffer.readi16(CameraData, 19) / QuatRounding

	local CameraCFrame = Quaternion.new(QuatW, QuatX, QuatY, QuatZ):ToCFrame(PosX, PosY, PosZ) --CFrame.new(PosX, PosY, PosZ, QuatX, QuatY, QuatZ, QuatW)
	
	print(tostring(Quaternion.new(QuatW, QuatX, QuatY, QuatZ)))
	print(CameraCFrame)

I spent day to find why it don’t works, and can’t find solution. Please, help…

1 Like