Generador de terreno voxel proyectado en paralelo

20

Su trabajo es generar un mapa de altura y mostrarlo como un paisaje voxel proyectado en paralelo. Las reglas son las siguientes:

  • El (mapa de altura del) paisaje debe generarse aleatoriamente
  • También debe describir cómo funciona el algoritmo que está utilizando, para que todos puedan aprender algo nuevo aquí.
  • También debe generar una imagen o mostrar el paisaje generado en la pantalla
  • La imagen resultante debe proyectarse paralelamente (por lo tanto, no en perspectiva), y solo puede contener vóxeles (por lo que debe estar hecha de pequeñas cajas)
  • Este es un concurso de popularidad, por lo que es posible que desee agregar algunas características adicionales a su programa para obtener más votos positivos.
  • El ganador es la respuesta válida más votada 7 días después de la última presentación válida. Todos los envíos válidos deben seguir las reglas, incluida la descripción del algoritmo utilizado. Puede agregar características adicionales que no siguen algunas de las reglas (como agregar un modo de perspectiva), pero en este caso tienen que ser características opcionales (por ejemplo, al desactivarlas, el resultado debe seguir todas las reglas)
  • Mi envío no cuenta como válido.

Una imagen de resultado de ejemplo es la siguiente:

paisaje vóxel

Imagen tomada desde aquí

Si necesitas algunos algoritmos marca aquí

SztupY
fuente
Los cubos renderizados de Minecraft esque no equivalen a vóxeles. También se requiere una verdadera proyección isométrica, o es la palabra usada libremente como es común en los juegos en.wikipedia.org/wiki/Video_games_with_isometric_graphics
shiona
@shiona: La descripción del tema se modificó hace unos días para decir paralelamente proyectada, por lo que cualquier cosa que no sea perspectiva debería contar. En cuanto a los vóxeles: creo que los cubos minecraftesqe son válidos en términos de vóxeles: pueden considerarse píxeles enormes en una gran cuadrícula 3D.
SztupY
No, los cubos Minecraftesque no son vóxeles, porque los vóxeles no son cubos, al igual que los píxeles no son cuadrados. citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.79.9093
Seudónimo
Estoy de acuerdo con el tipo de seudónimo. Sin embargo, creo que es válido si quieres que sean cubos. Sin embargo, elimina prácticamente cualquier otra técnica de rasterización de vóxel.
Tim Seguine

Respuestas:

12

Python2 3D Function Plotter Voxel Edition

Esta es mi entrada en esta competencia:

import math
import random
import Image

terrain=""
randA=(random.random()*4+5)
randB=(random.random()*4+10)
randC=(random.random()*4+1)
randD=(random.random()*4+1)
im = Image.new("RGBA", (1248, 1000), "black")
tile=[Image.open("voxel/1.png"),Image.open("voxel/2.png"),Image.open("voxel/3.png"),Image.open("voxel/4.png"),Image.open("voxel/5.png"),Image.open("voxel/6.png"),Image.open("voxel/7.png"),Image.open("voxel/8.png"),Image.open("voxel/9.png")]


for y in range (-40,40):
        for x in range (-10, 10):
                val=int(1+abs(4+2.5*(math.sin(1/randA*x+randC)+math.sin(1/randB*y+randD))))
                if (val<9):
                        terrain+=str(val)
                else:
                        terrain+="9"
print terrain

for i in range (0,80*20):
        if((i/20)%2==0):
                shift=0
        else:
                shift=-32
        im.paste(tile[int(terrain[i])-1],((i%20)*64+shift,((i/20)*16-(32*(int(terrain[i])-1)))-32),tile[int(terrain[i])-1])

im.show()

Como se indica claramente en el título, funciona como un trazador de funciones 3D, pero dado que esta competencia requiere que el terreno se genere aleatoriamente, esta función seno aleatoria 1.5*(math.sin(1/randA*x+randC)+math.sin(1/randB*y+randD))que depende de 4 variables aleatorias. Esto crea terrenos como este: Salida aleatoria

Por supuesto, podemos reemplazar esta función aleatoria por cualquier función variable 2, por ejemplo, sin(sqrt((x/2)²+(y/2)²))*3da este terreno: Función 3D

y -x*y*e^(-x^2-y^2)da esto: 3Dfunction2
(las parcelas de la derecha son calculadas por wolfram alpha)

Y mientras lo hacemos, Riemann zeta a lo largo de la franja crítica:

Función zeta de Riemann

Para las personas que no están familiarizadas con él, como puede ver, estos charcos de agua (que representan los ceros de la función) se encuentran en una línea recta (parte real = 0.5). ¡Si puede probar esto, obtendrá $ 1000000! Ver este enlace.

¡Espero que te guste!

Jens Renders
fuente
Hola Jens, buenas parcelas! Me preguntaba de dónde sacaste las imágenes de vóxel.
Willem
No recuerdo exactamente dónde, hice una búsqueda de imágenes de gogle y las edité con pintura
Jens Renders
10

C #, WPF

He experimentado con una caminata aleatoria , que funciona mejor de lo que esperaba. Comienzo en algún lugar del mapa, camino hacia un mosaico adyacente aleatorio e incremento su valor de altura , luego me muevo al siguiente y así sucesivamente. Esto se repite miles de veces y finalmente conduce a un mapa de altura como este (100 x 100):

mapa de altura ampliada mapa de altura

Luego, "discretizo" el mapa, reduzco el número de valores a los niveles de altura dados y asigno terreno / color en función de esa altura:

mapa del terreno ampliado mapa del terreno

terreno vóxel 1

Terrenos similares al archipiélago similares:

terreno vóxel 2

terreno vóxel 3

Vóxel terreno 4

terreno vóxel 5

Mayor número de pasos aleatorios y niveles de altura para obtener un terreno más montañoso:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí


Código

Características: Recrea el terreno con un botón. Mostrar terreno 3D y mapa 2D. Zoom (rueda del mouse) y desplazamiento 3D (teclas de flecha). Pero no es muy eficiente, después de todo, esto está escrito exclusivamente en WPF, no en DirectX u OpenGL.

MainWindow.xaml:

<Window x:Class="VoxelTerrainGenerator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Voxel Terrain Generator" Width="550" Height="280" KeyUp="Window_KeyUp">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <Viewport3D x:Name="ViewPort" MouseWheel="ViewPort_MouseWheel">
            <Viewport3D.Camera>
                <OrthographicCamera x:Name="Camera" Position="-100,-100,150" LookDirection="1,1,-1" UpDirection="0,0,1" Width="150" />
                <!--<PerspectiveCamera x:Name="Camera" Position="-100,-100,150" LookDirection="1,1,-1" UpDirection="0,0,1" />-->
            </Viewport3D.Camera>
        </Viewport3D>

        <Grid Grid.Column="1" Margin="10">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>

            <Image Grid.Row="0" x:Name="TopViewImage"/>
            <Button Grid.Row="1" Margin="0 10 0 0" Click="Button_Click" Content="Generate Terrain" />
        </Grid>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Media.Media3D;

namespace VoxelTerrainGenerator
{
    public partial class MainWindow : Window
    {
        const int RandomSteps = 20000;
        const int MapLengthX = 100;
        const int MapLengthY = 100;
        const int MaxX = MapLengthX - 1;
        const int MaxY = MapLengthY - 1;
        const bool ForceIntoBounds = true;
        readonly Random Random = new Random();

        readonly List<Color> ColorsByHeight = new List<Color> 
        { 
            Color.FromArgb(0, 0, 50),
            Color.FromArgb(170, 170, 20),
            Color.FromArgb(0, 150, 0),
            Color.FromArgb(0, 140, 0),
            Color.FromArgb(0, 130, 0),
            Color.FromArgb(0, 120, 0),
            Color.FromArgb(0, 110, 0),
            Color.FromArgb(100, 100, 100),
        };

        public MainWindow()
        {
            InitializeComponent();
            TopViewImage.Width = MapLengthX;
            TopViewImage.Height = MapLengthY;
        }

        public int[,] CreateRandomHeightMap()
        {
            var map = new int[MapLengthX, MapLengthY];

            int x = MapLengthX/2;
            int y = MapLengthY/2;

            for (int i = 0; i < RandomSteps; i++)
            {
                x += Random.Next(-1, 2);
                y += Random.Next(-1, 2);

                if (ForceIntoBounds)
                {
                    if (x < 0) x = 0;
                    if (x > MaxX) x = MaxX;
                    if (y < 0) y = 0;
                    if (y > MaxY) y = MaxY;
                }

                if (x >= 0 && x < MapLengthX && y >= 0 && y < MapLengthY)
                {
                    map[x, y]++;
                }
            }

            return map;
        }

        public int[,] Normalized(int[,] map, int newMax)
        {
            int max = map.Cast<int>().Max();
            float f = (float)newMax / (float)max;

            int[,] newMap = new int[MapLengthX, MapLengthY];
            for (int x = 0; x < MapLengthX; x++)
            {
                for (int y = 0; y < MapLengthY; y++)
                {
                    newMap[x, y] = (int)(map[x, y] * f);
                }
            }
            return newMap;
        }

        public Bitmap ToBitmap(int[,] map)
        {
            var bitmap = new Bitmap(MapLengthX, MapLengthY);
            for (int x = 0; x < MapLengthX; x++)
            {
                for (int y = 0; y < MapLengthY; y++)
                {
                    int height = map[x, y];
                    if (height > 255)
                    {
                        height = 255;
                    }
                    var color = Color.FromArgb(255, height, height, height);
                    bitmap.SetPixel(x, y, color);
                }
            }
            return bitmap;
        }

        public Bitmap ToColorcodedBitmap(int[,] map)
        {
            int maxHeight = ColorsByHeight.Count-1;
            var bitmap = new Bitmap(MapLengthX, MapLengthY);
            for (int x = 0; x < MapLengthX; x++)
            {
                for (int y = 0; y < MapLengthY; y++)
                {
                    int height = map[x, y];
                    if (height > maxHeight)
                    {
                        height = maxHeight;
                    }
                    bitmap.SetPixel(x, y, ColorsByHeight[height]);
                }
            }
            return bitmap;
        }

        private void ShowTopView(int[,] map)
        {
            using (var memory = new System.IO.MemoryStream())
            {
                ToColorcodedBitmap(map).Save(memory, ImageFormat.Png);
                memory.Position = 0;
                var bitmapImage = new System.Windows.Media.Imaging.BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.StreamSource = memory;
                bitmapImage.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad;
                bitmapImage.EndInit();
                TopViewImage.Source = bitmapImage;
            }
        }

        private void Show3DView(int[,] map)
        {
            ViewPort.Children.Clear();

            var light1 = new AmbientLight(System.Windows.Media.Color.FromArgb(255, 75, 75, 75));
            var lightElement1 = new ModelUIElement3D();
            lightElement1.Model = light1;
            ViewPort.Children.Add(lightElement1);

            var light2 = new DirectionalLight(
                System.Windows.Media.Color.FromArgb(255, 200, 200, 200),
                new Vector3D(0, 1, -0.1));
            var lightElement2 = new ModelUIElement3D();
            lightElement2.Model = light2;
            ViewPort.Children.Add(lightElement2);

            for (int x = 0; x < MapLengthX; x++)
            {
                for (int y = 0; y < MapLengthY; y++)
                {
                    int height = map[x, MapLengthY-y-1];
                    for (int h = 0; h <= height; h++)
                    {
                        Color color = ColorsByHeight[h];
                        if (height > 0 && h == 0)
                        {
                            // No water under sand
                            color = ColorsByHeight[1];
                        }

                        ViewPort.Children.Add(CreateCube(x, y, h, 1,
                            System.Windows.Media.Color.FromArgb(255, color.R, color.G, color.B)));
                    }
                }
            }
        }

        private ModelVisual3D CreateCube(int x, int y, int z, int length,
            System.Windows.Media.Color color)
        {
            List<Point3D> positions = new List<Point3D>()
            {
                new Point3D(x, y, z),
                new Point3D(x + length, y, z),
                new Point3D(x + length, y + length, z),
                new Point3D(x, y + length, z),
                new Point3D(x, y, z + length),
                new Point3D(x + length, y, z + length),
                new Point3D(x + length, y + length, z + length),
                new Point3D(x, y + length, z + length),
            };

            List<List<int>> quads = new List<List<int>> 
            { 
                new List<int> {3,2,1,0},
                new List<int> {0,1,5,4},
                new List<int> {2,6,5,1},
                new List<int> {3,7,6,2},
                new List<int> {3,0,4,7},
                new List<int> {4,5,6,7},
            };

            double halfLength = (double)length / 2.0;
            Point3D cubeCenter = new Point3D(x + halfLength, y + halfLength, z + halfLength);
            var mesh = new MeshGeometry3D();
            foreach (List<int> quad in quads)
            {
                int indexOffset = mesh.Positions.Count;
                mesh.Positions.Add(positions[quad[0]]);
                mesh.Positions.Add(positions[quad[1]]);
                mesh.Positions.Add(positions[quad[2]]);
                mesh.Positions.Add(positions[quad[3]]);

                mesh.TriangleIndices.Add(indexOffset);
                mesh.TriangleIndices.Add(indexOffset+1);
                mesh.TriangleIndices.Add(indexOffset+2);
                mesh.TriangleIndices.Add(indexOffset+2);
                mesh.TriangleIndices.Add(indexOffset+3);
                mesh.TriangleIndices.Add(indexOffset);

                double centroidX = quad.Select(v => mesh.Positions[v].X).Sum() / 4.0;
                double centroidY = quad.Select(v => mesh.Positions[v].Y).Sum() / 4.0;
                double centroidZ = quad.Select(v => mesh.Positions[v].Z).Sum() / 4.0;
                Vector3D normal = new Vector3D(
                    centroidX - cubeCenter.X,
                    centroidY - cubeCenter.Y,
                    centroidZ - cubeCenter.Z);
                for (int i = 0; i < 4; i++)
                {
                    mesh.Normals.Add(normal);
                }
            }

            Material material = new DiffuseMaterial(new System.Windows.Media.SolidColorBrush(color));
            GeometryModel3D model = new GeometryModel3D(mesh, material);
            ModelVisual3D visual = new ModelVisual3D();
            visual.Content = model;
            return visual;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            int[,] map = CreateRandomHeightMap();
            int[,] normalizedMap = (Normalized(map, ColorsByHeight.Count-1));

            ShowTopView(normalizedMap);
            Show3DView(normalizedMap);

            ToBitmap(Normalized(map, 255)).Save("heightmap-original.png");
            ToBitmap(Normalized(normalizedMap, 255)).Save("heightmap.png");
            ToColorcodedBitmap(normalizedMap).Save("terrainmap.png");
        }

        private void ViewPort_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            // Zoom in or out
            Camera.Width -= (double)e.Delta / 100;
        }

        private void Window_KeyUp(object sender, KeyEventArgs e)
        {
            // Scrolling by moving the 3D camera
            double x = 0;
            double y = 0;
            if (e.Key == Key.Left)
            {
                x = +10;
                y = -10;
            }
            else if (e.Key == Key.Up)
            {
                x = -10;
                y = -10;
            }
            else if (e.Key == Key.Right)
            {
                x = -10;
                y = +10;
            }
            else if (e.Key == Key.Down)
            {
                x = +10;
                y = +10;
            }

            Point3D cameraPosition = new Point3D(
                Camera.Position.X + x,
                Camera.Position.Y + y,
                Camera.Position.Z);
            Camera.Position = cameraPosition;
        }
    }
}
Sebastian Negraszus
fuente
Genial, pero podría verse mejor cuando 'discretiza' para incluir más cubos. ¿Quizás solo una o dos agrupaciones más? (¡No es necesario de ninguna manera! Todavía +1 de mi parte)
Gaffi
1
@Gaffi He agregado más resultados, incluidos los más montañosos
Sebastian Negraszus
4

JavaScript y Crafty.JS, para mejorar mucho

Aquí hay una muestra de salida:

captura de pantalla

Y aquí está el código (página web completa):

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
    <script type="text/javascript" src="http://craftyjs.com/release/0.4.2/crafty-min.js"></script>
    <script type="text/javascript">

    $(document).ready(function() {
        Crafty.init();

        var tilesize = 20
        Crafty.sprite(20, "sprite.png#UPDATE", {
            grass1: [0,0,1,3],
            grass2: [1,0,1,3],
            grass3: [2,0,1,3],
            stone1: [3,0,1,3],
            stone2: [4,0,1,3]
        });

        genTerrainInit()
        while(1) {
            try { stepTerrainGen() }
            catch(e) { break }
        }


        iso = Crafty.isometric.init(20);
        var z = 0;
        for(var i = tdata.length - 1; i >= 0; i--) {
            for(var y = 0; y < tdata[i].length; y++) {
                var which = Math.max(0, Math.round(tdata[i][y]))
                var tile = Crafty.e("2D, DOM, "+["grass1", "grass2", "grass3", "stone1", "stone2"][which])
                .attr('z',i+1 * y+1)

                iso.place(i,y,0, tile);
            }
        }

        Crafty.addEvent(this, Crafty.stage.elem, "mousedown", function(e) {
            if(e.button > 1) return;
            var base = {x: e.clientX, y: e.clientY};

            function scroll(e) {
                var dx = base.x - e.clientX,
                    dy = base.y - e.clientY;
                    base = {x: e.clientX, y: e.clientY};
                Crafty.viewport.x -= dx;
                Crafty.viewport.y -= dy;
            };

            Crafty.addEvent(this, Crafty.stage.elem, "mousemove", scroll);
            Crafty.addEvent(this, Crafty.stage.elem, "mouseup", function() {
                Crafty.removeEvent(this, Crafty.stage.elem, "mousemove", scroll);
            });
        });
    });

    function genTerrainInit() {
        //Variables
        size = Math.pow(2, 6) + 1; //MUST be a power of 2 plus 1!
        initHeight = 2;
        rndRange = 4;
        smoothSpeed = 0.5; // lower is faster

        tdata = new Array(size);
        toAverage = new Array(size);
        for (var i = 0; i < size; i ++) {
            tdata[i] = new Array(size);
            toAverage[i] = new Array(size);
            for (var i2 = 0; i2 < size; i2 ++) {
                tdata[i][i2] = null;
                toAverage[i][i2] = false;
            }
        }

        //Generate corners
        tdata[0][0] = initHeight;
        tdata[size-1][0] = initHeight;
        tdata[0][size-1] = initHeight;
        tdata[size-1][size-1] = initHeight;
    }

    function stepTerrainGen() {
        //The square step - for each square, take the center point and set it to the average of its corners plus a random amount
        oldi = 0;
        for (var i = 1; i < size; i ++) {
            if (tdata[0][i] != null) {
                oldi2 = 0;
                for (var i2 = 1; i2 < size; i2 ++) {
                    if (tdata[i2][i] != null) {
                        pointDistance = (i - oldi)/2;
                        tdata[(oldi2 + i2)/2][(oldi + i)/2] =
                            ((tdata[oldi2][oldi] + tdata[i2][oldi] + tdata[oldi2][i] + tdata[i2][i])/4) // average of 4 corners
                            + Math.random() * rndRange - (rndRange/2.0);                                // plus a random amount

                        // Now mark the squares for the diamond step
                        toAverage[(oldi2 + i2)/2][oldi] = true;
                        toAverage[oldi2][(oldi + i)/2] = true;
                        toAverage[(oldi2 + i2)/2][i] = true;
                        toAverage[i2][(oldi + i)/2] = true;
                        oldi2 = i2;
                    }
                }
                oldi = i;
            }
        }

        //The diamond step - same as the square step but with newly formed diamonds
        for (var i = 0; i < size; i ++) {
            for (var i2 = 0; i2 < size; i2 ++) {
                if (toAverage[i][i2]) {
                    diamondArray = [];
                    if (i-pointDistance >= 0) diamondArray = diamondArray.concat(tdata[i-pointDistance][i2]);
                    if (i+pointDistance < size) diamondArray = diamondArray.concat(tdata[i+pointDistance][i2]);
                    if (i2-pointDistance >= 0) diamondArray = diamondArray.concat(tdata[i][i2-pointDistance]);
                    if (i2+pointDistance < size) diamondArray = diamondArray.concat(tdata[i][i2+pointDistance]);
                    addedPoints = 0;
                    for (var i3 = 0; i3 < diamondArray.length; i3 ++) addedPoints += diamondArray[i3];
                    tdata[i][i2] = addedPoints/diamondArray.length + Math.floor(Math.random() * rndRange - (rndRange/2.0));
                }
            }
        }
        rndRange *= smoothSpeed;
        resetToAverage();
    }

    function resetToAverage() {
        for (var i = 0; i < size; i ++) {
            for (var i2 = 0; i2 < size; i2 ++) {
                toAverage[i][i2] = false;
            }
        }
    }

    </script>
    <title>Iso</title>
    <style>
    body, html { margin:0; padding: 0; overflow:hidden }
    </style>
</head>
<body>
</body>
</html>

Aqui esta sprite.png:

sprite.png

Ahora, tengo algunas cosas que decir.

  1. ¡No me juzgues por ese terrible código! : PI lo escribió hace muchos años cuando tenía una programación terrible. De hecho, es de los viejos tiempos del sitio web que tenía que ni siquiera recordaba que tenía. http://oddllama.cu.cc/terrain/

  2. Copié mucho código de la demostración isométrica de Crafty.JS . :PAG

  3. ¡La explicación vendrá pronto! Tengo que ir a dormir ahora, ya que es tarde aquí. (¡Es por eso que el sprite es tan terrible!)

Básicamente, ¡está realmente sin pulir y se mejorará enormemente más adelante!

Utiliza el mismo algoritmo de diamante cuadrado mencionado en la respuesta del OP.

Pomo de la puerta
fuente
¿Podemos tomar prestados esos sprites para usar en otros idiomas?
PyRulez
@PyRulez Bueno, yo, uh, los robé (y los edité) del sitio Crafty.JS, así que no tengo idea: P tal vez debería haber mencionado eso
Doorknob
3

Ruby + RMagick

Estoy usando el algoritmo Diamond-Square para generar el mapa de altura.

El algoritmo en resumen:

  • Use una matriz de matriz de envoltura, con un tamaño de 2 ^ n
  • Ajustar significa que cualquier índice fuera de los límites se ajusta, por ejemplo, si el tamaño de la matriz es 4 [0,0] == [4,0] == [0,4] == [4,4]. Además [-2,0] == [2,0], etc.
  • Establecer [0,0]en un color aleatorio
  • Siga los pasos que se muestran en la imagen.

Imagen de explicación

  • Tenga en cuenta que, dado que la matriz se ajusta, cuando tiene que indexar algo fuera de los límites, puede usar los datos del otro lado de la matriz.
  • También tenga en cuenta que en el primer paso las cuatro esquinas significan exactamente el mismo valor (como [0,0] == [4,0] == [0,4] == [4,4])
  • Para calcular el valor del punto negro, debe promediar los cuatro puntos que lo rodean
  • Como esto dará como resultado una imagen gris aburrida, debe agregar un número aleatorio a este valor en cada paso. Se prefiere que este valor aleatorio abarque todo el rango en la primera iteración, pero disminuye con el tiempo cuando se abordan subconjuntos cada vez más pequeños de la matriz. Cuanto menos disminuya esta aleatoriedad con el tiempo, más ruidosa será la imagen.

  • Después de terminar, simplemente estoy asignando un color para cada valor de altura.

Código:

generate.rb

#!/usr/bin/env ruby
require 'rubygems'
require 'bundler/setup'
Bundler.require(:default)

class Numeric
  def clamp min, max
    [[self, max].min, min].max
  end
end

class WrappedArray
  def initialize(size)
    @size = size
    @points = Array.new(size){Array.new(SIZE)}
  end
  def [](y,x)
    @points[(@size+y) % @size][(@size+x) % @size]
  end
  def []=(y,x,value)
    @points[(@size+y) % @size][(@size+x) % @size] = value.clamp(0,@size*@size-1)
  end
end

SIZE = 256
MAXHEIGHT = 256*256

points = WrappedArray.new(SIZE)

points[0,0] = 0

s = SIZE
d = []
sq = []
r = MAXHEIGHT
while s>1
  (0...SIZE).step(s) do |x|
    (0...SIZE).step(s) do |y|
      d << [y,x]
    end
  end
  while !d.empty?
    y,x = *d.shift
    mx = x+s/2
    my = y+s/2

    points[my,mx]  = (points[y,x]   + points[y,x+s]      + points[y+s,x] + points[y+s,x+s])/4 + rand(r)-r/2
    sq << [my,x]
    sq << [my,x+s]
    sq << [y,mx]
    sq << [y+s,mx]
  end
  while !sq.empty?
    y,x = *sq.shift
    points[y,x]    = (points[y-s/2,x] + points[y+s/2,x] + points[y,x-s/2] + points[y,x+s/2])/4 + rand(r)-r/2
  end
  s = s / 2
  r = r * 2 / 3
end

def get_color(height)
  val = height.to_f/MAXHEIGHT*3-1
  r = 0
  g = 0
  b = 0
  if val<=-0.25
    Magick::Pixel.new(0,0,128*256)
  elsif val<=0
    Magick::Pixel.new(0,0,255*256)
  elsif val<=0.0625
    Magick::Pixel.new(0,128*256,255*256)
  elsif val<=0.1250
    Magick::Pixel.new(240*256,240*256,64*256)
  elsif val<=0.3750
    Magick::Pixel.new(32*256,160*256,0)
  elsif val<=0.7500
    Magick::Pixel.new(224*256,224*256,0)
  else
    Magick::Pixel.new(128*256,128*256,128*256)
  end
end

canvas = Magick::ImageList.new
canvas.new_image(SIZE+1, SIZE+1)
0.upto(SIZE) do |y|
  0.upto(SIZE) do |x|
    canvas.pixel_color(x,y,get_color(points[y,x]))
  end
end
canvas.write('result.png')

Gemfile

source "https://rubygems.org"
gem 'rmagick'

Nota: La Imagemagick que estoy usando es de 16 bits

Imagen de resultado:

imagen de resultado

Nota: esta imagen es una representación isométrica de arriba hacia abajo, donde el tamaño de un voxel es exactamente un píxel, por lo que es válido de acuerdo con las reglas (excepto uno: que mi respuesta no se considera válida)

SztupY
fuente
¿La calidad de su solución isométrica de arriba a abajo de un píxel es un indicador de la seriedad con la que desea que las personas aborden su pregunta?
Jonathan Van Matre
No creo que la cuenta de arriba hacia abajo sea isométrica? en.wikipedia.org/wiki/Isometric_projection
mattnewport
@JonathanVanMatre: En la pregunta mostré el resultado deseado. En la respuesta, mostré lo mínimo que debe hacer para que la respuesta sea válida. Como se trata de un concurso de popularidad, puede elegir lo que quiere hacer, pero, por supuesto, debe prosperar para lograr el resultado deseado.
SztupY
@mattnewport: buen punto, lo estaba usando por error para todo tipo de proyecciones paralelas. Fijo.
SztupY
3

Java (usando la imagen en color de @ fejesjoco como algoritmo base)

Después de jugar un poco con las imágenes en color FullRGB de @fejesjoco, noté que podrían usarse como base para interesantes paisajes de voxel acantilados. En lugar de volver a implementar el algoritmo, utilicé su código como un ejecutable externo (descárguelo de http://joco.name/2014/03/02/all-rgb-colors-in-one-image/ y colóquelo como artgen. exe en el mismo directorio)

Avance:
Avance

mapa de altura utilizado (almacenado en el canal azul)
Mapa de altura

Imagen de entrada:
Entrada

El subalgoritmo que uso de él funciona de esa manera:
1. Clasificación
2. Comience con un píxel negro en el centro
3. Hasta que se usen todos los colores: coloque el color actual en el punto de ajuste más cercano y agregue los vecinos no utilizados como nuevos puntos utilizables. Cuando terminó, lo destrocé para reducirlo a 256 valores diferentes red&(green|blue) 4. luego uso sprites pregenerados y genero la imagen capa por capa

import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStreamReader;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.imageio.ImageIO;
import javax.xml.bind.DatatypeConverter;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author LH
 */
public class Voxelizer2
{
    static final String zipembeddedsprites =
            "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";
    public static void main(String[] args) throws Exception
    {
        //embedded zip idea borrowed from over here:
        //http://codegolf.stackexchange.com/a/22262/10801

        //algorithm and embedded executable borrowed from
        //http://joco.name/2014/03/02/all-rgb-colors-in-one-image/

        //256 8192 2048 4096 1024 1000 9263 11111111 hue-0 one
        /**/
        ProcessBuilder p = new ProcessBuilder("artgen","64","512","512","256","256","1",((int)(Math.random()*(2<<32)))+"","11111111","hue-0","one");
        Process po = p.start();
        BufferedReader x = new BufferedReader(new InputStreamReader(po.getInputStream()),1024);
        String xl = x.readLine();
        //String x2l = x2.readLine();
        while(!xl.startsWith("Press ENTER to exit"))
        {
            System.out.println(xl);
            xl=x.readLine();
        }
        System.out.println(xl);
        po.destroy();/**/
        BufferedImage source = ImageIO.read(new File("result00000.png"));
        BufferedImage heightmap = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_INT_RGB);
        for (int i = 0; i < source.getWidth(); i++)
        {
            for (int j = 0; j < source.getHeight(); j++)
            {
                int basecolor=source.getRGB(i, j)&0x00FFFFFF;
                int r = (basecolor&0x00FF0000)>>16;
                int g = (basecolor&0x0000FF00)>>8;
                int b = (basecolor&0x000000FF);
                int color = r&(g|b);//Math.max(r,Math.max(g,b));
                heightmap.setRGB(i, j, color);

            }
        }/**/
        ImageIO.write(heightmap, "png", new File("heightmap.png"));


        //generate sizedata for Sprites....

        ZipInputStream zippedSprites = new ZipInputStream(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(zipembeddedsprites)));
        ZipEntry z = zippedSprites.getNextEntry();
        BufferedImage water=null,grass=null,stone=null,air = new BufferedImage(24,24, BufferedImage.TYPE_INT_ARGB);
        while(z!=null)
        {
            String name = z.getName();
            switch(name)
            {
                case "water.png":
                    water=ImageIO.read(zippedSprites);
                    System.out.println("water");
                break;
                case "stone.png":
                    stone=ImageIO.read(zippedSprites);
                    System.out.println("stone");
                break;
                case "grass.png":
                    grass=ImageIO.read(zippedSprites);
                    System.out.println("grass");
                break;
            }
            z=zippedSprites.getNextEntry();
        }

        //int height = heightmap.getHeight()*12+12;
        int width16 = heightmap.getWidth()/16;
        int height16=heightmap.getHeight()/16;
        int widthtemp1 = 384+(height16-1)*(384/2);
        int width = (width16-1)*(384/2)+widthtemp1;
        //int heightt1=height16*(12*16)+12*16;
        int height = (width16-1)*(12*16)+(12*16);
        System.out.println(width*height);
        //if(true)return;

        int StartPos =heightmap.getHeight()*12;

        //BufferedImage[] layers = new BufferedImage[256];

        BufferedImage complete = new BufferedImage(width, height+(255*12), BufferedImage.TYPE_INT_ARGB);
        int mergeOffset=255*12;
        for (int i = 0; i < 256; i++)
        {
            System.out.println("Rendering layer"+i);
            BufferedImage layer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            int basePointerX = StartPos-12;
            int basePointerY=0;
            Graphics g = layer.getGraphics();
            for (int k = 0; k < heightmap.getHeight(); k++)
            {
                //System.out.println("Processing line"+k);
                int pointerX = basePointerX;
                int pointerY = basePointerY;
                for (int j = 0; j < heightmap.getWidth(); j++)
                {

                    Image tile = air;
                    int pxheight =heightmap.getRGB(j, k)&0x00FFFFFF;
                    if(pxheight>i)
                    {
                        tile=stone;
                    }
                    if(pxheight==i)
                    {
                        if(i<64)
                        {
                            tile=stone;
                        }
                        else
                        {
                            tile=grass;
                        }
                    }
                    if(pxheight<i)
                    {
                        if(i<64)
                        {
                            tile=water;
                        }
                        else
                        {
                            tile=air;
                        }
                    }
                    g.drawImage(tile, pointerX, pointerY, null);
                    pointerX+=12;
                    pointerY+=6;
                }

                basePointerX-=12;
                basePointerY+=6;


            }

            //

            complete.getGraphics().drawImage(layer, 0, mergeOffset, null);

            mergeOffset-=12;
        }
        ImageIO.write(complete, "png", new File("landscape.png"));
    }
}
masterX244
fuente
1

HTML + JavaScript

Aquí está mi intento en la competencia:

<html>
    <head>
        <script type='text/javascript' language='JavaScript'>
            function create() {
                var con = document.getElementById("can").getContext("2d"),
                    map = new Array(),
                    p = new Array(15 + Math.floor(Math.random() * 10)),
                    tc = ["#000040", "#000070", "#0000a0", "#5050ff", "#f0f000", "#007000", "#00aa00", "#00c000", "#00e000", "#00ff00", "#90ff90", "#a0ffa0", "#c0ffc0", "#e0ffe0", "#f0fff0"],
                    sc = ["#000020", "#000050", "#000085", "#3030df", "#d0d000", "#005000", "#008000", "#008000", "#00b500", "#00d000", "#00ea00", "#80ff80", "#a0ffa0", "#c0ffc0", "#d0ffd0"];
                for (var n = 0; n < p.length; n++) {
                    p[n] = [15 + Math.floor(Math.random() * 70), 15 + Math.floor(Math.random() * 70)];
                }
                for (var x = 0; x < 100; x++) {
                    map[x] = new Array();
                    for (var y = 0; y < 100; y++) {
                        map[x][y] = 0;
                        for (var n = 0; n < p.length; n++) {
                            if (20 - Math.sqrt(Math.pow(x - p[n][0], 2) + Math.pow(y - p[n][1], 2)) > map[x][y]) {
                                map[x][y] = 20 - Math.sqrt(Math.pow(x - p[n][0], 2) + Math.pow(y - p[n][2], 2));
                            }
                        }
                    }
                }
                for (var x = 0; x < 100; x++) {
                    for (var y = 0; y < 100; y++) {
                        if (map[x][y] < 0) {
                            map[x][y] = 0;
                        }
                        map[x][y] = Math.floor(map[x][y] / 2);
                        con.fillStyle = tc[map[x][y]];
                        con.fillRect(x * 10, y * 10 - map[x][y] * 4, 10, 10);
                        con.fillStyle = sc[map[x][y]];
                        con.fillRect(x * 10, y * 10 - map[x][y] * 4 + 10, 10, map[x][y] * 4);
                    }
                }
            }
        </script>
    </head>
    <body>
        <canvas id='can' width='1000' height='1000' style='border: 1px solid #000000;'></canvas>
        <button onclick='create();'>Create</button>
    </body>
</html>

Utilizo el algoritmo Euclidean F1 Cell Noise para generar un mapa de altura que luego transformo en una imagen tomando el color apropiado de una matriz y dibujando un cuadrado a 10x, 10y de altura para que se eleven los píxeles más altos. Luego dibujo un rectángulo como el lado usando el mismo color de una matriz diferente.

Ruido celular 1 Ruido celular 2

Aquí está el mismo código que usa un algoritmo de caminata aleatoria de 10,000 pasos:

<html>
    <head>
        <script type='text/javascript' language='JavaScript'>
            function create() {
                var con = document.getElementById("can").getContext("2d"),
                    map = new Array(),
                    l = 10000,
                    tc = ["#000040", "#000070", "#0000a0", "#5050ff", "#f0f000", "#007000", "#00aa00", "#00c000", "#00e000", "#00ff00", "#90ff90", "#a0ffa0", "#c0ffc0", "#e0ffe0", "#f0fff0"],
                    sc = ["#000020", "#000050", "#000085", "#3030df", "#d0d000", "#005000", "#008000", "#008000", "#00b500", "#00d000", "#00ea00", "#80ff80", "#a0ffa0", "#c0ffc0", "#d0ffd0"];
                for (var x = 0; x < 100; x++) {
                    map[x] = new Array();
                    for (var y = 0; y < 100; y++) {
                        map[x][y] = 0;
                    }
                }
                x = 49;
                y = 49;
                for (var n = 0; n < l; n++) {
                    var d = Math.floor(Math.random() * 4);
                    if (d == 0) {
                        x++
                    }
                    else if (d == 1) {
                        y++
                    }
                    else if (d == 2) {
                        x--
                    }
                    else if (d == 3) {
                        y--
                    }
                    map[(x % 100 + 100) % 100][(y % 100 + 100) % 100]++;
                }
                for (var x = 0; x < 100; x++) {
                    for (var y = 0; y < 100; y++) {
                        if (map[x][y] < 0) {
                            map[x][y] = 0;
                        }
                        map[x][y] = Math.floor(map[x][y] / 2);
                        con.fillStyle = tc[map[x][y]];
                        con.fillRect(x * 10, y * 10 - map[x][y] * 4, 10, 10);
                        con.fillStyle = sc[map[x][y]];
                        con.fillRect(x * 10, y * 10 - map[x][y] * 4 + 10, 10, map[x][y] * 4);
                    }
                }
            }
        </script>
    </head>
    <body>
        <canvas id='can' width='1000' height='1000' style='border: 1px solid #000000;'></canvas>
        <button onclick='create();'>Create</button>
    </body>
</html>

Paseo aleatorio 1 [Caminata aleatoria 2] [4]

Cuando 'camina' por un borde, se enrolla sobre el otro, por lo que todavía se ve bien en mosaico.

Todavía es técnicamente paralelo, solo desde un ángulo diferente.

kitcar2000
fuente