package org.osm2world.core.target.common.rendering; import static java.lang.Math.PI; import static org.osm2world.core.math.AxisAlignedBoundingBoxXZ.union; import java.util.Arrays; import java.util.List; import org.osm2world.core.map_data.creation.MapProjection; import org.osm2world.core.math.AxisAlignedBoundingBoxXZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; /** * calculates camera and projection information for orthographic tiles. */ public final class OrthoTilesUtil { /** 4 cardinal directions, can be used for camera placement */ public static enum CardinalDirection { N, E, S, W; /** * returns the closest cardinal direction for an angle * @param angle angle to north direction in radians; * consistent with {@link VectorXZ#angle()} */ public static CardinalDirection closestCardinal(double angle) { angle = angle % (2 * PI); if (angle < PI / 4) { return N; } else if (angle < 3 * PI / 4) { return E; } else if (angle < 5 * PI / 4) { return S; } else if (angle < 7 * PI / 4) { return W; } else { return N; } } public boolean isOppositeOf(CardinalDirection other) { return this == N && other == S || this == E && other == W || this == S && other == N || this == W && other == E; } }; /** prevents instantiation */ private OrthoTilesUtil() { } public static final Camera cameraForTile(MapProjection mapProjection, TileNumber tile, double angleDeg, CardinalDirection from) { return cameraForBounds(boundsForTile(mapProjection, tile), angleDeg, from); } public static final Camera cameraForTiles(MapProjection mapProjection, List<TileNumber> tiles, double angleDeg, CardinalDirection from) { if (tiles.isEmpty()) { throw new IllegalArgumentException("empty tiles list"); } AxisAlignedBoundingBoxXZ result = boundsForTiles(mapProjection, tiles); return cameraForBounds(result, angleDeg, from); } public static final Camera cameraForBounds( AxisAlignedBoundingBoxXZ bounds, double angleDeg, CardinalDirection from) { Camera result = new Camera(); VectorXYZ lookAt = new VectorXYZ( bounds.minX + bounds.sizeX() / 2, 0, bounds.minZ + bounds.sizeZ() / 2); // calculate camera position (start with position for view from south, // then modify it depending on parameters) double cameraDistance = Math.max(bounds.sizeX(), bounds.sizeZ()); double cameraOffsetX = 0; double cameraOffsetZ = - cameraDistance * Math.cos(Math.toRadians(angleDeg)); if (from == CardinalDirection.W || from == CardinalDirection.E) { double temp = cameraOffsetX; cameraOffsetX = cameraOffsetZ; cameraOffsetZ = temp; } if (from == CardinalDirection.N || from == CardinalDirection.E) { cameraOffsetX = -cameraOffsetX; cameraOffsetZ = -cameraOffsetZ; } result.setCamera(lookAt.x + cameraOffsetX, cameraDistance * Math.sin(Math.toRadians(angleDeg)), lookAt.z + cameraOffsetZ, lookAt.x, lookAt.y, lookAt.z); return result; } public static final Projection projectionForTile(MapProjection mapProjection, TileNumber tile, double angleDeg, CardinalDirection from) { AxisAlignedBoundingBoxXZ tileBounds = boundsForTile(mapProjection, tile); return projectionForBounds(tileBounds, angleDeg, from); } public static final Projection projectionForTiles(MapProjection mapProjection, List<TileNumber> tiles, double angleDeg, CardinalDirection from) { if (tiles.isEmpty()) { throw new IllegalArgumentException("empty tiles list"); } AxisAlignedBoundingBoxXZ result = boundsForTiles(mapProjection, tiles); return projectionForBounds(result, angleDeg, from); } public static final Projection projectionForBounds( AxisAlignedBoundingBoxXZ bounds, double angleDeg, CardinalDirection from) { double sin = Math.sin(Math.toRadians(angleDeg)); double sizeX = bounds.sizeX(); double sizeZ = bounds.sizeZ(); if (from == CardinalDirection.W || from == CardinalDirection.E) { double temp = sizeX; sizeX = sizeZ; sizeZ = temp; } return new Projection(true, sizeX / (sizeZ * sin), Double.NaN, sizeZ * sin, -10000, 10000); } private static final AxisAlignedBoundingBoxXZ boundsForTile( MapProjection mapProjection, TileNumber tile) { VectorXZ tilePos1 = mapProjection.calcPos( tile2lat(tile.y, tile.zoom), tile2lon(tile.x, tile.zoom)); VectorXZ tilePos2 = mapProjection.calcPos( tile2lat(tile.y+1, tile.zoom), tile2lon(tile.x+1, tile.zoom)); return new AxisAlignedBoundingBoxXZ(Arrays.asList(tilePos1, tilePos2)); } private static final AxisAlignedBoundingBoxXZ boundsForTiles( MapProjection mapProjection, List<TileNumber> tiles) { AxisAlignedBoundingBoxXZ result = boundsForTile(mapProjection, tiles.get(0)); for (int i=1; i<tiles.size(); i++) { AxisAlignedBoundingBoxXZ newBox = boundsForTile(mapProjection, tiles.get(i)); result = union(result, newBox); } return result; } private static final double tile2lon(int x, int z) { return x / Math.pow(2.0, z) * 360.0 - 180; } private static final double tile2lat(int y, int z) { double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z); return Math.toDegrees(Math.atan(Math.sinh(n))); } }