Skip to content

Commit

Permalink
Merge pull request #4225 from kwvanderlinde/bugfix/4213-colourless-li…
Browse files Browse the repository at this point in the history
…ghts-as-transparent

Stop rendering clear lights
  • Loading branch information
cwisniew authored Jul 31, 2023
2 parents 5811998 + d488009 commit 40f9132
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 54 deletions.
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

0 comments on commit 40f9132

Please sign in to comment.