Générateur de terrain voxel projeté parallèle

20

Votre travail consiste à générer une carte de hauteur et à l'afficher sous la forme d'un paysage de voxels projeté en parallèle. Les règles sont les suivantes:

  • La (hauteur du paysage) doit être générée aléatoirement
  • Vous devez également décrire fonctionnement de l'algorithme que vous utilisez, afin que chacun puisse apprendre quelque chose de nouveau ici
  • Vous devez également générer une image ou afficher le paysage généré à l'écran
  • L'image résultante doit être projetée parallèlement (donc pas en perspective), et elle ne peut contenir que des voxels (elle doit donc être faite de petites boîtes)
  • Il s'agit d'un concours de popularité, vous pouvez donc ajouter des fonctionnalités supplémentaires à votre programme pour obtenir plus de votes positifs.
  • Le gagnant est la réponse valide la plus votée 7 jours après la dernière soumission valide. Toutes les soumissions valides doivent suivre les règles, y compris la description de l'algorithme utilisé. Vous pouvez ajouter des fonctionnalités supplémentaires qui ne respectent pas certaines des règles (comme l'ajout d'un mode perspective), mais dans ce cas, elles doivent être des fonctionnalités facultatives (par exemple, lorsque vous les désactivez, le résultat doit suivre toutes les règles)
  • Ma soumission ne compte pas comme valide.

Un exemple d'image de résultat est le suivant:

paysage de voxel

Image prise d'ici

Si vous avez besoin d'algorithmes, vérifiez ici

SztupY
la source
Les cubes rendus Minecraft esque ne sont pas égaux aux voxels. Une projection isométrique vraie est également requise, ou le mot est-il utilisé librement comme il est courant dans les jeux en.wikipedia.org/wiki/Video_games_with_isometric_graphics
shiona
@shiona: La description du sujet a été modifiée il y a quelques jours pour indiquer la projection parallèle, donc tout ce qui n'est pas en perspective devrait compter. Quant aux voxels: je pense que les cubes minecraftesqe sont valables en termes de voxels: ils peuvent être considérés comme des pixels énormes sur une grande grille 3D.
SztupY
Non, les cubes minécraftesques ne sont pas des voxels, car les voxels ne sont pas des cubes, tout comme la façon dont les pixels ne sont pas des carrés. citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.79.9093
Pseudonyme du
Je suis d'accord avec le type @Pseudonym. Je pense que c'est valable si vous voulez qu'ils soient des cubes. Il élimine cependant à peu près toutes les autres techniques de rastérisation de voxels.
Tim Seguine

Réponses:

12

Traceur de fonctions 3D Python2, édition Voxel

Voici mon entrée dans ce concours:

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

Comme indiqué clairement dans le titre, il fonctionne comme un traceur de fonctions 3D, mais comme cette compétition nécessite que le terrain soit généré de manière aléatoire, cette fonction sinusoïdale aléatoire 1.5*(math.sin(1/randA*x+randC)+math.sin(1/randB*y+randD))dépend de 4 variables aléatoires. Cela crée des terrains comme celui-ci: Sortie aléatoire

On peut bien sûr remplacer cette fonction aléatoire par n'importe quelle fonction à 2 variables, par exemple sin(sqrt((x/2)²+(y/2)²))*3 donne ce terrain: Fonction 3D

et -x*y*e^(-x^2-y^2)donne ceci: 3Dfunction2
(les tracés à droite sont calculés par wolfram alpha)

Et pendant que nous y sommes, Riemann zeta le long de la bande critique:

Fonction Riemann zeta

Pour les personnes qui ne la connaissent pas, comme vous pouvez le voir, ces bassins d'eau (qui représentent les zéro de la fonction) se trouvent tous sur une ligne droite (partie réelle = 0,5). Si vous pouvez le prouver, vous obtiendrez 1000000 $! Voir ce lien.

J'espère que tu aimes!

Jens Renders
la source
Salut Jens, belles parcelles! Je me demandais d'où vous veniez les images de voxels?
Willem
Je ne me souviens pas exactement où, j'ai fait une recherche d'images Google et édité avec de la peinture
Jens Renders
10

C #, WPF

J'ai expérimenté une marche aléatoire , qui fonctionne mieux que je ne l'avais prévu. Je commence quelque part sur la carte, marche jusqu'à une tuile adjacente aléatoire et augmente sa valeur de hauteur , puis passe à la suivante et ainsi de suite. Ceci est répété des milliers de fois et conduit finalement à une carte de hauteur comme celle-ci (100 x 100):

carte de hauteur agrandie carte de la hauteur

Ensuite, je «discrétise» la carte, réduit le nombre de valeurs aux niveaux de hauteur donnés et attribue le terrain / la couleur en fonction de cette hauteur:

carte du terrain agrandie carte du relief

voxel terrain 1

Plus de terrains similaires à des archipels:

voxel terrain 2

voxel terrain 3

voxel terrain 4

voxel terrain 5

Augmentation du nombre d'étapes aléatoires et de niveaux de hauteur pour obtenir un terrain plus montagneux:

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici


Code

Caractéristiques: Recréez le terrain avec un bouton. Afficher le terrain 3D et la carte 2D. Zoom (molette de la souris) et défilement 3D (touches fléchées). Mais ce n'est pas très performant - après tout, cela est écrit uniquement en WPF, pas 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;
        }
    }
}
Sebastian Negraszus
la source
C'est bien, mais cela pourrait mieux paraître lorsque vous «discrétisez» pour inclure plus de compartiments. Peut-être seulement un ou deux groupes supplémentaires? (Pas du tout nécessaire! Encore +1 de ma part.)
Gaffi
1
@Gaffi J'ai ajouté plus de résultats, y compris plus de montagneux
Sebastian Negraszus
4

JavaScript et Crafty.JS, à améliorer considérablement

Voici un exemple de sortie:

capture d'écran

Et voici le code (page Web complète):

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

Voici sprite.png:

sprite.png

Maintenant, j'ai quelques choses à dire.

  1. Ne me jugez pas pour ce terrible code! : PI l'a écrit il y a de nombreuses années quand j'étais une terrible programmation. En fait, c'est de l'ancien temps du site que j'avais que je ne me souvenais même pas! http://oddllama.cu.cc/terrain/

  2. J'ai en quelque sorte copié beaucoup de code de la démo isométrique Crafty.JS . : P

  3. L'explication viendra bientôt! Je dois dormir maintenant car il est tard ici. (C'est aussi pourquoi le sprite est si terrible!)

Fondamentalement, c'est vraiment non poli et il sera considérablement amélioré plus tard!

Il utilise le même algorithme de diamant carré mentionné dans la réponse du PO.

Poignée de porte
la source
Pouvons-nous emprunter ces sprites pour les utiliser dans d'autres langues?
PyRulez
@PyRulez Eh bien, je, euh, je les ai volés (et édités) sur le site Crafty.JS, donc je n'ai aucune idée: P j'aurais peut-être dû le mentionner
Poignée de porte
3

Ruby + RMagick

J'utilise l' algorithme Diamond-Square pour générer la carte de hauteur.

L'algorithme en bref:

  • Utilisez une matrice de tableau enveloppant, avec une taille de 2 ^ n
  • L'habillage signifie que tout index en dehors des limites s'enroule, par exemple si la taille du tableau est 4 [0,0] == [4,0] == [0,4] == [4,4]. Aussi [-2,0] == [2,0], etc.
  • Définir [0,0]sur une couleur aléatoire
  • Suivez les étapes indiquées dans l'image.

Image d'explication

  • Notez que, puisque le tableau s'enroule, lorsque vous devez indexer quelque chose en dehors des limites, vous pouvez utiliser les données de l'autre côté du tableau.
  • Notez également que, dans la toute première étape, les quatre coins signifient exactement la même valeur (comme [0,0] == [4,0] == [0,4] == [4,4])
  • Pour calculer la valeur du point noir, vous devez faire la moyenne des quatre points qui l'entourent
  • Comme cela se traduira par une image grise ennuyeuse, vous devez ajouter un nombre aléatoire à cette valeur à chaque étape. Il est préférable que cette valeur aléatoire couvre toute la plage à la première itération, mais diminue au fil du temps lorsque vous vous adressez à des sous-ensembles de plus en plus petits du tableau. Moins ce caractère aléatoire diminue avec le temps, plus l'image sera bruyante.

  • Après avoir terminé, j'attribue simplement une couleur à chaque valeur de hauteur.

Code:

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'

Remarque: l'Imagemagick que j'utilise est de 16 bits

Image de résultat:

image de résultat

Remarque: cette image est une représentation isométrique de haut en bas, où la taille d'un voxel est exactement d'un pixel, donc elle est valide selon les règles (sauf une: que ma réponse n'est pas considérée comme valide)

SztupY
la source
La qualité de votre solution isométrique descendante d'un pixel est-elle un indicateur du sérieux avec lequel vous souhaitez que les gens abordent votre question?
Jonathan Van Matre
Je ne pense pas que le top down compte comme isométrique? en.wikipedia.org/wiki/Isometric_projection
mattnewport
@JonathanVanMatre: Dans la question, j'ai montré le résultat souhaité. Dans la réponse, j'ai montré le minimum que vous devez faire pour que la réponse soit valide. Comme il s'agit d'un concours de popularité, vous pouvez choisir ce que vous voulez faire, mais bien sûr, vous devez vous efforcer de faire le résultat souhaité.
SztupY
@mattnewport: bon point, je l' utilisais par erreur pour toutes sortes de projections parallèles. Fixé.
SztupY
3

Java (en utilisant l'image couleur de @ fejesjoco comme algorithme de base)

Après avoir joué un peu avec les images couleur FullRGB de @fejesjoco, j'ai remarqué qu'elles pouvaient être utilisées comme base pour d'intéressants paysages de voxels falaises. Au lieu de réimplémenter l'algorithme, j'ai utilisé son code comme un exécutable externe (téléchargez-le depuis http://joco.name/2014/03/02/all-rgb-colors-in-one-image/ et placez-le nommé artgen. exe dans le même répertoire)

Aperçu:
Aperçu

carte de hauteur utilisée (stockée dans le canal bleu)
Carte de hauteur

Image d'entrée:
Contribution

Le sous-algorithme que j'utilise fonctionne de cette manière:
1. Tri
2. Commencez avec un pixel noir au centre
3. Jusqu'à ce que toutes les couleurs soient utilisées: placez la couleur actuelle au point de montage le plus proche et ajoutez les voisins inutilisés comme nouveaux points utilisables Quand il a fini, je le modifie pour le réduire à 256 valeurs différentes red&(green|blue) 4. puis j'utilise des sprites prégénérés et génère l'image couche par couche

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
la source
1

HTML + JavaScript

Voici ma tentative de compétition:

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

J'utilise l' algorithme Euclidean F1 Cell Noise pour générer une carte de hauteur que je transforme ensuite en une image en prenant la couleur appropriée dans un tableau et en dessinant un carré à 10x, 10y-hauteur pour que des pixels plus élevés soient élevés. Je dessine ensuite un rectangle comme côté en utilisant la même couleur à partir d'un tableau différent.

Bruit cellulaire 1 Bruit cellulaire 2

Voici le même code utilisant un algorithme de marche aléatoire de 10 000 étapes:

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

Random Walk 1 ! [Random Walk 2] [4]

Quand il «marche» d'un bord, il s'enroule sur l'autre, donc il a toujours l'air bien carrelé.

Il est toujours techniquement parallèle, juste sous un angle différent.

kitcar2000
la source