We’re introducing pivot points to Roblox Studio. With this change you will be able to set the point around which each part and model in Roblox Studio is translated / rotated. You can enable pivot points via the Pivot Editor beta feature now available in the Beta Features panel:
Sounds simple right? Well, strap in, because there’s actually a lot to unpack between the feature’s visuals, tooling, APIs, and future implications.
Team Create Restriction
EDIT: The APIs are available everywhere, and the beta is available in Team Create
For the time being, the beta is not available in Team Create: The pivot functionality will be disabled while you’re in a team create place. We want a chance to potentially change some aspects of the pivot system in response to developer feedback, and due to the implementation details of this feature that would be much more difficult if we made it available in live Team Create places immediately.
Once we’re confident that we won’t be further modifying any core aspects of the feature we’ll allow the beta in Team Create as well and update the announcement post.
The Visuals
The first thing you will notice after enabling the beta is that some things in Roblox Studio will be visually different even before you explicitly edit any pivots.
-
The selection box for models and parts now includes a “Pivot Indicator” showing where the pivot for each object is:
-
The Move tool now shows its handles directly around the pivot of an object rather than out at the extents. The cases that were previously best served by having the move handles out at the edges of an object can be better served by temporarily modifying the pivot:
-
There’s a Pivot section in the Model tab of the ribbon bar.
The Tooling
Edit Pivot Tool
The primary way to work with pivots is the Edit Pivot tool now present in the Model tab of the ribbon bar. With this tool selected, you will be editing the pivot of the selected object, rather than the changing the location of the object itself. There are three ways you can edit the pivot using this tool:
-
Use the Move handles to move the pivot along an axis.
-
Use the Rotate handles to rotate the pivot.
-
Click and drag on the Pivot Indicator to freeform drag the pivot similar to how you can freeform drag Attachments
-
(Secret extra method) If you click and drag anywhere else on the part you will also freeform drag the pivot. This can help you quickly get the pivot where you want it if you “lost” it somewhere off in the distance without having to reach up for the reset pivot button.
All three of these options respect a “Snap” toggle located next to the Edit Pivot tool. When checked, this “Snap” toggle tells the Edit Pivot tool to snap the pivot to interesting “hotspots” on the bounding box of the hovered object.
-
Snap is checked: Try to snap the pivot to interesting “hotspots” on the bounding box of the object (that is, vertices, face center, and edge centers):
-
Snap is not checked: Move the pivot in the same way that Attachments are currently moved.
Properties Pane Support
The pivot also has full properties pane support, meaning you can finally move and rotate models precisely using the properties pane just like you previously could for parts!
The new “Origin Position” and “Origin Orientation” properties on parts and models represent the current location of the part or model’s pivot. Editing them will move the object just like editing the Position / Orientation properties will, but will move the object via the pivot rather than via the center of the bounding box.
You can also precisely edit the location of the pivot relative to the object (without moving the object) by editing the PivotOffset
property for parts, or the WorldPivot
property for models.
Pivot Behavior Details
The “Cascading” pivot
The most important thing to understand is the “cascading” pivot behavior that models have. For models without a PrimaryPart, the pivot is simply a world space CFrame (model.WorldPivot
). However, for models with a PrimaryPart, the pivot of the model is exactly equal to the pivot of the PrimaryPart of that model.
Thus, for models with a PrimaryPart, the pivot will naturally move along with the model when it physically simulates. This breaks models into two categories:
-
“Static” models that don’t have a PrimaryPart: These models either don’t move at all at run-time, or are moved into position via code and left at that position thereafter.
-
“Dynamic” models that do have a PrimaryPart: These models may be moved by physical simulation. Thanks to the cascading pivot behavior, the pivot of these models will follow the PrimaryPart.
This means that it will now be important to set a PrimaryPart on models that you intend to move / check the location of with both physics and scripting.
What happens when PrimaryPart get set?
When you set the PrimaryPart of a model, the cascading pivot behavior will naturally kick in: The old WorldPivot will be ignored, and the model’s pivot will be equal to the PrimaryPart’s pivot instead.
What happens when the PrimaryPart gets removed?
Okay, but what happens when the PrimaryPart gets removed for some reason? For instance if I blow up the car and the PrimaryPart gets deleted?
In the case where the PrimaryPart gets removed from the model, there is a special behavior: The WorldPivot will be set equal to what the PrimaryPart’s pivot was just before it got removed. Effectively, when the PrimaryPart gets removed, the pivot stays where it was just prior to that removal. The goal of this behavior is to prevent ever seeing a sudden “jump” in where the pivot is as parts get deleted out from underneath the model due to game logic, streaming, or any other reason.
Newly Created Models
When you first create a model, how does the pivot get set?
Models actually have a special behavior that applies specifically when they are first created: When a model is first created, it will be in a special state where its pivot will be equal to the center of the bounding box of the model’s contents.
This special state only lasts up to the first time that you do anything to move the model or explicitly set the pivot such as editing the WorldPivot
property. This still gives you an opportunity to create a model, parent some objects underneath it, and get a sensible pivot.
This means that the following code will work nicely:
local model = Instance.new("Model")
thing1:Clone().Parent = model
thing2:Clone().Parent = model
model:PivotTo(CFrame.new(...))
model.Parent = workspace
The API
The API for pivot point comes in the form of two methods and two properties.
The two methods represent the primary way that you will interact with object pivots in your game code:
Part/Model:GetPivot()
Part/Model:PivotTo(targetCFrame)
-
GetPivot()
lets you query the current world space location of an object’s pivot as a CFrame. This provides an uniform way to get a physical object’s position in 3d space regardless of whether it’s an individual part or a model. -
PivotTo(targetCFrame)
lets you move the object, such that its pivot is now located exactly at atarget
CFrame. This provides a uniform object movement API that applies to both individual parts and models. If you’re already familiar withSetPrimaryPartCFrame
, this works very similarly to that, only operating on parts as well as models, and operating on the pivot instead of the PrimaryPart, giving you more freedom over what point is being transformed.
Two properties let you configure the pivot at run-time (or at edit time in your community plugins):
Property CFrame BasePart.PivotOffset
Property CFrame Model.WorldPivot
-
The pivot of a model without a PrimaryPart is set using the
WorldPivot
property, which represents the pivot of that model in world space. -
The pivot of a part is set using the
PivotOffset
property, which represents the offset of the pivot from the CFrame of the part. -
The pivot of a model with a PrimaryPart is set by setting the
PivotOffset
of the PrimaryPart thanks to the cascading pivot behavior.
Potentially Breaking Change To GetBoundingBox()
This is a new API, so how could it have breaking changes? There is actually one small edge case, where the changes under this beta can actually change the behavior of the results you get back from calling GetBoundingBox()
.
The bounding box orientation of models is now based off of their pivot, rather than inferred from their contents. In most cases, this doesn’t change anything. For models that have a PrimaryPart, the bounding box orientation will always be exactly the same as before. The same is true for static models that don’t move at all, their bounding box orientation will also be the same as before.
However, if you have a model which is physically simulated, and does not have a PrimaryPart, and you call GetBoundingBox()
on that model, you will get a different result than before. The position part of the returned CFrame will still be the same as before, however, the orientation part may be different. The orientation part will stay equal to the initial orientation of the model even as it moves since the pivot will not be moving along with the model.
To correct this you can simply set a PrimaryPart on the model. Please let us know if you find a case in case your code that would be broken by this behavior change.
Other API Changes
ResetOrientationToIdentity is being soft-removed – calling it will not error, but will no longer do anything. Making this function have sensible behavior when existing alongside pivot would have been too messy to be useful, so we’re choosing to remove it instead. Don’t worry though, this won’t effect your code at all! It’s an extremely obscure function that only a dozen places still use and we’re individually contacting the owners of those places.
We will be deprecating GetPrimaryPartCFrame / SetPrimaryPartCFrame once pivot comes out of beta (don’t worry though, enough code depends on them that they’ll never be removed). GetPivot / PivotTo can be used as a drop-in replacement for them that preserves the current behavior, but offers more future flexibility. Note: This works because of the cascading pivot behavior. To have been using SetPrimaryPartCFrame, the model must have had a PrimaryPart, and if it has a PrimaryPart, then the pivot will be equal to the pivot of the PrimaryPart, which by default is equal to the CFrame of the PrimaryPart. Thus, PivotTo will have the same effect as SetPrimaryPartCFrame did.
Community Building Plugins
Many existing community building plugins unfortunately won’t “just work” with pivot, and will need to be updated to maintain it and / or respect it.
The key thing that needs to happen is that existing building plugins must make sure to update the pivot when moving models that do not have a PrimaryPart
set. For those models, as described above in the Behavior Details section, the pivot will not automatically move along with the parts in the model when they move. If you use building plugins that have not been updated with models that don’t have a PrimaryPart, the pivot will become “out of sync” with the parts, staying where it was.
How to update your plugins:
-
If you use the new
PivotTo
API to move models, then you will naturally be covered, as it updates the pivot. -
If you use the existing
Model:MoveTo
/Model:SetPrimaryPartCFrame
APIs to move models, then no action is needed. These APIs have also been modified to update the pivot. -
If you individually move parts, or move parts via
BulkMoveTo
, then you will have to update your code to set theWorldPivot
property of any Models within the selections you are moving. Here’s a sample place file demonstrating an InstanceMover module with several different styles of object movement: PivotSampleModuleTest.rbxl (53.7 KB)
Please DM me if you maintain a building plugin and need advice on the process of updating it.
Future Implications
-
We plan to set the pivot of imported MeshParts to be initially equal to the mesh origin, so that the mesh origin information that is currently lost is preserved in the form of the pivot. This should be coming soon later in the beta period.
-
We plan to use the pivot as the key reference point for packages, so that packages will no longer require a PrimaryPart, this will be coming as part of a later packages update (and will be a fully backwards compatible transition).
Thanks for your hard work on this feature: @tnavarts @LuckyKobold @wengawenga @4thchamber @JoshSedai