All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.gurkenlabs.litiengine.graphics.emitters.particles.Particle Maven / Gradle / Ivy

The newest version!
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 - 2024 Weber Informatics LLC | Privacy Policy