function Node_Image_Sheet(_x, _y, _group = noone) : Node(_x, _y, _group) constructor {
	name  = "Splice Spritesheet";
	
	newInput(0, nodeValue_Surface("Surface in", self));
	
	newInput(1, nodeValue_Vec2("Sprite size", self, [ 32, 32 ]));
	
	newInput(2, nodeValue_Int("Row", self, 1)); //unused
	newInput(3, nodeValue_Vec2("Amount", self, [ 1, 1 ]));
	
	newInput(4, nodeValue_Vec2("Offset", self, [ 0, 0 ]));
	
	newInput(5, nodeValue_Vec2("Spacing", self, [ 0, 0 ]));
	
	newInput(6, nodeValue_Padding("Padding", self, [0, 0, 0, 0]));
	
	newInput(7, nodeValue_Enum_Scroll("Output", self,  1, [ "Animation", "Array" ]));
	
	newInput(8, nodeValue_Float("Animation speed", self, 1));
	
	newInput(9, nodeValue_Enum_Scroll("Main Axis", self,  0, [ new scrollItem("Horizontal", s_node_alignment, 0), 
												               new scrollItem("Vertical",   s_node_alignment, 1), ]));
	
	newInput(10, nodeValue_Trigger("Auto fill", self, false, "Automatically set amount based on sprite size."))
		.setDisplay(VALUE_DISPLAY.button, { name: "Auto fill", UI : true, onClick: function() /*=>*/ {
			var _sur = getInputData(0);
			if(!is_surface(_sur) || _sur == DEF_SURFACE) return;
			
			var ww = surface_get_width(_sur);
			var hh = surface_get_height(_sur);
			
			var _size = getInputData(1);
			var _offs = getInputData(4);
			var _spac = getInputData(5);
			
			var sh_w = _size[0] + _spac[0];
			var sh_h = _size[1] + _spac[1];
		
			var fill_w = floor((ww - _offs[0]) / sh_w);
			var fill_h = floor((hh - _offs[1]) / sh_h);
			
			inputs[3].setValue([ fill_w, fill_h ]);
		
			doUpdate();
		} });
		
	newInput(11, nodeValue_Trigger("Sync animation", self, false ))
		.setDisplay(VALUE_DISPLAY.button, { name: "Sync frames", UI : true, onClick: function() /*=>*/ { 
			var _atl = outputs[1].getValue();
			var _spd = getInputData(8);
			TOTAL_FRAMES = max(1, _spd == 0? 1 : ceil(array_length(_atl) / _spd));
		} });
		
	newInput(12, nodeValue_Bool("Filter empty output", self, false));
		
	newInput(13, nodeValue_Enum_Scroll("Filtered Pixel", self,  0, [ "Transparent", "Color" ]));
	
	newInput(14, nodeValue_Color("Filtered Color", self, cola(c_black)))
	
	input_display_list = [
		["Sprite", false],	0, 1, 6, 
		["Sheet",  false],	3, 10, 9, 4, 5, 
		["Output", false],	7, 8, 11,
		["Filter Empty", true, 12], 13, 14, 
	];
	
	newOutput(0, nodeValue_Output("Surface out", self, VALUE_TYPE.surface, noone));
	
	newOutput(1, nodeValue_Output("Atlas Data", self, VALUE_TYPE.atlas, []))
		.setArrayDepth(1);
	
	attribute_surface_depth();
	
	drag_type    = 0;	
	drag_sx      = 0;
	drag_sy      = 0;
	drag_mx      = 0;
	drag_my      = 0;
	curr_off     = [0, 0];
	curr_dim     = [0, 0];
	curr_amo     = [0, 0];
	  
	surf_array   = [];
	atls_array   = [];
	
	surf_size_w  = 1;
	surf_size_h  = 1;
	 
	surf_space   = 0;
	surf_axis    = 0;
	
	sprite_pos   = [];
	sprite_valid = [];
	spliceSurf   = noone;
	
	temp_surface = [ noone ];
	
	static getPreviewValues  = function() { return getInputData(0); }
	static onValueFromUpdate = function() { _inSurf = noone; }
	static onValueUpdate     = function() { _inSurf = noone; }
	
	function getSpritePosition(index) {
		var _dim = curr_dim;
		var _off = curr_off;
		var _spa = surf_space;
		var _axs = surf_axis;
		
		var _irow, _icol;
		
		if(_axs == 0) {
			_irow = floor(index / curr_amo[0]);
			_icol = safe_mod(index, curr_amo[0]);
			
		} else {
			_icol = floor(index / curr_amo[1]);
			_irow = safe_mod(index, curr_amo[1]);
			
		}
		
		var _x, _y;
		
		var _x = _off[0] + _icol * (_dim[0] + _spa[0]);
		var _y = _off[1] + _irow * (_dim[1] + _spa[1]);
		
		return [ _x, _y ];
	} 
	
	static drawOverlay = function(hover, active, _x, _y, _s, _mx, _my, _snx, _sny) {
		var _inSurf  = getInputData(0);
		if(!is_surface(_inSurf)) return;
		
		var _out = getInputData(7);
		var _spc = getInputData(5);
		
		if(drag_type == 0) {
			curr_dim = getInputData(1);
			curr_amo = getInputData(3);
			curr_off = getInputData(4);
		}
		
		var _amo = array_safe_get_fast(curr_amo, 0) * array_safe_get_fast(curr_amo, 1);
		
		if(_amo < 256) {
			for(var i = _amo - 1; i >= 0; i--) {
				if(!array_safe_get_fast(sprite_valid, i, false))
					continue;
				
				var _f = sprite_pos[i];
				var _fx0 = _x + _f[0] * _s;
				var _fy0 = _y + _f[1] * _s;
				var _fx1 = _fx0 + curr_dim[0] * _s;
				var _fy1 = _fy0 + curr_dim[1] * _s;
			
				draw_set_color(COLORS._main_accent);
				draw_set_alpha(i == 0? 1 : 0.75);
				draw_rectangle(_fx0, _fy0, _fx1 - 1, _fy1 - 1, true);
				draw_set_alpha(1);
			}
		} else {
			var _f = sprite_pos[0];
			var _fx0 = _x + _f[0] * _s;
			var _fy0 = _y + _f[1] * _s;
			var _fx1 = _fx0 + curr_dim[0] * _s;
			var _fy1 = _fy0 + curr_dim[1] * _s;
			
			draw_set_color(COLORS._main_accent);
			draw_rectangle(_fx0, _fy0, _fx1 - 1, _fy1 - 1, true);
		}
		
		var __ax = curr_off[0];
		var __ay = curr_off[1];
		var __aw = curr_dim[0];
		var __ah = curr_dim[1];
						
		var _ax = __ax * _s + _x;
		var _ay = __ay * _s + _y;
		var _aw = __aw * _s;
		var _ah = __ah * _s;
		
		var _bw = curr_amo[0] * (curr_dim[0] + _spc[0]) - _spc[0]; _bw *= _s;
		var _bh = curr_amo[1] * (curr_dim[1] + _spc[1]) - _spc[1]; _bh *= _s;
		
		draw_sprite_colored(THEME.anchor, 0, _ax, _ay);
		draw_sprite_colored(THEME.anchor_selector, 0, _ax + _aw, _ay + _ah);
		draw_sprite_colored(THEME.anchor_arrow, 0, _ax + _bw + _s * 4, _ay + _bh / 2);
		draw_sprite_colored(THEME.anchor_arrow, 0, _ax + _bw / 2, _ay + _bh + _s * 4,, -90);
		
		if(active) {
			if(point_in_circle(_mx, _my, _ax + _aw, _ay + _ah, 8))
				draw_sprite_colored(THEME.anchor_selector, 1, _ax + _aw, _ay + _ah);
			else if(point_in_rectangle(_mx, _my, _ax - _aw, _ay - _ah, _ax + _aw, _ay + _ah))
				draw_sprite_colored(THEME.anchor, 0, _ax, _ay, 1.25, c_white);
			else if(point_in_circle(_mx, _my, _ax + _bw + _s * 4, _ay + _bh / 2, 8))
				draw_sprite_colored(THEME.anchor_arrow, 1, _ax + _bw + _s * 4, _ay + _bh / 2);
			else if(point_in_circle(_mx, _my, _ax + _bw / 2, _ay + _bh + _s * 4, 8))
				draw_sprite_colored(THEME.anchor_arrow, 1, _ax + _bw / 2, _ay + _bh + _s * 4,, -90);
		}
		
		#region area
			var __dim = getInputData(1);
			var __amo = getInputData(3);
			var __off = getInputData(4);
						
			var _ax = __off[0] * _s + _x;
			var _ay = __off[1] * _s + _y;
			var _aw = __dim[0] * _s;
			var _ah = __dim[1] * _s;
			
			if(drag_type == 1) {
				var _xx = value_snap(round(drag_sx + (_mx - drag_mx) / _s), _snx);
				var _yy = value_snap(round(drag_sy + (_my - drag_my) / _s), _sny);
							
				var off = [ _xx, _yy ];
				curr_off = off;
			
				if(mouse_release(mb_left)) {
					drag_type = 0;
					inputs[4].setValue(off);
				}
			} else if(drag_type == 2) {
				var _dx = value_snap(round(abs((_mx - drag_mx) / _s)), _snx);
				var _dy = value_snap(round(abs((_my - drag_my) / _s)), _sny);
				
				var dim = [_dx, _dy];
				curr_dim = dim;
							
				if(key_mod_press(SHIFT)) {
					dim[0] = max(_dx, _dy);
					dim[1] = max(_dx, _dy);
				}
				
				if(mouse_release(mb_left)) {
					drag_type = 0;
					inputs[1].setValue(dim);
				}
			} else if(drag_type == 3) {
				var _col = floor((abs(_mx - drag_mx) / _s - _spc[0]) / (__dim[0] + _spc[0]));
				curr_amo = [ _col, curr_amo[1] ];
				
				if(mouse_release(mb_left)) {
					drag_type = 0;
					inputs[3].setValue(curr_amo);
				}
			} else if(drag_type == 4) {
				var _row = floor((abs(_my - drag_my) / _s - _spc[1]) / (__dim[1] + _spc[1]));
				curr_amo = [ curr_amo[0], _row ];
				
				if(mouse_release(mb_left)) {
					drag_type = 0;
					inputs[3].setValue(curr_amo);
				}
			}
						
			if(mouse_press(mb_left, active)) {
				if(point_in_circle(_mx, _my, _ax + _aw, _ay + _ah, 8)) { // drag size
					drag_type = 2;
					drag_mx   = _ax;
					drag_my   = _ay;
				} else if(point_in_rectangle(_mx, _my, _ax - _aw, _ay - _ah, _ax + _aw, _ay + _ah)) { // drag position
					drag_type = 1;	
					drag_sx   = __off[0];
					drag_sy   = __off[1];
					drag_mx   = _mx;
					drag_my   = _my;
				} else if(point_in_circle(_mx, _my, _ax + _bw + _s * 4, _ay + _bh / 2, 8)) { // drag col
					drag_type = 3;
					drag_mx   = _ax;
					drag_my   = _ay;
				} else if(point_in_circle(_mx, _my, _ax + _bw / 2, _ay + _bh + _s * 4, 8)) { // drag row
					drag_type = 4;
					drag_mx   = _ax;
					drag_my   = _ay;
				}
			}
		#endregion
	}
	
	static step = function() {
		var _out  = getInputData(7);
		var _flty = getInputData(13);
		
		inputs[11].setVisible(!_out);
		inputs[ 8].setVisible(!_out);
		inputs[14].setVisible(_flty);
	}
	
	static spliceSprite = function() {
		var _inSurf  = getInputData(0);
		if(!is_surface(_inSurf)) return;
		
		spliceSurf   = _inSurf;
		
		var _outSurf = outputs[0].getValue();
		var _out	 = getInputData(7);
		var _dim	 = getInputData(1);
		var _amo	 = getInputData(3);
		var _off	 = getInputData(4);
		var _total   = _amo[0] * _amo[1];
		var _pad	 = getInputData(6);
		 
		surf_space   = getInputData(5);
		surf_axis    = getInputData(9);
		
		var ww = _dim[0] + _pad[0] + _pad[2];
		var hh = _dim[1] + _pad[1] + _pad[3];
		
		var _resizeSurf = surf_size_w != ww || surf_size_h != hh;
		
		surf_size_w = ww;
		surf_size_h = hh;
		
		var _filt = getInputData(12);
		var _fltp = getInputData(13);
		var _flcl = getInputData(14);
		
		var cDep = attrDepth();
		curr_dim = _dim;
		curr_amo = is_array(_amo)? _amo : [1, 1];
		curr_off = _off;
		
		if(ww < 1 || hh < 1) return;
		
		if(_filt) {
			var filSize = 4;
			temp_surface[0] = surface_verify(temp_surface[0], surface_get_width_safe(_inSurf), surface_get_height_safe(_inSurf));
			
			surface_set_shader(temp_surface[0], sh_slice_spritesheet_empty_scan, true, BLEND.over);
				shader_set_dim("dimension",  _inSurf);
				shader_set_f("paddingStart", _off);
				shader_set_f("spacing",		 surf_space);
				shader_set_f("spriteDim",	 _dim);
				shader_set_color("color",	 _flcl);
				shader_set_i("empty",		!_fltp);
				
				draw_surface_safe(_inSurf);
			surface_reset_shader();
		}
		
		var _atl = array_create(_total);
		var _sar = array_create(_total);
		var _arrAmo = 0, _s, _a;
		
		for(var i = 0; i < _total; i++) 
			sprite_pos[i] = getSpritePosition(i);
		
		for(var i = 0; i < _total; i++) {
			_s = array_safe_get_fast(surf_array, i);
		    _s = surface_verify(_s, ww, hh, cDep);
			
			_a = array_safe_get_fast(atls_array, i, 0);
			if(_a == 0) _a = new SurfaceAtlas(_s, 0, 0);
			else        _a.setSurface(_s);
			
			var _spr_pos = sprite_pos[i];
			
			surface_set_shader(_s, noone, true, BLEND.over);
				draw_surface_part(_inSurf, _spr_pos[0], _spr_pos[1], _dim[0], _dim[1], _pad[2], _pad[1]);
			surface_reset_shader();
			
			_a.x = _spr_pos[0];
			_a.y = _spr_pos[1];
				
			if(!_filt) {
				_atl[_arrAmo] = _a;
				_sar[_arrAmo] = _s;
				_arrAmo++;
				
				sprite_valid[i] = true;
				continue;
			}
			
			var empPx = surface_get_pixel_ext(temp_surface[0], _spr_pos[0], _spr_pos[1]);
			var empty = empPx == 0.;
					
			if(!empty) {
				_atl[_arrAmo] = _a;
				_sar[_arrAmo] = _s;
				_arrAmo++;
			}
			sprite_valid[i] = !empty;
		}
		
		for( var i = _arrAmo, n = array_length(surf_array); i < n; i++ )
			if(is_surface(surf_array[i])) surface_free(surf_array[i]);
			
		surf_array = array_create(_arrAmo);
		array_copy(surf_array, 0, _sar, 0, _arrAmo);
		
		atls_array = array_create(_arrAmo);
		array_copy(atls_array, 0, _atl, 0, _arrAmo);
		
		if(_out == 1) outputs[0].setValue(surf_array);
		outputs[1].setValue(atls_array);
	}
	
	static update = function(frame = CURRENT_FRAME) {
		spliceSprite();
		
		var _out = getInputData(7);
		if(_out == 1) {
			update_on_frame = false;
			return;
		}
		
		var _spd = getInputData(8);
		update_on_frame = true;
		
		if(array_length(surf_array)) {
			var ind = safe_mod(CURRENT_FRAME * _spd, array_length(surf_array));
			outputs[0].setValue(array_safe_get_fast(surf_array, ind));
		}
	}
}