เครื่องกำเนิดภูมิประเทศ voxel แบบขนานที่คาดการณ์ไว้


20

งานของคุณคือการสร้างแผนที่ความสูงและแสดงเป็นแนวนอนแบบ voxel ที่คาดการณ์ไว้ กฎมีดังต่อไปนี้:

  • ต้องสร้างภูมิทัศน์ (heightmap ของ the) แบบสุ่ม
  • คุณต้องอธิบายด้วยวิธีที่อัลกอริทึมที่คุณใช้งานอยู่เพื่อให้ทุกคนสามารถเรียนรู้สิ่งใหม่ที่นี่
  • คุณต้องสร้างรูปภาพหรือแสดงแนวนอนที่สร้างขึ้นบนหน้าจอ
  • ภาพที่ได้จะต้องมีการฉายภาพแบบ Paralell (ดังนั้นจึงไม่ใช่มุมมอง) และจะต้องมีช่องว่างของเสียง (เท่านั้นจึงต้องทำจากกล่องเล็ก ๆ )
  • นี่คือการประกวดความนิยมดังนั้นคุณอาจต้องการเพิ่มคุณสมบัติเพิ่มเติมบางอย่างลงในโปรแกรมของคุณเพื่อรับการอัปโหลดเพิ่มเติม
  • ผู้ชนะคือคำตอบที่ถูกโหวตมากที่สุดที่ถูกต้อง 7 วันหลังจากการส่งที่ถูกต้องครั้งสุดท้าย การส่งที่ถูกต้องทั้งหมดจะต้องปฏิบัติตามกฎรวมถึงคำอธิบายของอัลกอริทึมที่ใช้ คุณสามารถเพิ่มฟีเจอร์เพิ่มเติมที่ไม่เป็นไปตามกฎบางอย่าง (เช่นการเพิ่มโหมดเปอร์สเปคทีฟ) แต่ในกรณีนี้พวกเขาต้องเป็นฟีเจอร์เสริม (เช่นเมื่อปิดผลลัพธ์ควรปฏิบัติตามกฎทั้งหมด)
  • การส่งของฉันไม่นับว่าถูกต้อง

ภาพตัวอย่างผลลัพธ์มีดังต่อไปนี้:

ภูมิทัศน์ voxel

ภาพที่นำมาจากที่นี่

หากคุณต้องการอัลกอริทึมตรวจสอบที่นี่


Minecraft ที่แสดงผล esque cubes ไม่เท่ากับ voxels นอกจากนี้ยังมีการฉายภาพวาดสามมิติที่แท้จริงต้องหรือเป็นคำที่ใช้อย่างอิสระเป็นเป็นเรื่องธรรมดาในเกมen.wikipedia.org/wiki/Video_games_with_isometric_graphics
shiona

@shiona: คำอธิบายของหัวข้อนี้เปลี่ยนไปเมื่อไม่กี่วันที่ผ่านมาเพื่อบอกว่าฉาย paralell ดังนั้นสิ่งที่ไม่ใช่มุมมองควรนับ สำหรับ voxels: ฉันคิดว่าลูกบาศก์ minecraftesqe นั้นถูกต้องในแง่ของการเป็น voxels: พวกมันสามารถพิจารณาพิกเซลขนาดใหญ่บนกริด 3 มิติขนาดใหญ่ได้
SztupY

ไม่ Minecraftesque cubes ไม่ใช่ voxels เนื่องจาก voxels ไม่ใช่ cube เช่นเดียวกับที่พิกเซลไม่ได้เป็นสี่เหลี่ยม citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.79.9093
นามแฝง

ฉันเห็นด้วยกับการเรียงลำดับ @Pseudonym ของ ฉันคิดว่ามันถูกต้องแม้ว่าคุณต้องการให้มันเป็นลูกบาศก์ มันจะกำจัดเทคนิคการแรสเตอไรซ์ของ Voxel อื่น ๆ
Tim Seguine

คำตอบ:


12

ฟังก์ชัน Python2 3D Plotter Voxel Edition

นี่คือรายการของฉันในการแข่งขันนี้:

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

ตามที่ระบุไว้อย่างชัดเจนในชื่อมันทำงานเป็นพล็อตฟังก์ชั่น 3D แต่เนื่องจากการแข่งขันนี้ต้องการภูมิประเทศที่จะสร้างแบบสุ่มมันฟังก์ชั่นแบบสุ่มนี้1.5*(math.sin(1/randA*x+randC)+math.sin(1/randB*y+randD))ที่ขึ้นอยู่กับตัวแปรสุ่ม 4 ตัว สิ่งนี้จะสร้างภูมิประเทศเช่นนี้: เอาท์พุทแบบสุ่ม

เราสามารถ ofcourse แทนที่ฟังก์ชั่นแบบสุ่มนี้โดยฟังก์ชัน 2 ตัวแปรใด ๆ ตัวอย่างเช่นsin(sqrt((x/2)²+(y/2)²))*3ให้ภูมิประเทศนี้: 3Dfunction

และ-x*y*e^(-x^2-y^2)ให้สิ่งนี้: 3Dfunction2
(แปลงทางด้านขวาถูกคำนวณโดยวุลแฟรมแอลฟา)

และในขณะที่เราอยู่ที่นี่ Riemann zeta ไปตามแถบวิกฤต:

ฟังก์ชันซีตาของ Riemann

สำหรับคนที่ไม่คุ้นเคยกับมันคุณสามารถเห็นแอ่งน้ำเหล่านี้ (ซึ่งแสดงถึงการทำงานของศูนย์) ทั้งหมดอยู่ในแนวเส้นตรง (ส่วนจริง = 0.5) หากคุณพิสูจน์ได้คุณจะได้ $ 1000000! ดูลิงค์นี้

ฉันหวังว่าคุณจะชอบมัน!


สวัสดี Jens แผนการที่ดี! ฉันสงสัยว่าคุณได้ภาพ voxel มาจากไหน?
วิลเล็ม

ฉันจำไม่ได้ว่าที่ไหนฉันค้นหารูปภาพของ gogle และแก้ไขด้วยสี
Jens Renders

10

C #, WPF

ฉันทดลองเดินสุ่มซึ่งทำงานได้ดีกว่าที่ฉันคาดไว้ ฉันเริ่มต้นที่ไหนสักแห่งบนแผนที่เดินไปที่ไทล์ที่อยู่ติดกันแบบสุ่มและเพิ่มค่าความสูงของมันจากนั้นเลื่อนไปยังตำแหน่งถัดไปเป็นต้น นี่คือการทำซ้ำหลายพันครั้งและในที่สุดก็นำไปสู่แผนที่ความสูงเช่นนี้ (100 x 100):

แผนที่ความสูงขยาย แผนที่ความสูง

จากนั้นฉันจะ "แยก" แผนที่ลดจำนวนของค่าเป็นระดับความสูงที่กำหนดและกำหนดภูมิประเทศ / สีตามความสูงนั้น:

แผนที่ภูมิประเทศแบบขยาย แผนที่ภูมิประเทศ

ภูมิประเทศ voxel 1

ภูมิประเทศที่คล้ายกับหมู่เกาะที่คล้ายกันมากขึ้น:

ภูมิประเทศ voxel 2

ภูมิประเทศ voxel 3

ภูมิประเทศ voxel 4

ภูมิประเทศ voxel 5

เพิ่มจำนวนขั้นบันไดและระดับความสูงแบบสุ่มเพื่อให้ได้ภูมิประเทศที่เป็นภูเขามากขึ้น:

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่


รหัส

คุณสมบัติ: สร้างภูมิประเทศใหม่ด้วยปุ่ม แสดงภูมิประเทศ 3 มิติและแผนที่ 2 มิติ การซูม (ล้อเมาส์) และการเลื่อน 3 มิติ (ปุ่มลูกศร) แต่มันก็ไม่ค่อยมีประสิทธิภาพนัก - หลังจากทั้งหมดมันถูกเขียนขึ้นอย่างหมดจดใน WPF ไม่ใช่ DirectX หรือ 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;
        }
    }
}

เรียบร้อย แต่อาจดูดีขึ้นเมื่อคุณ 'แยก' เพื่อรวมถังเพิ่มเติม อาจจะมีเพียงหนึ่งหรือสองกลุ่มขึ้นไป? (ไม่จำเป็นต้องใช้วิธีการใด ๆ ! ยัง +1 จากฉัน)
Gaffi

1
@Gaffi ฉันได้เพิ่มผลลัพธ์เพิ่มเติมรวมถึงคนที่เป็นภูเขามากขึ้น
เซบาสเตียนเนปัสซัส

4

JavaScript และ Crafty.JS เพื่อปรับปรุงให้ดีขึ้นมาก

นี่คือตัวอย่างผลลัพธ์:

ภาพหน้าจอ

และนี่คือรหัส (หน้าเว็บแบบเต็ม):

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

นี่คือsprite.png:

sprite.png

ตอนนี้ฉันมีบางสิ่งที่จะพูด

  1. อย่าตัดสินฉันเพราะรหัสที่แย่มาก! : PI เขียนเมื่อหลายปีก่อนตอนที่ฉันเขียนโปรแกรมแย่มาก อันที่จริงมันมาจากวันเก่า ๆ ของเว็บไซต์ที่ฉันมีที่ฉันจำไม่ได้เลย! http://oddllama.cu.cc/terrain/

  2. ฉันคัดลอกโค้ดจำนวนมากจากการสาธิต Isometric ของ Crafty.JS : P

  3. คำอธิบายจะมาเร็ว ๆ นี้! ฉันต้องไปนอนแล้วตั้งแต่ตอนนี้ที่นี่สาย (นั่นเป็นเหตุผลว่าทำไมสไปรต์นั้นแย่มาก!)

โดยพื้นฐานแล้วมันไม่ได้ขัดจริงๆและจะปรับปรุงอย่างมากมายในภายหลัง!

มันใช้อัลกอริทึมสี่เหลี่ยมจัตุรัสสี่เหลี่ยมจัตุรัสเดียวกันกับที่กล่าวไว้ในคำตอบของ OP


เราสามารถยืมสไปรต์เหล่านั้นเพื่อใช้ในภาษาอื่นได้หรือไม่?
PyRulez

@PyRulez ดีฉันเอ่อชนิดของพวกเขาขโมย (และแก้ไข) จากเว็บไซต์ Crafty.JS ดังนั้นฉันไม่มีความคิด: P บางทีฉันควรจะกล่าวว่า
Doorknob

3

Ruby + RMagick

ฉันใช้อัลกอริทึมDiamond-Square เพื่อสร้างแผนที่ความสูง

อัลกอริทึมในระยะสั้น:

  • ใช้เมทริกซ์การห่ออาร์เรย์ที่มีขนาด 2 ^ n
  • ห่อหมายความว่าการใด ๆ นอกดัชนีของขอบเขตห่อรอบเช่นถ้าขนาดของอาร์เรย์คือ [0,0] == [4,0] == [0,4] == [4,4]4 นอกจากนี้ยัง[-2,0] == [2,0]มี
  • ตั้งค่า[0,0]เป็นสีแบบสุ่ม
  • ทำตามขั้นตอนที่แสดงในภาพ

รูปภาพคำอธิบาย

  • โปรดทราบว่าเนื่องจากอาร์เรย์ล้อมรอบเมื่อคุณต้องจัดทำดัชนีบางอย่างนอกขอบเขตคุณสามารถใช้ข้อมูลจากอีกด้านหนึ่งของอาร์เรย์ได้
  • โปรดทราบด้วยว่าในขั้นตอนแรกทั้งสี่มุมมีความหมายเหมือนกันทุกประการ (ตาม[0,0] == [4,0] == [0,4] == [4,4])
  • ในการคำนวณค่าจุดดำคุณต้องเฉลี่ยสี่จุดรอบ ๆ
  • เนื่องจากจะทำให้เกิดภาพที่น่าเบื่อและเป็นสีเทาคุณจะต้องเพิ่มตัวเลขสุ่มในค่านี้ในแต่ละขั้นตอน ขอแนะนำว่าค่าแบบสุ่มนี้จะขยายช่วงทั้งหมดที่การวนซ้ำครั้งแรก แต่จะลดลงเมื่อเวลาผ่านไปเมื่อคุณกำหนดแอดเดรสย่อยที่เล็กลงและเล็กลงของอาร์เรย์ ยิ่งการสุ่มน้อยลงเมื่อเวลาผ่านไปภาพก็จะยิ่งมีเสียงดังมากขึ้นเท่านั้น

  • หลังจากที่ฉันทำฉันแค่กำหนดสีสำหรับค่าความสูงแต่ละค่า

รหัส:

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'

หมายเหตุ: Imagemagick ที่ฉันใช้คือ 16 บิต

ภาพผล:

ภาพผล

หมายเหตุ: ภาพนี้เป็นภาพจากบนลงล่างภาพวาดสามมิติซึ่งขนาดของหนึ่ง voxel คือหนึ่งพิกเซลดังนั้นจึงเป็นไปตามกฎ (ยกเว้นหนึ่ง: คำตอบของฉันไม่ถือว่าถูกต้อง)


คุณภาพของโซลูชันภาพวาดสามมิติจากบนลงล่างของคุณมีคุณภาพหมายถึงตัวบ่งชี้ความร้ายแรงที่คุณต้องการให้ผู้คนเข้ามาถามคำถามของคุณหรือไม่?
Jonathan Van Matre

ฉันไม่คิดว่าจากบนลงล่างนับว่ามีมิติเท่ากัน? en.wikipedia.org/wiki/Isometric_project
mattnewport

@JonathanVanMatre: ในคำถามฉันแสดงผลลัพธ์ที่ต้องการ ในคำตอบที่ฉันแสดงขั้นต่ำที่คุณควรทำเพื่อให้คำตอบที่ถูกต้อง เนื่องจากเป็นการประกวดความนิยมคุณสามารถเลือกสิ่งที่คุณต้องการ แต่แน่นอนคุณควรเจริญเติบโตเพื่อให้ได้ผลลัพธ์ที่ต้องการ
SztupY

@ mattnewport: จุดที่ดีฉันใช้มันผิดพลาดสำหรับการประมาณการ paralell ทุกชนิด แก้ไขแล้ว.
SztupY

3

Java (การใช้ภาพสีของ @ fejesjoco เป็นอัลกอริธึมพื้นฐาน)

หลังจากเล่นไปเรื่อย ๆ ด้วยภาพสี FullRGB จาก @fejesjoco ฉันสังเกตว่าพวกมันสามารถใช้เป็นฐานสำหรับทิวทัศน์ voxel ที่น่าสนใจ แทนที่จะ reimplementing ขั้นตอนวิธีการที่ผมใช้รหัสของเขาในฐานะที่ปฏิบัติการภายนอก (ดาวน์โหลดได้จากhttp://joco.name/2014/03/02/all-rgb-colors-in-one-image/และสถานที่ที่มันตั้งชื่อเป็น artgen exe ในไดเรกทอรีเดียวกัน)

ตัวอย่าง:
ดูตัวอย่าง

heightmap ที่ใช้ (เก็บไว้ในช่องสีฟ้า)
heightmap

ภาพอินพุต:
อินพุต

อัลกอริทึมย่อยที่ฉันใช้จากมันทำงานแบบนั้น:
1. การเรียงลำดับ
2. เริ่มต้นด้วยพิกเซลสีดำที่กึ่งกลาง
3. จนกว่าจะมีการใช้สีทั้งหมด: วางสีปัจจุบันที่จุดปรับที่ใกล้ที่สุดและเพิ่มเพื่อนบ้านที่ไม่ได้ใช้เป็นจุดใช้งานใหม่ เมื่อเสร็จแล้วฉันจะทำให้มันลดลงเหลือ 256 ค่าที่แตกต่างกัน red&(green|blue) 4. จากนั้นฉันใช้ sprite ที่สร้างไว้ล่วงหน้าแล้วสร้างเลเยอร์ภาพทีละชั้น

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

นี่คือความพยายามของฉันในการแข่งขัน:

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

ฉันใช้อัลกอริธึมEuclidean F1 Cell Noiseเพื่อสร้างแผนที่ความสูงจากนั้นฉันเปลี่ยนเป็นภาพโดยการใช้สีที่เหมาะสมจากอาร์เรย์และวาดสี่เหลี่ยมจัตุรัสที่ 10x, 10y-height เพื่อเพิ่มพิกเซลที่สูงขึ้น จากนั้นฉันวาดสี่เหลี่ยมเป็นด้านใช้สีเดียวกันจากอาร์เรย์ที่แตกต่างกัน

เสียงรบกวนของเซลล์ 1 เสียงรบกวนของเซลล์ 2

นี่คือรหัสเดียวกันโดยใช้อัลกอริทึมการสุ่ม 10,000 ขั้นตอน:

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

สุ่มเดิน 1 ! [Random Walk 2] [4]

เมื่อมัน 'เดิน' ปิดขอบหนึ่งมันจะพันกันดังนั้นมันจึงยังดูดีอยู่

มันยังคงขนานกันทางเทคนิคเพียงแค่จากมุมที่แตกต่าง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.