Line, bridge path fix

This commit is contained in:
Tanasart 2024-04-26 14:47:44 +07:00
parent fda7448ce8
commit 796573b3c6
32 changed files with 3607 additions and 14709 deletions

View file

@ -0,0 +1,537 @@
// 2024-04-26 09:58:54
function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) constructor {
name = "Spawner";
update_on_frame = true;
inputs[| 0] = nodeValue("Particle sprite", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, noone );
inputs[| 1] = nodeValue("Spawn delay", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 4, "Frames delay between each particle spawn." )
.rejectArray();
inputs[| 2] = nodeValue("Spawn amount", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 2, 2 ], "Amount of particle spawn in that frame." )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 3] = nodeValue("Spawn area", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, DEF_AREA )
.setDisplay(VALUE_DISPLAY.area);
inputs[| 4] = nodeValue("Spawn distribution", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0 )
.rejectArray()
.setDisplay(VALUE_DISPLAY.enum_scroll, [ "Area", "Border", "Map" ] );
inputs[| 5] = nodeValue("Lifespan", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 20, 30 ] )
.setDisplay(VALUE_DISPLAY.range);
inputs[| 6] = nodeValue("Spawn direction", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 0, 45, 135, 0, 0 ] )
.setDisplay(VALUE_DISPLAY.rotation_random);
inputs[| 7] = nodeValue("Acceleration", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 8] = nodeValue("Orientation", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 0, 0, 0, 0, 0 ] )
.setDisplay(VALUE_DISPLAY.rotation_random);
inputs[| 9] = nodeValue("Rotational speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 10] = nodeValue("Spawn scale", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1, 1, 1 ] )
.setDisplay(VALUE_DISPLAY.vector_range, { linked : true });
inputs[| 11] = nodeValue("Scale over time", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, CURVE_DEF_11 );
inputs[| 12] = nodeValue("Color over lifetime", self, JUNCTION_CONNECT.input, VALUE_TYPE.gradient, new gradientObject(c_white) );
inputs[| 13] = nodeValue("Alpha", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ])
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 14] = nodeValue("Alpha over time", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, CURVE_DEF_11);
inputs[| 15] = nodeValue("Rotate by direction", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Make the particle rotates to follow its movement.");
inputs[| 16] = nodeValue("Spawn type", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0)
.rejectArray()
.setDisplay(VALUE_DISPLAY.enum_button, [ "Stream", "Burst", "Trigger" ]);
inputs[| 17] = nodeValue("Spawn size", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 18] = nodeValue("Spawn velocity", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 2 ] )
.setDisplay(VALUE_DISPLAY.range);
inputs[| 19] = nodeValue("Gravity", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true })
.rejectArray();
inputs[| 20] = nodeValue("Direction wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.vector, { label: [ "Amplitude", "Period" ], linkable: false, per_line: true })
.rejectArray();
inputs[| 21] = nodeValue("Loop", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, true )
.rejectArray();
inputs[| 22] = nodeValue("Surface array", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0, "Whether to select image from an array in order, at random, or treat array as animation." )
.setDisplay(VALUE_DISPLAY.enum_scroll, [ "Random", "Order", "Animation", "Scale" ])
.setVisible(false);
inputs[| 23] = nodeValue("Animation speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true })
.rejectArray()
.setVisible(false);
inputs[| 24] = nodeValue("Scatter", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 1)
.rejectArray()
.setDisplay(VALUE_DISPLAY.enum_button, [ "Uniform", "Random" ]);
inputs[| 25] = nodeValue("Boundary data", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [])
.setVisible(false, true);
inputs[| 26] = nodeValue("On animation end", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, ANIM_END_ACTION.loop)
.rejectArray()
.setDisplay(VALUE_DISPLAY.enum_button, [ "Loop", "Ping pong", "Destroy" ])
.setVisible(false);
inputs[| 27] = nodeValue("Spawn", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, true)
.rejectArray();
inputs[| 28] = nodeValue("Random blend", self, JUNCTION_CONNECT.input, VALUE_TYPE.gradient, new gradientObject(c_white) );
inputs[| 29] = nodeValue("Directed from center", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Make particle move away from the spawn center.")
.rejectArray();
inputs[| 30] = nodeValue("Distribution map", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, noone)
.rejectArray()
inputs[| 31] = nodeValue("Atlas", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, [] );
inputs[| 32] = nodeValue("Seed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, irandom_range(100000, 999999))
.rejectArray();
inputs[| 33] = nodeValue("Gravity direction", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, -90 )
.rejectArray()
.setDisplay(VALUE_DISPLAY.rotation);
inputs[| 34] = nodeValue("Turning", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 35] = nodeValue("Turn both directions", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Apply randomized 1, -1 multiplier to the turning speed." )
.rejectArray();
inputs[| 36] = nodeValue("Turn scale with speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, false )
.rejectArray();
inputs[| 37] = nodeValue("Collide ground", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false )
.rejectArray();
inputs[| 38] = nodeValue("Ground offset", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0 )
.rejectArray();
inputs[| 39] = nodeValue("Bounce amount", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0.5 )
.rejectArray()
.setDisplay(VALUE_DISPLAY.slider);
inputs[| 40] = nodeValue("Bounce friction", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0.1, "Apply horizontal friction once particle stop bouncing." )
.rejectArray()
.setDisplay(VALUE_DISPLAY.slider);
inputs[| 41] = nodeValue("Position wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.vector, { label: [ "Amplitude", "Period" ], linkable: false, per_line: true })
.rejectArray();
inputs[| 42] = nodeValue("Rotation wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.vector, { label: [ "Amplitude", "Period" ], linkable: false, per_line: true })
.rejectArray();
inputs[| 43] = nodeValue("Scale wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.vector, { label: [ "Amplitude", "Period" ], linkable: false, per_line: true })
.rejectArray();
inputs[| 44] = nodeValue("Spawn", self, JUNCTION_CONNECT.input, VALUE_TYPE.trigger, false )
.setDisplay(VALUE_DISPLAY.button, { name: "Trigger" })
.rejectArray();
inputs[| 45] = nodeValue("Follow Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false )
.rejectArray();
inputs[| 46] = nodeValue("Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.pathnode, noone )
.rejectArray();
inputs[| 47] = nodeValue("Path Deviation", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, CURVE_DEF_11 )
.rejectArray();
input_len = ds_list_size(inputs);
input_display_list = [ 32,
["Sprite", false], 0, 22, 23, 26,
["Spawn", true], 27, 16, 44, 1, 2, 3, 4, 30, 24, 5,
["Movement", true], 29, 6, 18,
["Follow path", true, 45], 46, 47,
["Physics", true], 7, 19, 33, 34, 35, 36,
["Ground", true, 37], 38, 39, 40,
["Rotation", true], 15, 8, 9,
["Scale", true], 10, 17, 11,
["Wiggles", true], 20, 41, 42, 43,
["Color", true], 12, 28, 13, 14,
["Render", true], 21,
];
attributes.part_amount = 512;
array_push(attributeEditors, ["Maximum particles", function() { return attributes.part_amount; },
new textBox(TEXTBOX_INPUT.number, function(val) { attributes.part_amount = val; }) ]);
parts = array_create(attributes.part_amount);
parts_runner = 0;
seed = 0;
spawn_index = 0;
scatter_index = 0;
def_surface = -1;
surface_cache = {};
wiggle_maps = {
wig_psx: new wiggleMap(seed, 1, 1000),
wig_psy: new wiggleMap(seed, 1, 1000),
wig_scx: new wiggleMap(seed, 1, 1000),
wig_scy: new wiggleMap(seed, 1, 1000),
wig_rot: new wiggleMap(seed, 1, 1000),
wig_dir: new wiggleMap(seed, 1, 1000),
};
curve_scale = noone;
curve_alpha = noone;
curve_path_div = noone;
for( var i = 0; i < attributes.part_amount; i++ )
parts[i] = new __part(self);
static spawn = function(_time = CURRENT_FRAME, _pos = -1) { #region
var _inSurf = getInputData( 0);
var _spawn_amount = getInputData( 2);
var _spawn_area = getInputData( 3);
var _distrib = getInputData( 4);
var _dist_map = getInputData(30);
var _scatter = getInputData(24);
var _life = getInputData( 5);
var _direction = getInputData( 6);
var _directCenter = getInputData(29);
var _velocity = getInputData(18);
var _accel = getInputData( 7);
var _grav = getInputData(19);
var _gvDir = getInputData(33);
var _turn = getInputData(34);
var _turnBi = getInputData(35);
var _turnSc = getInputData(36);
var _follow = getInputData(15);
var _rotation = getInputData( 8);
var _rotation_speed = getInputData( 9);
var _scale = getInputData(10);
var _size = getInputData(17);
var _color = getInputData(12);
var _blend = getInputData(28);
var _alpha = getInputData(13);
var _arr_type = getInputData(22);
var _anim_speed = getInputData(23);
var _anim_end = getInputData(26);
var _ground = getInputData(37);
var _ground_offset = getInputData(38);
var _ground_bounce = getInputData(39);
var _ground_frict = getInputData(40);
var _path = getInputData(46);
if(_rotation[1] < _rotation[0]) _rotation[1] += 360;
var _posDist = [];
random_set_seed(seed); seed++;
var _amo = irandom_range(_spawn_amount[0], _spawn_amount[1]);
if(_distrib == 2) _posDist = get_points_from_dist(_dist_map, _amo, seed);
for( var i = 0; i < _amo; i++ ) {
parts_runner = clamp(parts_runner, 0, array_length(parts) - 1);
var part = parts[parts_runner];
part.reset();
var _spr = _inSurf, _index = 0;
if(is_array(_inSurf)) {
switch(_arr_type) {
case 0 :
_index = irandom(array_length(_inSurf) - 1);
_spr = _inSurf[_index];
break;
case 1 :
_index = safe_mod(spawn_index, array_length(_inSurf));
_spr = _inSurf[_index];
break;
case 2 :
case 3 :
_spr = _inSurf;
break;
}
}
var xx = 0;
var yy = 0;
if(_pos == -1) {
if(is_instanceof(_spr, SurfaceAtlas)) {
xx = _spawn_area[0] + _spr.x + _spr.w / 2;
yy = _spawn_area[1] + _spr.y + _spr.h / 2;
part.atlas = _spr;
} else if(_distrib < 2) {
var sp = area_get_random_point(_spawn_area, _distrib, _scatter, spawn_index, _amo);
xx = sp[0];
yy = sp[1];
} else if(_distrib == 2) {
var sp = array_safe_get_fast(_posDist, i);
if(!is_array(sp)) continue;
xx = _spawn_area[0] + _spawn_area[2] * (sp[0] * 2 - 1.);
yy = _spawn_area[1] + _spawn_area[3] * (sp[1] * 2 - 1.);
}
} else {
xx = _pos[0];
yy = _pos[1];
}
var _lif = irandom_range(_life[0], _life[1]);
var _rot = angle_random_eval(_rotation);
var _rot_spd = random_range(_rotation_speed[0], _rotation_speed[1]);
var _dirr = _directCenter? point_direction(_spawn_area[0], _spawn_area[1], xx, yy) : angle_random_eval(_direction);
var _velo = random_range(_velocity[0], _velocity[1]);
var _vx = lengthdir_x(_velo, _dirr);
var _vy = lengthdir_y(_velo, _dirr);
var _acc = random_range(_accel[0], _accel[1]);
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]);
var _bld = _blend.eval(random(1));
part.seed = irandom_range(100000, 999999);
part.create(_spr, xx, yy, _lif);
part.anim_speed = random_range(_anim_speed[0], _anim_speed[1]);
part.anim_end = _anim_end;
part.arr_type = _arr_type;
var _trn = random_range(_turn[0], _turn[1]);
if(_turnBi) _trn *= choose(-1, 1);
var _gravity = random_range(_grav[0], _grav[1]);
part.setPhysic(_vx, _vy, _acc, _gravity, _gvDir, _trn, _turnSc);
part.setWiggle(wiggle_maps);
part.setGround(_ground, _ground_offset, _ground_bounce, _ground_frict);
part.setTransform(_scx, _scy, curve_scale, _rot, _rot_spd, _follow);
part.setDraw(_color, _bld, _alp, curve_alpha);
part.setPath(_path, curve_path_div);
spawn_index = safe_mod(spawn_index + 1, attributes.part_amount);
onSpawn(_time, part);
parts_runner = safe_mod(parts_runner + 1, attributes.part_amount);
}
} #endregion
static onSpawn = function(_time, part) {}
static updateParticleForward = function() {}
static getSurfaceCache = function() { #region
var surfs = getInputData(0);
if(!is_array(surfs)) surfs = [ surfs ];
if(array_empty(surfs)) return;
for( var i = 0, n = array_length(surfs); i < n; i++ ) {
if(is_surface(surface_cache[$ surfs[i]])) continue;
surface_cache[$ surfs[i]] = surface_clone(surfs[i]);
}
} #endregion
function reset() { #region
getInputs(0);
var keys = variable_struct_get_names(surface_cache);
for( var i = 0, n = array_length(keys); i < n; i++ )
surface_free_safe(surface_cache[$ keys[i]]);
surface_cache = {};
getSurfaceCache();
spawn_index = 0;
scatter_index = 0;
for(var i = 0; i < array_length(parts); i++) {
if(!parts[i].active) continue;
parts[i].kill(false);
}
#region ----- precomputes -----
resetSeed();
var _wigg_pos = getInputData(41);
var _wigg_rot = getInputData(42);
var _wigg_sca = getInputData(43);
var _wigg_dir = getInputData(20);
wiggle_maps.wig_psx.check(_wigg_pos[0], _wigg_pos[1], seed + 10);
wiggle_maps.wig_psy.check(_wigg_pos[0], _wigg_pos[1], seed + 20);
wiggle_maps.wig_rot.check(_wigg_rot[0], _wigg_rot[1], seed + 30);
wiggle_maps.wig_scx.check(_wigg_sca[0], _wigg_sca[1], seed + 40);
wiggle_maps.wig_scy.check(_wigg_sca[0], _wigg_sca[1], seed + 50);
wiggle_maps.wig_dir.check(_wigg_dir[0], _wigg_dir[1], seed + 60);
var _curve_sca = getInputData(11);
var _curve_alp = getInputData(14);
var _curve_pth = getInputData(47);
curve_scale = new curveMap(_curve_sca, TOTAL_FRAMES);
curve_alpha = new curveMap(_curve_alp, TOTAL_FRAMES);
curve_path_div = new curveMap(_curve_pth, TOTAL_FRAMES);
#endregion
render();
} #endregion
static resetSeed = function() { #region
seed = getInputData(32);
} #endregion
function checkPartPool() { #region
var _part_amo = attributes.part_amount;
var _curr_amo = array_length(parts);
if(_part_amo > _curr_amo) {
repeat(_part_amo - _curr_amo)
array_push(parts, new __part(self));
} else if(_part_amo < _curr_amo) {
array_resize(parts, _part_amo);
}
} #endregion
static runVFX = function(_time = CURRENT_FRAME, _render = true) { #region
var _spawn_delay = inputs[| 1].getValue(_time);
var _spawn_type = inputs[| 16].getValue(_time);
var _spawn_active = inputs[| 27].getValue(_time);
var _spawn_trig = inputs[| 44].getValue(_time);
//print($"{_time} : {_spawn_trig} | {ds_list_to_array(inputs[| 44].animator.values)}");
getInputs(_time);
getSurfaceCache();
if(_spawn_active) {
switch(_spawn_type) {
case 0 : if(safe_mod(_time, _spawn_delay) == 0) spawn(_time); break;
case 1 : if(_time == _spawn_delay) spawn(_time); break;
case 2 : if(_spawn_trig) spawn(_time); break;
}
}
//print($"\n===== Running VFX {_time} =====")
//var activeParts = 0;
for(var i = 0; i < array_length(parts); i++) {
//activeParts++;
parts[i].step(_time);
}
//print($"Run VFX frame {_time} seed {seed}");
//print($"[{display_name}] Running VFX frame {_time}: {activeParts} active particles.");
if(!_render) return;
render(_time);
} #endregion
static onStep = function() {}
static step = function() { #region
var _inSurf = getInputData(0);
var _dist = getInputData(4);
var _scatt = getInputData(24);
var _dirAng = getInputData(29);
var _turn = getInputData(34);
var _spwTyp = getInputData(16);
var _usePth = getInputData(45);
inputs[| 6].setVisible(!_dirAng);
inputs[| 24].setVisible(_dist < 2);
inputs[| 30].setVisible(_dist == 2, _dist == 2);
inputs[| 35].setVisible(_turn[0] != 0 && _turn[1] != 0);
inputs[| 36].setVisible(_turn[0] != 0 && _turn[1] != 0);
inputs[| 22].setVisible(false);
inputs[| 23].setVisible(false);
inputs[| 26].setVisible(false);
inputs[| 46].setVisible(true, _usePth);
inputs[| 1].setVisible(_spwTyp < 2);
if(_spwTyp == 0) inputs[| 1].name = "Spawn delay";
else if(_spwTyp == 1) inputs[| 1].name = "Spawn frame";
inputs[| 44].setVisible(_spwTyp == 2);
if(is_array(_inSurf)) {
inputs[| 22].setVisible(true);
var _type = getInputData(22);
if(_type == 2) {
inputs[| 23].setVisible(true);
inputs[| 26].setVisible(true);
}
}
onStep();
} #endregion
static drawOverlay = function(hover, active, _x, _y, _s, _mx, _my, _snx, _sny) { #region
var _spr = getInputData(0);
if(is_array(_spr)) _spr = _spr[0];
var _flag = is_instanceof(_spr, SurfaceAtlas)? 0b0001 : 0b0011;
inputs[| 3].drawOverlay(hover, active, _x, _y, _s, _mx, _my, _snx, _sny, _flag);
if(onDrawOverlay != -1)
onDrawOverlay(active, _x, _y, _s, _mx, _my);
} #endregion
static onDrawOverlay = -1;
static update = function(frame = CURRENT_FRAME) { #region
checkPartPool();
onUpdate(frame);
} #endregion
static onUpdate = function(frame = CURRENT_FRAME) {}
static render = function() {}
static onPartCreate = noone;
static onPartStep = noone;
static onPartDestroy = noone;
static doSerialize = function(_map) { #region
_map.part_base_length = input_len;
} #endregion
static postDeserialize = function() { #region
var _tlen = struct_try_get(load_map, "part_base_length", 40);
for( var i = _tlen; i < input_len; i++ )
array_insert(load_map.inputs, i, noone);
} #endregion
}

View file

@ -0,0 +1,539 @@
// 2024-04-26 09:57:25
function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) constructor {
name = "Spawner";
update_on_frame = true;
inputs[| 0] = nodeValue("Particle sprite", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, noone );
inputs[| 1] = nodeValue("Spawn delay", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 4, "Frames delay between each particle spawn." )
.rejectArray();
inputs[| 2] = nodeValue("Spawn amount", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 2, 2 ], "Amount of particle spawn in that frame." )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 3] = nodeValue("Spawn area", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, DEF_AREA )
.setDisplay(VALUE_DISPLAY.area);
inputs[| 4] = nodeValue("Spawn distribution", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0 )
.rejectArray()
.setDisplay(VALUE_DISPLAY.enum_scroll, [ "Area", "Border", "Map" ] );
inputs[| 5] = nodeValue("Lifespan", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 20, 30 ] )
.setDisplay(VALUE_DISPLAY.range);
inputs[| 6] = nodeValue("Spawn direction", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 0, 45, 135, 0, 0 ] )
.setDisplay(VALUE_DISPLAY.rotation_random);
inputs[| 7] = nodeValue("Acceleration", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 8] = nodeValue("Orientation", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 0, 0, 0, 0, 0 ] )
.setDisplay(VALUE_DISPLAY.rotation_random);
inputs[| 9] = nodeValue("Rotational speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 10] = nodeValue("Spawn scale", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1, 1, 1 ] )
.setDisplay(VALUE_DISPLAY.vector_range, { linked : true });
inputs[| 11] = nodeValue("Scale over time", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, CURVE_DEF_11 );
inputs[| 12] = nodeValue("Color over lifetime", self, JUNCTION_CONNECT.input, VALUE_TYPE.gradient, new gradientObject(c_white) );
inputs[| 13] = nodeValue("Alpha", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ])
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 14] = nodeValue("Alpha over time", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, CURVE_DEF_11);
inputs[| 15] = nodeValue("Rotate by direction", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Make the particle rotates to follow its movement.");
inputs[| 16] = nodeValue("Spawn type", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0)
.rejectArray()
.setDisplay(VALUE_DISPLAY.enum_button, [ "Stream", "Burst", "Trigger" ]);
inputs[| 17] = nodeValue("Spawn size", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 18] = nodeValue("Spawn velocity", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 2 ] )
.setDisplay(VALUE_DISPLAY.range);
inputs[| 19] = nodeValue("Gravity", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true })
.rejectArray();
inputs[| 20] = nodeValue("Direction wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.vector, { label: [ "Amplitude", "Period" ], linkable: false, per_line: true })
.rejectArray();
inputs[| 21] = nodeValue("Loop", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, true )
.rejectArray();
inputs[| 22] = nodeValue("Surface array", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0, "Whether to select image from an array in order, at random, or treat array as animation." )
.setDisplay(VALUE_DISPLAY.enum_scroll, [ "Random", "Order", "Animation", "Scale" ])
.setVisible(false);
inputs[| 23] = nodeValue("Animation speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true })
.rejectArray()
.setVisible(false);
inputs[| 24] = nodeValue("Scatter", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 1)
.rejectArray()
.setDisplay(VALUE_DISPLAY.enum_button, [ "Uniform", "Random" ]);
inputs[| 25] = nodeValue("Boundary data", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [])
.setVisible(false, true);
inputs[| 26] = nodeValue("On animation end", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, ANIM_END_ACTION.loop)
.rejectArray()
.setDisplay(VALUE_DISPLAY.enum_button, [ "Loop", "Ping pong", "Destroy" ])
.setVisible(false);
inputs[| 27] = nodeValue("Spawn", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, true)
.rejectArray();
inputs[| 28] = nodeValue("Random blend", self, JUNCTION_CONNECT.input, VALUE_TYPE.gradient, new gradientObject(c_white) );
inputs[| 29] = nodeValue("Directed from center", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Make particle move away from the spawn center.")
.rejectArray();
inputs[| 30] = nodeValue("Distribution map", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, noone)
.rejectArray()
inputs[| 31] = nodeValue("Atlas", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, [] )
.setArrayDepth(1)
.rejectArray();
inputs[| 32] = nodeValue("Seed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, irandom_range(100000, 999999))
.rejectArray();
inputs[| 33] = nodeValue("Gravity direction", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, -90 )
.rejectArray()
.setDisplay(VALUE_DISPLAY.rotation);
inputs[| 34] = nodeValue("Turning", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 35] = nodeValue("Turn both directions", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Apply randomized 1, -1 multiplier to the turning speed." )
.rejectArray();
inputs[| 36] = nodeValue("Turn scale with speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, false )
.rejectArray();
inputs[| 37] = nodeValue("Collide ground", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false )
.rejectArray();
inputs[| 38] = nodeValue("Ground offset", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0 )
.rejectArray();
inputs[| 39] = nodeValue("Bounce amount", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0.5 )
.rejectArray()
.setDisplay(VALUE_DISPLAY.slider);
inputs[| 40] = nodeValue("Bounce friction", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0.1, "Apply horizontal friction once particle stop bouncing." )
.rejectArray()
.setDisplay(VALUE_DISPLAY.slider);
inputs[| 41] = nodeValue("Position wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.vector, { label: [ "Amplitude", "Period" ], linkable: false, per_line: true })
.rejectArray();
inputs[| 42] = nodeValue("Rotation wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.vector, { label: [ "Amplitude", "Period" ], linkable: false, per_line: true })
.rejectArray();
inputs[| 43] = nodeValue("Scale wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ] )
.setDisplay(VALUE_DISPLAY.vector, { label: [ "Amplitude", "Period" ], linkable: false, per_line: true })
.rejectArray();
inputs[| 44] = nodeValue("Spawn", self, JUNCTION_CONNECT.input, VALUE_TYPE.trigger, false )
.setDisplay(VALUE_DISPLAY.button, { name: "Trigger" })
.rejectArray();
inputs[| 45] = nodeValue("Follow Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false )
.rejectArray();
inputs[| 46] = nodeValue("Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.pathnode, noone )
.rejectArray();
inputs[| 47] = nodeValue("Path Deviation", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, CURVE_DEF_11 )
.rejectArray();
input_len = ds_list_size(inputs);
input_display_list = [ 32,
["Sprite", false], 0, 22, 23, 26,
["Spawn", true], 27, 16, 44, 1, 2, 3, 4, 30, 24, 5,
["Movement", true], 29, 6, 18,
["Follow path", true, 45], 46, 47,
["Physics", true], 7, 19, 33, 34, 35, 36,
["Ground", true, 37], 38, 39, 40,
["Rotation", true], 15, 8, 9,
["Scale", true], 10, 17, 11,
["Wiggles", true], 20, 41, 42, 43,
["Color", true], 12, 28, 13, 14,
["Render", true], 21,
];
attributes.part_amount = 512;
array_push(attributeEditors, ["Maximum particles", function() { return attributes.part_amount; },
new textBox(TEXTBOX_INPUT.number, function(val) { attributes.part_amount = val; }) ]);
parts = array_create(attributes.part_amount);
parts_runner = 0;
seed = 0;
spawn_index = 0;
scatter_index = 0;
def_surface = -1;
surface_cache = {};
wiggle_maps = {
wig_psx: new wiggleMap(seed, 1, 1000),
wig_psy: new wiggleMap(seed, 1, 1000),
wig_scx: new wiggleMap(seed, 1, 1000),
wig_scy: new wiggleMap(seed, 1, 1000),
wig_rot: new wiggleMap(seed, 1, 1000),
wig_dir: new wiggleMap(seed, 1, 1000),
};
curve_scale = noone;
curve_alpha = noone;
curve_path_div = noone;
for( var i = 0; i < attributes.part_amount; i++ )
parts[i] = new __part(self);
static spawn = function(_time = CURRENT_FRAME, _pos = -1) { #region
var _inSurf = getInputData( 0);
var _spawn_amount = getInputData( 2);
var _spawn_area = getInputData( 3);
var _distrib = getInputData( 4);
var _dist_map = getInputData(30);
var _scatter = getInputData(24);
var _life = getInputData( 5);
var _direction = getInputData( 6);
var _directCenter = getInputData(29);
var _velocity = getInputData(18);
var _accel = getInputData( 7);
var _grav = getInputData(19);
var _gvDir = getInputData(33);
var _turn = getInputData(34);
var _turnBi = getInputData(35);
var _turnSc = getInputData(36);
var _follow = getInputData(15);
var _rotation = getInputData( 8);
var _rotation_speed = getInputData( 9);
var _scale = getInputData(10);
var _size = getInputData(17);
var _color = getInputData(12);
var _blend = getInputData(28);
var _alpha = getInputData(13);
var _arr_type = getInputData(22);
var _anim_speed = getInputData(23);
var _anim_end = getInputData(26);
var _ground = getInputData(37);
var _ground_offset = getInputData(38);
var _ground_bounce = getInputData(39);
var _ground_frict = getInputData(40);
var _path = getInputData(46);
if(_rotation[1] < _rotation[0]) _rotation[1] += 360;
var _posDist = [];
random_set_seed(seed); seed++;
var _amo = irandom_range(_spawn_amount[0], _spawn_amount[1]);
if(_distrib == 2) _posDist = get_points_from_dist(_dist_map, _amo, seed);
for( var i = 0; i < _amo; i++ ) {
parts_runner = clamp(parts_runner, 0, array_length(parts) - 1);
var part = parts[parts_runner];
part.reset();
var _spr = _inSurf, _index = 0;
if(is_array(_inSurf)) {
switch(_arr_type) {
case 0 :
_index = irandom(array_length(_inSurf) - 1);
_spr = _inSurf[_index];
break;
case 1 :
_index = safe_mod(spawn_index, array_length(_inSurf));
_spr = _inSurf[_index];
break;
case 2 :
case 3 :
_spr = _inSurf;
break;
}
}
var xx = 0;
var yy = 0;
if(_pos == -1) {
if(is_instanceof(_spr, SurfaceAtlas)) {
xx = _spawn_area[0] + _spr.x + _spr.w / 2;
yy = _spawn_area[1] + _spr.y + _spr.h / 2;
part.atlas = _spr;
} else if(_distrib < 2) {
var sp = area_get_random_point(_spawn_area, _distrib, _scatter, spawn_index, _amo);
xx = sp[0];
yy = sp[1];
} else if(_distrib == 2) {
var sp = array_safe_get_fast(_posDist, i);
if(!is_array(sp)) continue;
xx = _spawn_area[0] + _spawn_area[2] * (sp[0] * 2 - 1.);
yy = _spawn_area[1] + _spawn_area[3] * (sp[1] * 2 - 1.);
}
} else {
xx = _pos[0];
yy = _pos[1];
}
var _lif = irandom_range(_life[0], _life[1]);
var _rot = angle_random_eval(_rotation);
var _rot_spd = random_range(_rotation_speed[0], _rotation_speed[1]);
var _dirr = _directCenter? point_direction(_spawn_area[0], _spawn_area[1], xx, yy) : angle_random_eval(_direction);
var _velo = random_range(_velocity[0], _velocity[1]);
var _vx = lengthdir_x(_velo, _dirr);
var _vy = lengthdir_y(_velo, _dirr);
var _acc = random_range(_accel[0], _accel[1]);
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]);
var _bld = _blend.eval(random(1));
part.seed = irandom_range(100000, 999999);
part.create(_spr, xx, yy, _lif);
part.anim_speed = random_range(_anim_speed[0], _anim_speed[1]);
part.anim_end = _anim_end;
part.arr_type = _arr_type;
var _trn = random_range(_turn[0], _turn[1]);
if(_turnBi) _trn *= choose(-1, 1);
var _gravity = random_range(_grav[0], _grav[1]);
part.setPhysic(_vx, _vy, _acc, _gravity, _gvDir, _trn, _turnSc);
part.setWiggle(wiggle_maps);
part.setGround(_ground, _ground_offset, _ground_bounce, _ground_frict);
part.setTransform(_scx, _scy, curve_scale, _rot, _rot_spd, _follow);
part.setDraw(_color, _bld, _alp, curve_alpha);
part.setPath(_path, curve_path_div);
spawn_index = safe_mod(spawn_index + 1, attributes.part_amount);
onSpawn(_time, part);
parts_runner = safe_mod(parts_runner + 1, attributes.part_amount);
}
} #endregion
static onSpawn = function(_time, part) {}
static updateParticleForward = function() {}
static getSurfaceCache = function() { #region
var surfs = getInputData(0);
if(!is_array(surfs)) surfs = [ surfs ];
if(array_empty(surfs)) return;
for( var i = 0, n = array_length(surfs); i < n; i++ ) {
if(is_surface(surface_cache[$ surfs[i]])) continue;
surface_cache[$ surfs[i]] = surface_clone(surfs[i]);
}
} #endregion
function reset() { #region
getInputs(0);
var keys = variable_struct_get_names(surface_cache);
for( var i = 0, n = array_length(keys); i < n; i++ )
surface_free_safe(surface_cache[$ keys[i]]);
surface_cache = {};
getSurfaceCache();
spawn_index = 0;
scatter_index = 0;
for(var i = 0; i < array_length(parts); i++) {
if(!parts[i].active) continue;
parts[i].kill(false);
}
#region ----- precomputes -----
resetSeed();
var _wigg_pos = getInputData(41);
var _wigg_rot = getInputData(42);
var _wigg_sca = getInputData(43);
var _wigg_dir = getInputData(20);
wiggle_maps.wig_psx.check(_wigg_pos[0], _wigg_pos[1], seed + 10);
wiggle_maps.wig_psy.check(_wigg_pos[0], _wigg_pos[1], seed + 20);
wiggle_maps.wig_rot.check(_wigg_rot[0], _wigg_rot[1], seed + 30);
wiggle_maps.wig_scx.check(_wigg_sca[0], _wigg_sca[1], seed + 40);
wiggle_maps.wig_scy.check(_wigg_sca[0], _wigg_sca[1], seed + 50);
wiggle_maps.wig_dir.check(_wigg_dir[0], _wigg_dir[1], seed + 60);
var _curve_sca = getInputData(11);
var _curve_alp = getInputData(14);
var _curve_pth = getInputData(47);
curve_scale = new curveMap(_curve_sca, TOTAL_FRAMES);
curve_alpha = new curveMap(_curve_alp, TOTAL_FRAMES);
curve_path_div = new curveMap(_curve_pth, TOTAL_FRAMES);
#endregion
render();
} #endregion
static resetSeed = function() { #region
seed = getInputData(32);
} #endregion
function checkPartPool() { #region
var _part_amo = attributes.part_amount;
var _curr_amo = array_length(parts);
if(_part_amo > _curr_amo) {
repeat(_part_amo - _curr_amo)
array_push(parts, new __part(self));
} else if(_part_amo < _curr_amo) {
array_resize(parts, _part_amo);
}
} #endregion
static runVFX = function(_time = CURRENT_FRAME, _render = true) { #region
var _spawn_delay = inputs[| 1].getValue(_time);
var _spawn_type = inputs[| 16].getValue(_time);
var _spawn_active = inputs[| 27].getValue(_time);
var _spawn_trig = inputs[| 44].getValue(_time);
//print($"{_time} : {_spawn_trig} | {ds_list_to_array(inputs[| 44].animator.values)}");
getInputs(_time);
getSurfaceCache();
if(_spawn_active) {
switch(_spawn_type) {
case 0 : if(safe_mod(_time, _spawn_delay) == 0) spawn(_time); break;
case 1 : if(_time == _spawn_delay) spawn(_time); break;
case 2 : if(_spawn_trig) spawn(_time); break;
}
}
//print($"\n===== Running VFX {_time} =====")
//var activeParts = 0;
for(var i = 0; i < array_length(parts); i++) {
//activeParts++;
parts[i].step(_time);
}
//print($"Run VFX frame {_time} seed {seed}");
//print($"[{display_name}] Running VFX frame {_time}: {activeParts} active particles.");
if(!_render) return;
render(_time);
} #endregion
static onStep = function() {}
static step = function() { #region
var _inSurf = getInputData(0);
var _dist = getInputData(4);
var _scatt = getInputData(24);
var _dirAng = getInputData(29);
var _turn = getInputData(34);
var _spwTyp = getInputData(16);
var _usePth = getInputData(45);
inputs[| 6].setVisible(!_dirAng);
inputs[| 24].setVisible(_dist < 2);
inputs[| 30].setVisible(_dist == 2, _dist == 2);
inputs[| 35].setVisible(_turn[0] != 0 && _turn[1] != 0);
inputs[| 36].setVisible(_turn[0] != 0 && _turn[1] != 0);
inputs[| 22].setVisible(false);
inputs[| 23].setVisible(false);
inputs[| 26].setVisible(false);
inputs[| 46].setVisible(true, _usePth);
inputs[| 1].setVisible(_spwTyp < 2);
if(_spwTyp == 0) inputs[| 1].name = "Spawn delay";
else if(_spwTyp == 1) inputs[| 1].name = "Spawn frame";
inputs[| 44].setVisible(_spwTyp == 2);
if(is_array(_inSurf)) {
inputs[| 22].setVisible(true);
var _type = getInputData(22);
if(_type == 2) {
inputs[| 23].setVisible(true);
inputs[| 26].setVisible(true);
}
}
onStep();
} #endregion
static drawOverlay = function(hover, active, _x, _y, _s, _mx, _my, _snx, _sny) { #region
var _spr = getInputData(0);
if(is_array(_spr)) _spr = _spr[0];
var _flag = is_instanceof(_spr, SurfaceAtlas)? 0b0001 : 0b0011;
inputs[| 3].drawOverlay(hover, active, _x, _y, _s, _mx, _my, _snx, _sny, _flag);
if(onDrawOverlay != -1)
onDrawOverlay(active, _x, _y, _s, _mx, _my);
} #endregion
static onDrawOverlay = -1;
static update = function(frame = CURRENT_FRAME) { #region
checkPartPool();
onUpdate(frame);
} #endregion
static onUpdate = function(frame = CURRENT_FRAME) {}
static render = function() {}
static onPartCreate = noone;
static onPartStep = noone;
static onPartDestroy = noone;
static doSerialize = function(_map) { #region
_map.part_base_length = input_len;
} #endregion
static postDeserialize = function() { #region
var _tlen = struct_try_get(load_map, "part_base_length", 40);
for( var i = _tlen; i < input_len; i++ )
array_insert(load_map.inputs, i, noone);
} #endregion
}

View file

@ -0,0 +1,103 @@
// 2024-04-26 10:28:56
function buffer_get_color(buffer, _x, _y, w, h) { #region
buffer_seek(buffer, buffer_seek_start, (w * _y + _x) * 4);
var c = buffer_read(buffer, buffer_u32);
return c;
} #endregion
function buffer_get_string(buffer, text = true, limit = 400) { #region
if(is_array(buffer)) return "[buffer array]";
if(!buffer_exists(buffer)) return "";
buffer_seek(buffer, buffer_seek_start, 0);
var len = min(limit, buffer_get_size(buffer));
var ss = "";
for (var i = 0; i < len; i++) {
var _r = buffer_read(buffer, buffer_u8);
var _s = text? chr(_r) : dec_to_hex(_r, 2);
ss += _s;
if(!text && i % 2) ss += " ";
}
return ss;
} #endregion
function buffer_from_string(str) { #region
var _b = buffer_create(string_length(str) * 1, buffer_fast, 1);
for( var i = 1; i <= string_length(str); i++ )
buffer_write(_b, buffer_u8, ord(string_char_at(str, i)));
return _b;
} #endregion
function buffer_from_surface(surface, header = true) { #region
static header_length = 24;
if(!is_surface(surface)) return noone;
var bitSize = surface_format_get_bytes(surface_get_format(surface));
var _b = buffer_create((header_length * header) + surface_get_width_safe(surface) * surface_get_height_safe(surface) * bitSize, buffer_fixed, 1);
if(header) {
buffer_write(_b, buffer_text, "PXCS");
buffer_write(_b, buffer_u16, surface_get_width_safe(surface));
buffer_write(_b, buffer_u16, surface_get_height_safe(surface));
buffer_write(_b, buffer_u8, surface_get_format(surface));
}
buffer_get_surface(_b, surface, header_length * header);
return _b;
} #endregion
function buffer_from_file(path) { #region
if(!file_exists_empty(path)) return;
var _b = buffer_load(path);
return _b;
} #endregion
function buffer_read_at(buffer, position, type) { #region
buffer_seek(buffer, buffer_seek_start, position);
return buffer_read(buffer, type);
} #endregion
function buffer_serialize(buffer, compress = true) { #region
INLINE
if(!buffer_exists(buffer)) return "";
if(compress) {
var comp = buffer_compress(buffer, 0, buffer_get_size(buffer));
return buffer_base64_encode(comp, 0, buffer_get_size(comp));
}
return buffer_base64_encode(buffer, 0, buffer_get_size(buffer));
} #endregion
function buffer_deserialize(buffer, compress = true) { #region
INLINE
var buff = buffer_base64_decode(buffer);
if(!compress) return buff;
return buffer_decompress(buff);
} #endregion
function buffer_getPixel(buffer, _w, _h, _x, _y) { #region
if(_x < 0 || _y < 0 || _x >= _w || _y >= _h) return 0;
buffer_seek(buffer, buffer_seek_start, (_w * _y + _x) * 4);
return buffer_read(buffer, buffer_u32);
} #endregion
function buffer_setPixel(buffer, _w, _h, _x, _y, _c) { #region
if(_x < 0 || _y < 0 || _x >= _w || _y >= _h) return 0;
buffer_seek(buffer, buffer_seek_start, (_w * _y + _x) * 4);
buffer_write(buffer, buffer_u32, _c);
} #endregion
function buffer_compress_string(str) { #region
var _len = string_length(str);
var buffer = buffer_create(1, buffer_grow, 1);
buffer_write(buffer, buffer_string, str);
return buffer_compress(buffer, 0, buffer_get_size(buffer));
} #endregion

View file

@ -0,0 +1,103 @@
// 2024-04-26 10:28:10
function buffer_get_color(buffer, _x, _y, w, h) { #region
buffer_seek(buffer, buffer_seek_start, (w * _y + _x) * 4);
var c = buffer_read(buffer, buffer_u32);
return c;
} #endregion
function buffer_get_string(buffer, text = true, limit = 400) { #region
if(is_array(buffer)) return "[buffer array]";
if(!buffer_exists(buffer)) return "";
buffer_seek(buffer, buffer_seek_start, 0);
var len = min(limit, buffer_get_size(buffer));
var ss = "";
for (var i = 0; i < len; i++) {
var _r = buffer_read(buffer, buffer_u8);
var _s = text? chr(_r) : dec_to_hex(_r, 2);
ss += _s;
if(!text && i % 2) ss += " ";
}
return ss;
} #endregion
function buffer_from_string(str) { #region
var _b = buffer_create(string_length(str) * 1, buffer_fast, 1);
for( var i = 1; i <= string_length(str); i++ )
buffer_write(_b, buffer_u8, ord(string_char_at(str, i)));
return _b;
} #endregion
function buffer_from_surface(surface, header = true) { #region
static header_length = 24;
if(!is_surface(surface)) return noone;
var bitSize = surface_format_get_bytes(surface_get_format(surface));
var _b = buffer_create((header_length * header) + surface_get_width_safe(surface) * surface_get_height_safe(surface) * bitSize, buffer_fixed, 1);
if(header) {
buffer_write(_b, buffer_text, "PXCS");
buffer_write(_b, buffer_u16, surface_get_width_safe(surface));
buffer_write(_b, buffer_u16, surface_get_height_safe(surface));
buffer_write(_b, buffer_u8, surface_get_format(surface));
}
buffer_get_surface(_b, surface, header_length * header);
return _b;
} #endregion
function buffer_from_file(path) { #region
if(!file_exists_empty(path)) return;
var _b = buffer_load(path);
return _b;
} #endregion
function buffer_read_at(buffer, position, type) { #region
buffer_seek(buffer, buffer_seek_start, position);
return buffer_read(buffer, type);
} #endregion
function buffer_serialize(buffer, compress = true) { #region
INLINE
if(!buffer_exists(buffer)) return "";
if(compress) {
var comp = buffer_compress(buffer, 0, buffer_get_size(buffer));
return buffer_base64_encode(comp, 0, buffer_get_size(comp));
}
return buffer_base64_encode(buffer, 0, buffer_get_size(buffer));
} #endregion
function buffer_deserialize(buffer, compress = true) { #region
INLINE
var buff = buffer_base64_decode(buffer);
if(!compress) return buff;
return buffer_decompress(buff);
} #endregion
function buffer_getPixel(buffer, _w, _h, _x, _y) { #region
if(_x < 0 || _y < 0 || _x >= _w || _y >= _h) return 0;
buffer_seek(buffer, buffer_seek_start, (_w * _y + _x) * 4);
return buffer_read(buffer, buffer_u32);
} #endregion
function buffer_setPixel(buffer, _w, _h, _x, _y, _c) { #region
if(_x < 0 || _y < 0 || _x >= _w || _y >= _h) return 0;
buffer_seek(buffer, buffer_seek_start, (_w * _y + _x) * 4);
buffer_write(buffer, buffer_u32, _c);
} #endregion
function buffer_compress_string(str) { #region
var _len = string_length(str);
var buffer = buffer_create(1, buffer_grow, 1);
buffer_write(buffer, buffer_string, str);
return buffer_compress(buffer, 0, buffer_get_size(buffer));
} #endregion

View file

@ -0,0 +1,167 @@
// 2024-04-26 12:05:51
function __vec3Sub(_x = 0, _y = _x, _z = _x) : __vec3(_x, _y, _z) constructor {
old = false;
connected = [];
static smooth = function() {
if(array_empty(connected)) return;
var _n = array_length(connected);
var _k = _n / 2;
var beta = 3 / (5 * _k);
var _sx = x * (1 - _k * beta);
var _sy = y * (1 - _k * beta);
var _sz = z * (1 - _k * beta);
var _cx = 0;
var _cy = 0;
var _cz = 0;
for( var i = 0; i < _n; i++ ) {
_cx += connected[i].x;
_cy += connected[i].y;
_cz += connected[i].z;
}
x = _cx * 0.5 * beta + _sx;
y = _cy * 0.5 * beta + _sy;
z = _cz * 0.5 * beta + _sz;
}
static connectTo = function(point) { array_push(connected, point); }
}
function __3dICOSphere(radius = 0.5, level = 2, smt = false) : __3dObject() constructor {
VF = global.VF_POS_NORM_TEX_COL;
render_type = pr_trianglelist;
self.radius = radius;
self.level = level;
self.smooth = smt;
_vhash = ds_map_create();
static getVertex = function(vertex) {
var h = $"[{vertex.x},{vertex.y},{vertex.z}]";
if(ds_map_exists(_vhash, h))
return _vhash[? h];
_vhash[? h] = vertex;
return vertex;
}
static initModel = function() { // swap H, V because fuck me
var _vertices = ds_list_create();
var _normals = ds_list_create();
var phi = (1 + sqrt(5)) * 0.5; // golden ratio
var a = 1.0;
var b = 1.0 / phi;
icoverts = [
new __vec3Sub( 1, 1, 1)._normalize()._multiply(radius),
new __vec3Sub( 0, b, -a)._normalize()._multiply(radius),
new __vec3Sub( b, a, 0)._normalize()._multiply(radius),
new __vec3Sub(-b, a, 0)._normalize()._multiply(radius),
new __vec3Sub( 0, b, a)._normalize()._multiply(radius),
new __vec3Sub( 0, -b, a)._normalize()._multiply(radius),
new __vec3Sub(-a, 0, b)._normalize()._multiply(radius),
new __vec3Sub( 0, -b, -a)._normalize()._multiply(radius),
new __vec3Sub( a, 0, -b)._normalize()._multiply(radius),
new __vec3Sub( a, 0, b)._normalize()._multiply(radius),
new __vec3Sub(-a, 0, -b)._normalize()._multiply(radius),
new __vec3Sub( b, -a, 0)._normalize()._multiply(radius),
new __vec3Sub(-b, -a, 0)._normalize()._multiply(radius),
]
array_foreach(icoverts, function(vert) { vert.old = true; });
// Generate icosphere vertices
ds_list_add(_vertices, icoverts[ 3], icoverts[ 1], icoverts[ 2]);
ds_list_add(_vertices, icoverts[ 2], icoverts[ 4], icoverts[ 3]);
ds_list_add(_vertices, icoverts[ 6], icoverts[ 4], icoverts[ 5]);
ds_list_add(_vertices, icoverts[ 5], icoverts[ 4], icoverts[ 9]);
ds_list_add(_vertices, icoverts[ 8], icoverts[ 1], icoverts[ 7]);
ds_list_add(_vertices, icoverts[ 7], icoverts[ 1], icoverts[10]);
ds_list_add(_vertices, icoverts[12], icoverts[ 5], icoverts[11]);
ds_list_add(_vertices, icoverts[11], icoverts[ 7], icoverts[12]);
ds_list_add(_vertices, icoverts[10], icoverts[ 3], icoverts[ 6]);
ds_list_add(_vertices, icoverts[ 6], icoverts[12], icoverts[10]);
ds_list_add(_vertices, icoverts[ 9], icoverts[ 2], icoverts[ 8]);
ds_list_add(_vertices, icoverts[ 8], icoverts[11], icoverts[ 9]);
ds_list_add(_vertices, icoverts[ 3], icoverts[ 4], icoverts[ 6]);
ds_list_add(_vertices, icoverts[ 9], icoverts[ 4], icoverts[ 2]);
ds_list_add(_vertices, icoverts[10], icoverts[ 1], icoverts[ 3]);
ds_list_add(_vertices, icoverts[ 2], icoverts[ 1], icoverts[ 8]);
ds_list_add(_vertices, icoverts[12], icoverts[ 7], icoverts[10]);
ds_list_add(_vertices, icoverts[ 8], icoverts[ 7], icoverts[11]);
ds_list_add(_vertices, icoverts[ 6], icoverts[ 5], icoverts[12]);
ds_list_add(_vertices, icoverts[11], icoverts[ 5], icoverts[ 9]);
var lv = min(level, 5);
repeat(lv) { #region subdivide
ds_map_clear(_vhash);
var newVertices = ds_list_create();
for (var i = 0, n = ds_list_size(_vertices) / 3; i < n; i++) {
var v1 = _vertices[| i * 3 + 0];
var v2 = _vertices[| i * 3 + 1];
var v3 = _vertices[| i * 3 + 2];
var mid12Pos = getVertex(new __vec3Sub(v1.add(v2).divide(2)));
var mid23Pos = getVertex(new __vec3Sub(v2.add(v3).divide(2)));
var mid31Pos = getVertex(new __vec3Sub(v3.add(v1).divide(2)));
v1.connectTo(mid12Pos); v1.connectTo(mid31Pos);
v2.connectTo(mid12Pos); v2.connectTo(mid23Pos);
v3.connectTo(mid23Pos); v3.connectTo(mid31Pos);
ds_list_add(newVertices, v1, mid12Pos, mid31Pos);
ds_list_add(newVertices, mid12Pos, v2, mid23Pos);
ds_list_add(newVertices, mid31Pos, mid23Pos, v3);
ds_list_add(newVertices, mid12Pos, mid23Pos, mid31Pos);
}
for (var i = 0, n = ds_list_size(newVertices); i < n; i++) {
var _v = newVertices[| i];
if(_v.old) _v.smooth();
_v.old = true;
_v.connected = [];
}
ds_list_destroy(_vertices);
_vertices = newVertices;
} #endregion
for( var i = 0, n = ds_list_size(_vertices) / 3; i < n; i++ ) { #region normal, uv generation
var _v0 = _vertices[| i * 3 + 0];
var _v1 = _vertices[| i * 3 + 1];
var _v2 = _vertices[| i * 3 + 2];
if(smooth) {
ds_list_add(_normals, _v0.normalize(), _v1.normalize(), _v2.normalize());
} else {
var _n = _v2.subtract(_v0).cross(_v1.subtract(_v0));
ds_list_add(_normals, _n, _n, _n);
}
} #endregion
vertex = [ array_create(ds_list_size(_vertices)) ];
for( var i = 0, n = ds_list_size(_vertices); i < n; i++ ) {
var _v = _vertices[| i];
var _n = _normals[| i];
var _ha = point_direction(0, 0, _v.x, _v.y);
var _va = (point_direction(0, 0, _v.x, _v.z) + 90) % 360;
if(_va > 180) _va = 360 - _va;
vertex[0][i] = new __vertex(_v.x, _v.y, _v.z).setNormal(_n.x, _n.y, _n.z).setUV(_ha / 360, _va / 180);
}
ds_list_destroy(_vertices);
ds_list_destroy(_normals);
VB = build();
} initModel();
static onParameterUpdate = initModel;
}

View file

@ -0,0 +1,167 @@
// 2024-04-26 12:02:52
function __vec3Sub(_x = 0, _y = _x, _z = _x) : __vec3(_x, _y, _z) constructor {
old = false;
connected = [];
static smooth = function() {
if(array_empty(connected)) return;
var _n = array_length(connected);
var _k = _n / 2;
var beta = 3 / (5 * _k);
var _sx = x * (1 - _k * beta);
var _sy = y * (1 - _k * beta);
var _sz = z * (1 - _k * beta);
var _cx = 0;
var _cy = 0;
var _cz = 0;
for( var i = 0; i < _n; i++ ) {
_cx += connected[i].x;
_cy += connected[i].y;
_cz += connected[i].z;
}
x = _cx * 0.5 * beta + _sx;
y = _cy * 0.5 * beta + _sy;
z = _cz * 0.5 * beta + _sz;
}
static connectTo = function(point) { array_push(connected, point); }
}
function __3dICOSphere(radius = 0.5, level = 2, smt = false) : __3dObject() constructor {
VF = global.VF_POS_NORM_TEX_COL;
render_type = pr_trianglelist;
self.radius = radius;
self.level = level;
self.smooth = smt;
_vhash = ds_map_create();
static getVertex = function(vertex) {
var h = $"[{vertex.x},{vertex.y},{vertex.z}]";
if(ds_map_exists(_vhash, h))
return _vhash[? h];
_vhash[? h] = vertex;
return vertex;
}
static initModel = function() { // swap H, V because fuck me
var _vertices = ds_list_create();
var _normals = ds_list_create();
var phi = (1 + sqrt(5)) * 0.5; // golden ratio
var a = 1.0;
var b = 1.0 / phi;
icoverts = [
new __vec3Sub( 1, 1, 1)._normalize()._multiply(radius),
new __vec3Sub( 0, b, -a)._normalize()._multiply(radius),
new __vec3Sub( b, a, 0)._normalize()._multiply(radius),
new __vec3Sub(-b, a, 0)._normalize()._multiply(radius),
new __vec3Sub( 0, b, a)._normalize()._multiply(radius),
new __vec3Sub( 0, -b, a)._normalize()._multiply(radius),
new __vec3Sub(-a, 0, b)._normalize()._multiply(radius),
new __vec3Sub( 0, -b, -a)._normalize()._multiply(radius),
new __vec3Sub( a, 0, -b)._normalize()._multiply(radius),
new __vec3Sub( a, 0, b)._normalize()._multiply(radius),
new __vec3Sub(-a, 0, -b)._normalize()._multiply(radius),
new __vec3Sub( b, -a, 0)._normalize()._multiply(radius),
new __vec3Sub(-b, -a, 0)._normalize()._multiply(radius),
]
array_foreach(icoverts, function(vert) { vert.old = true; });
// Generate icosphere vertices
ds_list_add(_vertices, icoverts[ 3], icoverts[ 1], icoverts[ 2]);
ds_list_add(_vertices, icoverts[ 2], icoverts[ 4], icoverts[ 3]);
ds_list_add(_vertices, icoverts[ 6], icoverts[ 4], icoverts[ 5]);
ds_list_add(_vertices, icoverts[ 5], icoverts[ 4], icoverts[ 9]);
ds_list_add(_vertices, icoverts[ 8], icoverts[ 1], icoverts[ 7]);
ds_list_add(_vertices, icoverts[ 7], icoverts[ 1], icoverts[10]);
ds_list_add(_vertices, icoverts[12], icoverts[ 5], icoverts[11]);
ds_list_add(_vertices, icoverts[11], icoverts[ 7], icoverts[12]);
ds_list_add(_vertices, icoverts[10], icoverts[ 3], icoverts[ 6]);
ds_list_add(_vertices, icoverts[ 6], icoverts[12], icoverts[10]);
ds_list_add(_vertices, icoverts[ 9], icoverts[ 2], icoverts[ 8]);
ds_list_add(_vertices, icoverts[ 8], icoverts[11], icoverts[ 9]);
ds_list_add(_vertices, icoverts[ 3], icoverts[ 4], icoverts[ 6]);
ds_list_add(_vertices, icoverts[ 9], icoverts[ 4], icoverts[ 2]);
ds_list_add(_vertices, icoverts[10], icoverts[ 1], icoverts[ 3]);
ds_list_add(_vertices, icoverts[ 2], icoverts[ 1], icoverts[ 8]);
ds_list_add(_vertices, icoverts[12], icoverts[ 7], icoverts[10]);
ds_list_add(_vertices, icoverts[ 8], icoverts[ 7], icoverts[11]);
ds_list_add(_vertices, icoverts[ 6], icoverts[ 5], icoverts[12]);
ds_list_add(_vertices, icoverts[11], icoverts[ 5], icoverts[ 9]);
var lv = min(level, 5);
repeat(lv) { #region subdivide
ds_map_clear(_vhash);
var newVertices = ds_list_create();
for (var i = 0, n = ds_list_size(_vertices) / 3; i < n; i++) {
var v1 = _vertices[| i * 3 + 0];
var v2 = _vertices[| i * 3 + 1];
var v3 = _vertices[| i * 3 + 2];
var mid12Pos = getVertex(new __vec3Sub(v1.add(v2).divide(2)));
var mid23Pos = getVertex(new __vec3Sub(v2.add(v3).divide(2)));
var mid31Pos = getVertex(new __vec3Sub(v3.add(v1).divide(2)));
v1.connectTo(mid12Pos); v1.connectTo(mid31Pos);
v2.connectTo(mid12Pos); v2.connectTo(mid23Pos);
v3.connectTo(mid23Pos); v3.connectTo(mid31Pos);
ds_list_add(newVertices, v1, mid12Pos, mid31Pos);
ds_list_add(newVertices, mid12Pos, v2, mid23Pos);
ds_list_add(newVertices, mid31Pos, mid23Pos, v3);
ds_list_add(newVertices, mid12Pos, mid23Pos, mid31Pos);
}
for (var i = 0, n = ds_list_size(newVertices); i < n; i++) {
var _v = newVertices[| i];
if(_v.old) _v.smooth();
_v.old = true;
_v.connected = [];
}
ds_list_destroy(_vertices);
_vertices = newVertices;
} #endregion
for( var i = 0, n = ds_list_size(_vertices) / 3; i < n; i++ ) { #region normal, uv generation
var _v0 = _vertices[| i * 3 + 0];
var _v1 = _vertices[| i * 3 + 1];
var _v2 = _vertices[| i * 3 + 2];
if(smooth) {
ds_list_add(_normals, _v0.normalize(), _v1.normalize(), _v2.normalize());
} else {
var _n = _v2.subtract(_v0).cross(_v1.subtract(_v0));
ds_list_add(_normals, _n, _n, _n);
}
} #endregion
vertex = [ array_create(ds_list_size(_vertices)) ];
for( var i = 0, n = ds_list_size(_vertices); i < n; i++ ) {
var _v = _vertices[| i];
var _n = _normals[| i];
var _ha = point_direction(0, 0, _v.x, _v.y);
var _va = (point_direction(0, 0, _v.x, _v.z) + 90) % 360;
if(_va > 180) _va = 360 - _va;
vertex[0][i] = new __vertex(_v.x, _v.y, _v.z).setNormal(_n.x, _n.y, _n.z).setUV(_ha / 360, _va / 180);
}
ds_list_destroy(_vertices);
ds_list_destroy(_normals);
VB = build();
} initModel();
static onParameterUpdate = initModel;
}

View file

@ -0,0 +1,57 @@
// 2024-04-26 10:18:35
function Node_3D_Mesh_Extrude(_x, _y, _group = noone) : Node_3D_Mesh(_x, _y, _group) constructor {
name = "Surface Extrude";
object_class = __3dSurfaceExtrude;
inputs[| in_mesh + 0] = nodeValue("Surface in", self, JUNCTION_CONNECT.input, VALUE_TYPE.d3Material, new __d3dMaterial())
.setVisible(true, true);
inputs[| in_mesh + 1] = nodeValue("Height map", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, noone);
inputs[| in_mesh + 2] = nodeValue("Smooth", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false)
inputs[| in_mesh + 3] = nodeValue("Always update", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
input_display_list = [
__d3d_input_list_mesh,
__d3d_input_list_transform,
["Extrude", false], in_mesh + 0, in_mesh + 1, in_mesh + 3,
]
temp_surface = [ noone, noone ];
static processData = function(_output, _data, _output_index, _array_index = 0) { #region
var _mat = _data[in_mesh + 0];
if(!is_instanceof(_mat, __d3dMaterial)) return noone;
var _hght = _data[in_mesh + 1];
var _smt = _data[in_mesh + 2];
var _updt = _data[in_mesh + 3];
var _surf = _mat.surface;
if(!is_surface(_surf)) return noone;
temp_surface[0] = surface_cvt_8unorm(temp_surface[0], _surf);
temp_surface[1] = surface_cvt_8unorm(temp_surface[1], _hght);
var object = getObject(_array_index);
object.checkParameter({ surface: temp_surface[0], height: temp_surface[1], smooth: _smt }, _updt);
var _matN = _mat.clone();
var _nSurf = surface_create(surface_get_width(_surf), surface_get_height(_surf));
surface_set_shader(_nSurf, sh_d3d_extrude_extends);
shader_set_dim("dimension", _surf);
draw_surface_safe(_surf);
surface_reset_shader();
_matN.surface = _nSurf;
object.materials = [ _matN ];
setTransform(object, _data);
return object;
} #endregion
static getPreviewValues = function() { return array_safe_get_fast(all_inputs, in_mesh + 0, noone); }
}

View file

@ -0,0 +1,57 @@
// 2024-04-26 10:18:30
function Node_3D_Mesh_Extrude(_x, _y, _group = noone) : Node_3D_Mesh(_x, _y, _group) constructor {
name = "Surface Extrude";
object_class = __3dSurfaceExtrude;
inputs[| in_mesh + 0] = nodeValue("Surface in", self, JUNCTION_CONNECT.input, VALUE_TYPE.d3Material, new __d3dMaterial())
.setVisible(true, true);
inputs[| in_mesh + 1] = nodeValue("Height map", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, noone);
inputs[| in_mesh + 2] = nodeValue("Smooth", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false)
inputs[| in_mesh + 3] = nodeValue("Always update", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
input_display_list = [
__d3d_input_list_mesh,
__d3d_input_list_transform,
["Extrude", false], in_mesh + 0, in_mesh + 1, in_mesh + 3,
]
temp_surface = [ noone, noone ];
static processData = function(_output, _data, _output_index, _array_index = 0) { #region
var _mat = _data[in_mesh + 0];
if(!is_instanceof(_mat, __d3dMaterial)) return noone;
var _hght = _data[in_mesh + 1];
var _smt = _data[in_mesh + 2];
var _updt = _data[in_mesh + 3];
var _surf = _mat.surface;
if(!is_surface(_surf)) return noone;
temp_surface[0] = surface_cvt_8unorm(temp_surface[0], _surf);
temp_surface[1] = surface_cvt_8unorm(temp_surface[1], _hght);
var object = getObject(_array_index);
object.checkParameter({ surface: temp_surface[0], height: temp_surface[1], smooth: _smt }, _updt);
var _matN = _mat.clone();
var _nSurf = surface_create(surface_get_width(_surf), surface_get_height(_surf));
surface_set_shader(_nSurf, sh_d3d_extrude_extends);
shader_set_dim("dimension", _surf);
draw_surface_safe(_surf);
surface_reset_shader();
_matN.surface = _nSurf;
object.materials = [ _matN ];
setTransform(object, _data);
return object;
} #endregion
static getPreviewValues = function() { return array_safe_get_fast(all_inputs, in_mesh + 0, noone); }
}

View file

@ -1,4 +1,4 @@
// 2024-04-25 16:11:38
// 2024-04-26 08:55:06
function Node_Colors_Replace(_x, _y, _group = noone) : Node_Processor(_x, _y, _group) constructor {
name = "Replace Colors";

View file

@ -1,4 +1,4 @@
// 2024-04-25 16:11:37
// 2024-04-26 08:55:01
function Node_Colors_Replace(_x, _y, _group = noone) : Node_Processor(_x, _y, _group) constructor {
name = "Replace Colors";

View file

@ -0,0 +1,105 @@
// 2024-04-26 09:11:44
function Node_Iterate_Inline(_x, _y, _group = noone) : Node_Collection_Inline(_x, _y, _group) constructor {
name = "Loop";
color = COLORS.node_blend_loop;
icon = THEME.loop;
icon_24 = THEME.loop_24;
is_root = false;
inputs[| 0] = nodeValue("Repeat", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 1 )
.uncache();
managedRenderOrder = true;
attributes.junc_in = [ "", 0 ];
attributes.junc_out = [ "", 0 ];
junc_in = noone;
junc_out = noone;
value_buffer = undefined;
iterated = 0;
static getIterationCount = function() { return getInputData(0); }
static bypassConnection = function() { return iterated > 0 && !is_undefined(value_buffer);
} #endregion
static bypassNextNode = function() { #region
return iterated < getIterationCount() - 1;
} #endregion
static getNextNodes = function() { #region
LOG_BLOCK_START();
LOG_IF(global.FLAG.render == 1, "[outputNextNode] Get next node from inline iterate");
resetRender();
LOG_IF(global.FLAG.render == 1, $"Loop restart: iteration {iterated}");
var _nodes = __nodeLeafList(nodes);
array_push_unique(_nodes, junc_in.node);
iterated++;
LOG_BLOCK_END();
return _nodes;
} #endregion
static scanJunc = function() { #region
var node_in = PROJECT.nodeMap[? attributes.junc_in[0]];
var node_out = PROJECT.nodeMap[? attributes.junc_out[0]];
junc_in = node_in? node_in.inputs[| attributes.junc_in[1]] : noone;
junc_out = node_out? node_out.outputs[| attributes.junc_out[1]] : noone;
if(junc_in) { junc_in.value_from_loop = self; addNode(junc_in.node); }
if(junc_out) { array_push(junc_out.value_to_loop, self); addNode(junc_out.node); }
} #endregion
static updateValue = function() { #region
var type = junc_out.type;
var val = junc_out.getValue();
switch(type) {
case VALUE_TYPE.surface :
surface_array_free(value_buffer);
value_buffer = surface_array_clone(val);
break;
default :
value_buffer = variable_clone(val);
break;
}
} #endregion
static getValue = function(arr) { #region
INLINE
arr[@ 0] = value_buffer;
arr[@ 1] = junc_out;
} #endregion
static update = function() { #region
iteration_count = inputs[| 0].getValue();
iterated = 0;
value_buffer = undefined;
} #endregion
static drawConnections = function(params = {}) { #region
if(!active) return;
if(!junc_in || !junc_out) return;
if(!junc_in.node.active || !junc_out.node.active) return;
if(drawJuncConnection(junc_out, junc_in, params))
return self;
} #endregion
static postDeserialize = function() { #region
refreshMember();
scanJunc();
} #endregion
static onDestroy = function() { #region
if(junc_in) junc_in.value_from_loop = noone;
if(junc_out) array_remove(junc_out.value_to_loop, self);
} #endregion
}

View file

@ -0,0 +1,105 @@
// 2024-04-26 09:08:27
function Node_Iterate_Inline(_x, _y, _group = noone) : Node_Collection_Inline(_x, _y, _group) constructor {
name = "Loop";
color = COLORS.node_blend_loop;
icon = THEME.loop;
icon_24 = THEME.loop_24;
is_root = false;
inputs[| 0] = nodeValue("Repeat", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 1 )
.uncache();
managedRenderOrder = true;
attributes.junc_in = [ "", 0 ];
attributes.junc_out = [ "", 0 ];
junc_in = noone;
junc_out = noone;
value_buffer = undefined;
iterated = 0;
static getIterationCount = function() { return getInputData(0); }
static bypassConnection = function() { return iterated > 0 && !is_undefined(value_buffer);
} #endregion
static bypassNextNode = function() { #region
return iterated < getIterationCount() - 1;
} #endregion
static getNextNodes = function() { #region
LOG_BLOCK_START();
LOG_IF(global.FLAG.render == 1, "[outputNextNode] Get next node from inline iterate");
resetRender();
LOG_IF(global.FLAG.render == 1, $"Loop restart: iteration {iterated}");
var _nodes = __nodeLeafList(nodes);
array_push_unique(_nodes, junc_in.node);
iterated++;
LOG_BLOCK_END();
return _nodes;
} #endregion
static scanJunc = function() { #region
var node_in = PROJECT.nodeMap[? attributes.junc_in[0]];
var node_out = PROJECT.nodeMap[? attributes.junc_out[0]];
junc_in = node_in? node_in.inputs[| attributes.junc_in[1]] : noone;
junc_out = node_out? node_out.outputs[| attributes.junc_out[1]] : noone;
if(junc_in) { junc_in.value_from_loop = self; addNode(junc_in.node); }
if(junc_out) { array_push(junc_out.value_to_loop, self); addNode(junc_out.node); }
} #endregion
static updateValue = function() { #region
var type = junc_out.type;
var val = junc_out.getValue();
switch(type) {
case VALUE_TYPE.surface :
surface_array_free(value_buffer);
value_buffer = surface_array_clone(val);
break;
default :
value_buffer = variable_clone(val);
break;
}
} #endregion
static getValue = function(arr) { #region
INLINE
arr[@ 0] = value_buffer;
arr[@ 1] = junc_out;
} #endregion
static update = function() { #region
iteration_count = inputs[| 0].getValue();
iterated = 0;
value_buffer = undefined;
} #endregion
static drawConnections = function(params = {}) { #region
if(!active) return;
if(!junc_in || !junc_out) return;
if(!junc_in.node.active || !junc_out.node.active) return;
if(drawJuncConnection(junc_out, junc_in, params))
return self;
} #endregion
static postDeserialize = function() { #region
refreshMember();
scanJunc();
} #endregion
static onDestroy = function() { #region
if(junc_in) junc_in.value_from_loop = noone;
if(junc_out) array_remove(junc_out.value_to_loop, self);
} #endregion
}

View file

@ -0,0 +1,538 @@
// 2024-04-26 14:34:25
function Node_Line(_x, _y, _group = noone) : Node_Processor(_x, _y, _group) constructor {
name = "Line";
inputs[| 0] = nodeValue("Dimension", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, DEF_SURF )
.setDisplay(VALUE_DISPLAY.vector);
inputs[| 1] = nodeValue("Background", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
inputs[| 2] = nodeValue("Segment", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 1)
.setDisplay(VALUE_DISPLAY.slider, { range: [1, 32, 0.1] });
inputs[| 3] = nodeValue("Width", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 2, 2 ])
.setDisplay(VALUE_DISPLAY.vector);
inputs[| 4] = nodeValue("Wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0)
.setDisplay(VALUE_DISPLAY.slider, { range: [0, 16, 0.01] });
inputs[| 5] = nodeValue("Random seed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0);
inputs[| 6] = nodeValue("Rotation", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0)
.setDisplay(VALUE_DISPLAY.rotation);
inputs[| 7] = nodeValue("Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.pathnode, noone, "Draw line along path.")
.setVisible(true, true)
.setArrayDepth(1);
inputs[| 8] = nodeValue("Range", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [0, 1], "Range of the path to draw.")
.setDisplay(VALUE_DISPLAY.slider_range);
inputs[| 9] = nodeValue("Shift", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0)
.setDisplay(VALUE_DISPLAY._default, { slide_speed: 1 / 64 });
inputs[| 10] = nodeValue("Color over length", self, JUNCTION_CONNECT.input, VALUE_TYPE.gradient, new gradientObject(c_white) );
inputs[| 11] = nodeValue("Width over length", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, CURVE_DEF_11);
inputs[| 12] = nodeValue("Span width over path", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Apply the full 'width over length' to the trimmed path.");
inputs[| 13] = nodeValue("Round cap", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
inputs[| 14] = nodeValue("Round segment", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 4)
.setDisplay(VALUE_DISPLAY.slider, { range: [2, 16, 0.1] });
inputs[| 15] = nodeValue("Span color over path", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Apply the full 'color over length' to the trimmed path.");
inputs[| 16] = nodeValue("Greyscale over width", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
inputs[| 17] = nodeValue("1px mode", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Render pixel perfect 1px line.");
inputs[| 18] = nodeValue("Texture", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, noone);
inputs[| 19] = nodeValue("Fix length", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Fix length of each segment instead of segment count.");
inputs[| 20] = nodeValue("Segment length", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 4);
inputs[| 21] = nodeValue("Texture position", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ])
.setDisplay(VALUE_DISPLAY.vector);
inputs[| 22] = nodeValue("Texture rotation", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0)
.setDisplay(VALUE_DISPLAY.rotation);
inputs[| 23] = nodeValue("Texture scale", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ])
.setDisplay(VALUE_DISPLAY.vector);
inputs[| 24] = nodeValue("Random Blend", self, JUNCTION_CONNECT.input, VALUE_TYPE.gradient, new gradientObject(c_white) );
inputs[| 25] = nodeValue("Invert", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false );
inputs[| 26] = nodeValue("Clamp range", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false );
input_display_list = [
["Output", true], 0, 1,
["Line data", false], 6, 7, 19, 2, 20,
["Line settings", false], 17, 3, 11, 12, 8, 25, 9, 26, 13, 14,
["Wiggle", false], 4, 5,
["Render", false], 10, 24, 15, 16,
["Texture", false], 18, 21, 22, 23,
];
outputs[| 0] = nodeValue("Surface out", self, JUNCTION_CONNECT.output, VALUE_TYPE.surface, noone);
lines = [];
widthMap = ds_map_create();
attribute_surface_depth();
attribute_interpolation();
static drawOverlay = function(hover, active, _x, _y, _s, _mx, _my, _snx, _sny) { #region
draw_set_color(COLORS._main_accent);
for( var i = 0, n = array_length(lines); i < n; i++ ) {
var points = lines[i];
if(array_length(points) < 2) continue;
for( var j = 1; j < array_length(points); j++ ) {
var x0 = points[j - 1].x;
var y0 = points[j - 1].y;
var x1 = points[j].x;
var y1 = points[j].y;
x0 = _x + x0 * _s;
y0 = _y + y0 * _s;
x1 = _x + x1 * _s;
y1 = _y + y1 * _s;
draw_line(x0, y0, x1, y1);
}
}
} #endregion
static step = function() { #region
var px = !getInputData(17);
var _tex = inputs[| 18].value_from != noone;
var _flen = getInputData(19);
inputs[| 3].setVisible(px);
inputs[| 11].setVisible(px);
inputs[| 12].setVisible(px);
inputs[| 13].setVisible(px && !_tex);
inputs[| 14].setVisible(px);
inputs[| 18].setVisible(px);
inputs[| 15].setVisible(!_tex);
inputs[| 16].setVisible(!_tex);
inputs[| 2].setVisible(!_flen);
inputs[| 20].setVisible( _flen);
} #endregion
static onValueUpdate = function(index = 0) { #region
if(index == 11) ds_map_clear(widthMap);
} #endregion
static processData = function(_outSurf, _data, _output_index, _array_index) { #region
#region data
var _dim = _data[0];
var _bg = _data[1];
var _seg = _data[2];
var _wid = _data[3];
var _wig = _data[4];
var _sed = _data[5];
var _ang = _data[6] % 360;
var _pat = _data[7];
var _ratio = _data[8];
var _shift = _data[9];
var _color = _data[10];
var _widc = _data[11];
var _widap = _data[12];
var _cap = _data[13];
var _capP = _data[14];
var _colP = _data[15];
var _colW = _data[16];
var _1px = _data[17];
var _fixL = _data[19];
var _segL = _data[20];
var _tex = _data[18];
var _texPos = _data[21];
var _texRot = _data[22];
var _texSca = _data[23];
var _colb = _data[24];
var _ratInv = _data[25];
var _clamp = _data[26];
#endregion
if(IS_FIRST_FRAME || inputs[| 11].is_anim)
ds_map_clear(widthMap);
var __debug_timer = get_timer();
var _rangeMin = min(_ratio[0], _ratio[1]);
var _rangeMax = max(_ratio[0], _ratio[1]);
if(_rangeMax == 1) _rangeMax = 0.99999;
var _rtStr = min(_rangeMin, _rangeMax);
var _rtMax = max(_rangeMin, _rangeMax);
var _use_path = is_struct(_pat);
var _useTex = inputs[| 18].value_from != noone;
if(_useTex) {
_cap = false;
_1px = false;
}
if(_ang < 0) _ang = 360 + _ang;
inputs[| 6].setVisible(!_use_path);
random_set_seed(_sed);
var _sedIndex = 0;
_outSurf = surface_verify(_outSurf, _dim[0], _dim[1], attrDepth());
var p = new __vec2();
var _ox, _nx, _nx1, _oy, _ny, _ny1;
var _ow, _nw, _oa, _na, _oc, _nc, _owg, _nwg;
var _pathData = [];
if(_use_path) { #region
var lineLen = 1;
if(struct_has(_pat, "getLineCount"))
lineLen = _pat.getLineCount();
if(struct_has(_pat, "getPathData"))
_pathData = _pat.getPathData();
lines = array_verify(lines, lineLen);
var _lineAmo = 0;
if(_rtMax > 0)
for( var i = 0; i < lineLen; i++ ) {
var _useDistance = _fixL && struct_has(_pat, "getLength");
var _pathLength = _useDistance? _pat.getLength(i) : 1;
if(_pathLength == 0) continue;
var _pathStr = _rtStr;
var _pathEnd = _rtMax;
var _stepLen = min(_pathEnd, 1 / _seg); //Distance to move per step
if(_stepLen <= 0.00001) continue;
var _total = _pathEnd; //Length remaining
var _total_prev = _total; //Use to prevent infinite loop
var _freeze = 0; //Use to prevent infinite loop
var _prog_curr = _clamp? _shift : frac(_shift); //Pointer to the current position
var _prog_next = 0;
var _prog = _prog_curr + 1; //Record previous position to delete from _total
var _prog_total = 0; //Record the distance the pointer has moved so far
var points = is_array(lines[i])? lines[i] : [];
var pointArrLen = array_length(points);
var pointAmo = 0;
var wght;
var _pathPng;
if(_useDistance) {
_pathStr *= _pathLength;
_pathEnd *= _pathLength;
_stepLen = min(_segL, _pathEnd);
_total *= _pathLength;
_total_prev = _total;
_prog_curr *= _pathLength;
}
var _segLength = struct_has(_pat, "getAccuLength")? _pat.getAccuLength(i) : [];
var _segLengthAmo = array_length(_segLength);
var _segIndex = 0;
var _segIndexPrev = 0;
if(_segLengthAmo)
while(_prog_curr > _segLength[_segIndex]) {
_segIndex++;
if(_segIndex == _segLengthAmo) {
_segIndex = 0;
break;
}
}
// print($"===== {_prog_curr} / {_segLength} : {_segIndex} - {_pathLength} =====");
while(true) {
wght = 1;
_segIndexPrev = _segIndex;
if(_useDistance) {
var segmentLength = array_safe_get_fast(_segLength, _segIndex, _pathLength);
_prog_next = min(_prog_curr + _stepLen, _pathLength, segmentLength);
_pathPng = _ratInv? _pathLength - _prog_curr : _prog_curr;
//print($"{segmentLength}/{_pathLength} = {_prog_next}");
if(_prog_next == segmentLength) _segIndex++;
var _pp = _clamp? clamp(_pathPng, 0, _pathLength) : _pathPng;
// print($"_pp = {_pp}, total = {_total}");
p = _pat.getPointDistance(_pp, i, p);
if(struct_has(_pat, "getWeightDistance"))
wght = _pat.getWeightDistance(_pp, i);
} else {
_prog_next = min(_prog_curr + _stepLen, 1); //Move forward _stepLen or _total (if less) stop at 1
_pathPng = _ratInv? 1 - _prog_curr : _prog_curr;
var _pp = _clamp? clamp(_pathPng, 0, 1) : _pathPng
p = _pat.getPointRatio(_pp, i, p);
if(struct_has(_pat, "getWeightRatio"))
wght = _pat.getWeightRatio(_pp, i);
}
_nx = p.x;
_ny = p.y;
if(_total < _pathEnd) { //Do not wiggle the last point.
var _d = point_direction(_ox, _oy, _nx, _ny);
_nx += lengthdir_x(random1D(_sed + _sedIndex, -_wig, _wig), _d + 90); _sedIndex++;
_ny += lengthdir_y(random1D(_sed + _sedIndex, -_wig, _wig), _d + 90); _sedIndex++;
}
if(_prog_total >= _pathStr) { //Do not add point before range start. Do this instead of starting at _rtStr to prevent wiggle.
var _pntData;
if(pointAmo < pointArrLen && is_struct(points[pointAmo])) {
_pntData = points[pointAmo];
_pntData.x = _nx;
_pntData.y = _ny;
_pntData.prog = (_prog_total - _pathStr) / (_pathEnd - _pathStr);
_pntData.progCrop = _prog_curr / _pathLength;
_pntData.weight = wght;
} else {
_pntData = {
x: _nx,
y: _ny,
prog: (_prog_total - _pathStr) / (_pathEnd - _pathStr),
progCrop: _prog_curr / _pathLength,
weight: wght
}
points[pointAmo] = _pntData;
}
pointAmo++;
}
if(_total <= 0) break;
if(_prog_next == _prog_curr && _segIndexPrev == _segIndex) { print("Terminate line not moving"); break; }
else if(_prog_next > _prog_curr) {
_prog_total += _prog_next - _prog_curr;
_total -= _prog_next - _prog_curr;
}
_stepLen = min(_stepLen, _total);
_prog_curr = _prog_next;
_ox = _nx;
_oy = _ny;
if(_total_prev == _total && _segIndexPrev == _segIndex && ++_freeze > 16) { print("Terminate line not moving"); break; }
_total_prev = _total;
if(_segIndex >= _segLengthAmo) { print("Terminate line finish last segment"); break; }
}
array_resize(points, pointAmo);
lines[_lineAmo++] = points;
}
array_resize(lines, _lineAmo);
#endregion
} else { #region
var x0, y0, x1, y1;
var _0 = point_rectangle_overlap(_dim[0], _dim[1], (_ang + 180) % 360);
var _1 = point_rectangle_overlap(_dim[0], _dim[1], _ang);
x0 = _0[0]; y0 = _0[1];
x1 = _1[0]; y1 = _1[1];
var _l = point_distance(x0, y0, x1, y1);
var _d = point_direction(x0, y0, x1, y1);
var _od = _d, _nd = _d;
var ww = _rtMax / _seg;
var _total = _rtMax;
var _prog_curr = frac(_shift) - ww;
var _prog = _prog_curr + 1;
var _prog_total = 0;
var points = [];
while(_total > 0) {
if(_prog_curr >= 1) _prog_curr = 0;
else _prog_curr = min(_prog_curr + min(_total, ww), 1);
_prog_total += min(_total, ww);
_nx = x0 + lengthdir_x(_l * _prog_curr, _d);
_ny = y0 + lengthdir_y(_l * _prog_curr, _d);
var wgLen = random1D(_sed + _sedIndex, -_wig, _wig); _sedIndex++;
_nx += lengthdir_x(wgLen, _d + 90);
_ny += lengthdir_y(wgLen, _d + 90);
if(_prog_total > _rtStr) //prevent drawing point before range start.
array_push(points, { x: _nx, y: _ny, prog: _prog_total / _rtMax, progCrop: _prog_curr, weight: 1 });
if(_prog_curr > _prog)
_total -= (_prog_curr - _prog);
_prog = _prog_curr;
_ox = _nx;
_oy = _ny;
}
lines = [ points ];
} #endregion
#region draw
surface_set_target(_outSurf);
if(_bg) draw_clear_alpha(0, 1);
else DRAW_CLEAR
if(_useTex) { #region
var tex = surface_get_texture(_tex);
shader_set(sh_draw_mapping);
shader_set_f("position", _texPos);
shader_set_f("rotation", degtorad(_texRot));
shader_set_f("scale", _texSca);
shader_set_interpolation(_tex);
} #endregion
for( var i = 0, n = array_length(lines); i < n; i++ ) {
var points = lines[i];
if(array_length(points) < 2) continue;
var _caps = [];
if(_useTex) draw_primitive_begin_texture(pr_trianglestrip, tex);
else draw_primitive_begin(pr_trianglestrip);
random_set_seed(_sed + i);
var pxs = [];
var dat = array_safe_get_fast(_pathData, i, noone);
var _col_base = dat == noone? _colb.eval(random(1)) : dat.color;
for( var j = 0; j < array_length(points); j++ ) {
var p0 = points[j];
var _nx = p0.x - 0.5 * _1px;
var _ny = p0.y - 0.5 * _1px;
var prog = p0.prog;
var prgc = p0.progCrop;
var _dir = j? point_direction(_ox, _oy, _nx, _ny) : 0;
var widProg = value_snap_real(_widap? prog : prgc, 0.01);
_nw = random_range(_wid[0], _wid[1]);
if(!ds_map_exists(widthMap, widProg))
widthMap[? widProg] = eval_curve_x(_widc, widProg, 0.1);
_nw *= widthMap[? widProg];
_nw *= p0.weight;
_nc = colorMultiply(_col_base, _color.eval(_colP? prog : prgc));
if(_cap) { #region
if(j == 1) {
_d = _dir + 180;
_caps[0] = [ _oc, _ox, _oy, _ow / 2, _d - 90, _d ];
_caps[1] = [ _oc, _ox, _oy, _ow / 2, _d, _d + 90 ];
}
if(j == array_length(points) - 1) {
_d = _dir;
_caps[2] = [ _nc, _nx, _ny, _nw / 2, _d - 90, _d ];
_caps[3] = [ _nc, _nx, _ny, _nw / 2, _d, _d + 90 ];
}
} #endregion
if(_1px) { #region
if(j) {
var dst = point_distance(_ox, _oy, _nx, _ny);
if(dst <= 1 && i < array_length(points) - 1) continue;
draw_line_color(_ox, _oy, _nx, _ny, _oc, _nc);
}
_ox = _nx;
_oy = _ny;
_oc = _nc;
#endregion
} else { #region
if(j) {
var _nd0 = _dir;
var _nd1 = _nd0;
if(j < array_length(points) - 1) {
var p2 = points[j + 1];
var _nnx = p2.x;
var _nny = p2.y;
_nd1 = point_direction(_nx, _ny, _nnx, _nny);
_nd = _nd0 + angle_difference(_nd1, _nd0) / 2;
} else
_nd = _nd0;
if(_useTex) {
var _len = array_length(points) - 1;
var ox0 = _ox + lengthdir_x(_ow / 2, _od + 90);
var oy0 = _oy + lengthdir_y(_ow / 2, _od + 90);
var nx0 = _nx + lengthdir_x(_nw / 2, _nd + 90);
var ny0 = _ny + lengthdir_y(_nw / 2, _nd + 90);
var ox1 = _ox + lengthdir_x(_ow / 2, _od + 90 + 180);
var oy1 = _oy + lengthdir_y(_ow / 2, _od + 90 + 180);
var nx1 = _nx + lengthdir_x(_nw / 2, _nd + 90 + 180);
var ny1 = _ny + lengthdir_y(_nw / 2, _nd + 90 + 180);
draw_vertex_texture_color(ox0, oy0, 0, (j - 1) / _len, _oc, 1);
draw_vertex_texture_color(ox1, oy1, 1, (j - 1) / _len, _oc, 1);
draw_vertex_texture_color(nx0, ny0, 0, (j - 0) / _len, _nc, 1);
draw_vertex_texture_color(nx1, ny1, 1, (j - 0) / _len, _nc, 1);
} else if(_colW)
draw_line_width2_angle_width(_ox, _oy, _nx, _ny, _ow, _nw, _od + 90, _nd + 90, merge_color(_oc, c_black, 0.5), merge_color(_nc, c_black, 0.5));
else
draw_line_width2_angle(_ox, _oy, _nx, _ny, _ow, _nw, _od + 90, _nd + 90, _oc, _nc);
} else {
var p1 = points[j + 1];
_nd = point_direction(_nx, _ny, p1.x, p1.y);
}
_ox = _nx;
_oy = _ny;
_od = _nd;
_ow = _nw;
_oc = _nc;
}
#endregion
}
draw_primitive_end();
for( var j = 0, m = array_length(_caps); j < m; j++ ) {
var _cps = _caps[j];
draw_set_color(_cps[0]);
draw_circle_angle(_cps[1], _cps[2], _cps[3], _cps[4], _cps[5], _capP);
}
}
if(_useTex) shader_reset();
surface_reset_target();
#endregion
return _outSurf;
} #endregion
}

View file

@ -0,0 +1,538 @@
// 2024-04-26 14:33:39
function Node_Line(_x, _y, _group = noone) : Node_Processor(_x, _y, _group) constructor {
name = "Line";
inputs[| 0] = nodeValue("Dimension", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, DEF_SURF )
.setDisplay(VALUE_DISPLAY.vector);
inputs[| 1] = nodeValue("Background", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
inputs[| 2] = nodeValue("Segment", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 1)
.setDisplay(VALUE_DISPLAY.slider, { range: [1, 32, 0.1] });
inputs[| 3] = nodeValue("Width", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 2, 2 ])
.setDisplay(VALUE_DISPLAY.vector);
inputs[| 4] = nodeValue("Wiggle", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0)
.setDisplay(VALUE_DISPLAY.slider, { range: [0, 16, 0.01] });
inputs[| 5] = nodeValue("Random seed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0);
inputs[| 6] = nodeValue("Rotation", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0)
.setDisplay(VALUE_DISPLAY.rotation);
inputs[| 7] = nodeValue("Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.pathnode, noone, "Draw line along path.")
.setVisible(true, true)
.setArrayDepth(1);
inputs[| 8] = nodeValue("Range", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [0, 1], "Range of the path to draw.")
.setDisplay(VALUE_DISPLAY.slider_range);
inputs[| 9] = nodeValue("Shift", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0)
.setDisplay(VALUE_DISPLAY._default, { slide_speed: 1 / 64 });
inputs[| 10] = nodeValue("Color over length", self, JUNCTION_CONNECT.input, VALUE_TYPE.gradient, new gradientObject(c_white) );
inputs[| 11] = nodeValue("Width over length", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, CURVE_DEF_11);
inputs[| 12] = nodeValue("Span width over path", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Apply the full 'width over length' to the trimmed path.");
inputs[| 13] = nodeValue("Round cap", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
inputs[| 14] = nodeValue("Round segment", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 4)
.setDisplay(VALUE_DISPLAY.slider, { range: [2, 16, 0.1] });
inputs[| 15] = nodeValue("Span color over path", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Apply the full 'color over length' to the trimmed path.");
inputs[| 16] = nodeValue("Greyscale over width", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
inputs[| 17] = nodeValue("1px mode", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Render pixel perfect 1px line.");
inputs[| 18] = nodeValue("Texture", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, noone);
inputs[| 19] = nodeValue("Fix length", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Fix length of each segment instead of segment count.");
inputs[| 20] = nodeValue("Segment length", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 4);
inputs[| 21] = nodeValue("Texture position", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 0, 0 ])
.setDisplay(VALUE_DISPLAY.vector);
inputs[| 22] = nodeValue("Texture rotation", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 0)
.setDisplay(VALUE_DISPLAY.rotation);
inputs[| 23] = nodeValue("Texture scale", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ])
.setDisplay(VALUE_DISPLAY.vector);
inputs[| 24] = nodeValue("Random Blend", self, JUNCTION_CONNECT.input, VALUE_TYPE.gradient, new gradientObject(c_white) );
inputs[| 25] = nodeValue("Invert", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false );
inputs[| 26] = nodeValue("Clamp range", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false );
input_display_list = [
["Output", true], 0, 1,
["Line data", false], 6, 7, 19, 2, 20,
["Line settings", false], 17, 3, 11, 12, 8, 25, 9, 26, 13, 14,
["Wiggle", false], 4, 5,
["Render", false], 10, 24, 15, 16,
["Texture", false], 18, 21, 22, 23,
];
outputs[| 0] = nodeValue("Surface out", self, JUNCTION_CONNECT.output, VALUE_TYPE.surface, noone);
lines = [];
widthMap = ds_map_create();
attribute_surface_depth();
attribute_interpolation();
static drawOverlay = function(hover, active, _x, _y, _s, _mx, _my, _snx, _sny) { #region
draw_set_color(COLORS._main_accent);
for( var i = 0, n = array_length(lines); i < n; i++ ) {
var points = lines[i];
if(array_length(points) < 2) continue;
for( var j = 1; j < array_length(points); j++ ) {
var x0 = points[j - 1].x;
var y0 = points[j - 1].y;
var x1 = points[j].x;
var y1 = points[j].y;
x0 = _x + x0 * _s;
y0 = _y + y0 * _s;
x1 = _x + x1 * _s;
y1 = _y + y1 * _s;
draw_line(x0, y0, x1, y1);
}
}
} #endregion
static step = function() { #region
var px = !getInputData(17);
var _tex = inputs[| 18].value_from != noone;
var _flen = getInputData(19);
inputs[| 3].setVisible(px);
inputs[| 11].setVisible(px);
inputs[| 12].setVisible(px);
inputs[| 13].setVisible(px && !_tex);
inputs[| 14].setVisible(px);
inputs[| 18].setVisible(px);
inputs[| 15].setVisible(!_tex);
inputs[| 16].setVisible(!_tex);
inputs[| 2].setVisible(!_flen);
inputs[| 20].setVisible( _flen);
} #endregion
static onValueUpdate = function(index = 0) { #region
if(index == 11) ds_map_clear(widthMap);
} #endregion
static processData = function(_outSurf, _data, _output_index, _array_index) { #region
#region data
var _dim = _data[0];
var _bg = _data[1];
var _seg = _data[2];
var _wid = _data[3];
var _wig = _data[4];
var _sed = _data[5];
var _ang = _data[6] % 360;
var _pat = _data[7];
var _ratio = _data[8];
var _shift = _data[9];
var _color = _data[10];
var _widc = _data[11];
var _widap = _data[12];
var _cap = _data[13];
var _capP = _data[14];
var _colP = _data[15];
var _colW = _data[16];
var _1px = _data[17];
var _fixL = _data[19];
var _segL = _data[20];
var _tex = _data[18];
var _texPos = _data[21];
var _texRot = _data[22];
var _texSca = _data[23];
var _colb = _data[24];
var _ratInv = _data[25];
var _clamp = _data[26];
#endregion
if(IS_FIRST_FRAME || inputs[| 11].is_anim)
ds_map_clear(widthMap);
var __debug_timer = get_timer();
var _rangeMin = min(_ratio[0], _ratio[1]);
var _rangeMax = max(_ratio[0], _ratio[1]);
if(_rangeMax == 1) _rangeMax = 0.99999;
var _rtStr = min(_rangeMin, _rangeMax);
var _rtMax = max(_rangeMin, _rangeMax);
var _use_path = is_struct(_pat);
var _useTex = inputs[| 18].value_from != noone;
if(_useTex) {
_cap = false;
_1px = false;
}
if(_ang < 0) _ang = 360 + _ang;
inputs[| 6].setVisible(!_use_path);
random_set_seed(_sed);
var _sedIndex = 0;
_outSurf = surface_verify(_outSurf, _dim[0], _dim[1], attrDepth());
var p = new __vec2();
var _ox, _nx, _nx1, _oy, _ny, _ny1;
var _ow, _nw, _oa, _na, _oc, _nc, _owg, _nwg;
var _pathData = [];
if(_use_path) { #region
var lineLen = 1;
if(struct_has(_pat, "getLineCount"))
lineLen = _pat.getLineCount();
if(struct_has(_pat, "getPathData"))
_pathData = _pat.getPathData();
lines = array_verify(lines, lineLen);
var _lineAmo = 0;
if(_rtMax > 0)
for( var i = 0; i < lineLen; i++ ) {
var _useDistance = _fixL && struct_has(_pat, "getLength");
var _pathLength = _useDistance? _pat.getLength(i) : 1;
if(_pathLength == 0) continue;
var _pathStr = _rtStr;
var _pathEnd = _rtMax;
var _stepLen = min(_pathEnd, 1 / _seg); //Distance to move per step
if(_stepLen <= 0.00001) continue;
var _total = _pathEnd; //Length remaining
var _total_prev = _total; //Use to prevent infinite loop
var _freeze = 0; //Use to prevent infinite loop
var _prog_curr = _clamp? _shift : frac(_shift); //Pointer to the current position
var _prog_next = 0;
var _prog = _prog_curr + 1; //Record previous position to delete from _total
var _prog_total = 0; //Record the distance the pointer has moved so far
var points = is_array(lines[i])? lines[i] : [];
var pointArrLen = array_length(points);
var pointAmo = 0;
var wght;
var _pathPng;
if(_useDistance) {
_pathStr *= _pathLength;
_pathEnd *= _pathLength;
_stepLen = min(_segL, _pathEnd);
_total *= _pathLength;
_total_prev = _total;
_prog_curr *= _pathLength;
}
var _segLength = struct_has(_pat, "getAccuLength")? _pat.getAccuLength(i) : [];
var _segLengthAmo = array_length(_segLength);
var _segIndex = 0;
var _segIndexPrev = 0;
if(_segLengthAmo)
while(_prog_curr > _segLength[_segIndex]) {
_segIndex++;
if(_segIndex == _segLengthAmo) {
_segIndex = 0;
break;
}
}
print($"===== {_prog_curr} / {_segLength} : {_segIndex} - {_pathLength} =====");
while(true) {
wght = 1;
_segIndexPrev = _segIndex;
if(_useDistance) {
var segmentLength = array_safe_get_fast(_segLength, _segIndex, _pathLength);
_prog_next = min(_prog_curr + _stepLen, _pathLength, segmentLength);
_pathPng = _ratInv? _pathLength - _prog_curr : _prog_curr;
//print($"{segmentLength}/{_pathLength} = {_prog_next}");
if(_prog_next == segmentLength) _segIndex++;
var _pp = _clamp? clamp(_pathPng, 0, _pathLength) : _pathPng;
// print($"_pp = {_pp}, total = {_total}");
p = _pat.getPointDistance(_pp, i, p);
if(struct_has(_pat, "getWeightDistance"))
wght = _pat.getWeightDistance(_pp, i);
} else {
_prog_next = min(_prog_curr + _stepLen, 1); //Move forward _stepLen or _total (if less) stop at 1
_pathPng = _ratInv? 1 - _prog_curr : _prog_curr;
var _pp = _clamp? clamp(_pathPng, 0, 1) : _pathPng
p = _pat.getPointRatio(_pp, i, p);
if(struct_has(_pat, "getWeightRatio"))
wght = _pat.getWeightRatio(_pp, i);
}
_nx = p.x;
_ny = p.y;
if(_total < _pathEnd) { //Do not wiggle the last point.
var _d = point_direction(_ox, _oy, _nx, _ny);
_nx += lengthdir_x(random1D(_sed + _sedIndex, -_wig, _wig), _d + 90); _sedIndex++;
_ny += lengthdir_y(random1D(_sed + _sedIndex, -_wig, _wig), _d + 90); _sedIndex++;
}
if(_prog_total >= _pathStr) { //Do not add point before range start. Do this instead of starting at _rtStr to prevent wiggle.
var _pntData;
if(pointAmo < pointArrLen && is_struct(points[pointAmo])) {
_pntData = points[pointAmo];
_pntData.x = _nx;
_pntData.y = _ny;
_pntData.prog = (_prog_total - _pathStr) / (_pathEnd - _pathStr);
_pntData.progCrop = _prog_curr / _pathLength;
_pntData.weight = wght;
} else {
_pntData = {
x: _nx,
y: _ny,
prog: (_prog_total - _pathStr) / (_pathEnd - _pathStr),
progCrop: _prog_curr / _pathLength,
weight: wght
}
points[pointAmo] = _pntData;
}
pointAmo++;
}
if(_total <= 0) break;
if(_prog_next == _prog_curr && _segIndexPrev == _segIndex) { print("Terminate line not moving"); break; }
else if(_prog_next > _prog_curr) {
_prog_total += _prog_next - _prog_curr;
_total -= _prog_next - _prog_curr;
}
_stepLen = min(_stepLen, _total);
_prog_curr = _prog_next;
_ox = _nx;
_oy = _ny;
if(_total_prev == _total && _segIndexPrev == _segIndex && ++_freeze > 16) { print("Terminate line not moving"); break; }
_total_prev = _total;
if(_segIndex >= _segLengthAmo) { print("Terminate line finish last segment"); break; }
}
array_resize(points, pointAmo);
lines[_lineAmo++] = points;
}
array_resize(lines, _lineAmo);
#endregion
} else { #region
var x0, y0, x1, y1;
var _0 = point_rectangle_overlap(_dim[0], _dim[1], (_ang + 180) % 360);
var _1 = point_rectangle_overlap(_dim[0], _dim[1], _ang);
x0 = _0[0]; y0 = _0[1];
x1 = _1[0]; y1 = _1[1];
var _l = point_distance(x0, y0, x1, y1);
var _d = point_direction(x0, y0, x1, y1);
var _od = _d, _nd = _d;
var ww = _rtMax / _seg;
var _total = _rtMax;
var _prog_curr = frac(_shift) - ww;
var _prog = _prog_curr + 1;
var _prog_total = 0;
var points = [];
while(_total > 0) {
if(_prog_curr >= 1) _prog_curr = 0;
else _prog_curr = min(_prog_curr + min(_total, ww), 1);
_prog_total += min(_total, ww);
_nx = x0 + lengthdir_x(_l * _prog_curr, _d);
_ny = y0 + lengthdir_y(_l * _prog_curr, _d);
var wgLen = random1D(_sed + _sedIndex, -_wig, _wig); _sedIndex++;
_nx += lengthdir_x(wgLen, _d + 90);
_ny += lengthdir_y(wgLen, _d + 90);
if(_prog_total > _rtStr) //prevent drawing point before range start.
array_push(points, { x: _nx, y: _ny, prog: _prog_total / _rtMax, progCrop: _prog_curr, weight: 1 });
if(_prog_curr > _prog)
_total -= (_prog_curr - _prog);
_prog = _prog_curr;
_ox = _nx;
_oy = _ny;
}
lines = [ points ];
} #endregion
#region draw
surface_set_target(_outSurf);
if(_bg) draw_clear_alpha(0, 1);
else DRAW_CLEAR
if(_useTex) { #region
var tex = surface_get_texture(_tex);
shader_set(sh_draw_mapping);
shader_set_f("position", _texPos);
shader_set_f("rotation", degtorad(_texRot));
shader_set_f("scale", _texSca);
shader_set_interpolation(_tex);
} #endregion
for( var i = 0, n = array_length(lines); i < n; i++ ) {
var points = lines[i];
if(array_length(points) < 2) continue;
var _caps = [];
if(_useTex) draw_primitive_begin_texture(pr_trianglestrip, tex);
else draw_primitive_begin(pr_trianglestrip);
random_set_seed(_sed + i);
var pxs = [];
var dat = array_safe_get_fast(_pathData, i, noone);
var _col_base = dat == noone? _colb.eval(random(1)) : dat.color;
for( var j = 0; j < array_length(points); j++ ) {
var p0 = points[j];
var _nx = p0.x - 0.5 * _1px;
var _ny = p0.y - 0.5 * _1px;
var prog = p0.prog;
var prgc = p0.progCrop;
var _dir = j? point_direction(_ox, _oy, _nx, _ny) : 0;
var widProg = value_snap_real(_widap? prog : prgc, 0.01);
_nw = random_range(_wid[0], _wid[1]);
if(!ds_map_exists(widthMap, widProg))
widthMap[? widProg] = eval_curve_x(_widc, widProg, 0.1);
_nw *= widthMap[? widProg];
_nw *= p0.weight;
_nc = colorMultiply(_col_base, _color.eval(_colP? prog : prgc));
if(_cap) { #region
if(j == 1) {
_d = _dir + 180;
_caps[0] = [ _oc, _ox, _oy, _ow / 2, _d - 90, _d ];
_caps[1] = [ _oc, _ox, _oy, _ow / 2, _d, _d + 90 ];
}
if(j == array_length(points) - 1) {
_d = _dir;
_caps[2] = [ _nc, _nx, _ny, _nw / 2, _d - 90, _d ];
_caps[3] = [ _nc, _nx, _ny, _nw / 2, _d, _d + 90 ];
}
} #endregion
if(_1px) { #region
if(j) {
var dst = point_distance(_ox, _oy, _nx, _ny);
if(dst <= 1 && i < array_length(points) - 1) continue;
draw_line_color(_ox, _oy, _nx, _ny, _oc, _nc);
}
_ox = _nx;
_oy = _ny;
_oc = _nc;
#endregion
} else { #region
if(j) {
var _nd0 = _dir;
var _nd1 = _nd0;
if(j < array_length(points) - 1) {
var p2 = points[j + 1];
var _nnx = p2.x;
var _nny = p2.y;
_nd1 = point_direction(_nx, _ny, _nnx, _nny);
_nd = _nd0 + angle_difference(_nd1, _nd0) / 2;
} else
_nd = _nd0;
if(_useTex) {
var _len = array_length(points) - 1;
var ox0 = _ox + lengthdir_x(_ow / 2, _od + 90);
var oy0 = _oy + lengthdir_y(_ow / 2, _od + 90);
var nx0 = _nx + lengthdir_x(_nw / 2, _nd + 90);
var ny0 = _ny + lengthdir_y(_nw / 2, _nd + 90);
var ox1 = _ox + lengthdir_x(_ow / 2, _od + 90 + 180);
var oy1 = _oy + lengthdir_y(_ow / 2, _od + 90 + 180);
var nx1 = _nx + lengthdir_x(_nw / 2, _nd + 90 + 180);
var ny1 = _ny + lengthdir_y(_nw / 2, _nd + 90 + 180);
draw_vertex_texture_color(ox0, oy0, 0, (j - 1) / _len, _oc, 1);
draw_vertex_texture_color(ox1, oy1, 1, (j - 1) / _len, _oc, 1);
draw_vertex_texture_color(nx0, ny0, 0, (j - 0) / _len, _nc, 1);
draw_vertex_texture_color(nx1, ny1, 1, (j - 0) / _len, _nc, 1);
} else if(_colW)
draw_line_width2_angle_width(_ox, _oy, _nx, _ny, _ow, _nw, _od + 90, _nd + 90, merge_color(_oc, c_black, 0.5), merge_color(_nc, c_black, 0.5));
else
draw_line_width2_angle(_ox, _oy, _nx, _ny, _ow, _nw, _od + 90, _nd + 90, _oc, _nc);
} else {
var p1 = points[j + 1];
_nd = point_direction(_nx, _ny, p1.x, p1.y);
}
_ox = _nx;
_oy = _ny;
_od = _nd;
_ow = _nw;
_oc = _nc;
}
#endregion
}
draw_primitive_end();
for( var j = 0, m = array_length(_caps); j < m; j++ ) {
var _cps = _caps[j];
draw_set_color(_cps[0]);
draw_circle_angle(_cps[1], _cps[2], _cps[3], _cps[4], _cps[5], _capP);
}
}
if(_useTex) shader_reset();
surface_reset_target();
#endregion
return _outSurf;
} #endregion
}

View file

@ -0,0 +1,265 @@
// 2024-04-26 14:41:10
function Node_Path_Bridge(_x, _y, _group = noone) : Node(_x, _y, _group) constructor {
name = "Bridge Path";
w = 96;
inputs[| 0] = nodeValue("Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.pathnode, noone)
.setVisible(true, true)
.rejectArray();
inputs[| 1] = nodeValue("Amount", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 4)
.rejectArray();
inputs[| 2] = nodeValue("Smooth", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false)
.rejectArray();
outputs[| 0] = nodeValue("Path", self, JUNCTION_CONNECT.output, VALUE_TYPE.pathnode, self);
input_display_list = [ 0,
["Bridge", false], 1, 2,
]
cached_pos = ds_map_create();
#region ---- path ----
anchors = [];
controls = [];
lengths = [];
lengthAccs = [];
boundary = [];
lengthTotal = [];
cached_pos = ds_map_create();
#endregion
static drawOverlay = function(hover, active, _x, _y, _s, _mx, _my, _snx, _sny) { #region
var _path = getInputData(0);
var _smt = getInputData(2);
if(_path && struct_has(_path, "drawOverlay")) _path.drawOverlay(hover, active, _x, _y, _s, _mx, _my, _snx, _sny);
var _amo = array_length(anchors);
var ox, oy, nx, ny;
var _p = new __vec2();
draw_set_color(COLORS._main_icon);
for( var i = 0, n = _amo; i < n; i++ ) {
var _a = anchors[i];
if(_smt) {
var _smp = 1 / 32;
for( var j = 0; j <= 1; j += _smp ) {
_p = getPointRatio(j, i, _p);
nx = _x + _p.x * _s;
ny = _y + _p.y * _s;
if(j > 0) draw_line_width(ox, oy, nx, ny, 3);
ox = nx;
oy = ny;
}
} else {
for( var j = 0, m = array_length(_a); j < m; j++ ) {
nx = _x + _a[j][0] * _s;
ny = _y + _a[j][1] * _s;
if(j) draw_line_width(ox, oy, nx, ny, 3);
ox = nx;
oy = ny;
}
}
}
} #endregion
static getLineCount = function() { return getInputData(1); }
static getSegmentCount = function(ind = 0) { return array_safe_length(array_safe_get_fast(anchors, ind)); }
static getLength = function(ind = 0) { return array_safe_get_fast(lengths, ind); }
static getAccuLength = function(ind = 0) { return array_safe_get_fast(lengthAccs, ind); }
static getBoundary = function(ind = 0) { return array_safe_get_fast(boundary, ind); }
static getPointRatio = function(_rat, ind = 0, out = undefined) { return getPointDistance(clamp(_rat, 0, 1) * getLength(ind), ind, out); }
static getPointDistance = function(_dist, ind = 0, out = undefined) { #region
if(out == undefined) out = new __vec2(); else { out.x = 0; out.y = 0; }
var _cKey = $"{_dist},{ind}";
if(ds_map_exists(cached_pos, _cKey)) {
var _p = cached_pos[? _cKey];
out.x = _p.x;
out.y = _p.y;
return out;
}
var _smt = getInputData(2);
var _a = anchors[ind];
var _la = lengthAccs[ind];
if(_dist == 0) {
var _p = _a[0];
out.x = _p[0];
out.y = _p[1];
cached_pos[? _cKey] = out.clone();
return out;
}
var _ind = 0;
var n = array_length(_la);
for(; _ind < n; _ind++ ) if(_dist < _la[_ind]) break;
if(_ind >= n) {
var _p = _a[_ind];
out.x = _p[0];
out.y = _p[1];
cached_pos[? _cKey] = out.clone();
return out;
}
var _d = _ind == 0? _dist : _dist - _la[_ind - 1];
var _rat = _d / (_la[_ind] - (_ind == 0? 0 : _la[_ind - 1]));
var p0 = _a[_ind];
var p1 = _a[_ind + 1];
if(_smt) {
var _cnt = controls[ind];
var _c0x = _cnt[_ind][0];
var _c0y = _cnt[_ind][1];
var _c1x = _cnt[_ind][2];
var _c1y = _cnt[_ind][3];
var _p = eval_bezier(_rat, p0[0], p0[1], p1[0], p1[1], _c0x, _c0y, _c1x, _c1y);
out.x = _p[0];
out.y = _p[1];
} else {
out.x = lerp(p0[0], p1[0], _rat);
out.y = lerp(p0[1], p1[1], _rat);
}
cached_pos[? _cKey] = out.clone();
return out;
} #endregion
static update = function() { #region
ds_map_clear(cached_pos);
var _path = getInputData(0);
var _amo = getInputData(1);
var _smt = getInputData(2);
if(_path == noone) return;
#region bridge
var _lines = _path.getLineCount();
var _p = new __vec2();
var _rat;
anchors = array_create(_amo);
lengths = array_create(_amo);
lengthAccs = array_create(_amo);
for( var i = 0; i < _amo; i++ ) {
var _a = array_create(_lines);
_rat = _amo == 1? 0.5 : i / (_amo - 1);
for( var j = 0; j < _lines; j++ ) {
_p = _path.getPointRatio(clamp(_rat, 0, 0.999), j, _p);
_a[j] = [ _p.x, _p.y ];
}
anchors[i] = _a;
if(_smt) {
var _cnt = array_create(_lines - 1);
for( var j = 0; j < _lines - 1; j++ ) _cnt[j] = [ 0, 0, 0, 0 ];
_cnt[0] = [ _a[0][0], _a[ 0][1], _a[0][0], _a[0][1] ];
_cnt[_lines - 2] = [ _a[_lines - 1][0], _a[_lines - 1][1], _a[_lines - 1][0], _a[_lines - 1][1] ];
for( var j = 1; j < _lines - 1; j++ ) {
var _a0 = _a[j - 1];
var _a1 = _a[j];
var _a2 = _a[j + 1];
var _dr = point_direction(_a0[0], _a0[1], _a2[0], _a2[1]);
var _ds0 = point_distance(_a1[0], _a1[1], _a0[0], _a0[1]) / 2;
var _ds2 = point_distance(_a1[0], _a1[1], _a2[0], _a2[1]) / 2;
var c0x = _a1[0] - lengthdir_x(_ds0, _dr);
var c0y = _a1[1] - lengthdir_y(_ds0, _dr);
var c1x = _a1[0] + lengthdir_x(_ds2, _dr);
var c1y = _a1[1] + lengthdir_y(_ds2, _dr);
_cnt[j - 1][2] = c0x;
_cnt[j - 1][3] = c0y;
_cnt[j][0] = c1x;
_cnt[j][1] = c1y;
}
controls[i] = _cnt;
var _l = 0, _la = [];
var ox, oy, nx, ny;
for( var j = 0; j < _lines - 1; j++ ) {
var _a0 = _a[j];
var _a1 = _a[j + 1];
var _c0x = _cnt[j][0];
var _c0y = _cnt[j][1];
var _c1x = _cnt[j][2];
var _c1y = _cnt[j][3];
var _smp = 1 / 32;
var _ll = 0;
for( var k = 0; k < 1; k += _smp ) {
var _p = eval_bezier(k, _a0[0], _a0[1], _a1[0], _a1[1], _c0x, _c0y, _c1x, _c1y);
nx = _p[0];
ny = _p[1];
if(k > 0) _ll += point_distance(ox, oy, nx, ny);
ox = nx;
oy = ny;
}
_l += _ll;
array_push(_la, _l);
}
lengths[i] = _l;
lengthAccs[i] = _la;
} else {
var _l = 0, _la = [];
var ox, oy, nx, ny;
for( var j = 0, m = array_length(_a); j < m; j++ ) {
nx = _a[j][0];
ny = _a[j][1];
if(j) {
var d = point_distance(ox, oy, nx, ny);
_l += d;
array_push(_la, _l);
}
ox = nx;
oy = ny;
}
lengths[i] = _l;
lengthAccs[i] = _la;
}
}
#endregion
} #endregion
}

View file

@ -0,0 +1,265 @@
// 2024-04-26 14:39:57
function Node_Path_Bridge(_x, _y, _group = noone) : Node(_x, _y, _group) constructor {
name = "Bridge Path";
w = 96;
inputs[| 0] = nodeValue("Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.pathnode, noone)
.setVisible(true, true)
.rejectArray();
inputs[| 1] = nodeValue("Amount", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 4)
.rejectArray();
inputs[| 2] = nodeValue("Smooth", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false)
.rejectArray();
outputs[| 0] = nodeValue("Path", self, JUNCTION_CONNECT.output, VALUE_TYPE.pathnode, self);
input_display_list = [ 0,
["Bridge", false], 1, 2,
]
cached_pos = ds_map_create();
#region ---- path ----
anchors = [];
controls = [];
lengths = [];
lengthAccs = [];
boundary = [];
lengthTotal = [];
cached_pos = ds_map_create();
#endregion
static drawOverlay = function(hover, active, _x, _y, _s, _mx, _my, _snx, _sny) { #region
var _path = getInputData(0);
var _smt = getInputData(2);
if(_path && struct_has(_path, "drawOverlay")) _path.drawOverlay(hover, active, _x, _y, _s, _mx, _my, _snx, _sny);
var _amo = array_length(anchors);
var ox, oy, nx, ny;
var _p = new __vec2();
draw_set_color(COLORS._main_icon);
for( var i = 0, n = _amo; i < n; i++ ) {
var _a = anchors[i];
if(_smt) {
var _smp = 1 / 32;
for( var j = 0; j <= 1; j += _smp ) {
_p = getPointRatio(j, i, _p);
nx = _x + _p.x * _s;
ny = _y + _p.y * _s;
if(j > 0) draw_line_width(ox, oy, nx, ny, 3);
ox = nx;
oy = ny;
}
} else {
for( var j = 0, m = array_length(_a); j < m; j++ ) {
nx = _x + _a[j][0] * _s;
ny = _y + _a[j][1] * _s;
if(j) draw_line_width(ox, oy, nx, ny, 3);
ox = nx;
oy = ny;
}
}
}
} #endregion
static getLineCount = function() { return getInputData(1); }
static getSegmentCount = function(ind = 0) { return array_safe_length(array_safe_get_fast(anchors, ind)); }
static getLength = function(ind = 0) { return array_safe_get_fast(lengths, ind); }
static getAccuLength = function(ind = 0) { return array_safe_get_fast(lengthAccs, ind); }
static getBoundary = function(ind = 0) { return array_safe_get_fast(boundary, ind); }
static getPointRatio = function(_rat, ind = 0, out = undefined) { return getPointDistance(clamp(_rat, 0, 1) * getLength(ind), ind, out); }
static getPointDistance = function(_dist, ind = 0, out = undefined) { #region
if(out == undefined) out = new __vec2(); else { out.x = 0; out.y = 0; }
var _cKey = $"{_dist},{ind}";
if(ds_map_exists(cached_pos, _cKey)) {
var _p = cached_pos[? _cKey];
out.x = _p.x;
out.y = _p.y;
return out;
}
var _smt = getInputData(2);
var _a = anchors[ind];
var _la = lengthAccs[ind];
if(_dist == 0) {
var _p = _a[0];
out.x = _p[0];
out.y = _p[1];
cached_pos[? _cKey] = out.clone();
return out;
}
var _ind = 0;
var n = array_length(_la);
for(; _ind < n; _ind++ ) if(_dist < _la[_ind]) break;
if(_ind >= n) {
var _p = _a[_ind];
out.x = _p[0];
out.y = _p[1];
cached_pos[? _cKey] = out.clone();
return out;
}
var _d = _ind == 0? _dist : _dist - _la[_ind - 1];
var _rat = _d / (_la[_ind] - (_ind == 0? 0 : _la[_ind - 1]));
var p0 = _a[_ind];
var p1 = _a[_ind + 1];
if(_smt) {
var _cnt = controls[ind];
var _c0x = _cnt[_ind][0];
var _c0y = _cnt[_ind][1];
var _c1x = _cnt[_ind][2];
var _c1y = _cnt[_ind][3];
var _p = eval_bezier(_rat, p0[0], p0[1], p1[0], p1[1], _c0x, _c0y, _c1x, _c1y);
out.x = _p[0];
out.y = _p[1];
} else {
out.x = lerp(p0[0], p1[0], _rat);
out.y = lerp(p0[1], p1[1], _rat);
}
cached_pos[? _cKey] = out.clone();
return out;
} #endregion
static update = function() { #region
ds_map_clear(cached_pos);
var _path = getInputData(0);
var _amo = getInputData(1);
var _smt = getInputData(2);
if(_path == noone) return;
#region bridge
var _lines = _path.getLineCount();
var _p = new __vec2();
var _rat;
anchors = array_create(_amo);
lengths = array_create(_amo);
lengthAccs = array_create(_amo);
for( var i = 0; i < _amo; i++ ) {
var _a = array_create(_lines);
_rat = _amo == 1? 0.5 : i / (_amo - 1);
for( var j = 0; j < _lines; j++ ) {
_p = _path.getPointRatio(clamp(_rat, 0, 0.999), j, _p);
_a[j] = [ _p.x, _p.y ];
}
anchors[i] = _a;
if(_smt) {
var _cnt = array_create(_lines - 1);
for( var j = 0; j < _lines - 1; j++ ) _cnt[j] = [ 0, 0, 0, 0 ];
_cnt[0] = [ _a[0][0], _a[ 0][1], _a[0][0], _a[0][1] ];
_cnt[_lines - 2] = [ _a[_lines - 1][0], _a[_lines - 1][1], _a[_lines - 1][0], _a[_lines - 1][1] ];
for( var j = 1; j < _lines - 1; j++ ) {
var _a0 = _a[j - 1];
var _a1 = _a[j];
var _a2 = _a[j + 1];
var _dr = point_direction(_a0[0], _a0[1], _a2[0], _a2[1]);
var _ds0 = point_distance(_a1[0], _a1[1], _a0[0], _a0[1]) / 2;
var _ds2 = point_distance(_a1[0], _a1[1], _a2[0], _a2[1]) / 2;
var c0x = _a1[0] - lengthdir_x(_ds0, _dr);
var c0y = _a1[1] - lengthdir_y(_ds0, _dr);
var c1x = _a1[0] + lengthdir_x(_ds2, _dr);
var c1y = _a1[1] + lengthdir_y(_ds2, _dr);
_cnt[j - 1][2] = c0x;
_cnt[j - 1][3] = c0y;
_cnt[j][0] = c1x;
_cnt[j][1] = c1y;
}
controls[i] = _cnt;
var _l = 0, _la = [];
var ox, oy, nx, ny;
for( var j = 0; j < _lines - 1; j++ ) {
var _a0 = _a[j];
var _a1 = _a[j + 1];
var _c0x = _cnt[j][0];
var _c0y = _cnt[j][1];
var _c1x = _cnt[j][2];
var _c1y = _cnt[j][3];
var _smp = 1 / 32;
var _ll = 0;
for( var k = 0; k < 1; k += _smp ) {
var _p = eval_bezier(k, _a0[0], _a0[1], _a1[0], _a1[1], _c0x, _c0y, _c1x, _c1y);
nx = _p[0];
ny = _p[1];
if(k > 0) _ll += point_distance(ox, oy, nx, ny);
ox = nx;
oy = ny;
}
_l += _ll;
array_push(_la, _l);
}
lengths[i] = _l;
lengthAccs[i] = _la;
} else {
var _l = 0, _la = [];
var ox, oy, nx, ny;
for( var j = 0, m = array_length(_a); j < m; j++ ) {
nx = _a[j][0];
ny = _a[j][1];
if(j) {
var d = point_distance(ox, oy, nx, ny);
_l += d;
array_push(_la, _l);
}
ox = nx;
oy = ny;
}
lengths[i] = _l;
lengthAccs[i] = _la;
}
}
#endregion
} #endregion
}

View file

@ -1,4 +1,4 @@
// 2024-04-23 15:29:21
// 2024-04-26 12:17:59
#region preference
globalvar PREFERENCES, PREFERENCES_DEF, HOTKEYS_DATA;
PREFERENCES = {};

View file

@ -1,4 +1,4 @@
// 2024-04-23 11:00:26
// 2024-04-26 10:05:24
#region preference
globalvar PREFERENCES, PREFERENCES_DEF, HOTKEYS_DATA;
PREFERENCES = {};

View file

@ -303,11 +303,6 @@
{"$GMFolder":"","%Name":"text","folderPath":"folders/widgets/text.yy","name":"text","resourceType":"GMFolder","resourceVersion":"2.0",},
],
"IncludedFiles":[
{"$GMIncludedFile":"","%Name":"ApolloHelp.html","ConfigValues":{
"Itch":{
"CopyToMask":"0",
},
},"CopyToMask":0,"filePath":"datafiles","name":"ApolloHelp.html","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"Actions.zip","CopyToMask":-1,"filePath":"datafiles/data","name":"Actions.zip","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"icons.ai","CopyToMask":-1,"filePath":"datafiles/data/Actions","name":"icons.ai","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"Armature Build.json","CopyToMask":-1,"filePath":"datafiles/data/Actions/Nodes","name":"Armature Build.json","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
@ -362,6 +357,11 @@
{"$GMIncludedFile":"","%Name":"related_node.json","CopyToMask":-1,"filePath":"datafiles/data","name":"related_node.json","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"Theme.zip","CopyToMask":-1,"filePath":"datafiles/data","name":"Theme.zip","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"tooltip.zip","CopyToMask":3035426170322551022,"filePath":"datafiles/data","name":"tooltip.zip","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"dllcredits.txt","ConfigValues":{
"Itch":{
"CopyToMask":"0",
},
},"CopyToMask":0,"filePath":"datafiles","name":"dllcredits.txt","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"ffmpeg.exe","CopyToMask":-1,"filePath":"datafiles/ffmpeg/bin","name":"ffmpeg.exe","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"LICENSE","CopyToMask":-1,"filePath":"datafiles/ffmpeg","name":"LICENSE","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"README.txt","CopyToMask":-1,"filePath":"datafiles/ffmpeg","name":"README.txt","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
@ -416,11 +416,6 @@
{"$GMIncludedFile":"","%Name":"mf.dll","CopyToMask":-1,"filePath":"datafiles","name":"mf.dll","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"mfcore.dll","CopyToMask":-1,"filePath":"datafiles","name":"mfcore.dll","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"mfplat.dll","CopyToMask":-1,"filePath":"datafiles","name":"mfplat.dll","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"README.txt","ConfigValues":{
"Itch":{
"CopyToMask":"0",
},
},"CopyToMask":0,"filePath":"datafiles","name":"README.txt","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"data.win","CopyToMask":-1,"filePath":"datafiles/report","name":"data.win","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"execute_shell_simple_ext_x64.dll","CopyToMask":-1,"filePath":"datafiles/report","name":"execute_shell_simple_ext_x64.dll","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"options.ini","CopyToMask":-1,"filePath":"datafiles/report","name":"options.ini","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
@ -455,7 +450,6 @@
{"$GMIncludedFile":"","%Name":"CommonPS.hlsl","CopyToMask":-1,"filePath":"datafiles/Shaders/3dInstance","name":"CommonPS.hlsl","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"CommonVS.hlsl","CopyToMask":-1,"filePath":"datafiles/Shaders/3dInstance","name":"CommonVS.hlsl","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"rubber_duck_toy_1k.bin","CopyToMask":-1,"filePath":"datafiles/Shaders/3dInstance","name":"rubber_duck_toy_1k.bin","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"Steamworks_Extension_Documentation.html","CopyToMask":0,"filePath":"datafiles","name":"Steamworks_Extension_Documentation.html","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"ucrtbased.dll","ConfigValues":{},"CopyToMask":-1,"filePath":"datafiles","name":"ucrtbased.dll","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"webpmux.exe","CopyToMask":-1,"filePath":"datafiles/webp","name":"webpmux.exe","resourceType":"GMIncludedFile","resourceVersion":"2.0",},
{"$GMIncludedFile":"","%Name":"Welcome files.zip","CopyToMask":-1,"filePath":"datafiles","name":"Welcome files.zip","resourceType":"GMIncludedFile","resourceVersion":"2.0",},

View file

@ -1,988 +0,0 @@
<html><head>
<meta charset="UTF-8">
<title>Apollo cheat sheet</title>
<meta name="viewport" content="width=device-width" />
<meta name="livenode" content="#doc" />
<meta property="theme-color" content="#FFF037" />
<meta property="og:type" content="article" />
<meta property="og:locale" content="en_us" />
<meta property="og:site_name" content="YellowAfterlife" />
<meta property="og:title" content="Apollo cheat sheet" />
<meta property="og:url" content="<!--%[url]-->" />
<meta property="og:description" content="<!--%[desc]-->" />
<script type="text/javascript">
if (document.location.host == "yal.cc" && location.protocol == "http:") {
document.location.protocol = "https:";
}
</script>
<style type="text/css">
body, #doc tt {
font: 15px 'Open Sans', sans-serif;
line-height: 1.35;
}
body {
margin: 0;
}
.main {
width: 100%;
min-height: 100%;
min-height: 100vh;
background-color: #f9f9f9;
}
.page {
max-width: 656px;
background: #ffffff;
margin: 0 auto;
padding: 8px;
box-shadow: 0 0 0 2px #eee;
min-height: 100%;
min-height: 100vh;
box-sizing: border-box;
}
.page > p:first-child {
margin-top: 0;
}
#doc, #doc ul, #doc ol {
padding-left: 0;
margin: 0;
}
#doc div ul, #doc div ol {
padding-left: 20px;
}
#doc div ul li {
list-style: disc;
list-style-image: url('data:image/svg+xml;base256,<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20px" height="1em" viewBox="0 0 20px 1em"><circle cx="17px" cy="0.675em" r="2px"/></svg>');
}
#doc .header {
display: block;
outline: none;
text-decoration: none;
margin: 0;
font-weight: 700;
font-size: 100%;
color: #458;
border-left: 2px solid #f3f3f3;/* #f9f9f9*/
padding-left: 4px;
padding-top: 1px;
padding-bottom: 1px;
cursor: pointer;
}
#doc .header::before {
display: inline-block;
content: "+";
font: 12px monospace;
border: 1px solid #458;
line-height: 11px;
height: 11px;
width: 11px;
text-align: center;
border-radius: 50%;
margin-right: 4px;
vertical-align: middle;
position: relative;
top: -1px;
}
#doc .item.open > .header::before {
content: "-";
}
#doc .item.empty > .header::before {
content: /*"·"*/" ";
}
#doc .header:hover {
border-left-color: #f3f3f3;
background: #f3f3f3;
}
#doc .header .ret-arrow {
font-weight: normal;
margin: 0 0.1em;
}
#doc a.broken {
color: red;
}
#doc p {
margin: 0;
}
#doc p + p, #doc p.pad {
margin-top: 0.5em;
}
#doc img {
max-width: 100%;
}
#doc h3 {
margin: 0.25em 0;
font-size: 125%;
font-weight: normal;
border-bottom: 1px solid #ccc;
}
#doc .content, #doc ul {
padding-left: 20px;
border-left: 2px solid #f3f3f3;
}
#doc tt {
font-weight: bold;
}
#doc pre {
font-family: Consolas, Dejavu Sans Mono, Segoe UI Mono, Ubuntu Mono, Lucida Console, monospace;
font-size: 9pt;
line-height: 1.25;
background: white;
padding: 4px 2px 4px 10px;
margin: 0;
tab-size: 4;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
}
#doc pre a {
text-decoration: none;
}
#doc pre a.uf {
background-color: #f7f0ff;
}
#doc pre a.kw {
background-color: #f0f7ff;
}
#doc pre a.sf, #doc pre a.sv {
background-color: #fff3f0;
}
#doc pre a:hover {
text-decoration: underline;
}
#doc abbr[title] {
text-decoration: underline;
text-decoration-color: #bbb;
text-decoration-style: double;
}
/* delay display until load */
#doc[ready] .item:not(.open) > .content {
display: none;
}
</style>
<style type="text/css" id="md_gml">
/* GameMakerLanguage */
pre.gmlmd .md { color: #7A81A9 } /* #define */
pre.gmlmd .kw { color: #008; font-weight: bold } /* keyword */
pre.gmlmd .co { color: #080 } /* comment */
pre.gmlmd .nu { color: #00f } /* number */
pre.gmlmd .nx { color: #00f } /* hex */
pre.gmlmd .st { color: #00f } /* string */
pre.gmlmd .ts { color: #00f } /* template string */
pre.gmlmd .op { color: #000 } /* operator */
pre.gmlmd .cb { color: #008; font-weight: bold } /* curly brace */
pre.gmlmd .sv { color: #800 } /* std func */
pre.gmlmd .sf { color: #800 } /* std var */
pre.gmlmd .ri { color: #0078aa } /* assets */
pre.gmlmd .uf { color: #808 } /* user func */
pre.gmlmd .uv { color: #000 } /* user var */
pre.gmlmd .lv { color: #648 } /* local var */
pre.gmlmd .fd { color: #804 } /* field */
</style>
<style type="text/css" id="night_css">
#night { display: none }
label[for="night"] {
color: blue;
text-decoration: underline;
cursor: pointer;
}
#night:checked + .main {
background-color: #405070;
}
#night:checked + .main .page {
background-color: #1A202D;
box-shadow: 0 0 0 2px #9bccff63;
color: white;
}
#night:checked + .main a,
#night:checked + .main a:visited,
#night:checked + .main #doc .header,
#night:checked + .main label[for="night"] {
color: #9DEC76;
}
#night:checked + .main #doc .header:before,
#night:checked + .main #doc h3 {
border-color: #9DEC76
}
#night:checked + .main #doc .header,
#night:checked + .main #doc .header:hover,
#night:checked + .main #doc .content,
#night:checked + .main #doc ul {
border-left-color: rgba(205,225,255,0.1);
}
#night:checked + .main #doc .header:hover,
#night:checked + .main #doc .asset:hover {
background: rgba(205,225,255,0.1);
}
#night:checked + .main div ul li {
list-style-image: url('data:image/svg+xml;base256,<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="20px" height="1em" viewBox="0 0 20px 1em"><circle cx="17px" cy="0.675em" r="2px" fill="white"/></svg>');
}
#night:checked + .main pre {
color: #cccccc;
background: #000000;
border: 1px solid #3E4757;
}
#night:checked + .main pre.gmlmd .op { color: #CCCCCC }
#night:checked + .main pre.gmlmd .co { color: #5B995B }
#night:checked + .main pre.gmlmd .kw { color: #FFB871 }
#night:checked + .main pre.gmlmd .md { color: #FFB871 }
#night:checked + .main pre.gmlmd .cb { color: #FFB871 }
#night:checked + .main pre.gmlmd .sf { color: #FFB871 }
#night:checked + .main pre.gmlmd .uf { color: #FFB871 }
#night:checked + .main pre.gmlmd .nu { color: #FF8080 }
#night:checked + .main pre.gmlmd .st { color: #FCF320 }
#night:checked + .main pre.gmlmd .ts { color: #FF8080 }
#night:checked + .main pre.gmlmd .sv { color: #FF8080 }
#night:checked + .main pre.gmlmd .gv { color: #FF80FF }
#night:checked + .main pre.gmlmd .ri { color: #FF8080 }
#night:checked + .main pre.gmlmd .lv { color: #FFF899 }
#night:checked + .main pre.gmlmd .uv { color: #B2B1FF }
#night:checked + .main pre.gmlmd .fd { color: #B2B1FF }
#night:checked + .main pre.gmlmd a.uf {
background-color: #431;
}
#night:checked + .main pre.gmlmd a.kw {
background-color: #431;
}
#night:checked + .main pre.gmlmd a.sf,
#night:checked + .main pre.gmlmd a.sv {
background-color: #431;
}
</style>
<style>
.main.boxtt #doc tt {
display: inline-block;
background: #FFFBE4;
font-family: Consolas, Ubuntu Mono, Dejavu Sans Mono, Lucida Console, monospace;
font-weight: normal;
font-size: 12px;
line-height: 16px;
padding: 0 2px;
border: 1px solid #E6E0C4;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
max-width: 100%;
word-break: break-word;
}
#night:checked + .main.boxtt #doc tt {
background-color: #000;
border-color: #5b7c9f;
color: white;
}
</style>
<noscript><style>
#doc .header::before {
display: none;
}
#doc .header:hover {
border-left-color: #f3f3f3;
background: inherit;
}
#doc { display: inherit }
#doc .item:not(.open) > .content {
display: inherit;
}
</style></noscript>
</head><body>
<input type="checkbox" id="night" checked/>
<div class="main boxtt">
<script type="text/javascript">
(function() {
var night = document.getElementById("night");
var path = "docmd night mode";
var ls = window.localStorage;
if (ls) {
night.checked = ls.getItem(path) == "true";
night.onchange = function(_) {
ls.setItem(path, "" + night.checked);
};
}
})();
</script>
<div class="page">
<p>This is a function "cheat sheet" for Apollo extension by YellowAfterlife.
The extension can be acquired from <a href="https://marketplace.yoyogames.com/assets/5192/_">GM:Marketplace</a> or <a href="https://yellowafterlife.itch.io/gamemaker-lua">itch.io</a>.
For questions/support, use forums (<a href="https://yellowafterlife.itch.io/gamemaker-lua/community">itch.io</a>, <a href="https://forum.yoyogames.com/index.php?threads/27984/">GM forums</a>), or <a href="mailto://yellowafterlife@hotmail.com">send me an email</a>.
A most up-to-date version of the manual is always <a href="https://yal.cc/r/17/lua/">available online</a>.
The extension is currently available for Windows, Linux, and Mac (experimental).</p><p>
Click on sections to expand/collapse them.<br>
Quick display controls: <a href="#" onclick="opt_none(); return false">Categories</a>
&middot; <a href="#" onclick="opt_list(); return false">Sections</a>
&middot; <a href="#" onclick="opt_all(); return false">Everything</a>
&middot; <label for="night">Toggle night mode</label><br/>
</p><div id="doc">
<!--<doc--> <style>textarea {
font: 85% monospace;
border: 1px solid #ccc;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin: 0;
width: 100%;
padding: 4px;
box-sizing: border-box;
resize: vertical;
overflow-y: scroll;
word-wrap: normal;
white-space: pre;
}
pre.lua .kw1 { color: #0d8bca; font-weight: bold; }
pre.lua .co1, .lua .coMULTI { color: #888; font-weight: normal; }
pre.lua .nu0 { color: red; }
pre.lua .sy0 { color: #555; }
pre.lua .st0 { color: #008000; }
pre.lua .br0 { color: #555; }
pre.lua .op { color: #008; font-weight: bold }
#night:checked + .main pre.lua .op { color: #c0c0c0; }
#night:checked + .main pre.lua .nu0 { color: #FF8080; }
#night:checked + .main pre.lua .st0 { color: #FCF320; }
#night:checked + .main pre.lua .kw1 { color: #9DEC76; }
#night:checked + .main pre.lua .co1 { color: #5B995B; }
#night:checked + .main textarea {
color: #cccccc;
background: #000000;
border: 1px solid #3E4757;
}
.main {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAACECAMAAABmmnOVAAAAUVBMVEUAAABDVYhDVohBVIpDVYhFVIhIVpBFWYVBVIhEVIlBU4hCVIhBVo1CVYdEVYhCU4dDVYlEVIlDVYhDVYhDVYdEVYhEVIhEVYhEVYhEVYhDVYnEoPAwAAAAG3RSTlMAkDcLMxMEDyCKGywHKIQXfUxwYVF2JEFrV16cnkPAAAAFk0lEQVR42sTY2ZbiIBAG4L+KLWRfjFHf/0Hn9Jz04EAQEe3+bjzmIqeQWkCUODdSMkspB/w0rSqFL4K/Vd/fm0q1eETwsvbUrwuLkgAkMzf7G0WrAa01/mob/iIrgwi1kLMovMJ0/KWr21iMpu4kMysc0SdL9+xJI1/D3A0CCaLu2sPnF/LNAtnEkBW6UhqOGCk0Ph+FdsvPwSxrjZ2+0JFZ4zk18xkvMA3zv+o90bETnjFI5k4AZWEoS8eseu41jRdCbhhfUSwUc0NKxSwViqizAATFpZYomGu8A1OcRIJq8RYLxS34ISvFrfghPcVZxAxdizeiRxBx5tSoFRVPy7yOPVE/rvMycSXe+0t0zApxRl5HOtAvrN6WEx1LgZhhGumBcRoyq+OWG4PYVkqaN1HQJ1IxmJOlp9irwZ38jlmzbHFEuRDS7G2Akz072qo9fHyiTCf3nqIp6jQjZeslnNR5Ik1d6CUXhcKTldP09CIr88+YLQ7oiQqcdOZpuzpqeGalIrPIuneYo4GhRio0KveyGzk3hZCWXMNX91Ssr/+7i46W7HiTIjI5m/CZpWyXrRJai2qbaWcrPMkwt/DU2THYSdwte7L70xrPacLNUD1lWoy3smXfEQVfOyCgWMIjRso0IbDt2WnC3Q+jaILy1CtlkjggXWtMrRqmhudEmTYc2iJDouEBKQ1lWhKXjiaoBKnxmOkpjzWIMPY4Obvk9e5SnJTOtDeQ3J9CUi6BKOES90ExCHjanjJd8MC8d4vWC+7+hmW4Kq0M2vDA9sQx6uxniKJsFR6oaKcQFUyNK5WmROTEf0WM8senoXwaD+h0HXd+67p+IIhEVmhmjXvC0qe2g6zw/9HaP7kLc/n9ibljv1WY/Xg7pO/x5SW6m/1L53lPCYF7AwXKm5Uz4J7YB3prjjr9e9t2fMZIbnFgpED5AHNWvzQVQoacN45yx3hJUWXMz8JDjSO9ed4hdKVXbenNCFu3Zs5IicKDrjN6SdEAbWfimfyeI3+6jgauwubmlF9+0s017JdMntJrYIjhHNfpRKVm/0Kc3rSOjVdP2cpruWHhtfmPm+Ec9+6VPm71IuggWafbRLl4o2hZwm9YPX1c77VMCem1OUsfZ4MgtP7lINxe/NZ2uCB+PzH/FG+uK3LDMBQWR/JlfVl3FhrY93/RTgeKoWrjcSIn389hCELIsi7HnZuOKPfW6+JkpY24N217SRQyn7jAjKqOhKSu8sXAuqix6RgD6onyzqhNKpCLE8UP0gBelfxL+SZNRbguMnVjwK+YjEiqDbRm2AY6bMuDYtgQF4l0Zbp67IZkH5IspY1ujvUXqR4XxeGEyR4MFk5qhGiNHiEWFRLL85UepvajSYWnJ0ZGMyXfFSMZ5SJXfC9cNWjmtSsBMli6rElUg4NSlrQfn4V2aKh63GWP0B5eEKZWkibjbxb3V/asejlry3g56wWs1tS26DW1ypZNmNaekMcbgg2NN+oIB9KFAWxYY2lVl8DRO7jPZUFJCZneoxlZoYU9BQj0T7yBxEmjJU69stU0NP2bpdhLc6XsbYYI8dq0LwMB4AwZlTQGUsgZiiDZikIzaZwbhoWzlMcG0oxTZQLYUiisYaCNgzOQnWRaw4KNZpgXj2vdqbah0inCY1pGryO/0llYb9c0X2CysMGXnacVu137z8fOAXQDG8bNcof//8gkDDw5l8CR3nxu8/HRn9sYk4Dq6W6CQJjM4OjpACUDydCxjg4RgUAWcAYiHSRs3sgNEuhWnADR01k8nwlwIDOdZ0M+7k2/OTOHZkc345MAEgtNUFJlczMyJo566X+3hTd576ucKp5sgRbiagp+x2l4Uh2tpeKJ1JhCrz44uBQTvYjReVqNd7EKfuO7US/oYkpoqf1JIyK5brExHeQXguJJtNtjNjcAAAAASUVORK5CYII=');
background-position: top 20px right 20px;
background-repeat: no-repeat;
background-attachment: fixed;
}
#night:checked + .main {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAACECAYAAABRRIOnAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAv7SURBVHhe7Z1Ny/RIFYYHBRERBkGGWQyuXAmCi8GN4MKFkIXg/wd3/o+xrsxzYnX6TlKpU1WppGtxPW/36XzUx6nzVel+v/nPf3/6NH4f+D7wwxd/+MLew28D6tzHI4U35ReB3wR+9/WvOuZXAVOAPbjG0fkold3rlwF1fG2sPX8N/CMwBf799S/vkfM5x6nz35DCmxArQLzSbbLUOcDgAJPINUzOayM+PoZzuHZ8L4M20JZfB9S5JaHfTDaTnwrHby2UBSnsHAb8u4CaFOTfBmqvWJSGdnAv7rlWyMOBz4T7/jnwr4Ca9CM4j/M3lV4KOydeoUwG/j7ZJFaENpiC1FBIrv+3gJros/w9IMfsTXAD6AhKsGfaewXLAWfbTp//GVCTmwvXe1OKlzedwGD1supLY5YNF4M1SVEMjillGdZgKV7aEN+4BxgkGzTSQ3XMnSHuiF1eSoqLz1eTWQquv9wvvvGVMChxYIYffqKFMFIVA/eSG0CmwvWXIHjdgNasB4bXT1aENev+r5XibGqZy4+B+Z7xzVtDzm4DwQqplardAfqOi4wXA6/V5NVivnfcqJZwc1MG4gZ1zKfD2KiJqwWL8lILwaq4quR7B1q5C4P7dRNUDt5hL0JNXC2431CIjpkCauJqQbZRXSGImmuVcp+OmrTaVFUIomYLHK9MJbm3ZTR/CuArqdBhIinfTgEbEF4j4zOO4VjO4Vyu0bIfcbtaUNVCYBVMGVqnk+T2RMx/CZSu/8MUQFFq9+0xMYQpA5PSakXhmljJNRTgCO7JvVWl0UPrLGMuTqmGeGipDFz/j4HWK2kP3AxtKtF3xlHdoxbF6xCtlAGX4HlIpAW0DZdFW1UfUmAM1bVrMc+ZakgOtkuJMtTKKPDXvSvCGtqKKc51J63cRvG9DJSAKLyGMnDN2lvALaAPZ8eHRVB7AXD9JTheN6A32Am8IlCsxRSYffUJai8Grr/cL75xT6CxtZ4S6gH6lpqyfvQTU4BVmAKqA08CU51qLQj4SltKrvcW/L+8OUGNWAFNJZ9XjX8ymOyXVboBk9flU9dWBk41eSmQnvVUT2jN5gStQHFQoNxAk/N2FVAKd2DiUAZI6UAKKNaTAsdcGIPURcZxpIrqOltw/OH1pXADtAqfhzKUesqJ60wB1YFPZAqcGVsWJfNBvQKFMsvBv7xHCU4VCqVwA9u9JOhTn5+F6+Wavh7Bv1O2tl1RFhDwGhmf4RrUuTGMCcerMauOFApiV1EioGQVPEEZ6AOB8Bn3ybGcs9d/PitlhU8hhQJ7VLxEI/FjU0ANxJ3ATHv2Kjh3rzQ9BVJjiiNYxEnlcylcQaNQhrMVNgWr4wkBJCtc9S8HXIm6BzBWHqUzzN0fKoUUrjDr4NVW/OkTUssSC2MN11T3grdqYgbJi1oKV6ChJVxF7Zp8C1jNqm8l2LMUL/sNmdjC3rUSUlgBGqM6eifw96pvJdmLKbzZnSUGWIlNiyOFhaEhU0B18i4Q9Zfw5Udwj63sYwp43bY9xLRp8aWwME/YtSwZRB6xt5/DWKpzUjm0Em+CwuwFS3fiTJ3BC/dSbTAOA8MDdpOEN8EXJQaA3HcKqE7dCe+qzGGvojkFPMVB5hbXIa/xJgiYWfGWT5+QVUDNzGKLvYwDSmQdEiW0IoYn1cQcqY7ckSv2FbinakuMN8CUKCHKAB6zxCPoqhN3pGX8YBzFEcAYq3NdrAVW0fLkvLgc1YG74q0S5sA9VVtiqqTCa4HlqZ6vpT3JOkCvCgHFY4n4DY0wd5E7CJi6vW3dO9KrywDG2tM+PAIx4yJbf4gyYCVi+RmOouM70mtQaTBn6hopWE1icT3xhzSCDz3u4okPyvaYdsZQs1DXSIFMkjlfrET8ocUPuSYIRVINvju9FaYUuYuYuWbOl+pn/CFppidq3avB352WcURq/BDj2WtBGVCKucygDsjlCU9CbdHL5tYW86+/ZGKeYS50qQNyeFrtYU0P299H5LbP4og5eFYH5PCUXc09rn5A5ojcXVAUCYWYs0t1QA5PK0ZtUTPj8MZguaXsuP5UTCGeHD+s8T6PoChhYZkDde0UsA7zdgVviC4R5PqgnKj47pQMMksW89zZEH/IYZegIoMzVbUngb/3BJqc64kZFLlzuMAfb4WSc1XjPgEyAqzFmZXJsZxTY89njgM88OclD83AGww9BaqLmH8WGJNOsAa8RsZnZyuQZ3G7Mv6YQuSav9Jmb5CPOzXmj+145QYktbV+kI5no2uGPy+17Aw++aeAesNTwkYPvrMXKETuQzGfVIPondxaBMYAHfiBN7yA9UGpTAHVuEF7poCaoyOsWjkrBBYC1gelUiN9GuTBXKg5OuJFISw9Wh+UylCIfshVCJg9hfrgLFNANW7Qnimg5iiFYgoxgsp+KBJUehlpZz/kpp3UoFCI79WHZxmFqX7ILUwVVYhRuu6H3NI1SQWZ5re8YQ+D8nVu6XpsbvVDkc0te8gy9+v/nKsaN2gPc6HmKBn+jAdknkPuHC7wB5eBQsxP3WaAq1GNG7Qn1+0v8GfJQU2YwahFXE9uDeIFe4FCQG4J+1Mew++ZIr8oYy+8T01xrmrkoB25Ft5qEHP8YULeIMzNNFAk1chBO3IXs/wqn/1YyMuviZxkxBHXUfzLvgSWmBxP2jIKVNfhKUihDDDHj+qAXJ76gyF3wPuDIcuvDq4P8DJ2PtvjedL6JX6A9QFeSn5PcZAGE6rmIgX7CsbyJa31AV4wQeORunYw1rnVSStIwlJ/Wh9UglGkaofnh0tlZhkfYKA5npr4qEm0AeuQW3sArAKxA/O9yOMDDPMrLweeZFiJ+hQpVa9RwvHfI/QP1mEJBEuihJghFMKz+wlP+Q9UesT9ZNQWUhh4S0cywOVMAdWhQT5TwOPOd5HCgD1FlfvQjIGVUZ0a5OO13LtIYcCewkUpPJEsPOG/aewF7+9ukz0yr5uWXwq/sLKm10qgUFNAdXCQzhTwBpK2s7mZMEjhF7GV8NQlgJhEdXKQzrIBlYklC7BUJtdIYQSxBErhVQgYWUc+JbIKSxR2ywlSWAm0cnzt7zyM2eaKTgRXgzIcBqRSWBEszXiyKh3GqoR1Ntd/GINIYWVo1BRQAzD4P1PAG0SCJQdJMYgUNoDYhA6rgRj8XJr2bB0YFLBQBkgqH0jhAV5/ZrBnMp6deIcx8TzbGoNC4C6SryeFO1gFM/cZvjXDUrwyBUpYhmykcIc4ly0R7AB+cgSaP49BiZjBhRQegPlBITBFpdwHivbJKSl9L7XAXEhhAlbk8Ja113zidzso2JVaWG6kMAELVlCK0j4PZZsCavCeBMGjtxy9BpfjcjtSmEgcT5T2fVzvybuk9C0pDTwBY2auXH2ehBSewIoeUMMHsoKeFHBOAdeEbcDY2zy4MkApPIkFmaU13sA9PWFjjD7UeNIJZTD37fmy9owUdgomkUG9WzHrx0Bpl2rEylAkwJfCzsESkY30rBi0jcfka1lNiAP7YtmeFN4EVkeL/9jsDLSFCaoRT8VUUQaQwkKQW9fwmQoCKazGFd8+557cu5ZbUFhGUboOVFUhrHjVcqDA/Comu0aGMgX4CWH6VtMlHFHFCklhISz7gCs3bBg4awsrmcnEtLOyURgmGJ8PvEbGZxzDsZzDuVyjtiu4HCksSFynwLx1U6IdaKSwMJhVC4D49/GrrCBm3ZotJCmsAMGlxRRw6Z7/TYita7M4TAorEscVVwZkPYNViBcPY6aOq4IUVgZFoMQ64ol3YquAe22+aKRw0BxcgsVZZhUuWTBSeCEMwqcFnVgBUwRcxaX9l8ILsV+vYWA+JcZgEdDv1gU8iRReyNp0ohhdDNSnIIUXw4ohuIoVg9f41VZ7IyWhzfSHwlz37lAKO8EUI07B4A4Ww5Tgdm2Xwg5hZeFnsRS9DiptNEsQKwHQ9lvERFJ4U1AUJoNJYfBbpm3ca60EtOV28Y8U3hS1MrEoyIk/TFHUuTFMLiafFc/xTCrn2jXUOcDnwPEtlbEoUnhTmAQmg0lBCeKgNGZrspRCKdS5j0EKHwQrnVXOE1Ws7r1H1K0GgiIBASFKghwl49zuswQfP33zP7+P8Rke6878AAAAAElFTkSuQmCC');
}
</style><p><div class="item"><a class="header" id="general" href="#general" title="(permalink)">General</a><div class="content"><p>
<div class="item"><a class="header" id="lua_error_handler" href="#lua_error_handler" title="(permalink)">lua_error_handler : script(text, state_id)</a><div class="content"><p>
If you assign a script into this global variable, it will be called whenever an error occurs in Lua code.
</p><p>
So you could, for instance, make a script that displays a message box with error text,
</p><pre class="gmlmd">
<span class="co">/// scr_handle_lua_error(msg, state)</span>
<span class="kw">var</span> <span class="lv">state</span> <span class="op">=</span> <span class="sv">argument1</span><span class="op">;</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="st">"A Lua error occurred: "</span> <span class="op">+</span> <span class="sv">argument0</span><span class="op">)</span><span class="op">;</span>
</pre><p>and then link it up on game start:
</p><pre class="gmlmd">
<span class="sv">lua_error_handler</span> <span class="op">=</span> <span class="uf">scr_handle_lua_error</span><span class="op">;</span>
</pre></div></div>
<div class="item"><a class="header" id="lua_get_cwd" href="#lua_get_cwd" title="(permalink)">lua_get_cwd()<span class="ret-arrow">&#10140;</span>path</a><div class="content"><p>
Returns the current working directory (<tt>_wgetcwd</tt> on Windows, <tt>getcwd</tt> otherwise).
</p></div></div>
<div class="item"><a class="header" id="lua_set_cwd" href="#lua_set_cwd" title="(permalink)">lua_set_cwd(path)</a><div class="content"><p>
Previously <tt>lua_chdir</tt>.
</p><p>
Changes the current working directory (<tt>_wchdir</tt> on WIndows, <tt>chdir</tt> otherwise).
</p><p>
This affects where the Lua code would be reading/writing files from by default.
</p></div></div>
</p></div></div>
<div class="item"><a class="header" id="lua_state" href="#lua_state" title="(permalink)">Lua states</a><div class="content"><p>
<div class="item"><a class="header" id="lua_state_intro" href="#lua_state_intro" title="(permalink)">Introduction to states</a><div class="content"><p>
A state is a subject to most of the Apollo's functions.
</p><p>
To put it simply, a state is the Lua program along with it's variables and current execution stack.
</p></div></div>
<div class="item"><a class="header" id="lua_state_create" href="#lua_state_create" title="(permalink)">lua_state_create()<span class="ret-arrow">&#10140;</span>state_id</a><div class="content"><p>
Creates a new Lua state and returns it's ID.
</p><pre class="gmlmd">
<span class="uv">state</span> <span class="op">=</span> <span class="sf">lua_state_create</span><span class="op">(</span><span class="op">)</span><span class="op">;</span>
</pre><p>Lua' <a href="https://www.lua.org/manual/5.3/manual.html#6">standard libraries</a> are included by default.
</p><p>
If you don't want to expose certain API functions to the user, you can use <tt>lua_global_set</tt> to remove those entries:
</p><pre class="gmlmd">
<a class="sf" href="#lua_global_set">lua_global_set</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"io"</span><span class="op">,</span> <span class="sv">undefined</span><span class="op">)</span><span class="op">;</span>
</pre></div></div>
<div class="item"><a class="header" id="lua_state_destroy" href="#lua_state_destroy" title="(permalink)">lua_state_destroy(state_id)</a><div class="content"><p>
Destroys the given state, freeing up any resources used by it.
</p><pre class="gmlmd">
<span class="sf">lua_state_destroy</span><span class="op">(</span><span class="uv">state</span><span class="op">)</span><span class="op">;</span>
</pre><p>It is generally recommended that you clean up your states once they are no longer needed to avoid memory leaks.
</p></div></div>
<div class="item"><a class="header" id="lua_thread_create" href="#lua_thread_create" title="(permalink)">lua_thread_create(state_id)<span class="ret-arrow">&#10140;</span>thread_state_id</a><div class="content"><p>
Creates a Lua "thread"-state attached to the given state.
</p><p>
Not to be confused with OS-level threads,
Lua "threads" are separate execution states sharing the data from a parent state,
coroutine-style.
</p><p>
These share indexes with states.
</p></div></div>
<div class="item"><a class="header" id="lua_thread_destroy" href="#lua_thread_destroy" title="(permalink)">lua_thread_destroy(state_id)</a><div class="content"><p>
Destroys a previously created Lua "thread"-state.
</p><p>
Technically an alias for <a href="#lua_state_destroy">lua_state_destroy</a> due to shared index space.
</p></div></div>
<div class="item"><a class="header" id="lua_state_exists" href="#lua_state_exists" title="(permalink)">lua_state_exists(state_id)</a><div class="content"><p>
Returns whether there's a state/thread with the given ID.
</p></div></div>
<div class="item"><a class="header" id="lua_reset" href="#lua_reset" title="(permalink)">lua_reset()</a><div class="content"><p>
Destroys all existing Lua states and threads.
</p><p>
This also causes newly made states to have IDs start at 0 again.
</p></div></div>
<div class="item"><span class="header">lua_state_reuse_indexes()<span class="ret-arrow">&#10140;</span>count</span><div class="content"><p>
Here's the deal: As you might know, reusing indexes can cause some really mysterious bugs
if your code accidentally continues to use an index (which is now taken up by something else).
</p><p>
So, by default, Apollo will not do that. Which, in turn, means that after states are destroyed,
4 bytes worth of data (which were onece the state's address) will continue to be reserved per state.
</p><p>
While it would take about 500 000 states to run out of memory this way,
you might prefer not to have that anyway.
</p><p>
So, calling this function will mark that memory as available again, causing Apollo to reuse the
destroyed indexes for newly created states. This will only affect indexes of states that are
destroyed as of calling the function.
</p><p>
In other words, while developing your project, you would not call this at all
(so that if you use a destroyed state, you get an obvious error), and for final release
version you would call it once in a while (such as during room transitions - to further
reduce the odds of anything going strange).
</p><p>
The function returns the number of indexes that will be reused as result.
</p></div></div>
</p></div></div>
<div class="item"><a class="header" id="lua_add" href="#lua_add" title="(permalink)">Adding Lua code</a><div class="content"><p>
<div class="item"><a class="header" id="lua_add_code" href="#lua_add_code" title="(permalink)">lua_add_code(state_id, lua_code)</a><div class="content"><p>
Attempts to compile the given snippet of Lua code, add it to given Lua state, and execute it.
Returns whether all steps succeeded.
</p><pre class="gmlmd">
<span class="sf">lua_add_code</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"print('Hello!')"</span><span class="op">)</span><span class="op">;</span>
</pre><p>Same as with other things, compilation/runtime errors are forwarded to <tt>lua_error_handler</tt> if it is defined.
</p></div></div>
<div class="item"><a class="header" id="lua_add_file" href="#lua_add_file" title="(permalink)">lua_add_file(state_id, path, chdir = true)</a><div class="content"><p>
Attempts to load and run a snippet of Lua code from the file at the given path.
</p><p>
The function mimics GMS' file handling rules, preferring files in game's save directory over the files in game's installation directory.
</p><p>
It will, however, also accept absolute paths, bypassing sandbox restrictions.
</p><p>
So, if you added an included file called "some.lua", you could then load it with
</p><pre class="gmlmd">
<span class="sf">lua_add_file</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"some.lua"</span><span class="op">)</span><span class="op">;</span>
</pre><p>If <tt>chdir</tt> is left at <tt>true</tt>, the function will automatically call <a href="#lua_chdir">lua_chdir</a> when given a relative path so that the Lua code would work with files in that directory.
</p></div></div>
</p></div></div>
<div class="item"><a class="header" id="lua_global" href="#lua_global" title="(permalink)">Using Lua variables</a><div class="content"><p>
<div class="item"><a class="header" id="lua_global_get" href="#lua_global_get" title="(permalink)">lua_global_get(state_id, name)<span class="ret-arrow">&#10140;</span>value</a><div class="content"><p>
Returns the value of the state's given global variable.
</p><p>
Note that this returns <tt>undefined</tt> for unsupported types.
</p><pre class="gmlmd">
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"test = 'Hello!'"</span><span class="op">)</span><span class="op">;</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="sf">lua_global_get</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"test"</span><span class="op">)</span><span class="op">)</span><span class="op">;</span>
</pre></div></div>
<div class="item"><a class="header" id="lua_global_set" href="#lua_global_set" title="(permalink)">lua_global_set(state_id, name, value)</a><div class="content"><p>
Changes the value of the state's given global variable.
</p><pre class="gmlmd">
<span class="sf">lua_global_set</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"test"</span><span class="op">,</span> <span class="st">"Hello!"</span><span class="op">)</span><span class="op">;</span>
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"print(test)"</span><span class="op">)</span><span class="op">;</span> <span class="co">// 'Hello!'</span>
</pre></div></div>
<div class="item"><a class="header" id="lua_global_type" href="#lua_global_type" title="(permalink)">lua_global_type(state_id, name)<span class="ret-arrow">&#10140;</span>lua_type</a><div class="content"><p>
Returns the type of a state's global variable as a constant.
</p><p>
Possible values are as following: </p><ul>
<li> <tt>lua_type_none</tt>: placeholder for type ID 0
</li><li> <tt>lua_type_nil</tt>: an equivalent of GML's <tt>undefined</tt>. Not-yet-set values are <tt>nil</tt>.
</li><li> <tt>lua_type_bool</tt>: a boolean value (true or false).
</li><li> <tt>lua_type_number</tt>: a numeric type, same as GML's real.
</li><li> <tt>lua_type_string</tt>: same as GML's string type.
</li><li> <tt>lua_type_table</tt>: a Lua <a href="https://www.lua.org/manual/5.3/manual.html#2.1">table</a> (array or dictionary).
</li><li> <tt>lua_type_function</tt>: a Lua function (that can be called via <a href="#lua_call">lua_call</a> group).
</li><li> <tt>lua_type_thread</tt>: a Lua "thread"/coroutine.
</li><li> <tt>lua_type_userdata</tt>: an external reference (see Lua doc).
</li><li> <tt>lua_type_lightuserdata</tt>: an external struct (see Lua doc).
</li><li> <tt>lua_type_unknown</tt>: unrecognized type (never returns with normal Lua library)
</li></ul><p>
If the state does not exist, an error is thrown.
</p><pre class="gmlmd">
<span class="kw">if</span> <span class="op">(</span><span class="sf">lua_global_type</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"test"</span><span class="op">)</span> <span class="op">==</span> <span class="uv">lua_type_function</span><span class="op">)</span> <span class="cb">{</span>
<a class="sf" href="#lua_call">lua_call</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"test"</span><span class="op">)</span><span class="op">;</span>
<span class="cb">}</span> <span class="kw">else</span> <span class="sf">show_debug_message</span><span class="op">(</span><span class="st">"The state does not have a function called `test`!"</span><span class="op">)</span><span class="op">;</span>
</pre></div></div>
<div class="item"><a class="header" id="lua_global_typeof" href="#lua_global_typeof" title="(permalink)">lua_global_typeof(state_id, name)<span class="ret-arrow">&#10140;</span>type_name</a><div class="content"><p>
Returns the type of a state's global variable as a string.
</p><p>
Outside of debugging, you should prefer to use <a href="#lua_global_type">lua_global_type</a>, as numeric comparisons are much faster than string comparisons.
</p><p>
The usual returned values are as following:
</p><ul>
<li> <tt>"nil"</tt>: an equivalent of GML's <tt>undefined</tt>. Not-yet-set values are <tt>nil</tt>.
</li><li> <tt>"boolean"</tt>: a boolean value (true or false).
</li><li> <tt>"number"</tt>: a numeric type, same as GML's real.
</li><li> <tt>"string"</tt>: same as GML' string type.
</li><li> <tt>"table"</tt>: a Lua <a href="https://www.lua.org/manual/5.3/manual.html#2.1">table</a>. You currently can't do much with these from GML side.
</li><li> <tt>"function"</tt>: a Lua function - as such, a thing that could be called via <tt>lua_call</tt>.
</li><li> <tt>"thread"</tt>: a Lua "thread"/coroutine (more on these later).
</li></ul><p>
So you could use a snippet like this to check if a state has a function named "test":
</p><pre class="gmlmd">
<span class="kw">if</span> <span class="op">(</span><span class="sf">lua_global_typeof</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"test"</span><span class="op">)</span> <span class="op">==</span> <span class="st">"function"</span><span class="op">)</span> <span class="cb">{</span>
<a class="sf" href="#lua_call">lua_call</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"test"</span><span class="op">)</span><span class="op">;</span>
<span class="cb">}</span> <span class="kw">else</span> <span class="sf">show_debug_message</span><span class="op">(</span><span class="st">"The state does not have a function called `test`!"</span><span class="op">)</span><span class="op">;</span>
</pre></div></div>
</p></div></div>
<div class="item"><a class="header" id="lua_call" href="#lua_call" title="(permalink)">Calling Lua code</a><div class="content"><p>
<div class="item"><a class="header" id="lua_call" href="#lua_call" title="(permalink)">lua_call(state_id, name, ...arguments)</a><div class="content"><p>
Attempts to call a Lua function stored in the given global variable of the state.
</p><p>
Returns the first of the function's returned values.
</p><p>
If an error occurs, calls <tt>lua_error_handler</tt> and returns <tt>undefined</tt>.
</p><pre class="gmlmd">
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"function greet(s) return 'Hello, ' .. s end"</span><span class="op">)</span><span class="op">;</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="sf">lua_call</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"greet"</span><span class="op">,</span> <span class="st">"GameMaker"</span><span class="op">)</span><span class="op">)</span><span class="op">;</span>
</pre><p>If the function returns multiple values, the first one returned (use <a href="#lua_call_m">lua_call_m</a> / <a href="#lua_call_xm">lua_call_xm</a> / <a href="#lua_call_wm">lua_call_wm</a> / <a href="#lua_call_wxm">lua_call_wxm</a> to support multiple returned values).
</p><p>
If the function returns no values, you get an <tt>undefined</tt>.
</p></div></div>
<div class="item"><a class="header" id="lua_call_w" href="#lua_call_w" title="(permalink)">lua_call_w(state_id, name, argument_array)</a><div class="content"><p>
Like <a href="#lua_call">lua_call</a>, but allows to pass in the arguments as an array.
</p><pre class="gmlmd">
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"function add(a, b) return a + b end"</span><span class="op">)</span><span class="op">;</span>
<span class="kw">var</span> <span class="lv">args</span> <span class="op">=</span> <span class="sf">array_create</span><span class="op">(</span><span class="nu">2</span><span class="op">,</span> <span class="nu">7</span><span class="op">)</span><span class="op">;</span> <span class="co">// [7, 7]</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="sf">lua_call_w</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"add"</span><span class="op">,</span> <span class="lv">args</span><span class="op">)</span><span class="op">)</span><span class="op">;</span> <span class="co">// 14</span>
</pre></div></div>
<div class="item"><a class="header" id="lua_call_m" href="#lua_call_m" title="(permalink)">lua_call_m(state_id, name, ...arguments)<span class="ret-arrow">&#10140;</span>results_array</a><div class="content"><p>
Like <a href="#lua_call">lua_call</a>, but packs returned value(s) into an array.
</p><pre class="gmlmd">
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"function multi(v) return v, 1, 2 end"</span><span class="op">)</span><span class="op">;</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="sf">lua_call_m</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"multi"</span><span class="op">,</span> <span class="st">"test"</span><span class="op">)</span><span class="op">)</span><span class="op">;</span> <span class="co">// ["test", 1, 2]</span>
</pre><p>Returns the array with value(s) or an empty array if the function returned nothing.
</p></div></div>
<div class="item"><a class="header" id="lua_call_xm" href="#lua_call_xm" title="(permalink)">lua_call_xm(state_id, name, results_array, ...arguments)<span class="ret-arrow">&#10140;</span>results_count</a><div class="content"><p>
Like <a href="#lua_call_m">lua_call_m</a>, but instead writes returned value(s) into the specified array.
</p><p>
This allows to reuse the same array for frequently performed operations.
</p><p>
Returns the number of values written to the array.
</p><pre class="gmlmd">
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"function multi(v) return v, 1, 2 end"</span><span class="op">)</span><span class="op">;</span>
<span class="kw">var</span> <span class="lv">arr</span> <span class="op">=</span> <span class="sf">array_create</span><span class="op">(</span><span class="nu">5</span><span class="op">)</span><span class="op">;</span>
<span class="kw">var</span> <span class="lv">n</span> <span class="op">=</span> <span class="sf">lua_call_xm</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"multi"</span><span class="op">,</span> <span class="lv">arr</span><span class="op">,</span> <span class="st">"test"</span><span class="op">)</span><span class="op">;</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="lv">arr</span><span class="op">)</span><span class="op">;</span> <span class="co">// ["test", 1, 2, 0, 0]</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="lv">n</span><span class="op">)</span><span class="op">;</span> <span class="co">// 3</span>
</pre></div></div>
<div class="item"><a class="header" id="lua_call_wm" href="#lua_call_wm" title="(permalink)">lua_call_wm(state_id, name, argument_array)<span class="ret-arrow">&#10140;</span>results_array</a><div class="content"><p>
A combination of <a href="#lua_call_w">lua_call_w</a> and <a href="#lua_call_m">lua_call_m</a> - takes arguments as an array and returns a new array with returned values.
</p></div></div>
<div class="item"><a class="header" id="lua_call_wxm" href="#lua_call_wxm" title="(permalink)">lua_call_wxm(state_id, name, argument_array, results_array)<span class="ret-arrow">&#10140;</span>results_count</a><div class="content"><p>
A combination of <a href="#lua_call_w">lua_call_w</a> and <a href="#lua_call_xm">lua_call_xm</a> - takes arguments as an array, writes results to another array, and returns the number of results written.
</p></div></div>
</p></div></div>
<div class="item"><a class="header" id="Exposing-GML-to-Lua" href="#Exposing-GML-to-Lua" title="(permalink)">Exposing GML to Lua</a><div class="content"><p>
<div class="item"><a class="header" id="lua_add_function" href="#lua_add_function" title="(permalink)">lua_add_function(state_id, name, script_id)</a><div class="content"><p>
Exposes the given GM script to a Lua state as a global function.
</p><p>
For example, if you have some
</p><pre class="gmlmd">
<span class="co">/// scr_alert(text)</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="sv">argument0</span><span class="op">)</span><span class="op">;</span>
</pre><p>you could expose it to Lua via
</p><pre class="gmlmd">
<span class="sf">lua_add_function</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"alert"</span><span class="op">,</span> <span class="uf">scr_alert</span><span class="op">)</span><span class="op">;</span>
</pre><p>If you want to organize your functions in Lua-like modules, you can use lua_add_code for that:
</p><pre class="gmlmd">
<span class="sf">lua_add_function</span><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"game_alert"</span><span class="op">,</span> <span class="uf">scr_alert</span><span class="op">)</span><span class="op">;</span>
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">'
game = { alert: game_alert }
'</span><span class="op">)</span><span class="op">;</span>
</pre><p>which would then allow you to do
</p> <pre class="lua"><span class="kw1">game</span>.<span class="kw1">alert</span><span class="op">(</span><span class="st0">"Hello!"</span><span class="op">)</span></pre><p>on Lua side of things.
</p></div></div>
<div class="item"><a class="header" id="lua_return" href="#lua_return" title="(permalink)">lua_return(...values)</a><div class="content"><p>
Lua <a href="https://www.lua.org/manual/5.3/manual.html#3.3.4">allows to</a> return multiple values from a function call at once.
</p><p>
This function helps to do that in scripts exposed to Lua via <tt>lua_add_function</tt>.
</p><p>
So, you could have
</p><pre class="gmlmd">
<span class="co">/// lengthdir_xy(len, dir)</span>
<span class="kw">var</span> <span class="lv">len</span> <span class="op">=</span> <span class="sv">argument0</span><span class="op">,</span> <span class="lv">dir</span> <span class="op">=</span> <span class="sv">argument1</span><span class="op">;</span>
<span class="kw">return</span> <span class="sf">lua_return</span><span class="op">(</span><span class="sf">lengthdir_x</span><span class="op">(</span><span class="lv">len</span><span class="op">,</span> <span class="lv">dir</span><span class="op">)</span><span class="op">,</span> <span class="sf">lengthdir_y</span><span class="op">(</span><span class="lv">len</span><span class="op">,</span> <span class="lv">dir</span><span class="op">)</span><span class="op">)</span><span class="op">;</span>
</pre><p>expose it via
</p><pre class="gmlmd">
<a class="sf" href="#lua_add_function">lua_add_function</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"lengthdir_xy"</span><span class="op">,</span> <span class="uv">lengthdir_xy</span><span class="op">)</span><span class="op">;</span>
</pre><p>and use it from Lua side like
</p>
<pre class="lua"><span class="kw1">local</span> x<span class="op">,</span> y <span class="op">=</span> <span class="kw1">lengthdir_xy</span><span class="op">(</span><span class="nu0">30</span><span class="op">,</span> <span class="nu0">45</span><span class="op">)</span>
<span class="kw1">print</span><span class="op">(</span>x<span class="op">,</span> y<span class="op">)</span> <span class="co1">-- 21.21, -21.21</span></pre>
</div></div>
<div class="item"><a class="header" id="lua_return_w" href="#lua_return_w" title="(permalink)">lua_return_w(values:array)</a><div class="content"><p>
Same as aforementioned <tt>lua_return</tt>, but returns the contents of an array as a value list instead.
Note this will not work for nested arrays, however.
</p></div></div>
<div class="item"><a class="header" id="lua_return_add" href="#lua_return_add" title="(permalink)">lua_return_add(...values)</a><div class="content"><p>
Add zero or more values to the list of returned values.
</p><p>
This is particularly handy for any GML operations that are done in a loop, e.g.
</p><pre class="gmlmd">
<span class="co">/// instance_find_all(obj)</span>
<span class="kw">with</span> <span class="op">(</span><span class="sv">argument0</span><span class="op">)</span> <span class="sf">lua_return_add</span><span class="op">(</span><span class="sv">id</span><span class="op">)</span><span class="op">;</span>
<span class="kw">return</span> <span class="sf">lua_return_add</span><span class="op">(</span><span class="op">)</span><span class="op">;</span>
</pre><p>The last line with an empty <tt>lua_return_add</tt> is needed to return 0 values if loop matches no instances (as runtime would otherwise assume that you are going to return something with a regular <tt>return</tt>).
</p></div></div>
<div class="item"><a class="header" id="lua_bool" href="#lua_bool" title="(permalink)">lua_bool(value)</a><div class="content"><p>
While Lua has a separate boolean type, GameMaker uses <tt>1</tt> as true-value and <tt>0</tt> as false-value.
This makes it hard to tell whether you were meaning to send <tt>1</tt> or <tt>true</tt>.
</p><p>
So there's this function, which returns either a <tt>lua_true</tt> or a <tt>lua_false</tt> depending on argument, which can be told apart by the extension explicitly, and will become the according Lua values once sent to Lua.
</p></div></div>
<div class="item"><a class="header" id="lua_current" href="#lua_current" title="(permalink)">lua_current</a><div class="content"><p>
When a Lua state calls the exposed GML script, this variable holds the ID of the "caller" state. Can be used if you want to do anything aside of just returning value(s).
</p></div></div>
<div class="item"><a class="header" id="lua_show_error" href="#lua_show_error" title="(permalink)">lua_show_error(text)</a><div class="content"><p>
Sends an error message to the currently executing Lua state.
</p><p>
This should only be used inside scripts exposed via <a href="#lua_add_function">lua_add_function</a>.
</p><pre class="gmlmd">
<span class="co">/// scr_variable_global_get(name)</span>
<span class="kw">if</span> <span class="op">(</span><span class="sf">is_string</span><span class="op">(</span><span class="sv">argument0</span><span class="op">)</span><span class="op">)</span> <span class="cb">{</span>
<span class="kw">return</span> <span class="sf">variable_global_get</span><span class="op">(</span><span class="sv">argument0</span><span class="op">)</span><span class="op">;</span>
<span class="cb">}</span> <span class="kw">else</span> <span class="sf">lua_show_error</span><span class="op">(</span><span class="st">"Expected a variable name to be a string."</span><span class="op">)</span><span class="op">;</span>
</pre></div></div>
<div class="item"><a class="header" id="Using-GM-instance-variables-from-Lua" href="#Using-GM-instance-variables-from-Lua" title="(permalink)">Using GM instance variables from Lua</a><div class="content"><p>
If you are using GameMaker Studio 2 or an Early Access version of GameMaker: Studio 1, you can have Lua directly read and write variables on GameMaker instances.
</p><p>
To do so, you would add three scripts to your project:
</p><pre class="gmlmd">
<span class="co">/// ref_variable_instance_get(context, name)</span>
<span class="kw">var</span> <span class="lv">q</span> <span class="op">=</span> <span class="sv">argument0</span><span class="op">,</span> <span class="lv">s</span> <span class="op">=</span> <span class="sv">argument1</span><span class="op">;</span>
<span class="kw">with</span> <span class="op">(</span><span class="lv">q</span><span class="op">)</span> <span class="kw">return</span> <span class="sf">variable_instance_get</span><span class="op">(</span><span class="sv">id</span><span class="op">,</span> <span class="lv">s</span><span class="op">)</span><span class="op">;</span>
<span class="kw">if</span> <span class="op">(</span><span class="lv">q</span> <span class="op">&lt;</span> <span class="nu">100000</span><span class="op">)</span> <span class="cb">{</span>
<a class="sf" href="#lua_show_error">lua_show_error</a><span class="op">(</span><span class="st">"Couldn't find any instances of "</span> <span class="op">+</span> <span class="sf">string</span><span class="op">(</span><span class="lv">q</span><span class="op">)</span>
<span class="op">+</span> <span class="st">" ("</span> <span class="op">+</span> <span class="sf">object_get_name</span><span class="op">(</span><span class="lv">q</span><span class="op">)</span> <span class="op">+</span> <span class="st">")"</span><span class="op">)</span><span class="op">;</span>
<span class="cb">}</span> <span class="kw">else</span> <a class="sf" href="#lua_show_error">lua_show_error</a><span class="op">(</span><span class="st">"Couldn't find instance "</span> <span class="op">+</span> <span class="sf">string</span><span class="op">(</span><span class="lv">q</span><span class="op">)</span><span class="op">)</span><span class="op">;</span>
<span class="kw">return</span> <span class="sv">undefined</span><span class="op">;</span>
</pre><p>(reads a variable from an instance),
</p><pre class="gmlmd">
<span class="co">/// ref_variable_instance_set(context, name, value)</span>
<span class="kw">var</span> <span class="lv">q</span> <span class="op">=</span> <span class="sv">argument0</span><span class="op">,</span> <span class="lv">s</span> <span class="op">=</span> <span class="sv">argument1</span><span class="op">,</span> <span class="lv">v</span> <span class="op">=</span> <span class="sv">argument2</span><span class="op">,</span> <span class="lv">n</span> <span class="op">=</span> <span class="nu">0</span><span class="op">;</span>
<span class="kw">with</span> <span class="op">(</span><span class="lv">q</span><span class="op">)</span> <span class="cb">{</span> <span class="sf">variable_instance_set</span><span class="op">(</span><span class="sv">id</span><span class="op">,</span> <span class="lv">s</span><span class="op">,</span> <span class="lv">v</span><span class="op">)</span><span class="op">;</span> <span class="lv">n</span><span class="op">++</span><span class="op">;</span> <span class="cb">}</span>
<span class="kw">if</span> <span class="op">(</span><span class="lv">n</span><span class="op">)</span> <span class="kw">exit</span><span class="op">;</span>
<span class="kw">if</span> <span class="op">(</span><span class="lv">q</span> <span class="op">&lt;</span> <span class="nu">100000</span><span class="op">)</span> <span class="cb">{</span>
<a class="sf" href="#lua_show_error">lua_show_error</a><span class="op">(</span><span class="st">"Couldn't find any instances of "</span> <span class="op">+</span> <span class="sf">string</span><span class="op">(</span><span class="lv">q</span><span class="op">)</span>
<span class="op">+</span> <span class="st">" ("</span> <span class="op">+</span> <span class="sf">object_get_name</span><span class="op">(</span><span class="lv">q</span><span class="op">)</span> <span class="op">+</span> <span class="st">")"</span><span class="op">)</span><span class="op">;</span>
<span class="cb">}</span> <span class="kw">else</span> <a class="sf" href="#lua_show_error">lua_show_error</a><span class="op">(</span><span class="st">"Couldn't find instance "</span> <span class="op">+</span> <span class="sf">string</span><span class="op">(</span><span class="lv">q</span><span class="op">)</span><span class="op">)</span><span class="op">;</span>
</pre><p>(writes a variable to an instance(s)),
</p><pre class="gmlmd">
<span class="co">/// ref_variable_instance_init(lua_state)</span>
<span class="kw">var</span> <span class="lv">q</span> <span class="op">=</span> <span class="sv">argument0</span><span class="op">;</span>
<a class="sf" href="#lua_add_function">lua_add_function</a><span class="op">(</span><span class="lv">q</span><span class="op">,</span> <span class="st">"variable_instance_get"</span><span class="op">,</span> <span class="uv">ref_variable_instance_get</span><span class="op">)</span><span class="op">;</span>
<a class="sf" href="#lua_add_function">lua_add_function</a><span class="op">(</span><span class="lv">q</span><span class="op">,</span> <span class="st">"variable_instance_set"</span><span class="op">,</span> <span class="uv">ref_variable_instance_set</span><span class="op">)</span><span class="op">;</span>
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="lv">q</span><span class="op">,</span> <span class="st">'-- ref_variable_instance_init()
__idfields = __idfields or { };
debug.setmetatable(0, {
__index = function(self, name)
if (__idfields[name]) then
return _G[name];
else
return variable_instance_get(self, name);
end
end,
__newindex = variable_instance_set,
})
'</span><span class="op">)</span><span class="op">;</span>
</pre><p>(exposes the above scripts to a Lua state and sets it up to use them when trying to read/write a field on a numeric value (id)).
</p><p>
Then you can use them as following:
</p><pre class="gmlmd">
<span class="co">// create a Lua state:</span>
<span class="uv">state</span> <span class="op">=</span> <a class="sf" href="#lua_state_create">lua_state_create</a><span class="op">(</span><span class="op">)</span><span class="op">;</span>
<span class="co">// allow the state to work with GM instances:</span>
<span class="uf">ref_variable_instance_init</span><span class="op">(</span><span class="uv">state</span><span class="op">)</span><span class="op">;</span>
<span class="co">// add a test function to the state -</span>
<span class="co">// function takes an instance and modifies it's `result` variable.</span>
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"function test(q) q.result = 'Hello!' end"</span><span class="op">)</span><span class="op">;</span>
<span class="co">// call the test-function for the current instance and display the result:</span>
<span class="uv">result</span> <span class="op">=</span> <span class="st">""</span><span class="op">;</span>
<a class="sf" href="#lua_call">lua_call</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"test"</span><span class="op">,</span> <span class="sv">id</span><span class="op">)</span><span class="op">;</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="uv">result</span><span class="op">)</span><span class="op">;</span>
</pre></div></div>
</p>
<script type="text/javascript">(function(){function v(a,b){var l=a.exec(b);return null!=l?l[1]:null}window.luaScriptGen=function(a){var b,l,y=RegExp("^[aeouiy]","g"),f="",f=f+"var state = argument0;\n",m=[],w="",g;b=RegExp("^[ \\t]*(\\w+)[ \\t]*(?:#|=[ \\t]*([^\n;]+))[ \\t]*$","gm");a.replace(b,function(a,b,d){null==d&&(d=b);f+='lua_global_set(state, "'+b+'", '+d+");\n";return a});l=RegExp(":[ \\t]*(\\w+)","g");b=RegExp("^[ \\t]*(:)?[ \\t]*(\\w+)(?:[ \\t]*:[ \\t]*(\\w+))?[ \\t]*\\(([^\n)]*)\\)[ \\t]*(:[ \\t]*(\\w*)[ \\t]*)?[;]?[ \\t]*$",
"gm");a.replace(b,function(b,a,d,g,c,q,n){a=null!=a;null==g&&(g=d);var e=c;n=(q=null!=q)&&"bool"==n.toLowerCase();a&&(m.push(d),""!=e&&(e=", "+e),e="self"+e);f+='lua_add_function(state, "'+d+'", ref_'+d+");\n";c=""+("\n#define ref_"+d+"\n/// "+d+"("+e+")\n");var r;r=""!=e?e.split(","):[];e=r.length;c+="if (argument_count != "+e+") return "+('lua_show_error("'+d+": Expected "+e+" argument")+(1!=e?"s":"")+', got " + string(argument_count));\n';for(var h=-1;++h<e;){var x=r[h],p=v(l,x);if(null!=p){var k;
switch(p){case "bool":case "color":case "float":case "id":case "index":case "int":case "number":case "real":k=["is_real","is_int64"];break;case "string":k=["is_string"];break;default:k=null}if(null!=k){var t="argument"+h;c+="if !(";for(var u=0;u<k.length;c+=k[u++]+"("+t+")")0<u&&(c+=" || ");c+=""+(') return lua_show_error("'+d+": Expected "+(null!=v(y,p)?"an":"a")+(" "+p+" for "+t+" (")+x.trim()+('), got " + lua_print_value('+t+"));\n"))}}}a&&(c+="with (argument0) ");q&&(c+="return ");n&&(c+="lua_bool(");
c+=g+"(";for(h=d=a?1:0;h<e;c+="argument"+h++)h>d&&(c+=", ");n&&(c+=")");w+=""+(c+");\n");return b});b=m.length;if(0<b)for(a=0;a<b;f+="end');\n")for(g=m[a++],f+="lua_add_code(state, 'if (__idfields ~= nil) then\n",a=0;a<b;f+=" __idfields."+g+" = true;\n")g=m[a++];return f+=""+w}})();</script>
<p><div class="item"><a class="header" id="lua-gen" href="#lua-gen" title="(permalink)">Automatically exposing scripts/functions</a><div class="content"><p>
If you are building a medium-scale scripting API, you may find yourself needing to expose a large number of scripts (and/or built-in functions), as well as introducing argument type checking to prevent GML-side errors.
</p><p>
To save you from having to deal with that, Apollo includes a small utility that generates wrapper and loader scripts.
</p><p>
It accepts function definitions in <tt>funcname(arg1:type1, arg2:type2, ...):rtype</tt>,
</p><ul>
<li> <tt>arg1, arg2, ...</tt>: argument names. Will be shown in errors returned to Lua.
</li><li> <tt>type1, type2, ...</tt>: argument types. Optional.
If defined, code will be added to ensure that each argument matches it's type.
Known types are <tt>real</tt>, <tt>bool</tt>, <tt>string</tt>;
<tt>color</tt>, <tt>int</tt>, <tt>index</tt>, <tt>id</tt> can also be used, but are treated same as real.
</li><li> <tt>rtype</tt>: returned type, if the function returns a specific one.
If set to <tt>bool</tt>, return-statement will be wrapped in <tt>lua_bool</tt> call.
</li><li> If prefixed with <tt>:</tt>, function will be marked as "instance function" and will accept an instance ID as first argument, also allowing to call it as <tt>inst.func(...)</tt> if instance access scripts are set up.
</li></ul><p>
Constants can be defined either as <tt>name#</tt> (uses the value of same-named constant/variable) or <tt>name = value</tt> (computes the given GML value at inclusion time).
</p><p>
The tool is included with the extension as <tt>ApolloGen.exe</tt>;
</p><p>
A web-based version is available below:
</p> <textarea rows="7" id="gen_in" onchange="">// Examples:
show_debug_message(value)
is_string(value):bool
string_lower(s:string):
clamp(val:number, min:number, max:number):
:instance_destroy()
:motion_add(dir:number, speed:number)
c_white#
game_version = 1000</textarea><p>Whenever the contents of above field are changed, updated loader script will be output into the field below:
</p> <textarea rows="7" id="gen_out"></textarea><p>You can then save them into a .gml file and import it to your project.
</p> <script>(function() {
function proc() { gen_out.value = luaScriptGen(gen_in.value); }
gen_in.onchange = proc;
proc();
})();</script></div></div>
</p></div></div>
<div class="item"><a class="header" id="Writing-Lua-code" href="#Writing-Lua-code" title="(permalink)">Writing Lua code</a><div class="content"><p>
<div class="item"><a class="header" id="Learning-Lua" href="#Learning-Lua" title="(permalink)">Learning Lua</a><div class="content"><p>
<a href="https://www.lua.org/start.html">"getting started"</a> page on the Lua' website houses a large collection of links to tutorials, wikis, and other learning materials.
</p><p>
<a href="https://www.lua.org/manual/5.3/manual.html">Lua Manual</a> provides detailed explanations on how internal elements and all standard functions lf the language work.
</p></div></div>
<div class="item"><a class="header" id="Translating-GML-to-Lua" href="#Translating-GML-to-Lua" title="(permalink)">Translating GML to Lua</a><div class="content"><p>
If you have pre-existing GML code that you'd like to quickly port for use with Apollo, I have also developed an <a href="http://yal.cc/r/17/lua/gen/">online GML-&gt;Lua compiler</a>.
</p><p>
While automatic conversion won't make extensive use of Lua-specific language features, it produces functional code in vast majority of cases and the output is clean enough to tweak it manually if needed.
</p></div></div>
</p></div></div>
<div class="item"><a class="header" id="Lua-coroutines" href="#Lua-coroutines" title="(permalink)">Lua coroutines</a><div class="content"><p>
<div class="item"><a class="header" id="A-summary-on-Lua-coroutines" href="#A-summary-on-Lua-coroutines" title="(permalink)">A summary on Lua coroutines</a><div class="content"><p>
A <a href="https://en.wikipedia.org/wiki/Coroutine">coroutine</a>, in short, is a function that can pause/resume execution at arbitrary points. These can be used for iterators, cutscenes (pausing/resuming allows to write timing in an intuitive way), tweening, AI, or anything else that benefits from maintaining the state across multi-call execution.
</p></div></div>
<div class="item"><a class="header" id="lua_thread_create" href="#lua_thread_create" title="(permalink)">lua_thread_create(state_id)</a><div class="content"><p>
Creates a "thread" state for the given Lua state and returns it's ID.
</p><p>
Such "threads" share the global context (variables, functions, etc.) with their parent state, but have their own clal stack, meaning that they can do their own thing (namely, executing coroutines) while the parent state does something else.
</p><pre class="gmlmd">
<span class="uv">thread</span> <span class="op">=</span> <span class="sf">lua_thread_create</span><span class="op">(</span><span class="uv">state</span><span class="op">)</span><span class="op">;</span>
</pre></div></div>
<div class="item"><a class="header" id="lua_thread_destroy" href="#lua_thread_destroy" title="(permalink)">lua_thread_destroy(state_id)</a><div class="content"><p>
Destroys a previously created "thread" state.
</p><p>
Does not free resources of the parent state, only what was owned by the thread itself.
Is a convenience function and is interchangeable with <tt>lua_state_destroy</tt>.
</p></div></div>
<div class="item"><a class="header" id="lua_call_start" href="#lua_call_start" title="(permalink)">lua_call_start(state_id, name, ...arguments)</a><div class="content"><p>
Starts a coroutine call on the given sate, returns whether the operation succeeded.
</p><p>
Note that some functions will work oddly (or not work at all) on a state that is currently amidst the coroutine call, which is why you should generally create a thread for the coroutine call.
</p></div></div>
<div class="item"><a class="header" id="lua_call_start_w" href="#lua_call_start_w" title="(permalink)">lua_call_start_w(state_id, name, arguments:array)</a><div class="content"><p>
Same as <tt>lua_call_start</tt>, but takes arguments as an array.
</p></div></div>
<div class="item"><a class="header" id="lua_call_next" href="#lua_call_next" title="(permalink)">lua_call_next(state_id)</a><div class="content"><p>
Executes the next iteration on the given state and returns whether the coroutine call is ongoing (as opposed to finishing or encountering a runtime error).
</p><p>
The general scheme of performing coroutine calls is thus as following:
</p><pre class="gmlmd">
<a class="sf" href="#lua_add_code">lua_add_code</a><span class="op">(</span><span class="uv">state</span><span class="op">,</span> <span class="st">"
function test(num)
for i = 1, num do
coroutine.yield(i)
end
return 'rad!'
end
"</span><span class="op">)</span><span class="op">;</span>
<span class="uv">th</span> <span class="op">=</span> <a class="sf" href="#lua_thread_create">lua_thread_create</a><span class="op">(</span><span class="uv">state</span><span class="op">)</span><span class="op">;</span>
<span class="kw">if</span> <span class="op">(</span><a class="sf" href="#lua_call_start">lua_call_start</a><span class="op">(</span><span class="uv">th</span><span class="op">,</span> <span class="st">"test"</span><span class="op">,</span> <span class="nu">4</span><span class="op">)</span><span class="op">)</span> <span class="cb">{</span>
<span class="kw">while</span> <span class="op">(</span><span class="sf">lua_call_next</span><span class="op">(</span><span class="uv">th</span><span class="op">)</span><span class="op">)</span> <span class="cb">{</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="st">"yield: "</span> <span class="op">+</span> <span class="sf">string</span><span class="op">(</span><a class="sv" href="#lua_call_result">lua_call_result</a><span class="op">)</span><span class="op">)</span><span class="op">;</span>
<span class="cb">}</span>
<span class="sf">show_debug_message</span><span class="op">(</span><span class="st">"result: "</span> <span class="op">+</span> <span class="sf">string</span><span class="op">(</span><a class="sv" href="#lua_call_result">lua_call_result</a><span class="op">)</span><span class="op">)</span><span class="op">;</span>
<span class="cb">}</span>
<a class="sf" href="#lua_thread_destroy">lua_thread_destroy</a><span class="op">(</span><span class="uv">th</span><span class="op">)</span><span class="op">;</span>
</pre></div></div>
<div class="item"><a class="header" id="lua_call_result" href="#lua_call_result" title="(permalink)">lua_call_result</a><div class="content"><p>
Holds the result of the last <tt>lua_call_next</tt> - yielded value when the execution continues, and final returned value when the execution stops.
</p></div></div>
</p></div></div>
<div class="item"><a class="header" id="FAQ" href="#FAQ" title="(permalink)">FAQ</a><div class="content"><p>
<div class="item"><a class="header" id="lua-version" href="#lua-version" title="(permalink)">What Lua version is used?</a><div class="content"><p>
Apollo uses Lua 5.3.4 as of writing this.
</p><p>
In case you'd like a custom version (such as to use LuaJIT, or to use a Lua fork with
different syntax), C++ source code is included - it will work with any Lua build
down to 5.1.
</p></div></div>
<div class="item"><a class="header" id="What-platforms-does-it-run-on" href="#What-platforms-does-it-run-on" title="(permalink)">What platforms does it run on?</a><div class="content"><p>
The extension runs on Windows, Mac, and Linux - linked dynamically in all cases.
</p><p>
Apollo v2 beta currently only comes with a Windows (GMS1,GMS2) binary,
but you can compile it yourself (code is portable).
</p><p>
Mac may require additional tinkering (via <tt>install_name_tool</tt> - <a href="https://github.com/YellowAfterlife/steamworks.gml/blob/master/build_osx.sh#L7">example</a>), as library inclusion paths may vary depending on whether the game is running from IDE, whether YYC is enabled, and GMS version. If you are familiar with Mac extension development yourself, feel free to get in touch about better ways of handling this.
</p></div></div>
</p></div></div>
<div class="item"><a class="header" id="Limitations" href="#Limitations" title="(permalink)">Limitations</a><div class="content"><p>
<div class="item"><a class="header" id="Lua-tables-cannot-be-transmitted-to-GML-automatically" href="#Lua-tables-cannot-be-transmitted-to-GML-automatically" title="(permalink)">Lua tables cannot be transmitted to GML automatically</a><div class="content"><p>
While these are roughly equivalent to GM's <tt>ds_map</tt>s, the two work very differently -
<tt>ds_map</tt>s are passed by-index and managed manually (ds_map_destroy),
Lua' tables are passed by-reference and managed by garbage collector.
</p><p>
While future iterations on GML should make it possible to automatically convert between tables and lightweight data structures, this would only allow to return a new table/structure rather than modifying an existing one.
</p><p>
The issue can be approached in several ways:
</p><ul>
<li> Expose <tt>ds_map</tt>s to Lua code and use them instead of tables for transfer.
</li><li> Have a wrapper function on Lua side to expand the table into multiple values prior to calling the GML script and/or wrap multiple returned values from GML back into a table.
</li><li> If you do not need to read data from the table (but store/retrieve it), you can convert it to index and back on Lua side via lookup tables (see below).
</li></ul></div></div>
<div class="item"><a class="header" id="Lua-specific-reference-types-cannot-be-transmitted-to-GML-automatically" href="#Lua-specific-reference-types-cannot-be-transmitted-to-GML-automatically" title="(permalink)">Lua-specific reference types cannot be transmitted to GML automatically</a><div class="content"><p>
Lua supports several additional reference types (such as Lua function references),
but these cannot be safely sent to GML as pointers as they are garbage-collected,
and thus may get recycled while still referenced on the GML side of things
(resulting in hard crash when trying to use the passed back value).
</p><p>
A good way to deal with this is to make a pair of lookup tables - since Lua allows table indexes to be of any type, you can do something like the following:
</p> <pre class="lua">ref <span class="op">=</span> <span class="op">{</span>
__r2i <span class="op">=</span> <span class="op">{</span> <span class="op">},</span>
__i2r <span class="op">=</span> <span class="op">{</span> <span class="op">},</span>
__next <span class="op">=</span> <span class="nu0">0</span>
<span class="op">}</span>
<span class="kw1">function</span> ref.toid<span class="op">(</span>fn<span class="op">)</span>
<span class="kw1">local</span> id <span class="op">=</span> ref.__r2i<span class="op">[</span>fn<span class="op">]</span>
<span class="kw1">if</span> <span class="op">(</span>id <span class="op">==</span> <span class="kw1">nil</span><span class="op">)</span> <span class="kw1">then</span>
id <span class="op">=</span> ref.__next
ref.__next <span class="op">=</span> id <span class="op">+</span> <span class="nu0">1</span>
ref.__r2i<span class="op">[</span>fn<span class="op">]</span> <span class="op">=</span> id
ref.__i2r<span class="op">[</span>id<span class="op">]</span> <span class="op">=</span> fn
<span class="kw1">end</span>
<span class="kw1">return</span> id
<span class="kw1">end</span>
<span class="kw1">function</span> ref.fromid<span class="op">(</span>id<span class="op">)</span>
<span class="kw1">return</span> ref.__i2r<span class="op">[</span>id<span class="op">]</span>
<span class="kw1">end</span>
<span class="kw1">function</span> ref.free<span class="op">(</span>fn<span class="op">)</span>
<span class="kw1">local</span> id
<span class="kw1">if</span> <span class="op">(</span><span class="kw1">type</span><span class="op">(</span>fn<span class="op">)</span> <span class="op">==</span> <span class="st1">"number"</span><span class="op">)</span> <span class="kw1">then</span>
id <span class="op">=</span> fn
fn <span class="op">=</span> ref.__i2r<span class="op">[</span>id<span class="op">]</span>
<span class="kw1">else</span>
id <span class="op">=</span> ref.__r2i<span class="op">[</span>fn<span class="op">]</span>
<span class="kw1">end</span>
ref.__r2i<span class="op">[</span>fn<span class="op">]</span> <span class="op">=</span> <span class="kw1">nil</span>
ref.__i2r<span class="op">[</span>id<span class="op">]</span> <span class="op">=</span> <span class="kw1">nil</span>
<span class="kw1">end</span></pre><p>Which allow you to use <tt>ref.toid(some_reference)</tt> to return/create a numeric ID for a reference, <tt>ref.fromid(index)</tt> to convert one of those back to a reference, and <tt>ref.free(index_or_reference)</tt> to remove the lookup pairs (allowing Lua to safely recycle the reference when it is no longer used).
</p></div></div>
</p></div></div><!--doc>-->
</div></div></div>
<script>(function() {
var doc, headers;
//
var path = "Apollo cheat sheet";
var state = null;
if (window.localStorage && JSON.parse) {
state = window.localStorage.getItem(path);
state = state ? JSON.parse(state) : { };
if (state == null) state = { };
}
var isLocal = (location.host.indexOf("localhost") == 0);
//
function h3bind(h3) {
var node = h3.parentNode;
var snip = node.children[1];
var id = h3.id || h3.textContent;
h3.snip = snip;
h3.doc_set = function(z) {
if (z) node.classList.add("open"); else node.classList.remove("open");
if (state) {
state[id] = z;
window.localStorage.setItem(path, JSON.stringify(state));
}
}
h3.doc_hide = function() {
this.doc_set(false);
}
h3.doc_show = function() {
this.doc_set(true);
}
h3.onclick = function(_) {
var seen = !node.classList.contains("open");
h3.doc_set(seen);
return false;
};
}
function getHashFunc(id) {
var node = document.getElementById(id);
if (node == null) return null;
return function(e) {
while (node && node != doc) {
if (node.classList.contains("item")) {
node.classList.add("open");
}
node = node.parentElement;
}
};
}
// Display helpers:
window.opt_none = function() {
for (var li = 0; li < headers.length; li++) headers[li].doc_hide();
};
window.opt_list = function() {
for (var li = 0; li < headers.length; li++) {
var h3 = headers[li];
if (h3.parentNode.parentNode != doc) {
h3.doc_hide();
} else h3.doc_show();
}
};
window.opt_all = function() {
for (var li = 0; li < headers.length; li++) headers[li].doc_show();
};
window.live_post = function() {
doc = document.getElementById("doc");
headers = doc.getElementsByClassName("header");
//
for (var i = 0; i < headers.length; i++) h3bind(headers[i]);
// Clicks in document expand the related section:
var anchors = doc.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
var anchor = anchors[i];
if (anchor.classList.contains("header")) continue;
var href = anchor.getAttribute("href");
if (href[0] == "#") {
var fn = getHashFunc(href.substr(1));
if (!fn) {
anchor.classList.add("broken");
anchor.title = "(section missing)";
} else anchor.addEventListener("click", fn);
}
}
//
for (var li = 0; li < headers.length; li++) {
var h3 = headers[li];
var val = state ? state[h3.id || h3.textContent] : null;
if (val == null) val = isLocal || h3.parentNode.parentNode == doc;
if (val) h3.doc_show(); else h3.doc_hide();
}
};
window.live_post();
//
(function() {
var hash = document.location.hash;
if (hash) {
var _hash = hash.substr(1);
getHashFunc(_hash)();
setTimeout(function() {
document.location.hash = hash + " ";
setTimeout(function() {
document.location.hash = hash;
}, 100);
}, 100);
}
})();
//
doc.setAttribute("ready", "");
})();
</script>
</body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -99,9 +99,7 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
inputs[| 30] = nodeValue("Distribution map", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, noone)
.rejectArray()
inputs[| 31] = nodeValue("Atlas", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, [] )
.setArrayDepth(1)
.rejectArray();
inputs[| 31] = nodeValue("Atlas", self, JUNCTION_CONNECT.input, VALUE_TYPE.surface, [] );
inputs[| 32] = nodeValue("Seed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, irandom_range(100000, 999999))
.rejectArray();

View file

@ -6,6 +6,9 @@ function buffer_get_color(buffer, _x, _y, w, h) { #region
} #endregion
function buffer_get_string(buffer, text = true, limit = 400) { #region
if(is_array(buffer)) return "[buffer array]";
if(!buffer_exists(buffer)) return "";
buffer_seek(buffer, buffer_seek_start, 0);
var len = min(limit, buffer_get_size(buffer));
var ss = "";

View file

@ -5,20 +5,28 @@ function __vec3Sub(_x = 0, _y = _x, _z = _x) : __vec3(_x, _y, _z) constructor {
static smooth = function() {
if(array_empty(connected)) return;
var _k = array_length(connected) / 2;
var _n = array_length(connected);
var _k = _n / 2;
var beta = 3 / (5 * _k);
var _s = self.multiply(1 - _k * beta);
var _c = new __vec3();
var _sx = x * (1 - _k * beta);
var _sy = y * (1 - _k * beta);
var _sz = z * (1 - _k * beta);
var _cx = 0;
var _cy = 0;
var _cz = 0;
for( var i = 0; i < array_length(connected); i++ )
_c._add(connected[i]);
_c._multiply(0.5 * beta)._add(_s);
set(_c);
for( var i = 0; i < _n; i++ ) {
_cx += connected[i].x;
_cy += connected[i].y;
_cz += connected[i].z;
}
x = _cx * 0.5 * beta + _sx;
y = _cy * 0.5 * beta + _sy;
z = _cz * 0.5 * beta + _sz;
}
static connectTo = function(point) { array_push(connected, point); }
static clearConnect = function() { connected = []; }
}
function __3dICOSphere(radius = 0.5, level = 2, smt = false) : __3dObject() constructor {
@ -32,10 +40,10 @@ function __3dICOSphere(radius = 0.5, level = 2, smt = false) : __3dObject() cons
_vhash = ds_map_create();
static getVertex = function(vertex) {
var _hash = string(vertex);
if(ds_map_exists(_vhash, _hash))
return _vhash[? _hash];
_vhash[? _hash] = vertex;
var h = $"[{vertex.x},{vertex.y},{vertex.z}]";
if(ds_map_exists(_vhash, h))
return _vhash[? h];
_vhash[? h] = vertex;
return vertex;
}
@ -63,7 +71,7 @@ function __3dICOSphere(radius = 0.5, level = 2, smt = false) : __3dObject() cons
new __vec3Sub(-b, -a, 0)._normalize()._multiply(radius),
]
array_foreach(icoverts, function(vert) { vert.old = true; })
array_foreach(icoverts, function(vert) { vert.old = true; });
// Generate icosphere vertices
ds_list_add(_vertices, icoverts[ 3], icoverts[ 1], icoverts[ 2]);
@ -87,7 +95,8 @@ function __3dICOSphere(radius = 0.5, level = 2, smt = false) : __3dObject() cons
ds_list_add(_vertices, icoverts[ 6], icoverts[ 5], icoverts[12]);
ds_list_add(_vertices, icoverts[11], icoverts[ 5], icoverts[ 9]);
for( var w = 1; w <= level; w++ ) { #region subdivide
var lv = min(level, 5);
repeat(lv) { #region subdivide
ds_map_clear(_vhash);
var newVertices = ds_list_create();
@ -115,7 +124,7 @@ function __3dICOSphere(radius = 0.5, level = 2, smt = false) : __3dObject() cons
if(_v.old) _v.smooth();
_v.old = true;
_v.clearConnect();
_v.connected = [];
}
ds_list_destroy(_vertices);

View file

@ -28,6 +28,7 @@ function Node_3D_Mesh_Extrude(_x, _y, _group = noone) : Node_3D_Mesh(_x, _y, _gr
var _smt = _data[in_mesh + 2];
var _updt = _data[in_mesh + 3];
var _surf = _mat.surface;
if(!is_surface(_surf)) return noone;
temp_surface[0] = surface_cvt_8unorm(temp_surface[0], _surf);
temp_surface[1] = surface_cvt_8unorm(temp_surface[1], _hght);

View file

@ -22,12 +22,11 @@ function Node_Iterate_Inline(_x, _y, _group = noone) : Node_Collection_Inline(_x
static getIterationCount = function() { return getInputData(0); }
static bypassConnection = function() { #region
return iterated > 0 && !is_undefined(value_buffer);
static bypassConnection = function() { return iterated > 0 && !is_undefined(value_buffer);
} #endregion
static bypassNextNode = function() { #region
return iterated < getIterationCount();
return iterated < getIterationCount() - 1;
} #endregion
static getNextNodes = function() { #region

View file

@ -263,7 +263,7 @@ function Node_Line(_x, _y, _group = noone) : Node_Processor(_x, _y, _group) cons
// print($"===== {_prog_curr} / {_segLength} : {_segIndex} - {_pathLength} =====");
while(_total > 0) {
while(true) {
wght = 1;
_segIndexPrev = _segIndex;
@ -277,7 +277,7 @@ function Node_Line(_x, _y, _group = noone) : Node_Processor(_x, _y, _group) cons
if(_prog_next == segmentLength) _segIndex++;
var _pp = _clamp? clamp(_pathPng, 0, _pathLength) : _pathPng;
//print($"_pp = {_pp}");
// print($"_pp = {_pp}, total = {_total}");
p = _pat.getPointDistance(_pp, i, p);
@ -326,7 +326,9 @@ function Node_Line(_x, _y, _group = noone) : Node_Processor(_x, _y, _group) cons
pointAmo++;
}
if(_prog_next == _prog_curr && _segIndexPrev == _segIndex) break;
if(_total <= 0) break;
if(_prog_next == _prog_curr && _segIndexPrev == _segIndex) { print("Terminate line not moving"); break; }
else if(_prog_next > _prog_curr) {
_prog_total += _prog_next - _prog_curr;
_total -= _prog_next - _prog_curr;
@ -340,7 +342,7 @@ function Node_Line(_x, _y, _group = noone) : Node_Processor(_x, _y, _group) cons
if(_total_prev == _total && _segIndexPrev == _segIndex && ++_freeze > 16) { print("Terminate line not moving"); break; }
_total_prev = _total;
if(_segIndex >= _segLengthAmo) break;
if(_segIndex >= _segLengthAmo) { print("Terminate line finish last segment"); break; }
}
array_resize(points, pointAmo);

View file

@ -109,12 +109,8 @@ function Node_Path_Bridge(_x, _y, _group = noone) : Node(_x, _y, _group) constru
var _ind = 0;
var n = array_length(_la);
var _d = _dist;
for(; _ind < n; _ind++ ) {
if(_d < _la[_ind]) break;
_d -= _la[_ind];
}
for(; _ind < n; _ind++ ) if(_dist < _la[_ind]) break;
if(_ind >= n) {
var _p = _a[_ind];
@ -125,7 +121,8 @@ function Node_Path_Bridge(_x, _y, _group = noone) : Node(_x, _y, _group) constru
return out;
}
var _rat = _d / _la[_ind];
var _d = _ind == 0? _dist : _dist - _la[_ind - 1];
var _rat = _d / (_la[_ind] - (_ind == 0? 0 : _la[_ind - 1]));
var p0 = _a[_ind];
var p1 = _a[_ind + 1];
@ -164,6 +161,8 @@ function Node_Path_Bridge(_x, _y, _group = noone) : Node(_x, _y, _group) constru
var _rat;
anchors = array_create(_amo);
lengths = array_create(_amo);
lengthAccs = array_create(_amo);
for( var i = 0; i < _amo; i++ ) {
var _a = array_create(_lines);
@ -231,8 +230,8 @@ function Node_Path_Bridge(_x, _y, _group = noone) : Node(_x, _y, _group) constru
oy = ny;
}
array_push(_la, _ll);
_l += _ll;
array_push(_la, _l);
}
lengths[i] = _l;