godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.15k stars 97 forks source link

Add an option for grid friendly "additive" snap scaling like Unreal Engine #9487

Open elCommissar opened 6 months ago

elCommissar commented 6 months ago

Describe the project you are working on

Any 3D project that values in-engine level blockout.

Describe the problem or limitation you are having in your project

In Godot, scaling along the x axis using a 0.5 scale snap results in these steps: (Where each step is a separate scale operation.)

0: (1.5, 1, 1) 1: (2.25, 1, 1) 2: (3.375, 1, 1) 3: (5.0625, 1, 1) 4: (7.59375, 1, 1) 5: (11.39063, 1, 1) 6: (17.08594, 1, 1) 7: (25.62891, 1, 1) 8: (38.44336, 1, 1) 9: (57.66504, 1, 1)

Godot_v4 2 1-stable_win64_Fd5sXK6e1u

It essentially does scale += scale * (Vector3(1,0,0) * Vector3(0.5,0.5,0.5))

In Unreal, scaling along the x axis using a 0.5 scale snap results in these steps:

UnrealEditor_QSSf1D8fuu

0: (1.5, 1, 1) 1: (2, 1, 1) 2: (2.5, 1, 1) 3: (3, 1, 1) 4: (3.5, 1, 1) 5: (4, 1, 1) 6: (4.5, 1, 1) 7: 5, 1, 1) 8: (5.5, 1, 1) 9: (6, 1, 1)

It essentially does scale += (Vector3(1,0,0) * Vector3(0.5,0.5,0.5))

When doing level blockouts, the godot method of scaling prevents adhering to a grid, which is essential for when you export the blockout and use them as a reference for the actual final meshes in external software like Blender or Maya.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

An option to switch between current godot snap scaling and a new "additive scaling" that works like unreal editor's snap scaling, as shown in the scaling step example above. The option could live in the Configure Snap popup. Rather than being a percentage based scale snap that can go off grid, it would ask for a specific step size to add to the current scale along whatever axis is currently being scaled.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

I don't know exactly how this would work but this GDscript example gives a rough idea of Godot vs Unreal scaling and are what I used to get the example I showed earlier of scale steps in godot vs unreal. The godot one is a translation of what I saw in the godot source code. The unreal one is a complete guess on my part and the origin handling in both is likely wrong.


p_motion  = motion_mask * snap

func scale_godot(edit_center : Vector3, p_motion : Vector3, p_original : Transform3D) -> Transform3D:
    var s : Transform3D = Transform3D()
    var base : Transform3D = Transform3D(Basis(), edit_center)
    s = s.basis.scaled(p_motion + Vector3(1, 1, 1))
    s = base * (s * (base.inverse() * p_original))
    return s

func scale_unreal(edit_center : Vector3, p_motion : Vector3, p_original : Transform3D) -> Transform3D:
    var s : Transform3D = Transform3D()
    s = s.basis.scaled(p_motion)
    s.basis.x += p_original.basis.x
    s.basis.y += p_original.basis.y
    s.basis.z += p_original.basis.z
    s.origin += p_original.origin
    return s

The code would also have to provide a way to handle negative scaling. For example, the current godot scaling has a check to rotate 180 degrees along whatever axis is being scaled to ensure that negative scale values are conveniently handled.

If this enhancement will not be used often, can it be worked around with a few lines of script?

Editor Plugin:

I attempted to directly access the Node3d scaling handle via EditorNode3DGizmo and EditorNode3DGizmoPlugin but I believe that neither of these are intended or able to access the actual Node3D transform gizmo and are only for adding your own custom gizmos that use the circle helper handles, similar to the newer CSG nodes gizmo helpers.

I tried to add a custom Node3D editor plugin that would act as a wrapper, adding its current scale to its child node and then resetting its own scale to (1,1,1) which allows for a workaround that imitates the unreal additive scaling. However, while dragging the scale handle, the visual feedback is strictly limited to the "multiplicate" scaling of godot, regardless of what the actual scale of the child node was set to. I can only speculate that the visual feedback while scaling is using a snapshot of the scale of the child node right before the handle was dragged and does not update the visual position of the child node until you release the scaling handle.

Using CSG Nodes for Level Blockout

While you can use CSG nodes for blocking out, the CSG nodes that have the newer helper gizmos are limited and only really the CSGBox3D truely escapes the grid scaling issues. If you could use the CSGBox3D helper gizmo on a parent nodes bounding boxes to scale the children nodes, it would be fantastic. But the CSGBox3D seems to work by simultaneously changing the dimensions of the Box mesh itself and its position. I am unsure if you can directly manipulate the dimensions of a bounding box since it is defined by its contents.

You can blockout a level purely with CSGBox3D but it does not give you nearly as much insight into the design and feel of a level as the added benefit of grid snappable and scalable arches, windows, stairs, prisms etc. Any kind of simple boolean operations with CSGCombiner lose the benefit of the helper gizmo and are stuck with the grid scaling issue.

Is there a reason why this should be core and not an add-on in the asset library?

While I don't believe this method of snap scaling is common outside of Unreal Engine, it makes prototyping and level designing much more usable and enjoyable without being limited to CSGBox3d. I think having this option core allows level design and environment art professionals coming from software like Unreal Engine to embrace Godot as a serious competitor and encourages all users to stay inside Godot instead of reaching for 3rd party tools to handle the level design phase.

Calinou commented 6 months ago

Remember that scaling CSG nodes will also scale any child nodes (current or future), so keep this in mind if you're using scaling regularly during level blockouts. Resizing is usually preferred for this reason to avoid surprises when you add more CSG nodes as a child of another CSG node.

To me, it sounds like we should improve the CSG cylinder/sphere/torus gizmos to be draggable from one side only. We can use the same approach as https://github.com/godotengine/godot/pull/80278, but bespoke code instead of a reusable helper this time (as it probably won't be reused in other nodes).