From 524a568b9c241b83140d18d3f6ae766cde065382 Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Sun, 8 Oct 2023 20:36:11 +0200 Subject: [PATCH] lumen overlay --- .../maptool/client/ui/zone/ZoneRenderer.java | 19 +- .../ui/zone/gdx/AbstractDrawingDrawer.java | 2 +- .../client/ui/zone/gdx/AreaRenderer.java | 79 +++++--- .../client/ui/zone/gdx/GdxRenderer.java | 178 ++++++++---------- 4 files changed, 156 insertions(+), 122 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java index bab83fd772..120e60b023 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java @@ -28,6 +28,7 @@ import java.awt.font.TextLayout; import java.awt.geom.*; import java.awt.image.BufferedImage; +import java.io.File; import java.text.NumberFormat; import java.util.*; import java.util.List; @@ -38,6 +39,8 @@ import javax.annotation.Nullable; import javax.imageio.ImageIO; import javax.swing.*; + +import net.didion.jwnl.data.Exc; import net.rptools.lib.CodeTimer; import net.rptools.lib.MD5Key; import net.rptools.maptool.client.*; @@ -1587,7 +1590,8 @@ private void renderLumensOverlay( } timer.start("renderLumensOverlay:drawLights:fillArea"); - newG.setPaint(new Color(lightShade, lightShade, lightShade, lightOpacity)); + var color = new Color(lightShade, lightShade, lightShade, lightOpacity); + newG.setPaint(color); newG.fill(lumensLevel.lightArea()); newG.setPaint(new Color(0.f, 0.f, 0.f, 1.f)); @@ -1613,13 +1617,24 @@ private void renderLumensOverlay( timer.stop("renderLumensOverlay:drawLumens"); newG.dispose(); - timer.start("renderLumensOverlay:drawBuffer"); g.drawImage(lumensOverlay, null, 0, 0); timer.stop("renderLumensOverlay:drawBuffer"); } } + private void screenshot(String name, BufferedImage image) { + try { + File outputfile = new File("C:\\Users\\tkunze\\OneDrive\\Desktop\\" + name + "_j2d.png"); + if(outputfile.exists()) + return; + + ImageIO.write(image, "png", outputfile); + } catch (Exception e) { + System.out.println(e.toString()); + } + } + /** * Combines a set of lights into an image that is then rendered into the zone. * diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/gdx/AbstractDrawingDrawer.java b/src/main/java/net/rptools/maptool/client/ui/zone/gdx/AbstractDrawingDrawer.java index 7f85af11d2..1793ae7288 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/gdx/AbstractDrawingDrawer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/gdx/AbstractDrawingDrawer.java @@ -71,7 +71,7 @@ protected void line(PolygonSpriteBatch batch, Pen pen, float x1, float y1, float floats.add(x1, y1, x2, y2); var path = areaRenderer.path( - floats, + floats.toArray(), pen.getThickness(), pen.getSquareCap() ? AreaRenderer.JoinType.Pointy : AreaRenderer.JoinType.Round, false); diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/gdx/AreaRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/gdx/AreaRenderer.java index 455c13c315..5799f1b5e0 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/gdx/AreaRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/gdx/AreaRenderer.java @@ -25,6 +25,8 @@ import java.awt.geom.PathIterator; import java.util.ArrayList; import java.util.List; + +import com.badlogic.gdx.utils.IntArray; import net.rptools.lib.gdx.Joiner; import space.earlygrey.shapedrawer.DefaultSideEstimator; import space.earlygrey.shapedrawer.ShapeDrawer; @@ -45,6 +47,8 @@ public AreaRenderer( private FloatArray tmpFloat = new FloatArray(); + private IntArray segmentIndicies = new IntArray(); + private Color color; public void setColor(Color value) { @@ -76,6 +80,12 @@ public void fillArea(PolygonSpriteBatch batch, Area area) { if (area == null || area.isEmpty()) return; pathToFloatArray(area.getPathIterator(null)); + while (tmpFloat.get(0) == tmpFloat.get(tmpFloat.size - 2) + && tmpFloat.get(1) == tmpFloat.get(tmpFloat.size - 1)) { + // make sure we don't have last and first point the same + tmpFloat.pop(); + tmpFloat.pop(); + } paintVertices(batch, tmpFloat.toArray()); } @@ -85,10 +95,29 @@ public void drawArea(PolygonSpriteBatch batch, Area area, boolean rounded, float if (area == null || area.isEmpty()) return; pathToFloatArray(area.getPathIterator(null)); - var vertices = path(tmpFloat, thickness, rounded ? JoinType.Round : JoinType.Pointy, false); + float[] vertices = null; + var floats = tmpFloat.toArray(); + if(segmentIndicies.size == 1) { + vertices = path(floats, thickness, rounded ? JoinType.Round : JoinType.Pointy, false); + paintVertices(batch, vertices); + } else { + var lastSegmentIndex = 0; + var color = this.color; + for(int i=0; i(); var inner = new ArrayList(); float halfWidth = lineWidth / 2f; - if (path.size == 2) { - var x = path.get(0); - var y = path.get(1); + if (path.length == 2) { + var x = path[0]; + var y = path[1]; if (joinType == JoinType.Round) { addArc(outer, x, y, halfWidth, 0, MathUtils.PI2 - 0.1f, false); } else { @@ -255,9 +286,9 @@ public float[] path(FloatArray path, float lineWidth, JoinType joinType, boolean outer.add(y - halfWidth); } - } else if (path.size == 4) { - A.set(path.get(0), path.get(1)); - B.set(path.get(2), path.get(3)); + } else if (path.length == 4) { + A.set(path[0], path[1]); + B.set(path[2], path[3]); if (joinType == JoinType.Round) { Joiner.prepareFlatEndpoint(B, A, D, E, halfWidth); E0.set(D); @@ -296,10 +327,10 @@ public float[] path(FloatArray path, float lineWidth, JoinType joinType, boolean } } else { - for (int i = 2; i < path.size - 2; i += 2) { - A.set(path.get(i - 2), path.get(i - 1)); - B.set(path.get(i), path.get(i + 1)); - C.set(path.get(i + 2), path.get(i + 3)); + for (int i = 2; i < path.length - 2; i += 2) { + A.set(path[i - 2], path[i - 1]); + B.set(path[i], path[i + 1]); + C.set(path[i + 2], path[i + 3]); if (i == 2) { if (open) { if (joinType == JoinType.Round) { @@ -324,7 +355,7 @@ public float[] path(FloatArray path, float lineWidth, JoinType joinType, boolean inner.add(E.y); } } else { - vec1.set(path.get(path.size - 2), path.get(path.size - 1)); + vec1.set(path[path.length - 2], path[path.length - 1]); if (joinType == JoinType.Pointy) { Joiner.preparePointyJoin(vec1, A, B, D0, E0, halfWidth); } else { @@ -402,7 +433,7 @@ public float[] path(FloatArray path, float lineWidth, JoinType joinType, boolean } else { if (joinType == JoinType.Pointy) { // draw last link on path - A.set(path.get(0), path.get(1)); + A.set(path[0], path[1]); Joiner.preparePointyJoin(B, C, A, D, E, halfWidth); outer.add(D.x); outer.add(D.y); @@ -419,7 +450,7 @@ public float[] path(FloatArray path, float lineWidth, JoinType joinType, boolean // draw last link on path A.set(B); B.set(C); - C.set(path.get(0), path.get(1)); + C.set(path[0], path[1]); var bendsLeft = Joiner.prepareSmoothJoin(A, B, C, D, E, halfWidth, false); if (bendsLeft) { vec1.set(E); @@ -459,7 +490,7 @@ public float[] path(FloatArray path, float lineWidth, JoinType joinType, boolean A.set(B); B.set(C); - C.set(path.get(2), path.get(3)); + C.set(path[2], path[3]); bendsLeft = Joiner.prepareSmoothJoin(A, B, C, D, E, halfWidth, false); if (bendsLeft) { vec1.set(E); diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/gdx/GdxRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/gdx/GdxRenderer.java index ab01678d0f..cd36339e72 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/gdx/GdxRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/gdx/GdxRenderer.java @@ -48,6 +48,8 @@ import java.util.List; import java.util.zip.Deflater; import javax.swing.*; + +import com.jogamp.opengl.GL2; import net.rptools.lib.CodeTimer; import net.rptools.lib.MD5Key; import net.rptools.lib.gdx.GifDecoder; @@ -1228,110 +1230,97 @@ private void renderLights(PlayerView view) { if (AppState.isShowLumensOverlay()) { // Lumens overlay enabled. timer.start("renderLights:renderLumensOverlay"); - renderLumensOverlay( - view, - view.isGMView() ? null : ZoneRenderer.LightOverlayClipStyle.CLIP_TO_VISIBLE_AREA, - AppPreferences.getLumensOverlayOpacity() / 255.0f); + renderLumensOverlay(view, AppPreferences.getLumensOverlayOpacity() / 255.0f); timer.stop("renderLights:renderLumensOverlay"); } } - private void renderLumensOverlay( - PlayerView view, - ZoneRenderer.LightOverlayClipStyle lightOverlayClipStyle, - float overlayAlpha) { - /* final var disjointLumensLevels = zoneView.getDisjointObscuredLumensLevels(view); - - timer.start("renderLumensOverlay:allocateBuffer"); - try (final var bufferHandle = tempBufferPool.acquire()) { - BufferedImage lumensOverlay = bufferHandle.get(); - timer.stop("renderLumensOverlay:allocateBuffer"); - - Graphics2D newG = lumensOverlay.createGraphics(); - // At night, show any uncovered areas as dark. In daylight, show them as light (clear). - newG.setComposite(AlphaComposite.Src.derive(overlayOpacity)); - newG.setPaint( - zone.getVisionType() == Zone.VisionType.NIGHT - ? new java.awt.Color(0.f, 0.f, 0.f, 1.f) - : new java.awt.Color(0.f, 0.f, 0.f, 0.f)); - newG.fillRect(0, 0, lumensOverlay.getWidth(), lumensOverlay.getHeight()); - - newG.setComposite(AlphaComposite.SrcOver.derive(overlayOpacity)); - SwingUtil.useAntiAliasing(newG); - - if (clipStyle != null && visibleScreenArea != null) { - timer.start("renderLumensOverlay:setClip"); - Area clip = new Area(g.getClip()); - switch (clipStyle) { - case CLIP_TO_VISIBLE_AREA -> clip.intersect(visibleScreenArea); - case CLIP_TO_NOT_VISIBLE_AREA -> clip.subtract(visibleScreenArea); - } - newG.setClip(clip); - g.setClip(clip); - timer.stop("renderLumensOverlay:setClip"); - } + private void renderLumensOverlay(PlayerView view, float overlayAlpha) { + final var disjointLumensLevels = + zoneRenderer.getZoneView().getDisjointObscuredLumensLevels(view); - timer.start("renderLumensOverlay:setTransform"); - AffineTransform af = new AffineTransform(); - af.translate(getViewOffsetX(), getViewOffsetY()); - af.scale(getScale(), getScale()); - newG.setTransform(af); - timer.stop("renderLumensOverlay:setTransform"); - - timer.start("renderLumensOverlay:drawLumens"); - for (final var lumensLevel : disjointLumensLevels) { - final var lumensStrength = lumensLevel.lumensStrength(); - - // Light is weaker than darkness, so do it first. - float lightOpacity; - float lightShade; - if (lumensStrength == 0) { - // This area represents daylight, so draw it as clear despite the low value. - lightShade = 1.f; - lightOpacity = 0; - } else if (lumensStrength >= 100) { - // Bright light, render mostly clear. - lightShade = 1.f; - lightOpacity = 1.f / 10.f; - } else { - lightShade = Math.max(0.f, Math.min(lumensStrength / 100.f, 1.f)); - lightShade *= lightShade; - lightOpacity = 1.f; - } + timer.start("renderLumensOverlay:allocateBuffer"); + batch.flush(); + backBuffer.begin(); + timer.stop("renderLumensOverlay:allocateBuffer"); - timer.start("renderLumensOverlay:drawLights:fillArea"); - newG.setPaint(new java.awt.Color(lightShade, lightShade, lightShade, lightOpacity)); - newG.fill(lumensLevel.lightArea()); + batch.setBlendFunction(GL20.GL_ONE, GL20.GL_NONE); + var A_d = overlayAlpha; + // At night, show any uncovered areas as dark. In daylight, show them as light (clear). + if (zone.getVisionType() == Zone.VisionType.NIGHT) { + ScreenUtils.clear(0,0,0,overlayAlpha); + } else { + A_d = 0; + ScreenUtils.clear(Color.CLEAR); + } - newG.setPaint(new java.awt.Color(0.f, 0.f, 0.f, 1.f)); - newG.fill(lumensLevel.darknessArea()); - timer.stop("renderLumensOverlay:drawLights:fillArea"); - } + //batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + //batch.setBlendFunctionSeparate(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_ONE, GL20.GL_NONE); + + //batch.setBlendFunctionSeparate(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_ONE, GL20.GL_NONE); + batch.setBlendFunctionSeparate(GL20.GL_ONE, GL20.GL_NONE, GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA); + timer.start("renderLumensOverlay:drawLumens"); + for (final var lumensLevel : disjointLumensLevels) { + final var lumensStrength = lumensLevel.lumensStrength(); + + // Light is weaker than darkness, so do it first. + float lightOpacity; + float lightShade; + if (lumensStrength == 0) { + // This area represents daylight, so draw it as clear despite the low value. + lightShade = 1.f; + lightOpacity = 0; + } else if (lumensStrength >= 100) { + // Bright light, render mostly clear. + lightShade = 1.f; + lightOpacity = 1.f / 10.f; + } else { + lightShade = Math.max(0.f, Math.min(lumensStrength / 100.f, 1.f)); + lightShade *= lightShade; + lightOpacity = 1.f; + } - // Now draw borders around each region if configured. - final var borderThickness = AppPreferences.getLumensOverlayBorderThickness(); - if (borderThickness > 0) { - newG.setStroke( - new BasicStroke( - (float) borderThickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - newG.setComposite(AlphaComposite.SrcOver); - newG.setPaint(new java.awt.Color(0.f, 0.f, 0.f, 1.f)); - for (final var lumensLevel : disjointLumensLevels) { - timer.start("renderLumensOverlay:drawLights:drawArea"); - newG.draw(lumensLevel.lightArea()); - newG.draw(lumensLevel.darknessArea()); - timer.stop("renderLumensOverlay:drawLights:drawArea"); - } - } + timer.start("renderLumensOverlay:drawLights:fillArea"); + var A_s = lightOpacity * overlayAlpha; + var A_r = A_s + A_d*(1-A_s); + var C_s = lightShade * A_s; + lightShade = C_s / A_r; - timer.stop("renderLumensOverlay:drawLumens"); - newG.dispose(); + areaRenderer.setColor(tmpColor.set(lightShade, lightShade, lightShade, lightOpacity*overlayAlpha)); + areaRenderer.fillArea(batch, lumensLevel.lightArea()); - timer.start("renderLumensOverlay:drawBuffer"); - g.drawImage(lumensOverlay, null, 0, 0); - timer.stop("renderLumensOverlay:drawBuffer"); - } - */ + areaRenderer.setColor(tmpColor.set(0.f, 0.f, 0.f, overlayAlpha)); + areaRenderer.fillArea(batch, lumensLevel.darknessArea()); + timer.stop("renderLumensOverlay:drawLights:fillArea"); + } + + timer.stop("renderLumensOverlay:drawLumens"); + batch.flush(); + createScreenshot("lumens"); + backBuffer.end(); + + timer.start("renderLumensOverlay:drawBuffer"); + //batch.setColor(1,1,1,overlayAlpha); + batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + setProjectionMatrix(hudCam.combined); + batch.draw(backBuffer.getColorBufferTexture(), 0, 0, width, height, 0, 0, 1, 1); + setProjectionMatrix(cam.combined); + timer.stop("renderLumensOverlay:drawBuffer"); + + // Now draw borders around each region if configured. + batch.setColor(Color.WHITE); + final var borderThickness = AppPreferences.getLumensOverlayBorderThickness(); + if (borderThickness > 0) { + tmpColor.set(0.f, 0.f, 0.f, 1.f); + for (final var lumensLevel : disjointLumensLevels) { + timer.start("renderLumensOverlay:drawLights:drawArea"); + areaRenderer.setColor(tmpColor); + areaRenderer.drawArea(batch, lumensLevel.lightArea(), true, borderThickness); + areaRenderer.setColor(tmpColor); + areaRenderer.drawArea(batch, lumensLevel.darknessArea(), true, borderThickness); + timer.stop("renderLumensOverlay:drawLights:drawArea"); + } + } } private void renderLightOverlay(Collection lights, float alpha) { @@ -1379,7 +1368,6 @@ private void renderLightOverlay(Collection lights, float alpha) { timer.start("renderLightOverlay:drawBuffer"); batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); setProjectionMatrix(hudCam.combined); - // batch.setColor(1, 1, 1, alpha); batch.draw(backBuffer.getColorBufferTexture(), 0, 0, width, height, 0, 0, 1, 1); setProjectionMatrix(cam.combined); // batch.setColor(Color.WHITE);