Pixel-Composer/scripts/__strandSim/__strandSim.gml

474 lines
10 KiB
Plaintext

function StrandPoint(x = 0, y = 0) constructor {
self.x = x;
self.y = y;
px = x;
py = y;
ppx = x;
ppy = y;
dx = 0;
dy = 0;
ikx = noone;
iky = noone;
air_resist = 0.5;
static set = function(x ,y) { self.x = x; self.y = y; }
static motionDelta = function() {
dx = x - px;
dy = y - py;
px = x;
py = y;
}
static motionPropagate = function(timeStep = 1) {
x += dx / timeStep;
y += dy / timeStep;
}
static clone = function() { return new StrandPoint(x, y); }
static serialize = function(s) {
x = s.x;
y = s.y;
px = s.px;
py = s.py;
ppx = s.ppx;
ppy = s.ppy;
dx = s.dx;
dy = s.dy;
ikx = s.ikx;
iky = s.iky;
air_resist = s.air_resist;
return self;
}
}
function Strand(sx = 0, sy = 0, amount = 5, _length = 8, _direct = 0, curlFreq = 4, curlSize = 8) constructor {
id = irandom_range(10000, 99999);
points = [];
length = array_create(amount, _length);
direct = _direct;
curl_freq = curlFreq;
curl_size = curlSize;
tension = 0.8;
spring = 0.1;
angularTension = 0.1;
rootStrength = -1;
rootForce = 0;
free = false;
var _sx = sx;
var _sy = sy;
for( var i = 0; i < amount; i++ ) {
points[i] = new StrandPoint(sx, sy);
sx += lengthdir_x(self.length[i], direct);
sy += lengthdir_y(self.length[i], direct);
}
setOrigin(_sx, _sy);
restAngle = array_create(array_length(points), 0);
restAngle[0] = direct;
static motionDelta = function() {
rootForce = 0;
for( var i = !free; i < array_length(points); i++ )
points[i].motionDelta();
}
static motionPropagate = function(timeStep) {
for( var i = !free; i < array_length(points); i++ )
points[i].motionPropagate(timeStep);
}
static chainConstrain = function() {
for( var i = 1; i < array_length(points); i++ ) {
var p0 = points[i - 1];
var p1 = points[i - 0];
var dir = point_direction(p0.x, p0.y, p1.x, p1.y);
var dis = point_distance(p0.x, p0.y, p1.x, p1.y);
if(dis < 1) continue;
var len = lerp(dis, length[i], tension);
if(free) {
var dx = lengthdir_x(dis - len, dir) / 2;
var dy = lengthdir_y(dis - len, dir) / 2;
p0.x += dx;
p0.y += dy;
p1.x -= dx;
p1.y -= dy;
} else {
if(i == 1) rootForce += len;
p1.x = p0.x + lengthdir_x(len, dir);
p1.y = p0.y + lengthdir_y(len, dir);
}
}
var oa = restAngle[0], na;
for( var i = 1; i < array_length(points); i++ ) {
var p0 = points[i - 1];
var p1 = points[i - 0];
var pdir = point_direction(p0.px, p0.py, p1.px, p1.py);
var dir = point_direction(p0.x, p0.y, p1.x, p1.y);
var dis = point_distance(p0.x, p0.y, p1.x, p1.y);
var dst = oa + restAngle[i];
var adf = angle_difference(dst, dir);
if(dis < 1) continue;
var delt = adf * power(angularTension, 2) * power(1 - i / array_length(points), 2);
na = dir + delt;
var adlt = angle_difference(pdir, na);
var delt = adlt * power(1 - spring, 2);
na += delt;
var tx = p0.x + lengthdir_x(dis, na);
var ty = p0.y + lengthdir_y(dis, na);
p1.x = tx;
p1.y = ty;
oa = na;
}
}
static springConstrain = function() {
var spng = array_length(points) / curl_freq;
if(spng <= 0) return;
for( var i = spng; i < array_length(points); i++ ) {
var p0 = points[i - spng];
var p1 = points[i];
var dir = point_direction(p0.x, p0.y, p1.x, p1.y);
var dis = point_distance(p0.x, p0.y, p1.x, p1.y);
if(dis < 1) continue;
var len = lerp(dis, length[i] * curl_size * spng, spring);
p1.x = p0.x + lengthdir_x(len, dir);
p1.y = p0.y + lengthdir_y(len, dir);
}
}
static setOrigin = function(x, y) {
if(array_length(points) < 1) return;
if(free) return;
points[0].set(x, y);
}
static step = function(timeStep = 1, iteration = 4, detach = true) {
motionDelta();
repeat(timeStep) {
motionPropagate(timeStep);
repeat(iteration) {
chainConstrain();
springConstrain();
}
}
if(detach && rootStrength > -1 && rootForce > rootStrength)
free = true;
}
static freeze = function(fix = false) {
var a = restAngle[0];
for( var i = 1; i < array_length(points); i++ ) {
var p0 = points[i - 1];
var p1 = points[i - 0];
var dir = point_direction(p0.x, p0.y, p1.x, p1.y);
var dis = point_distance(p0.x, p0.y, p1.x, p1.y);
if(!fix) length[i] = dis;
restAngle[i] = angle_difference(dir, a);
a = dir;
}
}
static store = function() {
var op, np;
for( var i = 0, n = array_length(points); i < n; i++ ) {
np = points[i];
if(i) {
np.storeAngle = point_direction(op.x, op.y, np.x, np.y);
np.storeDistance = point_distance(op.x, op.y, np.x, np.y);
}
op = np;
}
}
static draw = function(_x, _y, _s, drawAngle = false, baked = false) {
if(drawAngle) {
draw_set_color(c_red);
var aa = 0;
var ox, oy, nx, ny;
for( var i = 0, n = array_length(points); i < n; i++ ) {
aa += restAngle[i];
if(i) {
nx = ox + lengthdir_x(length[i], aa);
ny = oy + lengthdir_y(length[i], aa);
draw_line(_x + ox * _s, _y + oy * _s, _x + nx * _s, _y + ny * _s);
} else {
nx = points[i].x;
ny = points[i].y;
}
ox = nx;
oy = ny;
}
}
//draw_set_color(c_lime);
//var ox, oy, nx, ny;
//for( var i = 0, n = array_length(points); i < n; i++ ) {
// nx = points[i].px;
// ny = points[i].py;
// nx = _x + nx * _s;
// ny = _y + ny * _s;
// if(i) draw_line(ox, oy, nx, ny);
// ox = nx;
// oy = ny;
//}
draw_set_color(baked? c_aqua : c_blue);
var ox, oy, nx, ny;
for( var i = 0, n = array_length(points); i < n; i++ ) {
nx = points[i].x;
ny = points[i].y;
nx = _x + nx * _s;
ny = _y + ny * _s;
if(i) draw_line(ox, oy, nx, ny);
ox = nx;
oy = ny;
}
for( var i = 0, n = array_length(points); i < n; i++ ) {
nx = points[i].x;
ny = points[i].y;
nx = _x + nx * _s;
ny = _y + ny * _s;
draw_circle_prec(nx, ny, 3, false);
}
}
static set = function(sx = points[0].x, sy = points[0].y) {
var ox, oy, aa = 0;
for( var i = 0, n = array_length(points); i < n; i++ ) {
aa += restAngle[i];
if(i) {
points[i].x = ox + lengthdir_x(length[i], aa);
points[i].y = oy + lengthdir_y(length[i], aa);
} else {
points[i].x = sx;
points[i].y = sy;
}
ox = points[i].x;
oy = points[i].y;
}
for( var i = 0, n = array_length(points); i < n; i++ ) {
points[i].px = points[i].x;
points[i].py = points[i].y;
}
}
static FABRIK = function(iter = 4) {
var op, np;
var amo = array_length(points);
var sx = points[0].x;
var sy = points[0].y;
var changed = false;
for( var i = 0; i < amo; i++ ) {
var p = points[i];
if(p.ikx == noone) continue;
if(p.iky == noone) continue;
if(p.x != p.ikx && p.y != p.iky)
changed = true;
p.x = p.ikx;
p.y = p.iky;
}
repeat(iter) {
for( var i = 0; i < amo; i++ ) {
np = points[amo - 1 - i];
if(i) {
var dir = point_direction(op.x, op.y, np.x, np.y);
var dis = length[amo - 1 - i];
np.x = op.x + lengthdir_x(dis, dir);
np.y = op.y + lengthdir_y(dis, dir);
}
op = np;
}
for( var i = 0; i < amo; i++ ) {
np = points[i];
if(i) {
var dir = point_direction(op.x, op.y, np.x, np.y);
var dis = length[i];
np.x = op.x + lengthdir_x(dis, dir);
np.y = op.y + lengthdir_y(dis, dir);
} else {
np.x = sx;
np.y = sy;
}
op = np;
}
}
for( var i = 0; i < amo; i++ ) {
p.ikx = noone;
p.iky = noone;
}
}
static clone = function() {
set();
var s = new Strand(points[0].x, points[0].y, array_length(points), length[0], direct, curl_freq, curl_size);
for( var i = 0, n = array_length(points); i < n; i++ )
s.points[i] = points[i].clone();
s.restAngle = array_clone(restAngle);
s.length = array_clone(length);
return s;
}
static serialize = function() { return { points, restAngle, length }; }
static deserialize = function(s) {
restAngle = s.restAngle;
length = s.length;
var _p = s.points;
points = array_create(array_length(_p));
for (var i = 0, n = array_length(_p); i < n; i++)
points[i] = new StrandPoint().serialize(_p[i]);
return self;
}
}
function StrandMesh() constructor {
hairs = [];
loop = false;
mesh = noone;
static step = function(iteration = 4) {
__iteration = iteration;
array_foreach(hairs, function(h) /*=>*/ { h.step(__iteration); });
}
static draw = function(_x, _y, _s, drawAngle = false, baked = false) {
__x = _x;
__y = _y;
__s = _s;
__d = drawAngle;
__b = baked;
array_foreach(hairs, function(h) /*=>*/ { h.draw(__x, __y, __s, __d, __b); });
}
static store = function() { array_foreach(hairs, function(h) /*=>*/ { h.store(); }); }
static freeze = function(fixLength = false) {
__fixLength = fixLength;
array_foreach(hairs, function(h) /*=>*/ { h.freeze(fixLength); });
}
static getPointRatio = function(rat, ind = 0) {
if(array_length(hairs) == 0) return new __vec2();
var h = array_safe_get_fast(hairs, ind);
var sg = rat * (array_length(h.points) - 1);
var fr = frac(sg);
var p0 = array_safe_get_fast(h.points, floor(sg));
var p1 = array_safe_get_fast(h.points, floor(sg) + 1);
return new __vec2(lerp(p0.x, p1.x, fr), lerp(p0.y, p1.y, fr));
}
static getLineCount = function() { return array_length(hairs); }
static set = function() { array_foreach(hairs, function(h) /*=>*/ { h.set(); }); return self; }
static clone = function() {
var s = new StrandMesh();
s.loop = loop;
s.mesh = mesh;
for( var i = 0, n = array_length(hairs); i < n; i++ )
s.hairs[i] = hairs[i].clone();
return s;
}
static serialize = function() {
var _h = [];
for( var i = 0, n = array_length(hairs); i < n; i++ )
_h[i] = hairs[i].serialize();
return json_stringify(_h);
}
static deserialize = function(s) {
var j = json_parse(s);
for( var i = 0, n = array_length(j); i < n; i++ )
hairs[i] = new Strand().deserialize(j[i]);
return self;
}
}