
de.gurkenlabs.litiengine.graphics.emitters.particles.Particle Maven / Gradle / Ivy
package de.gurkenlabs.litiengine.graphics.emitters.particles;
import de.gurkenlabs.litiengine.Game;
import de.gurkenlabs.litiengine.ITimeToLive;
import de.gurkenlabs.litiengine.graphics.RenderType;
import de.gurkenlabs.litiengine.graphics.emitters.Emitter;
import de.gurkenlabs.litiengine.graphics.emitters.xml.EmitterData;
import de.gurkenlabs.litiengine.physics.Collision;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public abstract class Particle implements ITimeToLive {
private static final Color DEFAULT_COLOR = Color.BLACK;
private long aliveTick;
private long aliveTime;
private float angle;
private float deltaAngle;
private Collision collisionType;
private Color color = DEFAULT_COLOR;
private float deltaHeight;
private float deltaWidth;
/**
* The horizontal velocity (horizontal movement per update) for this particle.
*/
private float velocityX;
/**
* The vertical velocity (vertical movement per update) for this particle.
*/
private float velocityY;
private float outlineThickness;
private boolean outlineOnly;
private boolean antiAliasing;
/**
* The horizontal acceleration (increase / decrease in velocity over time) for this particle.
*/
private float accelerationX;
/**
* The vertical acceleration (increase / decrease in velocity over time) for this particle.
*/
private float accelerationY;
private float height;
private int timeToLive;
private float width;
/**
* The current location of the particle on the X-axis.
*/
private float x;
/**
* The current location of the particle on the Y-axis.
*/
private float y;
private RenderType customRenderType = RenderType.NONE;
private boolean useCustomRenderType;
private boolean fade;
private boolean fadeOnCollision;
private boolean colliding;
private boolean continuousCollision;
private boolean stopOnCollision;
/**
* Constructs a new particle.
*
* @param width the particle width in pixels
* @param height the particle height in pixels
*/
protected Particle(final float width, final float height) {
this.setWidth(width);
this.setHeight(height);
this.collisionType = Collision.NONE;
this.fade = true;
this.setStopOnCollision(true);
this.setContinuousCollision(false);
}
@Override
public long getAliveTime() {
return aliveTime;
}
/**
* Gets the current bounding box of the particle, depending on its spawn location.
*
* @param origin the spawn location of this particle
* @return The Rectangular particle bounding box.
*/
public Rectangle2D getBoundingBox(final Point2D origin) {
return new Rectangle2D.Double(origin.getX() + this.getX(), origin.getY() + this.getY(),
this.getWidth(), this.getHeight());
}
public Collision getCollisionType() {
return collisionType;
}
public Color getColor() {
return color;
}
public float getDeltaHeight() {
return deltaHeight;
}
public float getDeltaWidth() {
return deltaWidth;
}
public float getVelocityX() {
return velocityX;
}
public float getVelocityY() {
return velocityY;
}
public float getAccelerationX() {
return accelerationX;
}
public float getAccelerationY() {
return accelerationY;
}
public float getAngle() {
return angle;
}
public float getDeltaAngle() {
return deltaAngle;
}
public float getHeight() {
return height;
}
public float getOutlineThickness() {
return outlineThickness;
}
public boolean isOutlineOnly() {
return outlineOnly;
}
public boolean isAntiAliased() {
return antiAliasing;
}
public float getOpacity() {
if (isFading() && getTimeToLive() > 0) {
float maxAlpha = getColor().getAlpha() / 255f;
float progress = (float) getAliveTime() / getTimeToLive();
return Math.clamp(maxAlpha - progress * maxAlpha, 0, 1);
}
return 1;
}
/**
* Gets the location relative to the specified effect location.
*
* @param effectLocation the effect position
* @return the location
*/
public Point2D getRenderLocation(Point2D effectLocation) {
// if we have a camera, we need to render the particle relative to the
// viewport
Point2D newEffectLocation =
Game.screens() != null ? Game.world().camera().getViewportLocation(effectLocation)
: effectLocation;
return getAbsoluteLocation(newEffectLocation);
}
public RenderType getCustomRenderType() {
return customRenderType;
}
@Override
public int getTimeToLive() {
return timeToLive;
}
public float getWidth() {
return width;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public boolean isFading() {
return fade;
}
public boolean isFadingOnCollision() {
return fadeOnCollision;
}
public boolean isContinuousCollisionEnabled() {
return continuousCollision;
}
public boolean isStoppingOnCollision() {
return stopOnCollision;
}
public abstract void render(final Graphics2D g, final Point2D emitterOrigin);
public Particle setCollisionType(final Collision collisionType) {
this.collisionType = collisionType;
return this;
}
/**
* Enabling this check can be very performance hungry and should be used with caution and only for a small amount of particles.
*
* @param ccd If set to true, the collision will be checked continuously by a ray-cast approximation.
* @return This particle instance.
*/
public Particle setContinuousCollision(boolean ccd) {
this.continuousCollision = ccd;
return this;
}
public Particle setStopOnCollision(boolean stopOnCollision) {
this.stopOnCollision = stopOnCollision;
return this;
}
public Particle setColor(final Color color) {
if (color != null) {
this.color = color;
}
return this;
}
public Particle setDeltaHeight(final float deltaHeight) {
this.deltaHeight = deltaHeight;
return this;
}
public Particle setAccelerationX(final float accelerationX) {
this.accelerationX = accelerationX;
return this;
}
public Particle setAccelerationY(final float accelerationY) {
this.accelerationY = accelerationY;
return this;
}
public Particle setAngle(final float angle) {
this.angle = angle;
return this;
}
public Particle setDeltaAngle(final float deltaAngle) {
this.deltaAngle = deltaAngle;
return this;
}
public Particle setDeltaWidth(final float deltaWidth) {
this.deltaWidth = deltaWidth;
return this;
}
public Particle setVelocityX(final float velocityX) {
this.velocityX = velocityX;
return this;
}
public Particle setVelocityY(final float velocityY) {
this.velocityY = velocityY;
return this;
}
public Particle setFade(boolean fade) {
this.fade = fade;
return this;
}
public Particle setFadeOnCollision(boolean fadeOnCollision) {
this.fadeOnCollision = fadeOnCollision;
return this;
}
public Particle setHeight(final float height) {
this.height = height;
return this;
}
public Particle setOutlineThickness(final float outlineThickness) {
this.outlineThickness = outlineThickness;
return this;
}
public Particle setOutlineOnly(final boolean outlineOnly) {
this.outlineOnly = outlineOnly;
return this;
}
public Particle setAntiAliasing(final boolean antiAliasing) {
this.antiAliasing = antiAliasing;
return this;
}
public Particle setCustomRenderType(RenderType renderType) {
this.customRenderType = renderType;
this.useCustomRenderType = true;
return this;
}
public Particle setWidth(final float width) {
this.width = width;
return this;
}
public Particle setX(final float x) {
this.x = x;
return this;
}
public Particle setY(final float y) {
this.y = y;
return this;
}
public Particle setTimeToLive(final int ttl) {
this.timeToLive = ttl;
return this;
}
public Particle init(final EmitterData data) {
this.setX((float) data.getParticleOffsetX().get());
this.setY((float) data.getParticleOffsetY().get());
this.setAccelerationX((float) data.getAccelerationX().get());
this.setAccelerationY((float) data.getAccelerationY().get());
this.setVelocityX((float) data.getVelocityX().get());
this.setVelocityY((float) data.getVelocityY().get());
this.setDeltaWidth((float) data.getDeltaWidth().get());
this.setDeltaHeight((float) data.getDeltaHeight().get());
this.setAngle((float) data.getAngle().get());
this.setDeltaAngle((float) data.getDeltaAngle().get());
this.setTimeToLive((int) data.getParticleTTL().get());
this.setColor(Game.random().choose(data.getDecodedColors()));
this.setOutlineThickness((float) data.getOutlineThickness().get());
this.setCollisionType(data.getCollision());
this.setOutlineOnly(data.isOutlineOnly());
this.setAntiAliasing(data.isAntiAliased());
this.setFade(data.isFading());
this.setFadeOnCollision(data.isFadingOnCollision());
return this;
}
@Override
public boolean timeToLiveReached() {
return getTimeToLive() > 0 && this.getAliveTime() >= this.getTimeToLive();
}
/**
* Updates the effect's position, change in xCurrent, change in yCurrent, remaining lifetime, and color.
*
* @param emitterOrigin The current {@link Emitter} origin
* @param updateRatio The update ratio for this particle.
*/
public void update(final Point2D emitterOrigin, final float updateRatio) {
if (this.aliveTick == 0) {
this.aliveTick = Game.time().now();
}
this.aliveTime = Game.time().since(this.aliveTick);
if (this.timeToLiveReached()) {
return;
}
if (this.colliding) {
return;
}
if (this.getDeltaWidth() != 0) {
this.width += this.getDeltaWidth() * updateRatio;
}
if (this.getDeltaHeight() != 0) {
this.height += this.getDeltaHeight() * updateRatio;
}
if (this.getDeltaAngle() != 0) {
this.angle += this.getDeltaAngle() * updateRatio;
}
if (hasRayCastCollision(emitterOrigin, updateRatio)) {
return;
}
if (this.getAccelerationX() != 0) {
this.velocityX += this.getAccelerationX() * updateRatio;
}
if (this.getAccelerationY() != 0) {
this.velocityY += this.getAccelerationY() * updateRatio;
}
}
/**
* Test for ray cast collisions
*
* @param emitterOrigin The current {@link Emitter} origin
* @param updateRatio The update ratio for this particle.
* @return True if ray cast collision occurs
*/
protected boolean hasRayCastCollision(final Point2D emitterOrigin, final float updateRatio) {
final float targetX = this.x + this.getVelocityX() * updateRatio;
final float targetY = this.y + this.getVelocityY() * updateRatio;
if (targetX == this.x && targetY == this.y) {
return true;
}
if (this.checkForCollision(emitterOrigin, targetX, targetY)) {
return true;
}
if (this.getVelocityX() != 0) {
this.x = targetX;
}
if (this.getVelocityY() != 0) {
this.y = targetY;
}
return false;
}
private boolean checkForCollision(final Point2D emitterOrigin, float targetX, float targetY) {
if (this.isStoppingOnCollision() && this.colliding) {
return true;
}
if (this.isContinuousCollisionEnabled()) {
Point2D start = this.getAbsoluteLocation(emitterOrigin);
double endX = emitterOrigin.getX() + targetX;
double endY = emitterOrigin.getY() + targetY;
Line2D ray = new Line2D.Double(start.getX(), start.getY(), endX, endY);
if (this.getCollisionType() != Collision.NONE && Game.physics() != null && Game.physics()
.collides(ray, this.getCollisionType())) {
collide();
return true;
}
} else if (this.getCollisionType() != Collision.NONE && Game.physics() != null && Game.physics()
.collides(this.getBoundingBox(emitterOrigin).getBounds2D(), this.getCollisionType())) {
collide();
return true;
}
return false;
}
private void collide() {
if (!this.colliding) {
this.colliding = true;
if (this.isFadingOnCollision()) {
this.setFade(true);
}
}
}
public Point2D getAbsoluteLocation(final Point2D effectLocation) {
return new Point2D.Float(getAbsoluteX(effectLocation), getAbsoluteY(effectLocation));
}
protected float getAbsoluteX(Point2D emitterOrigin) {
return (float) (emitterOrigin.getX() + this.getX() - this.getWidth() / 2.0);
}
protected float getAbsoluteY(Point2D emitterOrigin) {
return (float) (emitterOrigin.getY() + this.getY() - this.getHeight() / 2.0);
}
public boolean usesCustomRenderType() {
return useCustomRenderType;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy