Skip to content

Commit

Permalink
implement hillshading, not visual yet ...
Browse files Browse the repository at this point in the history
fix ARGBCalculator blend cache.
  • Loading branch information
svencc committed Feb 19, 2024
1 parent 88abbcf commit 0739ffe
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 37 deletions.
6 changes: 3 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* nicer color scheme for the map
* DEM (Digital Elevation Model) -> Höhenmodell -> Rename old things to DEM
* Slope Map (Neigungskarte) (/)
* Aspect Map (Hangrichtungskarte)
* Contour Map (Höhenlinienkarte)
* Hillshade Map (Schummerungskarte, Schattierungskarte)
* Aspect Map (Hangrichtungskarte) (/)
* Contour Map (Höhenlinienkarte)
* Hillshade Map (Schummerungskarte, Schattierungskarte) (<-)

* known issues or optimizations:
* prebuffering of zoom levels (for faster zooming) or find out what is the bottleneck
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
public class ARGBCalculator {

@NonNull
private static final Map<Integer, Integer> colorCache = new HashMap<>();
private static final Map<Long, Integer> colorCache = new HashMap<>();


public int blend(final int foregroundColor, final int backgroundColour) {
int cachedResult = colorCache.getOrDefault(foregroundColor, -1);
if (cachedResult != -1) {
return cachedResult;
final long key = ((long) foregroundColor << 32) | (backgroundColour & 0xFFFFFFFFL);
if (colorCache.containsKey(key)) {
return colorCache.get(key);
}

final double alpha = getAlphaComponent(foregroundColor) / 255.0;
Expand All @@ -28,7 +28,7 @@ public int blend(final int foregroundColor, final int backgroundColour) {
final int newAlpha = getAlphaComponent(backgroundColour);

int result = compose(newAlpha, (int) newRed, (int) newGreen, (int) newBlue);
colorCache.put(foregroundColor, result);
colorCache.put(key, result);

return result;
}
Expand Down Expand Up @@ -56,4 +56,13 @@ public int compose(final int alpha, final int red, final int green, final int bl
((blue & 0xFF << 0));
}

public int shade(final int baseColor, double brightness) {
return compose(
getAlphaComponent(baseColor),
(int) (getRedComponent(baseColor) * brightness),
(int) (getGreenComponent(baseColor) * brightness),
(int) (getBlueComponent(baseColor) * brightness)
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@
import com.recom.commons.math.Sign;
import com.recom.commons.model.Aspect;
import com.recom.commons.model.SlopeAndAspect;
import com.recom.commons.model.Vector3D;
import com.recom.commons.rasterizer.mapcolorscheme.MapShadowingScheme;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class D8CalculatorForSlopeAndAspectMaps {

@NonNull
private final ARGBCalculator colorCalculator = new ARGBCalculator();

private final double cellSize;
// Defines the relative positions of the 8 neighboring cells around a given cell.
private final int[] directionXComponentMatrix = {-1, -1, 0, 1, 1, 1, 0, -1};
Expand Down Expand Up @@ -65,7 +71,7 @@ private SlopeAndAspect calculateSlopeAndAspect(
final int differenceSign = Sign.of(relativeDifference);
final double absoluteDifference = Math.abs(relativeDifference);
// Elevation difference to the neighbor.
final double distance = (currentAspect.isCardinal()) ? cellSize : diagonalCellSize; // Choose the correct distance based on direction.
final double distance = (currentAspect.isCardinal()) ? cellSize : diagonalCellSize; // Choose the correct distance based on direction.
final double slope = absoluteDifference / distance; // Calculate the slope as elevation difference divided by distance.

// Update the maximum slope if this slope is steeper.
Expand All @@ -87,5 +93,39 @@ private SlopeAndAspect calculateSlopeAndAspect(
.build();
}

public double[][] calculateShadedMap(
@NonNull final SlopeAndAspect[][] slopeAndAspectMap,
@NonNull final MapShadowingScheme shadowingScheme
) {
final double[][] shadingMap = new double[slopeAndAspectMap.length][slopeAndAspectMap[0].length];

for (int x = 0; x < slopeAndAspectMap.length; x++) {
for (int y = 0; y < slopeAndAspectMap[0].length; y++) {
shadingMap[x][y] = calculateShading(slopeAndAspectMap[x][y], shadowingScheme);
}
}

return shadingMap;
}

private float calculateShading(
@NonNull final SlopeAndAspect slopeAndAspect,
@NonNull final MapShadowingScheme shadowingScheme
) {
final double slopeRad = Math.atan(slopeAndAspect.getSlope());

final Vector3D terrainNormal = Vector3D.builder()
.x(Math.cos(Math.toRadians(slopeAndAspect.getAspect().getAngle())) * Math.sin(slopeRad))
.y(Math.sin(Math.toRadians(slopeAndAspect.getAspect().getAngle())) * Math.sin(slopeRad))
.z(Math.cos(slopeRad))
.build();

// Berechnen Sie das Skalarprodukt der Vektoren für die Lichtintensität (wie stark das Licht auf die Zelle trifft; wie stark die beiden Vektoren aufeinander ausgerichtet sind).
final Vector3D sunLightVector = shadowingScheme.getSunLightVector();
final double dotProduct = VectorCalculator.dotProduct(sunLightVector, terrainNormal);
final double brightness = Math.max(0, dotProduct);

return colorCalculator.shade(shadowingScheme.getBaseColorTerrain(), brightness);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.recom.commons.calculator;

import com.recom.commons.model.Vector3D;

public class VectorCalculator {

public static double dotProduct(
final Vector3D sunLightVector,
final Vector3D terrainNormal
) {
return sunLightVector.getX() * terrainNormal.getX() +
sunLightVector.getY() * terrainNormal.getY() +
sunLightVector.getZ() * terrainNormal.getZ();
}

}
12 changes: 6 additions & 6 deletions libs/commons/src/main/java/com/recom/commons/model/Aspect.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ public enum Aspect {


final int aspectValue;
final int aspectAngle;
final int angle;
final boolean isCardinal;

Aspect(
final int aspectValue,
final int aspectAngle
final int enumValue,
final int angle
) {
this.aspectValue = aspectValue;
this.aspectAngle = aspectAngle;
this.isCardinal = aspectAngle % 90 == 0;
this.aspectValue = enumValue;
this.angle = angle;
this.isCardinal = angle % 90 == 0;
}

@NonNull
Expand Down
16 changes: 16 additions & 0 deletions libs/commons/src/main/java/com/recom/commons/model/Vector3D.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.recom.commons.model;

import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@Builder
@RequiredArgsConstructor
public class Vector3D {

private final double x;
private final double y;
private final double z;

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.recom.commons.rasterizer.mapcolorscheme;

import com.recom.commons.model.Vector3D;

public abstract class MapShadowingScheme {

private Double cachedSunAzimutRad = null;
private Double cachedSunElevationRad = null;
private Vector3D cachedSunLightVectorX = null;


public abstract int getBaseColorTerrain();

public abstract int getBaseColorWater();

public abstract int getBaseColorForest();


public abstract int getSunAzimutDeg();

public abstract int getSunElevationDeg();

public double getSunAzimutRad() {
if (cachedSunAzimutRad == null) {
cachedSunAzimutRad = Math.toRadians(getSunAzimutDeg());
}

return cachedSunAzimutRad;
}

public double getSunElevationRad() {
if (cachedSunElevationRad == null) {
cachedSunElevationRad = Math.toRadians(getSunElevationDeg());
}

return cachedSunElevationRad;
}

public Vector3D getSunLightVector() {
if (cachedSunLightVectorX == null) {
final double lx = Math.cos(getSunAzimutRad()) * Math.cos(getSunElevationRad());
final double ly = Math.sin(getSunAzimutRad()) * Math.cos(getSunElevationRad());
final double lz = Math.sin(getSunElevationRad());

cachedSunLightVectorX = Vector3D.builder()
.x(lx)
.y(ly)
.z(lz)
.build();
}

return cachedSunLightVectorX;
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.recom.commons.rasterizer.mapcolorscheme;

import lombok.Getter;

@Getter
public class ReforgerMapScheme extends MapShadowingScheme {

private final int baseColorTerrain = 0xFFF1F1F3;
private final int baseColorWater = 0xFFA1D2E8;
private final int baseColorForest = 0xFFA1D2E8;

private final int sunAzimutDeg = 315; // usually 315
private final int sunElevationDeg = 45; // usually 30-45

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.recom.commons.model.Aspect;
import com.recom.commons.model.SlopeAndAspect;
import com.recom.commons.rasterizer.mapcolorscheme.ReforgerMapScheme;
import lombok.NonNull;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -45,4 +46,32 @@ void calculateSlopeAndAspectMap() {
}
}

@Test
void calculateShadedMap() {
// ARRANGE
final ReforgerMapScheme mapScheme = new ReforgerMapScheme();
final D8CalculatorForSlopeAndAspectMaps algorithm = new D8CalculatorForSlopeAndAspectMaps(1.0);
final double[][] dem = {
{400, 400, 400, 400, 400},
{400, 450, 430, 400, 400},
{400, 450, 430, 400, 400},
{400, 400, 400, 400, 400},
{400, 400, 400, 400, 400}
};

// ACT
final SlopeAndAspect[][] slopeAndAspects = algorithm.calculateSlopeAndAspectMap(dem);
double[][] shadedMapToTest = algorithm.calculateShadedMap(slopeAndAspects, mapScheme);

// ASSERT
for (int x = 0; x < dem.length; x++) {
System.out.print("{");
for (int y = 0; y < dem[0].length; y++) {
System.out.print(shadedMapToTest[x][y] + ", ");
}
System.out.print("}");
System.out.println();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private void handleMouseDragCommand(
}
});
} else {
log.info("MouseDragCommand received: {} ({})", inputCommand.getClass().getSimpleName(), mouseDragCommand.getMouseButton());
log.debug("MouseDragCommand received: {} ({})", inputCommand.getClass().getSimpleName(), mouseDragCommand.getMouseButton());
}
}
}
Expand All @@ -107,15 +107,15 @@ private Optional<RECOMMapComponent> locateRecomMapComponent() {
private void logInputCommand(@NonNull final IsCommand<?> inputCommand) {
switch (inputCommand) {
case MouseButtonCommand mouseButtonCommand ->
log.info("MouseButtonCommand received: {} (doubleClick: {}) - timeBetweenDragStartAndDragStop {} / probableDraggingIntention {}", mouseButtonCommand.getMouseButton(), mouseButtonCommand.isDoubleClick(), mouseButtonCommand.getTimeBetweenDragStartAndDragStop(), mouseButtonCommand.isProbableDraggingIntention());
log.debug("MouseButtonCommand received: {} (doubleClick: {}) - timeBetweenDragStartAndDragStop {} / probableDraggingIntention {}", mouseButtonCommand.getMouseButton(), mouseButtonCommand.isDoubleClick(), mouseButtonCommand.getTimeBetweenDragStartAndDragStop(), mouseButtonCommand.isProbableDraggingIntention());
case MouseDragCommand mouseDragCommand ->
log.info("MouseDragCommand received: {} ({})", mouseDragCommand.getMouseButton(), inputCommand.getNanoTimedEvent().getEvent().getEventType());
log.debug("MouseDragCommand received: {} ({})", mouseDragCommand.getMouseButton(), inputCommand.getNanoTimedEvent().getEvent().getEventType());
case ScrollCommand scrollCommand ->
log.info("ScrollCommand received: {} ({})", inputCommand.getClass().getSimpleName(), mapToScrollDirection(scrollCommand.getNanoTimedEvent().getEvent()));
log.debug("ScrollCommand received: {} ({})", inputCommand.getClass().getSimpleName(), mapToScrollDirection(scrollCommand.getNanoTimedEvent().getEvent()));
case KeyboardCommand keyboardCommand ->
log.info("KeyboardCommand received: {} ({})", keyboardCommand.getNanoTimedEvent().getEvent().getEventType(), keyboardCommand.getNanoTimedEvent().getEvent().getCode());
log.debug("KeyboardCommand received: {} ({})", keyboardCommand.getNanoTimedEvent().getEvent().getEventType(), keyboardCommand.getNanoTimedEvent().getEvent().getCode());
default ->
log.info("GenericInputCommand received: {} -> {}", inputCommand.getClass().getSimpleName(), inputCommand.getNanoTimedEvent().getEvent().getEventType());
log.debug("GenericInputCommand received: {} -> {}", inputCommand.getClass().getSimpleName(), inputCommand.getNanoTimedEvent().getEvent().getEventType());
}
}

Expand Down

0 comments on commit 0739ffe

Please sign in to comment.