Gerador de terreno voxel projetado em paralelo


20

Seu trabalho é gerar um mapa de altura e mostrá-lo como um cenário de voxel projetado em paralelo. As regras são as seguintes:

  • O (mapa de altura da) paisagem deve ser gerado aleatoriamente
  • Você também precisa descrever como o algoritmo que você está usando funciona, para que todos possam aprender algo novo aqui
  • Você também deve gerar uma imagem ou exibir a paisagem gerada na tela
  • A imagem resultante deve ser projetada em paralelo (portanto, não em perspectiva) e só pode conter voxels (portanto, deve ser feita de pequenas caixas)
  • Este é um concurso de popularidade, portanto, você pode querer adicionar alguns recursos adicionais ao seu programa para obter mais votos.
  • O vencedor é a resposta válida mais votada 7 dias após o último envio válido. Todos os envios válidos precisam seguir as regras, incluindo a descrição do algoritmo usado. Você pode adicionar recursos adicionais que não seguem algumas regras (como adicionar um modo de perspectiva), mas nesse caso eles precisam ser recursos opcionais (por exemplo, ao desativá-los, o resultado deve seguir todas as regras)
  • Meu envio não conta como válido.

Um exemplo de imagem de resultado é o seguinte:

paisagem voxel

Imagem tirada daqui

Se você precisar de alguns algoritmos, verifique aqui


Os cubos renderizados pelo Minecraft não são iguais aos voxels. Também é verdadeira projecção isométrica necessário, ou é a palavra usada vagamente como é comum em jogos en.wikipedia.org/wiki/Video_games_with_isometric_graphics
Shiona

@ shiona: A descrição do tópico foi alterada há alguns dias para dizer paralelo projetado, então qualquer coisa que não seja perspectiva deve contar. Quanto aos voxels: acho que os cubos de minecraftesqe são válidos em termos de voxels: eles podem ser considerados pixels enormes em uma grande grade 3D.
SztupY

Não, os cubos Minecraftesque não são voxels, porque os voxels não são cubos, assim como os pixels não são quadrados. citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.79.9093
Pseudônimo

Concordo com @Pseudonym meio que. Eu acho que é válido se você quiser que eles sejam cubos. No entanto, elimina praticamente todas as outras técnicas de rasterização de voxel.
precisa saber é o seguinte

Respostas:


12

Plotador de funções 3D Python2 Voxel Edition

Esta é a minha entrada nesta competição:

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 claramente indicado no título, ele funciona como um plotador de funções 3D, mas como essa competição exige que o terreno seja gerado aleatoriamente, essa função senoidal aleatória 1.5*(math.sin(1/randA*x+randC)+math.sin(1/randB*y+randD))depende de 4 variáveis ​​aleatórias. Isso cria terrenos como este: Saída aleatória

É claro que podemos substituir essa função aleatória por qualquer 2 funções variáveis, por exemplo sin(sqrt((x/2)²+(y/2)²))*3 fornece este terreno: Função 3D

e -x*y*e^(-x^2-y^2)dá o seguinte: 3Dfunction2
(os gráficos à direita são calculados por wolfram alpha)

E enquanto estamos nisso, Riemann zeta ao longo da faixa crítica:

Função Riemann zeta

Para as pessoas que não estão familiarizadas com isso, como você pode ver essas poças de água (que representam os zeros da função), todas estão em uma linha reta (parte real = 0,5). Se você puder provar isso, receberá US $ 1000000! Veja este link.

Espero que você goste!


Oi Jens, bons enredos! Eu queria saber de onde você conseguiu as imagens voxel?
Willem

Eu não me lembro exatamente onde, eu fiz uma pesquisa de imagens Gogle e editados com tinta
Jens Renders

10

C #, WPF

Eu experimentei uma caminhada aleatória , que funciona melhor do que eu esperava. Começo em algum lugar do mapa, ando até um ladrilho adjacente aleatório e incremento seu valor de altura , depois passo para o próximo e assim por diante. Isso é repetido milhares de vezes e, eventualmente, leva a um mapa de altura como este (100 x 100):

mapa de altura ampliada mapa de altura

Então, "discretizo" o mapa, reduzo o número de valores para os níveis de altura determinados e atribuo terreno / cor com base nessa altura:

mapa de terreno ampliado mapa do terreno

terreno voxel 1

Mais terrenos semelhantes a arquipélagos:

terreno voxel 2

terreno voxel 3

terreno voxel 4

voxel terreno 5

Maior número de etapas aleatórias e níveis de altura para obter terreno mais montanhoso:

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui


Código

Características: Recrie o terreno com um botão. Mostrar terreno 3D e mapa 2D. Zoom (roda do mouse) e rolagem 3D (teclas de seta). Mas não tem muito desempenho - afinal, isso é escrito exclusivamente em WPF, não em DirectX ou 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;
        }
    }
}

Legal, mas pode parecer melhor quando você 'discretiza' incluir mais baldes. Talvez apenas mais um ou dois agrupamentos? (Não é necessário, por qualquer meio Ainda +1 de mim!.)
Gaffi

1
@Gaffi eu adicionei mais resultados, incluindo os mais montanhosas
Sebastian Negraszus

4

JavaScript e Crafty.JS, para ser muito melhorado

Aqui está um exemplo de saída:

captura de tela

E aqui está o código (página 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 está sprite.png:

sprite.png

Agora, tenho algumas coisas a dizer.

  1. Não me julgue por esse código terrível! : PI escreveu muitos anos atrás, quando eu era uma péssima programação. De fato, é dos velhos tempos do site que eu tinha que nem me lembrava! http://oddllama.cu.cc/terrain/

  2. Eu meio que copiei muito código da demonstração Crafty.JS Isometric. : P

  3. A explicação virá em breve! Eu tenho que ir dormir agora, já que é tarde aqui. (É também por isso que o sprite é tão terrível!)

Basicamente, é realmente polido e será muito melhorado mais tarde!

Ele usa o mesmo algoritmo de diamante quadrado mencionado na resposta do OP.


Podemos emprestar esses sprites para uso em outros idiomas?
precisa saber é o seguinte

@PyRulez Bem, eu, uh, tipo de roubou-los (e editado-los) a partir do site Crafty.JS, então eu não tenho nenhuma idéia: P talvez eu deveria ter mencionado que
Doorknob

3

Ruby + RMagick

Estou usando o algoritmo Diamond-Square para gerar o mapa de altura.

O algoritmo em resumo:

  • Use uma matriz de matriz de empacotamento, com um tamanho de 2 ^ n
  • Quebra automática significa que qualquer índice fora dos limites é contornado, por exemplo, se o tamanho da matriz for 4 [0,0] == [4,0] == [0,4] == [4,4]. Além disso[-2,0] == [2,0] etc.
  • Definir [0,0]para uma cor aleatória
  • Siga os passos mostrados na imagem.

Imagem de explicação

  • Observe que, como a matriz envolve, quando você precisa indexar algo fora dos limites, pode usar os dados do outro lado da matriz.
  • Observe também que, na primeira etapa, os quatro cantos significam exatamente o mesmo valor (como [0,0] == [4,0] == [0,4] == [4,4])
  • Para calcular o valor do ponto preto, você deve calcular a média dos quatro pontos ao seu redor
  • Como isso resultará em uma imagem chata e cinza, você deverá adicionar um número aleatório a esse valor a cada etapa. É preferível que esse valor aleatório abranja todo o intervalo na primeira iteração, mas diminua com o tempo ao abordar subconjuntos cada vez menores da matriz. Quanto menos essa aleatoriedade diminuir com o tempo, mais barulhenta será a imagem.

  • Depois de terminar, estou simplesmente atribuindo uma cor 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: O Imagemagick que estou usando tem 16 bits

Imagem do resultado:

imagem de resultado

Nota: esta imagem é uma representação isométrica de cima para baixo, em que o tamanho de um voxel é exatamente um pixel, portanto é válido de acordo com as regras (exceto um: que minha resposta não é considerada válida)


A qualidade da sua solução isométrica descendente de um pixel é um indicador da seriedade com que você deseja que as pessoas abordem sua pergunta?
Jonathan Van Matre

Eu não acho que de cima para baixo conta como isométrico? pt.wikipedia.org/wiki/Isometric_projection
mattnewport 28/02

@ JonathanVanMatre: Na pergunta, mostrei o resultado desejado. Na resposta, mostrei o mínimo que você deve fazer para que a resposta seja válida. Como este é um concurso de popularidade, você pode escolher o que deseja fazer, mas é claro que deve prosperar para obter o resultado desejado.
SztupY

@mattnewport: bom ponto, eu estava usando por engano para todos os tipos de projeções paralelas. Fixo.
SztupY

3

Java (usando a imagem colorida de @ fejesjoco como algoritmo base)

Depois de brincar um pouco com as imagens coloridas FullRGB do @fejesjoco, notei que elas poderiam ser usadas como base para paisagens interessantes de voxel. Em vez de reimplementar o algoritmo, usei seu código como executável externo (faça o download em http://joco.name/2014/03/02/all-rgb-colors-in-one-image/ e coloque-o como artgen. exe no mesmo diretório)

Pré-visualização:
Pré-visualização

mapa de altura usado (armazenado no canal azul)
Mapa de altura

Imagem de entrada:
Entrada

O subalgoritmo que utilizo funciona da seguinte maneira:
1. Classificação
2. Comece com um pixel preto no centro
3. Até que todas as cores sejam usadas: coloque a cor atual no ponto de ajuste mais próximo e adicione os vizinhos não utilizados como novos pontos utilizáveis Quando terminou, alterei-o para reduzi-lo a 256 valores diferentes red&(green|blue) 4. Depois, uso sprites pré-gerados e giro a imagem camada por camada

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"));
    }
}

1

HTML + JavaScript

Aqui está minha tentativa na competição:

<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>

Eu uso o algoritmo Euclidean F1 Cell Noise para gerar um mapa de altura que, em seguida, transformo em uma imagem, pegando a cor apropriada de uma matriz e desenhando um quadrado a 10x, 10y de altura para que pixels mais altos sejam gerados. Em seguida, desenho um retângulo como o lado usando a mesma cor de uma matriz diferente.

Cell Noise 1 Cell Noise 2

Aqui está o mesmo código usando um algoritmo de caminhada aleatória de 10.000 etapas:

<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>

Passeio aleatório 1 ! [Caminhada aleatória 2] [4]

Quando "sai" de uma borda, ela se embrulha na outra, para que ainda pareça bem ladrilhada.

Ainda é tecnicamente paralelo, apenas de um ângulo diferente.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.