From 39b27658d2a97db5840f48f48a6cbc310c987a47 Mon Sep 17 00:00:00 2001 From: rinold Date: Wed, 4 Feb 2015 20:25:02 +0300 Subject: [PATCH] Experimental pseudo-3d support for PointLight Experimental feature (#40) Only PolygonShapes currently drop dynamic shadow --- src/box2dLight/Light.java | 21 ++++++++ src/box2dLight/LightData.java | 13 +++++ src/box2dLight/PointLight.java | 80 +++++++++++++++++++++++++++++ src/box2dLight/PositionalLight.java | 47 +++++++++++++++-- src/box2dLight/RayHandler.java | 36 ++++++++++++- 5 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 src/box2dLight/LightData.java diff --git a/src/box2dLight/Light.java b/src/box2dLight/Light.java index 4a33031..1b9f6f7 100644 --- a/src/box2dLight/Light.java +++ b/src/box2dLight/Light.java @@ -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; /** @@ -47,6 +48,7 @@ public abstract class Light implements Disposable { protected Mesh lightMesh; protected Mesh softShadowMesh; + protected Array dynamicShadowMeshes = new Array(); protected float segments[]; protected float[] mx; @@ -90,6 +92,13 @@ public Light(RayHandler rayHandler, int rays, Color color, */ abstract void render(); + /** + * Render this light shadow + */ + void dynamicShadowRender() { + + } + /** * Sets light distance * @@ -223,6 +232,10 @@ public void remove() { public void dispose() { lightMesh.dispose(); softShadowMesh.dispose(); + for (Mesh mesh : dynamicShadowMeshes) { + mesh.dispose(); + } + dynamicShadowMeshes.clear(); } /** @@ -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; + } } diff --git a/src/box2dLight/LightData.java b/src/box2dLight/LightData.java new file mode 100644 index 0000000..a8dfb27 --- /dev/null +++ b/src/box2dLight/LightData.java @@ -0,0 +1,13 @@ +package box2dLight; + +public class LightData { + + float height; + + int shadowsDropped = 0; + + public LightData(float h) { + height = h; + } + +} diff --git a/src/box2dLight/PointLight.java b/src/box2dLight/PointLight.java index 77a90ae..1e0955a 100644 --- a/src/box2dLight/PointLight.java +++ b/src/box2dLight/PointLight.java @@ -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 @@ -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); + } + } } /** @@ -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); diff --git a/src/box2dLight/PositionalLight.java b/src/box2dLight/PositionalLight.java index 41f0dab..e24d61f 100644 --- a/src/box2dLight/PositionalLight.java +++ b/src/box2dLight/PositionalLight.java @@ -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 @@ -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(); @@ -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 affectedFixtures = new Array(); + + protected float height = 0f; + /** * Creates new positional light and automatically adds it to the specified * {@link RayHandler} instance. @@ -76,6 +86,7 @@ void update() { if (staticLight && !dirty) return; dirty = false; + updateMesh(); } @@ -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, @@ -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() { @@ -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 @@ -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. @@ -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; + } + + }; + } diff --git a/src/box2dLight/RayHandler.java b/src/box2dLight/RayHandler.java index e9063f6..99da914 100644 --- a/src/box2dLight/RayHandler.java +++ b/src/box2dLight/RayHandler.java @@ -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; @@ -72,6 +74,8 @@ public class RayHandler implements Disposable { *

NOTE: DO NOT MODIFY THIS LIST */ final Array disabledLights = new Array(false, 16); + + final Array bodies = new Array(); final LightMap lightMap; final ShaderProgram lightShader; @@ -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; @@ -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(); } @@ -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) { @@ -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(); @@ -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.