Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.24)

project(claycore
VERSION 0.120.1
VERSION 0.121.0
DESCRIPTION "Portable C++20 SDF + voxel engine library"
LANGUAGES C CXX) # C: ufbx

Expand Down
85 changes: 79 additions & 6 deletions bindings/c/clay.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ extern "C" {
#endif

#define CLAY_ABI_MAJOR 0
#define CLAY_ABI_MINOR 120
#define CLAY_ABI_PATCH 1
#define CLAY_ABI_MINOR 121
#define CLAY_ABI_PATCH 0

/* Upper bound on the element count of any batch call: points, rays, cells,
* selected node ids, stroke points, polygon vertices. A count above it is
Expand Down Expand Up @@ -1817,7 +1817,12 @@ clay_result clay_document_layer_composition(const clay_document* doc, clay_layer
* line every other minor draws: subdivision is deterministic, so a hierarchy
* without detail rebuilds from its cage and losing it is the ordinary "smaller
* or plainer" degrade. Detail is something an artist made and cannot be
* rebuilt. */
* rebuilt.
*
* Minor 20 (ABI 0.121.0) adds a third: an item carrying its OWN mirror axes
* (clay_item_set_mirror_axes) blocks every minor below 20, since an older
* build would hand it the layer's twins instead. Put such items back on the
* layer's mirror (CLAY_MIRROR_AXES_INHERIT) to write for an older build. */
clay_result clay_document_writable_at_minor(const clay_document* doc, uint32_t minor,
clay_layer_id* out_blocking_layer);

Expand Down Expand Up @@ -1848,9 +1853,10 @@ clay_result clay_document_writable_at_minor(const clay_document* doc, uint32_t m
* WHAT AN OLDER MINOR ACTUALLY LOSES, said once rather than per call: 14 comes
* back unsquashed, 15's instances come back as copies, 17 writes each payload
* once per node, and below 19 a hierarchy carrying no detail is absent and
* rebuilds from its cage. Every one of those is smaller or plainer. The two
* rebuilds from its cage. Every one of those is smaller or plainer. The three
* that would be a DIFFERENT SCULPTURE — a composition below 18, a hierarchy
* with detail below 19 — are refused instead. */
* with detail below 19, an item carrying its own mirror axes below 20 (ABI
* 0.121.0) — are refused instead. */
clay_result clay_document_save_at_minor(const clay_document* doc, const char* path,
uint32_t minor, clay_layer_id* out_blocking_layer);

Expand Down Expand Up @@ -2229,6 +2235,12 @@ clay_result clay_layer_zero_to_origin(clay_document* doc, clay_layer_id layer);
* mirror = 1 (#60); 1 is still accepted and means what it meant. Layers with
* no mirror axes evaluate exactly as before, whatever the items' flags.
*
* THE LAYER'S AXES ARE A DEFAULT, since ABI 0.121.0 (#664): an item that
* carries its own (clay_item_set_mirror_axes, clay_layer_set_node_mirror) is
* reflected through those instead, and this call no longer changes it. Items
* that never set any — every item in every document saved before — follow
* this call exactly as they always did.
*
* SETTING WHAT THE LAYER ALREADY CARRIES DOES NOTHING, since ABI 0.103.0
* (#536): identical axes and an identical mirror_k return CLAY_OK without
* recording an undo step and without invalidating anything. Through 0.102.0
Expand Down Expand Up @@ -2469,9 +2481,43 @@ clay_result clay_item_set_rounding(clay_item* item, float rounding); /* >= 0
clay_result clay_item_set_color(clay_item* item, const float rgb[3]);
/* Layer-mirror participation, the builder's spelling of clay_item_desc.mirror:
* -1 excludes the item from the layer's mirror, 0 and 1 follow it (the
* default). See clay_set_layer_mirror. */
* default). See clay_set_layer_mirror. Once the item carries its own axes
* (clay_item_set_mirror_axes) this decides only its RADIAL participation. */
clay_result clay_item_set_mirror(clay_item* item, int32_t mirror);

/* "Take the layer's mirror axes": the default own-axes value of every item,
* and what every document saved before ABI 0.121.0 loads with. */
#define CLAY_MIRROR_AXES_INHERIT 255

/* The item's OWN mirror axes, since ABI 0.121.0 (#664): CLAY_MIRROR_X|Y|Z OR'd
* together, 0 for none, or CLAY_MIRROR_AXES_INHERIT to follow the layer again.
* Anything else is CLAY_ERROR_INVALID_ARGUMENT.
*
* Set, the item's axes REPLACE the layer's for this item outright — whatever
* the layer's mirror is now or is changed to later, and whatever the item's
* participation flag says. That is what lets a host keep what was made under
* a symmetry as it was made: give each new item the axes symmetry had when it
* was made, and turning the layer's mirror off or pointing it at Y no longer
* moves any of them. An item set to 0 has no mirror copy on any layer.
*
* WHAT STAYS THE LAYER'S: the seam (mirror_k), the planes (the layer's local
* frame), and the radial mode, which still follows clay_item_set_mirror. A
* feathered volume replace takes no mirror copies whatever it carries, as it
* never has (clay_volume_params.feather).
*
* A Move or magnify drag reaches such an item through ITS reflections: both
* sides of an item that kept X move together, and an item held at 0 moves on
* the side the drag touched only (clay_layer_move_surface).
*
* NOT FREE ON THE WAY DOWN: a document holding an item with its own axes
* cannot be written at a format minor below 20 — clay_document_save_at_minor
* refuses it and names the layer — because dropping them would hand the item
* the layer's twins instead of its own. */
clay_result clay_item_set_mirror_axes(clay_item* item, uint8_t axes);
/* What clay_item_set_mirror_axes last set: CLAY_MIRROR_AXES_INHERIT for a
* builder that never set any. */
clay_result clay_item_mirror_axes(const clay_item* item, uint8_t* out_axes);

/* Appends one domain warp (clay_deform) to the item's chain: the local point
* is warped by the first one added first. params/param_count are the kind's
* own, listed on clay_deform; ease indexes an easing curve and is read by the
Expand Down Expand Up @@ -3001,6 +3047,33 @@ clay_result clay_layer_node_op_blend(const clay_document* doc, clay_layer_id lay
clay_result clay_layer_node_color(const clay_document* doc, clay_layer_id layer,
clay_node_id node, float out_rgb[3]);

/* A PLACED item's mirror participation and own axes, set as one undoable edit
* (#664) — the same two values clay_item_set_mirror and
* clay_item_set_mirror_axes give a builder, for an item already in the
* document. `mirror` takes clay_item_set_mirror's rule (negative excludes, 0
* and 1 follow); `axes` takes clay_item_set_mirror_axes's, so
* CLAY_MIRROR_AXES_INHERIT puts the item back on the layer's mirror. Before
* this a host could correct neither without removing and re-adding the item,
* which changes its id and its place in the chain.
*
* One command, one undo step: undo puts back both values the item had.
* Refused on a protected layer as every node edit is. A GROUP is
* CLAY_ERROR_INVALID_ARGUMENT: evaluation never reads a group's mirror, so a
* value there would be a control that does not act. Setting the values the
* item already holds is still recorded — it is not the layer mirror's no-op
* short-circuit — and invalidates only the item's own influence. */
clay_result clay_layer_set_node_mirror(clay_document* doc, clay_layer_id layer,
clay_node_id node, int32_t mirror, uint8_t axes);
/* The reader: `*out_mirror` 1 when the item follows the layer's mirror and -1
* when it opted out; `*out_axes` its own axes or CLAY_MIRROR_AXES_INHERIT;
* `*out_effective_axes` the axes it is ACTUALLY reflected through on this
* layer right now — its own, else the layer's when it participates, else 0,
* and 0 for a feathered volume replace. Any out pointer may be null. A group
* is CLAY_ERROR_INVALID_ARGUMENT, as its setter is. */
clay_result clay_layer_node_mirror(const clay_document* doc, clay_layer_id layer,
clay_node_id node, int32_t* out_mirror, uint8_t* out_axes,
uint8_t* out_effective_axes);

/* The layer's TOP-LEVEL nodes, count-then-index, in the layer's EVALUATION
* order — index 0 is the node evaluated first, and the index is the one
* clay_layer_add_group and clay_add_item_in_group place against. An index of
Expand Down
77 changes: 75 additions & 2 deletions bindings/c/clay_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,20 @@ clay_result check_palette_index(std::int32_t index, std::uint8_t* out) {
return CLAY_OK;
}

// An item's own mirror axes (#664): CLAY_MIRROR_X|Y|Z OR'd, 0, or the inherit
// sentinel. Kept apart from check_mirror_axes because the sentinel is only
// meaningful here, and accepting it for a voxel edit would be a control that
// means nothing there.
clay_result check_own_mirror_axes(std::uint8_t axes) {
static_assert(CLAY_MIRROR_AXES_INHERIT == scene::kMirrorAxesInherit,
"the C sentinel and the engine's must be one value");
constexpr unsigned kAll = CLAY_MIRROR_X | CLAY_MIRROR_Y | CLAY_MIRROR_Z;
if (axes == scene::kMirrorAxesInherit || (axes & ~kAll) == 0) return CLAY_OK;
return fail(CLAY_ERROR_INVALID_ARGUMENT,
"mirror axes outside CLAY_MIRROR_X|Y|Z and not CLAY_MIRROR_AXES_INHERIT: " +
std::to_string(axes));
}

clay_result check_mirror_axes(std::int32_t axes, std::uint8_t* out) {
constexpr std::int32_t kAll = CLAY_MIRROR_X | CLAY_MIRROR_Y | CLAY_MIRROR_Z;
if (axes < 0 || (axes & ~kAll) != 0)
Expand Down Expand Up @@ -5337,6 +5351,11 @@ eval::DeviceBuffer brick_slot(const eval::DeviceBuffer& whole, std::size_t at, s
// SENTENCES: a host reading "a layer carries a composition" about a layer that
// carries a hierarchy would go and look at the wrong thing.
std::string minor_refusal(const clay_document* doc, std::uint16_t at, clay_layer_id blocking) {
// At 19 the one scene field that blocks is an item's own mirror axes
// (#664), which layer_blocking_minor checks first at every minor below 20.
if (at < 20 && scene::layer_blocking_minor(doc->doc.document, 19) == blocking)
return "an item carries its own mirror axes, which this format minor cannot say: "
"writing it there would hand the item the layer's mirror copies instead";
if (scene::layer_blocking_minor(doc->doc.document, at) == blocking)
return "a layer carries a composition that this format minor cannot say: writing it "
"there would turn a cutting layer into a unioning one";
Expand Down Expand Up @@ -6256,6 +6275,42 @@ clay_result clay_layer_node_color(const clay_document* doc, clay_layer_id layer,
return CLAY_OK;
}

clay_result clay_layer_set_node_mirror(clay_document* doc, clay_layer_id layer,
clay_node_id node, int32_t mirror, uint8_t axes) {
clay_result r = check_own_mirror_axes(axes);
if (r != CLAY_OK) return r;
// A group's flag is never read by evaluation (emit_item copies items), so
// a value there would be a control that does not act. A miss falls
// through to apply_edit's NOT_FOUND, as the op/blend setter's does.
const scene::Node* target = peek_node(doc, layer, node);
if (target && target->is_group)
return fail(CLAY_ERROR_INVALID_ARGUMENT,
"a group takes no mirror: set it on the items inside");
return apply_edit(doc,
scene::Command{scene::SetNodeMirrorCmd{layer, node, mirror >= 0, axes}},
"node not found");
}

clay_result clay_layer_node_mirror(const clay_document* doc, clay_layer_id layer,
clay_node_id node, int32_t* out_mirror, uint8_t* out_axes,
uint8_t* out_effective_axes) {
const scene::Node* n = nullptr;
clay_result r = find_node(doc, layer, node, &n);
if (r != CLAY_OK) return r;
if (n->is_group)
return fail(CLAY_ERROR_INVALID_ARGUMENT, "a group takes no mirror: ask the items inside");
if (out_mirror) *out_mirror = n->mirror ? 1 : -1;
if (out_axes) *out_axes = n->own_mirror_axes;
if (out_effective_axes) {
// find_node resolved the layer, so it is there.
const scene::Layer* l = doc->doc.document.find_layer(layer);
*out_effective_axes = scene::item_is_feathered_replace(*n)
? std::uint8_t{0}
: scene::effective_mirror_axes(*n, *l);
}
return CLAY_OK;
}

clay_result clay_layer_node_count(const clay_document* doc, clay_layer_id layer,
size_t* out_count) {
if (!doc || !out_count) return fail(CLAY_ERROR_INVALID_ARGUMENT, "null document or count");
Expand Down Expand Up @@ -7149,6 +7204,20 @@ clay_result clay_item_set_mirror(clay_item* item, int32_t mirror) {
return CLAY_OK;
}

clay_result clay_item_set_mirror_axes(clay_item* item, uint8_t axes) {
if (!item) return fail(CLAY_ERROR_INVALID_ARGUMENT, "null item");
clay_result r = check_own_mirror_axes(axes);
if (r != CLAY_OK) return r;
item->node.own_mirror_axes = axes;
return CLAY_OK;
}

clay_result clay_item_mirror_axes(const clay_item* item, uint8_t* out_axes) {
if (!item || !out_axes) return fail(CLAY_ERROR_INVALID_ARGUMENT, "null item or out_axes");
*out_axes = item->node.own_mirror_axes;
return CLAY_OK;
}

clay_result clay_item_add_deformer(clay_item* item, int32_t deform, const float* params,
size_t param_count, int32_t ease) {
if (!item) return fail(CLAY_ERROR_INVALID_ARGUMENT, "null item");
Expand Down Expand Up @@ -8859,10 +8928,13 @@ clay_result move_surface_impl(clay_document* doc, clay_layer_id layer, const flo
const float pull = std::sqrt(displacement[0] * displacement[0] +
displacement[1] * displacement[1] +
displacement[2] * displacement[2]);
// The dragged items' OWN mirror axes join the layer's (#664): an item that
// kept a twin the layer's mirror no longer names moves it too.
std::vector<math::Aabb> reach;
for (const brush::DragImage& image :
brush::drag_images(*l, kernel::cf3(centre[0], centre[1], centre[2]),
kernel::cf3(displacement[0], displacement[1], displacement[2])))
kernel::cf3(displacement[0], displacement[1], displacement[2]),
brush::prepared_own_mirror_axes(prepared)))
reach.push_back(math::Aabb{image.centre, image.centre}.dilated(radius + pull));

const kernel::cfloat3 world_pull =
Expand Down Expand Up @@ -9014,7 +9086,8 @@ clay_result clay_layer_magnify_surface(clay_document* doc, clay_layer_id layer,
std::vector<math::Aabb> reach;
for (const brush::DragImage& image :
brush::drag_images(*l, kernel::cf3(centre[0], centre[1], centre[2]),
kernel::cf3(0.0f, 0.0f, 0.0f)))
kernel::cf3(0.0f, 0.0f, 0.0f),
brush::prepared_own_mirror_axes(prepared)))
reach.push_back(math::Aabb{image.centre, image.centre}.dilated(radius));

GestureResolver resolver;
Expand Down
Loading
Loading