1 package com.badlogic.gdx.graphics.g3d.particles.influencers; 2 3 import com.badlogic.gdx.graphics.g3d.particles.ParallelArray.FloatChannel; 4 import com.badlogic.gdx.graphics.g3d.particles.ParticleChannels; 5 import com.badlogic.gdx.graphics.g3d.particles.ParticleControllerComponent; 6 import com.badlogic.gdx.graphics.g3d.particles.values.ScaledNumericValue; 7 import com.badlogic.gdx.math.MathUtils; 8 import com.badlogic.gdx.math.Matrix4; 9 import com.badlogic.gdx.math.Quaternion; 10 import com.badlogic.gdx.math.Vector3; 11 import com.badlogic.gdx.utils.Json; 12 import com.badlogic.gdx.utils.JsonValue; 13 14 /** It's the base class for any kind of influencer which operates on angular velocity and acceleration of the particles. 15 * All the classes that will inherit this base class can and should be used 16 * only as sub-influencer of an instance of {@link DynamicsInfluencer} . 17 * @author Inferno */ 18 public abstract class DynamicsModifier extends Influencer{ 19 protected static final Vector3 TMP_V1 = new Vector3(), 20 TMP_V2 = new Vector3(), 21 TMP_V3 = new Vector3(); 22 protected static final Quaternion TMP_Q = new Quaternion(); 23 24 public static class FaceDirection extends DynamicsModifier { 25 FloatChannel rotationChannel, accellerationChannel; 26 27 public FaceDirection(){} 28 29 public FaceDirection (FaceDirection rotation) { 30 super(rotation); 31 } 32 @Override 33 public void allocateChannels() { 34 rotationChannel = controller.particles.addChannel(ParticleChannels.Rotation3D); 35 accellerationChannel = controller.particles.addChannel(ParticleChannels.Acceleration); 36 } 37 38 @Override 39 public void update () { 40 for(int i=0, accelOffset = 0, c = i +controller.particles.size *rotationChannel.strideSize; 41 i < c; 42 i +=rotationChannel.strideSize, accelOffset += accellerationChannel.strideSize){ 43 44 Vector3 axisZ = TMP_V1.set( accellerationChannel.data[accelOffset +ParticleChannels.XOffset], 45 accellerationChannel.data[accelOffset +ParticleChannels.YOffset], 46 accellerationChannel.data[accelOffset +ParticleChannels.ZOffset]).nor(), 47 axisY = TMP_V2.set(TMP_V1).crs(Vector3.Y).nor().crs(TMP_V1).nor(), 48 axisX = TMP_V3.set(axisY).crs(axisZ).nor(); 49 TMP_Q.setFromAxes(false, axisX.x, axisY.x, axisZ.x, 50 axisX.y, axisY.y, axisZ.y, 51 axisX.z, axisY.z, axisZ.z); 52 rotationChannel.data[i +ParticleChannels.XOffset] = TMP_Q.x; 53 rotationChannel.data[i +ParticleChannels.YOffset] = TMP_Q.y; 54 rotationChannel.data[i +ParticleChannels.ZOffset] = TMP_Q.z; 55 rotationChannel.data[i +ParticleChannels.WOffset] = TMP_Q.w; 56 } 57 } 58 59 @Override 60 public ParticleControllerComponent copy () { 61 return new FaceDirection(this); 62 } 63 } 64 65 public static abstract class Strength extends DynamicsModifier { 66 protected FloatChannel strengthChannel; 67 public ScaledNumericValue strengthValue; 68 69 public Strength(){ 70 strengthValue = new ScaledNumericValue(); 71 } 72 73 public Strength (Strength rotation) { 74 super(rotation); 75 strengthValue = new ScaledNumericValue(); 76 strengthValue.load(rotation.strengthValue); 77 } 78 79 @Override 80 public void allocateChannels() { 81 super.allocateChannels(); 82 ParticleChannels.Interpolation.id = controller.particleChannels.newId(); 83 strengthChannel = controller.particles.addChannel(ParticleChannels.Interpolation); 84 } 85 86 @Override 87 public void activateParticles (int startIndex, int count) { 88 float start, diff; 89 for(int i=startIndex*strengthChannel.strideSize, c = i +count*strengthChannel.strideSize; 90 i < c; 91 i +=strengthChannel.strideSize){ 92 start = strengthValue.newLowValue(); 93 diff = strengthValue.newHighValue(); 94 if(!strengthValue.isRelative()) 95 diff -= start; 96 strengthChannel.data[i + ParticleChannels.VelocityStrengthStartOffset] = start; 97 strengthChannel.data[i + ParticleChannels.VelocityStrengthDiffOffset] = diff; 98 } 99 } 100 101 @Override 102 public void write (Json json) { 103 super.write(json); 104 json.writeValue("strengthValue", strengthValue); 105 } 106 107 @Override 108 public void read (Json json, JsonValue jsonData) { 109 super.read(json, jsonData); 110 strengthValue = json.readValue("strengthValue", ScaledNumericValue.class, jsonData); 111 } 112 } 113 114 public static abstract class Angular extends Strength { 115 protected FloatChannel angularChannel; 116 /** Polar angle, XZ plane */ 117 public ScaledNumericValue thetaValue; 118 /** Azimuth, Y */ 119 public ScaledNumericValue phiValue; 120 121 public Angular(){ 122 thetaValue = new ScaledNumericValue(); 123 phiValue = new ScaledNumericValue(); 124 } 125 126 public Angular (Angular value) { 127 super(value); 128 thetaValue = new ScaledNumericValue(); 129 phiValue = new ScaledNumericValue(); 130 thetaValue.load(value.thetaValue); 131 phiValue.load(value.phiValue); 132 } 133 134 @Override 135 public void allocateChannels() { 136 super.allocateChannels(); 137 ParticleChannels.Interpolation4.id = controller.particleChannels.newId(); 138 angularChannel = controller.particles.addChannel(ParticleChannels.Interpolation4); 139 } 140 141 @Override 142 public void activateParticles (int startIndex, int count) { 143 super.activateParticles(startIndex, count); 144 float start, diff; 145 for(int i=startIndex*angularChannel.strideSize, c = i +count*angularChannel.strideSize; 146 i < c; 147 i +=angularChannel.strideSize){ 148 149 //Theta 150 start = thetaValue.newLowValue(); 151 diff = thetaValue.newHighValue(); 152 if(!thetaValue.isRelative()) 153 diff -= start; 154 angularChannel.data[i + ParticleChannels.VelocityThetaStartOffset] = start; 155 angularChannel.data[i + ParticleChannels.VelocityThetaDiffOffset] = diff; 156 157 //Phi 158 start = phiValue.newLowValue(); 159 diff = phiValue.newHighValue(); 160 if(!phiValue.isRelative()) 161 diff -= start; 162 angularChannel.data[i + ParticleChannels.VelocityPhiStartOffset] = start; 163 angularChannel.data[i + ParticleChannels.VelocityPhiDiffOffset] = diff; 164 } 165 } 166 167 @Override 168 public void write (Json json) { 169 super.write(json); 170 json.writeValue("thetaValue", thetaValue); 171 json.writeValue("phiValue", phiValue); 172 } 173 174 @Override 175 public void read (Json json, JsonValue jsonData) { 176 super.read(json, jsonData); 177 thetaValue = json.readValue("thetaValue", ScaledNumericValue.class, jsonData); 178 phiValue = json.readValue("phiValue", ScaledNumericValue.class, jsonData); 179 } 180 } 181 182 183 public static class Rotational2D extends Strength { 184 FloatChannel rotationalVelocity2dChannel; 185 186 public Rotational2D (){} 187 188 public Rotational2D (Rotational2D rotation) { 189 super(rotation); 190 } 191 192 @Override 193 public void allocateChannels() { 194 super.allocateChannels(); 195 rotationalVelocity2dChannel = controller.particles.addChannel(ParticleChannels.AngularVelocity2D); 196 } 197 198 @Override 199 public void update () { 200 for(int i=0, l = ParticleChannels.LifePercentOffset, s =0, 201 c = i +controller.particles.size*rotationalVelocity2dChannel.strideSize; 202 i < c; 203 s += strengthChannel.strideSize, i +=rotationalVelocity2dChannel.strideSize, l +=lifeChannel.strideSize){ 204 rotationalVelocity2dChannel.data[i] += strengthChannel.data[s + ParticleChannels.VelocityStrengthStartOffset] + 205 strengthChannel.data[s + ParticleChannels.VelocityStrengthDiffOffset]* strengthValue.getScale(lifeChannel.data[l]); 206 } 207 } 208 209 @Override 210 public Rotational2D copy () { 211 return new Rotational2D(this); 212 } 213 } 214 215 public static class Rotational3D extends Angular { 216 FloatChannel rotationChannel, rotationalForceChannel; 217 218 public Rotational3D(){} 219 220 public Rotational3D (Rotational3D rotation) { 221 super(rotation); 222 } 223 224 @Override 225 public void allocateChannels() { 226 super.allocateChannels(); 227 rotationChannel = controller.particles.addChannel(ParticleChannels.Rotation3D); 228 rotationalForceChannel = controller.particles.addChannel(ParticleChannels.AngularVelocity3D); 229 } 230 231 @Override 232 public void update () { 233 234 //Matrix3 I_t = defined by the shape, it's the inertia tensor 235 //Vector3 r = position vector 236 //Vector3 L = r.cross(v.mul(m)), It's the angular momentum, where mv it's the linear momentum 237 //Inverse(I_t) = a diagonal matrix where the diagonal is IyIz, IxIz, IxIy 238 //Vector3 w = L/I_t = inverse(I_t)*L, It's the angular velocity 239 //Quaternion spin = 0.5f*Quaternion(w, 0)*currentRotation 240 //currentRotation += spin*dt 241 //normalize(currentRotation) 242 243 //Algorithm 1 244 //Consider a simple channel which represent an angular velocity w 245 //Sum each w for each rotation 246 //Update rotation 247 248 //Algorithm 2 249 //Consider a channel which represent a sort of angular momentum L (r, v) 250 //Sum each L for each rotation 251 //Multiply sum by constant quantity k = m*I_to(-1) , m could be optional while I is constant and can be calculated at start 252 //Update rotation 253 254 //Algorithm 3 255 //Consider a channel which represent a simple angular momentum L 256 //Proceed as Algorithm 2 257 258 for(int i=0, l = ParticleChannels.LifePercentOffset, s =0, a = 0, 259 c = controller.particles.size*rotationalForceChannel.strideSize; 260 i < c; 261 s += strengthChannel.strideSize, i +=rotationalForceChannel.strideSize, 262 a += angularChannel.strideSize, l += lifeChannel.strideSize){ 263 264 float lifePercent = lifeChannel.data[l], 265 strength = strengthChannel.data[s + ParticleChannels.VelocityStrengthStartOffset] + 266 strengthChannel.data[s + ParticleChannels.VelocityStrengthDiffOffset]* strengthValue.getScale(lifePercent), 267 phi = angularChannel.data[a + ParticleChannels.VelocityPhiStartOffset] + 268 angularChannel.data[a + ParticleChannels.VelocityPhiDiffOffset]* phiValue.getScale(lifePercent), 269 theta = angularChannel.data[a + ParticleChannels.VelocityThetaStartOffset] + 270 angularChannel.data[a + ParticleChannels.VelocityThetaDiffOffset]* thetaValue.getScale(lifePercent); 271 272 float cosTheta = MathUtils.cosDeg(theta), sinTheta = MathUtils.sinDeg(theta), 273 cosPhi = MathUtils.cosDeg(phi), sinPhi = MathUtils.sinDeg(phi); 274 275 TMP_V3.set(cosTheta *sinPhi, cosPhi, sinTheta*sinPhi); 276 TMP_V3.scl(strength*MathUtils.degreesToRadians); 277 278 rotationalForceChannel.data[i +ParticleChannels.XOffset] += TMP_V3.x; 279 rotationalForceChannel.data[i +ParticleChannels.YOffset] += TMP_V3.y; 280 rotationalForceChannel.data[i +ParticleChannels.ZOffset] += TMP_V3.z; 281 } 282 } 283 284 @Override 285 public Rotational3D copy () { 286 return new Rotational3D(this); 287 } 288 } 289 290 291 public static class CentripetalAcceleration extends Strength { 292 FloatChannel accelerationChannel; 293 FloatChannel positionChannel; 294 public CentripetalAcceleration(){} 295 296 public CentripetalAcceleration (CentripetalAcceleration rotation) { 297 super(rotation); 298 } 299 300 @Override 301 public void allocateChannels() { 302 super.allocateChannels(); 303 accelerationChannel = controller.particles.addChannel(ParticleChannels.Acceleration); 304 positionChannel = controller.particles.addChannel(ParticleChannels.Position); 305 } 306 307 @Override 308 public void update () { 309 float cx = 0, cy = 0, cz = 0; 310 if(!isGlobal){ 311 float[] val = controller.transform.val; 312 cx = val[Matrix4.M03]; 313 cy = val[Matrix4.M13]; 314 cz = val[Matrix4.M23]; 315 } 316 317 int lifeOffset=ParticleChannels.LifePercentOffset, strengthOffset = 0, positionOffset = 0, forceOffset = 0; 318 for(int i=0, c= controller.particles.size; i < c; ++i, 319 positionOffset += positionChannel.strideSize, 320 strengthOffset += strengthChannel.strideSize, 321 forceOffset +=accelerationChannel.strideSize, 322 lifeOffset += lifeChannel.strideSize){ 323 324 float strength = strengthChannel.data[strengthOffset + ParticleChannels.VelocityStrengthStartOffset] + 325 strengthChannel.data[strengthOffset + ParticleChannels.VelocityStrengthDiffOffset]* strengthValue.getScale(lifeChannel.data[lifeOffset]); 326 TMP_V3.set( positionChannel.data[positionOffset +ParticleChannels.XOffset] -cx, 327 positionChannel.data[positionOffset +ParticleChannels.YOffset] -cy, 328 positionChannel.data[positionOffset +ParticleChannels.ZOffset] -cz) 329 .nor().scl(strength); 330 accelerationChannel.data[forceOffset +ParticleChannels.XOffset] += TMP_V3.x; 331 accelerationChannel.data[forceOffset +ParticleChannels.YOffset] += TMP_V3.y; 332 accelerationChannel.data[forceOffset +ParticleChannels.ZOffset] += TMP_V3.z; 333 } 334 } 335 336 @Override 337 public CentripetalAcceleration copy () { 338 return new CentripetalAcceleration(this); 339 } 340 } 341 342 public static class PolarAcceleration extends Angular { 343 FloatChannel directionalVelocityChannel; 344 public PolarAcceleration(){} 345 346 public PolarAcceleration (PolarAcceleration rotation) { 347 super(rotation); 348 } 349 350 @Override 351 public void allocateChannels() { 352 super.allocateChannels(); 353 directionalVelocityChannel = controller.particles.addChannel(ParticleChannels.Acceleration); 354 } 355 356 @Override 357 public void update () { 358 for(int i=0, l = ParticleChannels.LifePercentOffset, s =0, a = 0, 359 c = i +controller.particles.size*directionalVelocityChannel.strideSize; 360 i < c; 361 s += strengthChannel.strideSize, i +=directionalVelocityChannel.strideSize, 362 a += angularChannel.strideSize, l += lifeChannel.strideSize){ 363 364 float lifePercent = lifeChannel.data[l], 365 strength = strengthChannel.data[s + ParticleChannels.VelocityStrengthStartOffset] + 366 strengthChannel.data[s + ParticleChannels.VelocityStrengthDiffOffset]* strengthValue.getScale(lifePercent), 367 phi = angularChannel.data[a + ParticleChannels.VelocityPhiStartOffset] + 368 angularChannel.data[a + ParticleChannels.VelocityPhiDiffOffset]* phiValue.getScale(lifePercent), 369 theta = angularChannel.data[a + ParticleChannels.VelocityThetaStartOffset] + 370 angularChannel.data[a + ParticleChannels.VelocityThetaDiffOffset]* thetaValue.getScale(lifePercent); 371 372 float cosTheta = MathUtils.cosDeg(theta), sinTheta = MathUtils.sinDeg(theta), 373 cosPhi = MathUtils.cosDeg(phi), sinPhi = MathUtils.sinDeg(phi); 374 TMP_V3.set(cosTheta *sinPhi, cosPhi, sinTheta*sinPhi).nor().scl(strength); 375 directionalVelocityChannel.data[i +ParticleChannels.XOffset] += TMP_V3.x; 376 directionalVelocityChannel.data[i +ParticleChannels.YOffset] += TMP_V3.y; 377 directionalVelocityChannel.data[i +ParticleChannels.ZOffset] += TMP_V3.z; 378 } 379 } 380 381 @Override 382 public PolarAcceleration copy () { 383 return new PolarAcceleration(this); 384 } 385 } 386 387 public static class TangentialAcceleration extends Angular { 388 FloatChannel directionalVelocityChannel, positionChannel; 389 390 public TangentialAcceleration(){} 391 392 public TangentialAcceleration (TangentialAcceleration rotation) { 393 super(rotation); 394 } 395 396 @Override 397 public void allocateChannels() { 398 super.allocateChannels(); 399 directionalVelocityChannel = controller.particles.addChannel(ParticleChannels.Acceleration); 400 positionChannel = controller.particles.addChannel(ParticleChannels.Position); 401 } 402 403 @Override 404 public void update () { 405 for(int i=0, l = ParticleChannels.LifePercentOffset, s =0, a = 0, positionOffset = 0, 406 c = i +controller.particles.size*directionalVelocityChannel.strideSize; 407 i < c; 408 s += strengthChannel.strideSize, i +=directionalVelocityChannel.strideSize, 409 a += angularChannel.strideSize, l += lifeChannel.strideSize, positionOffset += positionChannel.strideSize ){ 410 411 float lifePercent = lifeChannel.data[l], 412 strength = strengthChannel.data[s + ParticleChannels.VelocityStrengthStartOffset] + 413 strengthChannel.data[s + ParticleChannels.VelocityStrengthDiffOffset]* strengthValue.getScale(lifePercent), 414 phi = angularChannel.data[a + ParticleChannels.VelocityPhiStartOffset] + 415 angularChannel.data[a + ParticleChannels.VelocityPhiDiffOffset]* phiValue.getScale(lifePercent), 416 theta = angularChannel.data[a + ParticleChannels.VelocityThetaStartOffset] + 417 angularChannel.data[a + ParticleChannels.VelocityThetaDiffOffset]* thetaValue.getScale(lifePercent); 418 419 float cosTheta = MathUtils.cosDeg(theta), sinTheta = MathUtils.sinDeg(theta), 420 cosPhi = MathUtils.cosDeg(phi), sinPhi = MathUtils.sinDeg(phi); 421 TMP_V3.set(cosTheta *sinPhi, cosPhi, sinTheta*sinPhi) 422 .crs( positionChannel.data[positionOffset +ParticleChannels.XOffset], 423 positionChannel.data[positionOffset +ParticleChannels.YOffset], 424 positionChannel.data[positionOffset +ParticleChannels.ZOffset]) 425 .nor().scl(strength); 426 directionalVelocityChannel.data[i +ParticleChannels.XOffset] += TMP_V3.x; 427 directionalVelocityChannel.data[i +ParticleChannels.YOffset] += TMP_V3.y; 428 directionalVelocityChannel.data[i +ParticleChannels.ZOffset] += TMP_V3.z; 429 } 430 } 431 432 @Override 433 public TangentialAcceleration copy () { 434 return new TangentialAcceleration(this); 435 } 436 } 437 438 public static class BrownianAcceleration extends Strength { 439 FloatChannel accelerationChannel; 440 public BrownianAcceleration(){} 441 442 public BrownianAcceleration (BrownianAcceleration rotation) { 443 super(rotation); 444 } 445 446 @Override 447 public void allocateChannels() { 448 super.allocateChannels(); 449 accelerationChannel = controller.particles.addChannel(ParticleChannels.Acceleration); 450 } 451 452 @Override 453 public void update () { 454 int lifeOffset=ParticleChannels.LifePercentOffset, strengthOffset = 0, forceOffset = 0; 455 for(int i=0, c= controller.particles.size; i < c; ++i, 456 strengthOffset += strengthChannel.strideSize, 457 forceOffset +=accelerationChannel.strideSize, 458 lifeOffset += lifeChannel.strideSize){ 459 460 float strength = strengthChannel.data[strengthOffset + ParticleChannels.VelocityStrengthStartOffset] + 461 strengthChannel.data[strengthOffset + ParticleChannels.VelocityStrengthDiffOffset]* strengthValue.getScale(lifeChannel.data[lifeOffset]); 462 TMP_V3.set(MathUtils.random(-1, 1f), MathUtils.random(-1, 1f), MathUtils.random(-1, 1f)).nor().scl(strength); 463 accelerationChannel.data[forceOffset +ParticleChannels.XOffset] += TMP_V3.x; 464 accelerationChannel.data[forceOffset +ParticleChannels.YOffset] += TMP_V3.y; 465 accelerationChannel.data[forceOffset +ParticleChannels.ZOffset] += TMP_V3.z; 466 } 467 } 468 469 @Override 470 public BrownianAcceleration copy () { 471 return new BrownianAcceleration(this); 472 } 473 } 474 475 476 public boolean isGlobal = false; 477 protected FloatChannel lifeChannel; 478 479 public DynamicsModifier(){} 480 481 public DynamicsModifier (DynamicsModifier modifier) { 482 this.isGlobal = modifier.isGlobal; 483 } 484 485 @Override 486 public void allocateChannels() { 487 lifeChannel = controller.particles.addChannel(ParticleChannels.Life); 488 } 489 490 @Override 491 public void write (Json json) { 492 super.write(json); 493 json.writeValue("isGlobal", isGlobal); 494 } 495 496 @Override 497 public void read (Json json, JsonValue jsonData) { 498 super.read(json, jsonData); 499 isGlobal = json.readValue("isGlobal", boolean.class, jsonData); 500 } 501 502 } 503 504