Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop rendering clear lights #4225

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public class StatSheetListener {
*/
@Subscribe
public void onHoverEnter(TokenHoverEnter event) {
System.out.println("TokenHoverListener.onHoverEnter");
if (AppPreferences.getShowStatSheet()
&& AppPreferences.getShowStatSheetModifier() == event.shiftDown()) {
var ssManager = new StatSheetManager();
Expand Down Expand Up @@ -69,7 +68,6 @@ public void onHoverEnter(TokenHoverEnter event) {
*/
@Subscribe
public void onHoverExit(TokenHoverExit event) {
System.out.println("TokenHoverListener.onHoverLeave");
MapTool.getFrame().showControlPanel();
if (statSheet != null) {
statSheet.clearContent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,27 @@
package net.rptools.maptool.client.ui.zone;

import java.awt.geom.Area;
import javax.annotation.Nonnull;
import net.rptools.maptool.model.drawing.DrawablePaint;

public class DrawableLight {

private DrawablePaint paint;
private Area area;
private @Nonnull DrawablePaint paint;
private @Nonnull Area area;
private int lumens;

public DrawableLight(DrawablePaint paint, Area area, int lumens) {
public DrawableLight(@Nonnull DrawablePaint paint, @Nonnull Area area, int lumens) {
super();
this.paint = paint;
this.area = area;
this.lumens = lumens;
}

public DrawablePaint getPaint() {
public @Nonnull DrawablePaint getPaint() {
return paint;
}

public Area getArea() {
public @Nonnull Area getArea() {
return area;
}

Expand Down
44 changes: 35 additions & 9 deletions src/main/java/net/rptools/maptool/client/ui/zone/Illumination.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
* <li>The obscured lumens levels. For each light area in the basic structure, subtract out any
* stronger darknesses, and for each darkness subtract out any stronger lights. The result is
* the obscured lit areas arranged by lumens level.
* <li>The complete visible area. This is the union of the light areas after the process in (1).
* <li>The complete lit area. This is the union of the light areas after the process in (1).
* <li>The disjoint obscured lumens levels. Starting from (1), we can additionally subtract strong
* light from weak light and strong darkness from weak darkness so that any given point is
* represented only in the strongest lumens level.
Expand Down Expand Up @@ -105,16 +105,24 @@ public LumensLevel copy() {
* <p>This is derived from {@link #obscuredLumensLevels} by unioning all light areas and leaving
* out all darkness areas.
*/
private Area visibleArea = null;
private Area litArea = null;

/**
* The complete darkened area.
*
* <p>This is derived from {@link #obscuredLumensLevels} by unioning all darkness areas and
* leaving out all light areas.
*/
private Area darkenedArea = null;

// endregion

/**
* Create a new {@code Illumination} from a set of base lumens levels.
*
* <p>The {@code lumensLevels} should contain the complete areas that <emp>could</emp> be covered
* covered by each level of lumens. Obscurement (darkness competing with light) should not already
* be calculated, as the {@code Illumination} will handle this.
* by each level of lumens. Obscurement (darkness competing with light) should not already be
* calculated, as the {@code Illumination} will handle this.
*
* @param lumensLevels The base areas covered by each level of lumens.
*/
Expand Down Expand Up @@ -210,18 +218,36 @@ public Optional<LumensLevel> getObscuredLumensLevel(int lumensStrength) {
* Get the total lit area from all lumens levels.
*
* <p>After subtracting stronger darkness from weaker lights, the resulting lights are unioned
* into a single visible area.
* into a single area.
*
* @return The lit area.
*/
public @Nonnull Area getVisibleArea() {
if (visibleArea == null) {
public @Nonnull Area getLitArea() {
if (litArea == null) {
final var result = new Area();
getObscuredLumensLevels().forEach(level -> result.add(level.lightArea()));
visibleArea = result;
litArea = result;
}

return new Area(litArea);
}

/**
* Get the total dark area from all lumens levels.
*
* <p>After subtracting stronger lights from weaker darkness, the resulting darknesses are unioned
* into a single area.
*
* @return The darkened area.
*/
public @Nonnull Area getDarkenedArea() {
if (darkenedArea == null) {
final var result = new Area();
getObscuredLumensLevels().forEach(level -> result.add(level.darknessArea()));
darkenedArea = result;
}

return new Area(visibleArea);
return new Area(darkenedArea);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import net.rptools.maptool.model.LightSource;

/**
* Manages the light sources and illuminations of a zone, for a given set of illumniator parameters.
* Manages the light sources and illuminations of a zone, for a given set of illuminator parameters.
*
* <p>This needs to be kept in sync with the associated {@code Zone} in order for the results to
* make sense
Expand Down
74 changes: 48 additions & 26 deletions src/main/java/net/rptools/maptool/client/ui/zone/ZoneRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.swing.*;
Expand Down Expand Up @@ -1109,7 +1110,7 @@ public void renderZone(Graphics2D g2d, PlayerView view) {
timer.stop("ZoneRenderer-getVisibleArea");

timer.start("createTransformedArea");
if (a != null && !a.isEmpty()) {
if (!a.isEmpty()) {
visibleScreenArea = a.createTransformedArea(af);
}
timer.stop("createTransformedArea");
Expand All @@ -1120,7 +1121,7 @@ public void renderZone(Graphics2D g2d, PlayerView view) {
{
// renderMoveSelectionSet() requires exposedFogArea to be properly set
exposedFogArea = new Area(zone.getExposedArea());
if (exposedFogArea != null && zone.hasFog()) {
if (zone.hasFog()) {
if (visibleScreenArea != null && !visibleScreenArea.isEmpty()) {
exposedFogArea.intersect(visibleScreenArea);
} else {
Expand Down Expand Up @@ -1195,6 +1196,8 @@ public void renderZone(Graphics2D g2d, PlayerView view) {
timer.stop("auras");
}

renderPlayerDarkness(g2d, view);

/**
* The following sections used to handle rendering of the Hidden (i.e. "GM") layer followed by
* the Token layer. The problem was that we want all drawables to appear below all tokens, and
Expand Down Expand Up @@ -1424,25 +1427,10 @@ private void renderLights(Graphics2D g, PlayerView view) {
overlayBlending,
view.isGMView() ? null : LightOverlayClipStyle.CLIP_TO_VISIBLE_AREA,
drawableLights,
Color.black,
overlayFillColor);
timer.stop("renderLights:renderLightOverlay");
}

if (!view.isGMView()) {
// Note that the ZoneView has already restricted the darkness to its affected areas.
final var darknessLights =
drawableLights.stream().filter(light -> light.getLumens() <= 0).toList();
renderLightOverlay(
g,
new SolidColorComposite(0xff000000),
AlphaComposite.SrcOver,
LightOverlayClipStyle.CLIP_TO_NOT_VISIBLE_AREA,
darknessLights,
Color.black,
new Color(0, 0, 0, 0));
}

if (AppState.isShowLumensOverlay()) {
// Lumens overlay enabled.
timer.start("renderLights:renderLumensOverlay");
Expand Down Expand Up @@ -1474,7 +1462,6 @@ private void renderAuras(Graphics2D g, PlayerView view) {
AlphaComposite.SrcOver,
view.isGMView() ? null : LightOverlayClipStyle.CLIP_TO_VISIBLE_AREA,
drawableAuras,
new Color(255, 255, 255, 150),
new Color(0, 0, 0, 0));
timer.stop("renderAuras:renderAuraOverlay");
}
Expand Down Expand Up @@ -1591,15 +1578,13 @@ private void renderLumensOverlay(
* @param clipStyle How to clip the overlay relative to the visible area. Set to null for no extra
* clipping.
* @param lights The lights that will be rendered and blended.
* @param defaultPaint A default paint for lights without a paint.
*/
private void renderLightOverlay(
Graphics2D g,
Composite lightBlending,
Composite overlayBlending,
@Nullable LightOverlayClipStyle clipStyle,
Collection<DrawableLight> lights,
Paint defaultPaint,
Paint backgroundFill) {
if (lights.isEmpty()) {
// No point spending resources accomplishing nothing.
Expand Down Expand Up @@ -1642,8 +1627,7 @@ private void renderLightOverlay(
// Draw lights onto the buffer image so the map doesn't affect how they blend
timer.start("renderLightOverlay:drawLights");
for (var light : lights) {
var paint = light.getPaint() != null ? light.getPaint().getPaint() : defaultPaint;
newG.setPaint(paint);
newG.setPaint(light.getPaint().getPaint());
timer.start("renderLightOverlay:fillLight");
newG.fill(light.getArea());
timer.stop("renderLightOverlay:fillLight");
Expand All @@ -1659,6 +1643,43 @@ private void renderLightOverlay(
}
}

/**
* Draws a solid black overlay wherever a non-GM player should see darkness.
*
* <p>If {@code view} is a GM view, this renders nothing.
*
* @param g The graphics object used to render the zone.
* @param view The player view.
*/
private void renderPlayerDarkness(Graphics2D g, PlayerView view) {
if (view.isGMView()) {
// GMs see the darkness rendered as lights, not as blackness.
return;
}

final var darkness = zoneView.getIllumination(view).getDarkenedArea();
if (darkness.isEmpty()) {
// Skip the rendering work if it isn't necessary.
return;
}

g = (Graphics2D) g.create();
try {
timer.start("renderPlayerDarkness:setTransform");
AffineTransform af = new AffineTransform();
af.translate(getViewOffsetX(), getViewOffsetY());
af.scale(getScale(), getScale());
g.setTransform(af);
timer.stop("renderPlayerDarkness:setTransform");

g.setComposite(AlphaComposite.Src);
g.setPaint(Color.black);
g.fill(darkness);
} finally {
g.dispose();
}
}

/**
* This outlines the area visible to the token under the cursor, clipped to the current
* fog-of-war. This is appropriate for the player view, but the GM sees everything.
Expand Down Expand Up @@ -1864,10 +1885,10 @@ private void renderFog(Graphics2D g, PlayerView view) {
}

private void renderFogArea(
final Graphics2D buffG, final PlayerView view, Area softFog, Area visibleArea) {
final Graphics2D buffG, final PlayerView view, Area softFog, @Nonnull Area visibleArea) {
if (zoneView.isUsingVision()) {
buffG.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
if (visibleArea != null && !visibleArea.isEmpty()) {
if (!visibleArea.isEmpty()) {
buffG.setColor(new Color(0, 0, 0, AppPreferences.getFogOverlayOpacity()));

// Fill in the exposed area
Expand All @@ -1889,9 +1910,10 @@ private void renderFogArea(
}
}

private void renderFogOutline(final Graphics2D buffG, PlayerView view, Area visibleArea) {
private void renderFogOutline(
final Graphics2D buffG, PlayerView view, @Nonnull Area visibleArea) {
// If there is no visible area, there is no outline that needs rendering.
if (zoneView.isUsingVision() && visibleArea != null && !visibleArea.isEmpty()) {
if (zoneView.isUsingVision() && !visibleArea.isEmpty()) {
// Transform the area (not G2D) because we want the drawn line to remain thin.
AffineTransform af = new AffineTransform();
af.translate(zoneScale.getOffsetX(), zoneScale.getOffsetY());
Expand Down
31 changes: 20 additions & 11 deletions src/main/java/net/rptools/maptool/client/ui/zone/ZoneView.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public Area getExposedArea(PlayerView view) {
* @param view the PlayerView
* @return the visible area
*/
public Area getVisibleArea(PlayerView view) {
public @Nonnull Area getVisibleArea(PlayerView view) {
return visibleAreaMap.computeIfAbsent(
view,
view2 -> {
Expand Down Expand Up @@ -520,7 +520,7 @@ private Illumination getIllumination(IlluminationKey illuminationKey) {
return personalLights;
}

private Illumination getIllumination(PlayerView view) {
public Illumination getIllumination(PlayerView view) {
var illumination = illuminationsPerView.get(view);
if (illumination == null) {
// Not yet calculated. Do so now.
Expand Down Expand Up @@ -625,14 +625,14 @@ public Area getVisibleArea(Token token, PlayerView view) {
// perspective.
final var singleTokenView = new PlayerView(view.getRole(), Collections.singletonList(token));
final var illumination = getIllumination(singleTokenView);
final var visibleArea = illumination.getVisibleArea();
visibleArea.intersect(tokenVisibleArea);
final var litArea = illumination.getLitArea();
litArea.intersect(tokenVisibleArea);

tokenVisionCache.put(token.getId(), visibleArea);
tokenVisionCache.put(token.getId(), litArea);

// log.info("getVisibleArea: \t\t" + stopwatch);

return visibleArea;
return litArea;
}

/**
Expand Down Expand Up @@ -750,6 +750,18 @@ public Collection<DrawableLight> getDrawableLights(PlayerView view) {
.filter(laud -> laud.lightInfo() != null)
.map(
(ContributedLight laud) -> {
var isDarkness = laud.litArea().lumens() < 0;
if (isDarkness && !view.isGMView()) {
// Non-GM players do not render the light aspect of darkness.
return null;
}

// Lights without a colour are "clear" and should not be rendered.
var paint = laud.lightInfo().light().getPaint();
if (paint == null) {
return null;
}

// Make sure each drawable light is restricted to the area it covers,
// accounting for darkness effects.
final var obscuredArea = new Area(laud.litArea().area());
Expand All @@ -761,13 +773,10 @@ public Collection<DrawableLight> getDrawableLights(PlayerView view) {
}

obscuredArea.intersect(
laud.litArea().lumens() < 0
isDarkness
? lumensLevel.get().darknessArea()
: lumensLevel.get().lightArea());
return new DrawableLight(
laud.lightInfo().light().getPaint(),
obscuredArea,
laud.litArea().lumens());
return new DrawableLight(paint, obscuredArea, laud.litArea().lumens());
})
.filter(Objects::nonNull)
.toList();
Expand Down