diff --git a/bindings/nodes/color.py b/bindings/nodes/color.py index 00f9a564..1a242f04 100644 --- a/bindings/nodes/color.py +++ b/bindings/nodes/color.py @@ -83,6 +83,33 @@ class _color_arg_color_mix(ct.Structure): ("factor", ct.POINTER(_value)) ] +class ramp_element(ct.Structure): + _fields_ = [ + ("color", cr_color), + ("position", ct.c_float) + ] + +class color_mode(IntEnum): + mode_rgb = 0 + mode_hsv = 1 + mode_hsl = 2 + +class interpolation(IntEnum): + ease = 0 + cardinal = 1 + linear = 2 + b_spline = 3 + constant = 4 + +class _color_arg_color_ramp(ct.Structure): + _fields_ = [ + ("factor", ct.POINTER(_value)), + ("color_mode", ct.c_int), + ("interpolation", ct.c_int), + ("elements", ct.POINTER(ramp_element)), + ("element_count", ct.c_int) + ] + class _color_arg(ct.Union): _fields_ = [ ("constant", cr_color), @@ -97,6 +124,7 @@ class _color_arg(ct.Union): ("vec_to_color", _color_arg_vec_to_color), ("gradient", _color_arg_gradient), ("color_mix", _color_arg_color_mix), + ("color_ramp", _color_arg_color_ramp), ] class _color_type(IntEnum): @@ -113,6 +141,7 @@ class _color_type(IntEnum): vec_to_color = 10 gradient = 11 color_mix = 12 + color_ramp = 13 _color._anonymous_ = ("arg",) _color._fields_ = [ @@ -226,3 +255,13 @@ def __init__(self, a, b, factor): self.factor = factor self.cr_struct.type = _color_type.color_mix self.cr_struct.color_mix = _color_arg_color_mix(self.a.castref(), self.b.castref(), self.factor.castref()) + +class NodeColorRamp(NodeColorBase): + def __init__(self, factor, color_mode, interpolation, elements): + super().__init__() + self.factor = factor + self.color_mode = color_mode + self.interpolation = interpolation + self.elements = (ramp_element * len(elements))(*elements) + self.element_count = len(elements) + self.cr_struct.color_ramp = _color_arg_color_ramp(self.factor.castref(), self.color_mode, self.interpolation, self.elements, self.element_count) diff --git a/bindings/nodes/convert.py b/bindings/nodes/convert.py index a21ea578..f7ef9568 100644 --- a/bindings/nodes/convert.py +++ b/bindings/nodes/convert.py @@ -106,6 +106,43 @@ def parse_color(input, group_inputs): val = parse_value(input.inputs['Value'], group_inputs) fac = parse_value(input.inputs['Fac'], group_inputs) return NodeColorHSVTransform(color, hue, sat, val, fac) + case 'ShaderNodeValToRGB': + # Confusing name, this is the Color Ramp node. + factor = parse_value(input.inputs['Fac'], group_inputs) + color = input.color + ramp = input.color_ramp + # ramp has: hue_interpolation, interpolation, color_mode + elements = [] + for element in ramp.elements: + blc = element.color + color = cr_color(blc[0], blc[1], blc[2], blc[3]) + elements.append(ramp_element(color, element.position)) + # element has: position, color, alpha + def match_interpolation(bl_mode): + match bl_mode: + case 'EASE': + return interpolation.ease + case 'CARDINAL': + return interpolation.cardinal + case 'LINEAR': + return interpolation.linear + case 'B_SPLINE': + return interpolation.b_spline + case 'CONSTANT': + return interpolation.constant + case _: + print("cm WTF: {}".format(bl_mode)) + def match_color_mode(bl_int): + match bl_int: + case 'RGB': + return color_mode.mode_rgb + case 'HSV': + return color_mode.mode_hsv + case 'HSL': + return color_mode.mode_hsl + case _: + print("interp WTF: {}".format(bl_int)) + return NodeColorRamp(factor, match_color_mode(ramp.color_mode), match_interpolation(ramp.interpolation), elements) # case 'ShaderNodeCombineRGB': # return warning_color # case 'ShaderNodeCombineHSL': @@ -234,6 +271,9 @@ def parse_value(input, group_inputs): ior = parse_value(input.inputs[0], group_inputs) normal = parse_vector(input.inputs[1], group_inputs) return NodeValueFresnel(ior, normal) + case 'ShaderNodeValToRGB': + color = parse_color(input, group_inputs) + return NodeValueGrayscale(color) case _: print("Unknown value node of type {}, maybe fix.".format(input.bl_idname)) return NodeValueConstant(0.0) diff --git a/include/c-ray/node.h b/include/c-ray/node.h index b16f5f65..c0480d4e 100644 --- a/include/c-ray/node.h +++ b/include/c-ray/node.h @@ -124,6 +124,7 @@ struct cr_color_node { cr_cn_vec_to_color, cr_cn_gradient, cr_cn_color_mix, + cr_cn_color_ramp, } type; union { @@ -189,6 +190,30 @@ struct cr_color_node { struct cr_color_node *b; struct cr_value_node *factor; } color_mix; + + struct cr_color_ramp_params { + struct cr_value_node *factor; + enum cr_color_mode { + cr_mode_rgb, + cr_mode_hsv, + cr_mode_hsl, + } color_mode; + + enum cr_interpolation { + cr_ease, // TODO + cr_cardinal, // TODO + cr_linear, + cr_b_spline, // TODO + cr_constant, + } interpolation; + + struct ramp_element { + struct cr_color color; + float position; // [0.0,1.0] + } *elements; + + int element_count; + } color_ramp; } arg; }; diff --git a/src/common/node_parse.c b/src/common/node_parse.c index 1c25bb04..4f0451ab 100644 --- a/src/common/node_parse.c +++ b/src/common/node_parse.c @@ -200,6 +200,38 @@ static struct cr_color_node *cn_alloc(struct cr_color_node d) { return desc; } +static enum cr_color_mode get_color_mode(char *mode_str) { + if (stringEquals(mode_str, "rgb")) return cr_mode_rgb; + if (stringEquals(mode_str, "hsv")) return cr_mode_hsv; + if (stringEquals(mode_str, "hsl")) return cr_mode_hsl; + return cr_mode_rgb; +} + +static enum cr_interpolation get_interpolation(char *interp_str) { + if (stringEquals(interp_str, "ease")) return cr_ease; + if (stringEquals(interp_str, "cardinal")) return cr_cardinal; + if (stringEquals(interp_str, "linear")) return cr_linear; + if (stringEquals(interp_str, "b_spline")) return cr_b_spline; + if (stringEquals(interp_str, "constant")) return cr_constant; + return cr_linear; +} + +static struct ramp_element elem_parse(const struct cJSON *elem_in) { + struct color c = color_parse(cJSON_GetObjectItem(elem_in, "color")); + float pos = cJSON_GetNumberValue(cJSON_GetObjectItem(elem_in, "position")); + return (struct ramp_element){ .color = { c.red, c.green, c.blue, c.alpha }, .position = pos }; +} + +static struct ramp_element *get_elements(const struct cJSON *arr) { + size_t count = cJSON_GetArraySize(arr); + if (!count) return NULL; + struct ramp_element *elems = calloc(count, sizeof(*elems)); + for (size_t i = 0; i < count; ++i) { + elems[i] = elem_parse(cJSON_GetArrayItem(arr, i)); + } + return elems; +} + struct cr_color_node *cr_color_node_build(const struct cJSON *desc) { if (!desc) return NULL; @@ -376,6 +408,18 @@ struct cr_color_node *cr_color_node_build(const struct cJSON *desc) { } }); } + if (stringEquals(type->valuestring, "color_ramp")) { + return cn_alloc((struct cr_color_node){ + .type = cr_cn_color_ramp, + .arg.color_ramp = { + .factor = cr_value_node_build(cJSON_GetObjectItem(desc, "factor")), + .color_mode = get_color_mode(cJSON_GetStringValue(cJSON_GetObjectItem(desc, "color_mode"))), + .interpolation = get_interpolation(cJSON_GetStringValue(cJSON_GetObjectItem(desc, "interpolation"))), + .elements = get_elements(cJSON_GetObjectItem(desc, "elements")), + .element_count = cJSON_GetArraySize(cJSON_GetObjectItem(desc, "elements")) + } + }); + } } logr(warning, "Failed to parse textureNode. Here's a dump:\n"); @@ -442,6 +486,10 @@ void cr_color_node_free(struct cr_color_node *d) { cr_color_node_free(d->arg.color_mix.b); cr_value_node_free(d->arg.color_mix.factor); break; + case cr_cn_color_ramp: + cr_value_node_free(d->arg.color_ramp.factor); + if (d->arg.color_ramp.elements) + free(d->arg.color_ramp.elements); } free(d); } diff --git a/src/common/vector.h b/src/common/vector.h index c9278a2c..790821ea 100644 --- a/src/common/vector.h +++ b/src/common/vector.h @@ -270,3 +270,19 @@ static inline float schlick(float cosine, float IOR) { r0 = r0 * r0; return r0 + (1.0f - r0) * powf((1.0f - cosine), 5.0f); } + +static inline float lerp(float min, float max, float t) { + return ((1.0f - t) * min) + (t * max); +} + +static inline float inv_lerp(float min, float max, float between) { + return (between - min) / (max - min); +} + +static inline struct vector vec_lerp(const struct vector a, const struct vector b, const float t) { + return (struct vector){ + lerp(a.x, b.x, t), + lerp(a.y, b.y, t), + lerp(a.z, b.z, t) + }; +} diff --git a/src/lib/api/c-ray.c b/src/lib/api/c-ray.c index 79e27f68..4d968883 100644 --- a/src/lib/api/c-ray.c +++ b/src/lib/api/c-ray.c @@ -650,7 +650,17 @@ struct cr_color_node *color_deepcopy(const struct cr_color_node *in) { out->arg.color_mix.a = color_deepcopy(in->arg.color_mix.a); out->arg.color_mix.b = color_deepcopy(in->arg.color_mix.b); out->arg.color_mix.factor = value_deepcopy(in->arg.color_mix.factor); - default: + break; + case cr_cn_color_ramp: + out->arg.color_ramp.factor = value_deepcopy(in->arg.color_ramp.factor); + out->arg.color_ramp.color_mode = in->arg.color_ramp.color_mode; + out->arg.color_ramp.interpolation = in->arg.color_ramp.interpolation; + out->arg.color_ramp.element_count = in->arg.color_ramp.element_count; + int ct = out->arg.color_ramp.element_count; + out->arg.color_ramp.elements = calloc(ct, sizeof(*out->arg.color_ramp.elements)); + for (int i = 0; i < ct; ++i) out->arg.color_ramp.elements[i] = in->arg.color_ramp.elements[i]; + break; + default: // FIXME: default remove break; } return out; diff --git a/src/lib/datatypes/spline.c b/src/lib/datatypes/spline.c index 67753e24..0696c7e7 100644 --- a/src/lib/datatypes/spline.c +++ b/src/lib/datatypes/spline.c @@ -17,18 +17,6 @@ struct spline { struct vector d; }; -float lerp(const float a, const float b, const float t) { - return (1.0f - t) * a + t * b; -} - -struct vector vec_lerp(const struct vector a, const struct vector b, const float t) { - return (struct vector){ - lerp(a.x, b.x, t), - lerp(a.y, b.y, t), - lerp(a.z, b.z, t) - }; -} - struct spline *spline_new(struct vector a, struct vector b, struct vector c, struct vector d) { struct spline *new = calloc(1, sizeof(*new)); *new = (struct spline){ a, b, c, d }; diff --git a/src/lib/datatypes/tile.c b/src/lib/datatypes/tile.c index 49bae822..b1f450ed 100644 --- a/src/lib/datatypes/tile.c +++ b/src/lib/datatypes/tile.c @@ -65,6 +65,7 @@ struct render_tile *tile_next_interactive(struct renderer *r, struct tile_set *s mutex_release(set->tile_mutex); return NULL; } + // FIXME: Use an atomic conditional for this, instead of polling here timer_sleep_ms(32); goto again; } diff --git a/src/lib/nodes/colornode.c b/src/lib/nodes/colornode.c index b71bc617..358ea8e5 100644 --- a/src/lib/nodes/colornode.c +++ b/src/lib/nodes/colornode.c @@ -102,8 +102,16 @@ const struct colorNode *build_color_node(struct cr_scene *s_ext, const struct cr build_color_node(s_ext, desc->arg.color_mix.a), build_color_node(s_ext, desc->arg.color_mix.b), build_value_node(s_ext, desc->arg.color_mix.factor)); - default: + case cr_cn_color_ramp: + return new_color_ramp(&s, + build_value_node(s_ext, desc->arg.color_ramp.factor), + desc->arg.color_ramp.color_mode, + desc->arg.color_ramp.interpolation, + desc->arg.color_ramp.elements, + desc->arg.color_ramp.element_count); + default: // FIXME: default remove return NULL; }; + return NULL; } diff --git a/src/lib/nodes/colornode.h b/src/lib/nodes/colornode.h index 93972771..67e1c825 100644 --- a/src/lib/nodes/colornode.h +++ b/src/lib/nodes/colornode.h @@ -35,6 +35,7 @@ struct colorNode { #include "converter/combinergb.h" #include "converter/combinehsl.h" #include "converter/combinehsv.h" +#include "converter/color_ramp.h" #include "textures/hsv_transform.h" // const struct colorNode *unknownTextureNode(const struct node_storage *s); diff --git a/src/lib/nodes/converter/color_ramp.c b/src/lib/nodes/converter/color_ramp.c new file mode 100644 index 00000000..9dee9d06 --- /dev/null +++ b/src/lib/nodes/converter/color_ramp.c @@ -0,0 +1,155 @@ +// +// color_ramp.c +// c-ray +// +// Created by Valtteri Koskivuori on 12/01/2024. +// Copyright © 2024 Valtteri Koskivuori. All rights reserved. +// + +#include +#include "../../../common/hashtable.h" +#include "../../../common/vector.h" +#include "../../datatypes/scene.h" +#include "../../../../include/c-ray/node.h" +#include "../colornode.h" + +#include "color_ramp.h" + +typedef struct ramp_element ramp_element; +dyn_array_def(ramp_element); + +struct color_ramp_node { + struct colorNode node; + const struct valueNode *input_value; + enum cr_color_mode color_mode; + enum cr_interpolation interpolation; + struct ramp_element_arr elements; +}; + +static bool compare(const void *A, const void *B) { + const struct color_ramp_node *this = A; + const struct color_ramp_node *other = B; + bool elements_match = true; + if (this->elements.count != other->elements.count) elements_match = false; + for (size_t i = 0; i < this->elements.count && elements_match; ++i) { + if (this->elements.items[i].position != other->elements.items[i].position) { + elements_match = false; + break; + } + struct cr_color a = this->elements.items[i].color; + struct cr_color b = other->elements.items[i].color; + if (a.r != b.r || a.g != b.g || a.b != b.b || a.a != b.a) { + elements_match = false; + break; + } + } + return this->input_value == other->input_value && + this->color_mode == other->color_mode && + this->interpolation == other->interpolation && + elements_match; +} + +static uint32_t hash(const void *p) { + const struct color_ramp_node *this = p; + uint32_t h = hashInit(); + h = hashBytes(h, &this->input_value, sizeof(this->input_value)); + h = hashBytes(h, &this->color_mode, sizeof(this->color_mode)); + h = hashBytes(h, &this->interpolation, sizeof(this->interpolation)); + for (size_t i = 0; i < this->elements.count; ++i) + h = hashBytes(h, &this->elements.items[i], sizeof(this->elements.items[i])); + return h; +} + +// static void dump(const void *node, char *dumpbuf, int bufsize) { +// struct color_ramp_node *self = (struct color_ramp_node *)node; +// // FIXME +// (void)self; +// } + +// TODO: This most certainly needs a bunch of tests to verify correctness +static struct color eval(const struct colorNode *node, sampler *sampler, const struct hitRecord *record) { + const struct color_ramp_node *this = (const struct color_ramp_node *)node; + const float pos = this->input_value->eval(this->input_value, sampler, record); + + if (this->elements.count == 1) + return *(struct color *)&this->elements.items[0].color; + + if (pos <= this->elements.items[0].position) + return *(struct color *)&this->elements.items[0].color; + + if (pos >= this->elements.items[this->elements.count - 1].position) + return *(struct color *)&this->elements.items[this->elements.count - 1].color; + + struct ramp_element *left = NULL; + for (size_t i = 0; i < this->elements.count; ++i) { + if (this->elements.items[i].position <= pos) { + left = &this->elements.items[i]; + } + } + struct ramp_element *right = NULL; + for (size_t i = this->elements.count - 1; i; --i) { + if (this->elements.items[i].position >= pos) { + right = &this->elements.items[i]; + } + } + + if (!left || !right) { + logr(warning, "color_ramp: Missing ramp elements: %s %s\n", !left ? "left" : "", !right ? "right" : ""); + return (struct color){ 0 }; + } + + if (this->interpolation == cr_constant) { + return *(struct color *)&left->color; + } + float t = inv_lerp(left->position, right->position, pos); + return colorLerp(*(struct color *)&left->color, *(struct color *)&right->color, t); +} + +const struct colorNode *new_color_ramp(const struct node_storage *s, + const struct valueNode *input_value, + enum cr_color_mode color_mode, + enum cr_interpolation interpolation, + struct ramp_element *elements, + int element_count) { + if (!elements || !element_count) { + logr(warning, "color_ramp: No control points provided, bailing out\n"); + return newConstantTexture(s, g_pink_color); + } + struct ramp_element_arr element_arr; + for (int i = 0; i < element_count; ++i) { + ramp_element_arr_add(&element_arr, elements[i]); + } + // Validate mode, interpolation and elements first + // Frankly, I don't even know what this mode does yet, need to look into that + // FIXME: Support HSV and HSL modes + if (color_mode != cr_mode_rgb) { + logr(warning, "color_ramp: color mode %s not supported yet, setting to RGB", + color_mode == cr_mode_hsv ? "HSV" : color_mode == cr_mode_hsl ? "HSL" : "RGB"); + color_mode = cr_mode_rgb; + } + // FIXME: Support all interpolation modes + if (interpolation != cr_linear && interpolation != cr_constant) { + logr(warning, "color_ramp: Only linear and constant interpolations are supported, setting to linear\n"); + interpolation = cr_linear; + } + // Now validate all the control points + for (size_t i = 0; i < element_arr.count; ++i) { + struct ramp_element *e = &element_arr.items[i]; + if (e->position < 0.0f || e->position > 1.0f) { + logr(warning, "Invalid control point position: %.3f, clamping\n", e->position); + if (e->position > 1.0f) e->position = 1.0f; + if (e->position < 0.0f) e->position = 0.0f; + } + } + HASH_CONS(s->node_table, hash, struct color_ramp_node, { + // TODO: If the input is constant, we can probably evaluate this at setup time, right? + .input_value = input_value ? input_value : newConstantValue(s, 0.0f), + .color_mode = color_mode, + .interpolation = interpolation, + .elements = element_arr, + .node = { + .eval = eval, + .base = { .compare = compare, .dump = NULL } + } + }); +} diff --git a/src/lib/nodes/converter/color_ramp.h b/src/lib/nodes/converter/color_ramp.h new file mode 100644 index 00000000..4834e386 --- /dev/null +++ b/src/lib/nodes/converter/color_ramp.h @@ -0,0 +1,21 @@ +// +// color_ramp.h +// c-ray +// +// Created by Valtteri Koskivuori on 12/01/2024. +// Copyright © 2024 Valtteri Koskivuori. All rights reserved. +// + +#pragma once + +#include "../../../common/color.h" +#include "../../../common/dyn_array.h" +#include "../valuenode.h" + +const struct colorNode *new_color_ramp(const struct node_storage *s, + const struct valueNode *input_value, + enum cr_color_mode color_mode, + enum cr_interpolation interpolation, + struct ramp_element *elements, + int element_count); + diff --git a/src/lib/nodes/converter/map_range.c b/src/lib/nodes/converter/map_range.c index b0812c91..f1853e46 100644 --- a/src/lib/nodes/converter/map_range.c +++ b/src/lib/nodes/converter/map_range.c @@ -62,10 +62,6 @@ static void dump(const void *node, char *dumpbuf, int bufsize) { input, from_min, from_max, to_min, to_max); } -static inline float lerp(float min, float max, float t) { - return ((1.0f - t) * min) + (t * max); -} - static float eval(const struct valueNode *node, sampler *sampler, const struct hitRecord *record) { const struct mapRangeNode *this = (const struct mapRangeNode *)node; const float input_value = this->input_value->eval(this->input_value, sampler, record); diff --git a/src/lib/protocol/protocol.c b/src/lib/protocol/protocol.c index 2b6aaa12..dba873f4 100644 --- a/src/lib/protocol/protocol.c +++ b/src/lib/protocol/protocol.c @@ -617,6 +617,19 @@ static cJSON *serialize_color_node(const struct cr_color_node *in) { cJSON_AddItemToObject(out, "b", serialize_color_node(in->arg.color_mix.b)); cJSON_AddItemToObject(out, "factor", serialize_value_node(in->arg.color_mix.factor)); break; + case cr_cn_color_ramp: + cJSON_AddStringToObject(out, "type", "color_ramp"); + cJSON_AddItemToObject(out, "factor", serialize_value_node(in->arg.color_ramp.factor)); + cJSON_AddNumberToObject(out, "color_mode", in->arg.color_ramp.color_mode); + cJSON_AddNumberToObject(out, "interpolation", in->arg.color_ramp.interpolation); + cJSON *array = cJSON_AddArrayToObject(out, "elements"); + for (int i = 0; i < in->arg.color_ramp.element_count; ++i) { + cJSON *element = cJSON_CreateObject(); + cJSON_AddItemToObject(element, "color", serialize_color(in->arg.color_ramp.elements[i].color)); + cJSON_AddNumberToObject(element, "position", in->arg.color_ramp.elements[i].position); + cJSON_AddItemToArray(array, element); + } + break; } return out; }