Cliffs terrain generation in minecraft-like game - c#

I want to generate something like this:
I use Perlin Noise with sharp curve, my code produces those cliffs:
.
for (int x = 0; x < sizeX; x++)
{
for (int z = 0; z < sizeZ; z++)
{
int floorY = map.GetMaxYNotWater(x, z);
float n = hillsNoise.Noise(x, z);
int hillY = (int)(curveHills.Evaluate(n) * 80f);
if (hillY > floorY + 5)
{
for (int y = hillY; y > floorY; y--)
{
map.SetBlock(GetBlock(y), new Vector3i(x, y, z));
}
}
}
}
How can I "cut" them to make hanging things?
I tried to do it like this with additional curve:
for (int x = 0; x < sizeX; x++)
{
for (int z = 0; z < sizeZ; z++)
{
int floorY = map.GetMaxYNotWater(x, z);
float n = hillsNoise.Noise(x, z);
int hillY = (int)(curveHills.Evaluate(n) * 80f);
if (hillY > floorY + 5)
{
int c = 0;
int max = hillY - floorY;
max = (int)(max * curveHillsFull.Evaluate(n)) + 1;
for (int y = hillY; y > floorY && c < max; y--, c++)
{
map.SetBlock(GetBlock(y), new Vector3i(x, y, z));
}
}
}
}
But it produces flying islands.
So what can I do to achieve the first screenshot results?

I can't say how Minecraft does it, but from my own experience with voxel terrain, the best way to approach it is to think of the voxel grid as something like a cloud: each voxel has a density, and when that density is high enough, it becomes a 'visible' part of the cloud and you fill the voxel.
So rather than calculating the min and max Y levels, work on calculating the density value, something like this:
for (int x = 0; x < sizeX; x++)
{
for (int y = 0; y > sizeY; y--)
{
for (int z = 0; z < sizeZ; z++)
{
//This means less density at higher elevations, great for turning
//a uniform cloud into a terrain. Multiply this for flatter worlds
float flatWorldDensity = y;
//This calculates 3d Noise: you will probably have to tweak this
//heavily. Multiplying input co-ordinates will allow you to scale
//terrain features, while multiplying the noise itself will make the
//features stronger and more or less apparent
float xNoise = hillsNoise.Noise(x, y);
float yNoise = hillsNoise.Noise(x, z);
float zNoise = hillsNoise.Noise(y, z);
float 3dNoiseDensity = (xNoise + yNoise + zNoise) / 3;
//And this adds them together. Change the constant "1" to get more or
//less land material. Simple!
float ActualDensity = flatWorldDensity + 3dNoiseDensity;
if (ActualDensity > 1)
{
map.SetBlock(GetBlock(y), new Vector3i(x, y, z));
}
}
}
}

Related

Raycast not capturing all Vector coordinates

I have a gameobject that occupies the whole screen just for testing purposes. I'm drawing a line btw. What I'm trying to achieve is if the mouse position hits a gameobject it will store the vector2 coordinates in a list. But raycast is not storing all the coordinates. Below is my code
private void Update()
{
if (Input.GetMouseButton(0))
{
Vector2 mousePos = Input.mousePosition;
Vector2 Pos = _camera.ScreenToWorldPoint(mousePos);
if(!mousePositions.Contains(Pos))
mousePositions.Add(Pos);
if (Physics.Raycast(Camera.main.ScreenPointToRay(mousePos), out RaycastHit hit))
{
Vector2 textureCoord = hit.textureCoord;
int pixelX = (int)(textureCoord.x * _templateDirtMask.width);
int pixelY = (int)(textureCoord.y * _templateDirtMask.height);
Vector2Int paintPixelPosition = new Vector2Int(pixelX, pixelY);
if (!linePositions.Contains(paintPixelPosition))
linePositions.Add(paintPixelPosition);
foreach (Vector2Int pos in linePositions)
{
int pixelXOffset = pos.x - (_brush.width / 2);
int pixelYOffset = pos.y - (_brush.height / 2);
for (int x = 0; x < _brush.width; x++)
{
for (int y = 0; y < _brush.height; y++)
{
_templateDirtMask.SetPixel(
pixelXOffset + x,
pixelYOffset + y,
Color.black
);
}
}
}
_templateDirtMask.Apply();
}
}
}
Everytime I checked the element count mousePositions are always greater than linePositions. I don't know what's causing this
the element count mousePositions are always greater than linePosition
well it is quite simple: In
int pixelX = (int)(textureCoord.x * _templateDirtMask.width);
int pixelY = (int)(textureCoord.y * _templateDirtMask.height);
you are casting to int and cut off any decimals after the comma (basically like doing Mathf.FloorToInt).
So you can totally have multiple mouse positions which result in float pixel positions like e.g.
1.2, 1.2
1.4, 1.7
1.02, 1.93
...
all these will map to
Vector2Int paintPixelPosition = new Vector2Int(1, 1);
Besides, you might want to look at some better line drawing algorithms like e.g. this simple one
And then note that calling SetPixel repeatedly is quite expensive. You want to do a single SetPixels call like e.g.
var pixels = _templateDirtMask.GetPixels();
foreach (Vector2Int pos in linePositions)
{
int pixelXOffset = pos.x - (_brush.width / 2);
int pixelYOffset = pos.y - (_brush.height / 2);
for (int x = 0; x < _brush.width; x++)
{
for (int y = 0; y < _brush.height; y++)
{
pixels[(pixelXOffset + x) + (pixelYOffset + y) * _templateDirtMask.width] = Color.black;
}
}
}
_templateDirtMask.SetPixels(pixels);
_templateDirtMask.Apply();
It happens because there is really could be a case, when several elements from mousePositions are associated with one elment from linePositions.
Rough example: your texture resolution is only 1x1px. In this case you linePositons will contain only one element. And this element will be associated with all elements from mosePositions.
So, relation of the number of elements in these lists depends on relation of your texture and screen resolutions.

Unity - Physics.CheckSphere problem with Masks

I am having trouble with Unity Physics.CheckSphere. I have added a mask "Wall" and assigned it to the object.
In my custom editor window I can select this mask and set values as a grid to check for this object. However, when I click the button I don't get any collisions. But when I change the object's mask to something like "TransparentFX" I get the results I expected.
What am I missing here?
Here is the code for the custom editor window:
private void OnGUI()
{
GUILayout.Label("Generate A*", EditorStyles.boldLabel);
showGridTools = EditorGUILayout.BeginFoldoutHeaderGroup(showGridTools, "Grid Tools");
if(showGridTools)
{
this.GridDimensions = EditorGUILayout.Vector3IntField("Grid Dimensions", GridDimensions);
this.ObstacleMask = EditorGUILayout.LayerField("Obstacle Mask", this.ObstacleMask);
if (GUILayout.Button("Generate Grid"))
CreateGrid();
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
And for checking the collisions:
private void CreateGrid()
{
for (int x = 0; x < this.GridDimensions.x; x++)
{
for (int z = 0; z < this.GridDimensions.z; z++)
{
for (int y = 0; y < this.GridDimensions.y; y++)
{
Vector3Int position = new Vector3Int(x, y, z);
bool obstacle = Physics.CheckSphere(position, 0.3f, this.ObstacleMask);
if(obstacle)
Debug.Log($"Position: {position}, Obstacle: {obstacle}");
}
}
}
}
----------- EDIT -----------
I just did this. Might be pretty slow, I don't know, but at least it
works for now.
private void CreateGrid()
{
this.Grid = new Node[this.GridDimensions.x, this.GridDimensions.y, this.GridDimensions.z];
for (int x = 0; x < this.GridDimensions.x; x++)
{
for (int z = 0; z < this.GridDimensions.z; z++)
{
for (int y = 0; y < this.GridDimensions.y; y++)
{
Vector3Int position = new Vector3Int(x, y, z);
var collisions = Physics.OverlapSphere(position, 0.3f);
bool obstacle = collisions.Where(c => c.gameObject.layer == this.ObstacleMask).Count() > 0;
if (obstacle)
Debug.Log($"Position: {position}, Obstacle: {obstacle}");
}
}
}
}

Using gizmos to illustrate the sorting process

The codes below generate 10000cubes(using gizmos). I'm stuck on how to further expand the code and using gizmos to illustrate the sorting process(movement of the cubes when mouse button is click), appreciate if can give me some hint on how to do it.
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
int x;
int y;
int z;
int[,] array1 = new int[100, 100];
int temp;
void Start()
{
for (x = 0; x < 100; x++)
{
for (y = 0; y < 100; y++)
{
array1[x, y] = Random.Range(0, 2);
Debug.Log(string.Format("{0},{1},{2}", x, y, array1[x, y]));
}
}
}
void OnDrawGizmos()
{
for (x = 0; x < 100; x++)
{
for (y = 0; y < 100; y++)
{
Vector2 pos1 = new Vector2(0 + x, 0 + y);
Gizmos.DrawCube(pos1, transform.position);
Gizmos.color = (array1[x, y] == 1) ? Color.black : Color.white;
}
}
}
void OnMouseDown()
{
for (z = 0; z < 100; z++)
{
for (x = 0; x < 100; x++)
{
for (y = 0; y < 100-z; y++)
{
if (array1[x, y] > array1[x, y + 1])
temp = array1[x, y];
array1[x + 1, y] = array1[x, y];
array1[x, y] = temp;
}
}
}
}
}
1.Possible Solution
I provide an answer in this part, and suggest better alternatives below.
The problem with your code is that the sorting happens synchronously, within one frame. What you can do is move the sorting code to Update, and sort a little between each frame. This will let the Gizmos call draw the correct state.
2.Use Progress Bar instead
You may use EditorUtility.DisplayProgressBar to display a progress bar in the Editor. Since you're using Gizmos which don't show up in the Build, this solution assumes you're working in the Editor.
3.Use Compute Buffers
CatLikeCoding has this fantastic article on using Compute Shaders to draw a Graph in high resolution. You can follow it and draw your sorting progress in a very high resolution. Furthermore, this will work in both editor and runtime.

Map flipped on the horizontal Axis when displayed

I am having a problem with displaying maps on screen. I am giving the developer the choice between square tiles and hexagonal tiles. The map is saved as a .txt
The square tiled map displays correctly as shown
where as the hexagonal map is upside down
I am completely stumped as to how to go about fixing the issue I have supplied the entire function bellow
void displayMap(){
//Draw Tiles on the Screen
if (useSquareTiles == true && useHexagonTiles == false) {
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
GameObject.Instantiate (mapArray [y] [x], new Vector3 (x, mapHeight - y, 0),
Quaternion.Euler (-90, 0, 0));
}
}
}
float hexWidth = 0.85f; //The width of the HexTiles
if (useHexagonTiles == true && useSquareTiles == false) {
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
GameObject.Instantiate (mapArray[y][x], new Vector3 (x * hexWidth, y + (0.5f * Mathf.Abs (x) % 1), 0),
Quaternion.Euler (-90, 0, 0));
}
}
}
}

C# XNA 4.0 frame rate problems, possible .SetData missuse

I have a problem regarding frame rate drop while trying to make real time 3D terrain changes. I use C#, XNA 4.0 and VS 2010. This is my old school project and time has come to finish it.
I already did terrain generation from image file, with all effects and stuff and it is running smoothly no matter what resolution image file is. Problem is with my terrain editor. I want it to be able to manually alter the terrain. I did that part too, but it works only if terrain size is equal or less than 128x128 pixels. If the terrain size is greater I start to get frame rate drops around 150x150 pixels, and it is completely unmanageable if terrain size is greater than 512x512 pixels.
I already tried several approaches:
tried to use threads, but then I get weird error saying something like "Draw method can be called in one thread at a time" or something similar, and that I can't resolve.
next I tried to use DynamicVertexBuffer and DynamicIndexBuffer. That helped a lot and now my code is working with acceptable frame rate for terrain size of up to 256x256 pixels.
Have a look at my code:
public void ChangeTerrain(float[,] heightData)
{
int x, y;
int v = 1;
if (currentMouseState.LeftButton == ButtonState.Pressed && currentMouseState.X < 512)
{
x = (int)currentMouseState.X / 2;
y = (int)currentMouseState.Y / 2;
if (x < 5)
x = 5;
if (x >= 251)
x = 251;
if (y < 5)
y = 5;
if (y >= 251)
y = 251;
for (int i = x - 4; i < x + 4; i++)
{
for (int j = y - 4; j < y + 4; j++)
{
if (i == x - 4 || i == x + 3 || j == y - 4 || j == y + 3)
v = 3;
else
v = 5;
if (heightData[i, j] < 210)
{
heightData[i, j] += v;
}
}
}
}
if (currentMouseState.RightButton == ButtonState.Pressed && currentMouseState.X < 512)
{
x = (int)currentMouseState.X / 2;
y = (int)currentMouseState.Y / 2;
if (x < 5)
x = 5;
if (x >= 251)
x = 251;
if (y < 5)
y = 5;
if (y >= 251)
y = 251;
for (int i = x - 4; i < x + 4; i++)
{
for (int j = y - 4; j < y + 4; j++)
{
if (heightData[i, j] > 0)
{
heightData[i, j] -= 1;
}
}
}
}
if (keyState.IsKeyDown(Keys.R))
{
for (int i = 0; i < 256; i++)
for (int j = 0; j < 256; j++)
heightData[i, j] = 0f;
}
SetUpTerrainVertices();
CalculateNormals();
terrainVertexBuffer.SetData(vertices, 0, vertices.Length);
}
I work with resolution of 1024x512 pixels, so I scale mouse position by 1/2 to get terrain position. I use left and right mouse button to alter terrain, i.e. to alter heightData from which 3D terrain is generated.
Last 3 lines create Vertices from new heightData, calculate Normals so shades could be applied and last line is just throwing Vertices data to Vertex Buffer.
Prior to that, I set up dynamic Vertex and Index buffer in LoadContent method and call initial Vertices and Indices setup. This method (ChangeTerrain) is called from Update method.
I did some debugging and found out that maximum size of vertices in most extreme case would be around 260000 +- few thousands. Is it possible that .SetData is so much time consuming it is causing frame rate drops? Or is it something else? How can I fix that and make my editor functioning normally for any terrain size?
Also, i red that I need to use this code with DynamicVertexBuffer, but I can't make it work in XNA 4.0.
terrainVertexBuffer.ContentLost += new EventHandler(TerrainVertexBufferContentLost);
public void TerrainVertexBufferContentLost()
{
terrainVertexBuffer(vertices, 0, vertices.Length, SetDataOptions.NoOverwrite);
}
Thanks for your help!
EDIT:
This is my SetUpTerrainVertices code:
private void SetUpTerrainVertices()
{
vertices = new VertexPositionNormalColored[terrainWidth * terrainLength];
for (int x = 0; x < terrainWidth; x++)
{
for (int y = 0; y < terrainLength; y++)
{
vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y);
vertices[x + y * terrainWidth].Color = Color.Gray;
}
}
}
And my CalculateNormals
private void CalculateNormals()
{
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal = new Vector3(0, 0, 0);
for (int i = 0; i < indices.Length / 3; i++)
{
int index1 = indices[i * 3];
int index2 = indices[i * 3 + 1];
int index3 = indices[i * 3 + 2];
Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
Vector3 normal = Vector3.Cross(side1, side2);
vertices[index1].Normal += normal;
vertices[index2].Normal += normal;
vertices[index3].Normal += normal;
}
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal.Normalize();
}
I set up vertex and index buffers in XNA LoadContent Method using lines:
terrainVertexBuffer = new DynamicVertexBuffer(device, VertexPositionNormalColored.VertexDeclaration, vertices.Length,
BufferUsage.None);
terrainIndexBuffer = new DynamicIndexBuffer(device, typeof(int), indices.Length, BufferUsage.None);
I call ChangeTerrain method from Update and this is how i Draw:
private void DrawTerrain(Matrix currentViewMatrix)
{
device.DepthStencilState = DepthStencilState.Default;
device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);
effect.CurrentTechnique = effect.Techniques["Colored"];
Matrix worldMatrix = Matrix.Identity;
effect.Parameters["xWorld"].SetValue(worldMatrix);
effect.Parameters["xView"].SetValue(currentViewMatrix);
effect.Parameters["xProjection"].SetValue(projectionMatrix);
effect.Parameters["xEnableLighting"].SetValue(true);
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
device.Indices = terrainIndexBuffer;
device.SetVertexBuffer(terrainVertexBuffer);
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);
}
}
EDIT2:
Ok, I decided to go for your second suggestion and got into some problems. I modified my methods like this:
public void ChangeTerrain(Texture2D heightmap)
{
Color[] mapColors = new Color[256 * 256];
Color[] originalColors = new Color[256 * 256];
for (int i = 0; i < 256 * 256; i++)
originalColors[i] = new Color(0, 0, 0);
heightMap2.GetData(mapColors);
device.Textures[0] = null;
device.Textures[1] = null;
int x, y;
int v = 1;
if (currentMouseState.LeftButton == ButtonState.Pressed && currentMouseState.X < 512)
{
x = (int)currentMouseState.X / 2;
y = (int)currentMouseState.Y / 2;
if (x < 4)
x = 4;
if (x >= 251)
x = 251;
if (y < 4)
y = 4;
if (y >= 251)
y = 251;
for (int i = x-4; i < x+4; i++)
{
for (int j = y-4; j < y+4; j++)
{
if (i == x - 4 || i == x + 3 || j == y - 4 || j == y + 3)
v = 3;
else
v = 5;
if (mapColors[i + j * 256].R < 210)
{
mapColors[i + j * 256].R += (byte)(v);
mapColors[i + j * 256].G += (byte)(v);
mapColors[i + j * 256].B += (byte)(v);
}
heightMap2.SetData(mapColors);
}
}
}
if (currentMouseState.RightButton == ButtonState.Pressed && currentMouseState.X < 512)
{
x = (int)currentMouseState.X / 2;
y = (int)currentMouseState.Y / 2;
if (x < 4)
x = 4;
if (x >= 251)
x = 251;
if (y < 4)
y = 4;
if (y >= 251)
y = 251;
for (int i = x - 4; i < x + 4; i++)
{
for (int j = y - 4; j < y + 4; j++)
{
if (mapColors[i + j * 256].R > 0)
{
mapColors[i + j * 256].R -= 1;
mapColors[i + j * 256].G -= 1;
mapColors[i + j * 256].B -= 1;
}
heightMap2.SetData(mapColors);
}
}
}
if (keyState.IsKeyDown(Keys.R))
heightMap2.SetData(originalColors);
}
Generating flat surface - only once in LoadContent() method:
vertices are assigned only once
private void SetUpTerrainVertices()
{
for (int x = 0; x < terrainWidth; x++)
{
for (int y = 0; y < terrainLength; y++)
{
vertices[x + y * terrainWidth].Position = new Vector3(x, 0, -y);
vertices[x + y * terrainLength].Color = Color.Gray;
}
}
}
Draw method is same as previous, but with one extra line:
effect.Parameters["xTexture0"].SetValue(heightMap2);
also, I made new technique called Editor and it looks like this:
//------- Technique: Editor --------
struct EditorVertexToPixel
{
float4 Position : POSITION;
float4 Color : COLOR0;
float LightingFactor: TEXCOORD0;
float2 TextureColor : TEXCOORD1;
};
struct EditorPixelToFrame
{
float4 Color : COLOR0;
};
EditorVertexToPixel EditorVS( float4 inPos : POSITION, float4 inColor: COLOR, float3 inNormal: NORMAL, float2 inTextureColor: TEXCOORD1)
{
EditorVertexToPixel Output = (EditorVertexToPixel)0;
float4x4 preViewProjection = mul (xView, xProjection);
float4x4 preWorldViewProjection = mul (xWorld, preViewProjection);
float4 Height;
float4 position2 = inPos;
position2.y += Height;
Output.Color = inColor;
Output.Position = mul(position2, preWorldViewProjection);
Output.TextureColor = inTextureColor;
float3 Normal = normalize(mul(normalize(inNormal), xWorld));
Output.LightingFactor = 1;
if (xEnableLighting)
Output.LightingFactor = saturate(dot(Normal, -xLightDirection));
return Output;
}
EditorPixelToFrame EditorPS(EditorVertexToPixel PSIn)
{
EditorPixelToFrame Output = (EditorPixelToFrame)0;
//float4 height2 = tex2D(HeightSAmpler, PSIn.TextureColor);
float4 colorNEW = float4(0.1f, 0.1f, 0.6f, 1);
Output.Color = PSIn.Color * colorNEW;
Output.Color.rgb *= saturate(PSIn.LightingFactor) + xAmbient;
return Output;
}
technique Editor
{
pass Pass0
{
VertexShader = compile vs_3_0 EditorVS();
PixelShader = compile ps_3_0 EditorPS();
}
}
this code doesn't work because float4 Height is not set. What I wanted to do is to sample texture colors into float4 Height (using Sample), but I can not use sampler in VertexShader. I get error message "X4532 cannot map expression to vertex shader instruction set".
Then, I red that you can use SampleLevel in VertexShader to sample color data and thought I found solution, but I get strange error that is only documented in one Russian blog, but I can't speak or read Russian. Error is: "X4814 unexpected Alias on texture declaration"
Is there a way to sample colors in PixelShader and then pass them to VertexShader?
This could work cos I managed to set float4 Height to various values and it altered vertices height. Problem is, I don't know how to read texture color in VertexShader, or how to pass red texture color data from PixelShader to VertexShader.
EDIT3:
I think I found solution. Was searching the net and found out about tex2Dlod function to use as VertexShader texture sampler. But there are different syntax displayed and I can't make them work.
Can anyone point out on good HLSL literature to learn a bit about HLSL coding. This task seems pretty easy, but somehow, I can't make it to work.
Ok, so I can't offer "real" performance advice - because I haven't measured your code. And measuring is probably the most important part of performance optimisation - you need to be able to answer the questions: "am I slower than my performance target?" and "why am I slower than my target?"
That being said - here are the things that stand out to me as a seasoned developer:
This method (ChangeTerrain) is called from Update method
You should probably consider splitting that method up so that, rather than recreating your data every frame, it only does work when the terrain is actually changed.
vertices = new VertexPositionNormalColored[terrainWidth * terrainLength];
Allocating a new vertices buffer each frame is huge memory allocation (6MB at 512x512). This is going to put a big strain on the garbage collector - and I suspect this is the primary cause of your performance issues.
Given that you're about to set all the data in that array anyway, simply delete that line and the old data in the array will be overwritten.
Better yet, you could leave the data that doesn't change as-is, and only modify the vertices that are actually changed. In much the same way you are doing for heightData.
As part of this, it would be a very good idea to modify CalculateNormals so that, rather than having to rely on the index buffer and going through every triangle, it could calculate the indices of surrounding vertices (that form triangles) for any specific vertex - something you can do because vertices is ordered. Again, kind of like what you're doing for heightData.
terrainVertexBuffer.SetData(vertices, 0, vertices.Length);
This is sending the full 6MB buffer to the GPU. There are versions of SetData that only send a subset of the full buffer to the GPU. You should probably try and use these.
Just remember that each SetData call comes with some overhead, so don't get too granular. It's probably best to have just one call per vertex buffer, even if that means some unmodified parts of the buffer must be sent.
This is probably the only place where "chunking" your terrain would have an significant impact, as it would allow you to specify a tighter region for each SetData call - allowing you to send less unmodified data. (I'll leave figuring out why this is the case as an exercise.)
(You're already using DynamicVertexBuffer, which is good, because this means the GPU will automatically handle the pipeline issues of having its buffer changed on-the-fly.)
Finally, if performance is still an issue, you could consider a different approach entirely.
One example might be to offload the calculation of the geometry to the GPU. You'd convert your heightData to a texture, and use a vertex shader (with a flat grid of vertices as input) to sample that texture and output the appropriate positions and normals.
One big advantage of this approach is that heightData can be a lot smaller (0.25MB at 512x512) than your vertex buffer - that's much less data that the CPU needs to process and send to the GPU.

Categories