diff --git a/Pixels Composer.yyp b/Pixels Composer.yyp index c1db80807..1d5e64288 100644 --- a/Pixels Composer.yyp +++ b/Pixels Composer.yyp @@ -22,7 +22,6 @@ {"id":{"name":"s_node_crop","path":"sprites/s_node_crop/s_node_crop.yy",},"order":26,}, {"id":{"name":"s_cursor_path_add","path":"sprites/s_cursor_path_add/s_cursor_path_add.yy",},"order":0,}, {"id":{"name":"s_node_counter","path":"sprites/s_node_counter/s_node_counter.yy",},"order":0,}, - {"id":{"name":"node_fracture","path":"scripts/node_fracture/node_fracture.yy",},"order":8,}, {"id":{"name":"node_2d_light","path":"scripts/node_2d_light/node_2d_light.yy",},"order":40,}, {"id":{"name":"s_minus_24","path":"sprites/s_minus_24/s_minus_24.yy",},"order":17,}, {"id":{"name":"node_dither","path":"scripts/node_dither/node_dither.yy",},"order":11,}, @@ -72,6 +71,7 @@ {"id":{"name":"icon_24","path":"sprites/icon_24/icon_24.yy",},"order":0,}, {"id":{"name":"s_node_sampler","path":"sprites/s_node_sampler/s_node_sampler.yy",},"order":98,}, {"id":{"name":"sh_polar","path":"shaders/sh_polar/sh_polar.yy",},"order":31,}, + {"id":{"name":"sh_pixel_cloud","path":"shaders/sh_pixel_cloud/sh_pixel_cloud.yy",},"order":13,}, {"id":{"name":"sh_de_stray","path":"shaders/sh_de_stray/sh_de_stray.yy",},"order":17,}, {"id":{"name":"sh_cell_noise_round","path":"shaders/sh_cell_noise_round/sh_cell_noise_round.yy",},"order":9,}, {"id":{"name":"event_recorder","path":"scripts/event_recorder/event_recorder.yy",},"order":4,}, @@ -268,7 +268,7 @@ {"id":{"name":"sh_bevel","path":"shaders/sh_bevel/sh_bevel.yy",},"order":3,}, {"id":{"name":"s_node_color_out","path":"sprites/s_node_color_out/s_node_color_out.yy",},"order":6,}, {"id":{"name":"node_sprite_stack","path":"scripts/node_sprite_stack/node_sprite_stack.yy",},"order":4,}, - {"id":{"name":"node_particle_sampler","path":"scripts/node_particle_sampler/node_particle_sampler.yy",},"order":6,}, + {"id":{"name":"node_fracture","path":"scripts/node_fracture/node_fracture.yy",},"order":6,}, {"id":{"name":"sh_shape","path":"shaders/sh_shape/sh_shape.yy",},"order":3,}, {"id":{"name":"draw_line_width2","path":"scripts/draw_line_width2/draw_line_width2.yy",},"order":2,}, {"id":{"name":"s_node_3d_cube","path":"sprites/s_node_3d_cube/s_node_3d_cube.yy",},"order":28,}, @@ -336,6 +336,7 @@ {"id":{"name":"s_node_palette","path":"sprites/s_node_palette/s_node_palette.yy",},"order":74,}, {"id":{"name":"sh_erode","path":"shaders/sh_erode/sh_erode.yy",},"order":20,}, {"id":{"name":"s_node_atlas","path":"sprites/s_node_atlas/s_node_atlas.yy",},"order":112,}, + {"id":{"name":"node_pixel_cloud","path":"scripts/node_pixel_cloud/node_pixel_cloud.yy",},"order":19,}, {"id":{"name":"sh_alpha_grey","path":"shaders/sh_alpha_grey/sh_alpha_grey.yy",},"order":9,}, {"id":{"name":"s_transparent","path":"sprites/s_transparent/s_transparent.yy",},"order":1,}, {"id":{"name":"o_dialog_animation","path":"objects/o_dialog_animation/o_dialog_animation.yy",},"order":9,}, @@ -531,6 +532,7 @@ {"id":{"name":"node_alpha_to_grey","path":"scripts/node_alpha_to_grey/node_alpha_to_grey.yy",},"order":21,}, {"id":{"name":"o_dialog_preference","path":"objects/o_dialog_preference/o_dialog_preference.yy",},"order":15,}, {"id":{"name":"node_wrap","path":"scripts/node_wrap/node_wrap.yy",},"order":2,}, + {"id":{"name":"s_node_pixel_cloud","path":"sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy",},"order":117,}, {"id":{"name":"o_dialog_about","path":"objects/o_dialog_about/o_dialog_about.yy",},"order":13,}, {"id":{"name":"s_node_shadow","path":"sprites/s_node_shadow/s_node_shadow.yy",},"order":68,}, {"id":{"name":"s_node_bg","path":"sprites/s_node_bg/s_node_bg.yy",},"order":1,}, diff --git a/objects/o_dialog_keyframe_curve/Create_0.gml b/objects/o_dialog_keyframe_curve/Create_0.gml index de1bdfddf..e2392677c 100644 --- a/objects/o_dialog_keyframe_curve/Create_0.gml +++ b/objects/o_dialog_keyframe_curve/Create_0.gml @@ -16,6 +16,5 @@ event_inherited(); } editWidget = new curveBox( - function(_modified) { value_target.inter_curve = _modified; }, - function(type) { value_target.curve_type = type; }); + function(_modified) { value_target.inter_curve = _modified; }); #endregion \ No newline at end of file diff --git a/objects/o_dialog_keyframe_curve/Draw_64.gml b/objects/o_dialog_keyframe_curve/Draw_64.gml index 505ac248a..371307af1 100644 --- a/objects/o_dialog_keyframe_curve/Draw_64.gml +++ b/objects/o_dialog_keyframe_curve/Draw_64.gml @@ -17,6 +17,6 @@ if !ready exit; editWidget.active = FOCUS == self; editWidget.hover = HOVER == self; editWidget.draw(dialog_x + 16, dialog_y + 48, dialog_w - 32, dialog_h - 48 - 16, - value_target.inter_curve, value_target.curve_type, [mouse_mx, mouse_my]); + value_target.inter_curve, [mouse_mx, mouse_my]); } #endregion \ No newline at end of file diff --git a/objects/o_main/Create_0.gml b/objects/o_main/Create_0.gml index bb0fb4bce..88bafdae6 100644 --- a/objects/o_main/Create_0.gml +++ b/objects/o_main/Create_0.gml @@ -14,7 +14,7 @@ file_text_close(f); #endregion -display_reset(0, 1); +display_reset(4, 1); #region window depth = 0; diff --git a/scripts/curveBox/curveBox.gml b/scripts/curveBox/curveBox.gml index 12fb19186..c21a32b87 100644 --- a/scripts/curveBox/curveBox.gml +++ b/scripts/curveBox/curveBox.gml @@ -1,6 +1,5 @@ -function curveBox(_onModify, _onTypeModify) constructor { +function curveBox(_onModify) constructor { onModify = _onModify; - typeModify = _onTypeModify; active = false; hover = false; @@ -14,45 +13,13 @@ function curveBox(_onModify, _onTypeModify) constructor { return _y + _h * clamp((y_max - val) / y_range, 0, 1); } - function draw(_x, _y, _w, _h, _data, _type, _m) { + function draw(_x, _y, _w, _h, _data, _m) { static curve_amo = 3; var curve_h = _h - 32; - #region type - var _tw = 48; - var _th = 24; - var _ty = _y + _h -_th; - - var _gh = 16; - var _gy = _ty + 4; - - for( var i = 0; i < curve_amo; i++ ) { - var _tx = _x + (_tw + 8) * i; - - draw_set_color(i == _type? c_ui_blue_white : c_ui_blue_grey); - draw_rectangle(_tx, _ty, _tx + _tw, _ty + _th, 1); - - draw_set_color(c_ui_blue_ltgrey); - switch(i) { - case CURVE_TYPE.bezier : draw_line_bezier_cubic(_tx, _gy, _tw, -_gh, 0, 0, 1, 1); break; - case CURVE_TYPE.bounce : draw_line_bounce(_tx, _gy, _tw, -_gh, 0, 0.5, 0.5, 1); break; - case CURVE_TYPE.damping : draw_line_damping(_tx, _gy, _tw, -_gh, 0, 0.5, 0.5, 1); break; - } - - if(active && point_in_rectangle(_m[0], _m[1], _tx, _ty, _tx + _tw, _ty + _th)) { - if(mouse_check_button_pressed(mb_left)) - typeModify(i); - } - } - #endregion - #region curve var _range; - switch(_type) { - case CURVE_TYPE.bezier : _range = bezier_range(_data[0], _data[1], _data[2], _data[3]); break; - case CURVE_TYPE.bounce : _range = bounce_range(_data[0], _data[1], _data[2], _data[3]); break; - case CURVE_TYPE.damping : _range = damp_range(_data[0], _data[1], _data[2], _data[3]); break; - } + _range = bezier_range(_data[0], _data[1], _data[2], _data[3]); var y_min = min(0, _range[0]); var y_max = max(1, _range[1]); var y_range = y_max - y_min; @@ -90,11 +57,7 @@ function curveBox(_onModify, _onTypeModify) constructor { var _dh = -curve_h / y_range; draw_set_color(c_ui_blue_ltgrey); - switch(_type) { - case CURVE_TYPE.bezier : draw_line_bezier_cubic(_x, _dy, _w, _dh, _y0, _y1, _y2, _y3); break; - case CURVE_TYPE.bounce : draw_line_bounce(_x, _dy, _w, _dh, _y0, _y1, _y2, _y3); break; - case CURVE_TYPE.damping : draw_line_damping(_x, _dy, _w, _dh, _y0, _y1, _y2, _y3); break; - } + draw_line_bezier_cubic(_x, _dy, _w, _dh, _y0, _y1, _y2, _y3); var node_hovering = -1; for(var i = 0; i < 4; i++) { diff --git a/scripts/node_fracture/node_fracture.gml b/scripts/node_fracture/node_fracture.gml index cbf96ec3f..90f051c2d 100644 --- a/scripts/node_fracture/node_fracture.gml +++ b/scripts/node_fracture/node_fracture.gml @@ -6,10 +6,7 @@ function Node_create_Fracture(_x, _y) { function Node_Fracture(_x, _y) : Node(_x, _y) constructor { name = "Fracture"; + auto_update = false; + use_cache = true; - inputs[| 0] = nodeValue(0, "Surface in", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, 0); - //inputs[| 0] = nodeValue(0, "Surface in", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, 0); - - outputs[| 0] = nodeValue(0, "Surface out", self, JUNCTION_CONNECT.output, VALUE_TYPE.surface, surface_create(1, 1)); - } \ No newline at end of file diff --git a/scripts/node_particle_sampler/node_particle.yy b/scripts/node_fracture/node_particle.yy similarity index 100% rename from scripts/node_particle_sampler/node_particle.yy rename to scripts/node_fracture/node_particle.yy diff --git a/scripts/node_gradient/node_gradient.gml b/scripts/node_gradient/node_gradient.gml index b7079fcd2..84a709a44 100644 --- a/scripts/node_gradient/node_gradient.gml +++ b/scripts/node_gradient/node_gradient.gml @@ -10,7 +10,8 @@ function Node_Gradient(_x, _y) : Node(_x, _y) constructor { uniform_grad_blend = shader_get_uniform(sh_gradient, "gradient_blend"); uniform_grad = shader_get_uniform(sh_gradient, "gradient_color"); uniform_grad_time = shader_get_uniform(sh_gradient, "gradient_time"); - uniform_key = shader_get_uniform(sh_gradient, "keys"); + uniform_grad_key = shader_get_uniform(sh_gradient, "gradient_keys"); + uniform_type = shader_get_uniform(sh_gradient, "type"); uniform_center = shader_get_uniform(sh_gradient, "center"); @@ -90,8 +91,9 @@ function Node_Gradient(_x, _y) : Node(_x, _y) constructor { shader_set_uniform_i(uniform_grad_blend, ds_list_get(_gra_data, 0)); shader_set_uniform_f_array(uniform_grad, _grad_color); shader_set_uniform_f_array(uniform_grad_time, _grad_time); + shader_set_uniform_i(uniform_grad_key, ds_list_size(_gra)); + shader_set_uniform_f_array(uniform_center, [_cnt[0] / _dim[0], _cnt[1] / _dim[1]]); - shader_set_uniform_i(uniform_key, ds_list_size(_gra)); shader_set_uniform_i(uniform_type, _typ); shader_set_uniform_f(uniform_angle, degtorad(_ang)); diff --git a/scripts/node_particle/node_particle.gml b/scripts/node_particle/node_particle.gml index e1773a061..094c9e964 100644 --- a/scripts/node_particle/node_particle.gml +++ b/scripts/node_particle/node_particle.gml @@ -37,9 +37,6 @@ function __part() constructor { life = 0; life_total = 0; - surf_w = 1; - surf_h = 1; - anim_speed = 1; is_loop = false; @@ -53,12 +50,8 @@ function __part() constructor { life = _life; life_total = life; - - if(is_array(_surf)) { - surf_w = surface_get_width(_surf[0]); - surf_h = surface_get_height(_surf[0]); - } } + function setPhysic(_sx, _sy, _ac, _g, _wig) { sx = _sx; sy = _sy; @@ -123,12 +116,11 @@ function __part() constructor { var ss = surf; if(is_array(surf)) ss = surf[safe_mod((life_total - life) * anim_speed, array_length(surf))]; - - if(!ss) return; + if(!is_surface(ss)) return; var cc = (col == -1)? c_white : gradient_eval(col, 1 - life / life_total); - var s_w = surf_w * scx; - var s_h = surf_h * scy; + var s_w = surface_get_width(ss) * scx; + var s_h = surface_get_height(ss) * scy; var _pp = point_rotate(-s_w / 2, -s_h / 2, 0, 0, rot); _pp[0] = x + _pp[0]; _pp[1] = y + _pp[1]; @@ -353,6 +345,7 @@ function Node_Particle(_x, _y) : Node(_x, _y) constructor { parts[| i].setPhysic(_vx, _vy, _acc, _grav, _wigg); parts[| i].setTransform(_scx, _scy, _scale_speed[0], _scale_speed[1], _rot, _rotation_speed, _follow); parts[| i].setDraw(_color, _alp, _fade); + setUpPart(parts[| i]); spawn_index = safe_mod(spawn_index + 1, PREF_MAP[? "part_max_amount"]); if(_loop && ANIMATOR.current_frame + _lif > ANIMATOR.frames_total) @@ -364,6 +357,8 @@ function Node_Particle(_x, _y) : Node(_x, _y) constructor { } } + function setUpPart(part) {} + function reset() { spawn_index = 0; for(var i = 0; i < PREF_MAP[? "part_max_amount"]; i++) { @@ -447,8 +442,12 @@ function Node_Particle(_x, _y) : Node(_x, _y) constructor { static drawOverlay = function(_active, _x, _y, _s, _mx, _my) { inputs[| 4].drawOverlay(_active, _x, _y, _s, _mx, _my); + if(onDrawOverlay != -1) + onDrawOverlay(_active, _x, _y, _s, _mx, _my); } + static onDrawOverlay = -1; + function render() { var _dim = inputs[| 1].getValue(); var _exact = inputs[| 19].getValue(); diff --git a/scripts/node_particle_sampler/node_particle_sampler.gml b/scripts/node_particle_sampler/node_particle_sampler.gml deleted file mode 100644 index 5356bb28c..000000000 --- a/scripts/node_particle_sampler/node_particle_sampler.gml +++ /dev/null @@ -1,313 +0,0 @@ -function Node_create_Particle_Sampler(_x, _y) { - var node = new Node_Particle_Sampler(_x, _y); - ds_list_add(PANEL_GRAPH.nodes_list, node); - return node; -} - -function __part_sampler() : __part() constructor { - sample = -1; - sam_x = 0; - sam_y = 0; - sam_w = 1; - sam_h = 1; - - function setSample(_sample, _sam_x, _sam_y, _sam_w, _sam_h) { - sample = _sample; - sam_x = _sam_x; - sam_y = _sam_y; - sam_w = _sam_w; - sam_h = _sam_h; - } - - function draw() { - if(!active) return; - if(col == -1) return; - if(surf) { - var cc = gradient_eval(col, 1 - life / life_total); - var _pp = point_rotate(x + sam_w * (1 - scx) / 2, y + sam_h * (1 - scy) / 2, x + sam_w * scx / 2, y + sam_h * scy / 2, rot); - - draw_surface_general(surf, sam_x, sam_y, sam_w, sam_h, _pp[0], _pp[1], scx, scy, rot, cc, cc, cc, cc, alp); - } - } -} - -function Node_Particle_Sampler(_x, _y) : Node(_x, _y) constructor { - name = "Particle Sampler"; - auto_update = false; - - use_cache = true; - - inputs[| 0] = nodeValue(0, "Sample surface", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, 0); - inputs[| 1] = nodeValue(1, "Particle shape", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, 0); - inputs[| 2] = nodeValue(2, "Particle size", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 32, 32 ] ) - .setDisplay(VALUE_DISPLAY.vector); - - inputs[| 3] = nodeValue(3, "Spawn delay", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 100); - inputs[| 4] = nodeValue(4, "Spawn amount", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 16); - inputs[| 5] = nodeValue(5, "Spawn area", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 16, 16, 16, 16, AREA_SHAPE.rectangle ]) - .setDisplay(VALUE_DISPLAY.area); - inputs[| 6] = nodeValue(6, "Spawn distribution", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0) - .setDisplay(VALUE_DISPLAY.enum_scroll, [ "Uniform", "Random", "Border" ]) - .setVisible(false); - - inputs[| 7] = nodeValue(7, "Lifespan", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 20, 30 ]) - .setDisplay(VALUE_DISPLAY.range); - - inputs[| 8] = nodeValue(8, "Velocity", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0, 0, 0 ]) - .setDisplay(VALUE_DISPLAY.vector_range); - inputs[| 9] = nodeValue(9, "Acceleration", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ]) - .setDisplay(VALUE_DISPLAY.vector); - - inputs[| 10] = nodeValue(10, "Spawn angle", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [0, 0]) - .setDisplay(VALUE_DISPLAY.rotation_range); - inputs[| 11] = nodeValue(11, "Rotational speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0); - - inputs[| 12] = nodeValue(12, "Spawn scale", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1, 1, 1 ] ) - .setDisplay(VALUE_DISPLAY.vector_range); - inputs[| 13] = nodeValue(13, "Scaling speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] ) - .setDisplay(VALUE_DISPLAY.vector); - - inputs[| 14] = nodeValue(14, "Color over lifetime", self, JUNCTION_CONNECT.input, VALUE_TYPE.color, c_white) - .setDisplay(VALUE_DISPLAY.gradient); - inputs[| 15] = nodeValue(15, "Alpha", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ]) - .setDisplay(VALUE_DISPLAY.range); - inputs[| 16] = nodeValue(16, "Alpha fading", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0); - - inputs[| 17] = nodeValue(17, "Point at center", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false) - .setVisible(false); - - inputs[| 18] = nodeValue(18, "Spawn type", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0) - .setDisplay(VALUE_DISPLAY.enum_button, [ "Stream", "Burst" ]) - .setVisible(false); - - inputs[| 19] = nodeValue(19, "Spawn size", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ] ) - .setDisplay(VALUE_DISPLAY.range); - - inputs[| 20] = nodeValue(20, "Output dimension", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, def_surf_size2, VALUE_TAG.dimension_2d) - .setDisplay(VALUE_DISPLAY.vector) - .setVisible(false, false); - - inputs[| 21] = nodeValue(21, "Output dimension", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, OUTPUT_SCALING.same_as_input) - .setDisplay(VALUE_DISPLAY.enum_scroll, [ "Same as sample", "Constant" ]) - .setVisible(false); - - inputs[| 22] = nodeValue(22, "Full surface", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0) - .setDisplay(VALUE_DISPLAY.button, [ function() { - render(); - - var _outSurf = outputs[| 0].getValue(); - var _w = surface_get_width(_outSurf); - var _h = surface_get_height(_outSurf); - - inputs[| 5].setValue([ _w / 2, _h / 2, _w / 2, _h / 2, AREA_SHAPE.rectangle ]); - }, "Full surface"] ); - - input_display_list = [ 0, 1, - ["Output", true], 21, 20, - ["Spawn", false], 18, 3, 4, 5, 22, 6, 7, - ["Movement", false], 8, 9, - ["Transform", false], 10, 11, 12, 19, 13, - ["Color", false], 14, 15, 16 - ]; - - outputs[| 0] = nodeValue(0, "Surface out", self, JUNCTION_CONNECT.output, VALUE_TYPE.surface, surface_create(1, 1)); - - def_surface = -1; - - parts = ds_list_create(); - for(var i = 0; i < PREF_MAP[? "part_max_amount"]; i++) - ds_list_add(parts, new __part_sampler()); - outputs[| 1] = nodeValue(1, "Particle data", self, JUNCTION_CONNECT.output, VALUE_TYPE.object, parts ); - - function spawn() { - var _inSurf = inputs[| 0].getValue(); - if(!is_surface(_inSurf)) return; - var _samSurf = inputs[| 1].getValue(); - if(!is_surface(_samSurf)) { - if(def_surface == -1 || !surface_exists(def_surface)) { - def_surface = surface_create(3, 3); - surface_set_target(def_surface); - draw_clear(c_white); - surface_reset_target(); - } - _samSurf = def_surface; - } - - var sam_w = surface_get_width(_samSurf); - var sam_h = surface_get_height(_samSurf); - - var _spawn_amount = inputs[| 4].getValue(); - var _amo = _spawn_amount; - - var _spawn_area = inputs[| 5].getValue(); - var _dist = inputs[| 6].getValue(); - var _life = inputs[| 7].getValue(); - var _velocity = inputs[| 8].getValue(); - var _accel = inputs[| 9].getValue(); - - var _point = inputs[| 17].getValue(); - var _rotation = inputs[| 10].getValue(); - var _rotation_speed = inputs[| 11].getValue(); - var _scale = inputs[| 12].getValue(); - var _size = inputs[| 19].getValue(); - var _scale_speed = inputs[| 13].getValue(); - - var _color = inputs[| 14].getValue(); - var _alpha = inputs[| 15].getValue(); - var _fade = inputs[| 16].getValue(); - - if(_rotation[1] < _rotation[0]) _rotation[1] += 360; - - - for(var i = 0; i < PREF_MAP[? "part_max_amount"]; i++) { - if(!parts[| i].active) { - var _spr = is_array(_inSurf)? _inSurf[irandom(array_length(_inSurf) - 1)] : _inSurf; - var xx, yy; - - var sp = area_get_random_point(_spawn_area, _dist, spawn_index, _spawn_amount); - xx = sp[0]; - yy = sp[1]; - - - var _lif = random_range(_life[0], _life[1]); - - var _rot = (_point? point_direction(_spawn_area[0], _spawn_area[1], xx, yy) : 0) + random_range(_rotation[0], _rotation[1]); - var _vx = random_range(_velocity[0], _velocity[1]); - var _vy = random_range(_velocity[2], _velocity[3]); - - var _ss = random_range(_size[0], _size[1]); - var _scx = random_range(_scale[0], _scale[1]) * _ss; - var _scy = random_range(_scale[2], _scale[3]) * _ss; - - var _alp = random_range(_alpha[0], _alpha[1]); - - parts[| i].create(_spr, xx, yy, _lif); - parts[| i].setSample(_samSurf, xx, yy, sam_w, sam_h); - parts[| i].setPhysic(_vx, _vy, _accel[0], _accel[1]); - parts[| i].setTransform(_scx, _scy, _scale_speed[0], _scale_speed[1], _rot, _rotation_speed); - parts[| i].setDraw(_color, _alp, _fade); - spawn_index = safe_mod(spawn_index + 1, PREF_MAP[? "part_max_amount"]); - if(_amo-- <= 0) - return; - } - } - } - - function reset() { - spawn_index = 0; - for(var i = 0; i < PREF_MAP[? "part_max_amount"]; i++) - parts[| i].kill(); - render(); - } - - function updateParticle() { - var jun = outputs[| 1]; - for(var j = 0; j < ds_list_size(jun.value_to); j++) { - if(jun.value_to[| j].value_from == jun) { - jun.value_to[| j].node.updateParticle(); - } - } - - render(); - } - - function resetPartPool() { - var _part_amo = PREF_MAP[? "part_max_amount"]; - if(_part_amo > ds_list_size(parts)) { - repeat(_part_amo - ds_list_size(parts)) { - ds_list_add(parts, new __part_sampler()); - } - } else if(_part_amo < ds_list_size(parts)) { - repeat(ds_list_size(parts) - _part_amo) { - ds_list_delete(parts, 0); - } - } - } - - function step() { - resetPartPool(); - var _spawn_type = inputs[| 18].getValue(); - if(_spawn_type == 0) { - inputs[| 3].name = "Spawn delay"; - } else { - inputs[| 3].name = "Spawn frame"; - } - - var _spawn_delay = inputs[| 3].getValue(); - - if(ANIMATOR.is_playing && ANIMATOR.frame_progress) { - if(ANIMATOR.current_frame == 0) reset(); - - if(_spawn_type == 0) { - if(safe_mod(ANIMATOR.current_frame, _spawn_delay) == 0) - spawn(); - } else if(_spawn_type == 1) { - if(ANIMATOR.current_frame == _spawn_delay) - spawn(); - } - - for(var i = 0; i < PREF_MAP[? "part_max_amount"]; i++) - parts[| i].step(); - updateParticle(); - - updateForward(); - } - - if(ANIMATOR.is_scrubing) { - recoverCache(); - } - } - - drag_type = -1; - drag_sx = 0; - drag_sy = 0; - drag_mx = 0; - drag_my = 0; - - static drawOverlay = function(_active, _x, _y, _s, _mx, _my) { - inputs[| 5].drawOverlay(_active, _x, _y, _s, _mx, _my); - - var out_type = inputs[| 21].getValue(); - switch(out_type) { - case OUTPUT_SCALING.same_as_input : - inputs[| 20].show_in_inspector = false; - break; - case OUTPUT_SCALING.constant : - inputs[| 20].show_in_inspector = true; - break; - } - } - - function render() { - var _inSurf = inputs[| 0].getValue(); - if(!is_surface(_inSurf)) return; - - var _dim = inputs[| 20].getValue(); - var out_type = inputs[| 21].getValue(); - - var _outSurf = outputs[| 0].getValue(); - - switch(out_type) { - case OUTPUT_SCALING.same_as_input : - surface_size_to(_outSurf, surface_valid(surface_get_width(_inSurf)), surface_valid(surface_get_height(_inSurf))); - break; - case OUTPUT_SCALING.constant : - surface_size_to(_outSurf, surface_valid(_dim[0]), surface_valid(_dim[1])); - break; - } - - surface_set_target(_outSurf); - draw_clear_alpha(0, 0); - for(var i = 0; i < PREF_MAP[? "part_max_amount"]; i++) - parts[| i].draw(); - surface_reset_target(); - - cacheCurrentFrame(_outSurf); - } - - function update() { - reset(); - } - update(); - render(); -} \ No newline at end of file diff --git a/scripts/node_pixel_cloud/node_pixel_cloud.gml b/scripts/node_pixel_cloud/node_pixel_cloud.gml new file mode 100644 index 000000000..e80f3ad8e --- /dev/null +++ b/scripts/node_pixel_cloud/node_pixel_cloud.gml @@ -0,0 +1,102 @@ +function Node_create_Pixel_Cloud(_x, _y) { + var node = new Node_Pixel_Cloud(_x, _y); + ds_list_add(PANEL_GRAPH.nodes_list, node); + return node; +} + +function Node_Pixel_Cloud(_x, _y) : Node_Processor(_x, _y) constructor { + name = "Pixel Cloud"; + + uniform_sed = shader_get_uniform(sh_pixel_cloud, "seed"); + uniform_str = shader_get_uniform(sh_pixel_cloud, "strength"); + uniform_dis = shader_get_uniform(sh_pixel_cloud, "dist"); + + uniform_map_use = shader_get_uniform(sh_pixel_cloud, "useMap"); + uniform_map = shader_get_sampler_index(sh_pixel_cloud, "strengthMap"); + + uniform_grad_blend = shader_get_uniform(sh_pixel_cloud, "gradient_blend"); + uniform_grad = shader_get_uniform(sh_pixel_cloud, "gradient_color"); + uniform_grad_time = shader_get_uniform(sh_pixel_cloud, "gradient_time"); + uniform_grad_key = shader_get_uniform(sh_pixel_cloud, "gradient_keys"); + + uniform_alpha = shader_get_uniform(sh_pixel_cloud, "alpha_curve"); + + inputs[| 0] = nodeValue(0, "Surface in", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, 0); + + inputs[| 1] = nodeValue(1, "Seed", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, irandom(100000)) + .setVisible(false, true); + + inputs[| 2] = nodeValue(2, "Strength", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0.1) + .setDisplay(VALUE_DISPLAY.slider, [ 0, 1, 0.01]) + .setVisible(false, true); + + inputs[| 3] = nodeValue(3, "Strength map", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, 0); + + inputs[| 4] = nodeValue(4, "Gradient", self, JUNCTION_CONNECT.input, VALUE_TYPE.color, c_white) + .setDisplay(VALUE_DISPLAY.gradient); + + inputs[| 5] = nodeValue(5, "Size", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 1); + + inputs[| 6] = nodeValue(6, "Alpha over lifetime", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, [1, 1, 1, 1]) + .setDisplay(VALUE_DISPLAY.curve); + + input_display_list = [ + ["Input", true], 0, 1, + ["Movement", false], 5, 2, 3, + ["Color", true], 4, 6 + ] + + outputs[| 0] = nodeValue(0, "Surface out", self, JUNCTION_CONNECT.output, VALUE_TYPE.surface, surface_create(1, 1)); + + function process_data(_outSurf, _data, _output_index) { + var _sed = _data[1]; + var _str = _data[2]; + var _map = _data[3]; + + var _gra = _data[4]; + var _gra_data = inputs[| 4].getExtraData(); + var _grad_color = []; + var _grad_time = []; + + var _dis = _data[5]; + var _alp = _data[6]; + + for(var i = 0; i < ds_list_size(_gra); i++) { + _grad_color[i * 4 + 0] = color_get_red(_gra[| i].value) / 255; + _grad_color[i * 4 + 1] = color_get_green(_gra[| i].value) / 255; + _grad_color[i * 4 + 2] = color_get_blue(_gra[| i].value) / 255; + _grad_color[i * 4 + 3] = 1; + _grad_time[i] = _gra[| i].time; + } + + surface_set_target(_outSurf); + draw_clear_alpha(0, 0); + BLEND_ADD + + shader_set(sh_pixel_cloud); + shader_set_uniform_f(uniform_sed, _sed); + shader_set_uniform_f(uniform_str, _str); + shader_set_uniform_f(uniform_dis, _dis); + if(is_surface(_map)) { + shader_set_uniform_i(uniform_map_use, 1); + texture_set_stage(uniform_map, surface_get_texture(_map)); + } else { + shader_set_uniform_i(uniform_map_use, 0); + } + + shader_set_uniform_i(uniform_grad_blend, ds_list_get(_gra_data, 0)); + shader_set_uniform_f_array(uniform_grad, _grad_color); + shader_set_uniform_f_array(uniform_grad_time, _grad_time); + shader_set_uniform_i(uniform_grad_key, ds_list_size(_gra)); + + shader_set_uniform_f_array(uniform_alpha, _alp); + + draw_surface_safe(_data[0], 0, 0); + shader_reset(); + + BLEND_NORMAL + surface_reset_target(); + + return _outSurf; + } +} \ No newline at end of file diff --git a/scripts/node_particle_sampler/node_particle_sampler.yy b/scripts/node_pixel_cloud/node_pixel_cloud.yy similarity index 85% rename from scripts/node_particle_sampler/node_particle_sampler.yy rename to scripts/node_pixel_cloud/node_pixel_cloud.yy index bfb0f35dc..f8c734968 100644 --- a/scripts/node_particle_sampler/node_particle_sampler.yy +++ b/scripts/node_pixel_cloud/node_pixel_cloud.yy @@ -6,7 +6,7 @@ "path": "folders/nodes/data/generator.yy", }, "resourceVersion": "1.0", - "name": "node_particle_sampler", + "name": "node_pixel_cloud", "tags": [], "resourceType": "GMScript", } \ No newline at end of file diff --git a/scripts/node_registry/node_registry.gml b/scripts/node_registry/node_registry.gml index 33293bd5c..b80aeb256 100644 --- a/scripts/node_registry/node_registry.gml +++ b/scripts/node_registry/node_registry.gml @@ -171,6 +171,7 @@ function NodeObject(_name, _spr, _create, tags = []) constructor { addNodeObject(generator, "Anisotropic noise", s_node_noise_aniso, "Node_Noise_Aniso", Node_create_Noise_Aniso); addNodeObject(generator, "Seperate shape", s_node_sepearte_shape, "Node_Seperate_Shape", Node_create_Seperate_Shape); addNodeObject(generator, "Text", s_node_text, "Node_Text", Node_create_Text); + addNodeObject(generator, "Pixel cloud", s_node_pixel_cloud, "Node_Pixel_Cloud", Node_create_Pixel_Cloud); var render = ds_list_create(); addNodeCatagory("Render", render); diff --git a/scripts/node_value/node_value.gml b/scripts/node_value/node_value.gml index 497887daa..317bd569b 100644 --- a/scripts/node_value/node_value.gml +++ b/scripts/node_value/node_value.gml @@ -356,8 +356,7 @@ function NodeValue(_index, _name, _node, _connect, _type, _value, _tag = VALUE_T visible = false; display_type = VALUE_DISPLAY.curve; editWidget = new curveBox( - function(_modified) { setValue(_modified); }, - function(type) { value.curve_type = type; } ); + function(_modified) { setValue(_modified); }); break; case VALUE_TYPE.text : editWidget = new textBox(TEXTBOX_INPUT.text, function(str) { diff --git a/scripts/panel_inspector/panel_inspector.gml b/scripts/panel_inspector/panel_inspector.gml index f536ac60c..125829e8b 100644 --- a/scripts/panel_inspector/panel_inspector.gml +++ b/scripts/panel_inspector/panel_inspector.gml @@ -289,9 +289,9 @@ function Panel_Inspector(_panel) : PanelContent(_panel) constructor { hh += 68 + padd; break; case VALUE_TYPE.curve : - jun.editWidget.draw(32, _hsy, w - 80, 160, jun.showValue(), jun.value.curve_type, _m); - _hey = _hsy + 168; - hh += 168 + padd; + jun.editWidget.draw(32, _hsy, w - 80, 160, jun.showValue(), _m); + _hey = _hsy + 136; + hh += 136 + padd; break; case VALUE_TYPE.text : jun.editWidget.draw(32, _hsy, w - 80, 34, jun.showValue(), _m, jun.display_type); diff --git a/shaders/sh_gradient/sh_gradient.fsh b/shaders/sh_gradient/sh_gradient.fsh index dffcd161e..1c69a45ac 100644 --- a/shaders/sh_gradient/sh_gradient.fsh +++ b/shaders/sh_gradient/sh_gradient.fsh @@ -9,26 +9,15 @@ varying vec4 v_vColour; uniform int gradient_blend; uniform vec4 gradient_color[16]; uniform float gradient_time[16]; +uniform int gradient_keys; + uniform vec2 center; uniform float angle; uniform float radius; uniform float shift; -uniform int keys; uniform int type; -void main() { - float prog = 0.; - if(type == 0) { - prog = .5 + (v_vTexcoord.x - center.x) * cos(angle) - (v_vTexcoord.y - center.y) * sin(angle); - } else if(type == 1) { - prog = distance(v_vTexcoord, center) / radius; - } else if(type == 2) { - vec2 _p = v_vTexcoord - center; - float _a = atan(_p.y, _p.x) + angle; - prog = (_a - floor(_a / TAU) * TAU) / TAU; - } - prog += shift; - +vec4 gradientEval(in float prog) { vec4 col = vec4(0.); for(int i = 0; i < 16; i++) { @@ -46,11 +35,29 @@ void main() { } break; } - if(i >= keys - 1) { - col = gradient_color[keys - 1]; + if(i >= gradient_keys - 1) { + col = gradient_color[gradient_keys - 1]; break; } } + return col; +} + +void main() { + float prog = 0.; + if(type == 0) { + prog = .5 + (v_vTexcoord.x - center.x) * cos(angle) - (v_vTexcoord.y - center.y) * sin(angle); + } else if(type == 1) { + prog = distance(v_vTexcoord, center) / radius; + } else if(type == 2) { + vec2 _p = v_vTexcoord - center; + float _a = atan(_p.y, _p.x) + angle; + prog = (_a - floor(_a / TAU) * TAU) / TAU; + } + prog += shift; + + vec4 col = gradientEval(prog); + gl_FragColor = col; } diff --git a/shaders/sh_pixel_cloud/sh_pixel_cloud.fsh b/shaders/sh_pixel_cloud/sh_pixel_cloud.fsh new file mode 100644 index 000000000..0e414ee47 --- /dev/null +++ b/shaders/sh_pixel_cloud/sh_pixel_cloud.fsh @@ -0,0 +1,92 @@ +// +// Simple passthrough fragment shader +// +varying vec2 v_vTexcoord; +varying vec4 v_vColour; + +uniform float seed; +uniform float strength; +uniform float dist; +uniform int useMap; +uniform sampler2D strengthMap; + +uniform int gradient_blend; +uniform vec4 gradient_color[16]; +uniform float gradient_time[16]; +uniform int gradient_keys; + +uniform float alpha_curve[4]; + +vec4 gradientEval(in float prog) { + vec4 col = vec4(0.); + + for(int i = 0; i < 16; i++) { + if(gradient_time[i] == prog) { + col = gradient_color[i]; + break; + } else if(gradient_time[i] > prog) { + if(i == 0) + col = gradient_color[i]; + else { + if(gradient_blend == 0) + col = mix(gradient_color[i - 1], gradient_color[i], (prog - gradient_time[i - 1]) / (gradient_time[i] - gradient_time[i - 1])); + else if(gradient_blend == 1) + col = gradient_color[i - 1]; + } + break; + } + if(i >= gradient_keys - 1) { + col = gradient_color[gradient_keys - 1]; + break; + } + } + + return col; +} + +float curveEval(in float curve[4], in float prog) { + return pow(1. - prog, 3.) * curve[0] + + 3. * pow(1. - prog, 2.) * prog * curve[1] + + 3. * (1. - prog) * pow(prog, 2.) * curve[2] + + pow(prog, 3.) * curve[3]; +} + +float frandom (in vec2 st, in float _seed) { + float f = fract(sin(dot(st.xy, vec2(12.9898, 78.233)) * mod(15.15 + seed, 32.156 + _seed) * 12.588) * 43758.5453123); + f = f * 2. - 1.; + return f; +} + +vec2 vrandom (in vec2 st) { + return vec2(frandom(st, 165.84), frandom(st, 98.01)); +} + +void main() { + vec2 _pos = v_vTexcoord; + float str = strength; + + vec2 _vec = vrandom(_pos) * str * dist; + + if(useMap == 1) { + vec4 _map = texture2D( strengthMap, _pos); + _vec.x *= _map.r; + _vec.y *= _map.g; + str *= dot(_map.rg, _map.rg); + } + + str += frandom(_pos, 12.01) * abs(.1) * str; + + vec2 _new_pos = _pos - _vec; + vec4 _col; + + if(_new_pos == clamp(_new_pos, 0., 1.)) { + _col = texture2D( gm_BaseTexture, _pos - _vec ); + _col.rgb *= gradientEval(str).rgb; + _col.a *= curveEval(alpha_curve, str); + } else { + _col = vec4(0.); + } + + gl_FragColor = _col; +} + diff --git a/shaders/sh_pixel_cloud/sh_pixel_cloud.vsh b/shaders/sh_pixel_cloud/sh_pixel_cloud.vsh new file mode 100644 index 000000000..3900c20f4 --- /dev/null +++ b/shaders/sh_pixel_cloud/sh_pixel_cloud.vsh @@ -0,0 +1,19 @@ +// +// Simple passthrough vertex shader +// +attribute vec3 in_Position; // (x,y,z) +//attribute vec3 in_Normal; // (x,y,z) unused in this shader. +attribute vec4 in_Colour; // (r,g,b,a) +attribute vec2 in_TextureCoord; // (u,v) + +varying vec2 v_vTexcoord; +varying vec4 v_vColour; + +void main() +{ + vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0); + gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos; + + v_vColour = in_Colour; + v_vTexcoord = in_TextureCoord; +} diff --git a/shaders/sh_pixel_cloud/sh_pixel_cloud.yy b/shaders/sh_pixel_cloud/sh_pixel_cloud.yy new file mode 100644 index 000000000..8538df7f0 --- /dev/null +++ b/shaders/sh_pixel_cloud/sh_pixel_cloud.yy @@ -0,0 +1,11 @@ +{ + "type": 1, + "parent": { + "name": "generator", + "path": "folders/shader/generator.yy", + }, + "resourceVersion": "1.0", + "name": "sh_pixel_cloud", + "tags": [], + "resourceType": "GMShader", +} \ No newline at end of file diff --git a/sprites/s_node_pixel_cloud/90f3e43f-6cad-4a92-8e7c-8764bdde2faf.png b/sprites/s_node_pixel_cloud/90f3e43f-6cad-4a92-8e7c-8764bdde2faf.png new file mode 100644 index 000000000..60ee1eef2 Binary files /dev/null and b/sprites/s_node_pixel_cloud/90f3e43f-6cad-4a92-8e7c-8764bdde2faf.png differ diff --git a/sprites/s_node_pixel_cloud/layers/90f3e43f-6cad-4a92-8e7c-8764bdde2faf/2d92f87b-0c00-4e67-b5e5-80435b8a9954.png b/sprites/s_node_pixel_cloud/layers/90f3e43f-6cad-4a92-8e7c-8764bdde2faf/2d92f87b-0c00-4e67-b5e5-80435b8a9954.png new file mode 100644 index 000000000..60ee1eef2 Binary files /dev/null and b/sprites/s_node_pixel_cloud/layers/90f3e43f-6cad-4a92-8e7c-8764bdde2faf/2d92f87b-0c00-4e67-b5e5-80435b8a9954.png differ diff --git a/sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy b/sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy new file mode 100644 index 000000000..93481955d --- /dev/null +++ b/sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy @@ -0,0 +1,79 @@ +{ + "bboxMode": 0, + "collisionKind": 1, + "type": 0, + "origin": 4, + "preMultiplyAlpha": false, + "edgeFiltering": false, + "collisionTolerance": 0, + "swfPrecision": 2.525, + "bbox_left": 2, + "bbox_right": 29, + "bbox_top": 2, + "bbox_bottom": 30, + "HTile": false, + "VTile": false, + "For3D": false, + "width": 32, + "height": 32, + "textureGroupId": { + "name": "Default", + "path": "texturegroups/Default", + }, + "swatchColours": null, + "gridX": 0, + "gridY": 0, + "frames": [ + {"compositeImage":{"FrameId":{"name":"90f3e43f-6cad-4a92-8e7c-8764bdde2faf","path":"sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy",},"LayerId":null,"resourceVersion":"1.0","name":"","tags":[],"resourceType":"GMSpriteBitmap",},"images":[ + {"FrameId":{"name":"90f3e43f-6cad-4a92-8e7c-8764bdde2faf","path":"sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy",},"LayerId":{"name":"2d92f87b-0c00-4e67-b5e5-80435b8a9954","path":"sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy",},"resourceVersion":"1.0","name":"","tags":[],"resourceType":"GMSpriteBitmap",}, + ],"parent":{"name":"s_node_pixel_cloud","path":"sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy",},"resourceVersion":"1.0","name":"90f3e43f-6cad-4a92-8e7c-8764bdde2faf","tags":[],"resourceType":"GMSpriteFrame",}, + ], + "sequence": { + "spriteId": {"name":"s_node_pixel_cloud","path":"sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy",}, + "timeUnits": 1, + "playback": 1, + "playbackSpeed": 30.0, + "playbackSpeedType": 0, + "autoRecord": true, + "volume": 1.0, + "length": 1.0, + "events": {"Keyframes":[],"resourceVersion":"1.0","resourceType":"KeyframeStore",}, + "moments": {"Keyframes":[],"resourceVersion":"1.0","resourceType":"KeyframeStore",}, + "tracks": [ + {"name":"frames","spriteId":null,"keyframes":{"Keyframes":[ + {"id":"10ae1b26-149a-4837-b0ef-ebc91848d562","Key":0.0,"Length":1.0,"Stretch":false,"Disabled":false,"IsCreationKey":false,"Channels":{"0":{"Id":{"name":"90f3e43f-6cad-4a92-8e7c-8764bdde2faf","path":"sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy",},"resourceVersion":"1.0","resourceType":"SpriteFrameKeyframe",},},"resourceVersion":"1.0","resourceType":"Keyframe",}, + ],"resourceVersion":"1.0","resourceType":"KeyframeStore",},"trackColour":0,"inheritsTrackColour":true,"builtinName":0,"traits":0,"interpolation":1,"tracks":[],"events":[],"isCreationTrack":false,"resourceVersion":"1.0","tags":[],"resourceType":"GMSpriteFramesTrack","modifiers":[],}, + ], + "visibleRange": {"x":0.0,"y":0.0,}, + "lockOrigin": false, + "showBackdrop": true, + "showBackdropImage": false, + "backdropImagePath": "", + "backdropImageOpacity": 0.5, + "backdropWidth": 1366, + "backdropHeight": 768, + "backdropXOffset": 0.0, + "backdropYOffset": 0.0, + "xorigin": 16, + "yorigin": 16, + "eventToFunction": {}, + "eventStubScript": null, + "parent": {"name":"s_node_pixel_cloud","path":"sprites/s_node_pixel_cloud/s_node_pixel_cloud.yy",}, + "resourceVersion": "1.3", + "name": "s_node_pixel_cloud", + "tags": [], + "resourceType": "GMSequence", + }, + "layers": [ + {"visible":true,"isLocked":false,"blendMode":0,"opacity":100.0,"displayName":"default","resourceVersion":"1.0","name":"2d92f87b-0c00-4e67-b5e5-80435b8a9954","tags":[],"resourceType":"GMImageLayer",}, + ], + "nineSlice": null, + "parent": { + "name": "icons", + "path": "folders/nodes/icons.yy", + }, + "resourceVersion": "1.0", + "name": "s_node_pixel_cloud", + "tags": [], + "resourceType": "GMSprite", +} \ No newline at end of file