mirror of
https://github.com/yacy/yacy_search_server.git
synced 2025-10-28 23:26:12 -04:00
159 lines
6.0 KiB
Java
159 lines
6.0 KiB
Java
/**
|
|
* IsometricCityscapePlotter
|
|
* Synthesizes a pixelated skyline using a faux-isometric projection.
|
|
* Copyright 2025 by Michael Peter Christen
|
|
* First released 20.09.2025 at https://yacy.net
|
|
*
|
|
* This file is part of YaCy.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program in the file lgpl21.txt
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package net.yacy.visualization;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
|
|
public class IsometricPlotter extends RasterPlotter {
|
|
|
|
private static final int TILE_WIDTH = 24;
|
|
private static final int TILE_HEIGHT = 12;
|
|
private static final int BASELINE = 260;
|
|
private static final int ORIGIN_X = 240;
|
|
|
|
private final int[][] heightMap;
|
|
private final long[] palette;
|
|
|
|
public IsometricPlotter(final int width, final int height, final int[][] heightMap) {
|
|
super(width, height, DrawMode.MODE_REPLACE, 0x00000C);
|
|
this.heightMap = heightMap;
|
|
this.palette = new long[]{0x1A2E59, 0x24407B, 0x33589B, 0x1F6C8F, 0x3F9FBF, 0x7FD3FF};
|
|
}
|
|
|
|
public void renderScene() {
|
|
clear();
|
|
drawBackgroundGrid();
|
|
for (int y = 0; y < heightMap.length; y++) {
|
|
for (int x = heightMap[y].length - 1; x >= 0; x--) {
|
|
final int h = heightMap[y][x];
|
|
if (h <= 0) continue;
|
|
final long baseColor = palette[(x + y) % palette.length];
|
|
drawBuilding(x, y, h, baseColor);
|
|
}
|
|
}
|
|
drawForeground();
|
|
}
|
|
|
|
private void drawBackgroundGrid() {
|
|
setColor(0x001133);
|
|
for (int i = 0; i < 7; i++) {
|
|
int y = BASELINE + i * 10;
|
|
line(0, y, getWidth(), y, 12 + i * 2);
|
|
}
|
|
setColor(0x002244);
|
|
for (int i = -6; i <= 6; i++) {
|
|
final int x0 = ORIGIN_X + i * TILE_WIDTH / 2;
|
|
line(x0, BASELINE, x0 + TILE_WIDTH * 6, BASELINE + TILE_HEIGHT * 6, 20);
|
|
line(x0, BASELINE, x0 - TILE_WIDTH * 6, BASELINE + TILE_HEIGHT * 6, 20);
|
|
}
|
|
}
|
|
|
|
private void drawBuilding(final int gridX, final int gridY, final int height, final long color) {
|
|
final int baseX = ORIGIN_X + (gridX - gridY) * (TILE_WIDTH / 2);
|
|
final int baseY = BASELINE + (gridX + gridY) * (TILE_HEIGHT / 2);
|
|
final int topY = baseY - height;
|
|
final int halfWidth = TILE_WIDTH / 2;
|
|
final int halfHeight = TILE_HEIGHT / 2;
|
|
|
|
// top face shading
|
|
setColor(lighten(color, 1.3d));
|
|
for (int i = 0; i <= halfHeight; i++) {
|
|
int y = topY - halfHeight + i;
|
|
int xLeft = baseX - halfWidth + i;
|
|
int xRight = baseX + halfWidth - i;
|
|
if (xLeft <= xRight) line(xLeft, y, xRight, y, 60);
|
|
}
|
|
for (int i = 0; i <= halfHeight; i++) {
|
|
int y = topY + i;
|
|
int xLeft = baseX - halfWidth + i;
|
|
int xRight = baseX + halfWidth - i;
|
|
if (xLeft <= xRight) line(xLeft, y, xRight, y, 60);
|
|
}
|
|
|
|
// left wall
|
|
setColor(darken(color, 0.8d));
|
|
for (int i = 0; i <= height; i++) {
|
|
int y = topY + i;
|
|
int xLeft = baseX - halfWidth;
|
|
int xRight = baseX - halfWidth + halfHeight;
|
|
line(xLeft, y, xRight, y + halfHeight, 45);
|
|
}
|
|
// right wall
|
|
setColor(darken(color, 0.6d));
|
|
for (int i = 0; i <= height; i++) {
|
|
int y = topY + i;
|
|
int xLeft = baseX + halfWidth - halfHeight;
|
|
int xRight = baseX + halfWidth;
|
|
line(xLeft, y + halfHeight, xRight, y, 50);
|
|
}
|
|
|
|
// outline edges
|
|
setColor(lighten(color, 1.6d));
|
|
line(baseX, topY - halfHeight, baseX + halfWidth, topY, 100);
|
|
line(baseX, topY - halfHeight, baseX - halfWidth, topY, 100);
|
|
line(baseX - halfWidth, topY, baseX - halfWidth, baseY, 80);
|
|
line(baseX + halfWidth, topY, baseX + halfWidth, baseY, 80);
|
|
line(baseX - halfWidth, baseY, baseX, baseY + halfHeight, 70);
|
|
line(baseX + halfWidth, baseY, baseX, baseY + halfHeight, 70);
|
|
|
|
// neon windows
|
|
setColor(0xFFEE66);
|
|
for (int row = 4; row < height; row += 6) {
|
|
int y = topY + row;
|
|
line(baseX - halfWidth / 2, y, baseX - halfWidth / 6, y, 85);
|
|
line(baseX + halfWidth / 6, y, baseX + halfWidth / 2, y, 85);
|
|
}
|
|
}
|
|
|
|
private void drawForeground() {
|
|
setColor(0x00AAFF);
|
|
PrintTool.print5(this, 6, 12, 0, "ISOMETRIC CITYSCAPE", -1, 90);
|
|
setColor(0x66FFFF);
|
|
PrintTool.print5(this, 6, 24, 0, "GRID SECTOR 7B", -1, 70);
|
|
setColor(0x00CCAA);
|
|
PrintTool.print5(this, getWidth() - 6, getHeight() - 8, 0, "POPULATION: 1.2M", 1, 60);
|
|
}
|
|
|
|
public static void main(final String[] args) {
|
|
System.setProperty("java.awt.headless", "true");
|
|
final File target = (args.length > 0) ? new File(args[0]) : new File(".");
|
|
if (!target.exists()) target.mkdirs();
|
|
final int[][] heights = {
|
|
{40, 60, 80, 70, 45},
|
|
{20, 50, 110, 90, 40},
|
|
{10, 30, 140, 120, 70},
|
|
{0, 40, 90, 80, 30},
|
|
{0, 10, 40, 30, 20}
|
|
};
|
|
final IsometricPlotter city = new IsometricPlotter(480, 320, heights);
|
|
city.renderScene();
|
|
final File out = new File(target, "isometric_city.png");
|
|
try {
|
|
city.save(out, "png");
|
|
} catch (final IOException e) {
|
|
throw new RuntimeException("Failed to save cityscape", e);
|
|
}
|
|
}
|
|
}
|