function Node_Mesh_Warp(_x, _y, _group = noone) : Node_Processor(_x, _y, _group) constructor { name = "Mesh Warp"; data = { points : [[]], tris : ds_list_create(), links : ds_list_create() } inputs[| 0] = nodeValue("Surface in", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, 0); inputs[| 1] = nodeValue("Sample", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 8, "Amount of grid subdivision. Higher number means more grid, detail.") .setDisplay(VALUE_DISPLAY.slider, [ 2, 32, 1 ] ); inputs[| 2] = nodeValue("Spring force", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0.5) .setDisplay(VALUE_DISPLAY.slider, [ 0, 1, 0.01 ] ); inputs[| 3] = nodeValue("Mesh", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0) .setDisplay(VALUE_DISPLAY.button, [ function() { setTriangle(); doUpdate(); }, "Generate"] ); inputs[| 4] = nodeValue("Diagonal link", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Include diagonal link to prevent drastic grid deformation."); inputs[| 5] = nodeValue("Active", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, true); active_index = 5; control_index = ds_list_size(inputs); function createControl() { var index = ds_list_size(inputs); inputs[| index] = nodeValue("Control point", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ PUPPET_FORCE_MODE.move, 16, 16, 8, 0, 8, 8]) .setDisplay(VALUE_DISPLAY.puppet_control) array_push(input_display_list, index); return inputs[| index]; } outputs[| 0] = nodeValue("Surface out", self, JUNCTION_CONNECT.output, VALUE_TYPE.surface, noone); outputs[| 1] = nodeValue("Mesh data", self, JUNCTION_CONNECT.output, VALUE_TYPE.object, data); input_display_list = [ 5, ["Mesh", false], 0, 1, 2, 4, 3, ["Control points", false], ]; input_display_index = array_length(input_display_list); tools = [ [ "Add / Remove (+ Shift) control point", THEME.control_add ], [ "Pin / unpin (+ Shift) mesh", THEME.control_pin ] ]; attributes[? "pin"] = ds_map_create(); static onValueFromUpdate = function(index) { if(index == 0 && ds_list_empty(data.tris)) { setTriangle(); } } static drawOverlay = function(active, _x, _y, _s, _mx, _my, _snx, _sny) { for(var i = 0; i < ds_list_size(data.links); i++) { data.links[| i].draw(_x, _y, _s); } for(var i = 0; i < ds_list_size(data.tris); i++) { data.tris[| i].drawPoints(_x, _y, _s); } var hover = -1; for(var i = control_index; i < ds_list_size(inputs); i++) { if(inputs[| i].drawOverlay(active, _x, _y, _s, _mx, _my, _snx, _sny)) hover = i; } var _tool = PANEL_PREVIEW.tool_index; if(!active) return; if(_tool == 0) { if(key_mod_press(SHIFT)) draw_sprite_ui_uniform(THEME.cursor_path_remove, 0, _mx + 16, _my + 16); else draw_sprite_ui_uniform(THEME.cursor_path_add, 0, _mx + 16, _my + 16); if(mouse_press(mb_left)) { if(hover == -1) { var i = createControl(); i.setValue( [PUPPET_FORCE_MODE.move, value_snap(_mx - _x, _snx) / _s, value_snap(_my - _y, _sny) / _s, 0, 0, 8, 8] ); i.drag_type = 2; i.drag_sx = 0; i.drag_sy = 0; i.drag_mx = _mx; i.drag_my = _my; } else if(key_mod_press(SHIFT)) { ds_list_delete(inputs, hover); array_delete(input_display_list, input_display_index + hover - control_index, 1); } reset(); control(input_display_list); } } else if(_tool == 1) { draw_sprite_ui_uniform(key_mod_press(SHIFT)? THEME.cursor_path_remove : THEME.cursor_path_add, 0, _mx + 16, _my + 16); draw_set_color(COLORS._main_accent); var rad = 16; draw_circle(_mx, _my, rad, true); var _xx = (_mx - _x) / _s; var _yy = (_my - _y) / _s; if(mouse_click(mb_left)) { for(var j = 0; j < ds_list_size(data.tris); j++) { var t = data.tris[| j]; if(point_in_circle(t.p0.x, t.p0.y, _xx, _yy, rad / _s)) t.p0.setPin(!key_mod_press(SHIFT)); if(point_in_circle(t.p1.x, t.p1.y, _xx, _yy, rad / _s)) t.p1.setPin(!key_mod_press(SHIFT)); if(point_in_circle(t.p2.x, t.p2.y, _xx, _yy, rad / _s)) t.p2.setPin(!key_mod_press(SHIFT)); } } } } function point(node, index, _x, _y) constructor { self.index = index; self.node = node; x = _x; y = _y; xp = x; yp = y; ndx = 0; ndy = 0; sx = x; sy = y; pin = ds_map_exists(node.attributes[? "pin"], index); static reset = function() { x = sx; y = sy; xp = x; yp = y; } static draw = function(_x, _y, _s) { if(pin) { draw_set_color(COLORS._main_accent); draw_circle(_x + x * _s, _y + y * _s, 3, false); } else { draw_set_color(COLORS.node_overlay_gizmo_inactive); draw_circle(_x + x * _s, _y + y * _s, 2, false); } } u = 0; v = 0; static mapTexture = function(ww, hh) { u = x / ww; v = y / hh; } static move = function(dx, dy) { if(pin) return; x += dx; y += dy; } static planMove = function(dx, dy) { if(pin) return; ndx += dx; ndy += dy; } static stepMove = function(rat) { if(pin) return; move(ndx * rat, ndy * rat); } static clearMove = function(rat) { if(pin) return; ndx = 0; ndy = 0; } static setPin = function(pin) { if(!pin && ds_map_exists(node.attributes[? "pin"], index)) ds_map_delete(node.attributes[? "pin"], index); if(pin && !ds_map_exists(node.attributes[? "pin"], index)) ds_map_add(node.attributes[? "pin"], index, 1); self.pin = pin; } } function link(_p0, _p1) constructor { p0 = _p0; p1 = _p1; k = 1; len = point_distance(p0.x, p0.y, p1.x, p1.y); static resolve = function() { var _len = point_distance(p0.x, p0.y, p1.x, p1.y); var _dir = point_direction(p0.x, p0.y, p1.x, p1.y); var f = k * (_len - len); var dx = lengthdir_x(f, _dir); var dy = lengthdir_y(f, _dir); p0.move( dx / 2, dy / 2); p1.move(-dx / 2, -dy / 2); } static draw = function(_x, _y, _s) { draw_set_color(c_red); draw_line(_x + p0.x * _s, _y + p0.y * _s, _x + p1.x * _s, _y + p1.y * _s); } } function triangle(_p0, _p1, _p2) constructor { p0 = _p0; p1 = _p1; p2 = _p2; static reset = function() { p0.reset(); p1.reset(); p2.reset(); } static initSurface = function(surf) { p0.mapTexture(surface_get_width(surf), surface_get_height(surf)); p1.mapTexture(surface_get_width(surf), surface_get_height(surf)); p2.mapTexture(surface_get_width(surf), surface_get_height(surf)); } static drawSurface = function(surf) { draw_set_color(c_white); draw_set_alpha(1); draw_primitive_begin_texture(pr_trianglelist, surface_get_texture(surf)); draw_vertex_texture(p0.x, p0.y, p0.u, p0.v); draw_vertex_texture(p1.x, p1.y, p1.u, p1.v); draw_vertex_texture(p2.x, p2.y, p2.u, p2.v); draw_primitive_end(); } static drawPoints = function(_x, _y, _s) { //draw_set_color(c_white); //draw_triangle(_x + p0.x * _s, _y + p0.y * _s, _x + p1.x * _s, _y + p1.y * _s, _x + p2.x * _s, _y + p2.y * _s, false) p0.draw(_x, _y, _s); p1.draw(_x, _y, _s); p2.draw(_x, _y, _s); } static contain = function(p) { return p == p0 || p == p1 || p == p2; } } static regularTri = function(surf) { var sample = inputs[| 1].getValue(); var spring = inputs[| 2].getValue(); var diagon = inputs[| 4].getValue(); if(!inputs[| 0].value_from) return; var useArray = is_array(surf); var ww = useArray? surface_get_width(surf[0]) : surface_get_width(surf); var hh = useArray? surface_get_height(surf[0]) : surface_get_height(surf); var gw = ww / sample; var gh = hh / sample; if(!useArray) { var cont = surface_create_valid(ww, hh); surface_set_target(cont); shader_set(sh_content_sampler); var uniform_dim = shader_get_uniform(sh_content_sampler, "dimension"); var uniform_sam = shader_get_uniform(sh_content_sampler, "sampler"); shader_set_uniform_f_array_safe(uniform_dim, [ww, hh]); shader_set_uniform_f_array_safe(uniform_sam, [gw, gh]); draw_surface_safe(surf, 0, 0); shader_reset(); surface_reset_target(); } data.points = [[]]; ds_list_clear(data.tris); ds_list_clear(data.links); var ind = 0; for(var i = 0; i <= sample; i++) for(var j = 0; j <= sample; j++) { var fill = false; if(useArray) { fill = true; } else { var c0 = surface_getpixel(cont, j * gw, i * gh); var c1 = surface_getpixel(cont, j * gw - 1, i * gh); var c2 = surface_getpixel(cont, j * gw, i * gh - 1); var c3 = surface_getpixel(cont, j * gw - 1, i * gh - 1); fill = c0 + c1 + c2 + c3 > 0; } if(fill) { data.points[i][j] = new point(self, ind++, min(j * gw, ww), min(i * gh, hh)); if(i == 0) continue; if(j && data.points[i - 1][j] != 0 && data.points[i][j - 1] != 0) ds_list_add(data.tris, new triangle(data.points[i - 1][j], data.points[i][j - 1], data.points[i][j])); if(j < sample && data.points[i - 1][j] != 0 && data.points[i - 1][j + 1] != 0) ds_list_add(data.tris, new triangle(data.points[i - 1][j], data.points[i - 1][j + 1], data.points[i][j])); } else data.points[i][j] = 0; } for(var i = 0; i <= sample; i++) for(var j = 0; j <= sample; j++) { if(data.points[i][j] == 0) continue; if(i && data.points[i - 1][j] != 0) { ds_list_add(data.links, new link(data.points[i][j], data.points[i - 1][j])); } if(j && data.points[i][j - 1] != 0) { ds_list_add(data.links, new link(data.points[i][j], data.points[i][j - 1])); } if(diagon) { if(i && j && data.points[i - 1][j - 1] != 0) { var l = new link(data.points[i][j], data.points[i - 1][j - 1]); l.k = spring; ds_list_add(data.links, l); } if(i && j < sample && data.points[i - 1][j + 1] != 0) { var l = new link(data.points[i][j], data.points[i - 1][j + 1]); l.k = spring; ds_list_add(data.links, l); } } } if(!useArray) surface_free(cont); } static reset = function() { for(var i = 0; i < ds_list_size(data.tris); i++) data.tris[| i].reset(); } static setTriangle = function() { var _inSurf = inputs[| 0].getValue(); regularTri(_inSurf); for(var i = 0; i < ds_list_size(data.tris); i++) data.tris[| i].initSurface(is_array(_inSurf)? _inSurf[0] : _inSurf); } static affectPoint = function(c, p) { var mode = c[PUPPET_CONTROL.mode]; var cx = c[PUPPET_CONTROL.cx]; var cy = c[PUPPET_CONTROL.cy]; var fx = c[PUPPET_CONTROL.fx]; var fy = c[PUPPET_CONTROL.fy]; var cw = c[PUPPET_CONTROL.width]; var ch = c[PUPPET_CONTROL.height]; switch(mode) { case PUPPET_FORCE_MODE.move: var dis = point_distance(cx, cy, p.x, p.y); var inf = clamp(1 - dis / cw, 0, 1); inf = ease_cubic_inout(inf); p.planMove(fx * inf, fy * inf); break; case PUPPET_FORCE_MODE.pinch: var dis = point_distance(cx, cy, p.x, p.y); var inf = power(clamp(1 - dis / cw, 0, 1), 2) / 2; var dir = point_direction(p.x, p.y, cx, cy); p.planMove(lengthdir_x(inf, dir) * ch, lengthdir_y(inf, dir) * ch); break; case PUPPET_FORCE_MODE.inflate: var dis = point_distance(cx, cy, p.x, p.y); var inf = power(clamp(1 - dis / cw, 0, 1), 2) / 2; var dir = point_direction(cx, cy, p.x, p.y); p.planMove(lengthdir_x(inf, dir) * ch, lengthdir_y(inf, dir) * ch); break; case PUPPET_FORCE_MODE.wind: var lx0 = cx + lengthdir_x(1000, fy); var ly0 = cy + lengthdir_y(1000, fy); var lx1 = cx - lengthdir_x(1000, fy); var ly1 = cy - lengthdir_y(1000, fy); var dist = distance_to_line(p.x, p.y, lx0, ly0, lx1, ly1); var inf = clamp(1 - dist / cw, 0, 1); inf = ease_cubic_inout(inf); p.planMove(lengthdir_x(fx * inf, fy), lengthdir_y(fx * inf, fy)); break; } } static control = function() { for(var i = control_index; i < ds_list_size(inputs); i++) { var c = inputs[| i].getValue(); for(var j = 0; j < ds_list_size(data.tris); j++) { affectPoint(c, data.tris[| j].p0); affectPoint(c, data.tris[| j].p1); affectPoint(c, data.tris[| j].p2); } } var it = PREF_MAP[? "verlet_iteration"]; var resit = it; var _rat = power(1 / it, 2); repeat(it) { for(var j = 0; j < ds_list_size(data.tris); j++) { var t = data.tris[| j]; t.p0.stepMove(_rat); t.p1.stepMove(_rat); t.p2.stepMove(_rat); } repeat(resit) { for(var i = 0; i < ds_list_size(data.links); i++) data.links[| i].resolve(); } } for(var j = 0; j < ds_list_size(data.tris); j++) { var t = data.tris[| j]; t.p0.clearMove(); t.p1.clearMove(); t.p2.clearMove(); } } static process_data = function(_outSurf, _data, _output_index, _array_index) { var _inSurf = _data[0]; if(!is_surface(_inSurf)) return _outSurf; reset(); control(); _outSurf = surface_verify(_outSurf, surface_get_width(_inSurf), surface_get_height(_inSurf)); surface_set_target(_outSurf); draw_clear_alpha(0, 0); for(var i = 0; i < ds_list_size(data.tris); i++) data.tris[| i].drawSurface(_inSurf); surface_reset_target(); return _outSurf; } static postDeserialize = function() { var _inputs = load_map[? "inputs"]; for(var i = control_index; i < ds_list_size(_inputs); i++) { createControl(); inputs[| i].deserialize(_inputs[| i]); } } static attributeSerialize = function() { var att = ds_map_create(); ds_map_add_map(att, "pin", attributes[? "pin"]); return att; } static attributeDeserialize = function(attr) { if(ds_map_exists(attr, "pin")) attributes[? "pin"] = ds_map_clone(attr[? "pin"]); } }