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 54267eaf40..9f351e8e3f 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 @@ -133,12 +133,6 @@ public class ZoneRenderer extends JComponent private Zone.Layer activeLayer; private String loadingProgress; private boolean isLoaded; - private BufferedImage fogBuffer; - /** - * I don't like this, at all, but it'll work for now, basically keep track of when the fog cache - * needs to be flushed in the case of switching views - */ - private boolean flushFog = true; /** In screen space */ private Area exposedFogArea; @@ -309,7 +303,6 @@ public void setZoneScale(Scale scale) { evt -> { if (Scale.PROPERTY_SCALE.equals(evt.getPropertyName())) { tokenLocationCache.clear(); - flushFog = true; } if (Scale.PROPERTY_OFFSET.equals(evt.getPropertyName())) { // flushFog = true; @@ -654,7 +647,7 @@ public void centerOn(CellPoint point) { /** * Remove the token from: tokenLocationCache, flipImageMap, opacityImageMap, replacementImageMap, * labelRenderingCache. Set the visibleScreenArea, tokenStackMap, drawableLights, drawableAuras to - * null. Flush the fog. Flush the token from the zoneView. + * null. Flush the token from the zoneView. * * @param token the token to flush */ @@ -674,7 +667,6 @@ public void flush(Token token) { // This could also be smarter tokenStackMap = null; - flushFog = true; drawableLights = null; drawableAuras = null; @@ -700,9 +692,9 @@ public void flush() { flushDrawableRenderer(); flipImageMap.clear(); flipIsoImageMap.clear(); - fogBuffer = null; drawableLights = null; drawableAuras = null; + zoneView.flushFog(); isLoaded = false; } @@ -717,7 +709,6 @@ public void flushLight() { /** Set flushFog to true, visibleScreenArea to null, and repaints */ public void flushFog() { - flushFog = true; visibleScreenArea = null; repaintDebouncer.dispatch(); } @@ -1076,11 +1067,9 @@ public Rectangle zoneExtents(PlayerView view) { /** * This method clears {@link #drawableAuras}, {@link #drawableLights}, {@link #visibleScreenArea}, - * and {@link #lastView}. It also flushes the {@link #zoneView} and sets the {@link #flushFog} - * flag so that fog will be recalculated. + * and {@link #lastView}. It also flushes the {@link #zoneView}. */ public void invalidateCurrentViewCache() { - flushFog = true; drawableLights = null; drawableAuras = null; visibleScreenArea = null; @@ -1616,9 +1605,8 @@ private void renderPlayerVisionOverlay(Graphics2D g, PlayerView view) { Area clip = new Area(new Rectangle(getSize().width, getSize().height)); Area viewArea = new Area(exposedFogArea); - List tokens = view.getTokens(); - if (tokens != null && !tokens.isEmpty()) { - for (Token tok : tokens) { + if (view.isUsingTokenView()) { + for (Token tok : view.getTokens()) { ExposedAreaMetaData exposedMeta = zone.getExposedAreaMetaData(tok.getExposedAreaGUID()); viewArea.add(exposedMeta.getExposedAreaHistory()); } @@ -1745,79 +1733,23 @@ private void renderLabels(Graphics2D g, PlayerView view) { timer.stop("labels-1"); } - // Private cache variables just for renderFog() and no one else. :) - Integer fogX = null; - Integer fogY = null; - - private Area renderFog(Graphics2D g, PlayerView view) { + private void renderFog(Graphics2D g, PlayerView view) { Dimension size = getSize(); Area fogClip = new Area(new Rectangle(0, 0, size.width, size.height)); - Area combined = null; - - // Optimization for panning - if (!flushFog - && fogX != null - && fogY != null - && (fogX != getViewOffsetX() || fogY != getViewOffsetY())) { - // This optimization does not seem to keep the alpha channel correctly, and sometimes leaves - // lines on some graphics boards, we'll leave it out for now - // if (Math.abs(fogX - getViewOffsetX()) < size.width && Math.abs(fogY - getViewOffsetY()) < - // size.height) { - // int deltaX = getViewOffsetX() - fogX; - // int deltaY = getViewOffsetY() - fogY; - // - // Graphics2D buffG = fogBuffer.createGraphics(); - // - // buffG.setComposite(AlphaComposite.Src); - // buffG.copyArea(0, 0, size.width, size.height, deltaX, deltaY); - // buffG.dispose(); - // - // fogClip = new Area(); - // if (deltaX < 0) { - // fogClip.add(new Area(new Rectangle(size.width+deltaX, 0, -deltaX, size.height))); - // } else if (deltaX > 0){ - // fogClip.add(new Area(new Rectangle(0, 0, deltaX, size.height))); - // } - // - // if (deltaY < 0) { - // fogClip.add(new Area(new Rectangle(0, size.height + deltaY, size.width, -deltaY))); - // } else if (deltaY > 0) { - // fogClip.add(new Area(new Rectangle(0, 0, size.width, deltaY))); - // } - // } - flushFog = true; - } - boolean cacheNotValid = - (fogBuffer == null - || fogBuffer.getWidth() != size.width - || fogBuffer.getHeight() != size.height); - timer.start("renderFog"); - if (flushFog || cacheNotValid) { - fogX = getViewOffsetX(); - fogY = getViewOffsetY(); - - boolean newImage = false; - if (cacheNotValid) { - newImage = true; - timer.start("renderFog-allocateBufferedImage"); - fogBuffer = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB_PRE); - timer.stop("renderFog-allocateBufferedImage"); - } - Graphics2D buffG = fogBuffer.createGraphics(); + + timer.start("renderFog-allocateBufferedImage"); + try (final var entry = tempBufferPool.acquire()) { + final var buffer = entry.get(); + timer.stop("renderFog-allocateBufferedImage"); + + timer.start("renderFog"); + final var fogX = getViewOffsetX(); + final var fogY = getViewOffsetY(); + + Graphics2D buffG = buffer.createGraphics(); buffG.setClip(fogClip); SwingUtil.useAntiAliasing(buffG); - // XXX Is this even needed? Immediately below is another call to fillRect() with the same - // dimensions! - if (!newImage) { - timer.start("renderFog-clearOldImage"); - // Composite oldComposite = buffG.getComposite(); - buffG.setComposite(AlphaComposite.Clear); - // buffG.fillRect(0, 0, size.width, size.height); // Jamz: Removed as it's called again - // below - // buffG.setComposite(oldComposite); - timer.stop("renderFog-clearOldImage"); - } timer.start("renderFog-fill"); // Fill double scale = getScale(); @@ -1848,84 +1780,23 @@ private Area renderFog(Graphics2D g, PlayerView view) { String msg = null; if (timer.isEnabled()) { - List list = view.getTokens(); - msg = "renderFog-combined(" + (list == null ? 0 : list.size()) + ")"; + msg = "renderFog-combined(" + (view.isUsingTokenView() ? view.getTokens().size() : 0) + ")"; } timer.start(msg); - combined = zone.getExposedArea(view); + Area combined = zoneView.getExposedArea(view); timer.stop(msg); timer.start("renderFogArea"); - Area exposedArea = null; - Area tempArea = new Area(); - boolean combinedView = - !zoneView.isUsingVision() - || MapTool.isPersonalServer() - || !MapTool.getServerPolicy().isUseIndividualFOW() - || view.isGMView(); - - if (view.getTokens() != null) { - // if there are tokens selected combine the areas, then, if individual FOW is enabled - // we pass the combined exposed area to build the soft FOW and visible area. - for (Token tok : view.getTokens()) { - ExposedAreaMetaData meta = zone.getExposedAreaMetaData(tok.getExposedAreaGUID()); - exposedArea = meta.getExposedAreaHistory(); - tempArea.add(new Area(exposedArea)); - } - if (combinedView) { - // combined = zone.getExposedArea(view); - buffG.fill(combined); - renderFogArea(buffG, view, combined, visibleArea); - renderFogOutline(buffG, view, visibleArea); - } else { - // 'combined' already includes the area encompassed by 'tempArea', so just - // use 'combined' instead in this block of code? - tempArea.add(combined); - buffG.fill(tempArea); - renderFogArea(buffG, view, tempArea, visibleArea); - renderFogOutline(buffG, view, visibleArea); - } - } else { - // No tokens selected, so if we are using Individual FOW, we build up all the owned tokens - // exposed area's to build the soft FOW. - if (combinedView) { - if (combined.isEmpty()) { - combined = zone.getExposedArea(); - } - buffG.fill(combined); - renderFogArea(buffG, view, combined, visibleArea); - renderFogOutline(buffG, view, visibleArea); - } else { - Area myCombined = new Area(); - List myToks = zone.getTokens(); - for (Token tok : myToks) { - if (!AppUtil.playerOwns( - tok)) { // Only here if !isGMview() so should the tokens already be in - // PlayerView.getTokens()? - continue; - } - ExposedAreaMetaData meta = zone.getExposedAreaMetaData(tok.getExposedAreaGUID()); - exposedArea = meta.getExposedAreaHistory(); - myCombined.add(new Area(exposedArea)); - } - buffG.fill(myCombined); - renderFogArea(buffG, view, myCombined, visibleArea); - renderFogOutline(buffG, view, visibleArea); - } - } - // renderFogArea(buffG, view, combined, visibleArea); + buffG.fill(combined); + renderFogArea(buffG, view, combined, visibleArea); + renderFogOutline(buffG, view, visibleArea); timer.stop("renderFogArea"); - // timer.start("renderFogOutline"); - // renderFogOutline(buffG, view, combined); - // timer.stop("renderFogOutline"); - buffG.dispose(); - flushFog = false; + timer.stop("renderFog"); + + g.drawImage(buffer, 0, 0, this); } - timer.stop("renderFog"); - g.drawImage(fogBuffer, 0, 0, this); - return combined; } private void renderFogArea( @@ -4822,7 +4693,7 @@ public void modelChanged(ModelChangeEvent event) { } } if (evt == Zone.Event.FOG_CHANGED) { - flushFog = true; + zoneView.flushFog(); } if (evt == Zone.Event.DRAWABLE_ADDED || evt == Zone.Event.DRAWABLE_REMOVED) { DrawnElement de = (DrawnElement) event.getArg(); diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java index e6f07ea7bd..2ff0788984 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java @@ -47,6 +47,8 @@ public class ZoneView implements ModelChangeListener { private final Map> lightSourceMap = new HashMap<>(); /** Map each token to their map between sightType and set of lights. */ private final Map>> drawableLightCache = new HashMap<>(); + /** Map the PlayerView to its exposed area. */ + private final Map exposedAreaMap = new HashMap<>(); /** Map the PlayerView to its visible area. */ private final Map visibleAreaMap = new HashMap<>(); /** Map each token to their personal drawable lights. */ @@ -70,6 +72,39 @@ public ZoneView(Zone zone) { zone.addModelChangeListener(this); } + public Area getExposedArea(PlayerView view) { + Area exposed = exposedAreaMap.get(view); + + if (exposed == null) { + boolean combinedView = + !isUsingVision() + || MapTool.isPersonalServer() + || !MapTool.getServerPolicy().isUseIndividualFOW() + || view.isGMView(); + + if (view.isUsingTokenView() || combinedView) { + exposed = zone.getExposedArea(view); + } else { + // Not a token-specific view, but we are using Individual FoW. So we build up all the owned + // tokens' exposed areas to build the soft FoW. Note that not all owned tokens may still + // have sight (so weren't included in the PlayerView), but could still have previously + // exposed areas. + exposed = new Area(); + for (Token tok : zone.getTokens()) { + if (!AppUtil.playerOwns(tok)) { + continue; + } + ExposedAreaMetaData meta = zone.getExposedAreaMetaData(tok.getExposedAreaGUID()); + Area exposedArea = meta.getExposedAreaHistory(); + exposed.add(new Area(exposedArea)); + } + } + + exposedAreaMap.put(view, exposed); + } + return exposed; + } + /** * Calculate the visible area of the view, cache it in visibleAreaMap, and return it * @@ -588,7 +623,7 @@ public Set getDrawableLights(PlayerView view) { lightSet.addAll(set); } } - if (view != null && view.getTokens() != null) { + if (view != null && view.isUsingTokenView()) { // Get the personal drawable lights of the tokens of the player view for (Token token : view.getTokens()) { Set lights = personalDrawableLightCache.get(token.getId()); @@ -608,11 +643,16 @@ public void flush() { tokenVisibleAreaCache.clear(); tokenVisionCache.clear(); lightSourceCache.clear(); + exposedAreaMap.clear(); visibleAreaMap.clear(); drawableLightCache.clear(); personalDrawableLightCache.clear(); } + public void flushFog() { + exposedAreaMap.clear(); + } + /** * Flush the ZoneView cache of the token. Remove token from tokenVisibleAreaCache, * tokenVisionCache, lightSourceCache, drawableLightCache, and personal light caches. Can clear @@ -632,8 +672,10 @@ public void flush(Token token) { if (hadLightSource || token.hasLightSources()) { // Have to recalculate all token vision tokenVisionCache.clear(); + exposedAreaMap.clear(); visibleAreaMap.clear(); } else if (token.getHasSight()) { + exposedAreaMap.clear(); visibleAreaMap.clear(); } } @@ -739,6 +781,7 @@ public void modelChanged(ModelChangeEvent event) { lightSourceCache.clear(); drawableLightCache.clear(); personalDrawableLightCache.clear(); + exposedAreaMap.clear(); visibleAreaMap.clear(); topologyAreas.clear(); topologyTrees.clear(); @@ -781,7 +824,10 @@ private boolean processTokenAddChangeEvent(List tokens) { hasSight |= token.getHasSight(); } - if (hasSight) visibleAreaMap.clear(); + if (hasSight) { + exposedAreaMap.clear(); + visibleAreaMap.clear(); + } return hasTopology; } diff --git a/src/main/java/net/rptools/maptool/client/walker/astar/AbstractAStarWalker.java b/src/main/java/net/rptools/maptool/client/walker/astar/AbstractAStarWalker.java index 7320ae9f9a..522f41225e 100644 --- a/src/main/java/net/rptools/maptool/client/walker/astar/AbstractAStarWalker.java +++ b/src/main/java/net/rptools/maptool/client/walker/astar/AbstractAStarWalker.java @@ -223,7 +223,7 @@ protected List calculatePath(CellPoint start, CellPoint goal) { newFowExposedArea = zoneRenderer.getZone().hasFog() - ? zoneRenderer.getZone().getExposedArea(zoneRenderer.getPlayerView()) + ? zoneView.getExposedArea(zoneRenderer.getPlayerView()) : null; } diff --git a/src/main/java/net/rptools/maptool/model/Zone.java b/src/main/java/net/rptools/maptool/model/Zone.java index 58b481bc87..612cd2e458 100644 --- a/src/main/java/net/rptools/maptool/model/Zone.java +++ b/src/main/java/net/rptools/maptool/model/Zone.java @@ -740,9 +740,8 @@ public boolean isPointVisible(ZonePoint point, PlayerView view) { } if (MapTool.getServerPolicy().isUseIndividualFOW() && getVisionType() != VisionType.OFF) { Area combined = new Area(exposedArea); - List toks = view.getTokens(); // only owned and HasSight tokens are returned - if (toks != null && !toks.isEmpty()) { - for (Token tok : toks) { + if (view.isUsingTokenView()) { + for (Token tok : view.getTokens()) { // only owned and HasSight tokens are returned ExposedAreaMetaData meta = exposedAreaMeta.get(tok.getExposedAreaGUID()); if (meta != null) { combined.add(meta.getExposedAreaHistory()); @@ -806,11 +805,9 @@ public boolean isTokenVisible(Token token) { Area combined = new Area(exposedArea); PlayerView view = MapTool.getFrame().getZoneRenderer(this).getPlayerView(); if (MapTool.getServerPolicy().isUseIndividualFOW() && getVisionType() != VisionType.OFF) { - List toks = view.getTokens(); - // Jamz: Lets change the logic a bit looking for ownerships - if (toks != null && !toks.isEmpty()) { - for (Token tok : toks) { + if (view.isUsingTokenView()) { + for (Token tok : view.getTokens()) { if (!AppUtil.playerOwns(tok)) { continue; } @@ -844,10 +841,9 @@ public boolean isTokenFootprintVisible(Token token) { Area combined = new Area(exposedArea); PlayerView view = MapTool.getFrame().getZoneRenderer(this).getPlayerView(); if (MapTool.getServerPolicy().isUseIndividualFOW() && getVisionType() != VisionType.OFF) { - List toks = view.getTokens(); - if (toks != null && !toks.isEmpty()) { + if (view.isUsingTokenView()) { // Should this use FindTokenFunctions.OwnedFilter and zone.getTokenList()? - for (Token tok : toks) { + for (Token tok : view.getTokens()) { if (!AppUtil.playerOwns(tok)) { continue; } @@ -1173,14 +1169,13 @@ public ZonePoint getNearestVertex(ZonePoint point) { public Area getExposedArea(PlayerView view) { Area combined = new Area(exposedArea); - List toks = view.getTokens(); // Don't need to worry about StrictTokenOwnership since the PlayerView only contains tokens we // own by calling // AppUtil.playerOwns() - if (toks == null || toks.isEmpty()) { + if (!view.isUsingTokenView()) { return combined; } - for (Token tok : toks) { + for (Token tok : view.getTokens()) { // Don't need this IF statement; see // net.rptools.maptool.client.ui.zone.ZoneRenderer.getPlayerView(Role) // if (!tok.getHasSight() || !AppUtil.playerOwns(tok)) {