Eliminating Floating-Point? (Or getting around it)

I have recently come across an issue with a door script: Due to the fact that Pi is rounded slightly, my entire code doesn’t work, as everything is slightly off. The problem with this is that I can’t set the orientation of a primary part without it, or if I can, it is very difficult. My code requires exact numbers, and so this causes some issues.
The code:

local function open()
	lookVector = primaryPart.CFrame.LookVector
	repeat
		y = math.rad(primaryPart.Orientation.Y - 90)
		nextCFrame = CFrame.new(primaryPart.Position) * CFrame.Angles(0, y, 0)
		group:SetPrimaryPartCFrame(nextCFrame)
		print(primaryPart.CFrame.LookVector)
		wait(0.5)
	until primaryPart.CFrame.LookVector == Vector3.new(lookVector.Z - lookVector.Z * 2, -0, lookVector.X)
	isOpen = true
end

Basically, I need to either remove the radians entirely, or find a way to make them more precise. math.floor() and math.cleil() do not work, as I have tried them.

The reason that it turns 90 degrees at a time is for testing reasons. Same with the 0.5 second delay.

Don’t question the -0, as that’s just what Roblox thinks that 0 is for some reason. It affects nothing, as it is the y lookVector, and I don’t want my door to open like a garage door.

It makes the lookVector turn into numbers such as -4.13624194632178346 (Not actually what it is, but it is very similar), when I need numbers such as -1.

You’ll never be able to get perfectly-precise decimal numbers due to float-point errors (or just in general in most computer archetypes). The issue many people have with Model:SetPrimaryPartCFrame() is that it accumulates float-point errors over time, as the method has no baseline to compare each iteration to.

There are some ways around it, like using Welds or other rigid constraints to maintain the original offset, or you can store the data yourself. I haven’t personally used it, but this module by EtiTheSpirit appears to do just that.

If you’re trying to make a door that just snaps open and closed, you can save the original and all goal CFrames to variables and move the door using one of the more-precise methods to the desired location based on stored logical context rather than evaluating the CFrame for context.

I’m not sure what you mean by this, could you elaborate?

The idea behind using a Weld is that the C0 and C1 properties are both CFrames describing the offset of Part0 from Part1, and they are only ever set explicitly. That means that regardless of wherever one of the parts a Weld is controlling moves to, the Weld always knows exactly how to move the other part, and this will not degrade over time because it is not changed with each move like it is between parts moved via Model:SetPrimaryPartCFrame().

When using a Weld, C0 (and C1 when necessary) have to be set by code. WeldConstraints offer similar functionally and set their C0 and C1 offsets automatically as well as being able to be created easily in Studio using tools under the “Model” tab, which may make them more intuitive for static models. Bear in mind, though, that if you use WeldConstraints, you must always update a part’s CFrame, not its Position. There is more detail on this on the WeldConstraint DevHub article.

1 Like

This is what the new PivotTo is useful for. PivotTo does not accumulate floating point errors, is much more performant, and overall is just better.

@OP
Just position the pivot of the model at the edge of the door, and call model:PivotTo(positionalCFrame * rotationalCFrame).

Additionally, there are two improvements you can make to your code. One is to use Stepped or Heartbeat rather than long waits, waits will be choppy and can throttle resulting in odd stuttering behaviour when the thread scheduler is over used (doing things like waiting lots of times at once in different threads, running lots of scripts, making lots of Debris calls, spawning lots of events, and calling spawn/delay all contribute to this). Heartbeat fires after physics calculations are done. Stepped fires before physics calculations are done. (Generally I use Heartbeat for static visual things, and Stepped for non static physics stuff, like changing the speed of objects)

That will mean the door has smooth movement.

The second is to not offset the object’s location each step, instead calculating the position based on time will yield much more precise and fluid results. Floating point errors will not be a problem then because you’ll only have a very small amount of error that only happens once.

So basically, rather than doing something similar to rotation = rotation + offsetRotation you’re doing something similar to rotation = elapsedTime / totalTime * totalRotation

Here is how I might change the above code:

local doorPivot = door:GetPivot() -- Get the starting pivot position of the door

local function open()
	local openAngle = 90 -- The angle offset of an opened door

	local startTime = os.clock() -- Mark the time the door is opening at
	local openTime = 1 -- How long it should take to open the door

	local elapsedTime = os.clock() - startTime
	while elapsedTime < openTime do
		local alpha = elapsedTime / openTime -- A decimal amount from 0-1 for how "open" the door is

		door:PivotTo(doorPivot * CFrame.Angles(0, alpha * openAngle, 0)) -- Move the door so its rotated from its original location
		RunService.Stepped:Wait() -- Wait a single physics step

		elapsedTime = os.clock() - startTime -- Update elapsed time
	end
	isOpen = true
end

If you’re not wanting a smooth rotation, you don’t have to have one, I am not really sure what you were trying to do with the loop in your code.

But, tl;dr PivotTo would fix your issues.

3 Likes

Thanks! This actually fixed the issue, and also allows me to use Tweening with it. (I think, I’m about to test it.(It does work, but I think it still has some issues. I will try the pivotTo solution and see if that works…))

That’s because I changed the code for testing purposes. I wanted to see if it would work 90 degrees at a time before I went out and did the entire thing.

Also, after trying that solution, I got an error: Part::Pivot is not enabled yet. I have no clue what that means, is it something that I have to enable, or something else entirely?

Ah, I thought it had released because it was enabled in live games already and seems to be working just fine. Maybe your studio is out of date? It might still be in beta or something I guess. You can enable it with File > Beta Features > Pivot Editor:

1 Like

Welp. I have no clue what just happened.


I used the group itself as the pivot object, and due to the fact that I still am not sure what pivot even does, I have no clue how to debug it. Looking up the API doesn’t help, as it just says this:
Which doesn’t help whatsoever. My question is, what does pivot even do? Does it pivot an object around a center point, or does it do something completely different?

Pivot points are a newer feature, basically they let you control the “center” or a model/bounding box in a sense.

PivotTo moves the whole model so that its pivot point matches whatever location you put, so think about it like the model is “attached” to the pivot point kind of and using PivotTo is moving and rotating the point its attached to.

In studio, going to the Model tab you will see there is a pivot edit tool:
image

1 Like

I have made it far, and now I just have one more question: What in the actual crap is going on here?


I have no clue why this is happening, as the code should do pretty much the same thing every time.
I did make some changes to the code as well; here is the revised version:

local function openClose(angle, openBool)
	openAngle = door.Orientation.Y - angle

	startTime = os.clock()
	openTime = 0.5

	timeElapsed = os.clock() - startTime
	repeat
		alpha = timeElapsed / openTime

		door:PivotTo(pivot * CFrame.Angles(0, math.rad(alpha * openAngle), 0))
		RunService.Stepped:Wait()

		timeElapsed = os.clock() - startTime
	until timeElapsed >= openTime
	isOpen = openBool
	debounce = false
end

(Angle is set to 90 when opening, and -90 when closing. isOpen just decides whether to open the door or close it.)

This should be the last kink to work out with this. I just spent the last hour trying to figure this out, to no avail.

Just realized that there is a second problem. I am still dealing with floating-point errors, and I still don’t know how to get rid of them. That is causing my door to slowly get further and further from the wall. (Also, I’m still not sure about what is causing the door to reset, but one thing at a time.)

Just save the original cframe of the door and rotate from and to that
No new fancy roblox function is going to save code which is inherently flawed in that it relies on itself, allowing a tiny error to infinitely add onto itself
Simply save a baseline value from which to rotate

Ok, I feel kind of dumb for not thinking of that, but it’s fine. Thanks for the help! Now to figure out why the door keeps snapping back to the start…