Module sverchok.utils.pulga_physics_core
Expand source code
# This file is part of project Sverchok. It's copyrighted by the contributors
# recorded in the version control history of the file, available from
# its original location https://github.com/nortikin/sverchok/commit/master
#
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE
import numpy as np
def cross_indices3(n):
'''create crossed indices'''
nu = np.sum(np.arange(n, dtype=np.int64))
ind = np.zeros((nu, 2), dtype=np.int16)
c = 0
for i in range(n-1):
l = n-i-1
np_i = np.full(n-i-1, i, dtype=np.int32)
np_j = np.arange(i+1, n, dtype=np.int32)
np_a = np.stack((np_i, np_j), axis=-1)
ind[c:c+l, :] = np_a
c += l
return ind
def numpy_match_long_repeat(p):
'''match list length by repeating last one'''
q = []
maxl = 0
for g in p:
maxl = max(maxl, g.shape[0])
for g in p:
difl = maxl - g.shape[0]
if difl > 0:
rr = np.repeat(g[np.newaxis, -1], difl, axis=0)
g = np.concatenate((g, rr))
q.append(g)
return q
def numpy_fit_long_repeat(p, maxl):
'''match list length by repeating last one or removing end'''
q = []
for g in p:
difl = maxl - g.shape[0]
if difl > 0:
rr = np.repeat(g[np.newaxis, -1], difl, axis=0)
g = np.concatenate((g, rr))
if difl < 0:
g = g[:maxl]
q.append(g)
return q
def match_cylce(p, n):
'''append cylcing till have n items'''
difference = n - len(p)
for i in range(difference):
p.append(p[i % len(p)])
def expand_pols_numpy(p, len_max):
'''to fit variable sided polygons in one array (cycling)'''
np_match_cycle = np.vectorize(match_cylce)
np_match_cycle(p, len_max)
new_pols = np.zeros((len(p), len_max), dtype=np.int32)
for i in range(len(p)):
new_pols[i, :] = p[i]
return new_pols
def self_react(params):
'''behaviors between particles: collide, attract and fit'''
ps, collision, sum_rad, gates, att_params, fit_params = params
use_collide, use_attract, use_grow = gates
indexes = ps.params['indexes']
if use_grow:
sum_rad = ps.rads[indexes[:, 0]] + ps.rads[indexes[:, 1]]
if use_attract:
att_params[2] = ps.mass[indexes[:, 0]] * ps.mass[indexes[:, 1]]
dif_v = ps.verts[indexes[:, 0], :] - ps.verts[indexes[:, 1], :]
dist = np.linalg.norm(dif_v, axis=1)
mask = sum_rad > dist
index_inter = indexes[mask]
some_collisions = use_collide and len(index_inter) > 0
some_attractions = use_attract and(len(index_inter) < len(indexes))
if some_collisions or some_attractions:
result = np.zeros((ps.v_len, ps.v_len, 3), dtype=np.float64)
dist_cor = np.clip(dist, 1e-6, 1e4)
normal_v = dif_v/dist_cor[:, np.newaxis]
if some_collisions:
self_collision_force(result, dist, sum_rad, index_inter, mask, normal_v, collision)
if some_attractions:
antimask = np.invert(mask)
attract_force(result, dist_cor, antimask, indexes, normal_v, att_params)
ps.r += np.sum(result, axis=1)
if use_grow:
fit_force(ps, index_inter, fit_params)
ps.mass = ps.density * np.power(ps.rads, 3)
def self_collision_force(result, dist, sum_rad, index_inter, mask, normal_v, self_collision):
'''apply collision forces between particles'''
id0 = index_inter[:, 0]
id1 = index_inter[:, 1]
le = dist[mask, np.newaxis] - sum_rad[mask, np.newaxis]
no = normal_v[mask]
variable_coll = len(self_collision) > 1
sf = self_collision[:, np.newaxis]
len0, len1 = [sf[id1], sf[id0]] if variable_coll else [sf, sf]
result[id0, id1, :] = -no * le * len0
result[id1, id0, :] = no * le * len1
def attract_force(result, dist, mask, index, norm_v, att_params):
'''apply attractions between particles'''
attract, att_decay, mass_product = att_params
dist2 = np.power(dist, att_decay)[mask, np.newaxis]
index_non_inter = index[mask]
id0 = index_non_inter[:, 0]
id1 = index_non_inter[:, 1]
direction = norm_v[mask] / dist2 * mass_product[mask, np.newaxis]
variable_att = len(attract) > 1
att = attract
len0, len1 = [att[id1], att[id0]] if variable_att else [att, att]
result[id0, id1, :] = - direction * len0
result[id1, id0, :] = direction * len1
def fit_force(ps, index_inter, fit_params):
'''the untouched particles will grow, the ones that collide will shrink'''
grow, min_rad, max_rad = fit_params
touch = np.unique(index_inter)
free = np.setdiff1d(np.arange(ps.v_len, dtype=np.int16), touch)
v_grow = len(grow) > 1
grow_un, grow_tou = [grow[free], grow[touch]] if v_grow else [grow, grow]
ps.rads[free] += grow_un*0.1
ps.rads[touch] -= grow_tou*0.1
ps.rads = np.clip(ps.rads, min_rad, max_rad)
def self_react_setup(ps, dicti, forces_composite):
'''prepare self reacting data'''
local_func, local_gates, local_params = dicti
collision, attract, att_decay, grow, min_rad, max_rad = local_params
use_self_collision, use_attract, use_grow = local_gates
np_collision = np.array(collision)
use_self_collision = use_self_collision and np.any(np_collision > 0)
np_attract = np.array(attract)[:, np.newaxis]
use_attract = use_attract and np.any(np_attract != 0)
np_grow = np.array(grow)
use_grow = use_grow and np.any(np_grow != 0)
use_self_react = (use_attract or use_self_collision or use_grow)
if not use_self_react:
return
ps.params['indexes'] = cross_indices3(ps.v_len)
sum_rad = ps.rads[ps.params['indexes'][:, 0]] + ps.rads[ps.params['indexes'][:, 1]]
att_params = att_setup(use_attract, ps, np_attract, att_decay)
fit_params = fit_setup(use_grow, np_grow, min_rad, max_rad)
gates = [use_self_collision, use_attract, use_grow]
params = [ps, np_collision, sum_rad, gates, att_params, fit_params]
forces_composite[0].append(local_func)
forces_composite[1].append(params)
def att_setup(use_attract, ps, np_attract, attract_decay):
'''Prepare self-attracting data'''
if use_attract:
np_att_decay = np.array(attract_decay)
indexes = ps.params['indexes']
mass_product = ps.mass[indexes[:, 0]] * ps.mass[indexes[:, 1]]
att_params = [np_attract, np_att_decay, mass_product]
else:
att_params = []
return att_params
def fit_setup(use_grow, np_grow, min_rad, max_rad):
'''Prepare fitting data'''
if use_grow:
np_min_rad = np.array(min_rad)
np_max_rad = np.array(max_rad)
fit_params = [np_grow, np_min_rad, np_max_rad]
else:
fit_params = []
return fit_params
def attractors_setup(ps, dicti, forces_composite):
'''prepare attractors system and data'''
local_func, use_attractors, local_params = dicti
if use_attractors:
attractors, att_force, att_clamp, att_decay_power = local_params
np_attrac = np.array(attractors)
np_attrac_f = np.array(att_force)
np_attrac_clamp = np.array(att_clamp)
np_attrac_decay_pow = np.array(att_decay_power)
params = [np_attrac, np_attrac_f, np_attrac_clamp, np_attrac_decay_pow]
forces_composite[0].append(local_func)
forces_composite[1].append([ps] + numpy_match_long_repeat(params))
def attractors_force(params):
'''apply attractors force (as planets)'''
ps, np_attrac, np_attrac_f, np_attrac_clamp, decay = params
v_attract = ps.verts[np.newaxis, :, :] - np_attrac[:, np.newaxis, :]
dist_attract = np.linalg.norm(v_attract, axis=2)
mask = dist_attract > np_attrac_clamp[:, np.newaxis]
mask_true = np.invert(mask)
dist_attract2 = np.power(dist_attract, decay[:, np.newaxis])
dist_attract_cor = np.clip(dist_attract2[mask_true], 1e-2, 1e4)
v_attract *= np_attrac_f[:, np.newaxis, np.newaxis]
v_attract_normal = v_attract[mask_true] / dist_attract_cor[:, np.newaxis]
v_attract[mask_true] = -v_attract_normal
v_attract[mask, :] = 0
r_attract = np.sum(v_attract, axis=0)
ps.r += r_attract
def calc_rest_length(np_verts, np_springs):
'''calculate edges length'''
pairs_springs = np_verts[np_springs, :]
vect_rest = (pairs_springs[:, 0, :] - pairs_springs[:, 1, :])
dist_rest = np.linalg.norm(vect_rest, axis=1)
return dist_rest
def spring_setup(ps, dicti, forces_composite):
'''prepare spring system and data'''
local_func, local_gates, local_params = dicti
use_springs, use_fix_len = local_gates
if use_springs:
springs, fixed_len, spring_k = local_params
spring_k = np.array(spring_k)
if np.any(spring_k != 0):
springs = np.array(springs)
if use_fix_len or fixed_len[0] > 0:
dist_rest = fixed_len
else:
dist_rest = calc_rest_length(ps.verts, springs)
spring_params = [ps, springs, dist_rest, spring_k]
forces_composite[0].append(local_func)
forces_composite[1].append(spring_params)
def spring_force(spring_params):
'''apply spring forces related to edges resistance'''
ps, np_springs, dist_rest, spring_k = spring_params
pairs = ps.verts[np_springs, :]
dif_v = pairs[:, 0, :] - pairs[:, 1, :]
dist = np.linalg.norm(dif_v, axis=1)
dif_l = dist - dist_rest
dist[dist == 0] = 1
normal_v = dif_v / dist[:, np.newaxis]
x = dif_l[:, np.newaxis]
k = spring_k[:, np.newaxis]
result = np.zeros((ps.v_len, 3), dtype=np.float64)
np.add.at(result, np_springs[:, 0], -normal_v * x * k)
np.add.at(result, np_springs[:, 1], normal_v * x * k)
ps.r += result
def inflate_setup(ps, dicti, forces_composite):
'''prepare data to inflate'''
local_func, use_inflate, local_params = dicti
if use_inflate:
pols, inflate = local_params
np_pols = np.array(pols)
p_len = len(pols)
if np_pols.dtype == np.object:
np_len = np.vectorize(len)
pols_sides = np_len(np_pols)
pol_side_max = np.amax(pols_sides)
np_pols = expand_pols_numpy(np_pols, pol_side_max)
p_regular = False
else:
p_regular = True
pols_sides = np.array(p_len)
pol_side_max = len(pols[0])
inflate_params = [ps, np_pols, pol_side_max, pols_sides, inflate, p_regular]
forces_composite[0].append(local_func)
forces_composite[1].append(inflate_params)
def pols_normals(pol_v, mag):
'''get actual polygons normal with controlled magnitude'''
v1 = pol_v[:, 1, :] - pol_v[:, 0, :]
v2 = pol_v[:, 2, :] - pol_v[:, 0, :]
pols_normal = np.cross(v1, v2)
pols_normal_d = np.linalg.norm(pols_normal, axis=1)
return pols_normal / pols_normal_d[:, np.newaxis] * mag
def inflate_force(inflate_params):
'''apply force based on normals'''
ps, np_pols, pol_side_max, pols_sides, inflate, p_regular = inflate_params
pol_v = ps.verts[np_pols, :]
pols_normal = pols_normals(pol_v, inflate)
result = np.zeros((ps.v_len, 3), dtype=np.float64)
if p_regular:
p_area = calc_area(pol_side_max, pol_v, pols_normal)[:, np.newaxis]
for i in range(pol_side_max):
np.add.at(result, np_pols[:, i], pols_normal * p_area)
else:
p_area = calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal)[:, np.newaxis]
for i in range(pol_side_max):
mask = pols_sides > i
np.add.at(result, np_pols[mask, i], pols_normal[mask] * p_area[mask])
ps.r += result
def calc_area(pol_side_max, pol_v, pols_normal):
'''calculate polygons area (equal sided polygons)'''
prod = np.zeros((pol_side_max, len(pols_normal), 3), dtype=np.float32)
for i in range(pol_side_max):
prod[i, :, :] = np.cross(pol_v[:, i, :], pol_v[:, (i + 1) % pol_side_max, :])
tot = np.sum(prod, axis=0)
return abs(np.sum(tot * pols_normal, axis=1) / 2)
def calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal):
'''calculate polygons area (variable sided polygons)'''
prod = np.zeros((pol_side_max, len(pols_sides), 3), dtype=np.float32)
for i in range(pol_side_max):
mask = pols_sides > i
prod[i, mask, :] = np.cross(pol_v[mask, i, :], pol_v[mask, (i + 1) % pol_side_max, :])
tot = np.sum(prod, axis=0)
return abs(np.sum(tot * pols_normal, axis=1) / 2)
def drag_setup(ps, dicti, forces_composite):
'''prepare drag data'''
local_func, local_gates, local_params = dicti
use_drag, use_grow = local_gates
if use_drag:
drag_force = local_params
np_drag_force = np.array(drag_force)[:, np.newaxis]
use_drag = np.any(np_drag_force > 0)
if use_drag:
surf = np.power(ps.rads, 2)
drag_params = [ps, use_grow, np_drag_force, surf]
forces_composite[0].append(local_func)
forces_composite[1].append(drag_params)
def drag_force_apply(params):
'''apply drag force (resistance from environment)'''
ps, use_grow, drag_mag, surf = params
vel_mag = np.linalg.norm(ps.vel, axis=1)
vel_mag_zero = vel_mag == 0
vel_mag[vel_mag_zero] = 1
# squaring the speed is more accurate but harder to control
# vel_mag2 = vel_mag * vel_mag
vel_mag2 = vel_mag
vel_norm = ps.vel/vel_mag[:, np.newaxis]
if use_grow:
surf = np.power(ps.rads, 2)
drag = -vel_norm * drag_mag * vel_mag2[:, np.newaxis] * surf[:, np.newaxis]
ps.r += drag
def random_setup(ps, dicti, forces_composite):
'''initialize random force'''
local_func, local_gate, local_params = dicti
if local_gate:
random_seed, random_force, random_variation = local_params
np_random_force = np.array(random_force)
if any(np_random_force != 0):
np_random_variation = np.array(random_variation)
random_force = np_random_force[:, np.newaxis]
random_variation = np_random_variation[:, np.newaxis]
random_variate = any(random_variation != 0)
np.random.seed(random_seed[0])
ps.random_v = random_force * np.random.random((ps.v_len, 3)) - random_force / 2
random_params = [ps, random_force, random_variate, random_variation]
forces_composite[0].append(local_func)
forces_composite[1].append(random_params)
def random_force_apply(params):
'''apply random vectors and change it'''
ps, random_force, random_variate, random_variation = params
if random_variate:
random_var = 2 * random_force * np.random.random((ps.v_len, 3)) - random_force
ps.random_v = ps.random_v * (1 - random_variation) + random_var * random_variation
ps.r += ps.random_v
def baricentric_mask(p_triang, edges, np_pol_v, coll_normals, ed_id, rad_axis):
'''helper function to mask which points are inside the triangles'''
edge = edges[:, ed_id, :]
v0 = np_pol_v[:, ed_id, :]
vp0 = p_triang - v0[:, np.newaxis, :]
cross = np.cross(edge[:, np.newaxis, :], vp0)
return np.sum(coll_normals * cross, axis=2) > -rad_axis
def collisions_setup(ps, dicti, limits_composite):
'''prepare collision data'''
local_func, use_collide, local_params = dicti
if use_collide:
obstacles, obstacles_pols, obstacles_bounce = local_params
np_collide_v = np.array(obstacles)
np_collide_pol = np.array(obstacles_pols, dtype=np.int16)
np_pol_v = np_collide_v[np_collide_pol]
collide_v1 = np_pol_v[:, 1, :] - np_pol_v[:, 0, :]
collide_v2 = np_pol_v[:, 2, :] - np_pol_v[:, 0, :]
edges = np.zeros(np_pol_v.shape, dtype=np.float32)
edges[:, 0, :] = collide_v1
edges[:, 1, :] = np_pol_v[:, 2, :] - np_pol_v[:, 1, :]
edges[:, 2, :] = np_pol_v[:, 0, :] - np_pol_v[:, 2, :]
coll_norm = collide_normals_get(collide_v1, collide_v2)
coll_p_co = np_pol_v[:, 0, :]
bounce = np.array(obstacles_bounce)
collide_params = [ps, coll_p_co, coll_norm, np_pol_v, bounce, edges]
limits_composite[0].append(local_func)
limits_composite[1].append(collide_params)
def collide_normals_get(collide_v1, collide_v2):
'''get normalized normals'''
collide_normals = np.cross(collide_v1, collide_v2)
collide_normals_d = np.linalg.norm(collide_normals, axis=1)
return collide_normals / collide_normals_d[:, np.newaxis]
def b_box_coll_filter(np_pol_v, verts, rad):
'''filter intersections by checking bounding box overlap'''
verts_x = verts[np.newaxis, :, :]
rads_x = rad[np.newaxis, :, np.newaxis]
keep = np.amin(np_pol_v, axis=1)[:, np.newaxis, :] < verts_x + rads_x
keep2 = np.amax(np_pol_v, axis=1)[:, np.newaxis, :] > verts_x - rads_x
keep3 = np.any(keep*keep2, axis=0)
keep = np.any(keep*keep2, axis=1)
verts_m = np.all(keep3, axis=1)
pols_m = np.all(keep, axis=1)
return pols_m, verts_m
def collisions_apply(collide_params):
'''apply collisions with obstacles'''
ps, collide_p_co, collide_normals, pol_v, bounce, edges = collide_params
pols_m, verts_m = b_box_coll_filter(pol_v, ps.verts, ps.rads)
verts = ps.verts[verts_m]
rads = ps.rads[verts_m]
vels = ps.vel[verts_m]
index = ps.index[verts_m]
coll_norm = collide_normals[pols_m, np.newaxis, :]
coll_dist = collision_dist(verts, collide_p_co[pols_m], coll_norm)
coll_inter = np.absolute(coll_dist) - rads[np.newaxis, :]
coll_mask = coll_inter < 0
if not np.any(coll_mask):
return
coll_sign = 2*(coll_dist > 0) - 1
mask_none = np.any(coll_mask, axis=0)
p_triang = verts[mask_none] + coll_dist[:, mask_none, np.newaxis] * coll_norm
rad_axis = rads[np.newaxis, mask_none]
index = index[mask_none]
coll_mask = coll_mask[:, mask_none]
coll_inter = coll_inter[:, mask_none]
coll_sign = coll_sign[:, mask_none]
vels = vels[mask_none]
wuv = pts_in_tris(p_triang, edges[pols_m], pol_v[pols_m], coll_norm, rad_axis, coll_mask)
num_int = np.maximum(np.sum(wuv, axis=0), 1)[:, np.newaxis]
displace = np.zeros((ps.v_len, 3), dtype=np.float32)
velocity = np.zeros((ps.v_len, 3), dtype=np.float32)
displace[index, :] = coll_displace(coll_norm, coll_inter, coll_sign, wuv, num_int)
velocity[index, :] = coll_vel(coll_norm, vels, wuv, num_int)
ps.verts += displace
ps.vel += velocity * bounce
def collision_dist(verts, collide_p_co, coll_norm):
'''get collision distance and normal'''
vector_coll = verts[np.newaxis, :, :] - collide_p_co[:, np.newaxis, :]
coll_dist = np.sum(vector_coll * coll_norm, axis=2)
return coll_dist
def pts_in_tris(p_triang, edges, pol_v, coll_normals, rad_axis, coll_mask):
'''calculate if points are inside the triangles'''
w = baricentric_mask(p_triang, edges, pol_v, coll_normals, 0, rad_axis)
u = baricentric_mask(p_triang, edges, pol_v, coll_normals, 1, rad_axis)
v = baricentric_mask(p_triang, edges, pol_v, coll_normals, 2, rad_axis)
return w * u * v * coll_mask
def coll_displace(coll_normals, coll_inter, coll_sign, wuv, num_int):
'''calculate resultant collision displacement'''
rr = wuv[:, :, np.newaxis] * coll_normals * coll_inter[:, :, np.newaxis]*-coll_sign[:, :, np.newaxis]
return np.sum(rr, axis=0) / num_int
def coll_vel(coll_normals, vels, wuv, num_int):
'''calculate resultant collision speed'''
new_vel_mag = np.sum(coll_normals * vels[np.newaxis, :, :], axis=-1)
new_vel = new_vel_mag[:, :, np.newaxis] * coll_normals * wuv[:, :, np.newaxis]
return np.sum(new_vel, axis=0)*-2 / num_int
def b_box_setup(ps, dicti, limits_composite):
'''prepare b_box data'''
local_func, use_b_box, b_box = dicti
if use_b_box:
np_bbox = np.array(b_box)
bbox_max = np.amax(np_bbox, axis=0)
bbox_min = np.amin(np_bbox, axis=0)
b_box_params = [ps, bbox_min, bbox_max]
limits_composite[0].append(local_func)
limits_composite[1].append(b_box_params)
def b_box_apply(b_box_params):
'''apply hard limit to verts'''
ps, bbox_min, bbox_max = b_box_params
ps.verts = np.clip(ps.verts, bbox_min + ps.rads[:, np.newaxis], bbox_max - ps.rads[:, np.newaxis])
def world_forces_setup(ps, dicti, forces_composite):
'''prepare world forces data'''
local_func, gates, params = dicti
use_world_f, size_change = gates
world_forces, wind = params
if use_world_f:
np_world_f = np.array(world_forces)
np_wind = np.array(wind)
if len(world_forces) > 1:
np_world_f = numpy_fit_long_repeat([np_world_f], ps.v_len)[0]
if not size_change:
np_world_f = np_world_f * ps.mass[:, np.newaxis]
func = local_func[0]
else:
func = local_func[1]
if len(np_wind)> 1:
np_wind = numpy_fit_long_repeat([np_wind], ps.v_len)[0]
ps.params['gravity'] = np_world_f
ps.params['wind'] = np_wind
forces_composite[0].append(func)
forces_composite[1].append(ps)
def world_forces_apply(ps):
'''apply constant forces'''
ps.r += ps.params['gravity'] + ps.params['wind']
def world_forces_apply_var(ps):
'''apply constant forces'''
ps.r += ps.params['gravity'] * ps.mass[:, np.newaxis] + ps.params['wind']
def pins_setup(ps, dicti, forces_composite):
'''prepare pins data'''
ps.pins_init(dicti, forces_composite)
def pins_apply(ps):
'''apply pin mask'''
ps.apply_pins()
def apply_forces_setup(ps, dicti, forces_composite):
'''prepare applying all forces process'''
local_func, _, _ = dicti
forces_composite[0].append(local_func)
forces_composite[1].append(ps)
def apply_all_forces(ps):
'''applying forces and resetting resultant'''
ps.apply_forces()
def limit_speed(np_vel, max_vel):
''''constrain speed magniture'''
vel_mag = np.linalg.norm(np_vel, axis=1)
vel_exceded = vel_mag > max_vel
np_vel[vel_exceded] = np_vel[vel_exceded] / vel_mag[vel_exceded, np.newaxis] * max_vel
FORCE_CHAIN = ["self_react", "Springs", "drag", "inflate", "random", "attractors", "world_f", "Pins", "apply_f", "b_box", "Obstacles"]
class PulgaSystem():
'''Store states'''
verts, rads, vel, density = [[], [], [], []]
v_len = []
params = {}
def __init__(self, init_params):
self.main_setup(init_params)
self.mass = self.density * np.power(self.rads, 3)
self.random_v = []
self.r = np.zeros((self.v_len, 3), dtype=np.float64)
self.index = np.arange(self.v_len)
def main_setup(self, local_params):
'''prepare main data'''
p = self.params
initial_pos, rads_in, initial_vel, max_vel, density = local_params
self.verts = np.array(initial_pos)
self.rads = np.array(rads_in, dtype=np.float64)
self.vel = np.array(initial_vel, dtype=np.float64)
self.v_len = len(self.verts)
p['max_vel'] = np.array(max_vel)
self.rads, self.vel = numpy_fit_long_repeat([self.rads, self.vel], self.v_len)
if len(p['max_vel']) > 1:
p['max_vel'] = numpy_fit_long_repeat([p['max_vel']], self.v_len)[0]
self.density = np.array(density)
if len(density) > 1:
self.density = numpy_fit_long_repeat([self.density], self.v_len)[0]
p["Pins Reactions"] = []
def hard_update(self, cache, size_change, pins_gates):
'''replace verts, rads and vel (in NumPy)'''
verts, rads, vel, react = cache
if len(verts) == self.v_len:
if pins_gates[0] and pins_gates[1]:
unpinned = self.params['unpinned']
self.verts[unpinned] = verts[unpinned]
else:
self.verts = verts
self.vel = vel
if not size_change:
self.rads = rads
def hard_update_list(self, cache, size_change, pins_gates):
'''replace verts, rads and velocity'''
verts, rads, vel, react = cache
if type(verts) == list:
if len(verts) == self.v_len:
if pins_gates[0] and pins_gates[1]:
unpinned = self.params['unpinned']
self.verts[unpinned] = np.array(verts)[unpinned]
else:
self.verts = np.array(verts)
if not size_change:
self.rads = np.array(rads)
self.vel = np.array(vel)
else:
self.hard_update(cache, size_change, pins_gates)
def apply_forces(self):
'''resultant --> acceleration --> speed --> position'''
acc = self.r / self.mass[:, np.newaxis]
self.vel += acc
if np.any(self.params['max_vel']) > 0:
limit_speed(self.vel, self.params['max_vel'])
self.verts += self.vel
self.r[:] = 0
def pins_init(self, dicti, forces_composite):
local_func, local_gates, local_params = dicti
pins, pins_goal_pos = local_params
use_pins, use_pins_goal = local_gates
if not use_pins:
self.params["Pins Reactions"] = np.array([[]])
return
self.params['Pins'] = np.array(pins)
if self.params['Pins'].dtype == np.int32:
if len(self.params['Pins']) == len(self.verts):
self.params['Pins'] = self.params['Pins'] == 1
self.params['unpinned'] = np.invert(self.params['Pins'])
else:
self.params['unpinned'] = np.ones(len(self.verts), dtype=np.bool)
self.params['unpinned'][self.params['Pins']] = False
self.vel[self.params['Pins'], :] = 0
if use_pins_goal:
self.verts[self.params['Pins'], :] = np.array(pins_goal_pos)
forces_composite[0].append(local_func)
forces_composite[1].append(self)
return
def apply_pins(self):
'''cancel forces on pins'''
self.params["Pins Reactions"] = -self.r[self.params["Pins"]]
self.r[self.params["Pins"], :] = 0
FUNC_DICT = {
"self_react": self_react,
"Springs": spring_force,
"drag": drag_force_apply,
"inflate": inflate_force,
"random": random_force_apply,
"attractors": attractors_force,
"world_f": (world_forces_apply, world_forces_apply_var),
"Pins": PulgaSystem.apply_pins,
"apply_f": PulgaSystem.apply_forces,
"b_box": b_box_apply,
"Obstacles": collisions_apply
}
INIT_FUNC_DICT = {
"self_react": self_react_setup,
"Springs": spring_setup,
"drag": drag_setup,
"inflate": inflate_setup,
"random": random_setup,
"attractors": attractors_setup,
"world_f": world_forces_setup,
"Pins": pins_setup,
"apply_f": apply_forces_setup,
"b_box": b_box_setup,
"Obstacles": collisions_setup
}
PARAMS_GROUPS = {
"main": ("Initial_Pos", "rads_in", 'Initial Velocity', "max_vel", "Density"),
"Springs": ("Springs", "fixed_len", "spring_k" ),
"Pins": ("Pins", "Pins Goal Position"),
"self_react": ("self_collision", "self_attract", "attract_decay", "grow", "min_rad", "max_rad"),
"inflate": ("Pols", "inflate"),
"drag": ("drag_force"),
"attractors": ("Attractors", "att_force", "att_clamp", "att_decay_power"),
"random": ("random_seed", "random_force", "random_variation"),
"world_f": ("Gravity", "Wind"),
"b_box": ("Bounding Box"),
"Obstacles": ("Obstacles", "Obstacles_pols", "obstacles_bounce"),
}
def fill_params_dict(p_dict, parameters, par):
'''redistribute parameters'''
for x in PARAMS_GROUPS:
if type(PARAMS_GROUPS[x]) == tuple:
p_dict[x] = [par[p] for p in PARAMS_GROUPS[x] ]
else:
p_dict[x] = par[PARAMS_GROUPS[x]]
p_dict["apply_f"] = True
def local_dict(dictionaries, name):
'''get all related to the name'''
return [dictionaries[0][name], dictionaries[1][name], dictionaries[2][name]]
def pulga_system_init(params, parameters, gates, out_lists, cache):
'''the main function of the engine'''
dictionaries = [FUNC_DICT, gates, {}]
fill_params_dict(dictionaries[2], parameters, params)
force_map = []
force_parameters = []
forces_composite = [force_map, force_parameters]
ps = PulgaSystem(dictionaries[2]["main"])
for force in FORCE_CHAIN:
INIT_FUNC_DICT[force](ps, local_dict(dictionaries, force), forces_composite)
iterations = parameters[1]
iterations_max = max(iterations)
iterations_rec = [i-1 for i in iterations]
out_params = [iterations_rec, ps, dictionaries[1]["output"], out_lists]
if dictionaries[1]["accumulate"]:
if len(cache) > 0:
ps.hard_update_list(cache, gates["self_react"][2], gates["Pins"])
iterate(iterations_max, force_map, force_parameters, out_params)
return ps.verts, ps.rads, ps.vel, ps.params["Pins Reactions"]
def iterate(iterations_max, force_map, force_parameters, out_params):
''' execute repeatedly the defined force map'''
num_forces = len(force_map)
for it in range(iterations_max):
for i in range(num_forces):
force_map[i](force_parameters[i])
output_data(it, out_params)
def output_data(it, params):
'''if is pertinent output the data'''
iterations_rec, ps, gate, out_lists = params
record_iteration = it in iterations_rec
if record_iteration:
data_out = prepare_output_data(ps, gate)
record_data(data_out, out_lists)
def prepare_output_data(ps, gate):
'''prepare data to output'''
use_numpy_out = gate
if use_numpy_out:
return [ps.verts, ps.rads, ps.vel, ps.params["Pins Reactions"]]
else:
return [ps.verts.tolist(), ps.rads.tolist(), ps.vel.tolist(), ps.params["Pins Reactions"].tolist()]
def record_data(data_out, out_lists):
'''save data to main list'''
verts_out, rads_out, velocity_out, reactions_out = out_lists
new_verts, new_rad, new_vel, new_react = data_out
verts_out.append(new_verts)
rads_out.append(new_rad)
velocity_out.append(new_vel)
reactions_out.append(new_react)
Functions
def apply_all_forces(ps)
-
applying forces and resetting resultant
Expand source code
def apply_all_forces(ps): '''applying forces and resetting resultant''' ps.apply_forces()
def apply_forces_setup(ps, dicti, forces_composite)
-
prepare applying all forces process
Expand source code
def apply_forces_setup(ps, dicti, forces_composite): '''prepare applying all forces process''' local_func, _, _ = dicti forces_composite[0].append(local_func) forces_composite[1].append(ps)
def att_setup(use_attract, ps, np_attract, attract_decay)
-
Prepare self-attracting data
Expand source code
def att_setup(use_attract, ps, np_attract, attract_decay): '''Prepare self-attracting data''' if use_attract: np_att_decay = np.array(attract_decay) indexes = ps.params['indexes'] mass_product = ps.mass[indexes[:, 0]] * ps.mass[indexes[:, 1]] att_params = [np_attract, np_att_decay, mass_product] else: att_params = [] return att_params
def attract_force(result, dist, mask, index, norm_v, att_params)
-
apply attractions between particles
Expand source code
def attract_force(result, dist, mask, index, norm_v, att_params): '''apply attractions between particles''' attract, att_decay, mass_product = att_params dist2 = np.power(dist, att_decay)[mask, np.newaxis] index_non_inter = index[mask] id0 = index_non_inter[:, 0] id1 = index_non_inter[:, 1] direction = norm_v[mask] / dist2 * mass_product[mask, np.newaxis] variable_att = len(attract) > 1 att = attract len0, len1 = [att[id1], att[id0]] if variable_att else [att, att] result[id0, id1, :] = - direction * len0 result[id1, id0, :] = direction * len1
def attractors_force(params)
-
apply attractors force (as planets)
Expand source code
def attractors_force(params): '''apply attractors force (as planets)''' ps, np_attrac, np_attrac_f, np_attrac_clamp, decay = params v_attract = ps.verts[np.newaxis, :, :] - np_attrac[:, np.newaxis, :] dist_attract = np.linalg.norm(v_attract, axis=2) mask = dist_attract > np_attrac_clamp[:, np.newaxis] mask_true = np.invert(mask) dist_attract2 = np.power(dist_attract, decay[:, np.newaxis]) dist_attract_cor = np.clip(dist_attract2[mask_true], 1e-2, 1e4) v_attract *= np_attrac_f[:, np.newaxis, np.newaxis] v_attract_normal = v_attract[mask_true] / dist_attract_cor[:, np.newaxis] v_attract[mask_true] = -v_attract_normal v_attract[mask, :] = 0 r_attract = np.sum(v_attract, axis=0) ps.r += r_attract
def attractors_setup(ps, dicti, forces_composite)
-
prepare attractors system and data
Expand source code
def attractors_setup(ps, dicti, forces_composite): '''prepare attractors system and data''' local_func, use_attractors, local_params = dicti if use_attractors: attractors, att_force, att_clamp, att_decay_power = local_params np_attrac = np.array(attractors) np_attrac_f = np.array(att_force) np_attrac_clamp = np.array(att_clamp) np_attrac_decay_pow = np.array(att_decay_power) params = [np_attrac, np_attrac_f, np_attrac_clamp, np_attrac_decay_pow] forces_composite[0].append(local_func) forces_composite[1].append([ps] + numpy_match_long_repeat(params))
def b_box_apply(b_box_params)
-
apply hard limit to verts
Expand source code
def b_box_apply(b_box_params): '''apply hard limit to verts''' ps, bbox_min, bbox_max = b_box_params ps.verts = np.clip(ps.verts, bbox_min + ps.rads[:, np.newaxis], bbox_max - ps.rads[:, np.newaxis])
def b_box_coll_filter(np_pol_v, verts, rad)
-
filter intersections by checking bounding box overlap
Expand source code
def b_box_coll_filter(np_pol_v, verts, rad): '''filter intersections by checking bounding box overlap''' verts_x = verts[np.newaxis, :, :] rads_x = rad[np.newaxis, :, np.newaxis] keep = np.amin(np_pol_v, axis=1)[:, np.newaxis, :] < verts_x + rads_x keep2 = np.amax(np_pol_v, axis=1)[:, np.newaxis, :] > verts_x - rads_x keep3 = np.any(keep*keep2, axis=0) keep = np.any(keep*keep2, axis=1) verts_m = np.all(keep3, axis=1) pols_m = np.all(keep, axis=1) return pols_m, verts_m
def b_box_setup(ps, dicti, limits_composite)
-
prepare b_box data
Expand source code
def b_box_setup(ps, dicti, limits_composite): '''prepare b_box data''' local_func, use_b_box, b_box = dicti if use_b_box: np_bbox = np.array(b_box) bbox_max = np.amax(np_bbox, axis=0) bbox_min = np.amin(np_bbox, axis=0) b_box_params = [ps, bbox_min, bbox_max] limits_composite[0].append(local_func) limits_composite[1].append(b_box_params)
def baricentric_mask(p_triang, edges, np_pol_v, coll_normals, ed_id, rad_axis)
-
helper function to mask which points are inside the triangles
Expand source code
def baricentric_mask(p_triang, edges, np_pol_v, coll_normals, ed_id, rad_axis): '''helper function to mask which points are inside the triangles''' edge = edges[:, ed_id, :] v0 = np_pol_v[:, ed_id, :] vp0 = p_triang - v0[:, np.newaxis, :] cross = np.cross(edge[:, np.newaxis, :], vp0) return np.sum(coll_normals * cross, axis=2) > -rad_axis
def calc_area(pol_side_max, pol_v, pols_normal)
-
calculate polygons area (equal sided polygons)
Expand source code
def calc_area(pol_side_max, pol_v, pols_normal): '''calculate polygons area (equal sided polygons)''' prod = np.zeros((pol_side_max, len(pols_normal), 3), dtype=np.float32) for i in range(pol_side_max): prod[i, :, :] = np.cross(pol_v[:, i, :], pol_v[:, (i + 1) % pol_side_max, :]) tot = np.sum(prod, axis=0) return abs(np.sum(tot * pols_normal, axis=1) / 2)
def calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal)
-
calculate polygons area (variable sided polygons)
Expand source code
def calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal): '''calculate polygons area (variable sided polygons)''' prod = np.zeros((pol_side_max, len(pols_sides), 3), dtype=np.float32) for i in range(pol_side_max): mask = pols_sides > i prod[i, mask, :] = np.cross(pol_v[mask, i, :], pol_v[mask, (i + 1) % pol_side_max, :]) tot = np.sum(prod, axis=0) return abs(np.sum(tot * pols_normal, axis=1) / 2)
def calc_rest_length(np_verts, np_springs)
-
calculate edges length
Expand source code
def calc_rest_length(np_verts, np_springs): '''calculate edges length''' pairs_springs = np_verts[np_springs, :] vect_rest = (pairs_springs[:, 0, :] - pairs_springs[:, 1, :]) dist_rest = np.linalg.norm(vect_rest, axis=1) return dist_rest
def coll_displace(coll_normals, coll_inter, coll_sign, wuv, num_int)
-
calculate resultant collision displacement
Expand source code
def coll_displace(coll_normals, coll_inter, coll_sign, wuv, num_int): '''calculate resultant collision displacement''' rr = wuv[:, :, np.newaxis] * coll_normals * coll_inter[:, :, np.newaxis]*-coll_sign[:, :, np.newaxis] return np.sum(rr, axis=0) / num_int
def coll_vel(coll_normals, vels, wuv, num_int)
-
calculate resultant collision speed
Expand source code
def coll_vel(coll_normals, vels, wuv, num_int): '''calculate resultant collision speed''' new_vel_mag = np.sum(coll_normals * vels[np.newaxis, :, :], axis=-1) new_vel = new_vel_mag[:, :, np.newaxis] * coll_normals * wuv[:, :, np.newaxis] return np.sum(new_vel, axis=0)*-2 / num_int
def collide_normals_get(collide_v1, collide_v2)
-
get normalized normals
Expand source code
def collide_normals_get(collide_v1, collide_v2): '''get normalized normals''' collide_normals = np.cross(collide_v1, collide_v2) collide_normals_d = np.linalg.norm(collide_normals, axis=1) return collide_normals / collide_normals_d[:, np.newaxis]
def collision_dist(verts, collide_p_co, coll_norm)
-
get collision distance and normal
Expand source code
def collision_dist(verts, collide_p_co, coll_norm): '''get collision distance and normal''' vector_coll = verts[np.newaxis, :, :] - collide_p_co[:, np.newaxis, :] coll_dist = np.sum(vector_coll * coll_norm, axis=2) return coll_dist
def collisions_apply(collide_params)
-
apply collisions with obstacles
Expand source code
def collisions_apply(collide_params): '''apply collisions with obstacles''' ps, collide_p_co, collide_normals, pol_v, bounce, edges = collide_params pols_m, verts_m = b_box_coll_filter(pol_v, ps.verts, ps.rads) verts = ps.verts[verts_m] rads = ps.rads[verts_m] vels = ps.vel[verts_m] index = ps.index[verts_m] coll_norm = collide_normals[pols_m, np.newaxis, :] coll_dist = collision_dist(verts, collide_p_co[pols_m], coll_norm) coll_inter = np.absolute(coll_dist) - rads[np.newaxis, :] coll_mask = coll_inter < 0 if not np.any(coll_mask): return coll_sign = 2*(coll_dist > 0) - 1 mask_none = np.any(coll_mask, axis=0) p_triang = verts[mask_none] + coll_dist[:, mask_none, np.newaxis] * coll_norm rad_axis = rads[np.newaxis, mask_none] index = index[mask_none] coll_mask = coll_mask[:, mask_none] coll_inter = coll_inter[:, mask_none] coll_sign = coll_sign[:, mask_none] vels = vels[mask_none] wuv = pts_in_tris(p_triang, edges[pols_m], pol_v[pols_m], coll_norm, rad_axis, coll_mask) num_int = np.maximum(np.sum(wuv, axis=0), 1)[:, np.newaxis] displace = np.zeros((ps.v_len, 3), dtype=np.float32) velocity = np.zeros((ps.v_len, 3), dtype=np.float32) displace[index, :] = coll_displace(coll_norm, coll_inter, coll_sign, wuv, num_int) velocity[index, :] = coll_vel(coll_norm, vels, wuv, num_int) ps.verts += displace ps.vel += velocity * bounce
def collisions_setup(ps, dicti, limits_composite)
-
prepare collision data
Expand source code
def collisions_setup(ps, dicti, limits_composite): '''prepare collision data''' local_func, use_collide, local_params = dicti if use_collide: obstacles, obstacles_pols, obstacles_bounce = local_params np_collide_v = np.array(obstacles) np_collide_pol = np.array(obstacles_pols, dtype=np.int16) np_pol_v = np_collide_v[np_collide_pol] collide_v1 = np_pol_v[:, 1, :] - np_pol_v[:, 0, :] collide_v2 = np_pol_v[:, 2, :] - np_pol_v[:, 0, :] edges = np.zeros(np_pol_v.shape, dtype=np.float32) edges[:, 0, :] = collide_v1 edges[:, 1, :] = np_pol_v[:, 2, :] - np_pol_v[:, 1, :] edges[:, 2, :] = np_pol_v[:, 0, :] - np_pol_v[:, 2, :] coll_norm = collide_normals_get(collide_v1, collide_v2) coll_p_co = np_pol_v[:, 0, :] bounce = np.array(obstacles_bounce) collide_params = [ps, coll_p_co, coll_norm, np_pol_v, bounce, edges] limits_composite[0].append(local_func) limits_composite[1].append(collide_params)
def cross_indices3(n)
-
create crossed indices
Expand source code
def cross_indices3(n): '''create crossed indices''' nu = np.sum(np.arange(n, dtype=np.int64)) ind = np.zeros((nu, 2), dtype=np.int16) c = 0 for i in range(n-1): l = n-i-1 np_i = np.full(n-i-1, i, dtype=np.int32) np_j = np.arange(i+1, n, dtype=np.int32) np_a = np.stack((np_i, np_j), axis=-1) ind[c:c+l, :] = np_a c += l return ind
def drag_force_apply(params)
-
apply drag force (resistance from environment)
Expand source code
def drag_force_apply(params): '''apply drag force (resistance from environment)''' ps, use_grow, drag_mag, surf = params vel_mag = np.linalg.norm(ps.vel, axis=1) vel_mag_zero = vel_mag == 0 vel_mag[vel_mag_zero] = 1 # squaring the speed is more accurate but harder to control # vel_mag2 = vel_mag * vel_mag vel_mag2 = vel_mag vel_norm = ps.vel/vel_mag[:, np.newaxis] if use_grow: surf = np.power(ps.rads, 2) drag = -vel_norm * drag_mag * vel_mag2[:, np.newaxis] * surf[:, np.newaxis] ps.r += drag
def drag_setup(ps, dicti, forces_composite)
-
prepare drag data
Expand source code
def drag_setup(ps, dicti, forces_composite): '''prepare drag data''' local_func, local_gates, local_params = dicti use_drag, use_grow = local_gates if use_drag: drag_force = local_params np_drag_force = np.array(drag_force)[:, np.newaxis] use_drag = np.any(np_drag_force > 0) if use_drag: surf = np.power(ps.rads, 2) drag_params = [ps, use_grow, np_drag_force, surf] forces_composite[0].append(local_func) forces_composite[1].append(drag_params)
def expand_pols_numpy(p, len_max)
-
to fit variable sided polygons in one array (cycling)
Expand source code
def expand_pols_numpy(p, len_max): '''to fit variable sided polygons in one array (cycling)''' np_match_cycle = np.vectorize(match_cylce) np_match_cycle(p, len_max) new_pols = np.zeros((len(p), len_max), dtype=np.int32) for i in range(len(p)): new_pols[i, :] = p[i] return new_pols
def fill_params_dict(p_dict, parameters, par)
-
redistribute parameters
Expand source code
def fill_params_dict(p_dict, parameters, par): '''redistribute parameters''' for x in PARAMS_GROUPS: if type(PARAMS_GROUPS[x]) == tuple: p_dict[x] = [par[p] for p in PARAMS_GROUPS[x] ] else: p_dict[x] = par[PARAMS_GROUPS[x]] p_dict["apply_f"] = True
def fit_force(ps, index_inter, fit_params)
-
the untouched particles will grow, the ones that collide will shrink
Expand source code
def fit_force(ps, index_inter, fit_params): '''the untouched particles will grow, the ones that collide will shrink''' grow, min_rad, max_rad = fit_params touch = np.unique(index_inter) free = np.setdiff1d(np.arange(ps.v_len, dtype=np.int16), touch) v_grow = len(grow) > 1 grow_un, grow_tou = [grow[free], grow[touch]] if v_grow else [grow, grow] ps.rads[free] += grow_un*0.1 ps.rads[touch] -= grow_tou*0.1 ps.rads = np.clip(ps.rads, min_rad, max_rad)
def fit_setup(use_grow, np_grow, min_rad, max_rad)
-
Prepare fitting data
Expand source code
def fit_setup(use_grow, np_grow, min_rad, max_rad): '''Prepare fitting data''' if use_grow: np_min_rad = np.array(min_rad) np_max_rad = np.array(max_rad) fit_params = [np_grow, np_min_rad, np_max_rad] else: fit_params = [] return fit_params
def inflate_force(inflate_params)
-
apply force based on normals
Expand source code
def inflate_force(inflate_params): '''apply force based on normals''' ps, np_pols, pol_side_max, pols_sides, inflate, p_regular = inflate_params pol_v = ps.verts[np_pols, :] pols_normal = pols_normals(pol_v, inflate) result = np.zeros((ps.v_len, 3), dtype=np.float64) if p_regular: p_area = calc_area(pol_side_max, pol_v, pols_normal)[:, np.newaxis] for i in range(pol_side_max): np.add.at(result, np_pols[:, i], pols_normal * p_area) else: p_area = calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal)[:, np.newaxis] for i in range(pol_side_max): mask = pols_sides > i np.add.at(result, np_pols[mask, i], pols_normal[mask] * p_area[mask]) ps.r += result
def inflate_setup(ps, dicti, forces_composite)
-
prepare data to inflate
Expand source code
def inflate_setup(ps, dicti, forces_composite): '''prepare data to inflate''' local_func, use_inflate, local_params = dicti if use_inflate: pols, inflate = local_params np_pols = np.array(pols) p_len = len(pols) if np_pols.dtype == np.object: np_len = np.vectorize(len) pols_sides = np_len(np_pols) pol_side_max = np.amax(pols_sides) np_pols = expand_pols_numpy(np_pols, pol_side_max) p_regular = False else: p_regular = True pols_sides = np.array(p_len) pol_side_max = len(pols[0]) inflate_params = [ps, np_pols, pol_side_max, pols_sides, inflate, p_regular] forces_composite[0].append(local_func) forces_composite[1].append(inflate_params)
def iterate(iterations_max, force_map, force_parameters, out_params)
-
execute repeatedly the defined force map
Expand source code
def iterate(iterations_max, force_map, force_parameters, out_params): ''' execute repeatedly the defined force map''' num_forces = len(force_map) for it in range(iterations_max): for i in range(num_forces): force_map[i](force_parameters[i]) output_data(it, out_params)
def limit_speed(np_vel, max_vel)
-
'constrain speed magniture
Expand source code
def limit_speed(np_vel, max_vel): ''''constrain speed magniture''' vel_mag = np.linalg.norm(np_vel, axis=1) vel_exceded = vel_mag > max_vel np_vel[vel_exceded] = np_vel[vel_exceded] / vel_mag[vel_exceded, np.newaxis] * max_vel
def local_dict(dictionaries, name)
-
get all related to the name
Expand source code
def local_dict(dictionaries, name): '''get all related to the name''' return [dictionaries[0][name], dictionaries[1][name], dictionaries[2][name]]
def match_cylce(p, n)
-
append cylcing till have n items
Expand source code
def match_cylce(p, n): '''append cylcing till have n items''' difference = n - len(p) for i in range(difference): p.append(p[i % len(p)])
def numpy_fit_long_repeat(p, maxl)
-
match list length by repeating last one or removing end
Expand source code
def numpy_fit_long_repeat(p, maxl): '''match list length by repeating last one or removing end''' q = [] for g in p: difl = maxl - g.shape[0] if difl > 0: rr = np.repeat(g[np.newaxis, -1], difl, axis=0) g = np.concatenate((g, rr)) if difl < 0: g = g[:maxl] q.append(g) return q
def numpy_match_long_repeat(p)
-
match list length by repeating last one
Expand source code
def numpy_match_long_repeat(p): '''match list length by repeating last one''' q = [] maxl = 0 for g in p: maxl = max(maxl, g.shape[0]) for g in p: difl = maxl - g.shape[0] if difl > 0: rr = np.repeat(g[np.newaxis, -1], difl, axis=0) g = np.concatenate((g, rr)) q.append(g) return q
def output_data(it, params)
-
if is pertinent output the data
Expand source code
def output_data(it, params): '''if is pertinent output the data''' iterations_rec, ps, gate, out_lists = params record_iteration = it in iterations_rec if record_iteration: data_out = prepare_output_data(ps, gate) record_data(data_out, out_lists)
def pins_apply(ps)
-
apply pin mask
Expand source code
def pins_apply(ps): '''apply pin mask''' ps.apply_pins()
def pins_setup(ps, dicti, forces_composite)
-
prepare pins data
Expand source code
def pins_setup(ps, dicti, forces_composite): '''prepare pins data''' ps.pins_init(dicti, forces_composite)
def pols_normals(pol_v, mag)
-
get actual polygons normal with controlled magnitude
Expand source code
def pols_normals(pol_v, mag): '''get actual polygons normal with controlled magnitude''' v1 = pol_v[:, 1, :] - pol_v[:, 0, :] v2 = pol_v[:, 2, :] - pol_v[:, 0, :] pols_normal = np.cross(v1, v2) pols_normal_d = np.linalg.norm(pols_normal, axis=1) return pols_normal / pols_normal_d[:, np.newaxis] * mag
def prepare_output_data(ps, gate)
-
prepare data to output
Expand source code
def prepare_output_data(ps, gate): '''prepare data to output''' use_numpy_out = gate if use_numpy_out: return [ps.verts, ps.rads, ps.vel, ps.params["Pins Reactions"]] else: return [ps.verts.tolist(), ps.rads.tolist(), ps.vel.tolist(), ps.params["Pins Reactions"].tolist()]
def pts_in_tris(p_triang, edges, pol_v, coll_normals, rad_axis, coll_mask)
-
calculate if points are inside the triangles
Expand source code
def pts_in_tris(p_triang, edges, pol_v, coll_normals, rad_axis, coll_mask): '''calculate if points are inside the triangles''' w = baricentric_mask(p_triang, edges, pol_v, coll_normals, 0, rad_axis) u = baricentric_mask(p_triang, edges, pol_v, coll_normals, 1, rad_axis) v = baricentric_mask(p_triang, edges, pol_v, coll_normals, 2, rad_axis) return w * u * v * coll_mask
def pulga_system_init(params, parameters, gates, out_lists, cache)
-
the main function of the engine
Expand source code
def pulga_system_init(params, parameters, gates, out_lists, cache): '''the main function of the engine''' dictionaries = [FUNC_DICT, gates, {}] fill_params_dict(dictionaries[2], parameters, params) force_map = [] force_parameters = [] forces_composite = [force_map, force_parameters] ps = PulgaSystem(dictionaries[2]["main"]) for force in FORCE_CHAIN: INIT_FUNC_DICT[force](ps, local_dict(dictionaries, force), forces_composite) iterations = parameters[1] iterations_max = max(iterations) iterations_rec = [i-1 for i in iterations] out_params = [iterations_rec, ps, dictionaries[1]["output"], out_lists] if dictionaries[1]["accumulate"]: if len(cache) > 0: ps.hard_update_list(cache, gates["self_react"][2], gates["Pins"]) iterate(iterations_max, force_map, force_parameters, out_params) return ps.verts, ps.rads, ps.vel, ps.params["Pins Reactions"]
def random_force_apply(params)
-
apply random vectors and change it
Expand source code
def random_force_apply(params): '''apply random vectors and change it''' ps, random_force, random_variate, random_variation = params if random_variate: random_var = 2 * random_force * np.random.random((ps.v_len, 3)) - random_force ps.random_v = ps.random_v * (1 - random_variation) + random_var * random_variation ps.r += ps.random_v
def random_setup(ps, dicti, forces_composite)
-
initialize random force
Expand source code
def random_setup(ps, dicti, forces_composite): '''initialize random force''' local_func, local_gate, local_params = dicti if local_gate: random_seed, random_force, random_variation = local_params np_random_force = np.array(random_force) if any(np_random_force != 0): np_random_variation = np.array(random_variation) random_force = np_random_force[:, np.newaxis] random_variation = np_random_variation[:, np.newaxis] random_variate = any(random_variation != 0) np.random.seed(random_seed[0]) ps.random_v = random_force * np.random.random((ps.v_len, 3)) - random_force / 2 random_params = [ps, random_force, random_variate, random_variation] forces_composite[0].append(local_func) forces_composite[1].append(random_params)
def record_data(data_out, out_lists)
-
save data to main list
Expand source code
def record_data(data_out, out_lists): '''save data to main list''' verts_out, rads_out, velocity_out, reactions_out = out_lists new_verts, new_rad, new_vel, new_react = data_out verts_out.append(new_verts) rads_out.append(new_rad) velocity_out.append(new_vel) reactions_out.append(new_react)
def self_collision_force(result, dist, sum_rad, index_inter, mask, normal_v, self_collision)
-
apply collision forces between particles
Expand source code
def self_collision_force(result, dist, sum_rad, index_inter, mask, normal_v, self_collision): '''apply collision forces between particles''' id0 = index_inter[:, 0] id1 = index_inter[:, 1] le = dist[mask, np.newaxis] - sum_rad[mask, np.newaxis] no = normal_v[mask] variable_coll = len(self_collision) > 1 sf = self_collision[:, np.newaxis] len0, len1 = [sf[id1], sf[id0]] if variable_coll else [sf, sf] result[id0, id1, :] = -no * le * len0 result[id1, id0, :] = no * le * len1
def self_react(params)
-
behaviors between particles: collide, attract and fit
Expand source code
def self_react(params): '''behaviors between particles: collide, attract and fit''' ps, collision, sum_rad, gates, att_params, fit_params = params use_collide, use_attract, use_grow = gates indexes = ps.params['indexes'] if use_grow: sum_rad = ps.rads[indexes[:, 0]] + ps.rads[indexes[:, 1]] if use_attract: att_params[2] = ps.mass[indexes[:, 0]] * ps.mass[indexes[:, 1]] dif_v = ps.verts[indexes[:, 0], :] - ps.verts[indexes[:, 1], :] dist = np.linalg.norm(dif_v, axis=1) mask = sum_rad > dist index_inter = indexes[mask] some_collisions = use_collide and len(index_inter) > 0 some_attractions = use_attract and(len(index_inter) < len(indexes)) if some_collisions or some_attractions: result = np.zeros((ps.v_len, ps.v_len, 3), dtype=np.float64) dist_cor = np.clip(dist, 1e-6, 1e4) normal_v = dif_v/dist_cor[:, np.newaxis] if some_collisions: self_collision_force(result, dist, sum_rad, index_inter, mask, normal_v, collision) if some_attractions: antimask = np.invert(mask) attract_force(result, dist_cor, antimask, indexes, normal_v, att_params) ps.r += np.sum(result, axis=1) if use_grow: fit_force(ps, index_inter, fit_params) ps.mass = ps.density * np.power(ps.rads, 3)
def self_react_setup(ps, dicti, forces_composite)
-
prepare self reacting data
Expand source code
def self_react_setup(ps, dicti, forces_composite): '''prepare self reacting data''' local_func, local_gates, local_params = dicti collision, attract, att_decay, grow, min_rad, max_rad = local_params use_self_collision, use_attract, use_grow = local_gates np_collision = np.array(collision) use_self_collision = use_self_collision and np.any(np_collision > 0) np_attract = np.array(attract)[:, np.newaxis] use_attract = use_attract and np.any(np_attract != 0) np_grow = np.array(grow) use_grow = use_grow and np.any(np_grow != 0) use_self_react = (use_attract or use_self_collision or use_grow) if not use_self_react: return ps.params['indexes'] = cross_indices3(ps.v_len) sum_rad = ps.rads[ps.params['indexes'][:, 0]] + ps.rads[ps.params['indexes'][:, 1]] att_params = att_setup(use_attract, ps, np_attract, att_decay) fit_params = fit_setup(use_grow, np_grow, min_rad, max_rad) gates = [use_self_collision, use_attract, use_grow] params = [ps, np_collision, sum_rad, gates, att_params, fit_params] forces_composite[0].append(local_func) forces_composite[1].append(params)
def spring_force(spring_params)
-
apply spring forces related to edges resistance
Expand source code
def spring_force(spring_params): '''apply spring forces related to edges resistance''' ps, np_springs, dist_rest, spring_k = spring_params pairs = ps.verts[np_springs, :] dif_v = pairs[:, 0, :] - pairs[:, 1, :] dist = np.linalg.norm(dif_v, axis=1) dif_l = dist - dist_rest dist[dist == 0] = 1 normal_v = dif_v / dist[:, np.newaxis] x = dif_l[:, np.newaxis] k = spring_k[:, np.newaxis] result = np.zeros((ps.v_len, 3), dtype=np.float64) np.add.at(result, np_springs[:, 0], -normal_v * x * k) np.add.at(result, np_springs[:, 1], normal_v * x * k) ps.r += result
def spring_setup(ps, dicti, forces_composite)
-
prepare spring system and data
Expand source code
def spring_setup(ps, dicti, forces_composite): '''prepare spring system and data''' local_func, local_gates, local_params = dicti use_springs, use_fix_len = local_gates if use_springs: springs, fixed_len, spring_k = local_params spring_k = np.array(spring_k) if np.any(spring_k != 0): springs = np.array(springs) if use_fix_len or fixed_len[0] > 0: dist_rest = fixed_len else: dist_rest = calc_rest_length(ps.verts, springs) spring_params = [ps, springs, dist_rest, spring_k] forces_composite[0].append(local_func) forces_composite[1].append(spring_params)
def world_forces_apply(ps)
-
apply constant forces
Expand source code
def world_forces_apply(ps): '''apply constant forces''' ps.r += ps.params['gravity'] + ps.params['wind']
def world_forces_apply_var(ps)
-
apply constant forces
Expand source code
def world_forces_apply_var(ps): '''apply constant forces''' ps.r += ps.params['gravity'] * ps.mass[:, np.newaxis] + ps.params['wind']
def world_forces_setup(ps, dicti, forces_composite)
-
prepare world forces data
Expand source code
def world_forces_setup(ps, dicti, forces_composite): '''prepare world forces data''' local_func, gates, params = dicti use_world_f, size_change = gates world_forces, wind = params if use_world_f: np_world_f = np.array(world_forces) np_wind = np.array(wind) if len(world_forces) > 1: np_world_f = numpy_fit_long_repeat([np_world_f], ps.v_len)[0] if not size_change: np_world_f = np_world_f * ps.mass[:, np.newaxis] func = local_func[0] else: func = local_func[1] if len(np_wind)> 1: np_wind = numpy_fit_long_repeat([np_wind], ps.v_len)[0] ps.params['gravity'] = np_world_f ps.params['wind'] = np_wind forces_composite[0].append(func) forces_composite[1].append(ps)
Classes
class PulgaSystem (init_params)
-
Store states
Expand source code
class PulgaSystem(): '''Store states''' verts, rads, vel, density = [[], [], [], []] v_len = [] params = {} def __init__(self, init_params): self.main_setup(init_params) self.mass = self.density * np.power(self.rads, 3) self.random_v = [] self.r = np.zeros((self.v_len, 3), dtype=np.float64) self.index = np.arange(self.v_len) def main_setup(self, local_params): '''prepare main data''' p = self.params initial_pos, rads_in, initial_vel, max_vel, density = local_params self.verts = np.array(initial_pos) self.rads = np.array(rads_in, dtype=np.float64) self.vel = np.array(initial_vel, dtype=np.float64) self.v_len = len(self.verts) p['max_vel'] = np.array(max_vel) self.rads, self.vel = numpy_fit_long_repeat([self.rads, self.vel], self.v_len) if len(p['max_vel']) > 1: p['max_vel'] = numpy_fit_long_repeat([p['max_vel']], self.v_len)[0] self.density = np.array(density) if len(density) > 1: self.density = numpy_fit_long_repeat([self.density], self.v_len)[0] p["Pins Reactions"] = [] def hard_update(self, cache, size_change, pins_gates): '''replace verts, rads and vel (in NumPy)''' verts, rads, vel, react = cache if len(verts) == self.v_len: if pins_gates[0] and pins_gates[1]: unpinned = self.params['unpinned'] self.verts[unpinned] = verts[unpinned] else: self.verts = verts self.vel = vel if not size_change: self.rads = rads def hard_update_list(self, cache, size_change, pins_gates): '''replace verts, rads and velocity''' verts, rads, vel, react = cache if type(verts) == list: if len(verts) == self.v_len: if pins_gates[0] and pins_gates[1]: unpinned = self.params['unpinned'] self.verts[unpinned] = np.array(verts)[unpinned] else: self.verts = np.array(verts) if not size_change: self.rads = np.array(rads) self.vel = np.array(vel) else: self.hard_update(cache, size_change, pins_gates) def apply_forces(self): '''resultant --> acceleration --> speed --> position''' acc = self.r / self.mass[:, np.newaxis] self.vel += acc if np.any(self.params['max_vel']) > 0: limit_speed(self.vel, self.params['max_vel']) self.verts += self.vel self.r[:] = 0 def pins_init(self, dicti, forces_composite): local_func, local_gates, local_params = dicti pins, pins_goal_pos = local_params use_pins, use_pins_goal = local_gates if not use_pins: self.params["Pins Reactions"] = np.array([[]]) return self.params['Pins'] = np.array(pins) if self.params['Pins'].dtype == np.int32: if len(self.params['Pins']) == len(self.verts): self.params['Pins'] = self.params['Pins'] == 1 self.params['unpinned'] = np.invert(self.params['Pins']) else: self.params['unpinned'] = np.ones(len(self.verts), dtype=np.bool) self.params['unpinned'][self.params['Pins']] = False self.vel[self.params['Pins'], :] = 0 if use_pins_goal: self.verts[self.params['Pins'], :] = np.array(pins_goal_pos) forces_composite[0].append(local_func) forces_composite[1].append(self) return def apply_pins(self): '''cancel forces on pins''' self.params["Pins Reactions"] = -self.r[self.params["Pins"]] self.r[self.params["Pins"], :] = 0
Class variables
var density
var params
var rads
var v_len
var vel
var verts
Methods
def apply_forces(self)
-
resultant –> acceleration –> speed –> position
Expand source code
def apply_forces(self): '''resultant --> acceleration --> speed --> position''' acc = self.r / self.mass[:, np.newaxis] self.vel += acc if np.any(self.params['max_vel']) > 0: limit_speed(self.vel, self.params['max_vel']) self.verts += self.vel self.r[:] = 0
def apply_pins(self)
-
cancel forces on pins
Expand source code
def apply_pins(self): '''cancel forces on pins''' self.params["Pins Reactions"] = -self.r[self.params["Pins"]] self.r[self.params["Pins"], :] = 0
def hard_update(self, cache, size_change, pins_gates)
-
replace verts, rads and vel (in NumPy)
Expand source code
def hard_update(self, cache, size_change, pins_gates): '''replace verts, rads and vel (in NumPy)''' verts, rads, vel, react = cache if len(verts) == self.v_len: if pins_gates[0] and pins_gates[1]: unpinned = self.params['unpinned'] self.verts[unpinned] = verts[unpinned] else: self.verts = verts self.vel = vel if not size_change: self.rads = rads
def hard_update_list(self, cache, size_change, pins_gates)
-
replace verts, rads and velocity
Expand source code
def hard_update_list(self, cache, size_change, pins_gates): '''replace verts, rads and velocity''' verts, rads, vel, react = cache if type(verts) == list: if len(verts) == self.v_len: if pins_gates[0] and pins_gates[1]: unpinned = self.params['unpinned'] self.verts[unpinned] = np.array(verts)[unpinned] else: self.verts = np.array(verts) if not size_change: self.rads = np.array(rads) self.vel = np.array(vel) else: self.hard_update(cache, size_change, pins_gates)
def main_setup(self, local_params)
-
prepare main data
Expand source code
def main_setup(self, local_params): '''prepare main data''' p = self.params initial_pos, rads_in, initial_vel, max_vel, density = local_params self.verts = np.array(initial_pos) self.rads = np.array(rads_in, dtype=np.float64) self.vel = np.array(initial_vel, dtype=np.float64) self.v_len = len(self.verts) p['max_vel'] = np.array(max_vel) self.rads, self.vel = numpy_fit_long_repeat([self.rads, self.vel], self.v_len) if len(p['max_vel']) > 1: p['max_vel'] = numpy_fit_long_repeat([p['max_vel']], self.v_len)[0] self.density = np.array(density) if len(density) > 1: self.density = numpy_fit_long_repeat([self.density], self.v_len)[0] p["Pins Reactions"] = []
def pins_init(self, dicti, forces_composite)
-
Expand source code
def pins_init(self, dicti, forces_composite): local_func, local_gates, local_params = dicti pins, pins_goal_pos = local_params use_pins, use_pins_goal = local_gates if not use_pins: self.params["Pins Reactions"] = np.array([[]]) return self.params['Pins'] = np.array(pins) if self.params['Pins'].dtype == np.int32: if len(self.params['Pins']) == len(self.verts): self.params['Pins'] = self.params['Pins'] == 1 self.params['unpinned'] = np.invert(self.params['Pins']) else: self.params['unpinned'] = np.ones(len(self.verts), dtype=np.bool) self.params['unpinned'][self.params['Pins']] = False self.vel[self.params['Pins'], :] = 0 if use_pins_goal: self.verts[self.params['Pins'], :] = np.array(pins_goal_pos) forces_composite[0].append(local_func) forces_composite[1].append(self) return