8000 Bring in the gizmos! by setzer22 · Pull Request #62 · setzer22/blackjack · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
This repository was archived by the owner on Apr 9, 2024. It is now read-only.

Bring in the gizmos! #62

Merged
merged 54 commits into from
Nov 6, 2022
Merged

Bring in the gizmos! #62

merged 54 commits into from
Nov 6, 2022

Conversation

setzer22
Copy link
Owner
@setzer22 setzer22 commented Nov 6, 2022

This PR adds an initial implementation of gizmos 🎉
gizmos_pr

Gizmos are a new powerful feature in Blackjack, allowing to edit a node's parameters right from the 3d viewport. Tweaking a gizmo is an equivalent, but sometimes more comfortable way of editing a node's parameters parameters manually, meaning the procedural workflow remains exactly the same.

This PR only includes one gizmo, but lays down the foundations so that many more gizmo types can be added. Still, even a single gizmo type, powered by egui-gizmo, is versatile enough so that many different nodes can benefit from it!

You can edit a line's endpoints 🔛
edit_line_endpoints

Position your primitives 📍
position_primitives

Or edit groups of polygons in a mesh, blender-style 🟠
edit_geometry

Another cool feature of gizmos are their time-travel capabilities 🏃‍♂️ 💨. You can enable past nodes' gizmos and tweak them to see thir results on the final mesh.
time_travel

If you have a custom node, making your node gizmo-ready can be as simple as writing a single line of declarative code:

gizmos = { Gz.tweak_point("point") }

But if you need fine-grained control, scroll down for details! Gizmos are defined as a very rich API allowing you to craft very specific behaviors: Each node can define as many gizmos as it wants, each affecting a subset of its parameters.

Last but not least, this PR also brings in several miscellaneous but very important bugfixes and improvements:

  • Fixed Godot's engine integration, which is now working on latest stable godot (3.5.1)
    godot_integration_fixed

  • Revamped Blackjack's serialization format: The old format consisted on two kinds of file, .blj for data saved in the editor, and .jack for exported Jacks, or game engine integration files. From now on, there will be a single serialization file, .bjk. This file can be used both in the editor and loaded into game engine integrations.

  • Unfortunately, the change in serialization format means models made in previous versions of Blackjack will no longer load. On the bright side, this new serialization format is defined to be versioned and more robust so that future breaking changes are minimized and migration strategies will be easier to implement.

  • Changed the internal node execution strategy: Originally, we compiled the node graph to Lua code. With time, this proved to be an over-complicated solution, and in order to greatly simplify the new gizmo system, Blackjack will now walk the graph in Rust code, only calling into Lua to invoke each of the nodes' implementations.

Instead of generating Lua code to then run it, we will not interpret the
BjkGraphs directly. This simplifies a lot the code so that we can
complicate it a bit when introducing the Gizmo logic.
This is a small detour initiated in #53, due to the fact that the Jack
file format is now broken after we no longer represent graphs themselves
as compiled lua code (which is a prerequisite to implement Gizmos, among
other useful optimizations).

The idea is to get a single canonical file format that can be used both
as an editor representation, and a representation for exported 'Jacks'.
Additionally, we will take extra care to make the format
forwards-compatible by defining mirror structs for the serialization.
This may be tedious, but gives us better control of the serialization
output and lets us build a more future-proof format.
Also remove WIP serialization code for it. The rationale is that we
don't want to store parameter configuration in the graph or the nodes.
This is data that corresponds to the nodes, and is stored in the Lua
code describing each node. Having it duplicated was always a data
redundancy waiting to bite us!
The UI was storing redundant information here, and this information
would fall out of date when the node definitions are updated. Now,
the only thing the UI stores about a node is it op_name, and uses
the NodeDefinitions to fetch the information when needed.
It was cool, but we no longer translate the graph into Lua code
There's a single file format now: BJK, which can be used both for
editing (BLJ) and engine integration (JACK).
It was enabled by default, but trying to watch for changes under a
res:// directory fails for obvious reasons.
If there's a need for customizable gizmos, we will think about it later,
for now let's YAGNI
I need to fix this so that the gizmo only overrides the input parameters
of a node when the gizmo is being interacted with.
This gives clear semantics to which gizmo should be the one responding
to keyboard events and showing the on-screen buttons.
Instead of functions returning a list of gizmos, we now have a list of
functions, each one handling one gizmo. This allows easier composition
of gizmos via helper functions.
Showing a gizmo in those cases makes no sense.
Nodes that don't accept rotation / scale can disable those options now.
Also reworks the gizmo lifetime, removing the `pre_op` and instead
exposing a __gizmos_enabled parameter that the `op` can use to decide
when to compute extra data for the gizmos.
Copy link
Owner Author
@setzer22 setzer22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some behind-the-scenes comments for the curious 👀

Comment on lines +18 to +35
/// A gizmo representing a 3d transformation, allowing to translate, rotate and
/// scale the manipulated object.
#[derive(Debug, Copy, Clone)]
pub struct TransformGizmo {
pub translation: Vec3,
pub rotation: Quat,
pub scale: Vec3,

pub pre_translation: Vec3,
pub pre_rotation: Quat,
pub pre_scale: Vec3,

pub translation_enabled: bool,
pub rotation_enabled: bool,
pub scale_enabled: bool,

pub gizmo_mode: TransformGizmoMode,
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new transform gizmo in all its might. It stores separate translation / rotation / scale components because that's how nodes typically expose their parameters.

Comment on lines +79 to +97
#[lua_impl]
impl TransformGizmo {
/// Returns the translation of this transform gizmo
#[lua(map = "LVec3(x)")]
pub fn translation(&self) -> Vec3 {
self.translation
}

/// Returns the pre-translation of this transform gizmo
#[lua(map = "LVec3(x)")]
pub fn pre_translation(&self) -> Vec3 {
self.pre_translation
}

/// Sets the translation component of this transform gizmo
#[lua]
pub fn set_translation(&mut self, tr: LVec3) {
self.translation = tr.0;
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gizmos define a Lua API and are exported to Lua as UserData so that they can be manipulated by nodes.

Comment on lines +196 to +202
#[derive(Clone, Debug)]
pub enum BlackjackGizmo {
Transform(TransformGizmo),
// This special value is sometimes returned by the UI to indicate a gizmo
// wasn't initialized. No gizmo should be rendered for this value.
None,
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I didn't want to over-design this so all gizmo types live in an enum. We may want to redesign this in the future to allow extending the editor with new gizmo types, but I still have no good solution for a Rust plugin system and in the meantime this works just fine.

Comment on lines +18 to +19
/// The core `bjk` file format
pub mod serialization;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All serialization now lives inside blackjack_engine, in the serialization module. This module defines mirror structs for each thing we want to save. No other struct outside this module should derive Serialize / Deserialize from now on.

This type mirroring is a bit tedious, but provides us with an abstraction layer to make the data format more robust to changes. Without this, even the smallest rearchitecting would break models from previous versions. Despite serde annotations like #[serde(default)] can help alleviate that to some extent, handwritten code is far more flexible in that regard.

Comment on lines -25 to -26
/// Executing an arbitrary lua expression (computed).
Computed(LuaExpression),
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was unused. I need to think of a better way to enable Lua expressions in parameters.

Comment on lines +74 to +75
// TODO: Do something better for error reporting
println!("Error in viewport: {err}")
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#23 would be great for this

Comment on lines +36 to +42
let node_positions = editor_state
.node_positions
.iter()
.map(|(node_id, pos2)| (node_id_to_idx(node_id), glam::Vec2::new(pos2.x, pos2.y)))
.sorted_by_key(|(idx, _pos)| *idx)
.map(|(_idx, pos)| pos)
.collect();
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More boring serialization code. One important bit is that I no longer store node ids as slotmap keys. Instead, I compute a 1:1 mapping between node ids and integers, and all node ids in the serialized file are just integers. This makes things more robust, should I ever swap the slotmaps with a different data structure.

@@ -167,6 +168,7 @@ impl<'a, 'b> SmartDragValue<'a, 'b> {
decimals: 2,
ranges: Ranges::new(speeds, labels),
side: RangeSelectorSide::Right,
default_range_index: None,
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Integer drag values will no longer allow updating the value in decimal scales. This parameter was added to implement that feature.

Comment on lines +123 to +131
let node_def = user_state
.node_definitions
.node_def(&graph[node_id].user_data.op_name);
if node_def.is_none() {
ui.label("⚠ no node definition")
.on_hover_text("This node is referencing a node definition that doesn't exist.");
return Default::default();
}
let node_def = node_def.unwrap();
Copy link
E982
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The graph drawing code now checks the NodeDefinition directly instead of copying parameter configuration inside the NodeData. This required a few changes in egui_node_graph: setzer22/egui_node_graph#69

Comment on lines +20 to +26
if use_slider:
$HSlider.min_value = min_val
$HSlider.max_value = max_val
$HSlider.step = (max_val - min_val) / 100.0
$HSlider.value = value
else:
$LineEdit.value = str(value)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that these are optional, we have to change the sliders in the Godot integration with a LineEdit when there's no min or max value. In the UI this is fixed by having the more versatile SmartDragValue, but porting that to Godot would be a considerable undertaking.

Maybe https://github.com/setzer22/godot-egui can be used for this.

@setzer22 setzer22 merged commit 1e6d813 into main Nov 6, 2022
This was referenced Nov 6, 2022
@setzer22 setzer22 mentioned this pull request Nov 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant
0