mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Mostly working instancing + box packing
This commit is contained in:
committed by
Keavon Chambers
parent
c9192307c7
commit
fa3e6eeca9
@@ -33,6 +33,22 @@ fn string_slice(_: impl Ctx, #[implementations(String)] string: String, start: f
|
||||
string.char_indices().skip(start).take(n).map(|(_, c)| c).collect()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn split_string_by_index(
|
||||
_: impl Ctx,
|
||||
#[implementations(String)]
|
||||
/// The comma-separated string to split.
|
||||
input: String,
|
||||
/// The zero-based index of the item to retrieve.
|
||||
#[default(0.0)]
|
||||
#[min(0.0)]
|
||||
index: f64,
|
||||
) -> String {
|
||||
let parts: Vec<&str> = input.split(',').map(|s| s.trim()).collect();
|
||||
let floored_index = index.floor() as usize;
|
||||
if floored_index < parts.len() { parts[floored_index].to_string() } else { String::new() }
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn string_length(_: impl Ctx, #[implementations(String)] string: String) -> usize {
|
||||
string.len()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::instances::Instance;
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, OwnedContextImpl};
|
||||
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicGroupTable, OwnedContextImpl};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(name("Instance on Points"), category("Vector: Shape"), path(graphene_core::vector))]
|
||||
@@ -33,6 +33,36 @@ async fn instance_on_points(
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(name("Group Instance on Points"), category("Vector: Shape"), path(graphene_core::vector))]
|
||||
async fn group_instance_on_points(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
points: VectorDataTable,
|
||||
#[implementations(Context -> GraphicGroupTable)] instance_node: impl Node<'n, Context<'static>, Output = GraphicGroupTable>,
|
||||
) -> GraphicGroupTable {
|
||||
let mut result = GraphicGroupTable::empty();
|
||||
|
||||
for Instance { instance: points, transform, .. } in points.instances() {
|
||||
for (index, &point) in points.point_domain.positions().iter().enumerate() {
|
||||
let transformed_point = transform.transform_point2(point);
|
||||
|
||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
|
||||
let instanced = instance_node.eval(new_ctx.into_context()).await;
|
||||
|
||||
for instanced_element in instanced.instances() {
|
||||
let new_instance = result.push_instance(instanced_element);
|
||||
*new_instance.transform *= DAffine2::from_translation(transformed_point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove once we support empty tables, currently this is here to avoid crashing
|
||||
if result.is_empty() {
|
||||
return GraphicGroupTable::empty();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Attributes"), path(graphene_core::vector))]
|
||||
async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
|
||||
match ctx.vararg(0).map(|dynamic| dynamic.downcast_ref::<DVec2>()) {
|
||||
|
||||
@@ -806,6 +806,98 @@ where
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn box_pack(
|
||||
_: impl Ctx,
|
||||
#[implementations(GraphicGroupTable)] instances: GraphicGroupTable,
|
||||
#[expose]
|
||||
#[implementations(VectorDataTable)]
|
||||
container_shape: VectorDataTable,
|
||||
#[default(10.)] padding: f64,
|
||||
#[default(false)] sort_by_area: bool,
|
||||
) -> GraphicGroupTable {
|
||||
let mut result = GraphicGroupTable::empty();
|
||||
|
||||
// Get the container's bounding box
|
||||
let container_transform = container_shape.transform();
|
||||
let container_bbox = container_shape
|
||||
.one_instance()
|
||||
.instance
|
||||
.bounding_box_with_transform(container_transform)
|
||||
.unwrap_or_else(|| [DVec2::ZERO, DVec2::ZERO]);
|
||||
|
||||
let container_size = container_bbox[1] - container_bbox[0];
|
||||
let container_origin = container_bbox[0];
|
||||
|
||||
// Extract bounding boxes and original instances
|
||||
let mut items = Vec::new();
|
||||
for instance in instances.instances() {
|
||||
// Get the bounding box directly from the instance transform
|
||||
if let Some(bbox) = instance.instance.bounding_box(*instance.transform) {
|
||||
let size = bbox[1] - bbox[0];
|
||||
items.push((size, *instance.transform, instance.instance.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort instances by area if requested (larger first for better packing)
|
||||
if sort_by_area {
|
||||
items.sort_by(|a, b| {
|
||||
let area_a = a.0.x * a.0.y;
|
||||
let area_b = b.0.x * b.0.y;
|
||||
area_b.partial_cmp(&area_a).unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
}
|
||||
|
||||
// Simple bin packing algorithm
|
||||
let mut current_x = container_origin.x;
|
||||
let mut current_y = container_origin.y;
|
||||
let mut row_height = 0.0;
|
||||
|
||||
for (size, _, instance) in items {
|
||||
// Add padding to the item size
|
||||
let item_width = size.x + padding;
|
||||
let item_height = size.y + padding;
|
||||
|
||||
// Check if we need to move to the next row
|
||||
if current_x + item_width > container_origin.x + container_size.x {
|
||||
current_x = container_origin.x;
|
||||
current_y += row_height;
|
||||
row_height = 0.0;
|
||||
}
|
||||
|
||||
// Check if we've exceeded the container height
|
||||
if current_y + item_height > container_origin.y + container_size.y {
|
||||
// We could implement multi-page packing here
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate the position for this item
|
||||
let position = DVec2::new(current_x, current_y);
|
||||
|
||||
// Extract the current center of the instance
|
||||
let instance_center = {
|
||||
if let Some(bbox) = instance.bounding_box(DAffine2::IDENTITY) {
|
||||
(bbox[0] + bbox[1]) * 0.5
|
||||
} else {
|
||||
DVec2::ZERO
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new transform that positions the instance correctly
|
||||
let new_transform = DAffine2::from_translation(position + instance_center);
|
||||
|
||||
// Add to the result
|
||||
let pushed = result.push(instance);
|
||||
*pushed.transform = new_transform;
|
||||
|
||||
// Update position tracking
|
||||
current_x += item_width;
|
||||
row_height = row_height.max(item_height);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn center_instances(
|
||||
_: impl Ctx,
|
||||
|
||||
Reference in New Issue
Block a user