// // Created by Sam Gondelman on 7/22/2016 // Copyright 2016 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // // When you pass these uniforms in by setting the user data, the order must match // and the number of elements (floats) must be divisible by 4 #BEGIN_HIFI_UPDATE_UNIFORMS #define MAX_OBJECTS 50 vec4 NUM_OBJECTS; vec4 objects[2 * MAX_OBJECTS]; // 2 * MAX_OBJECTS elements, alternating pos/dim #END_HIFI_UPDATE_UNIFORMS // Must define the following update methods: // vec4 initPosition(int index); // vec4 initVelocity(int index); // vec4 updatePosition(vec4 pos, vec4 vel, int index, ivec2 iFragCoord); // vec4 updateVelocity(vec4 pos, vec4 vel, int index, ivec2 iFragCoord); #BEGIN_HIFI_UPDATE_METHODS /*const int TEST = 0; const float TEST_DIM[5] = float[](0.01, 0.1, 1.0, 10.0, 100.0); const float MIN_LIFETIME[5] = float[](0.1, 0.5, 1.0, 5.0, 10.0); // 5 * log(x + 1) const float PARTICLE_LIFETIME[5] = float[](0.5, 1.0, 7.5, 10.0, 20.0); // 10 * log(0.75 * x + 1) const float G[5] = float[](0.0001, 0.01, 0.1, 10.0, 100.0); // 100 * pow(log(0.001 * x * x + 1), 0.75) const float SQRT_G[5] = float[](0.01, 0.1, 0.316, 3.16, 10.0); // sqrt(G) const float MAX_VEL[5] = float[](0.25, 0.5, 2.5, 50.0, 250.0); // 150 * pow(log(0.01 * x * x + 1), 0.75) */ const int PARTICLES_PER_OBJECT = 1000; const float FAST_DEATH_SPEED = 3.0; const float dt = 0.0167; const float log10Inv = 1.0/log(10.0); float max3(vec3 v) { return max(v.x, max(v.y, v.z)); } float log10(float x) { return log(x) * log10Inv; } // These functions will determine the values of "constants" in terms of an object's largest dimension float calcMinLifetime(float x) { return 5.0 * log10(x + 1.0); } float calcParticleLifetime(float x) { return 10.0 * log10(0.75 * x + 1.0); } float calcG(float x) { return 100.0 * pow(log10(0.001 * x * x + 1.0), 0.75); } float calcMaxVel(float x) { return 150.0 * pow(log10(0.01 * x * x + 1.0), 0.75); } float hash(float n) { return fract(sin(n) * 753.5453123); } vec4 initPosition(int index) { // Particles start off dead return vec4(vec3(0.0), FLT_MAX); } vec4 initVelocity(int index) { // Particles start off dead return vec4(vec3(0.0), FLT_MAX); } vec4 updatePosition(vec4 pos, vec4 vel, int index, ivec2 iFragCoord) { // Don't get reborn if we don't have enough objects if (vel.w == FLT_MAX && pos.w > 0.0 && NUM_OBJECTS.x > 0 && index < NUM_OBJECTS.x * PARTICLES_PER_OBJECT) { // If this particle just died, move it to be near an unrezzed object // TODO: The hash function is never exactly 1, so the last object won't ever be picked. // The -0.01 fixes that, but should probably be changed to something else int object = int(hash(index * 1692.0 + particle.iGlobalTime) * (NUM_OBJECTS.x - 0.01)); vec3 objPos = objects[2 * object].xyz; vec3 objDim = objects[2 * object + 1].xyz;//vec3(TEST_DIM[TEST]); const float ORBIT_DIM_RATIO = 1.5; vec3 offset = objDim * ORBIT_DIM_RATIO * vec3(hash(index * 872.0 + particle.iGlobalTime) - 0.5, hash(index * 1667.0 + particle.iGlobalTime) - 0.5, hash(index * 23589.0 + particle.iGlobalTime) - 0.5); float maxDim = max3(objDim); pos.w = -(calcMinLifetime(maxDim) + calcParticleLifetime(maxDim) * hash(index * 7344.0)); pos.xyz = objPos + offset; } else if (vel.w < FLT_MAX && pos.w < 0.0) { pos.w = -pos.w; } else if (vel.w < FLT_MAX && pos.w > 0.0) { // Otherwise, move by dx pos += vec4(vel.xyz * dt, 0.0); } return pos; } vec4 updateVelocity(vec4 pos, vec4 vel, int index, ivec2 iFragCoord) { // Die quickly if we don't have enough objects if (index >= NUM_OBJECTS.x * PARTICLES_PER_OBJECT) { vel.w += dt * FAST_DEATH_SPEED; return vel; } vec3 minR; float minD2 = FLT_MAX; int minIndex; // pull particles towards nearest object for (int i = 0; i < int(NUM_OBJECTS.x); i++) { vec3 objPos = objects[2 * i].xyz; vec3 r = objPos - pos.xyz; float d2 = dot(r, r); if (d2 < minD2) { minR = r; minD2 = d2; minIndex = i; } } if (minD2 != FLT_MAX) { float maxDim = max3(objects[2 * minIndex + 1].xyz); float G = calcG(maxDim); float SQRT_G = sqrt(G); float d = sqrt(minD2); if (pos.w < 0.0) { float velMag = SQRT_G / sqrt(d); vel.xyz = velMag * normalize(cross(UP, -minR)); } else { vel.xyz += G / (d * minD2) * minR * dt; if (vel.w < pos.w) { // Particle still alive and there are still unrezzed objects vel.w += dt; } else if (vel.w < FLT_MAX) { // Particle died vel = vec4(FLT_MAX); } else { // Particle reborn vel.w = 0.0; } } float MAX_VEL = calcMaxVel(maxDim); vel.xyz = dot(vel.xyz, vel.xyz) > MAX_VEL * MAX_VEL ? MAX_VEL * normalize(vel.xyz) : vel.xyz; //vel.x = abs(vel.x) > MAX_VEL ? sign(vel.x) * MAX_VEL : vel.x; //vel.y = abs(vel.y) > MAX_VEL ? sign(vel.y) * MAX_VEL : vel.y; //vel.z = abs(vel.z) > MAX_VEL ? sign(vel.z) * MAX_VEL : vel.z; } else { // Die quickly if there are no objects left vel.w += dt * FAST_DEATH_SPEED; } return vel; } #END_HIFI_UPDATE_METHODS // Must define the following draw methods: // vec4 setColor(vec4 pos, vec4 vel, int index, ivec2 iFragCoord); // float setRadius(vec4 pos, vec4 vel, int index, ivec2 iFragCoord); #BEGIN_HIFI_DRAW_METHODS vec4 setColor(vec4 pos, vec4 vel, int index, ivec2 iFragCoord) { return particle.color; } float setRadius(vec4 pos, vec4 vel, int index, ivec2 iFragCoord) { const float GROW_TIME = 0.75; float remainingTime = pos.w - vel.w; remainingTime = max(remainingTime, 0.0); float ratio = min(min(vel.w / GROW_TIME, 1.0), min(remainingTime / GROW_TIME, 1.0)); return ratio * particle.radius; } #END_HIFI_DRAW_METHODS