/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics.g3d.particles.influencers; import com.badlogic.gdx.graphics.g3d.particles.ParallelArray.FloatChannel; import com.badlogic.gdx.graphics.g3d.particles.ParticleChannels; import com.badlogic.gdx.graphics.g3d.particles.ParticleControllerComponent; import com.badlogic.gdx.graphics.g3d.particles.values.ScaledNumericValue; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Json; import com.badlogic.gdx.utils.JsonValue; /** It's the base class for any kind of influencer which operates on angular velocity and acceleration of the particles. All the * classes that will inherit this base class can and should be used only as sub-influencer of an instance of * {@link DynamicsInfluencer} . * @author Inferno */ public abstract class DynamicsModifier extends Influencer { protected static final Vector3 TMP_V1 = new Vector3(), TMP_V2 = new Vector3(), TMP_V3 = new Vector3(); protected static final Quaternion TMP_Q = new Quaternion(); public static class FaceDirection extends DynamicsModifier { FloatChannel rotationChannel, accellerationChannel; public FaceDirection () { } public FaceDirection (FaceDirection rotation) { super(rotation); } @Override public void allocateChannels () { rotationChannel = controller.particles.addChannel(ParticleChannels.Rotation3D); accellerationChannel = controller.particles.addChannel(ParticleChannels.Acceleration); } @Override public void update () { for (int i = 0, accelOffset = 0, c = i + controller.particles.size * rotationChannel.strideSize; i < c; i += rotationChannel.strideSize, accelOffset += accellerationChannel.strideSize) { Vector3 axisZ = TMP_V1.set(accellerationChannel.data[accelOffset + ParticleChannels.XOffset], accellerationChannel.data[accelOffset + ParticleChannels.YOffset], accellerationChannel.data[accelOffset + ParticleChannels.ZOffset]).nor(), axisY = TMP_V2.set(TMP_V1) .crs(Vector3.Y).nor().crs(TMP_V1).nor(), axisX = TMP_V3.set(axisY).crs(axisZ).nor(); TMP_Q.setFromAxes(false, axisX.x, axisY.x, axisZ.x, axisX.y, axisY.y, axisZ.y, axisX.z, axisY.z, axisZ.z); rotationChannel.data[i + ParticleChannels.XOffset] = TMP_Q.x; rotationChannel.data[i + ParticleChannels.YOffset] = TMP_Q.y; rotationChannel.data[i + ParticleChannels.ZOffset] = TMP_Q.z; rotationChannel.data[i + ParticleChannels.WOffset] = TMP_Q.w; } } @Override public ParticleControllerComponent copy () { return new FaceDirection(this); } } public static abstract class Strength extends DynamicsModifier { protected FloatChannel strengthChannel; public ScaledNumericValue strengthValue; public Strength () { strengthValue = new ScaledNumericValue(); } public Strength (Strength rotation) { super(rotation); strengthValue = new ScaledNumericValue(); strengthValue.load(rotation.strengthValue); } @Override public void allocateChannels () { super.allocateChannels(); ParticleChannels.Interpolation.id = controller.particleChannels.newId(); strengthChannel = controller.particles.addChannel(ParticleChannels.Interpolation); } @Override public void activateParticles (int startIndex, int count) { float start, diff; for (int i = startIndex * strengthChannel.strideSize, c = i + count * strengthChannel.strideSize; i < c; i += strengthChannel.strideSize) { start = strengthValue.newLowValue(); diff = strengthValue.newHighValue(); if (!strengthValue.isRelative()) diff -= start; strengthChannel.data[i + ParticleChannels.VelocityStrengthStartOffset] = start; strengthChannel.data[i + ParticleChannels.VelocityStrengthDiffOffset] = diff; } } @Override public void write (Json json) { super.write(json); json.writeValue("strengthValue", strengthValue); } @Override public void read (Json json, JsonValue jsonData) { super.read(json, jsonData); strengthValue = json.readValue("strengthValue", ScaledNumericValue.class, jsonData); } } public static abstract class Angular extends Strength { protected FloatChannel angularChannel; /** Polar angle, XZ plane */ public ScaledNumericValue thetaValue; /** Azimuth, Y */ public ScaledNumericValue phiValue; public Angular () { thetaValue = new ScaledNumericValue(); phiValue = new ScaledNumericValue(); } public Angular (Angular value) { super(value); thetaValue = new ScaledNumericValue(); phiValue = new ScaledNumericValue(); thetaValue.load(value.thetaValue); phiValue.load(value.phiValue); } @Override public void allocateChannels () { super.allocateChannels(); ParticleChannels.Interpolation4.id = controller.particleChannels.newId(); angularChannel = controller.particles.addChannel(ParticleChannels.Interpolation4); } @Override public void activateParticles (int startIndex, int count) { super.activateParticles(startIndex, count); float start, diff; for (int i = startIndex * angularChannel.strideSize, c = i + count * angularChannel.strideSize; i < c; i += angularChannel.strideSize) { // Theta start = thetaValue.newLowValue(); diff = thetaValue.newHighValue(); if (!thetaValue.isRelative()) diff -= start; angularChannel.data[i + ParticleChannels.VelocityThetaStartOffset] = start; angularChannel.data[i + ParticleChannels.VelocityThetaDiffOffset] = diff; // Phi start = phiValue.newLowValue(); diff = phiValue.newHighValue(); if (!phiValue.isRelative()) diff -= start; angularChannel.data[i + ParticleChannels.VelocityPhiStartOffset] = start; angularChannel.data[i + ParticleChannels.VelocityPhiDiffOffset] = diff; } } @Override public void write (Json json) { super.write(json); json.writeValue("thetaValue", thetaValue); json.writeValue("phiValue", phiValue); } @Override public void read (Json json, JsonValue jsonData) { super.read(json, jsonData); thetaValue = json.readValue("thetaValue", ScaledNumericValue.class, jsonData); phiValue = json.readValue("phiValue", ScaledNumericValue.class, jsonData); } } public static class Rotational2D extends Strength { FloatChannel rotationalVelocity2dChannel; public Rotational2D () { } public Rotational2D (Rotational2D rotation) { super(rotation); } @Override public void allocateChannels () { super.allocateChannels(); rotationalVelocity2dChannel = controller.particles.addChannel(ParticleChannels.AngularVelocity2D); } @Override public void update () { for (int i = 0, l = ParticleChannels.LifePercentOffset, s = 0, c = i + controller.particles.size * rotationalVelocity2dChannel.strideSize; i < c; s += strengthChannel.strideSize, i += rotationalVelocity2dChannel.strideSize, l += lifeChannel.strideSize) { rotationalVelocity2dChannel.data[i] += strengthChannel.data[s + ParticleChannels.VelocityStrengthStartOffset] + strengthChannel.data[s + ParticleChannels.VelocityStrengthDiffOffset] * strengthValue.getScale(lifeChannel.data[l]); } } @Override public Rotational2D copy () { return new Rotational2D(this); } } public static class Rotational3D extends Angular { FloatChannel rotationChannel, rotationalForceChannel; public Rotational3D () { } public Rotational3D (Rotational3D rotation) { super(rotation); } @Override public void allocateChannels () { super.allocateChannels(); rotationChannel = controller.particles.addChannel(ParticleChannels.Rotation3D); rotationalForceChannel = controller.particles.addChannel(ParticleChannels.AngularVelocity3D); } @Override public void update () { // Matrix3 I_t = defined by the shape, it's the inertia tensor // Vector3 r = position vector // Vector3 L = r.cross(v.mul(m)), It's the angular momentum, where mv it's the linear momentum // Inverse(I_t) = a diagonal matrix where the diagonal is IyIz, IxIz, IxIy // Vector3 w = L/I_t = inverse(I_t)*L, It's the angular velocity // Quaternion spin = 0.5f*Quaternion(w, 0)*currentRotation // currentRotation += spin*dt // normalize(currentRotation) // Algorithm 1 // Consider a simple channel which represent an angular velocity w // Sum each w for each rotation // Update rotation // Algorithm 2 // Consider a channel which represent a sort of angular momentum L (r, v) // Sum each L for each rotation // Multiply sum by constant quantity k = m*I_to(-1) , m could be optional while I is constant and can be calculated at // start // Update rotation // Algorithm 3 // Consider a channel which represent a simple angular momentum L // Proceed as Algorithm 2 for (int i = 0, l = ParticleChannels.LifePercentOffset, s = 0, a = 0, c = controller.particles.size * rotationalForceChannel.strideSize; i < c; s += strengthChannel.strideSize, i += rotationalForceChannel.strideSize, a += angularChannel.strideSize, l += lifeChannel.strideSize) { float lifePercent = lifeChannel.data[l], strength = strengthChannel.data[s + ParticleChannels.VelocityStrengthStartOffset] + strengthChannel.data[s + ParticleChannels.VelocityStrengthDiffOffset] * strengthValue.getScale(lifePercent), phi = angularChannel.data[a + ParticleChannels.VelocityPhiStartOffset] + angularChannel.data[a + ParticleChannels.VelocityPhiDiffOffset] * phiValue.getScale(lifePercent), theta = angularChannel.data[a + ParticleChannels.VelocityThetaStartOffset] + angularChannel.data[a + ParticleChannels.VelocityThetaDiffOffset] * thetaValue.getScale(lifePercent); float cosTheta = MathUtils.cosDeg(theta), sinTheta = MathUtils.sinDeg(theta), cosPhi = MathUtils.cosDeg(phi), sinPhi = MathUtils .sinDeg(phi); TMP_V3.set(cosTheta * sinPhi, cosPhi, sinTheta * sinPhi); TMP_V3.scl(strength * MathUtils.degreesToRadians); rotationalForceChannel.data[i + ParticleChannels.XOffset] += TMP_V3.x; rotationalForceChannel.data[i + ParticleChannels.YOffset] += TMP_V3.y; rotationalForceChannel.data[i + ParticleChannels.ZOffset] += TMP_V3.z; } } @Override public Rotational3D copy () { return new Rotational3D(this); } } public static class CentripetalAcceleration extends Strength { FloatChannel accelerationChannel; FloatChannel positionChannel; public CentripetalAcceleration () { } public CentripetalAcceleration (CentripetalAcceleration rotation) { super(rotation); } @Override public void allocateChannels () { super.allocateChannels(); accelerationChannel = controller.particles.addChannel(ParticleChannels.Acceleration); positionChannel = controller.particles.addChannel(ParticleChannels.Position); } @Override public void update () { float cx = 0, cy = 0, cz = 0; if (!isGlobal) { float[] val = controller.transform.val; cx = val[Matrix4.M03]; cy = val[Matrix4.M13]; cz = val[Matrix4.M23]; } int lifeOffset = ParticleChannels.LifePercentOffset, strengthOffset = 0, positionOffset = 0, forceOffset = 0; for (int i = 0, c = controller.particles.size; i < c; ++i, positionOffset += positionChannel.strideSize, strengthOffset += strengthChannel.strideSize, forceOffset += accelerationChannel.strideSize, lifeOffset += lifeChannel.strideSize) { float strength = strengthChannel.data[strengthOffset + ParticleChannels.VelocityStrengthStartOffset] + strengthChannel.data[strengthOffset + ParticleChannels.VelocityStrengthDiffOffset] * strengthValue.getScale(lifeChannel.data[lifeOffset]); TMP_V3 .set(positionChannel.data[positionOffset + ParticleChannels.XOffset] - cx, positionChannel.data[positionOffset + ParticleChannels.YOffset] - cy, positionChannel.data[positionOffset + ParticleChannels.ZOffset] - cz).nor().scl(strength); accelerationChannel.data[forceOffset + ParticleChannels.XOffset] += TMP_V3.x; accelerationChannel.data[forceOffset + ParticleChannels.YOffset] += TMP_V3.y; accelerationChannel.data[forceOffset + ParticleChannels.ZOffset] += TMP_V3.z; } } @Override public CentripetalAcceleration copy () { return new CentripetalAcceleration(this); } } public static class PolarAcceleration extends Angular { FloatChannel directionalVelocityChannel; public PolarAcceleration () { } public PolarAcceleration (PolarAcceleration rotation) { super(rotation); } @Override public void allocateChannels () { super.allocateChannels(); directionalVelocityChannel = controller.particles.addChannel(ParticleChannels.Acceleration); } @Override public void update () { for (int i = 0, l = ParticleChannels.LifePercentOffset, s = 0, a = 0, c = i + controller.particles.size * directionalVelocityChannel.strideSize; i < c; s += strengthChannel.strideSize, i += directionalVelocityChannel.strideSize, a += angularChannel.strideSize, l += lifeChannel.strideSize) { float lifePercent = lifeChannel.data[l], strength = strengthChannel.data[s + ParticleChannels.VelocityStrengthStartOffset] + strengthChannel.data[s + ParticleChannels.VelocityStrengthDiffOffset] * strengthValue.getScale(lifePercent), phi = angularChannel.data[a + ParticleChannels.VelocityPhiStartOffset] + angularChannel.data[a + ParticleChannels.VelocityPhiDiffOffset] * phiValue.getScale(lifePercent), theta = angularChannel.data[a + ParticleChannels.VelocityThetaStartOffset] + angularChannel.data[a + ParticleChannels.VelocityThetaDiffOffset] * thetaValue.getScale(lifePercent); float cosTheta = MathUtils.cosDeg(theta), sinTheta = MathUtils.sinDeg(theta), cosPhi = MathUtils.cosDeg(phi), sinPhi = MathUtils .sinDeg(phi); TMP_V3.set(cosTheta * sinPhi, cosPhi, sinTheta * sinPhi).nor().scl(strength); directionalVelocityChannel.data[i + ParticleChannels.XOffset] += TMP_V3.x; directionalVelocityChannel.data[i + ParticleChannels.YOffset] += TMP_V3.y; directionalVelocityChannel.data[i + ParticleChannels.ZOffset] += TMP_V3.z; } } @Override public PolarAcceleration copy () { return new PolarAcceleration(this); } } public static class TangentialAcceleration extends Angular { FloatChannel directionalVelocityChannel, positionChannel; public TangentialAcceleration () { } public TangentialAcceleration (TangentialAcceleration rotation) { super(rotation); } @Override public void allocateChannels () { super.allocateChannels(); directionalVelocityChannel = controller.particles.addChannel(ParticleChannels.Acceleration); positionChannel = controller.particles.addChannel(ParticleChannels.Position); } @Override public void update () { for (int i = 0, l = ParticleChannels.LifePercentOffset, s = 0, a = 0, positionOffset = 0, c = i + controller.particles.size * directionalVelocityChannel.strideSize; i < c; s += strengthChannel.strideSize, i += directionalVelocityChannel.strideSize, a += angularChannel.strideSize, l += lifeChannel.strideSize, positionOffset += positionChannel.strideSize) { float lifePercent = lifeChannel.data[l], strength = strengthChannel.data[s + ParticleChannels.VelocityStrengthStartOffset] + strengthChannel.data[s + ParticleChannels.VelocityStrengthDiffOffset] * strengthValue.getScale(lifePercent), phi = angularChannel.data[a + ParticleChannels.VelocityPhiStartOffset] + angularChannel.data[a + ParticleChannels.VelocityPhiDiffOffset] * phiValue.getScale(lifePercent), theta = angularChannel.data[a + ParticleChannels.VelocityThetaStartOffset] + angularChannel.data[a + ParticleChannels.VelocityThetaDiffOffset] * thetaValue.getScale(lifePercent); float cosTheta = MathUtils.cosDeg(theta), sinTheta = MathUtils.sinDeg(theta), cosPhi = MathUtils.cosDeg(phi), sinPhi = MathUtils .sinDeg(phi); TMP_V3 .set(cosTheta * sinPhi, cosPhi, sinTheta * sinPhi) .crs(positionChannel.data[positionOffset + ParticleChannels.XOffset], positionChannel.data[positionOffset + ParticleChannels.YOffset], positionChannel.data[positionOffset + ParticleChannels.ZOffset]).nor().scl(strength); directionalVelocityChannel.data[i + ParticleChannels.XOffset] += TMP_V3.x; directionalVelocityChannel.data[i + ParticleChannels.YOffset] += TMP_V3.y; directionalVelocityChannel.data[i + ParticleChannels.ZOffset] += TMP_V3.z; } } @Override public TangentialAcceleration copy () { return new TangentialAcceleration(this); } } public static class BrownianAcceleration extends Strength { FloatChannel accelerationChannel; public BrownianAcceleration () { } public BrownianAcceleration (BrownianAcceleration rotation) { super(rotation); } @Override public void allocateChannels () { super.allocateChannels(); accelerationChannel = controller.particles.addChannel(ParticleChannels.Acceleration); } @Override public void update () { int lifeOffset = ParticleChannels.LifePercentOffset, strengthOffset = 0, forceOffset = 0; for (int i = 0, c = controller.particles.size; i < c; ++i, strengthOffset += strengthChannel.strideSize, forceOffset += accelerationChannel.strideSize, lifeOffset += lifeChannel.strideSize) { float strength = strengthChannel.data[strengthOffset + ParticleChannels.VelocityStrengthStartOffset] + strengthChannel.data[strengthOffset + ParticleChannels.VelocityStrengthDiffOffset] * strengthValue.getScale(lifeChannel.data[lifeOffset]); TMP_V3.set(MathUtils.random(-1, 1f), MathUtils.random(-1, 1f), MathUtils.random(-1, 1f)).nor().scl(strength); accelerationChannel.data[forceOffset + ParticleChannels.XOffset] += TMP_V3.x; accelerationChannel.data[forceOffset + ParticleChannels.YOffset] += TMP_V3.y; accelerationChannel.data[forceOffset + ParticleChannels.ZOffset] += TMP_V3.z; } } @Override public BrownianAcceleration copy () { return new BrownianAcceleration(this); } } public boolean isGlobal = false; protected FloatChannel lifeChannel; public DynamicsModifier () { } public DynamicsModifier (DynamicsModifier modifier) { this.isGlobal = modifier.isGlobal; } @Override public void allocateChannels () { lifeChannel = controller.particles.addChannel(ParticleChannels.Life); } @Override public void write (Json json) { super.write(json); json.writeValue("isGlobal", isGlobal); } @Override public void read (Json json, JsonValue jsonData) { super.read(json, jsonData); isGlobal = json.readValue("isGlobal", boolean.class, jsonData); } }