From 3413e10134e9d36c1fe4dc50d1c4f7720871dcce Mon Sep 17 00:00:00 2001 From: Onek8 Date: Sun, 1 Jun 2025 22:11:11 +0000 Subject: [PATCH] moisesjpelaez - Physics Improvements --- leenkx/blender/lnx/material/make_particle.py | 189 ++++++++++++++++++- 1 file changed, 179 insertions(+), 10 deletions(-) diff --git a/leenkx/blender/lnx/material/make_particle.py b/leenkx/blender/lnx/material/make_particle.py index d22e534..8001dbf 100644 --- a/leenkx/blender/lnx/material/make_particle.py +++ b/leenkx/blender/lnx/material/make_particle.py @@ -1,3 +1,4 @@ +import bpy import lnx.utils import lnx.material.mat_state as mat_state @@ -10,6 +11,48 @@ else: def write(vert, particle_info=None, shadowmap=False): + ramp_el_len = 0 + + ramp_positions = [] + ramp_colors_b = [] + size_over_time_factor = 0 + + use_rotations = False + rotation_mode = 'NONE' + rotation_factor_random = 0 + phase_factor = 0 + phase_factor_random = 0 + + for obj in bpy.data.objects: + for psys in obj.particle_systems: + psettings = psys.settings + + if psettings.instance_object: + if psettings.instance_object.active_material: + # FIXME: Different particle systems may share the same particle object. This ideally should check the correct `ParticleSystem` using an id or name in the particle's object material. + if psettings.instance_object.active_material.name.replace(".", "_") == vert.context.matname: + # Rotation data + use_rotations = psettings.use_rotations + rotation_mode = psettings.rotation_mode + rotation_factor_random = psettings.rotation_factor_random + phase_factor = psettings.phase_factor + phase_factor_random = psettings.phase_factor_random + + # Texture slots data + if psettings.texture_slots and len(psettings.texture_slots.items()) != 0: + for tex_slot in psettings.texture_slots: + if not tex_slot: break + if not tex_slot.use_map_size: break # TODO: check also for other influences + if tex_slot.texture and tex_slot.texture.use_color_ramp: + if tex_slot.texture.color_ramp and tex_slot.texture.color_ramp.elements: + ramp_el_len = len(tex_slot.texture.color_ramp.elements.items()) + for element in tex_slot.texture.color_ramp.elements: + ramp_positions.append(element.position) + ramp_colors_b.append(element.color[2]) + size_over_time_factor = tex_slot.size_factor + break + + # Outs out_index = True if particle_info != None and particle_info['index'] else False out_age = True if particle_info != None and particle_info['age'] else False @@ -19,19 +62,50 @@ def write(vert, particle_info=None, shadowmap=False): out_velocity = True if particle_info != None and particle_info['velocity'] else False out_angular_velocity = True if particle_info != None and particle_info['angular_velocity'] else False + # Force Leenkx to create a new shader per material ID + vert.write(f'#ifdef PARTICLE_ID_{vert.context.material.lnx_material_id}') + vert.write('#endif') + vert.add_uniform('mat4 pd', '_particleData') + vert.add_uniform('float pd_size_random', '_particleSizeRandom') + vert.add_uniform('float pd_random', '_particleRandom') + vert.add_uniform('float pd_size', '_particleSize') + + if ramp_el_len != 0: + vert.add_const('float', 'P_SIZE_OVER_TIME_FACTOR', str(size_over_time_factor)) + for i in range(ramp_el_len): + vert.add_const('float', f'P_RAMP_POSITION_{i}', str(ramp_positions[i])) + vert.add_const('float', f'P_RAMP_COLOR_B_{i}', str(ramp_colors_b[i])) str_tex_hash = "float fhash(float n) { return fract(sin(n) * 43758.5453); }\n" vert.add_function(str_tex_hash) + + if (ramp_el_len != 0): + str_ramp_scale = "float get_ramp_scale(float age) {\n" + + for i in range(ramp_el_len): + if i == 0: + str_ramp_scale += f"if (age <= P_RAMP_POSITION_{i + 1})" + elif i == ramp_el_len - 1: + str_ramp_scale += f"return P_RAMP_COLOR_B_{ramp_el_len - 1};" + break + else: + str_ramp_scale += f"else if (age <= P_RAMP_POSITION_{i + 1})" + str_ramp_scale += f""" {{ + float t = (age - P_RAMP_POSITION_{i}) / (P_RAMP_POSITION_{i + 1} - P_RAMP_POSITION_{i}); + return mix(P_RAMP_COLOR_B_{i}, P_RAMP_COLOR_B_{i + 1}, t); + }} + """ + str_ramp_scale += "}\n" + vert.add_function(str_ramp_scale) + prep = 'float ' if out_age: prep = '' vert.add_out('float p_age') # var p_age = lapTime - p.i * spawnRate vert.write(prep + 'p_age = pd[3][3] - gl_InstanceID * pd[0][1];') - # p_age -= p_age * fhash(i) * r.lifetime_random; - vert.write('p_age -= p_age * fhash(gl_InstanceID) * pd[2][3];') # Loop # pd[0][0] - animtime, loop stored in sign @@ -43,13 +117,18 @@ def write(vert, particle_info=None, shadowmap=False): if out_lifetime: prep = '' vert.add_out('float p_lifetime') - vert.write(prep + 'p_lifetime = pd[0][2];') + vert.write(prep + 'p_lifetime = pd[0][2] * (1 - (fhash(gl_InstanceID + 4 * pd[0][3] + pd_random) * pd[2][3]));') # clip with nan vert.write('if (p_age < 0 || p_age > p_lifetime) {') vert.write(' gl_Position /= 0.0;') vert.write(' return;') vert.write('}') + if (ramp_el_len != 0): + vert.write('float n_age = clamp(p_age / p_lifetime, 0.0, 1.0);') + vert.write(f'spos.xyz *= 1 + (get_ramp_scale(n_age) - 1) * {size_over_time_factor};') + vert.write('spos.xyz *= 1 - (fhash(gl_InstanceID + 3 * pd[0][3] + pd_random) * pd_size_random);') + # vert.write('p_age /= 2;') # Match # object_align_factor / 2 + gxyz @@ -57,20 +136,20 @@ def write(vert, particle_info=None, shadowmap=False): if out_velocity: prep = '' vert.add_out('vec3 p_velocity') - vert.write(prep + 'p_velocity = vec3(pd[1][0], pd[1][1], pd[1][2]);') + vert.write(prep + 'p_velocity = vec3(pd[1][0] * (1 / pd_size), pd[1][1] * (1 / pd_size), pd[1][2] * (1 / pd_size));') - vert.write('p_velocity.x += fhash(gl_InstanceID) * pd[1][3] - pd[1][3] / 2;') - vert.write('p_velocity.y += fhash(gl_InstanceID + pd[0][3]) * pd[1][3] - pd[1][3] / 2;') - vert.write('p_velocity.z += fhash(gl_InstanceID + 2 * pd[0][3]) * pd[1][3] - pd[1][3] / 2;') + vert.write('p_velocity.x += (fhash(gl_InstanceID + pd_random) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];') + vert.write('p_velocity.y += (fhash(gl_InstanceID + pd_random + pd[0][3]) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];') + vert.write('p_velocity.z += (fhash(gl_InstanceID + pd_random + 2 * pd[0][3]) * 2.0 / pd_size - 1.0 / pd_size) * pd[1][3];') # factor_random = pd[1][3] # p.i = gl_InstanceID # particles.length = pd[0][3] # gxyz - vert.write('p_velocity.x += (pd[2][0] * p_age) / 5;') - vert.write('p_velocity.y += (pd[2][1] * p_age) / 5;') - vert.write('p_velocity.z += (pd[2][2] * p_age) / 5;') + vert.write('p_velocity.x += (pd[2][0] / (2 * pd_size)) * p_age;') + vert.write('p_velocity.y += (pd[2][1] / (2 * pd_size)) * p_age;') + vert.write('p_velocity.z += (pd[2][2] / (2 * pd_size)) * p_age;') prep = 'vec3 ' if out_location: @@ -80,6 +159,96 @@ def write(vert, particle_info=None, shadowmap=False): vert.write('spos.xyz += p_location;') + # Rotation + if use_rotations: + if rotation_mode != 'NONE': + vert.write(f'float p_angle = ({phase_factor} + (fhash(gl_InstanceID + pd_random + 5 * pd[0][3])) * {phase_factor_random});') + vert.write('p_angle *= 3.141592;') + vert.write('float c = cos(p_angle);') + vert.write('float s = sin(p_angle);') + vert.write('vec3 center = spos.xyz - p_location;') + + match rotation_mode: + case 'OB_X': + vert.write('vec3 rz = vec3(center.y, -center.x, center.z);') + vert.write('vec2 rotation = vec2(rz.y * c - rz.z * s, rz.y * s + rz.z * c);') + vert.write('spos.xyz = vec3(rz.x, rotation.x, rotation.y) + p_location;') + + if (not shadowmap): + vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);') + vert.write('vec2 n_rot = vec2(wnormal.y * c - wnormal.z * s, wnormal.y * s + wnormal.z * c);') + vert.write('wnormal = normalize(vec3(wnormal.x, n_rot.x, n_rot.y));') + case 'OB_Y': + vert.write('vec2 rotation = vec2(center.x * c + center.z * s, -center.x * s + center.z * c);') + vert.write('spos.xyz = vec3(rotation.x, center.y, rotation.y) + p_location;') + + if (not shadowmap): + vert.write('wnormal = normalize(vec3(wnormal.x * c + wnormal.z * s, wnormal.y, -wnormal.x * s + wnormal.z * c));') + case 'OB_Z': + vert.write('vec3 rz = vec3(center.y, -center.x, center.z);') + vert.write('vec3 ry = vec3(-rz.z, rz.y, rz.x);') + vert.write('vec2 rotation = vec2(ry.x * c - ry.y * s, ry.x * s + ry.y * c);') + vert.write('spos.xyz = vec3(rotation.x, rotation.y, ry.z) + p_location;') + + if (not shadowmap): + vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);') + vert.write('wnormal = vec3(-wnormal.z, wnormal.y, wnormal.x);') + vert.write('vec2 n_rot = vec2(wnormal.x * c - wnormal.y * s, wnormal.x * s + wnormal.y * c);') + vert.write('wnormal = normalize(vec3(n_rot.x, n_rot.y, wnormal.z));') + case 'VEL': + vert.write('vec3 forward = -normalize(p_velocity);') + vert.write('if (length(forward) > 1e-5) {') + vert.write('vec3 world_up = vec3(0.0, 0.0, 1.0);') + + vert.write('if (abs(dot(forward, world_up)) > 0.999) {') + vert.write('world_up = vec3(-1.0, 0.0, 0.0);') + vert.write('}') + + vert.write('vec3 right = cross(world_up, forward);') + vert.write('if (length(right) < 1e-5) {') + vert.write('forward = -forward;') + vert.write('right = cross(world_up, forward);') + vert.write('}') + vert.write('right = normalize(right);') + vert.write('vec3 up = normalize(cross(forward, right));') + + vert.write('mat3 rot = mat3(right, -forward, up);') + vert.write('mat3 phase = mat3(vec3(c, 0.0, -s), vec3(0.0, 1.0, 0.0), vec3(s, 0.0, c));') + vert.write('mat3 final_rot = rot * phase;') + vert.write('spos.xyz = final_rot * center + p_location;') + + if (not shadowmap): + vert.write('wnormal = normalize(final_rot * wnormal);') + vert.write('}') + + if rotation_factor_random != 0: + str_rotate_around = '''vec3 rotate_around(vec3 v, vec3 angle) { + // Rotate around X + float cx = cos(angle.x); + float sx = sin(angle.x); + v = vec3(v.x, v.y * cx - v.z * sx, v.y * sx + v.z * cx); + // Rotate around Y + float cy = cos(angle.y); + float sy = sin(angle.y); + v = vec3(v.x * cy + v.z * sy, v.y, -v.x * sy + v.z * cy); + // Rotate around Z + float cz = cos(angle.z); + float sz = sin(angle.z); + v = vec3(v.x * cz - v.y * sz, v.x * sz + v.y * cz, v.z); + return v; + }''' + vert.add_function(str_rotate_around) + + vert.write(f'''vec3 r_angle = vec3((fhash(gl_InstanceID + pd_random + 6 * pd[0][3]) * 4 - 2) * {rotation_factor_random}, + (fhash(gl_InstanceID + pd_random + 7 * pd[0][3]) * 4 - 2) * {rotation_factor_random}, + (fhash(gl_InstanceID + pd_random + 8 * pd[0][3]) * 4 - 2) * {rotation_factor_random});''') + vert.write('vec3 r_center = spos.xyz - p_location;') + vert.write('r_center = rotate_around(r_center, r_angle);') + vert.write('spos.xyz = r_center + p_location;') + + if not shadowmap: + vert.write('wnormal = normalize(rotate_around(wnormal, r_angle));') + # Particle fade if mat_state.material.lnx_particle_flag and lnx.utils.get_rp().lnx_particles == 'On' and mat_state.material.lnx_particle_fade: vert.add_out('float p_fade')