diff --git a/.gitignore b/.gitignore
index 12eb36f..fb3b6b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
.project
.settings/
.idea/
+.gradle/
+build/
libs/
target/
*.class
diff --git a/build.gradle b/build.gradle
index 025d5df..0f14a4f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,7 +15,7 @@ sourceSets {
}
}
-ext.gdxVersion = "1.9.10"
+ext.gdxVersion = "1.9.11"
version = "1.6-SNAPSHOT"
repositories {
diff --git a/src/box2dLight/ChainLight.java b/src/box2dLight/ChainLight.java
index 0d09bc7..67086fd 100644
--- a/src/box2dLight/ChainLight.java
+++ b/src/box2dLight/ChainLight.java
@@ -1,5 +1,6 @@
package box2dLight;
+import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
@@ -115,14 +116,19 @@ public ChainLight(RayHandler rayHandler, int rays, Color color,
startY = new float[rays];
this.chain = (chain != null) ?
new FloatArray(chain) : new FloatArray();
-
+
+ Mesh.VertexDataType vertexDataType = Mesh.VertexDataType.VertexArray;
+ if (Gdx.gl30 != null) {
+ vertexDataType = VertexDataType.VertexBufferObjectWithVAO;
+ }
+
lightMesh = new Mesh(
- VertexDataType.VertexArray, false, vertexNum, 0,
+ vertexDataType, 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,
+ vertexDataType, false, vertexNum * 2,
0, new VertexAttribute(Usage.Position, 2, "vertex_positions"),
new VertexAttribute(Usage.ColorPacked, 4, "quad_colors"),
new VertexAttribute(Usage.Generic, 1, "s"));
diff --git a/src/box2dLight/ConeLight.java b/src/box2dLight/ConeLight.java
index 7adec16..b84aa09 100644
--- a/src/box2dLight/ConeLight.java
+++ b/src/box2dLight/ConeLight.java
@@ -45,6 +45,11 @@ public ConeLight(RayHandler rayHandler, int rays, Color color,
@Override
public void update () {
+ if (rayHandler.pseudo3d) {
+ prepareFixtureData();
+ updateDynamicShadowMeshes();
+ }
+
updateBody();
if (dirty) setEndPoints();
diff --git a/src/box2dLight/DirectionalLight.java b/src/box2dLight/DirectionalLight.java
index 99dd603..c7e64a0 100644
--- a/src/box2dLight/DirectionalLight.java
+++ b/src/box2dLight/DirectionalLight.java
@@ -1,15 +1,23 @@
-
package box2dLight;
+import com.badlogic.gdx.Gdx;
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.Intersector;
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.ChainShape;
+import com.badlogic.gdx.physics.box2d.CircleShape;
+import com.badlogic.gdx.physics.box2d.EdgeShape;
+import com.badlogic.gdx.physics.box2d.Fixture;
+import com.badlogic.gdx.physics.box2d.PolygonShape;
+import com.badlogic.gdx.physics.box2d.Shape;
+import com.badlogic.gdx.physics.box2d.Shape.Type;
/**
* Light which source is at infinite distance
@@ -19,6 +27,8 @@
* @author kalle_h
*/
public class DirectionalLight extends Light {
+ protected boolean flipDirection = false;
+ Color tmpColor = new Color();
protected final Vector2 start[];
protected final Vector2 end[];
@@ -28,6 +38,13 @@ public class DirectionalLight extends Light {
/** The body that could be set as ignored by this light type **/
protected Body body;
+ /**
+ * Dynamic shadows variables *
+ */
+ protected final Vector2 lstart = new Vector2();
+ protected float xDisp;
+ protected float yDisp;
+
/**
* Creates directional light which source is at infinite distance,
* direction and intensity is same everywhere
@@ -56,14 +73,19 @@ public DirectionalLight(RayHandler rayHandler, int rays, Color color,
start[i] = new Vector2();
end[i] = new Vector2();
}
+
+ Mesh.VertexDataType vertexDataType = Mesh.VertexDataType.VertexArray;
+ if (Gdx.gl30 != null) {
+ vertexDataType = VertexDataType.VertexBufferObjectWithVAO;
+ }
lightMesh = new Mesh(
- VertexDataType.VertexArray, staticLight, vertexNum, 0,
+ vertexDataType, staticLight, 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, staticLight, vertexNum, 0,
+ vertexDataType, staticLight, vertexNum, 0,
new VertexAttribute(Usage.Position, 2, "vertex_positions"),
new VertexAttribute(Usage.ColorPacked, 4, "quad_colors"),
new VertexAttribute(Usage.Generic, 1, "s"));
@@ -73,6 +95,7 @@ public DirectionalLight(RayHandler rayHandler, int rays, Color color,
@Override
public void setDirection (float direction) {
+ if (flipDirection) direction += 180;
this.direction = direction;
sin = MathUtils.sinDeg(direction);
cos = MathUtils.cosDeg(direction);
@@ -80,8 +103,21 @@ public void setDirection (float direction) {
}
@Override
- void update () {
- if (staticLight && !dirty) return;
+ void update() {
+ if (rayHandler.pseudo3d) {
+ float width = (rayHandler.x2 - rayHandler.x1);
+ float height = (rayHandler.y2 - rayHandler.y1);
+ float sizeOfScreen = width > height ? width : height;
+ xDisp = -sizeOfScreen * cos;
+ yDisp = -sizeOfScreen * sin;
+
+ prepareFixtureData();
+ updateDynamicShadowMeshes();
+ }
+
+ if (staticLight && !dirty) {
+ return;
+ }
dirty = false;
final float width = (rayHandler.x2 - rayHandler.x1);
@@ -117,7 +153,7 @@ void update () {
mx[i] = end[i].x = steppedX + xAxelOffSet;
my[i] = end[i].y = steppedY + yAxelOffSet;
- if (rayHandler.world != null && !xray) {
+ if (rayHandler.world != null && !xray && !rayHandler.pseudo3d) {
rayHandler.world.rayCast(ray, start[i], end[i]);
}
}
@@ -139,7 +175,9 @@ void update () {
}
lightMesh.setVertices(segments, 0, size);
- if (!soft || xray) return;
+ if (!soft || xray || rayHandler.pseudo3d) {
+ return;
+ }
size = 0;
for (int i = 0; i < arraySize; i++) {
@@ -159,15 +197,227 @@ void update () {
@Override
void render () {
rayHandler.lightRenderedLastFrame++;
+ rayHandler.simpleBlendFunc.apply();
+
lightMesh.render(
rayHandler.lightShader, GL20.GL_TRIANGLE_STRIP, 0, vertexNum);
-
- if (soft && !xray) {
+
+ if (soft && !xray && !rayHandler.pseudo3d) {
softShadowMesh.render(
- rayHandler.lightShader, GL20.GL_TRIANGLE_STRIP, 0, vertexNum);
+ rayHandler.lightShader, GL20.GL_TRIANGLE_STRIP, 0, vertexNum);
}
}
-
+
+ protected void prepareFixtureData() {
+ rayHandler.world.QueryAABB(
+ dynamicShadowCallback,
+ rayHandler.x1, rayHandler.y1,
+ rayHandler.x2, rayHandler.y2);
+ }
+
+ protected void updateDynamicShadowMeshes() {
+ int meshInd = 0;
+ float colBits = rayHandler.ambientLight.toFloatBits();
+ //We never clear the affectedFixtures array except the lightsource moves.
+ //This prevents shadows from disappearing when fixture is out of sight but shadow should be still there
+ for (Fixture fixture : affectedFixtures) {
+ LightData data = (LightData) fixture.getUserData();
+ if (data == null) {
+ continue;
+ }
+
+ Shape fixtureShape = fixture.getShape();
+ Type type = fixtureShape.getType();
+ Body body = fixture.getBody();
+ center.set(body.getWorldCenter());
+ lstart.set(center).add(xDisp, yDisp);
+
+ int shadowSize = 0;
+ float l = data.height / (float) Math.tan(pseudo3dHeight * MathUtils.degRad);
+ float f = 1f;
+
+ tmpColor.set(Color.BLACK);
+ float startColBits = rayHandler.shadowColorInterpolation
+ ? tmpColor.lerp(rayHandler.ambientLight, 1 - f).toFloatBits()
+ : oneColorBits;
+ tmpColor.set(Color.WHITE);
+ float endColBits = rayHandler.shadowColorInterpolation
+ ? tmpColor.lerp(rayHandler.ambientLight, 1 - f).toFloatBits()
+ : colBits;
+
+ if (type == Type.Polygon || type == Type.Chain) {
+ boolean isPolygon = (type == Type.Polygon);
+ ChainShape cShape = isPolygon
+ ? null : (ChainShape) fixtureShape;
+ PolygonShape pShape = isPolygon
+ ? (PolygonShape) fixtureShape : null;
+ int vertexCount = isPolygon
+ ? pShape.getVertexCount() : cShape.getVertexCount();
+ int minN = -1;
+ int maxN = -1;
+ int minDstN = -1;
+ float minDst = Float.POSITIVE_INFINITY;
+ boolean hasGasp = false;
+ tmpVerts.clear();
+ for (int n = 0; n < vertexCount; n++) {
+ if (isPolygon) {
+ pShape.getVertex(n, tmpVec);
+ } else {
+ cShape.getVertex(n, tmpVec);
+ }
+ tmpVec.set(body.getWorldPoint(tmpVec));
+ tmpVerts.add(tmpVec.cpy());
+
+ tmpEnd.set(tmpVec).sub(lstart).limit2(0.0001f).add(tmpVec);
+ if (fixture.testPoint(tmpEnd)) {
+ if (minN == -1) {
+ minN = n;
+ }
+ maxN = n;
+ hasGasp = true;
+ continue;
+ }
+ float currDist = tmpVec.dst2(lstart);
+ if (currDist < minDst) {
+ minDst = currDist;
+ minDstN = n;
+ }
+ }
+
+ ind.clear();
+ if (!hasGasp) {
+ tmpVec.set(tmpVerts.get(minDstN));
+ for (int n = minDstN; n < vertexCount; n++) {
+ ind.add(n);
+ }
+ for (int n = 0; n < minDstN; n++) {
+ ind.add(n);
+ }
+ if (Intersector.pointLineSide(lstart, center, tmpVec) > 0) {
+ int z = ind.get(0);
+ ind.removeIndex(0);
+ ind.reverse();
+ ind.insert(0, z);
+ }
+ } else if (minN == 0 && maxN == vertexCount - 1) {
+ for (int n = maxN - 1; n > minN; n--) {
+ ind.add(n);
+ }
+ } else {
+ for (int n = minN - 1; n > -1; n--) {
+ ind.add(n);
+ }
+ for (int n = vertexCount - 1; n > maxN; n--) {
+ ind.add(n);
+ }
+ }
+
+ for (int n : ind.toArray()) {
+ tmpVec.set(tmpVerts.get(n));
+ tmpEnd.set(tmpVec).sub(lstart).setLength(l).add(tmpVec);
+
+ segments[shadowSize++] = tmpVec.x;
+ segments[shadowSize++] = tmpVec.y;
+ segments[shadowSize++] = startColBits;
+ segments[shadowSize++] = f;
+
+ segments[shadowSize++] = tmpEnd.x;
+ segments[shadowSize++] = tmpEnd.y;
+ segments[shadowSize++] = endColBits;
+ segments[shadowSize++] = f;
+ }
+ if (data.shadow) {
+ for (int n = 0; n < vertexCount; n++) {
+ tmpVec.set(tmpVerts.get(n));
+ segments[shadowSize++] = tmpVec.x;
+ segments[shadowSize++] = tmpVec.y;
+ segments[shadowSize++] = startColBits;
+ segments[shadowSize++] = f;
+ if (n == vertexCount - 1) {
+ tmpVec.set(tmpVerts.get(0));
+ segments[shadowSize++] = tmpVec.x;
+ segments[shadowSize++] = tmpVec.y;
+ segments[shadowSize++] = startColBits;
+ segments[shadowSize++] = f;
+ }
+ }
+ }
+ } else if (type == Type.Circle) {
+ CircleShape shape = (CircleShape) fixtureShape;
+
+ float r = shape.getRadius();
+ float dst = tmpVec.set(center).dst(lstart);
+ float a = (float) Math.acos(r / dst);
+
+ tmpVec.set(lstart).sub(center).clamp(r, r).rotateRad(a);
+ tmpStart.set(center).add(tmpVec);
+
+ float angle = (MathUtils.PI2 - 2f * a)
+ / RayHandler.CIRCLE_APPROX_POINTS;
+ for (int k = 0; k < RayHandler.CIRCLE_APPROX_POINTS; k++) {
+ tmpStart.set(center).add(tmpVec);
+ segments[shadowSize++] = tmpStart.x;
+ segments[shadowSize++] = tmpStart.y;
+ segments[shadowSize++] = startColBits;
+ segments[shadowSize++] = f;
+
+ tmpEnd.set(tmpStart).sub(lstart).setLength(l).add(tmpStart);
+ segments[shadowSize++] = tmpEnd.x;
+ segments[shadowSize++] = tmpEnd.y;
+ segments[shadowSize++] = endColBits;
+ segments[shadowSize++] = f;
+
+ tmpVec.rotateRad(angle);
+ }
+ } else if (type == Type.Edge) {
+ EdgeShape shape = (EdgeShape) fixtureShape;
+
+ shape.getVertex1(tmpVec);
+ tmpVec.set(body.getWorldPoint(tmpVec));
+
+ segments[shadowSize++] = tmpVec.x;
+ segments[shadowSize++] = tmpVec.y;
+ segments[shadowSize++] = startColBits;
+ segments[shadowSize++] = f;
+
+ tmpEnd.set(tmpVec).sub(lstart).setLength(l).add(tmpVec);
+ segments[shadowSize++] = tmpEnd.x;
+ segments[shadowSize++] = tmpEnd.y;
+ segments[shadowSize++] = endColBits;
+ segments[shadowSize++] = f;
+
+ shape.getVertex2(tmpVec);
+ tmpVec.set(body.getWorldPoint(tmpVec));
+ segments[shadowSize++] = tmpVec.x;
+ segments[shadowSize++] = tmpVec.y;
+ segments[shadowSize++] = startColBits;
+ segments[shadowSize++] = f;
+
+ tmpEnd.set(tmpVec).sub(lstart).setLength(l).add(tmpVec);
+ segments[shadowSize++] = tmpEnd.x;
+ segments[shadowSize++] = tmpEnd.y;
+ segments[shadowSize++] = endColBits;
+ segments[shadowSize++] = f;
+ }
+
+ Mesh shadowMesh = null;
+ if (meshInd >= dynamicShadowMeshes.size) {
+ shadowMesh = new Mesh(
+ VertexDataType.VertexArray, false, 128, 0,
+ new VertexAttribute(Usage.Position, 2, "vertex_positions"),
+ new VertexAttribute(Usage.ColorPacked, 4, "quad_colors"),
+ new VertexAttribute(Usage.Generic, 1, "s"));
+ dynamicShadowMeshes.add(shadowMesh);
+ } else {
+ shadowMesh = dynamicShadowMeshes.get(meshInd);
+ }
+ shadowMesh.setVertices(segments, 0, shadowSize);
+ meshInd++;
+
+ }
+ dynamicShadowMeshes.truncate(meshInd);
+ }
+
@Override
public boolean contains (float x, float y) {
boolean oddNodes = false;
@@ -191,21 +441,46 @@ public boolean contains (float x, float y) {
return oddNodes;
}
- /** Not applicable for this light type **/
+ /**
+ * Sets the horizontal angle for directional light in degrees
+ *
+ *
+ * This could be used to simulate sun cycles *
+ */
+ @Override
+ public void setHeight(float degrees) {
+ flipDirection = false;
+ if (degrees < 0f) pseudo3dHeight = 0f;
+ else {
+ degrees = degrees % 360;
+ if (degrees > 180f) {
+ pseudo3dHeight = -1f;
+ }
+ else if (degrees > 90f) {
+ pseudo3dHeight = 180f - degrees;
+ flipDirection = true;
+ }
+ else pseudo3dHeight = degrees;
+ }
+ }
+
+ /**
+ * Not applicable for this light type *
+ */
@Deprecated
@Override
- public void attachToBody (Body body) {
+ public void attachToBody(Body body) {
}
/** Not applicable for this light type **/
@Deprecated
@Override
- public void setPosition (float x, float y) {
+ public void setPosition(float x, float y) {
}
/** Returns the ignored by this light body or {@code null} if not set **/
@Override
- public Body getBody () {
+ public Body getBody() {
return body;
}
@@ -214,7 +489,7 @@ public Body getBody () {
**/
@Deprecated
@Override
- public float getX () {
+ public float getX() {
return 0;
}
@@ -223,14 +498,14 @@ public float getX () {
**/
@Deprecated
@Override
- public float getY () {
+ public float getY() {
return 0;
}
/** Not applicable for this light type **/
@Deprecated
@Override
- public void setPosition (Vector2 position) {
+ public void setPosition(Vector2 position) {
}
/** Not applicable for this light type **/
diff --git a/src/box2dLight/Light.java b/src/box2dLight/Light.java
index f3de27a..232fc1c 100644
--- a/src/box2dLight/Light.java
+++ b/src/box2dLight/Light.java
@@ -1,13 +1,17 @@
package box2dLight;
import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.GL20;
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.QueryCallback;
import com.badlogic.gdx.physics.box2d.RayCastCallback;
+import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.utils.IntArray;
/**
* Light is data container for all the light parameters. When created lights
@@ -22,6 +26,7 @@ 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 float oneColorBits = Color.toFloatBits(1f, 1f, 1f, 1f);
static final int MIN_RAYS = 3;
protected final Color color = new Color();
@@ -54,7 +59,26 @@ public abstract class Light implements Disposable {
protected float[] f;
protected int m_index = 0;
- /**
+ /**
+ * Dynamic shadows variables *
+ */
+ protected static final LightData tmpData = new LightData(0f);
+
+ protected float pseudo3dHeight = 0f;
+
+ protected final Array dynamicShadowMeshes = new Array();
+ //Should never be cleared except when the light changes position (not direction). Prevents shadows from disappearing when fixture is out of sight.
+ protected final Array affectedFixtures = new Array();
+ protected final Array tmpVerts = new Array();
+
+ protected final IntArray ind = new IntArray();
+
+ protected final Vector2 tmpStart = new Vector2();
+ protected final Vector2 tmpEnd = new Vector2();
+ protected final Vector2 tmpVec = new Vector2();
+ protected final Vector2 center = new Vector2();
+
+ /**
* Creates new active light and automatically adds it to the specified
* {@link RayHandler} instance.
*
@@ -71,7 +95,7 @@ public abstract class Light implements Disposable {
* direction in degrees (if applicable)
*/
public Light(RayHandler rayHandler, int rays, Color color,
- float distance, float directionDegree) {
+ float distance, float directionDegree) {
rayHandler.lightList.add(this);
this.rayHandler = rayHandler;
setRayNum(rays);
@@ -90,7 +114,16 @@ public Light(RayHandler rayHandler, int rays, Color color,
* Render this light
*/
abstract void render();
-
+
+ /**
+ * Render this light shadow
+ */
+ protected void dynamicShadowRender() {
+ for (Mesh m : dynamicShadowMeshes) {
+ m.render(rayHandler.lightShader, GL20.GL_TRIANGLE_STRIP);
+ }
+ }
+
/**
* Sets light distance
*
@@ -230,8 +263,13 @@ public void remove(boolean doDispose) {
* Disposes all light resources
*/
public void dispose() {
+ affectedFixtures.clear();
lightMesh.dispose();
softShadowMesh.dispose();
+ for (Mesh mesh : dynamicShadowMeshes) {
+ mesh.dispose();
+ }
+ dynamicShadowMeshes.clear();
}
/**
@@ -353,7 +391,6 @@ public float getDistance() {
return distance / RayHandler.gammaCorrectionParameter;
}
-
/**
* @return direction in degrees (0 if not applicable)
*/
@@ -388,7 +425,11 @@ public void setIgnoreAttachedBody(boolean flag) {
public boolean getIgnoreAttachedBody() {
return ignoreBody;
}
-
+
+ public void setHeight(float height) {
+ this.pseudo3dHeight = height;
+ }
+
/**
* Internal method for mesh update depending on ray number
*/
@@ -434,7 +475,6 @@ final public float reportRayFixture(Fixture fixture, Vector2 point,
// if (fixture.isSensor())
// return -1;
-
mx[m_index] = point.x;
my[m_index] = point.y;
f[m_index] = fraction;
@@ -508,4 +548,38 @@ static public void setGlobalContactFilter(short categoryBits, short groupIndex,
globalFilterA.maskBits = maskBits;
}
+ protected boolean onDynamicCallback(Fixture fixture) {
+
+ if ((globalFilterA != null) && !globalContactFilter(fixture)) {
+ return false;
+ }
+
+ if ((filterA != null) && !contactFilter(fixture)) {
+ return false;
+ }
+
+ if (ignoreBody && fixture.getBody() == getBody()) {
+ return false;
+ }
+ //We only add the affectedFixtures once
+ return !affectedFixtures.contains(fixture, true);
+ }
+
+ final QueryCallback dynamicShadowCallback = new QueryCallback() {
+
+ @Override
+ public boolean reportFixture(Fixture fixture) {
+ if (!onDynamicCallback(fixture)) {
+ return true;
+ }
+ affectedFixtures.add(fixture);
+ if (fixture.getUserData() instanceof LightData) {
+ LightData data = (LightData) fixture.getUserData();
+ data.shadowsDropped++;
+ }
+ return true;
+ }
+
+ };
+
}
diff --git a/src/box2dLight/LightData.java b/src/box2dLight/LightData.java
new file mode 100644
index 0000000..43d52d6
--- /dev/null
+++ b/src/box2dLight/LightData.java
@@ -0,0 +1,56 @@
+package box2dLight;
+
+import com.badlogic.gdx.physics.box2d.Fixture;
+
+public class LightData {
+
+ public Object userData = null;
+
+ public float height;
+
+ public boolean shadow;
+
+ int shadowsDropped = 0;
+
+ public LightData (float h) {
+ height = h;
+ }
+
+ public LightData (float h, boolean shadow) {
+ height = h;
+ this.shadow = shadow;
+ }
+
+ public LightData (Object data, float h, boolean shadow) {
+ height = h;
+ userData = data;
+ this.shadow = shadow;
+ }
+
+ public float getLimit (float distance, float lightHeight, float lightRange) {
+ float l = 0f;
+ if (lightHeight > height) {
+ l = distance * height / (lightHeight - height);
+ float diff = lightRange - distance;
+ if (l > diff) {
+ l = diff;
+ }
+ } else if (lightHeight == 0f) {
+ l = lightRange;
+ } else {
+ l = lightRange - distance;
+ }
+
+ return l > 0 ? l : 0f;
+ }
+
+ public static Object getUserData (Fixture fixture) {
+ Object data = fixture.getUserData();
+ if (data instanceof LightData) {
+ return ((LightData) data).userData;
+ } else {
+ return data;
+ }
+ }
+
+}
diff --git a/src/box2dLight/LightMap.java b/src/box2dLight/LightMap.java
index 01206e6..5d4c3bf 100644
--- a/src/box2dLight/LightMap.java
+++ b/src/box2dLight/LightMap.java
@@ -1,9 +1,6 @@
package box2dLight;
-import shaders.DiffuseShader;
-import shaders.Gaussian;
-import shaders.ShadowShader;
-import shaders.WithoutShadowShader;
+import shaders.*;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
@@ -27,117 +24,154 @@ class LightMap {
private ShaderProgram blurShader;
private ShaderProgram diffuseShader;
+ FrameBuffer shadowBuffer;
+
boolean lightMapDrawingDisabled;
- public void render() {
+ private final int fboWidth, fboHeight;
- boolean needed = rayHandler.lightRenderedLastFrame > 0;
+ public LightMap(RayHandler rayHandler, int fboWidth, int fboHeight) {
+ this.rayHandler = rayHandler;
+
+ if (fboWidth <= 0)
+ fboWidth = 1;
+ if (fboHeight <= 0)
+ fboHeight = 1;
+
+ this.fboWidth = fboWidth;
+ this.fboHeight = fboHeight;
+
+ frameBuffer = new FrameBuffer(Format.RGBA8888, fboWidth,
+ fboHeight, false);
+ pingPongBuffer = new FrameBuffer(Format.RGBA8888, fboWidth,
+ fboHeight, false);
+ shadowBuffer = new FrameBuffer(Format.RGBA8888, fboWidth,
+ fboHeight, false);
+
+ lightMapMesh = createLightMapMesh();
+
+ createShaders();
+ }
+ public void render() {
+ boolean needed = rayHandler.lightRenderedLastFrame > 0;
if (lightMapDrawingDisabled)
return;
- frameBuffer.getColorBufferTexture().bind(0);
+
+ if (rayHandler.pseudo3d) {
+ frameBuffer.getColorBufferTexture().bind(1);
+ shadowBuffer.getColorBufferTexture().bind(0);
+ } else {
+ frameBuffer.getColorBufferTexture().bind(0);
+ }
// at last lights are rendered over scene
if (rayHandler.shadows) {
final Color c = rayHandler.ambientLight;
ShaderProgram shader = shadowShader;
- if (RayHandler.isDiffuse) {
+ if (rayHandler.pseudo3d) {
+ shader.bind();
+ if (RayHandler.isDiffuse) {
+ rayHandler.diffuseBlendFunc.apply();
+ shader.setUniformf("ambient", c.r, c.g, c.b, c.a);
+ } else {
+ rayHandler.shadowBlendFunc.apply();
+ shader.setUniformf("ambient", c.r * c.a, c.g * c.a,
+ c.b * c.a, 1f - c.a);
+ }
+ shader.setUniformi("isDiffuse", RayHandler.isDiffuse ? 1 : 0);
+ shader.setUniformi("u_texture", 1);
+ shader.setUniformi("u_shadows", 0);
+ } else if (RayHandler.isDiffuse) {
shader = diffuseShader;
- shader.begin();
+ shader.bind();
rayHandler.diffuseBlendFunc.apply();
shader.setUniformf("ambient", c.r, c.g, c.b, c.a);
} else {
- shader.begin();
+ shader.bind();
rayHandler.shadowBlendFunc.apply();
shader.setUniformf("ambient", c.r * c.a, c.g * c.a,
c.b * c.a, 1f - c.a);
}
- // shader.setUniformi("u_texture", 0);
+
lightMapMesh.render(shader, GL20.GL_TRIANGLE_FAN);
- shader.end();
} else if (needed) {
rayHandler.simpleBlendFunc.apply();
- withoutShadowShader.begin();
- // withoutShadowShader.setUniformi("u_texture", 0);
+ withoutShadowShader.bind();
+
lightMapMesh.render(withoutShadowShader, GL20.GL_TRIANGLE_FAN);
- withoutShadowShader.end();
}
Gdx.gl20.glDisable(GL20.GL_BLEND);
}
- public void gaussianBlur() {
-
+ public void gaussianBlur(FrameBuffer buffer, int blurNum) {
Gdx.gl20.glDisable(GL20.GL_BLEND);
- for (int i = 0; i < rayHandler.blurNum; i++) {
- frameBuffer.getColorBufferTexture().bind(0);
+ for (int i = 0; i < blurNum; i++) {
+ buffer.getColorBufferTexture().bind(0);
// horizontal
pingPongBuffer.begin();
{
- blurShader.begin();
- // blurShader.setUniformi("u_texture", 0);
+ blurShader.bind();
blurShader.setUniformf("dir", 1f, 0f);
lightMapMesh.render(blurShader, GL20.GL_TRIANGLE_FAN, 0, 4);
- blurShader.end();
+
}
pingPongBuffer.end();
pingPongBuffer.getColorBufferTexture().bind(0);
// vertical
- frameBuffer.begin();
+ buffer.begin();
{
- blurShader.begin();
- // blurShader.setUniformi("u_texture", 0);
+ blurShader.bind();
blurShader.setUniformf("dir", 0f, 1f);
lightMapMesh.render(blurShader, GL20.GL_TRIANGLE_FAN, 0, 4);
- blurShader.end();
-
}
if (rayHandler.customViewport) {
- frameBuffer.end(
+ buffer.end(
rayHandler.viewportX,
rayHandler.viewportY,
rayHandler.viewportWidth,
rayHandler.viewportHeight);
} else {
- frameBuffer.end();
+ buffer.end();
}
}
Gdx.gl20.glEnable(GL20.GL_BLEND);
}
- public LightMap(RayHandler rayHandler, int fboWidth, int fboHeight) {
- this.rayHandler = rayHandler;
+ void dispose() {
+ disposeShaders();
- if (fboWidth <= 0)
- fboWidth = 1;
- if (fboHeight <= 0)
- fboHeight = 1;
- frameBuffer = new FrameBuffer(Format.RGBA8888, fboWidth,
- fboHeight, false);
- pingPongBuffer = new FrameBuffer(Format.RGBA8888, fboWidth,
- fboHeight, false);
+ lightMapMesh.dispose();
- lightMapMesh = createLightMapMesh();
+ frameBuffer.dispose();
+ shadowBuffer.dispose();
+ pingPongBuffer.dispose();
+ }
+
+ void createShaders() {
+ disposeShaders();
- shadowShader = ShadowShader.createShadowShader();
+ shadowShader = rayHandler.pseudo3d ? DynamicShadowShader.createShadowShader() : ShadowShader.createShadowShader();
diffuseShader = DiffuseShader.createShadowShader();
withoutShadowShader = WithoutShadowShader.createShadowShader();
blurShader = Gaussian.createBlurShader(fboWidth, fboHeight);
-
}
- void dispose() {
- shadowShader.dispose();
- blurShader.dispose();
- lightMapMesh.dispose();
- frameBuffer.dispose();
- pingPongBuffer.dispose();
-
+ private void disposeShaders() {
+ if (shadowShader != null)
+ shadowShader.dispose();
+ if (diffuseShader != null)
+ diffuseShader.dispose();
+ if (withoutShadowShader != null)
+ withoutShadowShader.dispose();
+ if (blurShader != null)
+ blurShader.dispose();
}
private Mesh createLightMapMesh() {
@@ -194,5 +228,4 @@ private Mesh createLightMapMesh() {
static public final int Y4 = 13;
static public final int U4 = 14;
static public final int V4 = 15;
-
}
diff --git a/src/box2dLight/PointLight.java b/src/box2dLight/PointLight.java
index e8c5da0..abc403f 100644
--- a/src/box2dLight/PointLight.java
+++ b/src/box2dLight/PointLight.java
@@ -1,7 +1,12 @@
package box2dLight;
import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.Mesh;
+import com.badlogic.gdx.graphics.VertexAttribute;
+import com.badlogic.gdx.graphics.VertexAttributes;
+import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.MathUtils;
+import com.badlogic.gdx.physics.box2d.*;
/**
* Light shaped as a circle with given radius
@@ -50,6 +55,11 @@ public PointLight(RayHandler rayHandler, int rays, Color color,
@Override
public void update () {
+ if (rayHandler.pseudo3d) {
+ prepareFixtureData();
+ updateDynamicShadowMeshes();
+ }
+
updateBody();
if (dirty) setEndPoints();
@@ -59,7 +69,7 @@ public void update () {
dirty = false;
updateMesh();
}
-
+
/**
* Sets light distance
*
diff --git a/src/box2dLight/PositionalLight.java b/src/box2dLight/PositionalLight.java
index 1a95c99..8bb4da3 100644
--- a/src/box2dLight/PositionalLight.java
+++ b/src/box2dLight/PositionalLight.java
@@ -1,15 +1,14 @@
package box2dLight;
-import com.badlogic.gdx.graphics.Color;
-import com.badlogic.gdx.graphics.GL20;
-import com.badlogic.gdx.graphics.Mesh;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.*;
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.Intersector;
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.*;
/**
* Abstract base class for all positional lights
@@ -20,6 +19,8 @@
*/
public abstract class PositionalLight extends Light {
+ Color tmpColor = new Color();
+
protected final Vector2 tmpEnd = new Vector2();
protected final Vector2 start = new Vector2();
@@ -59,10 +60,14 @@ public PositionalLight(RayHandler rayHandler, int rays, Color color, float dista
start.x = x;
start.y = y;
- lightMesh = new Mesh(VertexDataType.VertexArray, false, vertexNum, 0, new VertexAttribute(Usage.Position, 2,
+ Mesh.VertexDataType vertexDataType = Mesh.VertexDataType.VertexArray;
+ if (Gdx.gl30 != null) {
+ vertexDataType = VertexDataType.VertexBufferObjectWithVAO;
+ }
+ lightMesh = new Mesh(vertexDataType, 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,
+ softShadowMesh = new Mesh(vertexDataType, 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();
@@ -84,10 +89,9 @@ void render() {
if (rayHandler.culling && culled) return;
rayHandler.lightRenderedLastFrame++;
- lightMesh.render(
- rayHandler.lightShader, GL20.GL_TRIANGLE_FAN, 0, vertexNum);
-
- if (soft && !xray) {
+ lightMesh.render(rayHandler.lightShader, GL20.GL_TRIANGLE_FAN, 0, vertexNum);
+
+ if (soft && !xray && !rayHandler.pseudo3d) {
softShadowMesh.render(
rayHandler.lightShader,
GL20.GL_TRIANGLE_STRIP,
@@ -177,6 +181,10 @@ public void setPosition(Vector2 position) {
if (staticLight) dirty = true;
}
+ public boolean contains(Vector2 pos) {
+ return contains(pos.x, pos.y);
+ }
+
@Override
public boolean contains(float x, float y) {
// fast fail
@@ -238,13 +246,20 @@ 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 prepareFixtureData() {
+ rayHandler.world.QueryAABB(
+ dynamicShadowCallback,
+ start.x - distance, start.y - distance,
+ start.x + distance, start.y + distance);
+ }
+
protected void setMesh() {
// ray starting point
int size = 0;
@@ -262,7 +277,7 @@ protected void setMesh() {
}
lightMesh.setVertices(segments, 0, size);
- if (!soft || xray) return;
+ if (!soft || xray || rayHandler.pseudo3d) return;
size = 0;
// rays ending points.
@@ -280,6 +295,246 @@ protected void setMesh() {
softShadowMesh.setVertices(segments, 0, size);
}
+ protected void updateDynamicShadowMeshes() {
+ int meshInd = 0;
+ float colBits = rayHandler.ambientLight.toFloatBits();
+ for (Fixture fixture : affectedFixtures) {
+ LightData data = (LightData)fixture.getUserData();
+ if (data == null || fixture.isSensor()) continue;
+
+ int size = 0;
+ float l;
+
+ Shape fixtureShape = fixture.getShape();
+ Shape.Type type = fixtureShape.getType();
+ Body body = fixture.getBody();
+ center.set(body.getWorldCenter());
+
+ if (type == Shape.Type.Polygon || type == Shape.Type.Chain) {
+ boolean isPolygon = (type == Shape.Type.Polygon);
+ ChainShape cShape = isPolygon ?
+ null : (ChainShape)fixtureShape;
+ PolygonShape pShape = isPolygon ?
+ (PolygonShape)fixtureShape : null;
+ int vertexCount = isPolygon ?
+ pShape.getVertexCount() : cShape.getVertexCount();
+ int minN = -1;
+ int maxN = -1;
+ int minDstN = -1;
+ float minDst = Float.POSITIVE_INFINITY;
+ boolean hasGasp = false;
+ tmpVerts.clear();
+ for (int n = 0; n < vertexCount; n++) {
+ if (isPolygon) {
+ pShape.getVertex(n, tmpVec);
+ } else {
+ cShape.getVertex(n, tmpVec);
+ }
+ tmpVec.set(body.getWorldPoint(tmpVec));
+ tmpVerts.add(tmpVec.cpy());
+ tmpEnd.set(tmpVec).sub(start).limit2(0.0001f).add(tmpVec);
+ if (fixture.testPoint(tmpEnd)) {
+ if (minN == -1) minN = n;
+ maxN = n;
+ hasGasp = true;
+ continue;
+ }
+
+ float currDist = tmpVec.dst2(start);
+ if (currDist < minDst) {
+ minDst = currDist;
+ minDstN = n;
+ }
+ }
+
+ ind.clear();
+ if (!hasGasp) {
+ tmpVec.set(tmpVerts.get(minDstN));
+ for (int n = minDstN; n < vertexCount; n++) {
+ ind.add(n);
+ }
+ for (int n = 0; n < minDstN; n++) {
+ ind.add(n);
+ }
+ if (Intersector.pointLineSide(start, center, tmpVec) > 0) {
+ ind.reverse();
+ ind.insert(0, ind.pop());
+ }
+ } else if (minN == 0 && maxN == vertexCount - 1) {
+ for (int n = maxN - 1; n > minN; n--) {
+ ind.add(n);
+ }
+ } else {
+ for (int n = minN - 1; n > -1; n--) {
+ ind.add(n);
+ }
+ for (int n = vertexCount - 1; n > maxN ; n--) {
+ ind.add(n);
+ }
+ }
+
+ boolean contained = false;
+ for (int n : ind.toArray()) {
+ tmpVec.set(tmpVerts.get(n));
+ if (contains(tmpVec.x, tmpVec.y)){
+ contained = true;
+ break;
+ }
+ }
+
+ if (!contained)
+ continue;
+
+ for (int n : ind.toArray()) {
+ tmpVec.set(tmpVerts.get(n));
+
+ float dst = tmpVec.dst(start);
+ l = data.getLimit(dst, pseudo3dHeight, distance);
+ tmpEnd.set(tmpVec).sub(start).setLength(l).add(tmpVec);
+
+ float f1 = 1f - dst / distance;
+ float f2 = 1f - (dst + l) / distance;
+
+ tmpColor.set(Color.BLACK);
+ float startColBits = rayHandler.shadowColorInterpolation ?
+ tmpColor.lerp(rayHandler.ambientLight, f1).toFloatBits() :
+ oneColorBits;
+ tmpColor.set(Color.WHITE);
+ float endColBits = rayHandler.shadowColorInterpolation ?
+ tmpColor.lerp(rayHandler.ambientLight, f2).toFloatBits() :
+ colBits;
+
+ segments[size++] = tmpVec.x;
+ segments[size++] = tmpVec.y;
+ segments[size++] = startColBits;
+ segments[size++] = f1;
+
+ segments[size++] = tmpEnd.x;
+ segments[size++] = tmpEnd.y;
+ segments[size++] = endColBits;
+ segments[size++] = f2;
+ }
+ } else if (type == Shape.Type.Circle) {
+ CircleShape shape = (CircleShape)fixtureShape;
+ float r = shape.getRadius();
+ if (!contains(tmpVec.set(center).add(r, r)) && !contains(tmpVec.set(center).add(-r, -r))
+ && !contains(tmpVec.set(center).add(r, -r)) && !contains(tmpVec.set(center).add(-r, r))) {
+ continue;
+ }
+
+ float dst = tmpVec.set(center).dst(start);
+ float a = (float) Math.acos(r/dst);
+ l = data.getLimit(dst, pseudo3dHeight, distance);
+ float f1 = 1f - dst / distance;
+ float f2 = 1f - (dst + l) / distance;
+ tmpColor.set(Color.BLACK);
+ float startColBits = rayHandler.shadowColorInterpolation ?
+ tmpColor.lerp(rayHandler.ambientLight, f1).toFloatBits() :
+ oneColorBits;
+ tmpColor.set(Color.WHITE);
+ float endColBits = rayHandler.shadowColorInterpolation ?
+ tmpColor.lerp(rayHandler.ambientLight, f2).toFloatBits() :
+ colBits;
+
+ tmpVec.set(start).sub(center).clamp(r, r).rotateRad(a);
+ tmpStart.set(center).add(tmpVec);
+
+ float angle = (MathUtils.PI2 - 2f * a) /
+ RayHandler.CIRCLE_APPROX_POINTS;
+ for (int k = 0; k < RayHandler.CIRCLE_APPROX_POINTS; k++) {
+ tmpStart.set(center).add(tmpVec);
+ segments[size++] = tmpStart.x;
+ segments[size++] = tmpStart.y;
+ segments[size++] = startColBits;
+ segments[size++] = f1;
+
+ tmpEnd.set(tmpStart).sub(start).setLength(l).add(tmpStart);
+ segments[size++] = tmpEnd.x;
+ segments[size++] = tmpEnd.y;
+ segments[size++] = endColBits;
+ segments[size++] = f2;
+
+ tmpVec.rotateRad(angle);
+ }
+ } else if (type == Shape.Type.Edge) {
+ EdgeShape shape = (EdgeShape)fixtureShape;
+
+ shape.getVertex1(tmpVec);
+ tmpVec.set(body.getWorldPoint(tmpVec));
+ if (!contains(tmpVec)) {
+ continue;
+ }
+ float dst = tmpVec.dst(start);
+ l = data.getLimit(dst, pseudo3dHeight, distance);
+ float f1 = 1f - dst / distance;
+ float f2 = 1f - (dst + l) / distance;
+ tmpColor.set(Color.BLACK);
+ float startColBits = rayHandler.shadowColorInterpolation ?
+ tmpColor.lerp(rayHandler.ambientLight, f1).toFloatBits() :
+ oneColorBits;
+ tmpColor.set(Color.WHITE);
+ float endColBits = rayHandler.shadowColorInterpolation ?
+ tmpColor.lerp(rayHandler.ambientLight, f2).toFloatBits() :
+ colBits;
+
+ segments[size++] = tmpVec.x;
+ segments[size++] = tmpVec.y;
+ segments[size++] = startColBits;
+ segments[size++] = f1;
+
+ tmpEnd.set(tmpVec).sub(start).setLength(l).add(tmpVec);
+ segments[size++] = tmpEnd.x;
+ segments[size++] = tmpEnd.y;
+ segments[size++] = endColBits;
+ segments[size++] = f2;
+
+ shape.getVertex2(tmpVec);
+ tmpVec.set(body.getWorldPoint(tmpVec));
+ if (!contains(tmpVec)) {
+ continue;
+ }
+ dst = tmpVec.dst(start);
+ l = data.getLimit(dst, pseudo3dHeight, distance);
+ f1 = 1f - dst / distance;
+ f2 = 1f - (dst + l) / distance;
+ tmpColor.set(Color.BLACK);
+ startColBits = rayHandler.shadowColorInterpolation ?
+ tmpColor.lerp(rayHandler.ambientLight, f1).toFloatBits() :
+ oneColorBits;
+ tmpColor.set(Color.WHITE);
+ endColBits = rayHandler.shadowColorInterpolation ?
+ tmpColor.lerp(rayHandler.ambientLight, f2).toFloatBits() :
+ colBits;
+
+ segments[size++] = tmpVec.x;
+ segments[size++] = tmpVec.y;
+ segments[size++] = startColBits;
+ segments[size++] = f1;
+
+ tmpEnd.set(tmpVec).sub(start).setLength(l).add(tmpVec);
+ segments[size++] = tmpEnd.x;
+ segments[size++] = tmpEnd.y;
+ segments[size++] = endColBits;
+ segments[size++] = f2;
+ }
+
+ Mesh mesh = null;
+ if (meshInd >= dynamicShadowMeshes.size) {
+ mesh = new Mesh(
+ Mesh.VertexDataType.VertexArray, false, RayHandler.MAX_SHADOW_VERTICES, 0,
+ new VertexAttribute(VertexAttributes.Usage.Position, 2, "vertex_positions"),
+ new VertexAttribute(VertexAttributes.Usage.ColorPacked, 4, "quad_colors"),
+ new VertexAttribute(VertexAttributes.Usage.Generic, 1, "s"));
+ dynamicShadowMeshes.add(mesh);
+ } else {
+ mesh = dynamicShadowMeshes.get(meshInd);
+ }
+ mesh.setVertices(segments, 0, size);
+ meshInd++;
+ }
+ dynamicShadowMeshes.truncate(meshInd);
+ }
+
public float getBodyOffsetX() {
return bodyOffsetX;
}
diff --git a/src/box2dLight/RayHandler.java b/src/box2dLight/RayHandler.java
index f7aeea1..3de93d8 100644
--- a/src/box2dLight/RayHandler.java
+++ b/src/box2dLight/RayHandler.java
@@ -30,11 +30,18 @@ public class RayHandler implements Disposable {
static boolean gammaCorrection = false;
static float gammaCorrectionParameter = 1f;
- /** if this is public why we have a setter?
- * TODO: remove public modifier and add getter
- * */
- public static boolean isDiffuse = false;
+ /**
+ * TODO: This could be made adaptive to ratio of camera sizes * zoom vs the
+ * CircleShape radius - thus will provide smooth radial shadows while
+ * resizing and zooming in and out
+ */
+ static int CIRCLE_APPROX_POINTS = 32;
+
+ static float dynamicShadowColorReduction = 1;
+
+ static int MAX_SHADOW_VERTICES = 64;
+ static boolean isDiffuse = false;
/**
* Blend function for lights rendering with both shadows and diffusion
* Default: (GL20.GL_DST_COLOR, GL20.GL_ZERO)
@@ -80,7 +87,11 @@ public class RayHandler implements Disposable {
boolean culling = true;
boolean shadows = true;
boolean blur = true;
-
+
+ /** Experimental mode */
+ boolean pseudo3d = false;
+ boolean shadowColorInterpolation = false;
+
int blurNum = 1;
boolean customViewport = false;
@@ -113,11 +124,16 @@ public class RayHandler implements Disposable {
*
ambientLight = 0f
*
*
- * @see #RayHandler(World, int, int)
+ * @see #RayHandler(World, int, int, RayHandlerOptions)
*/
public RayHandler(World world) {
this(world, Gdx.graphics.getWidth() / 4, Gdx.graphics
- .getHeight() / 4);
+ .getHeight() / 4, null);
+ }
+
+ public RayHandler(World world, RayHandlerOptions options) {
+ this(world, Gdx.graphics.getWidth() / 4, Gdx.graphics
+ .getHeight() / 4, options);
}
/**
@@ -127,8 +143,19 @@ public RayHandler(World world) {
* @see #RayHandler(World)
*/
public RayHandler(World world, int fboWidth, int fboHeight) {
+ this(world, fboWidth, fboHeight, null);
+ }
+
+ public RayHandler(World world, int fboWidth, int fboHeight, RayHandlerOptions options) {
this.world = world;
+ if (options != null) {
+ isDiffuse = options.isDiffuse;
+ gammaCorrection = options.gammaCorrection;
+ pseudo3d = options.pseudo3d;
+ shadowColorInterpolation = options.shadowColorInterpolation;
+ }
+
resizeFBO(fboWidth, fboHeight);
lightShader = LightShader.createLightShader();
}
@@ -300,7 +327,6 @@ public void prepareRender() {
Gdx.gl.glDepthMask(false);
Gdx.gl.glEnable(GL20.GL_BLEND);
- simpleBlendFunc.apply();
boolean useLightMap = (shadows || blur);
if (useLightMap) {
@@ -309,17 +335,20 @@ public void prepareRender() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
+ simpleBlendFunc.apply();
+
ShaderProgram shader = customLightShader != null ? customLightShader : lightShader;
- shader.begin();
+ shader.bind();
{
+ lightShader.setUniformMatrix("u_projTrans", combined);
shader.setUniformMatrix("u_projTrans", combined);
if (customLightShader != null) updateLightShader();
+
for (Light light : lightList) {
if (customLightShader != null) updateLightShaderPerLight(light);
light.render();
}
}
- shader.end();
if (useLightMap) {
if (customViewport) {
@@ -331,12 +360,34 @@ public void prepareRender() {
} else {
lightMap.frameBuffer.end();
}
+ }
+
+ if (useLightMap && pseudo3d) {
+ lightMap.shadowBuffer.begin();
+ Gdx.gl.glClearColor(0f, 0f, 0f, 0f);
+ Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
+
+ for (Light light : lightList) {
+ light.dynamicShadowRender();
+ }
- boolean needed = lightRenderedLastFrame > 0;
- // this way lot less binding
- if (needed && blur)
- lightMap.gaussianBlur();
+ if (customViewport) {
+ lightMap.shadowBuffer.end(
+ viewportX,
+ viewportY,
+ viewportWidth,
+ viewportHeight);
+ } else {
+ lightMap.shadowBuffer.end();
+ }
}
+
+ boolean needed = lightRenderedLastFrame > 0;
+ // this way lot less binding
+ if (needed && blur)
+ lightMap.gaussianBlur(lightMap.frameBuffer, blurNum);
+ if (needed && blur && pseudo3d)
+ lightMap.gaussianBlur(lightMap.shadowBuffer, blurNum);
}
/**
@@ -571,9 +622,10 @@ public static boolean getGammaCorrection() {
* NOTE: To match the visuals with gamma uncorrected lights the light
* distance parameters is modified implicitly.
*/
- public static void setGammaCorrection(boolean gammaCorrectionWanted) {
+ public void applyGammaCorrection(boolean gammaCorrectionWanted) {
gammaCorrection = gammaCorrectionWanted;
gammaCorrectionParameter = gammaCorrection ? GAMMA_COR : 1f;
+ lightMap.createShaders();
}
/**
@@ -583,10 +635,35 @@ public static void setGammaCorrection(boolean gammaCorrectionWanted) {
* more realistic model than normally used as it preserve colors but might
* look bit darker and also it might improve performance slightly.
*/
- public static void useDiffuseLight(boolean useDiffuse) {
+ public void setDiffuseLight(boolean useDiffuse) {
isDiffuse = useDiffuse;
+ lightMap.createShaders();
}
-
+
+ public static boolean isDiffuseLight() {
+ return isDiffuse;
+ }
+
+ public static float getDynamicShadowColorReduction () {
+ return dynamicShadowColorReduction;
+ }
+
+ /**
+ * Static setters are deprecated, use {@link RayHandlerOptions}
+ */
+ @Deprecated
+ public static void useDiffuseLight(boolean useDiffuse) {
+
+ }
+
+ /**
+ * Static setters are deprecated, use {@link RayHandlerOptions}
+ */
+ @Deprecated
+ public static void setGammaCorrection(boolean gammaCorrectionWanted) {
+
+ }
+
/**
* Sets rendering to custom viewport with specified position and size
*
Note: you will be responsible for update of viewport via this method
@@ -609,6 +686,28 @@ public void useDefaultViewport() {
customViewport = false;
}
+ /**
+ * /!\ Experimental mode with dynamic shadowing in pseudo-3d world
+ *
+ * @param flag enable pseudo 3d effect
+ */
+ public void setPseudo3dLight(boolean flag) {
+ setPseudo3dLight(flag, false);
+ }
+
+ /**
+ * /!\ Experimental mode with dynamic shadowing in pseudo-3d world
+ *
+ * @param flag enable pseudo 3d effect
+ * @param interpolateShadows interpolate shadow color
+ */
+ public void setPseudo3dLight(boolean flag, boolean interpolateShadows) {
+ pseudo3d = flag;
+ shadowColorInterpolation = interpolateShadows;
+
+ lightMap.createShaders();
+ }
+
/**
* Enables/disables lightMap automatic rendering.
*
@@ -641,5 +740,4 @@ public Texture getLightMapTexture() {
public FrameBuffer getLightMapBuffer() {
return lightMap.frameBuffer;
}
-
}
diff --git a/src/box2dLight/RayHandlerOptions.java b/src/box2dLight/RayHandlerOptions.java
new file mode 100644
index 0000000..1abe7dd
--- /dev/null
+++ b/src/box2dLight/RayHandlerOptions.java
@@ -0,0 +1,26 @@
+package box2dLight;
+
+public class RayHandlerOptions {
+ boolean gammaCorrection = false;
+ boolean isDiffuse = false;
+
+ boolean pseudo3d = false;
+ boolean shadowColorInterpolation = false;
+
+ public void setDiffuse (boolean diffuse) {
+ isDiffuse = diffuse;
+ }
+
+ public void setGammaCorrection (boolean gammaCorrection) {
+ this.gammaCorrection = gammaCorrection;
+ }
+
+ public void setPseudo3d (boolean pseudo3d) {
+ setPseudo3d(pseudo3d, false);
+ }
+
+ public void setPseudo3d (boolean pseudo3d, boolean shadowColorInterpolation) {
+ this.pseudo3d = pseudo3d;
+ this.shadowColorInterpolation = shadowColorInterpolation;
+ }
+}
diff --git a/src/shaders/DiffuseShader.java b/src/shaders/DiffuseShader.java
index ed0895e..92ec9e3 100644
--- a/src/shaders/DiffuseShader.java
+++ b/src/shaders/DiffuseShader.java
@@ -15,7 +15,7 @@ static final public ShaderProgram createShadowShader() {
+ " gl_Position = a_position;\n" //
+ "}\n";
- // this is allways perfect precision
+ // this is always perfect precision
final String fragmentShader = "#ifdef GL_ES\n" //
+ "precision lowp float;\n" //
+ "#define MED mediump\n"
diff --git a/src/shaders/DynamicShadowShader.java b/src/shaders/DynamicShadowShader.java
new file mode 100644
index 0000000..ec6029a
--- /dev/null
+++ b/src/shaders/DynamicShadowShader.java
@@ -0,0 +1,62 @@
+package shaders;
+
+import box2dLight.RayHandler;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.glutils.ShaderProgram;
+
+public class DynamicShadowShader {
+ static final public ShaderProgram createShadowShader() {
+ final String vertexShader = "attribute vec4 a_position;\n" //
+ + "attribute vec2 a_texCoord;\n" //
+ + "varying vec2 v_texCoords;\n" //
+ + "\n" //
+ + "void main()\n" //
+ + "{\n" //
+ + " v_texCoords = a_texCoord;\n" //
+ + " gl_Position = a_position;\n" //
+ + "}\n";
+
+ // this is always perfect precision
+ final String fragmentShader = "#ifdef GL_ES\n" //
+ + "precision lowp float;\n" //
+ + "#define MED mediump\n"
+ + "#else\n"
+ + "#define MED \n"
+ + "#endif\n" //
+ + "varying MED vec2 v_texCoords;\n" //
+ + "uniform sampler2D u_texture;\n" //
+ + "uniform sampler2D u_shadows;\n" //
+ + "uniform vec4 ambient;\n"
+ + "uniform int isDiffuse;\n" //
+ + "void main()\n"//
+ + "{\n" //
+ + "if(isDiffuse == 0)\n"//
+ + "{\n"//
+ + "vec4 c = texture2D(u_texture, v_texCoords);\n"//
+ + "vec4 sh = texture2D(u_shadows, v_texCoords);\n"//
+ + "gl_FragColor.rgb = (ambient.rgb + c.rgb * c.a) - sh.rgb;\n"//
+ + "gl_FragColor.a = ambient.a - c.a;\n"//
+ + "}\n"//
+ + "else\n"//
+ + "{\n"//
+ + " vec4 c = texture2D(u_texture, v_texCoords);\n"//
+ + " vec4 sh = texture2D(u_shadows, v_texCoords);\n"//
+ + " gl_FragColor.rgb = (ambient.rgb + (" + RayHandler.getDynamicShadowColorReduction() + " * c.rgb)) - sh.rgb;\n"
+ + " gl_FragColor.a = 1.0;\n"//
+ + "}\n"//
+ + "}\n";
+ ShaderProgram.pedantic = false;
+ ShaderProgram shadowShader = new ShaderProgram(vertexShader,
+ fragmentShader);
+ if (!shadowShader.isCompiled()) {
+ shadowShader = new ShaderProgram("#version 330 core\n" +vertexShader,
+ "#version 330 core\n" +fragmentShader);
+ if(!shadowShader.isCompiled()){
+ Gdx.app.log("ERROR", shadowShader.getLog());
+ }
+ }
+
+ return shadowShader;
+ }
+
+}
diff --git a/src/shaders/Gaussian.java b/src/shaders/Gaussian.java
index 119752d..c822509 100644
--- a/src/shaders/Gaussian.java
+++ b/src/shaders/Gaussian.java
@@ -10,7 +10,7 @@ public class Gaussian {
public static ShaderProgram createBlurShader(int width, int heigth) {
final String FBO_W = Integer.toString(width);
final String FBO_H = Integer.toString(heigth);
- final String rgb = RayHandler.isDiffuse ? ".rgb" : "";
+ final String rgb = RayHandler.isDiffuseLight() ? ".rgb" : "";
final String vertexShader = "attribute vec4 a_position;\n" //
+ "uniform vec2 dir;\n" //
+ "attribute vec2 a_texCoord;\n" //
diff --git a/test/tests/Box2dLightCustomShaderTest.java b/test/tests/Box2dLightCustomShaderTest.java
index be32ae6..acbbc02 100644
--- a/test/tests/Box2dLightCustomShaderTest.java
+++ b/test/tests/Box2dLightCustomShaderTest.java
@@ -110,13 +110,13 @@ public void create() {
normalProjection.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
/** BOX2D LIGHT STUFF BEGIN */
- RayHandler.setGammaCorrection(true);
- RayHandler.useDiffuseLight(true);
-
normalShader = createNormalShader();
lightShader = createLightShader();
- rayHandler = new RayHandler(world, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()) {
+ RayHandlerOptions options = new RayHandlerOptions();
+ options.setDiffuse(true);
+ options.setGammaCorrection(true);
+ rayHandler = new RayHandler(world, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), options) {
@Override protected void updateLightShader () {}
@Override protected void updateLightShaderPerLight (Light light) {
diff --git a/test/tests/Box2dLightTest.java b/test/tests/Box2dLightTest.java
index 2c82ad0..e67a538 100644
--- a/test/tests/Box2dLightTest.java
+++ b/test/tests/Box2dLightTest.java
@@ -2,12 +2,7 @@
import java.util.ArrayList;
-import box2dLight.ChainLight;
-import box2dLight.ConeLight;
-import box2dLight.DirectionalLight;
-import box2dLight.Light;
-import box2dLight.PointLight;
-import box2dLight.RayHandler;
+import box2dLight.*;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
@@ -104,10 +99,10 @@ public void create() {
0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
/** BOX2D LIGHT STUFF BEGIN */
- RayHandler.setGammaCorrection(true);
- RayHandler.useDiffuseLight(true);
-
- rayHandler = new RayHandler(world);
+ RayHandlerOptions options = new RayHandlerOptions();
+ options.setDiffuse(true);
+ options.setGammaCorrection(true);
+ rayHandler = new RayHandler(world, options);
rayHandler.setAmbientLight(0f, 0f, 0f, 0.5f);
rayHandler.setBlurNum(3);
diff --git a/test/tests/Box2dLightTest2.java b/test/tests/Box2dLightTest2.java
new file mode 100644
index 0000000..d39b091
--- /dev/null
+++ b/test/tests/Box2dLightTest2.java
@@ -0,0 +1,542 @@
+package tests;
+
+import java.util.ArrayList;
+
+import box2dLight.*;
+import com.badlogic.gdx.Application;
+
+import com.badlogic.gdx.ApplicationListener;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.Input;
+import com.badlogic.gdx.InputAdapter;
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.GL20;
+import com.badlogic.gdx.graphics.OrthographicCamera;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.SpriteBatch;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.math.MathUtils;
+import com.badlogic.gdx.math.Matrix4;
+import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.physics.box2d.Body;
+import com.badlogic.gdx.physics.box2d.BodyDef;
+import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
+import com.badlogic.gdx.physics.box2d.ChainShape;
+import com.badlogic.gdx.physics.box2d.Fixture;
+import com.badlogic.gdx.physics.box2d.FixtureDef;
+import com.badlogic.gdx.physics.box2d.PolygonShape;
+import com.badlogic.gdx.physics.box2d.QueryCallback;
+import com.badlogic.gdx.physics.box2d.World;
+import com.badlogic.gdx.physics.box2d.joints.MouseJoint;
+import com.badlogic.gdx.physics.box2d.joints.MouseJointDef;
+
+public class Box2dLightTest2 extends InputAdapter implements ApplicationListener {
+
+ static final int RAYS_PER_BALL = 128;
+ static final int BALLSNUM = 5;
+ static final float LIGHT_DISTANCE = 16f;
+ static final float RADIUS = 1f;
+
+ static final float viewportWidth = 48;
+ static final float viewportHeight = 32;
+
+ OrthographicCamera camera;
+
+ SpriteBatch batch;
+ BitmapFont font;
+ TextureRegion textureRegion;
+ Texture bg;
+
+ /** our box2D world **/
+ World world;
+
+ /** our boxes **/
+ ArrayList
balls = new ArrayList(BALLSNUM);
+
+ /** our ground box **/
+ Body groundBody;
+
+ /** our mouse joint **/
+ MouseJoint mouseJoint = null;
+
+ /** a hit body **/
+ Body hitBody = null;
+
+ /** pixel perfect projection for font rendering */
+ Matrix4 normalProjection = new Matrix4();
+
+ boolean showText = true;
+
+ /** BOX2D LIGHT STUFF */
+ RayHandler rayHandler;
+
+ ArrayList lights = new ArrayList(BALLSNUM);
+
+ float sunDirection = -90f;
+
+ @Override
+ public void create() {
+ Gdx.app.setLogLevel(Application.LOG_DEBUG);
+ MathUtils.random.setSeed(Long.MIN_VALUE);
+
+ camera = new OrthographicCamera(viewportWidth, viewportHeight);
+ camera.position.set(0, viewportHeight / 2f, 0);
+ camera.update();
+
+ batch = new SpriteBatch();
+ font = new BitmapFont();
+ font.setColor(Color.RED);
+
+ textureRegion = new TextureRegion(new Texture(
+ Gdx.files.internal("test/data/marble.png")));
+ bg = new Texture(Gdx.files.internal("test/data/bg.png"));
+
+ createPhysicsWorld();
+ Gdx.input.setInputProcessor(this);
+
+ normalProjection.setToOrtho2D(
+ 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
+
+ RayHandlerOptions options = new RayHandlerOptions();
+ options.setDiffuse(true);
+ options.setGammaCorrection(true);
+ options.setPseudo3d(true);
+ /** BOX2D LIGHT STUFF BEGIN */
+ rayHandler = new RayHandler(world, options);
+ //rayHandler.setAmbientLight(0f, 0f, 0f, 0.5f);
+ //rayHandler.setBlurNum(3);
+ rayHandler.setShadows(true);
+ initPointLights();
+ /** BOX2D LIGHT STUFF END */
+
+ }
+
+ @Override
+ public void render() {
+
+ /** Rotate directional light like sun :) */
+ if (lightsType == 3) {
+ sunDirection += Gdx.graphics.getDeltaTime() * 8f;
+ float degrees = (sunDirection % 360);
+ lights.get(0).setDirection(degrees);
+ lights.get(0).setHeight(degrees);
+ Gdx.app.debug("Degrees:", degrees+"");
+ }
+
+ camera.update();
+
+ boolean stepped = fixedStep(Gdx.graphics.getDeltaTime());
+ Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1);
+ Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BITS | GL20.GL_BLEND_SRC_ALPHA);
+
+ batch.setProjectionMatrix(camera.combined);
+ batch.disableBlending();
+ batch.begin();
+ {
+ batch.draw(bg, -viewportWidth / 2f, 0, viewportWidth, viewportHeight);
+ batch.enableBlending();
+ for (int i = 0; i < BALLSNUM; i++) {
+ Body ball = balls.get(i);
+ Vector2 position = ball.getPosition();
+ float angle = MathUtils.radiansToDegrees * ball.getAngle();
+ batch.draw(
+ textureRegion,
+ position.x - RADIUS, position.y - RADIUS,
+ RADIUS, RADIUS,
+ RADIUS * 2, RADIUS * 2,
+ 1f, 1f,
+ angle);
+ }
+ }
+ batch.end();
+
+ /** BOX2D LIGHT STUFF BEGIN */
+ rayHandler.setCombinedMatrix(camera);
+
+ if (stepped) rayHandler.update();
+ rayHandler.render();
+ /** BOX2D LIGHT STUFF END */
+
+ long time = System.nanoTime();
+
+ boolean atShadow = rayHandler.pointAtShadow(testPoint.x,
+ testPoint.y);
+ aika += System.nanoTime() - time;
+
+ /** FONT */
+ if (showText) {
+ batch.setProjectionMatrix(normalProjection);
+ batch.begin();
+
+ font.draw(batch,
+ "F1 - PointLight",
+ 0, Gdx.graphics.getHeight());
+ font.draw(batch,
+ "F2 - ConeLight",
+ 0, Gdx.graphics.getHeight() - 15);
+ font.draw(batch,
+ "F3 - ChainLight",
+ 0, Gdx.graphics.getHeight() - 30);
+ font.draw(batch,
+ "F4 - DirectionalLight",
+ 0, Gdx.graphics.getHeight() - 45);
+ font.draw(batch,
+ "F5 - random lights colors",
+ 0, Gdx.graphics.getHeight() - 75);
+ font.draw(batch,
+ "F6 - random lights distance",
+ 0, Gdx.graphics.getHeight() - 90);
+ font.draw(batch,
+ "F9 - default blending (1.3)",
+ 0, Gdx.graphics.getHeight() - 120);
+ font.draw(batch,
+ "F10 - over-burn blending (default in 1.2)",
+ 0, Gdx.graphics.getHeight() - 135);
+ font.draw(batch,
+ "F11 - some other blending",
+ 0, Gdx.graphics.getHeight() - 150);
+
+ font.draw(batch,
+ "F12 - toggle help text",
+ 0, Gdx.graphics.getHeight() - 180);
+
+ font.draw(batch,
+ Integer.toString(Gdx.graphics.getFramesPerSecond())
+ + "mouse at shadows: " + atShadow
+ + " time used for shadow calculation:"
+ + aika / ++times + "ns" , 0, 20);
+
+ batch.end();
+ }
+ }
+
+ void clearLights() {
+ if (lights.size() > 0) {
+ for (Light light : lights) {
+ light.remove();
+ }
+ lights.clear();
+ }
+ groundBody.setActive(true);
+ }
+
+ void initPointLights() {
+ clearLights();
+ for (int i = 0; i < BALLSNUM; i++) {
+ PointLight light = new PointLight(
+ rayHandler, RAYS_PER_BALL, null, LIGHT_DISTANCE, 0f, 0f);
+ light.attachToBody(balls.get(i), RADIUS / 2f, RADIUS / 2f);
+ light.setColor(
+ MathUtils.random(),
+ MathUtils.random(),
+ MathUtils.random(),
+ 1f);
+ light.setHeight(i+2);
+ lights.add(light);
+ }
+ }
+
+ void initConeLights() {
+ clearLights();
+ for (int i = 0; i < BALLSNUM; i++) {
+ ConeLight light = new ConeLight(
+ rayHandler, RAYS_PER_BALL, null, LIGHT_DISTANCE,
+ 0, 0, 0f, MathUtils.random(15f, 40f));
+ light.attachToBody(
+ balls.get(i),
+ RADIUS / 2f, RADIUS / 2f, MathUtils.random(0f, 360f));
+ light.setColor(
+ MathUtils.random(),
+ MathUtils.random(),
+ MathUtils.random(),
+ 1f);
+ lights.add(light);
+ }
+ }
+
+ void initChainLights() {
+ clearLights();
+ for (int i = 0; i < BALLSNUM; i++) {
+ ChainLight light = new ChainLight(
+ rayHandler, RAYS_PER_BALL, null, LIGHT_DISTANCE, 1,
+ new float[]{-5, 0, 0, 3, 5, 0});
+ light.attachToBody(
+ balls.get(i),
+ MathUtils.random(0f, 360f));
+ light.setColor(
+ MathUtils.random(),
+ MathUtils.random(),
+ MathUtils.random(),
+ 1f);
+ lights.add(light);
+ }
+ }
+
+ void initDirectionalLight() {
+ clearLights();
+
+ groundBody.setActive(false);
+ sunDirection = MathUtils.random(0f, 360f);
+
+ DirectionalLight light = new DirectionalLight(
+ rayHandler, 4 * RAYS_PER_BALL, new Color(1, 1, 1, 0.5f), sunDirection);
+ light.setHeight(0);
+ lights.add(light);
+ }
+
+ private final static int MAX_FPS = 30;
+ private final static int MIN_FPS = 15;
+ public final static float TIME_STEP = 1f / MAX_FPS;
+ private final static float MAX_STEPS = 1f + MAX_FPS / MIN_FPS;
+ private final static float MAX_TIME_PER_FRAME = TIME_STEP * MAX_STEPS;
+ private final static int VELOCITY_ITERS = 6;
+ private final static int POSITION_ITERS = 2;
+
+ float physicsTimeLeft;
+ long aika;
+ int times;
+
+ private boolean fixedStep(float delta) {
+ physicsTimeLeft += delta;
+ if (physicsTimeLeft > MAX_TIME_PER_FRAME)
+ physicsTimeLeft = MAX_TIME_PER_FRAME;
+
+ boolean stepped = false;
+ while (physicsTimeLeft >= TIME_STEP) {
+ world.step(TIME_STEP, VELOCITY_ITERS, POSITION_ITERS);
+ physicsTimeLeft -= TIME_STEP;
+ stepped = true;
+ }
+ return stepped;
+ }
+
+ private void createPhysicsWorld() {
+
+ world = new World(new Vector2(0, 0), true);
+
+ float halfWidth = viewportWidth / 2f;
+ ChainShape chainShape = new ChainShape();
+ chainShape.createLoop(new Vector2[] {
+ new Vector2(-halfWidth, 0f),
+ new Vector2(halfWidth, 0f),
+ new Vector2(halfWidth, viewportHeight),
+ new Vector2(-halfWidth, viewportHeight) });
+ BodyDef chainBodyDef = new BodyDef();
+ chainBodyDef.type = BodyType.StaticBody;
+ groundBody = world.createBody(chainBodyDef);
+ groundBody.createFixture(chainShape, 0);
+ chainShape.dispose();
+ createBoxes();
+ }
+
+ private void createBoxes() {
+ PolygonShape polygonShape = new PolygonShape();
+ polygonShape.setAsBox(RADIUS, RADIUS);
+ FixtureDef def = new FixtureDef();
+ def.restitution = 0.9f;
+ def.friction = 0.01f;
+ def.shape = polygonShape;
+ def.density = 1f;
+ BodyDef boxBodyDef = new BodyDef();
+ boxBodyDef.type = BodyType.DynamicBody;
+
+ for (int i = 0; i < BALLSNUM; i++) {
+ // Create the BodyDef, set a random position above the
+ // ground and create a new body
+ boxBodyDef.position.x = -20 + (float) (Math.random() * 40);
+ boxBodyDef.position.y = 10 + (float) (Math.random() * 15);
+ Body boxBody = world.createBody(boxBodyDef);
+ boxBody.createFixture(def).setUserData(new LightData(1,true));
+ balls.add(boxBody);
+ }
+ polygonShape.dispose();
+ }
+
+ /**
+ * we instantiate this vector and the callback here so we don't irritate the
+ * GC
+ **/
+ Vector3 testPoint = new Vector3();
+ QueryCallback callback = new QueryCallback() {
+ @Override
+ public boolean reportFixture(Fixture fixture) {
+ if (fixture.getBody() == groundBody)
+ return true;
+
+ if (fixture.testPoint(testPoint.x, testPoint.y)) {
+ hitBody = fixture.getBody();
+ return false;
+ } else
+ return true;
+ }
+ };
+
+ @Override
+ public boolean touchDown(int x, int y, int pointer, int newParam) {
+ // translate the mouse coordinates to world coordinates
+ testPoint.set(x, y, 0);
+ camera.unproject(testPoint);
+
+ // ask the world which bodies are within the given
+ // bounding box around the mouse pointer
+ hitBody = null;
+ world.QueryAABB(callback, testPoint.x - 0.1f, testPoint.y - 0.1f,
+ testPoint.x + 0.1f, testPoint.y + 0.1f);
+
+ // if we hit something we create a new mouse joint
+ // and attach it to the hit body.
+ if (hitBody != null) {
+ MouseJointDef def = new MouseJointDef();
+ def.bodyA = groundBody;
+ def.bodyB = hitBody;
+ def.collideConnected = true;
+ def.target.set(testPoint.x, testPoint.y);
+ def.maxForce = 1000.0f * hitBody.getMass();
+
+ mouseJoint = (MouseJoint) world.createJoint(def);
+ hitBody.setAwake(true);
+ }
+
+ return false;
+ }
+
+ /** another temporary vector **/
+ Vector2 target = new Vector2();
+
+ @Override
+ public boolean touchDragged(int x, int y, int pointer) {
+ camera.unproject(testPoint.set(x, y, 0));
+ target.set(testPoint.x, testPoint.y);
+ // if a mouse joint exists we simply update
+ // the target of the joint based on the new
+ // mouse coordinates
+ if (mouseJoint != null) {
+ mouseJoint.setTarget(target);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean touchUp(int x, int y, int pointer, int button) {
+ // if a mouse joint exists we simply destroy it
+ if (mouseJoint != null) {
+ world.destroyJoint(mouseJoint);
+ mouseJoint = null;
+ }
+ return false;
+ }
+
+ @Override
+ public void dispose() {
+ rayHandler.dispose();
+ world.dispose();
+ }
+
+ /**
+ * Type of lights to use:
+ * 0 - PointLight
+ * 1 - ConeLight
+ * 2 - ChainLight
+ * 3 - DirectionalLight
+ */
+ int lightsType = 0;
+
+ @Override
+ public boolean keyDown(int keycode) {
+ switch (keycode) {
+
+ case Input.Keys.F1:
+ if (lightsType != 0) {
+ initPointLights();
+ lightsType = 0;
+ }
+ return true;
+
+ case Input.Keys.F2:
+ if (lightsType != 1) {
+ initConeLights();
+ lightsType = 1;
+ }
+ return true;
+
+ case Input.Keys.F3:
+ if (lightsType != 2) {
+ initChainLights();
+ lightsType = 2;
+ }
+ return true;
+
+ case Input.Keys.F4:
+ if (lightsType != 3) {
+ initDirectionalLight();
+ lightsType = 3;
+ }
+ return true;
+
+ case Input.Keys.F5:
+ for (Light light : lights)
+ light.setColor(
+ MathUtils.random(),
+ MathUtils.random(),
+ MathUtils.random(),
+ 1f);
+ return true;
+
+ case Input.Keys.F6:
+ for (Light light : lights)
+ light.setDistance(MathUtils.random(
+ LIGHT_DISTANCE * 0.5f, LIGHT_DISTANCE * 2f));
+ return true;
+
+ case Input.Keys.F9:
+ rayHandler.diffuseBlendFunc.reset();
+ return true;
+
+ case Input.Keys.F10:
+ rayHandler.diffuseBlendFunc.set(
+ GL20.GL_DST_COLOR, GL20.GL_SRC_COLOR);
+ return true;
+
+ case Input.Keys.F11:
+ rayHandler.diffuseBlendFunc.set(
+ GL20.GL_SRC_COLOR, GL20.GL_DST_COLOR);
+ return true;
+
+ case Input.Keys.F12:
+ showText = !showText;
+ return true;
+
+ default:
+ return false;
+
+ }
+ }
+
+ @Override
+ public boolean mouseMoved(int x, int y) {
+ testPoint.set(x, y, 0);
+ camera.unproject(testPoint);
+ return false;
+ }
+
+ @Override
+ public boolean scrolled(int amount) {
+ camera.rotate((float) amount * 3f, 0, 0, 1);
+ return false;
+ }
+
+ @Override
+ public void pause() {
+ }
+
+ @Override
+ public void resize(int arg0, int arg1) {
+ }
+
+ @Override
+ public void resume() {
+ }
+
+}
diff --git a/test/tests/DesktopLauncher.java b/test/tests/DesktopLauncher.java
index 3f22486..a2cd60b 100644
--- a/test/tests/DesktopLauncher.java
+++ b/test/tests/DesktopLauncher.java
@@ -15,7 +15,7 @@ public static void main(String[] argv) {
config.vSyncEnabled = true;
config.fullscreen = false;
- new LwjglApplication(new Box2dLightTest(), config);
+ new LwjglApplication(new Box2dLightTest2(), config);
}
}