package box2dLight;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Filter;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.RayCastCallback;
import com.badlogic.gdx.utils.Disposable;
/**
* Light is data container for all the light parameters. When created lights
* are automatically added to rayHandler and could be removed by calling
* {@link #remove()} and added manually by calling {@link #add(RayHandler)}.
*
* <p>Implements {@link Disposable}
*
* @author kalle_h
*/
public abstract class Light implements Disposable {
static final Color DefaultColor = new Color(0.75f, 0.75f, 0.5f, 0.75f);
static final float zeroColorBits = Color.toFloatBits(0f, 0f, 0f, 0f);
static final int MIN_RAYS = 3;
protected final Color color = new Color();
protected final Vector2 tmpPosition = new Vector2();
protected RayHandler rayHandler;
protected boolean active = true;
protected boolean soft = true;
protected boolean xray = false;
protected boolean staticLight = false;
protected boolean culled = false;
protected boolean dirty = true;
protected int rayNum;
protected int vertexNum;
protected float distance;
protected float direction;
protected float colorF;
protected float softShadowLength = 2.5f;
protected Mesh lightMesh;
protected Mesh softShadowMesh;
protected float segments[];
protected float[] mx;
protected float[] my;
protected float[] f;
protected int m_index = 0;
/**
* Creates new active light and automatically adds it to the specified
* {@link RayHandler} instance.
*
* @param rayHandler
* not 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
* light color
* @param distance
* light distance (if applicable)
* @param directionDegree
* direction in degrees (if applicable)
*/
public Light(RayHandler rayHandler, int rays, Color color,
float distance, float directionDegree) {
rayHandler.lightList.add(this);
this.rayHandler = rayHandler;
setRayNum(rays);
setColor(color);
setDistance(distance);
setDirection(directionDegree);
}
/**
* Updates this light
*/
abstract void update();
/**
* Render this light
*/
abstract void render();
/**
* Sets light distance
*
* <p>NOTE: MIN value should be capped to 0.1f meter
*/
public abstract void setDistance(float dist);
/**
* Sets light direction
*/
public abstract void setDirection(float directionDegree);
/**
* Attaches light to specified body
*
* @param body
* that will be automatically followed, note that the body
* rotation angle is taken into account for the light offset
* and direction calculations
*/
public abstract void attachToBody(Body body);
/**
* @return attached body or {@code null}
*
* @see #attachToBody(Body, float, float)
*/
public abstract Body getBody();
/**
* Sets light starting position
*
* @see #setPosition(Vector2)
*/
public abstract void setPosition(float x, float y);
/**
* Sets light starting position
*
* @see #setPosition(float, float)
*/
public abstract void setPosition(Vector2 position);
/**
* @return horizontal starting position of light in world coordinates
*/
public abstract float getX();
/**
* @return vertical starting position of light in world coordinates
*/
public abstract float getY();
/**
* @return starting position of light in world coordinates
* <p>NOTE: changing this vector does nothing
*/
public Vector2 getPosition() {
return tmpPosition;
}
/**
* Sets light color
*
* <p>NOTE: you can also use colorless light with shadows, e.g. (0,0,0,1)
*
* @param newColor
* RGB set the color and Alpha set intensity
*
* @see #setColor(float, float, float, float)
*/
public void setColor(Color newColor) {
if (newColor != null) {
color.set(newColor);
} else {
color.set(DefaultColor);
}
colorF = color.toFloatBits();
if (staticLight) dirty = true;
}
/**
* Sets light color
*
* <p>NOTE: you can also use colorless light with shadows, e.g. (0,0,0,1)
*
* @param r
* lights color red component
* @param g
* lights color green component
* @param b
* lights color blue component
* @param a
* lights shadow intensity
*
* @see #setColor(Color)
*/
public void setColor(float r, float g, float b, float a) {
color.set(r, g, b, a);
colorF = color.toFloatBits();
if (staticLight) dirty = true;
}
/**
* Adds light to specified RayHandler
*/
public void add(RayHandler rayHandler) {
this.rayHandler = rayHandler;
if (active) {
rayHandler.lightList.add(this);
} else {
rayHandler.disabledLights.add(this);
}
}
/**
* Removes light from specified RayHandler
*/
public void remove() {
if (active) {
rayHandler.lightList.removeValue(this, false);
} else {
rayHandler.disabledLights.removeValue(this, false);
}
rayHandler = null;
}
/**
* Disposes all light resources
*/
public void dispose() {
lightMesh.dispose();
softShadowMesh.dispose();
}
/**
* @return if this light is active
*/
public boolean isActive() {
return active;
}
/**
* Enables/disables this light update and rendering
*/
public void setActive(boolean active) {
if (active == this.active)
return;
this.active = active;
if (rayHandler == null)
return;
if (active) {
rayHandler.lightList.add(this);
rayHandler.disabledLights.removeValue(this, true);
} else {
rayHandler.disabledLights.add(this);
rayHandler.lightList.removeValue(this, true);
}
}
/**
* @return if this light beams go through obstacles
*/
public boolean isXray() {
return xray;
}
/**
* Enables/disables x-ray beams for this light
*
* <p>Enabling this will allow beams go through obstacles that reduce CPU
* burden of light about 70%.
*
* <p>Use the combination of x-ray and non x-ray lights wisely
*/
public void setXray(boolean xray) {
this.xray = xray;
if (staticLight) dirty = true;
}
/**
* @return if this light is static
* <p>Static light do not get any automatic updates but setting
* any parameters will update it. Static lights are useful for
* lights that you want to collide with static geometry but ignore
* all the dynamic objects.
*/
public boolean isStaticLight() {
return staticLight;
}
/**
* Enables/disables this light static behavior
*
* <p>Static light do not get any automatic updates but setting any
* parameters will update it. Static lights are useful for lights that you
* want to collide with static geometry but ignore all the dynamic objects
*
* <p>Reduce CPU burden of light about 90%
*/
public void setStaticLight(boolean staticLight) {
this.staticLight = staticLight;
if (staticLight) dirty = true;
}
/**
* @return if tips of this light beams are soft
*/
public boolean isSoft() {
return soft;
}
/**
* Enables/disables softness on tips of this light beams
*/
public void setSoft(boolean soft) {
this.soft = soft;
if (staticLight) dirty = true;
}
/**
* @return softness value for beams tips
* <p>Default: {@code 2.5f}
*/
public float getSoftShadowLength() {
return softShadowLength;
}
/**
* Sets softness value for beams tips
*
* <p>Default: {@code 2.5f}
*/
public void setSoftnessLength(float softShadowLength) {
this.softShadowLength = softShadowLength;
if (staticLight) dirty = true;
}
/**
* @return current color of this light
*/
public Color getColor() {
return color;
}
/**
* @return rays distance of this light (without gamma correction)
*/
public float getDistance() {
return distance / RayHandler.gammaCorrectionParameter;
}
/**
* Checks if given point is inside of this light area
*
* @param x - horizontal position of point in world coordinates
* @param y - vertical position of point in world coordinates
*/
public boolean contains(float x, float y) {
return false;
}
/**
* Internal method for mesh update depending on ray number
*/
void setRayNum(int rays) {
if (rays < MIN_RAYS)
rays = MIN_RAYS;
rayNum = rays;
vertexNum = rays + 1;
segments = new float[vertexNum * 8];
mx = new float[vertexNum];
my = new float[vertexNum];
f = new float[vertexNum];
}
/** Global lights filter **/
static private Filter filterA = null;
final RayCastCallback ray = new RayCastCallback() {
@Override
final public float reportRayFixture(Fixture fixture, Vector2 point,
Vector2 normal, float fraction) {
if ((filterA != null) && !contactFilter(fixture))
return -1;
// if (fixture.isSensor())
// return -1;
mx[m_index] = point.x;
my[m_index] = point.y;
f[m_index] = fraction;
return fraction;
}
};
boolean contactFilter(Fixture fixtureB) {
Filter filterB = fixtureB.getFilterData();
if (filterA.groupIndex != 0 &&
filterA.groupIndex == filterB.groupIndex)
return filterA.groupIndex > 0;
return (filterA.maskBits & filterB.categoryBits) != 0 &&
(filterA.categoryBits & filterB.maskBits) != 0;
}
/**
* Sets given contact filter for ALL LIGHTS
*/
static public void setContactFilter(Filter filter) {
filterA = filter;
}
/**
* Creates new contact filter for ALL LIGHTS with give parameters
*
* @param categoryBits - see {@link Filter#categoryBits}
* @param groupIndex - see {@link Filter#groupIndex}
* @param maskBits - see {@link Filter#maskBits}
*/
static public void setContactFilter(short categoryBits, short groupIndex,
short maskBits) {
filterA = new Filter();
filterA.categoryBits = categoryBits;
filterA.groupIndex = groupIndex;
filterA.maskBits = maskBits;
}
}