Skip to content

Commit

Permalink
Experimental pseudo-3d support for PointLight
Browse files Browse the repository at this point in the history
Experimental feature (libgdx#40)
Only PolygonShapes currently drop dynamic shadow
  • Loading branch information
rinold committed Feb 4, 2015
1 parent b9bff07 commit 39b2765
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 4 deletions.
21 changes: 21 additions & 0 deletions src/box2dLight/Light.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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.Array;
import com.badlogic.gdx.utils.Disposable;

/**
Expand Down Expand Up @@ -47,6 +48,7 @@ public abstract class Light implements Disposable {

protected Mesh lightMesh;
protected Mesh softShadowMesh;
protected Array<Mesh> dynamicShadowMeshes = new Array<Mesh>();

protected float segments[];
protected float[] mx;
Expand Down Expand Up @@ -90,6 +92,13 @@ public Light(RayHandler rayHandler, int rays, Color color,
*/
abstract void render();

/**
* Render this light shadow
*/
void dynamicShadowRender() {

}

/**
* Sets light distance
*
Expand Down Expand Up @@ -223,6 +232,10 @@ public void remove() {
public void dispose() {
lightMesh.dispose();
softShadowMesh.dispose();
for (Mesh mesh : dynamicShadowMeshes) {
mesh.dispose();
}
dynamicShadowMeshes.clear();
}

/**
Expand Down Expand Up @@ -444,5 +457,13 @@ static public void setContactFilter(short categoryBits, short groupIndex,
filterA.groupIndex = groupIndex;
filterA.maskBits = maskBits;
}

/** Returns the distance between the given line and point. Note the specified line is not a line segment. */
public static float sqDistanceLinePoint (float startX, float startY, float endX, float endY, float pointX, float pointY) {
float tmp1 = (endX - startX);
float tmp2 = (endY - startY);
float normalLength2 = tmp1 * tmp1 + tmp2 * tmp2;
return ((pointX - startX) * tmp2 - (pointY - startY) * tmp1) / normalLength2;
}

}
13 changes: 13 additions & 0 deletions src/box2dLight/LightData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package box2dLight;

public class LightData {

float height;

int shadowsDropped = 0;

public LightData(float h) {
height = h;
}

}
80 changes: 80 additions & 0 deletions src/box2dLight/PointLight.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
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.math.MathUtils;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.Shape;

/**
* Light shaped as a circle with given radius
Expand Down Expand Up @@ -58,6 +66,74 @@ public void update () {

dirty = false;
updateMesh();

if (rayHandler.pseudo3d && height != 0f) {
prepeareFixtureData();
}
}

@Override
void dynamicShadowRender () {
if (height == 0f) return;

updateDynamicShadowMeshes();
for (Mesh m : dynamicShadowMeshes) {
m.render(rayHandler.lightShader, GL20.GL_TRIANGLE_STRIP);
}
}


protected void updateDynamicShadowMeshes() {
for (Mesh mesh : dynamicShadowMeshes) {
mesh.dispose();
}
dynamicShadowMeshes.clear();

if (dynamicSegments == null) {
dynamicSegments = new float[vertexNum * 16];
}

float colBits = rayHandler.ambientLight.toFloatBits();
for (Fixture fixture : affectedFixtures) {
LightData data = (LightData)fixture.getUserData();
Shape fixtureShape = fixture.getShape();
if (fixtureShape instanceof PolygonShape) {
Mesh mesh = new Mesh(
VertexDataType.VertexArray, staticLight, 2 * vertexNum, 0,
new VertexAttribute(Usage.Position, 2, "vertex_positions"),
new VertexAttribute(Usage.ColorPacked, 4, "quad_colors"),
new VertexAttribute(Usage.Generic, 1, "s"));
PolygonShape shape = (PolygonShape)fixtureShape;
int size = 0;
float l;
float dst = fixture.getBody().getWorldCenter().dst(start);
float f = 1f / data.shadowsDropped;
for (int n = 0; n < shape.getVertexCount(); n++) {
shape.getVertex(n, tmpVec);
tmpVec.set(fixture.getBody().getWorldPoint(tmpVec));

dynamicSegments[size++] = tmpVec.x;
dynamicSegments[size++] = tmpVec.y;
dynamicSegments[size++] = colBits;
dynamicSegments[size++] = f;

if (height > data.height) {
l = dst * data.height / (height - data.height);
if (l > distance - dst) l = distance - dst;
} else {
l = distance - dst;
}

tmpEnd.set(tmpVec).sub(start).limit(l).add(tmpVec);
dynamicSegments[size++] = tmpEnd.x;
dynamicSegments[size++] = tmpEnd.y;
dynamicSegments[size++] = colBits;
dynamicSegments[size++] = f;
}
mesh.setVertices(dynamicSegments, 0, size);
dynamicShadowMeshes.add(mesh);
}
}
}

/**
Expand All @@ -73,6 +149,10 @@ public void setDistance(float dist) {
dirty = true;
}

public void setHeight(float height) {
this.height = height;
}

/** Updates light basing on it's distance and rayNum **/
void setEndPoints() {
float angleNum = 360f / (rayNum - 1);
Expand Down
47 changes: 44 additions & 3 deletions src/box2dLight/PositionalLight.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.QueryCallback;
import com.badlogic.gdx.utils.Array;

/**
* Abstract base class for all positional lights
Expand All @@ -20,6 +23,7 @@
*/
public abstract class PositionalLight extends Light {

protected final Vector2 tmpStart = new Vector2();
protected final Vector2 tmpEnd = new Vector2();
protected final Vector2 start = new Vector2();

Expand All @@ -34,6 +38,12 @@ public abstract class PositionalLight extends Light {
protected float endX[];
protected float endY[];

protected float[] dynamicSegments;
final Vector2 tmpVec = new Vector2();
final Array<Fixture> affectedFixtures = new Array<Fixture>();

protected float height = 0f;

/**
* Creates new positional light and automatically adds it to the specified
* {@link RayHandler} instance.
Expand Down Expand Up @@ -76,6 +86,7 @@ void update() {
if (staticLight && !dirty) return;

dirty = false;

updateMesh();
}

Expand All @@ -87,7 +98,7 @@ void render() {
lightMesh.render(
rayHandler.lightShader, GL20.GL_TRIANGLE_FAN, 0, vertexNum);

if (soft && !xray) {
if (soft && !xray && !rayHandler.pseudo3d) {
softShadowMesh.render(
rayHandler.lightShader,
GL20.GL_TRIANGLE_STRIP,
Expand Down Expand Up @@ -208,6 +219,10 @@ protected void setRayNum(int rays) {
cos = new float[rays];
endX = new float[rays];
endY = new float[rays];

if (rayHandler.pseudo3d) {
dynamicSegments = new float[vertexNum * 16];
}
}

protected boolean cull() {
Expand Down Expand Up @@ -238,12 +253,26 @@ protected void updateMesh() {
mx[i] = tmpEnd.x;
tmpEnd.y = endY[i] + start.y;
my[i] = tmpEnd.y;
if (rayHandler.world != null && !xray) {
if (rayHandler.world != null && !xray && !rayHandler.pseudo3d) {
rayHandler.world.rayCast(ray, start, tmpEnd);
}
}
setMesh();
}

protected void prepeareFixtureData() {
affectedFixtures.clear();
rayHandler.world.QueryAABB(
dynamicShadowCallback,
start.x - distance, start.y - distance,
start.x + distance, start.y + distance);
for (Fixture fixture : affectedFixtures) {
if (fixture.getUserData() instanceof LightData) {
LightData data = (LightData)fixture.getUserData();
data.shadowsDropped++;
}
}
}

protected void setMesh() {
// ray starting point
Expand All @@ -262,7 +291,7 @@ protected void setMesh() {
}
lightMesh.setVertices(segments, 0, size);

if (!soft || xray) return;
if (!soft || xray || rayHandler.pseudo3d) return;

size = 0;
// rays ending points.
Expand All @@ -280,4 +309,16 @@ protected void setMesh() {
softShadowMesh.setVertices(segments, 0, size);
}

final QueryCallback dynamicShadowCallback = new QueryCallback() {

@Override
public boolean reportFixture(Fixture fixture) {
if (fixture.getBody() != body) {
affectedFixtures.add(fixture);
}
return true;
}

};

}
36 changes: 35 additions & 1 deletion src/box2dLight/RayHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
Expand Down Expand Up @@ -72,6 +74,8 @@ public class RayHandler implements Disposable {
* <p>NOTE: DO NOT MODIFY THIS LIST
*/
final Array<Light> disabledLights = new Array<Light>(false, 16);

final Array<Body> bodies = new Array<Body>();

final LightMap lightMap;
final ShaderProgram lightShader;
Expand All @@ -80,6 +84,9 @@ public class RayHandler implements Disposable {
boolean shadows = true;
boolean blur = true;

/** Experimental mode **/
boolean pseudo3d = false;

int blurNum = 1;

boolean customViewport = false;
Expand Down Expand Up @@ -271,6 +278,16 @@ public void updateAndRender() {
* @see #render()
*/
public void update() {
world.getBodies(bodies);
for (Body body : bodies) {
for (Fixture fixture : body.getFixtureList()) {
if (fixture.getUserData() instanceof LightData) {
LightData data = (LightData)fixture.getUserData();
data.shadowsDropped = 0;
}
}
}

for (Light light : lightList) {
light.update();
}
Expand All @@ -297,7 +314,6 @@ public void render() {

Gdx.gl.glDepthMask(false);
Gdx.gl.glEnable(GL20.GL_BLEND);
simpleBlendFunc.apply();

boolean useLightMap = (shadows || blur);
if (useLightMap) {
Expand All @@ -309,9 +325,18 @@ public void render() {
lightShader.begin();
{
lightShader.setUniformMatrix("u_projTrans", combined);

simpleBlendFunc.apply();
for (Light light : lightList) {
light.render();
}

if (pseudo3d) {
shadowBlendFunc.apply();
for (Light light : lightList) {
light.dynamicShadowRender();
}
}
}
lightShader.end();

Expand Down Expand Up @@ -535,6 +560,15 @@ public void useCustomViewport(int x, int y, int width, int height) {
public void useDefaultViewport() {
customViewport = false;
}

/**
* /!\ Experimental mode with dynamic shadowing in pseudo-3d world
*
* @param flag
*/
public void setPseudo3dLight(boolean flag) {
pseudo3d = flag;
}

/**
* Enables/disables lightMap automatic rendering.
Expand Down

0 comments on commit 39b2765

Please sign in to comment.