diff --git a/paint/sources/base.c b/paint/sources/base.c index b5648b7b..c2624a1d 100644 --- a/paint/sources/base.c +++ b/paint/sources/base.c @@ -1128,8 +1128,8 @@ void ui_base_update(void *_) { } else { g_context->brush_radius += mouse_movement_x / 150.0; - g_context->brush_radius = math_max(0.01, math_min(4.0, g_context->brush_radius)); - g_context->brush_radius = math_round(g_context->brush_radius * 100) / 100.0; + g_context->brush_radius = math_max(0.01, math_min(4.0, g_context->brush_radius)); + g_context->brush_radius = math_round(g_context->brush_radius * 100) / 100.0; } ui_header_handle->redraws = 2; } @@ -1223,13 +1223,13 @@ void ui_base_update(void *_) { } else if (operator_shortcut(any_map_get(config_keymap, "brush_radius_decrease"), SHORTCUT_TYPE_REPEAT)) { g_context->brush_radius -= ui_base_get_radius_increment(); - g_context->brush_radius = math_max(math_round(g_context->brush_radius * 100) / 100.0, 0.01); - ui_header_handle->redraws = 2; + g_context->brush_radius = math_max(math_round(g_context->brush_radius * 100) / 100.0, 0.01); + ui_header_handle->redraws = 2; } else if (operator_shortcut(any_map_get(config_keymap, "brush_radius_increase"), SHORTCUT_TYPE_REPEAT)) { g_context->brush_radius += ui_base_get_radius_increment(); - g_context->brush_radius = math_round(g_context->brush_radius * 100) / 100.0; - ui_header_handle->redraws = 2; + g_context->brush_radius = math_round(g_context->brush_radius * 100) / 100.0; + ui_header_handle->redraws = 2; } else if (decal_mask) { if (operator_shortcut(string("%s+%s", any_map_get(config_keymap, "decal_mask"), any_map_get(config_keymap, "brush_radius_decrease")), @@ -1396,56 +1396,88 @@ void ui_base_update(void *_) { physics_world_t *world = physics_world_active; physics_world_update(world); - if (g_context->particle_timer != NULL) { + for (i32 i = 0; i < 32; ++i) { + if (g_context->particles[i].timer != NULL && g_context->particles[i].timer->delay <= 0) { + physics_body_remove(g_context->particles[i].body); + mesh_object_remove((mesh_object_t *)g_context->particles[i].bullet->ext); + memset(&g_context->particles[i], 0, sizeof(g_context->particles[i])); + } + } + + bool any_active = false; + for (i32 i = 0; i < 32; ++i) { + if (g_context->particles[i].timer != NULL) { + any_active = true; + break; + } + } + if (any_active) { g_context->ddirty = 2; g_context->rdirty = 2; iron_delay_idle_sleep(); } + bool particle_just_fired = false; if (mouse_started("left") && context_in_paint_area() && !g_context->paint2d) { - if (g_context->particle_timer != NULL) { - tween_stop(g_context->particle_timer); - tween_anim_t *timer = g_context->particle_timer; - timer->done(timer->done_data); - g_context->particle_timer = NULL; + + i32 slot = -1; + for (i32 i = 0; i < 32; ++i) { + if (g_context->particles[i].timer == NULL) { + slot = i; + break; + } } - history_push_undo = true; - g_context->particle_hit_x = g_context->particle_hit_y = g_context->particle_hit_z = 0; - object_t *o = scene_spawn_object(".Sphere", NULL, true); - mesh_object_t *mo = o->ext; - mo->base->name = ".Bullet"; - mo->base->visible = true; + if (slot >= 0) { + history_push_undo = true; + g_context->brush_blend_dirty = true; - camera_object_t *camera = scene_camera; - transform_t *ct = camera->base->transform; - mo->base->transform->loc = (vec4_t){transform_world_x(ct), transform_world_y(ct), transform_world_z(ct), 1.0}; - mo->base->transform->scale = (vec4_t){g_context->brush_radius * 0.2, g_context->brush_radius * 0.2, g_context->brush_radius * 0.2, 1.0}; - transform_build_matrix(mo->base->transform); + object_t *o = scene_spawn_object(".Sphere", NULL, true); + mesh_object_t *mo = o->ext; + mo->base->name = ".Bullet"; + mo->base->visible = true; - physics_body_t *body = physics_body_create(); - body->shape = PHYSICS_SHAPE_SPHERE; - body->mass = 1.0; - physics_body_init(body, mo->base); + camera_object_t *camera = scene_camera; + transform_t *ct = camera->base->transform; + mo->base->transform->loc = (vec4_t){transform_world_x(ct), transform_world_y(ct), transform_world_z(ct), 1.0}; + mo->base->transform->scale = (vec4_t){g_context->brush_radius * 0.2, g_context->brush_radius * 0.2, g_context->brush_radius * 0.2, 1.0}; + transform_build_matrix(mo->base->transform); - ray_t *ray = raycast_get_ray(mouse_view_x(), mouse_view_y(), camera); - physics_body_apply_impulse(body, vec4_mult(ray->dir, 0.15)); + physics_body_t *body = physics_body_create(); + body->shape = PHYSICS_SHAPE_SPHERE; + body->mass = 1.0; + physics_body_init(body, mo->base); - g_context->particle_timer = tween_timer(5, &mesh_object_remove, mo); + ray_t *ray = raycast_get_ray(mouse_view_x(), mouse_view_y(), camera); + physics_body_apply_impulse(body, vec4_mult(ray->dir, 0.12)); + + g_context->particles[slot].body = body; + g_context->particles[slot].bullet = mo->base; + g_context->particles[slot].timer = tween_timer(3, NULL, NULL); + particle_just_fired = true; + } } #ifdef arm_physics - physics_pair_t_array_t *pairs = physics_world_get_contact_pairs(world, g_context->paint_body); - if (pairs != NULL) { - for (i32 i = 0; i < pairs->length; ++i) { - physics_pair_t *p = pairs->buffer[i]; - g_context->last_particle_hit_x = g_context->particle_hit_x != 0 ? g_context->particle_hit_x : p->pos_a_x; - g_context->last_particle_hit_y = g_context->particle_hit_y != 0 ? g_context->particle_hit_y : p->pos_a_y; - g_context->last_particle_hit_z = g_context->particle_hit_z != 0 ? g_context->particle_hit_z : p->pos_a_z; - g_context->particle_hit_x = p->pos_a_x; - g_context->particle_hit_y = p->pos_a_y; - g_context->particle_hit_z = p->pos_a_z; - g_context->pdirty = 1; - break; // 1 pair for now + if (!particle_just_fired) { + for (i32 i = 0; i < 32; ++i) { + if (g_context->particles[i].timer == NULL) { + continue; + } + physics_pair_t_array_t *pairs = physics_world_get_contact_pairs(world, g_context->particles[i].body); + if (pairs != NULL && pairs->length > 0) { + physics_pair_t *p = pairs->buffer[0]; + g_context->particles[i].hit_last_x = g_context->particles[i].hit_x != 0 ? g_context->particles[i].hit_x : p->pos_a_x; + g_context->particles[i].hit_last_y = g_context->particles[i].hit_y != 0 ? g_context->particles[i].hit_y : p->pos_a_y; + g_context->particles[i].hit_last_z = g_context->particles[i].hit_z != 0 ? g_context->particles[i].hit_z : p->pos_a_z; + g_context->particles[i].hit_x = p->pos_a_x; + g_context->particles[i].hit_y = p->pos_a_y; + g_context->particles[i].hit_z = p->pos_a_z; + g_context->particles[i].hit_nor_x = p->nor_x; + g_context->particles[i].hit_nor_y = p->nor_y; + g_context->particles[i].hit_nor_z = p->nor_z; + g_context->particles[i].contact_time += sys_delta(); + g_context->pdirty = 1; + } } } #endif @@ -1727,7 +1759,7 @@ void base_init() { project_filepath = start_arm; gc_root(project_filepath); base_start_arm_found = true; - args_player = true; + args_player = true; } if (args_player) { diff --git a/paint/sources/functions.h b/paint/sources/functions.h index 6765f04a..ad29f2fa 100644 --- a/paint/sources/functions.h +++ b/paint/sources/functions.h @@ -104,7 +104,7 @@ void config_disable_plugin(char *f); bool config_is_iphone(); physics_body_t *physics_body_create(); void physics_body_init(physics_body_t *body, object_t *obj); -void physics_body_remove(i32 uid); +void physics_body_remove(physics_body_t *body); void physics_body_apply_impulse(physics_body_t *body, vec4_t dir); void physics_body_sync_transform(physics_body_t *body); void physics_body_update(physics_body_t *body); @@ -320,6 +320,7 @@ void tab_fonts_draw(ui_handle_t *htab); physics_world_t *physics_world_create(); void physics_world_update(physics_world_t *world); physics_pair_t_array_t *physics_world_get_contact_pairs(physics_world_t *world, physics_body_t *body); +float physics_world_get_speed(physics_world_t *world, physics_body_t *body); void ui_menu_render(); void ui_menu_draw(void (*commands)(void), i32 x, i32 y); void ui_menu_separator(); @@ -393,7 +394,7 @@ void sim_update(); void sim_play(); void sim_stop(); void sim_add_body(object_t *o, physics_shape_t shape, f32 mass); -void sim_remove_body(i32 uid); +void sim_remove_body(physics_body_t *pb); void sim_duplicate(); void sim_delete(); ui_node_link_t *parser_material_get_input_link(ui_node_socket_t *inp); diff --git a/paint/sources/make_particle.c b/paint/sources/make_particle.c index 3fc62143..477f0441 100644 --- a/paint/sources/make_particle.c +++ b/paint/sources/make_particle.c @@ -7,14 +7,13 @@ void make_particle_mask(node_shader_t *kong) { node_shader_write_attrib_vert(kong, "output.wpos = constants.W * float4(input.pos.xyz, 1.0);"); 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 pa: float3 = input.wpos.xyz - constants.particle_hit;"); node_shader_write_frag(kong, "var ba: float3 = constants.particle_hit_last - constants.particle_hit;"); - 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) * 10.0;"); - + node_shader_write_frag(kong, "var h: float = clamp(dot(pa, ba) / max(dot(ba, ba), 0.00000001), 0.0, 1.0);"); + node_shader_write_frag(kong, "dist = length(pa - ba * h) * (5.0 / constants.particle_radius);"); node_shader_write_frag(kong, "if (dist > 1.0) { discard; }"); node_shader_write_frag(kong, "var str: float = clamp(pow(1.0 / dist * constants.brush_hardness * 0.2, 4.0), 0.0, 1.0) * opacity;"); - node_shader_write_frag(kong, "if (constants.particle_hit.x == 0.0 && constants.particle_hit.y == 0.0 && constants.particle_hit.z == 0.0) { str = 0.0; }"); node_shader_write_frag(kong, "if (str == 0.0) { discard; }"); } diff --git a/paint/sources/physics_body.c b/paint/sources/physics_body.c index b1881491..c234886a 100644 --- a/paint/sources/physics_body.c +++ b/paint/sources/physics_body.c @@ -53,12 +53,11 @@ void physics_body_init(physics_body_t *body, object_t *obj) { body->_body = asim_body_create(body->shape, body->mass, body->dimx, body->dimy, body->dimz, loc.x, loc.y, loc.z, posa, inda, scale_pos); } -void physics_body_remove(i32 uid) { - physics_body_t *body = any_imap_get(physics_body_object_map, uid); +void physics_body_remove(physics_body_t *body) { if (body == NULL) { return; } - imap_delete(physics_body_object_map, uid); + imap_delete(physics_body_object_map, body->obj->uid); asim_body_remove(body->_body); } diff --git a/paint/sources/physics_sim.c b/paint/sources/physics_sim.c index 720cfca5..1f637c04 100644 --- a/paint/sources/physics_sim.c +++ b/paint/sources/physics_sim.c @@ -97,8 +97,8 @@ void sim_add_body(object_t *o, physics_shape_t shape, f32 mass) { physics_body_init(body, o); } -void sim_remove_body(i32 uid) { - physics_body_remove(uid); +void sim_remove_body(physics_body_t *pb) { + physics_body_remove(pb); } void sim_duplicate() { @@ -127,5 +127,6 @@ void sim_delete() { mesh_object_t *so = g_context->selected_object->ext; array_remove(project_paint_objects, so); mesh_object_remove(so); - sim_remove_body(so->base->uid); + physics_body_t *pb = any_imap_get(physics_body_object_map, so->base->uid); + sim_remove_body(pb); } diff --git a/paint/sources/physics_world.c b/paint/sources/physics_world.c index 012c1fb7..279eec94 100644 --- a/paint/sources/physics_world.c +++ b/paint/sources/physics_world.c @@ -25,9 +25,15 @@ void physics_world_update(physics_world_t *world) { physics_pair_t_array_t *physics_world_get_contact_pairs(physics_world_t *world, physics_body_t *body) { physics_pair_t_array_t *pairs = any_array_create_from_raw((void *[]){}, 0); - physics_pair_t *p = asim_world_get_contact(); - any_array_push(pairs, p); + physics_pair_t *p = asim_world_get_contact(body->_body); + if (p->pos_a_x != 0 || p->pos_a_y != 0 || p->pos_a_z != 0) { + any_array_push(pairs, p); + } return pairs; } +float physics_world_get_speed(physics_world_t *world, physics_body_t *body) { + return asim_body_get_speed(body->_body); +} + #endif diff --git a/paint/sources/render_path_paint.c b/paint/sources/render_path_paint.c index 1fa86279..50970063 100644 --- a/paint/sources/render_path_paint.c +++ b/paint/sources/render_path_paint.c @@ -102,7 +102,7 @@ void render_path_paint_init() { } void render_path_paint_draw_fullscreen_triangle(char *ctx) { - // Note that vertices are mangled in vertex shader to form a fullscreen triangle, + // Vertices are mangled in vertex shader to form a fullscreen triangle, // so plane transform does not matter mesh_object_t *plane = scene_get_child(".Plane")->ext; bool _visible = plane->base->visible; @@ -112,6 +112,60 @@ void render_path_paint_draw_fullscreen_triangle(char *ctx) { render_path_end(); } +void render_path_paint_commands_particle(i32 tid, char *texpaint, bool is_mask) { + // Hide bullets to avoid self-painting + for (i32 _pi = 0; _pi < 32; ++_pi) { + if (g_context->particles[_pi].bullet != NULL) { + g_context->particles[_pi].bullet->visible = false; + } + } + // Paint pass per particle + 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; + if (is_mask) { + i32 ptid = g_context->layer->parent->id; + if (slot_layer_is_group(g_context->layer->parent)) { + for (i32 i = 0; i < slot_layer_get_children(g_context->layer->parent)->length; ++i) { + slot_layer_t *c = slot_layer_get_children(g_context->layer->parent)->buffer[i]; + ptid = c->id; + break; + } + } + string_array_t *additional = + any_array_create_from_raw((void *[]){string("texpaint_nor%d", ptid), string("texpaint_pack%d", ptid), "texpaint_blend0"}, 3); + render_path_set_target(texpaint, additional, NULL, GPU_CLEAR_NONE, 0, 0.0); + } + else { + string_array_t *additional = + any_array_create_from_raw((void *[]){string("texpaint_nor%d", tid), string("texpaint_pack%d", tid), "texpaint_blend0"}, 3); + render_path_set_target(texpaint, additional, NULL, GPU_CLEAR_NONE, 0, 0.0); + } + render_path_bind_target("main", "gbufferD"); + if (g_context->xray || g_config->brush_angle_reject) { + render_path_bind_target("gbuffer0", "gbuffer0"); + } + render_path_bind_target("texpaint_blend1", "paintmask"); + if (g_context->colorid_picked) { + render_path_bind_target("texpaint_colorid", "texpaint_colorid"); + } + render_path_draw_meshes("paint"); + } + for (i32 _pi = 0; _pi < 32; ++_pi) { + if (g_context->particles[_pi].bullet != NULL) { + g_context->particles[_pi].bullet->visible = true; + } + } +} + void render_path_paint_commands_paint(bool dilation) { i32 tid = g_context->layer->id; @@ -336,51 +390,64 @@ void render_path_paint_commands_paint(bool dilation) { render_path_bind_target("texpaint_blend0", "tex"); render_path_draw_shader("Scene/copy_pass/copyR8_pass"); bool is_mask = slot_layer_is_mask(g_context->layer); - if (is_mask) { - i32 ptid = g_context->layer->parent->id; - if (slot_layer_is_group(g_context->layer->parent)) { // Group mask - for (i32 i = 0; i < slot_layer_get_children(g_context->layer->parent)->length; ++i) { - slot_layer_t *c = slot_layer_get_children(g_context->layer->parent)->buffer[i]; - ptid = c->id; - break; - } - } - string_array_t *additional = any_array_create_from_raw( - (void *[]){ - string("texpaint_nor%d", ptid), - string("texpaint_pack%d", ptid), - "texpaint_blend0", - }, - 3); - render_path_set_target(texpaint, additional, NULL, GPU_CLEAR_NONE, 0, 0.0); - } - else { - string_array_t *additional = any_array_create_from_raw( - (void *[]){ - string("texpaint_nor%d", tid), - string("texpaint_pack%d", tid), - "texpaint_blend0", - }, - 3); - render_path_set_target(texpaint, additional, NULL, GPU_CLEAR_NONE, 0, 0.0); - } - render_path_bind_target("main", "gbufferD"); - if (g_context->xray || g_config->brush_angle_reject) { - render_path_bind_target("gbuffer0", "gbuffer0"); - } - render_path_bind_target("texpaint_blend1", "paintmask"); - if (g_context->colorid_picked) { - render_path_bind_target("texpaint_colorid", "texpaint_colorid"); - } // Read texcoords from gbuffer bool read_tc = (g_context->tool == TOOL_TYPE_FILL && g_context->fill_type_handle->i == FILL_TYPE_FACE) || g_context->tool == TOOL_TYPE_CLONE || g_context->tool == TOOL_TYPE_BLUR || g_context->tool == TOOL_TYPE_SMUDGE; - if (read_tc) { - render_path_bind_target("gbuffer2", "gbuffer2"); - } - render_path_draw_meshes("paint"); + if (g_context->tool == TOOL_TYPE_PARTICLE) { + render_path_paint_commands_particle(tid, texpaint, is_mask); + } + else { + if (is_mask) { + i32 ptid = g_context->layer->parent->id; + if (slot_layer_is_group(g_context->layer->parent)) { // Group mask + for (i32 i = 0; i < slot_layer_get_children(g_context->layer->parent)->length; ++i) { + slot_layer_t *c = slot_layer_get_children(g_context->layer->parent)->buffer[i]; + ptid = c->id; + break; + } + } + string_array_t *additional = any_array_create_from_raw( + (void *[]){ + string("texpaint_nor%d", ptid), + string("texpaint_pack%d", ptid), + "texpaint_blend0", + }, + 3); + render_path_set_target(texpaint, additional, NULL, GPU_CLEAR_NONE, 0, 0.0); + } + else { + string_array_t *additional = any_array_create_from_raw( + (void *[]){ + string("texpaint_nor%d", tid), + string("texpaint_pack%d", tid), + "texpaint_blend0", + }, + 3); + render_path_set_target(texpaint, additional, NULL, GPU_CLEAR_NONE, 0, 0.0); + } + render_path_bind_target("main", "gbufferD"); + if (g_context->xray || g_config->brush_angle_reject) { + render_path_bind_target("gbuffer0", "gbuffer0"); + } + render_path_bind_target("texpaint_blend1", "paintmask"); + if (g_context->colorid_picked) { + render_path_bind_target("texpaint_colorid", "texpaint_colorid"); + } + if (read_tc) { + render_path_bind_target("gbuffer2", "gbuffer2"); + } + object_t *bullet = scene_get_child(".Bullet"); + bool bullet_vis = bullet != NULL && bullet->visible; + if (bullet != NULL) { + bullet->visible = false; + } + render_path_draw_meshes("paint"); + if (bullet != NULL) { + bullet->visible = bullet_vis; + } + } if (g_context->tool == TOOL_TYPE_BAKE && g_context->bake_type == BAKE_TYPE_CURVATURE && g_context->bake_curv_smooth > 0) { if (any_map_get(render_path_render_targets, "texpaint_blur") == NULL) { diff --git a/paint/sources/types.h b/paint/sources/types.h index 2b1129d6..078f3334 100644 --- a/paint/sources/types.h +++ b/paint/sources/types.h @@ -382,8 +382,23 @@ typedef struct context { f32 last_particle_hit_x; f32 last_particle_hit_y; f32 last_particle_hit_z; - struct tween_anim *particle_timer; struct physics_body *paint_body; + struct { + f32 hit_x; + f32 hit_y; + f32 hit_z; + f32 hit_last_x; + f32 hit_last_y; + f32 hit_last_z; + f32 hit_nor_x; + f32 hit_nor_y; + f32 hit_nor_z; + f32 contact_time; + struct tween_anim *timer; + struct physics_body *body; + struct object *bullet; + } particles[32]; + i32 particle_index; } context_t; typedef struct node_shader { diff --git a/paint/sources/ui_header.c b/paint/sources/ui_header.c index aa0c1d76..a9b37e3e 100644 --- a/paint/sources/ui_header.c +++ b/paint/sources/ui_header.c @@ -475,7 +475,7 @@ void ui_header_draw_tool_properties() { vars)); } - if (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_CLONE || decal_mask) { + if (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_CLONE || decal_mask || g_context->tool == TOOL_TYPE_PARTICLE) { ui_handle_t *h = ui_handle(__ID__); if (h->init) { h->f = g_context->brush_hardness; diff --git a/paint/sources/uniforms_ext.c b/paint/sources/uniforms_ext.c index 50f5995c..e9fd4225 100644 --- a/paint/sources/uniforms_ext.c +++ b/paint/sources/uniforms_ext.c @@ -89,6 +89,15 @@ f32 uniforms_ext_f32_link(object_t *object, material_data_t *mat, char *link) { else if (string_equals(link, "_dilate_radius")) { return util_uv_dilatemap != NULL ? g_config->dilate_radius : 0.0; } + else if (string_equals(link, "_particle_radius")) { + i32 idx = g_context->particle_index; + f32 speed = (physics_world_active != NULL && g_context->particles[idx].body != NULL) + ? physics_world_get_speed(physics_world_active, g_context->particles[idx].body) + : 0.0f; + f32 vel_scale = fminf(speed / 0.12f, 1.0f); + f32 contact_scale = 1.0f - fminf(g_context->particles[idx].contact_time, 1.0f); + return fmaxf(g_context->brush_radius * vel_scale * contact_scale, 0.1f); + } else if (string_equals(link, "_decal_layer_dim")) { vec4_t sc = mat4_get_scale(g_context->layer->decal_mat); return sc.z * 0.5;