Files
armorpaint/paint/sources/render/make_sculpt.c
T
2026-08-23 20:08:04 +02:00

1275 lines
66 KiB
C

#include "../global.h"
i32 sculpt_object_vertex_offset(mesh_object_t *o) {
i32 offset = 0;
for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) {
mesh_object_t *p = g_project->_->paint_objects->buffer[i];
if (p == o) {
break;
}
offset += p->data->index_array->length;
}
return offset;
}
static void sculpt_ensure_texture_res() {
i32 required = 0;
for (i32 o = 0; o < g_project->_->paint_objects->length; ++o) {
required += g_project->_->paint_objects->buffer[o]->data->index_array->length;
}
bool changed = false;
while (config_get_texture_res_x() * config_get_texture_res_y() < required && config_get_texture_res_x() < 8192) {
i32 next = config_get_texture_res_x() < 2048 ? 2048 : config_get_texture_res_x() < 4096 ? 4096 : 8192;
i32 pos = config_get_texture_res_pos(next);
config_set_texture_res(pos);
base_res_handle->i = pos;
changed = true;
}
if (changed) {
layers_resize();
}
}
void sculpt_import_mesh_pack_to_texture(gpu_texture_t *target) {
// Pack positions and normals into texture
u32 capacity = config_get_texture_res_x() * config_get_texture_res_y();
buffer_t *b = buffer_create(capacity * 4 * 4);
i32 vertex_offset = 0;
for (i32 o = 0; o < g_project->_->paint_objects->length; ++o) {
mesh_data_t *mesh = g_project->_->paint_objects->buffer[o]->data;
for (i32 i = 0; i < math_floor(mesh->index_array->length); ++i) {
i32 index = mesh->index_array->buffer[i];
i32 t = i + vertex_offset;
if (t >= capacity) {
break;
}
buffer_set_f32(b, 4 * t * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4] / 32767.0);
buffer_set_f32(b, 4 * t * 4 + 1 * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 1] / 32767.0);
buffer_set_f32(b, 4 * t * 4 + 2 * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 2] / 32767.0);
f32 nor_x = mesh->vertex_arrays->buffer[1]->values->buffer[index * 2] / 32767.0f;
f32 nor_y = mesh->vertex_arrays->buffer[1]->values->buffer[index * 2 + 1] / 32767.0f;
f32 nor_z = mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 3] / 32767.0f;
f32 l1 = math_abs(nor_x) + math_abs(nor_y) + math_abs(nor_z);
f32 oct_x = l1 > 0.0f ? nor_x / l1 : 0.0f;
f32 oct_y = l1 > 0.0f ? nor_y / l1 : 0.0f;
if (nor_z < 0.0f) {
f32 ox = oct_x;
f32 oy = oct_y;
oct_x = (1.0f - math_abs(oy)) * (ox >= 0.0f ? 1.0f : -1.0f);
oct_y = (1.0f - math_abs(ox)) * (oy >= 0.0f ? 1.0f : -1.0f);
}
buffer_set_f32(b, 4 * t * 4 + 3 * 4, (oct_x + 1.0f) * 0.5f + math_floor((oct_y + 1.0f) * 0.5f * 255.0f + 0.5f));
}
vertex_offset += mesh->index_array->length;
}
gpu_texture_t *imgmesh = gpu_create_texture_from_bytes(b, config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_RGBA128);
array_free(b);
free(b);
draw_begin(target, false, 0);
draw_set_pipeline(pipes_copy128);
draw_scaled_image(imgmesh, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
draw_set_pipeline(NULL);
draw_end();
gpu_delete_texture(imgmesh);
}
static char *sculpt_blend_mode(node_shader_t *kong, i32 blending, char *cola, char *colb, char *opac) {
// mix/add/screen/lighten/difference (raise), subtract/darken (carve) and linear light (bidirectional)
if (blending == BLEND_TYPE_DARKEN) {
return string_tmp("lerp(%s, min(%s, %s), %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_MULTIPLY) {
return string_tmp("lerp(%s, %s * %s, %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_BURN) {
return string_tmp("lerp(%s, 1.0 - (1.0 - %s) / %s, %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_LIGHTEN) {
return string_tmp("max(%s, %s * %s)", cola, colb, opac);
}
else if (blending == BLEND_TYPE_SCREEN) {
return string_tmp("(1.0 - (1.0 - %s * %s) * (1.0 - %s))", colb, opac, cola);
}
else if (blending == BLEND_TYPE_DODGE) {
return string_tmp("lerp(%s, %s / (1.0 - %s), %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_ADD) {
return string_tmp("lerp(%s, %s + %s, %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_OVERLAY) {
char *res = "sculpt_overlay_res";
node_shader_write_frag(kong, string_tmp("var %s: float;", res));
node_shader_write_frag(kong, string_tmp("if (%s < 0.5) { %s = 2.0 * %s * %s; } else { %s = 1.0 - 2.0 * (1.0 - %s) * (1.0 - %s); }", cola, res, cola,
colb, res, cola, colb));
return string_tmp("lerp(%s, %s, %s)", cola, res, opac);
}
else if (blending == BLEND_TYPE_SOFT_LIGHT) {
return string_tmp("((1.0 - %s) * %s + %s * ((1.0 - %s) * %s * %s + %s * (1.0 - (1.0 - %s) * (1.0 - %s))))", opac, cola, opac, cola, colb, cola, cola,
colb, cola);
}
else if (blending == BLEND_TYPE_LINEAR_LIGHT) {
return string_tmp("(%s + %s * (2.0 * (%s - 0.5)))", cola, opac, colb);
}
else if (blending == BLEND_TYPE_DIFFERENCE) {
return string_tmp("lerp(%s, abs(%s - %s), %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_SUBTRACT) {
return string_tmp("lerp(%s, %s - %s, %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_DIVIDE) {
return string_tmp("((1.0 - %s) * %s + %s * %s / %s)", opac, cola, opac, cola, colb);
}
else { // BLEND_TYPE_MIX, hue / saturation / color / value
return string_tmp("lerp(%s, %s, %s)", cola, colb, opac);
}
}
node_shader_context_t *sculpt_make_sculpt_run(material_t *data) {
char *context_id = "paint";
shader_context_t *props = ALLOC_INIT(shader_context_t, {.name = context_id,
.depth_write = false,
.compare_mode = "always",
.cull_mode = "none",
.vertex_elements = any_array_create_from_raw(
(void *[]){
ALLOC_INIT(vertex_element_t, {.name = "pos", .data = "float2"}),
},
1),
.color_attachments = any_array_create_from_raw(
(void *[]){
"RGBA128",
"R8",
},
2)});
node_shader_context_t *con_paint = node_shader_context_create(data, props);
con_paint->data->color_writes_red = u8_array_create_from_raw(
(u8[]){
true,
true,
true,
true,
},
4);
con_paint->data->color_writes_green = u8_array_create_from_raw(
(u8[]){
true,
true,
true,
true,
},
4);
con_paint->data->color_writes_blue = u8_array_create_from_raw(
(u8[]){
true,
true,
true,
true,
},
4);
con_paint->data->color_writes_alpha = u8_array_create_from_raw(
(u8[]){
true,
true,
true,
true,
},
4);
node_shader_t *kong = node_shader_context_make_kong(con_paint);
bool decal = context_is_decal();
bool particle = g_context->tool == TOOL_TYPE_PARTICLE;
// Grab drags the vertices along with the cursor instead of displacing along the normal
bool grab = g_context->tool == TOOL_TYPE_BRUSH && g_context->brush_sculpt == SCULPT_TYPE_GRAB;
// Decal fill layer: displacement must be confined to the decal box projection
bool decal_layer = g_context->layer->fill_material != NULL && g_context->layer->uv_type == UV_TYPE_PROJECT && g_context->tool == TOOL_TYPE_FILL;
bool has_wposition = g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_CLONE ||
g_context->tool == TOOL_TYPE_BLUR || g_context->tool == TOOL_TYPE_PARTICLE || g_context->tool == TOOL_TYPE_FILL || decal;
bool sculpt_triplanar = has_wposition && !decal && !decal_layer && g_context->tool != TOOL_TYPE_BLUR;
node_shader_add_out(kong, "tex_coord: float2");
node_shader_write_vert(kong, "var madd: float2 = float2(0.5, 0.5);");
node_shader_write_vert(kong, "output.tex_coord = input.pos.xy * madd + madd;");
node_shader_write_vert(kong, "output.tex_coord.y = 1.0 - output.tex_coord.y;");
node_shader_write_vert(kong, "output.pos = float4(input.pos.xy, 0.0, 1.0);");
node_shader_write_attrib_frag(kong, "var tex_coord: float2 = input.tex_coord;");
node_shader_write_attrib_frag(kong, "var sculpt_uv: float2 = tex_coord;");
// Restrict displacement to the object selected in the layer's object combo
if (g_project->_->paint_objects->length > 1) {
node_shader_add_texture(kong, "texpaint_sculpt_undo", "_texpaint_sculpt_undo");
node_shader_add_constant(kong, "texpaint_undo_size: float2", "_size(_texpaint_sculpt_undo)");
node_shader_add_constant(kong, "sculpt_mask_offset: float", "_sculpt_mask_offset");
node_shader_add_constant(kong, "sculpt_mask_count: float", "_sculpt_mask_count");
node_shader_write_frag(kong, "var sculpt_mask_lin: float = floor(sculpt_uv.y * constants.texpaint_undo_size.y) * "
"constants.texpaint_undo_size.x + floor(sculpt_uv.x * constants.texpaint_undo_size.x);");
node_shader_write_frag(kong, "if (sculpt_mask_lin < constants.sculpt_mask_offset || sculpt_mask_lin >= "
"constants.sculpt_mask_offset + constants.sculpt_mask_count) { discard; }");
}
node_shader_add_constant(kong, "inp: float4", "_input_brush");
node_shader_add_constant(kong, "inplast: float4", "_input_brush_last");
node_shader_add_texture(kong, "gbufferD", NULL);
kong->frag_out = "float4[2]";
node_shader_add_constant(kong, "brush_radius: float", "_brush_radius");
node_shader_add_constant(kong, "brush_opacity: float", "_brush_opacity");
node_shader_add_constant(kong, "brush_hardness: float", "_brush_hardness");
node_shader_write_frag(kong, "var dist: float = 0.0;");
if (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_CLONE || g_context->tool == TOOL_TYPE_BLUR ||
g_context->tool == TOOL_TYPE_PARTICLE || decal) {
node_shader_add_constant(kong, "invVP: float4x4", "_inv_view_proj_matrix");
if (grab) {
// Anchor a camera-facing plane at the grab_start surface point
node_shader_add_constant(kong, "grab_start: float2", "_grab_start");
node_shader_write_frag(kong, "var grab_ndc: float2 = float2(constants.grab_start.x, 1.0 - constants.grab_start.y) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var grab_adepth: float = sample_lod(gbufferD, sampler_linear, constants.grab_start, 0.0).r;");
node_shader_write_frag(kong, "var grab_anchor4: float4 = constants.invVP * float4(grab_ndc, grab_adepth, 1.0);");
node_shader_write_frag(kong, "var grab_anchor: float3 = grab_anchor4.xyz / grab_anchor4.w;");
node_shader_write_frag(kong, "var grab_near4: float4 = constants.invVP * float4(grab_ndc, 0.0, 1.0);");
node_shader_write_frag(kong, "var grab_far4: float4 = constants.invVP * float4(grab_ndc, 1.0, 1.0);");
node_shader_write_frag(kong, "var grab_plane_n: float3 = normalize(grab_far4.xyz / grab_far4.w - grab_near4.xyz / grab_near4.w);");
// Intersect the cursor ray with the grab plane
node_shader_write_frag(kong, "var winp_ndc: float2 = float2(constants.inp.x, 1.0 - constants.inp.y) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var winp_o4: float4 = constants.invVP * float4(winp_ndc, 0.0, 1.0);");
node_shader_write_frag(kong, "var winp_f4: float4 = constants.invVP * float4(winp_ndc, 1.0, 1.0);");
node_shader_write_frag(kong, "var winp_o: float3 = winp_o4.xyz / winp_o4.w;");
node_shader_write_frag(kong, "var winp_d: float3 = normalize(winp_f4.xyz / winp_f4.w - winp_o);");
node_shader_write_frag(kong,
"var winp: float4 = float4(winp_o + winp_d * (dot(grab_anchor - winp_o, grab_plane_n) / dot(winp_d, grab_plane_n)), 1.0);");
}
else {
node_shader_write_frag(kong, "var depth: float = sample_lod(gbufferD, sampler_linear, constants.inp.xy, 0.0).r;");
node_shader_write_frag(kong, "var winp: float4 = float4(float2(constants.inp.x, 1.0 - constants.inp.y) * 2.0 - 1.0, depth, 1.0);");
node_shader_write_frag(kong, "winp = constants.invVP * winp;");
node_shader_write_frag(kong, "winp.xyz = winp.xyz / winp.w;");
}
node_shader_add_constant(kong, "W: float4x4", "_world_matrix");
node_shader_write_attrib_frag(kong, "var read_undo: float4 = texpaint_sculpt_undo[uint2(uint(tex_coord.x * constants.texpaint_undo_size.x), "
"uint(tex_coord.y * constants.texpaint_undo_size.y))];");
node_shader_write_attrib_frag(kong, "var wposition: float3 = (constants.W * float4(read_undo.xyz, 1.0)).xyz;");
if (grab) {
node_shader_write_frag(kong, "var winpl_ndc: float2 = float2(constants.inplast.x, 1.0 - constants.inplast.y) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var winpl_o4: float4 = constants.invVP * float4(winpl_ndc, 0.0, 1.0);");
node_shader_write_frag(kong, "var winpl_f4: float4 = constants.invVP * float4(winpl_ndc, 1.0, 1.0);");
node_shader_write_frag(kong, "var winpl_o: float3 = winpl_o4.xyz / winpl_o4.w;");
node_shader_write_frag(kong, "var winpl_d: float3 = normalize(winpl_f4.xyz / winpl_f4.w - winpl_o);");
node_shader_write_frag(
kong, "var winplast: float4 = float4(winpl_o + winpl_d * (dot(grab_anchor - winpl_o, grab_plane_n) / dot(winpl_d, grab_plane_n)), 1.0);");
}
else {
node_shader_write_frag(kong, "var depthlast: float = sample_lod(gbufferD, sampler_linear, constants.inplast.xy, 0.0).r;");
node_shader_write_frag(kong, "var winplast: float4 = float4(float2(constants.inplast.x, 1.0 - constants.inplast.y) * 2.0 - 1.0, depthlast, 1.0);");
node_shader_write_frag(kong, "winplast = constants.invVP * winplast;");
node_shader_write_frag(kong, "winplast.xyz = winplast.xyz / winplast.w;");
}
if (particle) {
node_shader_add_constant(kong, "particle_hit: float3", "_particle_hit");
node_shader_add_constant(kong, "particle_hit_last: float3", "_particle_hit_last");
node_shader_add_constant(kong, "particle_radius: float", "_particle_radius");
node_shader_write_frag(kong, "var ppa: float3 = wposition.xyz - constants.particle_hit;");
node_shader_write_frag(kong, "var pba: float3 = constants.particle_hit_last - constants.particle_hit;");
node_shader_write_frag(kong, "var pph: float = clamp(dot(ppa, pba) / max(dot(pba, pba), 0.00000001), 0.0, 1.0);");
node_shader_write_frag(kong, "dist = length(ppa - pba * pph) * (5.0 / constants.particle_radius);");
node_shader_write_frag(kong, "if (dist > 1.0) { discard; }");
}
else if (!decal) {
if (g_context->xray) {
node_shader_write_frag(kong, "var xray_ndc: float2 = float2(constants.inp.x, 1.0 - constants.inp.y) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var xray_near: float4 = constants.invVP * float4(xray_ndc, 0.0, 1.0);");
node_shader_write_frag(kong, "var xray_far: float4 = constants.invVP * float4(xray_ndc, 1.0, 1.0);");
node_shader_write_frag(kong, "var xray_axis: float3 = normalize(xray_far.xyz / xray_far.w - xray_near.xyz / xray_near.w);");
}
if (g_context->brush_lazy_radius > 0 && g_context->brush_lazy_step > 0) { // Sphere
if (g_context->xray) {
node_shader_write_frag(kong, "var pa: float3 = wposition.xyz - winp.xyz;");
node_shader_write_frag(kong, "dist = length(pa - xray_axis * dot(xray_axis, pa));");
}
else {
node_shader_write_frag(kong, "dist = distance(wposition.xyz, winp.xyz);");
}
}
else { // Capsule
node_shader_write_frag(kong, "var pa: float3 = wposition.xyz - winp.xyz;");
node_shader_write_frag(kong, "var ba: float3 = winplast.xyz - winp.xyz;");
if (g_context->xray) {
node_shader_write_frag(kong, "pa = pa - xray_axis * dot(xray_axis, pa);");
node_shader_write_frag(kong, "ba = ba - xray_axis * dot(xray_axis, ba);");
}
node_shader_write_frag(kong, "var h: float = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);");
node_shader_write_frag(kong, "dist = length(pa - ba * h);");
}
node_shader_write_frag(kong, "if (dist > constants.brush_radius) { discard; }");
}
else { // decal
node_shader_add_constant(kong, "decal_mask: float4", "_decal_mask");
node_shader_write_frag(kong, "if (constants.decal_mask.z > 0.0) {");
if (g_context->brush_lazy_radius > 0 && g_context->brush_lazy_step > 0) {
node_shader_write_frag(kong, "dist = distance(wposition.xyz, winp.xyz);");
}
else {
node_shader_write_frag(kong, "var pa: float3 = wposition.xyz - winp.xyz;");
node_shader_write_frag(kong, "var ba: float3 = winplast.xyz - winp.xyz;");
node_shader_write_frag(kong, "var h: float = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);");
node_shader_write_frag(kong, "dist = length(pa - ba * h);");
}
node_shader_write_frag(kong, "if (dist > constants.brush_radius) { discard; }");
node_shader_write_frag(kong, "}");
}
}
else if (g_context->tool == TOOL_TYPE_FILL) {
node_shader_add_constant(kong, "W: float4x4", "_world_matrix");
node_shader_write_attrib_frag(kong, "var read_undo: float4 = texpaint_sculpt_undo[uint2(uint(tex_coord.x * constants.texpaint_undo_size.x), "
"uint(tex_coord.y * constants.texpaint_undo_size.y))];");
node_shader_write_attrib_frag(kong, "var wposition: float3 = (constants.W * float4(read_undo.xyz, 1.0)).xyz;");
}
if (decal_layer) {
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_add_constant(kong, "decal_layer_matrix: float4x4", "_decal_layer_matrix");
node_shader_write_frag(kong, "var decal_proj: float4 = constants.decal_layer_matrix * float4(read_undo.xyz, 1.0);");
node_shader_write_frag(kong, "var uvsp: float2 = decal_proj.xy / decal_proj.w;");
node_shader_write_frag(kong, "uvsp.x = uvsp.x * 0.5 + 0.5;");
node_shader_write_frag(kong, "uvsp.y = 1.0 - (uvsp.y * 0.5 + 0.5);");
node_shader_write_frag(kong, "if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) { discard; }");
f32 uv_angle = g_context->layer->angle;
if (uv_angle != 0.0) {
node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle");
node_shader_write_frag(kong, "uvsp = float2(uvsp.x * constants.brush_angle.x - uvsp.y * constants.brush_angle.y, uvsp.x * "
"constants.brush_angle.y + uvsp.y * constants.brush_angle.x);");
}
// Reject surfaces not facing the decal direction
node_shader_write_frag(kong, "var dproj_undo: float4 = texpaint_sculpt_undo[uint2(uint(sculpt_uv.x * constants.texpaint_undo_size.x), uint(sculpt_uv.y "
"* constants.texpaint_undo_size.y))];");
node_shader_write_frag(kong, "var dproj_nv: float = floor(dproj_undo.a) / 255.0;");
node_shader_write_frag(kong, "var dproj_oct: float2 = float2(dproj_undo.a - floor(dproj_undo.a), dproj_nv) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var dproj_nz: float = 1.0 - abs(dproj_oct.x) - abs(dproj_oct.y);");
node_shader_write_frag(kong, "var dproj_nor: float3 = float3(dproj_oct.xy, dproj_nz);");
node_shader_write_frag(kong, "if (dproj_nz < 0.0) { dproj_nor.xy = octahedron_wrap(dproj_oct.xy); }");
node_shader_write_frag(kong, "dproj_nor = normalize((constants.W * float4(normalize(dproj_nor), 0.0)).xyz);");
node_shader_add_constant(kong, "decal_layer_nor: float3", "_decal_layer_nor");
f32 dot_angle = g_context->brush_angle_reject_dot;
node_shader_write_frag(kong, string_tmp("if (abs(dot(dproj_nor, constants.decal_layer_nor) - 1.0) > %s) { discard; }", f32_to_string(dot_angle)));
// Reject surfaces outside the decal box
node_shader_add_constant(kong, "decal_layer_loc: float3", "_decal_layer_loc");
node_shader_add_constant(kong, "decal_layer_dim: float", "_decal_layer_dim");
node_shader_write_frag(kong,
"if (abs(dot(constants.decal_layer_nor, constants.decal_layer_loc - wposition.xyz)) > constants.decal_layer_dim) { discard; }");
node_shader_add_constant(kong, "brush_scale: float", "_brush_scale");
node_shader_write_frag(kong, "tex_coord = uvsp * constants.brush_scale;");
}
if (decal) {
// Tangent-space decal projection
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_add_texture(kong, "gbuffer0_undo", NULL);
node_shader_add_constant(kong, "decal_mask: float4", "_decal_mask");
node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix");
node_shader_add_constant(kong, "aspect_ratio: float", "_aspect_ratio_window");
node_shader_add_constant(kong, "camera_right: float3", "_camera_right");
node_shader_add_constant(kong, "camera_up: float3", "_camera_up");
node_shader_add_constant(kong, "camera_align: float", "_brush_camera_align");
node_shader_write_attrib_frag(kong, "var uvsp: float2 = float2(0.0, 0.0);");
node_shader_write_attrib_frag(kong, "if (constants.camera_align > 0.0) {");
// Project the surface point to screen-space for the planar, camera-aligned decal
node_shader_write_attrib_frag(kong, "var sp4: float4 = constants.VP * float4(wposition.xyz, 1.0);");
node_shader_write_attrib_frag(kong, "var sp: float2 = sp4.xy / sp4.w;");
node_shader_write_attrib_frag(kong, "sp.x = sp.x * 0.5 + 0.5;");
node_shader_write_attrib_frag(kong, "sp.y = 1.0 - (sp.y * 0.5 + 0.5);");
node_shader_write_attrib_frag(kong, "var ca_depth: float = sample_lod(gbufferD, sampler_linear, constants.decal_mask.xy, 0.0).r;");
node_shader_write_attrib_frag(kong, "var ca_coord: float2 = float2(constants.decal_mask.x * 2.0 - 1.0, 1.0 - constants.decal_mask.y * 2.0);");
node_shader_write_attrib_frag(kong, "var ca_homog: float4 = constants.invVP * float4(ca_coord.x, ca_coord.y, ca_depth, 1.0);");
node_shader_write_attrib_frag(kong, "var ca_clip_w: float = 1.0 / ca_homog.w;");
node_shader_write_attrib_frag(kong, "var vp_up_y: float = (constants.VP * float4(constants.camera_up, 0.0)).y;");
node_shader_write_attrib_frag(kong, "uvsp = sp.xy - constants.decal_mask.xy;");
node_shader_write_attrib_frag(kong, "uvsp.x *= constants.aspect_ratio;");
node_shader_write_attrib_frag(kong, "uvsp = uvsp * (ca_clip_w / (vp_up_y * constants.brush_radius));");
node_shader_write_attrib_frag(kong, "}");
node_shader_write_attrib_frag(kong, "else {");
// When mask is active, anchor the decal at the frozen position
node_shader_write_attrib_frag(kong, "var decal_xy: float2 = constants.inp.xy;");
node_shader_write_attrib_frag(kong, "if (constants.decal_mask.z > 0.0) { decal_xy = constants.decal_mask.xy; }");
// Unproject the decal anchor point from the depth buffer
node_shader_write_attrib_frag(kong, "var decal_depth: float = sample_lod(gbufferD, sampler_linear, decal_xy, 0.0).r;");
node_shader_write_attrib_frag(kong, "var decal_wpos4: float4 = float4(float2(decal_xy.x, 1.0 - decal_xy.y) * 2.0 - 1.0, decal_depth, 1.0);");
node_shader_write_attrib_frag(kong, "decal_wpos4 = constants.invVP * decal_wpos4;");
node_shader_write_attrib_frag(kong, "var decal_wpos: float3 = decal_wpos4.xyz / decal_wpos4.w;");
// Decode face normal at anchor point
node_shader_write_attrib_frag(kong, "var dg0: float2 = sample_lod(gbuffer0_undo, sampler_linear, decal_xy, 0.0).rg;");
node_shader_write_attrib_frag(kong, "var dn: float3;");
node_shader_write_attrib_frag(kong, "dn.z = 1.0 - abs(dg0.x) - abs(dg0.y);");
node_shader_write_attrib_frag(
kong, "if (dn.z >= 0.0) { dn.x = dg0.x; dn.y = dg0.y; } else { var fw: float2 = octahedron_wrap(dg0.xy); dn.x = fw.x; dn.y = fw.y; }");
node_shader_write_attrib_frag(kong, "dn = normalize(dn);");
// Build tangent basis
node_shader_write_attrib_frag(kong, "var d_right: float3 = constants.camera_right;");
node_shader_write_attrib_frag(kong, "if (abs(dot(dn, d_right)) > 0.999) { d_right = float3(0.0, 0.0, 1.0); }");
node_shader_write_attrib_frag(kong, "var d_tan: float3 = normalize(d_right - dn * dot(d_right, dn));");
node_shader_write_attrib_frag(kong, "var d_bin: float3 = cross(d_tan, dn);");
node_shader_write_attrib_frag(kong, "var d_offset: float3 = wposition.xyz - decal_wpos;");
node_shader_write_attrib_frag(kong, "var decal_radius: float = constants.brush_radius;");
node_shader_write_attrib_frag(kong, "if (constants.decal_mask.z > 0.0) { decal_radius = constants.decal_mask.w; }");
node_shader_write_attrib_frag(kong, "if (abs(dot(d_offset, dn)) > decal_radius) { discard; }");
node_shader_write_attrib_frag(kong, "uvsp = float2(dot(d_offset, d_tan), dot(d_offset, d_bin));");
node_shader_write_attrib_frag(kong, "uvsp = uvsp / decal_radius * 0.5;");
node_shader_write_attrib_frag(kong, "}");
if (g_context->brush_directional) {
node_shader_add_constant(kong, "brush_direction: float3", "_brush_direction");
node_shader_write_attrib_frag(kong, "if (constants.brush_direction.z == 0.0) { discard; }");
node_shader_write_attrib_frag(kong, "uvsp = float2(uvsp.x * constants.brush_direction.x - uvsp.y * constants.brush_direction.y, uvsp.x * "
"constants.brush_direction.y + uvsp.y * constants.brush_direction.x);");
}
f32 angle = g_context->brush_angle + g_context->brush_nodes_angle;
if (angle != 0.0) {
node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle");
node_shader_write_attrib_frag(kong, "uvsp = float2(uvsp.x * constants.brush_angle.x - uvsp.y * constants.brush_angle.y, uvsp.x * "
"constants.brush_angle.y + uvsp.y * constants.brush_angle.x);");
}
node_shader_add_constant(kong, "brush_scale_x: float", "_brush_scale_x");
node_shader_write_attrib_frag(kong, "uvsp.x *= constants.brush_scale_x;");
node_shader_write_attrib_frag(kong, "uvsp += float2(0.5, 0.5);");
node_shader_write_attrib_frag(kong, "if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) { discard; }");
node_shader_add_constant(kong, "brush_scale: float", "_brush_scale");
node_shader_write_attrib_frag(kong, "tex_coord = uvsp * constants.brush_scale;");
}
if (sculpt_triplanar) {
// Project the world position onto the three axis planes
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_add_constant(kong, "brush_scale: float", "_brush_scale");
// Decode this vertexs world-space surface normal for blending
node_shader_write_frag(kong, "var tri_nv: float = floor(read_undo.a) / 255.0;");
node_shader_write_frag(kong, "var tri_oct: float2 = float2(read_undo.a - floor(read_undo.a), tri_nv) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var tri_nz: float = 1.0 - abs(tri_oct.x) - abs(tri_oct.y);");
node_shader_write_frag(kong, "var tri_nor: float3 = float3(tri_oct.xy, tri_nz);");
node_shader_write_frag(kong, "if (tri_nz < 0.0) { tri_nor.xy = octahedron_wrap(tri_oct.xy); }");
node_shader_write_frag(kong, "tri_nor = normalize((constants.W * float4(normalize(tri_nor), 0.0)).xyz);");
node_shader_write_frag(kong, "var tri_weight: float3 = tri_nor * tri_nor;");
node_shader_write_frag(kong, "var tri_max: float = max(tri_weight.x, max(tri_weight.y, tri_weight.z));");
node_shader_write_frag(kong, "tri_weight = max3(tri_weight - float3(tri_max * 0.75, tri_max * 0.75, tri_max * 0.75), float3(0.0, 0.0, 0.0));");
node_shader_write_frag(kong, "var tex_coord_blend: float3 = tri_weight * (1.0 / (tri_weight.x + tri_weight.y + tri_weight.z));");
node_shader_write_frag(kong, "tex_coord = wposition.yz * constants.brush_scale * 0.5;");
node_shader_write_frag(kong, "var tex_coord1: float2 = wposition.xz * constants.brush_scale * 0.5;");
node_shader_write_frag(kong, "var tex_coord2: float2 = wposition.xy * constants.brush_scale * 0.5;");
f32 sculpt_uv_angle = g_context->layer->fill_material != NULL ? g_context->layer->angle : g_context->brush_angle + g_context->brush_nodes_angle;
if (sculpt_uv_angle != 0.0) {
node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle");
node_shader_write_frag(kong, "tex_coord = float2(tex_coord.x * constants.brush_angle.x - tex_coord.y * constants.brush_angle.y, tex_coord.x * "
"constants.brush_angle.y + tex_coord.y * constants.brush_angle.x);");
node_shader_write_frag(kong, "tex_coord1 = float2(tex_coord1.x * constants.brush_angle.x - tex_coord1.y * constants.brush_angle.y, tex_coord1.x * "
"constants.brush_angle.y + tex_coord1.y * constants.brush_angle.x);");
node_shader_write_frag(kong, "tex_coord2 = float2(tex_coord2.x * constants.brush_angle.x - tex_coord2.y * constants.brush_angle.y, tex_coord2.x * "
"constants.brush_angle.y + tex_coord2.y * constants.brush_angle.x);");
}
parser_material_triplanar = true;
}
// parser_material_parse may add vertex elements
i32 velen = con_paint->data->vertex_elements->length;
shader_out_t *sout = parser_material_parse(g_context->material->canvas, con_paint, kong);
con_paint->data->vertex_elements->length = velen;
parser_material_triplanar = false;
node_shader_write_frag(kong, string_tmp("var disp: float3 = %s;", sout->out_basecol));
if (kong->frag_bposition) {
kong->frag_bposition = false;
node_shader_write_attrib_frag(kong, "var bposition: float3 = wposition.xyz;");
}
node_shader_write_frag(kong, "var basecol: float3 = float3(1.0, 1.0, 1.0);");
node_shader_write_frag(kong, string_tmp("var opacity: float = %s;", sout->out_opacity));
if (g_context->layer->fill_material == NULL) {
node_shader_write_frag(kong, "opacity *= constants.brush_opacity;");
}
else {
node_shader_write_frag(kong, "opacity *= 20.0;");
}
if (g_context->brush_mask_image != NULL && g_context->tool == TOOL_TYPE_DECAL) {
node_shader_add_texture(kong, "texbrushmask", "_texbrushmask");
node_shader_write_frag(kong, "var mask_sample: float4 = sample_lod(texbrushmask, sampler_linear, uvsp, 0.0);");
if (g_context->brush_mask_image_is_alpha) {
node_shader_write_frag(kong, "opacity *= mask_sample.a;");
}
else {
node_shader_write_frag(kong, "opacity *= mask_sample.r * mask_sample.a;");
}
}
else if (g_context->tool == TOOL_TYPE_TEXT) {
node_shader_add_texture(kong, "textexttool", "_textexttool");
node_shader_write_frag(kong, "opacity *= sample_lod(textexttool, sampler_linear, uvsp, 0.0).r;");
}
if (g_context->brush_mask_image != NULL && (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER)) {
node_shader_add_texture(kong, "texbrushmask", "_texbrushmask");
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_add_texture(kong, "gbuffer0_undo", NULL);
node_shader_add_constant(kong, "camera_right: float3", "_camera_right");
node_shader_write_frag(kong, "var mn0: float2 = sample_lod(gbuffer0_undo, sampler_linear, constants.inp.xy, 0.0).rg;");
node_shader_write_frag(kong, "var mn: float3;");
node_shader_write_frag(kong, "mn.z = 1.0 - abs(mn0.x) - abs(mn0.y);");
node_shader_write_frag(
kong, "if (mn.z >= 0.0) { mn.x = mn0.x; mn.y = mn0.y; } else { var mfw: float2 = octahedron_wrap(mn0.xy); mn.x = mfw.x; mn.y = mfw.y; }");
node_shader_write_frag(kong, "mn = normalize(mn);");
node_shader_write_frag(kong, "var mr: float3 = constants.camera_right;");
node_shader_write_frag(kong, "if (abs(dot(mn, mr)) > 0.999) { mr = float3(0.0, 0.0, 1.0); }");
node_shader_write_frag(kong, "var mt: float3 = normalize(mr - mn * dot(mr, mn));");
node_shader_write_frag(kong, "var mb: float3 = cross(mt, mn);");
node_shader_write_frag(kong, "var pa_mask_3d: float3 = wposition.xyz - winp.xyz;");
node_shader_write_frag(kong, "var pa_mask: float2 = float2(dot(pa_mask_3d, mt), dot(pa_mask_3d, mb));");
if (g_context->brush_directional) {
node_shader_add_constant(kong, "brush_direction: float3", "_brush_direction");
node_shader_write_frag(kong, "if (constants.brush_direction.z == 0.0) { discard; }");
node_shader_write_frag(kong, "pa_mask = float2(pa_mask.x * constants.brush_direction.x - pa_mask.y * constants.brush_direction.y, pa_mask.x * "
"constants.brush_direction.y + pa_mask.y * constants.brush_direction.x);");
}
f32 angle = g_context->brush_angle + g_context->brush_nodes_angle;
if (angle != 0.0) {
node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle");
node_shader_write_frag(kong, "pa_mask.xy = float2(pa_mask.x * constants.brush_angle.x - pa_mask.y * constants.brush_angle.y, pa_mask.x * "
"constants.brush_angle.y + pa_mask.y * constants.brush_angle.x);");
}
node_shader_write_frag(kong, "pa_mask = pa_mask / constants.brush_radius * 0.5 + 0.5;");
node_shader_write_frag(kong, "var mask_sample: float4 = sample_lod(texbrushmask, sampler_linear, pa_mask, 0.0);");
if (g_context->brush_mask_image_is_alpha) {
node_shader_write_frag(kong, "opacity *= mask_sample.a;");
}
else {
node_shader_write_frag(kong, "opacity *= mask_sample.r * mask_sample.a;");
}
}
if (g_context->select_active && has_wposition) {
node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix");
node_shader_add_constant(kong, "select_mask: float4", "_select_mask");
node_shader_write_frag(kong, "var select_sp4: float4 = constants.VP * float4(wposition.xyz, 1.0);");
node_shader_write_frag(kong, "var select_sp: float2 = select_sp4.xy / select_sp4.w;");
node_shader_write_frag(kong, "select_sp.x = select_sp.x * 0.5 + 0.5;");
node_shader_write_frag(kong, "select_sp.y = 1.0 - (select_sp.y * 0.5 + 0.5);");
node_shader_write_frag(kong, "if (select_sp.x < constants.select_mask.x || select_sp.x > constants.select_mask.z || select_sp.y < "
"constants.select_mask.y || select_sp.y > constants.select_mask.w) { discard; }");
}
if (g_context->brush_stencil_image != NULL && has_wposition) {
node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix");
node_shader_add_constant(kong, "aspect_ratio: float", "_aspect_ratio_window");
node_shader_add_texture(kong, "texbrushstencil", "_texbrushstencil");
node_shader_add_constant(kong, "texbrushstencil_size: float2", "_size(_texbrushstencil)");
node_shader_add_constant(kong, "stencil_transform: float4", "_stencil_transform");
node_shader_write_frag(kong, "var stencil_sp4: float4 = constants.VP * float4(wposition.xyz, 1.0);");
node_shader_write_frag(kong, "var stencil_sp: float2 = stencil_sp4.xy / stencil_sp4.w;");
node_shader_write_frag(kong, "stencil_sp.x = stencil_sp.x * 0.5 + 0.5;");
node_shader_write_frag(kong, "stencil_sp.y = 1.0 - (stencil_sp.y * 0.5 + 0.5);");
node_shader_write_frag(
kong,
"var stencil_uv: float2 = (stencil_sp.xy - constants.stencil_transform.xy) / constants.stencil_transform.z * float2(constants.aspect_ratio, 1.0);");
node_shader_write_frag(kong, "var stencil_size: float2 = constants.texbrushstencil_size;");
node_shader_write_frag(kong, "var stencil_ratio: float = stencil_size.y / stencil_size.x;");
node_shader_write_frag(kong, "stencil_uv -= float2(0.5 / stencil_ratio, 0.5);");
node_shader_write_frag(kong,
"stencil_uv = float2(stencil_uv.x * cos(constants.stencil_transform.w) - stencil_uv.y * sin(constants.stencil_transform.w),\
stencil_uv.x * sin(constants.stencil_transform.w) + stencil_uv.y * cos(constants.stencil_transform.w));");
node_shader_write_frag(kong, "stencil_uv += float2(0.5 / stencil_ratio, 0.5);");
node_shader_write_frag(kong, "stencil_uv.x *= stencil_ratio;");
node_shader_write_frag(kong, "if (stencil_uv.x < 0.0 || stencil_uv.x > 1.0 || stencil_uv.y < 0.0 || stencil_uv.y > 1.0) { discard; }");
node_shader_write_frag(kong, "var texbrushstencil_sample: float4 = sample_lod(texbrushstencil, sampler_linear, stencil_uv, 0.0);");
if (g_context->brush_stencil_image_is_alpha) {
node_shader_write_frag(kong, "opacity *= texbrushstencil_sample.a;");
}
else {
node_shader_write_frag(kong, "opacity *= texbrushstencil_sample.r * texbrushstencil_sample.a;");
}
}
node_shader_write_frag(kong, "if (opacity == 0.0) { discard; }");
if (particle) {
node_shader_write_frag(kong, "var str: float = clamp(pow(dist * constants.brush_hardness * 0.2, 2.0) / 10.0, 0.0, 1.0) * opacity;");
}
else {
node_shader_write_frag(kong, "var t: float = clamp(dist / constants.brush_radius, 0.0, 1.0);");
node_shader_write_frag(kong, "var t2: float = clamp((t - constants.brush_hardness) / max(1.0 - constants.brush_hardness, 0.001), 0.0, 1.0);");
node_shader_write_frag(kong, "var falloff: float = 1.0 - t2 * t2 * (3.0 - 2.0 * t2);");
node_shader_write_frag(kong, "var str: float = falloff * constants.brush_radius * 0.05 * opacity;");
}
if (decal) {
node_shader_write_frag(kong, "str = str * 0.2;");
}
node_shader_add_texture(kong, "texpaint_sculpt_undo", "_texpaint_sculpt_undo");
node_shader_add_constant(kong, "texpaint_undo_size: float2", "_size(_texpaint_sculpt_undo)");
node_shader_write_frag(kong, "var sample_undo: float4 = sample_lod(texpaint_sculpt_undo, sampler_linear, sculpt_uv, 0.0);");
node_shader_write_frag(kong, "var raw_undo: float4 = texpaint_sculpt_undo[uint2(uint(sculpt_uv.x * constants.texpaint_undo_size.x), uint(sculpt_uv.y * "
"constants.texpaint_undo_size.y))];");
if (g_context->layer->fill_material != NULL || g_context->tool == TOOL_TYPE_FILL) {
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_write_frag(kong, "var nor_v: float = floor(raw_undo.a) / 255.0;");
node_shader_write_frag(kong, "var nor_oct: float2 = float2(raw_undo.a - floor(raw_undo.a), nor_v) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var nor_z: float = 1.0 - abs(nor_oct.x) - abs(nor_oct.y);");
node_shader_write_frag(kong, "var nor_xyz: float3 = float3(nor_oct.xy, nor_z);");
node_shader_write_frag(kong, "if (nor_z < 0.0) { nor_xyz.xy = octahedron_wrap(nor_oct.xy); }");
node_shader_write_frag(kong, "var n: float3 = normalize((constants.W * float4(normalize(nor_xyz), 0.0)).xyz);");
}
else {
node_shader_write_frag(kong, "if (sample_undo.r == 0.0 && sample_undo.g == 0.0 && sample_undo.b == 0.0) { discard; }");
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_add_texture(kong, "gbuffer0_undo", NULL);
if (particle) {
node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix");
node_shader_add_constant(kong, "particle_hit: float3", "_particle_hit");
node_shader_write_frag(kong, "var hit_ndc: float4 = constants.VP * float4(constants.particle_hit, 1.0);");
node_shader_write_frag(kong, "var hit_uv: float2 = hit_ndc.xy / hit_ndc.w;");
node_shader_write_frag(kong, "hit_uv.x = hit_uv.x * 0.5 + 0.5;");
node_shader_write_frag(kong, "hit_uv.y = 1.0 - (hit_uv.y * 0.5 + 0.5);");
node_shader_write_frag(kong, "var g0_undo: float2 = sample_lod(gbuffer0_undo, sampler_linear, hit_uv, 0.0).rg;");
}
else {
node_shader_write_frag(kong, "var g0_undo: float2 = sample_lod(gbuffer0_undo, sampler_linear, constants.inp.xy, 0.0).rg;");
}
node_shader_write_frag(kong, "var wn: float3;");
node_shader_write_frag(kong, "wn.z = 1.0 - abs(g0_undo.x) - abs(g0_undo.y);");
node_shader_write_frag(kong, "if (wn.z >= 0.0) { wn.xy = g0_undo.xy; } else { wn.xy = octahedron_wrap(g0_undo.xy); }");
node_shader_write_frag(kong, "var n: float3 = normalize(wn);");
node_shader_add_constant(kong, "sculpt_symmetry_reflect: float4x4", "_sculpt_symmetry_reflect");
node_shader_write_frag(kong, "n = normalize((constants.sculpt_symmetry_reflect * float4(n, 0.0)).xyz);");
if (g_context->xray) {
node_shader_write_frag(kong, "var xray_nnv: float = floor(raw_undo.a) / 255.0;");
node_shader_write_frag(kong, "var xray_noct: float2 = float2(raw_undo.a - floor(raw_undo.a), xray_nnv) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var xray_nnz: float = 1.0 - abs(xray_noct.x) - abs(xray_noct.y);");
node_shader_write_frag(kong, "var xray_fnor: float3 = float3(xray_noct.xy, xray_nnz);");
node_shader_write_frag(kong, "if (xray_nnz < 0.0) { xray_fnor.xy = octahedron_wrap(xray_noct.xy); }");
node_shader_write_frag(kong, "n = normalize((constants.W * float4(normalize(xray_fnor), 0.0)).xyz);");
}
}
if (g_context->tool == TOOL_TYPE_BLUR) {
// Even out the surface by relaxing each vertex toward the cursors tangent plane
node_shader_write_frag(kong, "var plane_dist: float = dot(wposition.xyz - winp.xyz, n);");
node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb - n * plane_dist * str, raw_undo.a);");
}
else if (g_context->tool == TOOL_TYPE_ERASER) {
node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb - n * disp * str, raw_undo.a);");
}
else if (grab) {
node_shader_write_frag(kong, "var grab_delta: float3 = (winp.xyz - winplast.xyz) * falloff * opacity;");
node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb + grab_delta, raw_undo.a);");
}
else {
node_shader_write_frag(kong, string_tmp("var sculpt_disp: float = %s;", sculpt_blend_mode(kong, g_context->brush_blending, "0.0", "disp.x", "str")));
node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb + n * sculpt_disp, raw_undo.a);");
}
node_shader_write_frag(kong, "output[1] = float4(str, 0.0, 0.0, 1.0);");
parser_material_finalize(con_paint);
con_paint->data->shader_from_source = true;
gpu_create_shaders_from_kong(node_shader_get(kong), &con_paint->data->vertex_shader, &con_paint->data->fragment_shader,
&con_paint->data->_->vertex_shader_size, &con_paint->data->_->fragment_shader_size);
return con_paint;
}
static void sculpt_mesh_write(node_shader_t *kong, bool attrib, char *s) {
if (attrib) {
node_shader_write_attrib_vert(kong, s);
}
else {
node_shader_write_vert(kong, s);
}
}
bool sculpt_layer_has_visible_masks(slot_layer_t *l) {
slot_layer_t_array_t *masks = slot_layer_get_masks(l, true);
if (masks == NULL) {
return false;
}
for (i32 i = 0; i < masks->length; ++i) {
if (slot_layer_is_visible(masks->buffer[i])) {
return true;
}
}
return false;
}
bool sculpt_mask_value(node_shader_t *kong, slot_layer_t *l, char *out_var, bool attrib, slot_layer_t *skip) {
if (!sculpt_layer_has_visible_masks(l)) {
return false;
}
slot_layer_t_array_t *masks = slot_layer_get_masks(l, true);
sculpt_mesh_write(kong, attrib, string_tmp("var %s: float = 1.0;", out_var));
for (i32 i = 0; i < masks->length; ++i) {
slot_layer_t *m = masks->buffer[i];
if (!slot_layer_is_visible(m) || m == skip) {
continue;
}
node_shader_add_texture(kong, string_tmp("texpaint_vert%s", i32_to_string(m->id)), string_tmp("_texpaint_vert%s", i32_to_string(m->id)));
f32 opac = slot_layer_get_opacity(m);
sculpt_mesh_write(kong, attrib,
string_tmp("%s *= lerp(1.0, sample_lod(texpaint_vert%s, sampler_linear, input.tex, 0.0).r, float(%s));", out_var,
i32_to_string(m->id), f32_to_string(opac)));
}
sculpt_mesh_write(kong, attrib, string_tmp("%s = clamp(%s, 0.0, 1.0);", out_var, out_var));
return true;
}
void sculpt_make_mesh_run(node_shader_t *kong, slot_layer_t_array_t *sculpt_layers, i32_array_t *sculpt_indices) {
i32 count = sculpt_layers->length;
if (count == 0) {
return;
}
i32 idx0 = sculpt_indices->buffer[0];
node_shader_add_constant(kong, "WVP: float4x4", "_world_view_proj_matrix");
node_shader_add_constant(kong, "W: float4x4", "_world_matrix");
node_shader_add_constant(kong, "N: float3x3", "_normal_matrix");
// Per-object start index into the shared sculpt grid
node_shader_add_constant(kong, "sculpt_vertex_offset: int", "_sculpt_vertex_offset");
node_shader_add_out(kong, "wnormal: float3");
kong->frag_n = false;
node_shader_add_constant(kong, string_tmp("texpaint_sculpt_size%d: float2", idx0), string_tmp("_size(_texpaint_sculpt%d)", idx0));
for (i32 i = 0; i < count; ++i) {
i32 idx = sculpt_indices->buffer[i];
node_shader_add_texture(kong, string_tmp("texpaint_sculpt%d", idx), string_tmp("_texpaint_sculpt%d", idx));
}
// The base layer holds the absolute mesh position
bool base_masked = sculpt_layer_has_visible_masks(sculpt_layers->buffer[0]);
if (count > 1 || base_masked) {
node_shader_add_texture(kong, "texpaint_sculpt_base", "_texpaint_sculpt_base");
}
// Position
node_shader_write_vert(kong, "var sculpt_vid: uint = uint(vertex_id()) + uint(constants.sculpt_vertex_offset);");
node_shader_write_vert(kong, string_tmp("var sculpt_grid_w: uint = uint(constants.texpaint_sculpt_size%d.x);", idx0));
node_shader_write_vert(kong, "var sculpt_uv: uint2 = uint2(sculpt_vid % sculpt_grid_w, sculpt_vid / sculpt_grid_w);");
node_shader_write_vert(kong, string_tmp("var texpaint_sculpt_sample: float4 = texpaint_sculpt%d[sculpt_uv];", idx0));
node_shader_write_vert(kong, "var sculpt_pos: float3 = texpaint_sculpt_sample.xyz;");
if (sculpt_mask_value(kong, sculpt_layers->buffer[0], "sculpt_pmask0", false, NULL)) {
// Blend the base layers deformation back toward the rest pose where the mask is dark
node_shader_write_vert(kong, "var sculpt_pbase0: float4 = texpaint_sculpt_base[sculpt_uv];");
node_shader_write_vert(kong, "sculpt_pos = sculpt_pbase0.xyz + (sculpt_pos - sculpt_pbase0.xyz) * sculpt_pmask0;");
}
for (i32 i = 1; i < count; ++i) {
i32 idx = sculpt_indices->buffer[i];
node_shader_write_vert(kong, string_tmp("var texpaint_sculpt_sample_%d: float4 = texpaint_sculpt%d[sculpt_uv];", idx, idx));
node_shader_write_vert(kong, string_tmp("var texpaint_sculpt_base_sample_%d: float4 = texpaint_sculpt_base[sculpt_uv];", idx));
if (sculpt_mask_value(kong, sculpt_layers->buffer[i], string_tmp("sculpt_pmask_%d", idx), false, NULL)) {
node_shader_write_vert(
kong,
string_tmp("sculpt_pos = sculpt_pos + (texpaint_sculpt_sample_%d.xyz - texpaint_sculpt_base_sample_%d.xyz) * sculpt_pmask_%d;", idx, idx, idx));
}
else {
node_shader_write_vert(kong, string_tmp("sculpt_pos = sculpt_pos + texpaint_sculpt_sample_%d.xyz - texpaint_sculpt_base_sample_%d.xyz;", idx, idx));
}
}
node_shader_write_vert(kong, "output.pos = constants.WVP * float4(sculpt_pos, 1.0);");
node_shader_write_vert(kong, "output.wposition = (constants.W * float4(sculpt_pos, 1.0)).xyz;");
// Normal
node_shader_write_attrib_vert(kong, "var sculpt_nvid: uint = uint(vertex_id()) + uint(constants.sculpt_vertex_offset);");
node_shader_write_attrib_vert(kong, "var base_vertex0: uint = sculpt_nvid - (sculpt_nvid % uint(3));");
node_shader_write_attrib_vert(kong, "var base_vertex1: uint = base_vertex0 + uint(1);");
node_shader_write_attrib_vert(kong, "var base_vertex2: uint = base_vertex0 + uint(2);");
node_shader_write_attrib_vert(kong, string_tmp("var sculpt_ngrid_w: uint = uint(constants.texpaint_sculpt_size%d.x);", idx0));
node_shader_write_attrib_vert(kong, "var uv0: uint2 = uint2(base_vertex0 % sculpt_ngrid_w, base_vertex0 / sculpt_ngrid_w);");
node_shader_write_attrib_vert(kong, "var uv1: uint2 = uint2(base_vertex1 % sculpt_ngrid_w, base_vertex1 / sculpt_ngrid_w);");
node_shader_write_attrib_vert(kong, "var uv2: uint2 = uint2(base_vertex2 % sculpt_ngrid_w, base_vertex2 / sculpt_ngrid_w);");
node_shader_write_attrib_vert(kong, string_tmp("var meshpos0: float4 = texpaint_sculpt%d[uv0];", idx0));
node_shader_write_attrib_vert(kong, string_tmp("var meshpos1: float4 = texpaint_sculpt%d[uv1];", idx0));
node_shader_write_attrib_vert(kong, string_tmp("var meshpos2: float4 = texpaint_sculpt%d[uv2];", idx0));
for (i32 i = 1; i < count; ++i) {
i32 idx = sculpt_indices->buffer[i];
node_shader_write_attrib_vert(kong, string_tmp("meshpos0 = meshpos0 + texpaint_sculpt%d[uv0] - texpaint_sculpt_base[uv0];", idx));
node_shader_write_attrib_vert(kong, string_tmp("meshpos1 = meshpos1 + texpaint_sculpt%d[uv1] - texpaint_sculpt_base[uv1];", idx));
node_shader_write_attrib_vert(kong, string_tmp("meshpos2 = meshpos2 + texpaint_sculpt%d[uv2] - texpaint_sculpt_base[uv2];", idx));
}
// Unmasked sculpt face normal
node_shader_write_attrib_vert(kong, "var meshnor: float3 = normalize(cross(meshpos2.xyz - meshpos1.xyz, meshpos0.xyz - meshpos1.xyz));");
node_shader_write_attrib_vert(kong, "output.wnormal = constants.N * meshnor;");
// Reconstruct the face normal from the masked world position
node_shader_write_attrib_frag(kong, "var n: float3 = normalize(cross(ddx3(input.wposition), ddy3(input.wposition)));");
node_shader_write_attrib_frag(kong, "if (dot(n, normalize(input.wnormal)) < 0.0) { n = -n; }");
}
void sculpt_make_paint_run(node_shader_t *kong) {
slot_layer_t_array_t *sculpt_layers = any_array_create_from_raw((void *[]){}, 0);
i32_array_t *sculpt_indices = i32_array_create(0);
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (l->texpaint_sculpt != NULL && slot_layer_is_visible(l)) {
any_array_push(sculpt_layers, l);
i32_array_push(sculpt_indices, i);
}
}
i32 scount = sculpt_layers->length;
if (scount > 0) {
i32 idx0 = sculpt_indices->buffer[0];
node_shader_add_constant(kong, string_tmp("texpaint_sculpt_size%d: float2", idx0), string_tmp("_size(_texpaint_sculpt%d)", idx0));
for (i32 i = 0; i < scount; ++i) {
i32 idx = sculpt_indices->buffer[i];
node_shader_add_texture(kong, string_tmp("texpaint_sculpt%d", idx), string_tmp("_texpaint_sculpt%d", idx));
}
bool base_masked = sculpt_layer_has_visible_masks(sculpt_layers->buffer[0]);
if (scount > 1 || base_masked) {
node_shader_add_texture(kong, "texpaint_sculpt_base", "_texpaint_sculpt_base");
}
node_shader_write_attrib_vert(kong, string_tmp("var sculpt_paint_w: uint = uint(constants.texpaint_sculpt_size%d.x);", idx0));
node_shader_write_attrib_vert(kong, "var sculpt_uv_paint: uint2 = uint2(uint(vertex_id()) % sculpt_paint_w, uint(vertex_id()) / sculpt_paint_w);");
node_shader_write_attrib_vert(kong, string_tmp("var sculpt_pos_paint: float4 = texpaint_sculpt%d[sculpt_uv_paint];", idx0));
if (sculpt_mask_value(kong, sculpt_layers->buffer[0], "sculpt_pmask0", true, g_context->layer)) {
node_shader_write_attrib_vert(kong, "var sculpt_pbase0: float4 = texpaint_sculpt_base[sculpt_uv_paint];");
node_shader_write_attrib_vert(kong, "sculpt_pos_paint = sculpt_pbase0 + (sculpt_pos_paint - sculpt_pbase0) * sculpt_pmask0;");
}
for (i32 i = 1; i < scount; ++i) {
i32 idx = sculpt_indices->buffer[i];
if (sculpt_mask_value(kong, sculpt_layers->buffer[i], string_tmp("sculpt_pmask_%d", idx), true, g_context->layer)) {
node_shader_write_attrib_vert(kong, string_tmp("var psamp_%d: float4 = texpaint_sculpt%d[sculpt_uv_paint];", idx, idx));
node_shader_write_attrib_vert(kong, string_tmp("var pbasel_%d: float4 = texpaint_sculpt_base[sculpt_uv_paint];", idx));
node_shader_write_attrib_vert(kong,
string_tmp("sculpt_pos_paint = sculpt_pos_paint + (psamp_%d - pbasel_%d) * sculpt_pmask_%d;", idx, idx, idx));
}
else {
node_shader_write_attrib_vert(
kong, string_tmp("sculpt_pos_paint = sculpt_pos_paint + texpaint_sculpt%d[sculpt_uv_paint] - texpaint_sculpt_base[sculpt_uv_paint];", idx));
}
}
node_shader_write_attrib_vert(kong, "output.ndc = constants.WVP * float4(sculpt_pos_paint.xyz, 1.0);");
if (kong->frag_wposition) {
node_shader_write_attrib_vert(kong, "output.wposition = (constants.W * float4(sculpt_pos_paint.xyz, 1.0)).xyz;");
}
}
array_delete(sculpt_layers);
array_delete(sculpt_indices);
}
void sculpt_init_sculpt_texture(slot_layer_t *l) {
i32 id = l->id;
{
render_target_t *t = render_target_create();
t->name = string("texpaint_sculpt%s", i32_to_string(id));
t->width = config_get_texture_res_x();
t->height = config_get_texture_res_y();
t->format = "RGBA128";
l->texpaint_sculpt = render_path_create_render_target(t)->_image;
}
sculpt_import_mesh_pack_to_texture(l->texpaint_sculpt);
i32 sculpt_layer_count = 0;
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
if (g_project->_->layers->buffer[i]->texpaint_sculpt != NULL) {
sculpt_layer_count++;
}
}
if (any_map_get(render_path_render_targets, "texpaint_sculpt_base") == NULL) {
render_target_t *t = render_target_create();
t->name = "texpaint_sculpt_base";
t->width = config_get_texture_res_x();
t->height = config_get_texture_res_y();
t->format = "RGBA128";
render_path_create_render_target(t);
}
if (sculpt_layer_count == 1) {
render_path_set_target("texpaint_sculpt_base", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target(string_tmp("texpaint_sculpt%s", i32_to_string(id)), "tex");
render_path_draw_shader("Scene/copy_pass/copyRGBA128_pass");
}
}
void sculpt_init_meshes() {
sculpt_ensure_texture_res();
mesh_object_t_array_t *objects = g_project->_->paint_objects;
f32 max_scale = 0.0;
for (i32 o = 0; o < objects->length; ++o) {
if (objects->buffer[o]->data->scale_pos > max_scale) {
max_scale = objects->buffer[o]->data->scale_pos;
}
}
for (i32 o = 0; o < objects->length; ++o) {
mesh_object_t *object = objects->buffer[o];
mesh_data_t *md = object->data;
f32 ratio = objects->length > 1 ? md->scale_pos / max_scale : 1.0;
i16_array_t *posa = i16_array_create(md->index_array->length * 4);
i16_array_t *nora = i16_array_create(md->index_array->length * 2);
i16_array_t *texa = i16_array_create(md->index_array->length * 2);
for (i32 i = 0; i < posa->length; ++i) {
posa->buffer[i] = 32767;
}
for (i32 i = 0; i < md->index_array->length; ++i) {
i32 index = md->index_array->buffer[i];
posa->buffer[i * 4] = math_floor(md->vertex_arrays->buffer[0]->values->buffer[index * 4] * ratio);
posa->buffer[i * 4 + 1] = math_floor(md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 1] * ratio);
posa->buffer[i * 4 + 2] = math_floor(md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 2] * ratio);
posa->buffer[i * 4 + 3] = md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 3];
nora->buffer[i * 2] = md->vertex_arrays->buffer[1]->values->buffer[index * 2];
nora->buffer[i * 2 + 1] = md->vertex_arrays->buffer[1]->values->buffer[index * 2 + 1];
texa->buffer[i * 2] = md->vertex_arrays->buffer[2]->values->buffer[index * 2];
texa->buffer[i * 2 + 1] = md->vertex_arrays->buffer[2]->values->buffer[index * 2 + 1];
}
u32_array_t *inda = u32_array_create(md->index_array->length);
for (i32 i = 0; i < inda->length; ++i) {
inda->buffer[i] = i;
}
mesh_data_t *raw = ALLOC_INIT(mesh_data_t, {.name = md->name,
.vertex_arrays = any_array_create_from_raw(
(void *[]){
ALLOC_INIT(vertex_array_t, {.values = posa, .attrib = "pos", .data = "short4norm"}),
ALLOC_INIT(vertex_array_t, {.values = nora, .attrib = "nor", .data = "short2norm"}),
ALLOC_INIT(vertex_array_t, {.values = texa, .attrib = "tex", .data = "short2norm"}),
},
3),
.index_array = inda,
.scale_pos = 1.0,
.scale_tex = 1.0});
mesh_object_set_data(object, mesh_data_create(raw));
}
if (objects->length > 1) {
f32 ex = 0.0, ey = 0.0, ez = 0.0;
for (i32 o = 0; o < objects->length; ++o) {
vec4_t aabb = mesh_data_calculate_aabb(objects->buffer[o]->data);
ex = fmaxf(ex, aabb.x);
ey = fmaxf(ey, aabb.y);
ez = fmaxf(ez, aabb.z);
}
f32 r = math_sqrt(ex * ex + ey * ey + ez * ez);
mesh_object_t *main_object = context_main_object();
main_object->base->transform->loc = (vec4_t){0, 0, 0, 1.0};
main_object->base->transform->scale = (vec4_t){2.0 / r, 2.0 / r, 2.0 / r, 1.0};
transform_build_matrix(main_object->base->transform);
}
}
void sculpt_init() {
sculpt_init_meshes();
if (g_context->merged_object != NULL) {
util_mesh_merge(NULL);
}
make_material_parse_paint_material(true);
make_material_parse_mesh_material();
if (any_map_get(render_path_render_targets, "gbuffer0_undo") != NULL) {
return;
}
{
render_target_t *t = render_target_create();
t->name = "gbuffer0_undo";
t->width = 0;
t->height = 0;
t->format = "RGBA64";
t->scale = render_path_base_get_super_sampling();
render_path_create_render_target(t);
}
{
render_target_t *t = render_target_create();
t->name = "gbufferD_undo";
t->width = 0;
t->height = 0;
t->format = "R32";
t->scale = render_path_base_get_super_sampling();
render_path_create_render_target(t);
}
{
// Holds the previous-frame sculpt state during a stroke
render_target_t *t = render_target_create();
t->name = "texpaint_sculpt_ref";
t->width = config_get_texture_res_x();
t->height = config_get_texture_res_y();
t->format = "RGBA128";
render_path_create_render_target(t);
}
render_path_load_shader("Scene/copy_pass/copyR32_pass");
for (i32 i = 0; i < history_undo_layers->length; ++i) {
char *ext = string_tmp("_undo%s", i32_to_string(i));
slot_layer_t *ul = history_undo_layers->buffer[i];
render_target_t *t = render_target_create();
t->name = string("texpaint_sculpt%s", ext);
t->width = config_get_texture_res_x();
t->height = config_get_texture_res_y();
t->format = "RGBA128";
ul->texpaint_sculpt = render_path_create_render_target(t)->_image;
}
}
void sculpt_layers_create_sculpt_layer() {
slot_layer_t *l = layers_new_layer(true, -1, NULL);
l->name = string("Sculpt %d", l->id + 1);
sculpt_init_meshes();
sculpt_init_sculpt_texture(l);
sculpt_init();
}
void render_path_sculpt_displace_pass(char *texpaint_sculpt) {
// Snapshot the current state so the displacement pass accumulates on top of the previous one
render_path_set_target("texpaint_sculpt_ref", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target(texpaint_sculpt, "tex");
render_path_draw_shader("Scene/copy_pass/copyRGBA128_pass");
render_path_set_target("texpaint_blend1", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("texpaint_blend0", "tex");
render_path_draw_shader("Scene/copy_pass/copyR8_pass");
string_array_t *additional = any_array_create_from_raw(
(void *[]){
"texpaint_blend0",
},
1);
render_path_set_target(texpaint_sculpt, additional, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("gbufferD_undo", "gbufferD");
if (g_context->xray || g_config->brush_angle_reject) {
render_path_bind_target("gbuffer0", "gbuffer0");
}
render_path_bind_target("texpaint_blend1", "paintmask");
render_path_bind_target("gbuffer0_undo", "gbuffer0_undo");
shader_data_t *mat = g_project->_->paint_objects->buffer[0]->material;
shader_context_t *shader_context = shader_data_get_context(mat, "paint");
gpu_set_pipeline(shader_context->_->pipe);
uniforms_set_context_consts(shader_context, _render_path_bind_params);
uniforms_set_obj_consts(shader_context, g_project->_->paint_objects->buffer[0]->base);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
render_path_end();
}
void render_path_sculpt_commands() {
if (g_context->pdirty <= 0) {
return;
}
i32 tid = g_context->layer->id;
char *texpaint_sculpt = string_tmp("texpaint_sculpt%s", i32_to_string(tid));
if (g_context->tool == TOOL_TYPE_PARTICLE) {
// Accumulate one displacement pass per active particle impact
for (i32 pi = 0; pi < 32; ++pi) {
if (g_context->particles[pi].timer == NULL || g_context->particles[pi].hit_x == 0) {
continue;
}
g_context->particle_index = pi;
g_context->particle_hit_x = g_context->particles[pi].hit_x;
g_context->particle_hit_y = g_context->particles[pi].hit_y;
g_context->particle_hit_z = g_context->particles[pi].hit_z;
g_context->last_particle_hit_x = g_context->particles[pi].hit_last_x;
g_context->last_particle_hit_y = g_context->particles[pi].hit_last_y;
g_context->last_particle_hit_z = g_context->particles[pi].hit_last_z;
render_path_sculpt_displace_pass(texpaint_sculpt);
}
return;
}
render_path_sculpt_displace_pass(texpaint_sculpt);
}
void render_path_sculpt_snapshot_gbuffer() {
render_path_set_target("gbuffer0_undo", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("gbuffer0", "tex");
render_path_draw_shader("Scene/copy_pass/copyRGBA64_pass");
render_path_set_target("gbufferD_undo", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("main", "tex");
render_path_draw_shader("Scene/copy_pass/copyR32_pass");
}
void render_path_sculpt_begin() {
if (!render_path_paint_paint_enabled()) {
return;
}
render_path_paint_push_undo_last = history_push_undo;
if (history_push_undo && history_undo_layers != NULL) {
history_paint();
render_path_sculpt_snapshot_gbuffer();
g_context->grab_start_x = g_context->paint_vec.x;
g_context->grab_start_y = g_context->paint_vec.y;
}
sculpt_push_undo = false;
}
void sculpt_bake_to_mesh() {
slot_layer_t_array_t *sculpt_layers = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (l->texpaint_sculpt != NULL && slot_layer_is_visible(l)) {
any_array_push(sculpt_layers, l);
}
}
i32 count = sculpt_layers->length;
if (count == 0) {
array_delete(sculpt_layers);
return;
}
if (g_context->merged_object == NULL) {
util_mesh_merge(NULL);
}
mesh_data_t *g = g_context->merged_object->data;
i16_array_t *va0 = g->vertex_arrays->buffer[0]->values;
i16_array_t *va1 = g->vertex_arrays->buffer[1]->values;
u32_array_t *ia = g->index_array;
i32 nv = math_floor(va0->length / 4.0);
// The base layer stores absolute positions, higher layers store deltas from the rest pose
buffer_t *base_pixels = gpu_get_texture_pixels(sculpt_layers->buffer[0]->texpaint_sculpt);
buffer_t *rest_pixels = NULL;
buffer_t **layer_pixels = NULL;
if (count > 1) {
render_target_t *rest = any_map_get(render_path_render_targets, "texpaint_sculpt_base");
rest_pixels = gpu_get_texture_pixels(rest->_image);
layer_pixels = malloc(sizeof(buffer_t *) * count);
for (i32 i = 1; i < count; ++i) {
layer_pixels[i] = gpu_get_texture_pixels(sculpt_layers->buffer[i]->texpaint_sculpt);
}
}
f32_array_t *pos = f32_array_create(nv * 3);
f32 max_abs = 1.0;
for (i32 v = 0; v < nv; ++v) {
f32 x = buffer_get_f32(base_pixels, v * 16);
f32 y = buffer_get_f32(base_pixels, v * 16 + 4);
f32 z = buffer_get_f32(base_pixels, v * 16 + 8);
for (i32 i = 1; i < count; ++i) {
x += buffer_get_f32(layer_pixels[i], v * 16) - buffer_get_f32(rest_pixels, v * 16);
y += buffer_get_f32(layer_pixels[i], v * 16 + 4) - buffer_get_f32(rest_pixels, v * 16 + 4);
z += buffer_get_f32(layer_pixels[i], v * 16 + 8) - buffer_get_f32(rest_pixels, v * 16 + 8);
}
pos->buffer[v * 3] = x;
pos->buffer[v * 3 + 1] = y;
pos->buffer[v * 3 + 2] = z;
if (math_abs(x) > max_abs)
max_abs = math_abs(x);
if (math_abs(y) > max_abs)
max_abs = math_abs(y);
if (math_abs(z) > max_abs)
max_abs = math_abs(z);
}
if (count > 1) {
free(layer_pixels);
}
f32 inv = 1.0 / max_abs;
for (i32 v = 0; v < nv; ++v) {
va0->buffer[v * 4] = math_floor(pos->buffer[v * 3] * inv * 32767.0);
va0->buffer[v * 4 + 1] = math_floor(pos->buffer[v * 3 + 1] * inv * 32767.0);
va0->buffer[v * 4 + 2] = math_floor(pos->buffer[v * 3 + 2] * inv * 32767.0);
}
for (i32 f = 0; f < math_floor(ia->length / 3.0); ++f) {
i32 i1 = ia->buffer[f * 3];
i32 i2 = ia->buffer[f * 3 + 1];
i32 i3 = ia->buffer[f * 3 + 2];
vec4_t va = (vec4_t){pos->buffer[i1 * 3], pos->buffer[i1 * 3 + 1], pos->buffer[i1 * 3 + 2], 1.0};
vec4_t vb = (vec4_t){pos->buffer[i2 * 3], pos->buffer[i2 * 3 + 1], pos->buffer[i2 * 3 + 2], 1.0};
vec4_t vc = (vec4_t){pos->buffer[i3 * 3], pos->buffer[i3 * 3 + 1], pos->buffer[i3 * 3 + 2], 1.0};
vec4_t cb = vec4_norm(vec4_cross(vec4_sub(vc, vb), vec4_sub(va, vb)));
i32 nx = math_floor(cb.x * 32767);
i32 ny = math_floor(cb.y * 32767);
i32 nz = math_floor(cb.z * 32767);
va1->buffer[i1 * 2] = nx;
va1->buffer[i1 * 2 + 1] = ny;
va0->buffer[i1 * 4 + 3] = nz;
va1->buffer[i2 * 2] = nx;
va1->buffer[i2 * 2 + 1] = ny;
va0->buffer[i2 * 4 + 3] = nz;
va1->buffer[i3 * 2] = nx;
va1->buffer[i3 * 2 + 1] = ny;
va0->buffer[i3 * 4 + 3] = nz;
}
g->scale_pos = max_abs;
mesh_data_build_vertices(g->_->vertex_buffer, g->vertex_arrays);
render_path_raytrace_ready = false;
array_delete(pos);
free(layer_pixels);
array_delete(sculpt_layers);
}
void slot_layer_apply_sculpt(slot_layer_t *raw) {
if (raw->texpaint_sculpt == NULL) {
return;
}
mesh_object_t_array_t *objects = g_project->_->paint_objects;
buffer_t *pixels = gpu_get_texture_pixels(raw->texpaint_sculpt);
// Each object occupies a contiguous slice of the shared sculpt grid
f32 max_abs = 1.0;
i32 offset = 0;
for (i32 o = 0; o < objects->length; ++o) {
mesh_data_t *g = objects->buffer[o]->data;
i32 nv = math_floor(g->vertex_arrays->buffer[0]->values->length / 4.0);
for (i32 i = 0; i < nv; ++i) {
i32 t = i + offset;
f32 x = math_abs(buffer_get_f32(pixels, t * 16));
f32 y = math_abs(buffer_get_f32(pixels, t * 16 + 4));
f32 z = math_abs(buffer_get_f32(pixels, t * 16 + 8));
if (x > max_abs)
max_abs = x;
if (y > max_abs)
max_abs = y;
if (z > max_abs)
max_abs = z;
}
offset += g->index_array->length;
}
offset = 0;
for (i32 o = 0; o < objects->length; ++o) {
mesh_object_t *ob = objects->buffer[o];
mesh_data_t *g = ob->data;
i16_array_t *va0 = g->vertex_arrays->buffer[0]->values;
i32 nv = math_floor(va0->length / 4.0);
if (max_abs > 1.0) {
ob->base->transform->scale_world = g->scale_pos = g->scale_pos * max_abs;
transform_build_matrix(ob->base->transform);
}
for (i32 i = 0; i < nv; ++i) {
i32 t = i + offset;
va0->buffer[i * 4] = math_floor(buffer_get_f32(pixels, t * 16) / max_abs * 32767.0);
va0->buffer[i * 4 + 1] = math_floor(buffer_get_f32(pixels, t * 16 + 4) / max_abs * 32767.0);
va0->buffer[i * 4 + 2] = math_floor(buffer_get_f32(pixels, t * 16 + 8) / max_abs * 32767.0);
}
mesh_data_build_vertices(g->_->vertex_buffer, g->vertex_arrays);
offset += g->index_array->length;
}
util_mesh_calc_normals(true);
render_path_raytrace_ready = false;
slot_layer_delete(raw);
}