Package box2dLight

Source Code of box2dLight.ChainLight

package box2dLight;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Mesh.VertexDataType;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix3;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.Pools;

/**
* A light whose ray starting points are evenly distributed along a chain of
* vertices
*
* <p> Extends {@link Light}
*
* @author spruce
*/
public class ChainLight extends Light {
 
  public static float defaultRayStartOffset = 0.001f;
  public float rayStartOffset;
  public final FloatArray chain;

  protected int rayDirection;
  protected float bodyAngle;
  protected float bodyAngleOffset;

  protected Body body;

  protected final FloatArray segmentAngles = new FloatArray();
  protected final FloatArray segmentLengths = new FloatArray();

  protected final float[] startX;
  protected final float[] startY;
  protected final float[] endX;
  protected final float[] endY;

  protected final Vector2 bodyPosition = new Vector2();
  protected final Vector2 tmpEnd = new Vector2();
  protected final Vector2 tmpStart = new Vector2();
  protected final Vector2 tmpPerp = new Vector2();
  protected final Vector2 tmpVec = new Vector2();

  protected final Matrix3 zeroPosition = new Matrix3();
  protected final Matrix3 rotateAroundZero = new Matrix3();
  protected final Matrix3 restorePosition = new Matrix3();

  protected final Rectangle chainLightBounds = new Rectangle();
  protected final Rectangle rayHandlerBounds = new Rectangle();
 
  /**
   * Creates chain light without vertices, they can be added any time later
   *
   * @param rayHandler
   *            not {@code null} instance of RayHandler
   * @param rays
   *            number of rays - more rays make light to look more realistic
   *            but will decrease performance, can't be less than MIN_RAYS
   * @param color
   *            color, set to {@code null} to use the default color
   * @param distance
   *            distance of light
   * @param rayDirection
   *            direction of rays
   *            <ul>
   *            <li>1 = left</li>
   *            <li>-1 = right</li>
    *            </ul>
    */
  public ChainLight(RayHandler rayHandler, int rays, Color color,
      float distance, int rayDirection) {
    this(rayHandler, rays, color, distance, rayDirection, null);
  }

  /**
   * Creates chain light from specified vertices
   *
   * @param rayHandler
   *            not {@code null} instance of RayHandler
   * @param rays
   *            number of rays - more rays make light to look more realistic
   *            but will decrease performance, can't be less than MIN_RAYS
   * @param color
   *            color, set to {@code null} to use the default color
   * @param distance
   *            distance of light
   * @param rayDirection
   *            direction of rays
   *            <ul>
   *            <li>1 = left</li>
   *            <li>-1 = right</li>
   *            </ul>
   * @param chain
   *            float array of (x, y) vertices from which rays will be
   *            evenly distributed
   */
  public ChainLight(RayHandler rayHandler, int rays, Color color,
      float distance, int rayDirection, float[] chain) {
   
    super(rayHandler, rays, color, distance, 0f);
    rayStartOffset = ChainLight.defaultRayStartOffset;
    this.rayDirection = rayDirection;
    vertexNum = (vertexNum - 1) * 2;
    endX = new float[rays];
    endY = new float[rays];
    startX = new float[rays];
    startY = new float[rays];
    this.chain = (chain != null) ?
           new FloatArray(chain) : new FloatArray();
   
    lightMesh = new Mesh(
        VertexDataType.VertexArray, false, vertexNum, 0,
        new VertexAttribute(Usage.Position, 2, "vertex_positions"),
        new VertexAttribute(Usage.ColorPacked, 4, "quad_colors"),
        new VertexAttribute(Usage.Generic, 1, "s"));
    softShadowMesh = new Mesh(
        VertexDataType.VertexArray, false, vertexNum * 2,
        0, new VertexAttribute(Usage.Position, 2, "vertex_positions"),
        new VertexAttribute(Usage.ColorPacked, 4, "quad_colors"),
        new VertexAttribute(Usage.Generic, 1, "s"));
    setMesh();
  }
 
  @Override
  void update() {
    if (dirty) {
      updateChain();
      applyAttachment();
    } else {
      updateBody();
    }
   
    if (cull()) return;
    if (staticLight && !dirty) return;
    dirty = false;
   
    updateMesh();
  }
 
  @Override
  void render() {
    if (rayHandler.culling && culled) return;
   
    rayHandler.lightRenderedLastFrame++;
    lightMesh.render(
      rayHandler.lightShader, GL20.GL_TRIANGLE_STRIP, 0, vertexNum);
   
    if (soft && !xray) {
      softShadowMesh.render(
        rayHandler.lightShader, GL20.GL_TRIANGLE_STRIP, 0, vertexNum);
    }
  }
 
  /**
   * Draws a polygon, using ray start and end points as vertices
   */
  public void debugRender(ShapeRenderer shapeRenderer) {
    shapeRenderer.setColor(Color.YELLOW);
    FloatArray vertices = Pools.obtain(FloatArray.class);
    vertices.clear();
    for (int i = 0; i < rayNum; i++) {
      vertices.addAll(mx[i], my[i]);
    }
    for (int i = rayNum - 1; i > -1; i--) {
      vertices.addAll(startX[i], startY[i]);
    }
    shapeRenderer.polygon(vertices.shrink());
    Pools.free(vertices);
  }
 
  @Override
  public void attachToBody(Body body) {
    attachToBody(body, 0f);
  }
 
  /**
   * Attaches light to specified body with relative direction offset
   *
   * @param body
   *            that will be automatically followed, note that the body
   *            rotation angle is taken into account for the light offset
   *            and direction calculations
   * @param degrees
   *            directional relative offset in degrees
   */
  public void attachToBody(Body body, float degrees) {
    this.body = body;
    this.bodyPosition.set(body.getPosition());
    bodyAngleOffset = MathUtils.degreesToRadians * degrees;
    bodyAngle = body.getAngle();
    applyAttachment();
    if (staticLight) dirty = true;
  }
 
  @Override
  public Body getBody() {
    return body;
  }
 
  @Override
  public float getX() {
    return tmpPosition.x;
  }
 
  @Override
  public float getY() {
    return tmpPosition.y;
  }
 
  @Override
  public void setPosition(float x, float y) {
    tmpPosition.x = x;
    tmpPosition.y = y;
    if (staticLight) dirty = true;
  }
 
  @Override
  public void setPosition(Vector2 position) {
    tmpPosition.x = position.x;
    tmpPosition.y = position.y;
    if (staticLight) dirty = true;
  }
 
  @Override
  public boolean contains(float x, float y) {
    // fast fail
    if (!this.chainLightBounds.contains(x, y))
      return false;
    // actual check
    FloatArray vertices = Pools.obtain(FloatArray.class);
    vertices.clear();
   
    for (int i = 0; i < rayNum; i++) {
      vertices.addAll(mx[i], my[i]);
    }
    for (int i = rayNum - 1; i > -1; i--) {
      vertices.addAll(startX[i], startY[i]);
    }
   
    int intersects = 0;
    for (int i = 0; i < vertices.size; i += 2) {
      float x1 = vertices.items[i];
      float y1 = vertices.items[i + 1];
      float x2 = vertices.items[(i + 2) % vertices.size];
      float y2 = vertices.items[(i + 3) % vertices.size];
      if (((y1 <= y && y < y2) || (y2 <= y && y < y1)) &&
          x < ((x2 - x1) / (y2 - y1) * (y - y1) + x1))
        intersects++;
    }
    boolean result = (intersects & 1) == 1;

    Pools.free(vertices);
    return result;
  }
 
  /**
   * Sets light distance
   *
   * <p>MIN value capped to 0.1f meter
   * <p>Actual recalculations will be done only on {@link #update()} call
   */
  @Override
  public void setDistance(float dist) {
    dist *= RayHandler.gammaCorrectionParameter;
    this.distance = dist < 0.01f ? 0.01f : dist;
    dirty = true;
  }
 
  /** Not applicable for this light type **/
  @Deprecated
  @Override
  public void setDirection(float directionDegree) {
  }
 
  /**
   * Calculates ray positions and angles along chain. This should be called
   * any time the number or values of elements changes in {@link #chain}.
   */
  public void updateChain() {
    Vector2 v1 = Pools.obtain(Vector2.class);
    Vector2 v2 = Pools.obtain(Vector2.class);
    Vector2 vSegmentStart = Pools.obtain(Vector2.class);
    Vector2 vDirection = Pools.obtain(Vector2.class);
    Vector2 vRayOffset = Pools.obtain(Vector2.class);
    Spinor tmpAngle = Pools.obtain(Spinor.class);
    // Spinors used to represent perpendicular angle of each segment
    Spinor previousAngle = Pools.obtain(Spinor.class);
    Spinor currentAngle = Pools.obtain(Spinor.class);
    Spinor nextAngle = Pools.obtain(Spinor.class);
    // Spinors used to represent start, end and interpolated ray
    // angles for a given segment
    Spinor startAngle = Pools.obtain(Spinor.class);
    Spinor endAngle = Pools.obtain(Spinor.class);
    Spinor rayAngle = Pools.obtain(Spinor.class);
   
    int segmentCount = chain.size / 2 - 1;
   
    segmentAngles.clear();
    segmentLengths.clear();
    float remainingLength = 0;
   
    for (int i = 0, j = 0; i < chain.size - 2; i += 2, j++) {
      v1.set(chain.items[i + 2], chain.items[i + 3])
        .sub(chain.items[i], chain.items[i + 1]);
      segmentLengths.add(v1.len());
      segmentAngles.add(
        v1.rotate90(rayDirection).angle() * MathUtils.degreesToRadians
      );
      remainingLength += segmentLengths.items[j];
    }
   
    int rayNumber = 0;
    int remainingRays = rayNum;
   
    for (int i = 0; i < segmentCount; i++) {
      // get this and adjacent segment angles
      previousAngle.set(
        (i == 0) ?
        segmentAngles.items[i] : segmentAngles.items[i - 1]);
      currentAngle.set(segmentAngles.items[i]);
      nextAngle.set(
        (i == segmentAngles.size - 1) ?
        segmentAngles.items[i] : segmentAngles.items[i + 1]);
     
      // interpolate to find actual start and end angles
      startAngle.set(previousAngle).slerp(currentAngle, 0.5f);
      endAngle.set(currentAngle).slerp(nextAngle, 0.5f);

      int segmentVertex = i * 2;
      vSegmentStart.set(
        chain.items[segmentVertex], chain.items[segmentVertex + 1]);
      vDirection.set(
        chain.items[segmentVertex + 2], chain.items[segmentVertex + 3]
      ).sub(vSegmentStart).nor();

      float raySpacing = remainingLength / remainingRays;
      int segmentRays = (i == segmentCount - 1) ?
        remainingRays :
        (int) ((segmentLengths.items[i] / remainingLength) *
            remainingRays);
     
      for (int j = 0; j < segmentRays; j++) {
        float position = j * raySpacing;

        // interpolate ray angle based on position within segment
        rayAngle.set(startAngle).slerp(
          endAngle, position / segmentLengths.items[i]);
        float angle = rayAngle.angle();
        vRayOffset.set(this.rayStartOffset, 0).rotateRad(angle);
        v1.set(vDirection).scl(position).add(vSegmentStart).add(vRayOffset);
       
        this.startX[rayNumber] = v1.x;
        this.startY[rayNumber] = v1.y;
        v2.set(distance, 0).rotateRad(angle).add(v1);
        this.endX[rayNumber] = v2.x;
        this.endY[rayNumber] = v2.y;
        rayNumber++;
      }
     
      remainingRays -= segmentRays;
      remainingLength -= segmentLengths.items[i];
     
    }
   
    Pools.free(v1);
    Pools.free(v2);
    Pools.free(vSegmentStart);
    Pools.free(vDirection);
    Pools.free(vRayOffset);
    Pools.free(previousAngle);
    Pools.free(currentAngle);
    Pools.free(nextAngle);
    Pools.free(startAngle);
    Pools.free(endAngle);
    Pools.free(rayAngle);
    Pools.free(tmpAngle);
  }
 
  /**
   * Applies attached body initial transform to all lights rays
   */
  void applyAttachment() {
    if (body == null || staticLight) return;
   
    restorePosition.setToTranslation(bodyPosition);
    rotateAroundZero.setToRotationRad(bodyAngle + bodyAngleOffset);
    for (int i = 0; i < rayNum; i++) {
      tmpVec.set(startX[i], startY[i]).mul(rotateAroundZero).mul(restorePosition);
      startX[i] = tmpVec.x;
      startY[i] = tmpVec.y;
      tmpVec.set(endX[i], endY[i]).mul(rotateAroundZero).mul(restorePosition);
      endX[i] = tmpVec.x;
      endY[i] = tmpVec.y;
    }
  }
 
  protected boolean cull() {
    if (!rayHandler.culling) {
      culled = false;
    } else {
      updateBoundingRects();
      culled = chainLightBounds.width > 0 &&
           chainLightBounds.height > 0 &&
           !chainLightBounds.overlaps(rayHandlerBounds);
    }
    return culled;
  }
 
  void updateBody() {
    if (body == null || staticLight) return;
 
    final Vector2 vec = body.getPosition();
    tmpVec.set(0, 0).sub(bodyPosition);
    bodyPosition.set(vec);
    zeroPosition.setToTranslation(tmpVec);
    restorePosition.setToTranslation(bodyPosition);
    rotateAroundZero.setToRotationRad(bodyAngle).inv().rotateRad(body.getAngle());
    bodyAngle = body.getAngle();
   
    for (int i = 0; i < rayNum; i++) {
      tmpVec.set(startX[i], startY[i]).mul(zeroPosition).mul(rotateAroundZero)
        .mul(restorePosition);
      startX[i] = tmpVec.x;
      startY[i] = tmpVec.y;

      tmpVec.set(endX[i], endY[i]).mul(zeroPosition).mul(rotateAroundZero)
        .mul(restorePosition);
      endX[i] = tmpVec.x;
      endY[i] = tmpVec.y;
    }
  }
 
  protected void updateMesh() {
    for (int i = 0; i < rayNum; i++) {
      m_index = i;
      f[i] = 1f;
      tmpEnd.x = endX[i];
      mx[i] = tmpEnd.x;
      tmpEnd.y = endY[i];
      my[i] = tmpEnd.y;
      tmpStart.x = startX[i];
      tmpStart.y = startY[i];
      if (rayHandler.world != null && !xray) {
        rayHandler.world.rayCast(ray, tmpStart, tmpEnd);
      }
    }
    setMesh();
  }
 
  protected void setMesh() {
    int size = 0;
    for (int i = 0; i < rayNum; i++) {
      segments[size++] = startX[i];
      segments[size++] = startY[i];
      segments[size++] = colorF;
      segments[size++] = 1;
      segments[size++] = mx[i];
      segments[size++] = my[i];
      segments[size++] = colorF;
      segments[size++] = 1 - f[i];
    }
    lightMesh.setVertices(segments, 0, size);
    if (!soft || xray) return;

    size = 0;
    for (int i = 0; i < rayNum; i++) {
      segments[size++] = mx[i];
      segments[size++] = my[i];
      segments[size++] = colorF;
      final float s = (1 - f[i]);
      segments[size++] = s;
      tmpPerp.set(mx[i], my[i]).sub(startX[i], startY[i]).nor()
        .scl(softShadowLength * s).add(mx[i], my[i]);
      segments[size++] = tmpPerp.x;
      segments[size++] = tmpPerp.y;
      segments[size++] = zeroColorBits;
      segments[size++] = 0f;
    }
    softShadowMesh.setVertices(segments, 0, size);
  }
 
  /** Internal method for bounding rectangle recalculation **/
  protected void updateBoundingRects() {
    float maxX = startX[0];
    float minX = startX[0];
    float maxY = startY[0];
    float minY = startY[0];

    for (int i = 0; i < rayNum; i++) {
      maxX = maxX > startX[i] ? maxX : startX[i];
      maxX = maxX > mx[i] ? maxX : mx[i];
      minX = minX < startX[i] ? minX : startX[i];
      minX = minX < mx[i] ? minX : mx[i];
      maxY = maxY > startY[i] ? maxY : startY[i];
      maxY = maxY > my[i] ? maxY : my[i];
      minY = minY < startY[i] ? minY : startY[i];
      minY = minY < my[i] ? minY : my[i];
    }
    chainLightBounds.set(minX, minY, maxX - minX, maxY - minY);
    rayHandlerBounds.set(
      rayHandler.x1, rayHandler.y1,
      rayHandler.x2 - rayHandler.x1, rayHandler.y2 - rayHandler.y1);
  }
 
}
TOP

Related Classes of box2dLight.ChainLight

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.