-
-
Notifications
You must be signed in to change notification settings - Fork 68
Conversation
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.
There was a problem hiding this 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 👀
/// 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, | ||
} |
There was a problem hiding this comment.
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.
#[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; | ||
} |
There was a problem hiding this comment.
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.
#[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, | ||
} |
There was a problem hiding this comment.
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.
/// The core `bjk` file format | ||
pub mod serialization; |
There was a problem hiding this comment.
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.
/// Executing an arbitrary lua expression (computed). | ||
Computed(LuaExpression), |
There was a problem hiding this comment.
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.
// TODO: Do something better for error reporting | ||
println!("Error in viewport: {err}") |
There was a problem hiding this comment.
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
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(); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
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(); |
There was a problem hiding this comment.
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
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) |
There was a problem hiding this comment.
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.
This PR adds an initial implementation of gizmos 🎉

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 🔛

Position your primitives 📍

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

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.

If you have a custom node, making your node gizmo-ready can be as simple as writing a single line of declarative code:
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)

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.