Using Perlin Noise across multiple Unity Terrain objects - c#

I have a class project in which we are supposed to use Unities Terrain 3D objects and create a 3x3 smoothly generated terrain. For this we have been told to create a central Terrain the has adjacent terrains in the 8 cardinal directions. I have gotten the Perlin Noise to work through this method
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TerrainNoiseGeneration : MonoBehaviour
{
private TerrainData myTerrainData;
public Vector3 worldSize;
public int resolution = 129;
private float userInput = (float)4.2;
public float offsetX;
public float offsetZ;
// Start is called before the first frame update
void Start()
{
myTerrainData = gameObject.GetComponent<TerrainCollider>().terrainData;
worldSize = new Vector3(100, 50, 100);
myTerrainData.size = worldSize;
myTerrainData.heightmapResolution = resolution;
float[,] heightArray = new float[resolution, resolution];
heightArray = PerlinNoise(userInput, offsetX, offsetZ);
myTerrainData.SetHeights(0, 0, heightArray);
}
// Update is called once per frame
void Update()
{
float[,] heightArray = new float[resolution, resolution];
heightArray = PerlinNoise(userInput, offsetX, offsetZ);
myTerrainData.SetHeights(0, 0, heightArray);
}
float[,] PerlinNoise(float userInput, float offsetX, float offsetZ)
{
float[,] heights = new float[resolution, resolution];
for (int z = 0; z < resolution; z++)
{
for (int x = 0; x < resolution; x++)
{
float nx = (x + offsetX) / resolution * userInput;
float ny = (z + offsetZ) / resolution * userInput;
heights[z, x] = Mathf.PerlinNoise(nx, ny);
}
}
return heights;
}
This code allows me to Generate a smooth terrain in the first Terrain object but when I try entering the offset values so that the edges can line-up they do not have the same values.
I would appreciate any assistance on this issue as I have tried a lot of different solutions, none of which are working
Update: I was able to solve the problem with a rather simple solution of the fact that I needed to use my resolution as the offset not the distance between the terrains

I needed to set the OffsetX and OffsetZ equal to that of their respective resolution positions instead of their unity positions.
For example my terrains are 100x100 so I was setting offset to 100 or -100 depending on its location but instead I needed to use 128 or -128 to keep it in line with the resolution

Related

Two different Vector3.zero's, but no parent?

UPDATE
I found out that the mesh center of the mesh object is not at 0,0,0. Does that do anything?
I have the following problem. I am generating a terrain from Perlin noise and that works fine. However, as soon as I try to instantiate any objects on it, some are spawned in the terrain area and some completely outside. When I reset the object's transform, it teleports to (0,0,0) as expected, but when I reset another object, that was not instantiated at runtime, the (0,0,0) is at a completely different location! I have no parent set to these objects and no parent set to the other object as well. Below is my code for generating the objects:
private void AddRocks(Terrain terrain, int count)
{
for (int i = 0; i < count; i++)
{
float randX = Random.Range(0, 256); //256 is my terrain size, the transform is all zeros and 1 for the transform size.
float randZ = Random.Range(0, 256);
GameObject newGameObject = Instantiate(rockPrefab,
new Vector3(randX, terrain.terrainData.GetHeight((int)randX, (int)randZ),
randZ), Quaternion.identity);
}
}
This is my code for generating the perlin noise terrain:
TerrainData GenerateTerrain(TerrainData terrainData)
{
terrainData.heightmapResolution = width + 1;
terrainData.size = new Vector3(width, depth, height);
terrainData.SetHeights(0, 0, GenerateHeights());
return terrainData;
}
float[,] GenerateHeights()
{
float[,] heights = new float[width, height];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
heights[x, y] = CalculateHeight(x, y);
}
}
return heights;
}
float CalculateHeight(int x, int y)
{
float xCoord = (float)x / width * scale + offsetX;
float yCoord = (float)y / height * scale + offsetY;
return Mathf.PerlinNoise(xCoord, yCoord);
}
This is how I call them in Start:
terrain.terrainData = GenerateTerrain(terrain.terrainData);
AddRocks(terrain: terrain, count: 20);
This is how it looks after generating:
This is how the rocks look:
The rocks are generated from a script that lies on the mainterrain itself.
I have no parent set to these objects and no parent set to the other object as well.
Actually, you do set parent:
GameObject newGameObject = Instantiate(rockPrefab,
new Vector3(randX, terrain.terrainData.GetHeight((int)randX, (int)randZ),
randZ), Quaternion.identity, rockHolder.transform);
The last parameter ( rockHolder.transform) is a transform to which the instantiated object will be attached and the position you set will become a localPosition of the instantiated object relative to the parent ( rockHolder).
But I don't see the rockHolder object in the hierarchy view screenshot. Seems like you have rockHolder.transform = null, in other words it's not initialized. So, when you call Instantiate (...) and pass the rockHolder.transform as a desired parent for the rocks, it is null, so Unity spawns the objects and assign them to null (no parent).
Can't tell if this is the root of the problem but it's certainly not okay anyway.

How to smooth between multiple perlin noise chunks?

Smoothing Between Chunks
So I've been working on a game in unity and want to expand my world from a 150x150 map into a seemingly infinite procedural world. My plan is to use Perlin Noise as the base and use the different values from 0-1 to determine the terrain type. The issue I'm running into is when I draw out my chunks and offset accordingly my chunks do not line up correctly, which kind of break the illusion of an infinite world.
(seen here)
WorldChunk.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;
[System.Serializable]
public class WorldChunk
{
public int2 Position;
public int[,] Data;
public float[,] Sample;
public WorldChunk(int chunkSize = 16){
Data = new int[chunkSize, chunkSize];
Sample = new float[chunkSize, chunkSize];
}
}
WorldGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;
public class WorldGenerator : MonoBehaviour
{
// Base World Data
public int ChunkSize = 75;
public string Seed = "";
[Range(1f, 40f)]
public float PerlinScale = 10f;
// Pseudo Random Number Generator
private System.Random pseudoRandom;
// Chunk Data Split into Sections (Each Chunk having Coords (x, y))
public Dictionary<string, WorldChunk> chunks = new Dictionary<string, WorldChunk>();
//============================================================
// Set Warm-Up Data
//============================================================
private void Awake() {
// Get/Create Seed
if (Seed == ""){
Seed = GenerateRandomSeed();
}
// Get Random Number Generator
pseudoRandom = new System.Random(Seed.GetHashCode());
// Using to Clear while Making Test Adjustments
chunks.Clear();
// Generate Starting Chunk
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
// Draw Test Chunks
GenerateChunk(x, y);
}
}
}
//============================================================
// Generation Code
//============================================================
// ===
// Create New Chunks
// ===
public void GenerateChunk(int x, int y){
// Set Key to use
string key = $"{x},{y}";
// Check if key exists if not Generate New Chunk
if (!chunks.ContainsKey(key)){
// Add Chunk, Set Position in chunk grid (for calling and block data later), Then Generate data
chunks.Add(key, new WorldChunk(ChunkSize));
chunks[key].Position = new int2(x, y);
GenerateChunkData(chunks[key]);
}
}
// ===
// Fill Chunks with Perlin Data
// ===
private void GenerateChunkData(WorldChunk chunk){
// Set Offsets
float xOffset = (float)chunk.Position.x * ChunkSize;
float yOffset = (float)chunk.Position.y * ChunkSize;
// Set Data to Chunk
for (int x = 0; x < ChunkSize; x++)
{
for (int y = 0; y < ChunkSize; y++)
{
// Get Perlin Map
float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
float py = (float)(y) / ChunkSize * PerlinScale + yOffset;
// Set Temp Sample For Testing (This will change for Map Data (Hills and Water) later)
chunk.Sample[x,y] = Mathf.PerlinNoise(px, py);
}
}
}
// ===
// Generate Random Seed of Length
// ===
private string GenerateRandomSeed(int maxCharAmount = 10, int minCharAmount = 10){
//Set Characters To Pick from
const string glyphs= "abcdefghijklmnopqrstuvwxyz0123456789";
//Set Length from min to max
int charAmount = UnityEngine.Random.Range(minCharAmount, maxCharAmount);
// Set output Variable
string output = "";
// Do Random Addition
for(int i=0; i<charAmount; i++)
{
output += glyphs[UnityEngine.Random.Range(0, glyphs.Length)];
}
// Output New Random String
return output;
}
//============================================================
// Draw Example
//============================================================
private void OnDrawGizmos() {
// Do this because I'm lazy and don't want to draw pixels to generated Sprites
Awake();
// For Each WorldChunk in the chunk Data
foreach (WorldChunk c in chunks.Values)
{
// Check if it exists (Foreach is stupid sometimes... When live editing)
if (c != null){
// Get World Positions for Chunk (Should probably Set to a Variable in the Chunk Data)
Vector3 ChunkPosition = new Vector3(c.Position.x * ChunkSize, c.Position.y * ChunkSize);
// For Each X & For Each Y in the chunk
for (int x = 0; x < ChunkSize; x++)
{
for (int y = 0; y < ChunkSize; y++)
{
// Get Cell position
Vector3 cellPos = new Vector3((ChunkPosition.x - ChunkSize/2f) + x, (ChunkPosition.y - ChunkSize/2f) + y);
// Get Temp Sample and set to color
float samp = c.Sample[x,y];
Gizmos.color = new Color(samp, samp, samp);
// Draw Tile as Sample black or white.
Gizmos.DrawCube(cellPos, Vector3.one);
}
}
// Size for Cubes
Vector3 size = new Vector3(ChunkSize, ChunkSize, 1f);
// Set Color Opaque Green
Gizmos.color = new Color(0f, 1f, 0f, 0.25f);
// Draw Chunk Borders (Disable to show issue)
// Gizmos.DrawWireCube(ChunkPosition, size);
}
}
}
}
I would like to point out when I use:
// Get Perlin Map
float px = (float)(x + xOffset) / ChunkSize * PerlinScale;
float py = (float)(y + yOffset) / ChunkSize * PerlinScale;
instead of
// Get Perlin Map
float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
float py = (float)(y) / ChunkSize * PerlinScale + yOffset;
Everything aligns up correctly but the perlin noise just repeats.
What would be the best way for me to smooth between the chunks so that everything matches up?
Is there a better way to write this maybe?
EDIT:
Thanks for the help Draykoon D! here is the updated info and links to the updated scripts on pastebin if anyone needs them!
Here is the update code for anyone who wants it:
** WorldGenerator.cs**
https://pastebin.com/3BjLy5Hk
** WorldGenerator.cs**
https://pastebin.com/v3JJte3N
Hope that helps!
The key word you are looking for is tileable.
But I have a great news for you, noise function such as perlin are periodic in nature.
So instead of calling ChunckSize * ChunkSize a noise function you should only call it once and then divide the results.
I will advice you to read this excellent tutorial:
https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-1D-noise
Don't use Perlin noise. It has heavy bias towards the 45 and 90 degree directions. Your hills are all aligned to these, and aren't oriented along a more interesting variety of directions. You could use Unity.mathematics.noise.snoise(float2) but its repeat period is rather small, and it might not be very fast if you aren't using Unity Burst jobs. this is what I created/use/recommend, but it's certainly not the only option out there! Note that all these noises are range -1 to 1 rather than 0 to 1, so if that's important than do value=value*0.5+0.5; to rescale it.
Now that that's out of the way, to solve your issue you need to separate the idea of chunks and generation. This is a good idea in general, and I always believe in hiding backend implementation details (e.g chunks) from gameplay as much as possible (e.g. avoid visible boundaries). Each time you generate a chunk, you should find its start coordinate in the world, so that coordinates continue seamlessly with the rest. For example, if the chunks are 128x128, then the chunk starting at (0, 0) should have starting coordinate (0, 0), then the chunk starting at (0, 1) should have starting coordinate (0, 128). Only then, convert a world coordinate into a noise coordinate by multiplying by your desired frequency.

Getting weird results from terrain generation sys using 2D Perlin Noise

I am trying to make a terrain generation system in Unity, similar to Minecraft's, but using Unity's Perlin Noise function (so only 2D noise).
So I have a 16x16x16 chunk with a vector2int that has it's position (so like, if x & z = 0, then the blocks inside are from 0 to 16 in world coordinates).
This is how I'm trying to generate the height map of a chunk:
public void generate(float scale) {
GameObject root = new GameObject("Root");
// this.z & this.x are the chunk coordinates, size is 16
for(int z = this.z * size; z < (this.z + size); ++z) {
for (int x = this.x * size; x < (this.x + size); ++x) {
float[] coord = new float[2] { (float)x / size * scale,
(float)z / size * scale };
Debug.LogFormat("<color='blue'>Perlin coords |</color> x: {0}; y: {1}", coord[0], coord[1]);
float value = Mathf.PerlinNoise(coord[0], coord[1]);
// temporary
GameObject Cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
Cube.transform.position = new Vector3(x, value, z);
Cube.transform.parent = root.transform;
}
}
return;
}
The results are... bad. See for yourself:
What can I do?
It looks good, looks just scrunched on the y transform.
float value = Mathf.PerlinNoise(coord[0], coord[1]);
This is going to give you problems, I'm not sure what coord[0] and coord[1] are but Mathf.PerlinNoise will return a random float between coord[0] and coord[1], so a random float will never be able to produce well aligned tiles.
Better off doing something like
int numTilesHigh = Random.Range(0,15);
for (int i = 0; i < numTilesHigh; i++) {
GameObject Cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
Cube.transform.position = new Vector3(x, <cube height> * i, z);
Cube.transform.parent = root.transform;
}
ps I kind of like your screen shot, not in a minecraft way but it does look cool : - )

How can i create a plane out of many meshes?

What i want to do is to extrude a mesh plane.
The plane is in red in the scene view. Each mesh have two triangles.
First i don't understand what is the Res X and Res Z are for.
What i want to create first is a plane from vertices and triangles in size of 16x16 or any other size by height(Length should be height) and width.
But after i set all the properties to 16 the plane is built from 15x15 meshes not 16x16.
And my main goal is now to extrude the plane. I mean to use OnMouseDown and by a click on the plane to find the closet and neighbours of the vertices from where i clicked on and to extrude this vertice/s. Extrude i mean for example only the z to change the vertices i clicked on position on z only.
Something the same idea like in this image. Marked it in red circle:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class meshPlane : MonoBehaviour
{
public int length;
public int width;
public int resX;
public int resZ;
private MeshFilter meshf;
private Mesh mesh;
private Vector3[] vertices;
private void Start()
{
GenerateOrigin();
}
private void GenerateOrigin()
{
// You can change that line to provide another MeshFilter
meshf = GetComponent<MeshFilter>();
mesh = new Mesh();
meshf.mesh = mesh;
mesh.Clear();
#region Vertices
vertices = new Vector3[resX * resZ];
for (int z = 0; z < resZ; z++)
{
// [ -length / 2, length / 2 ]
float zPos = ((float)z / (resZ - 1) - .5f) * length;
for (int x = 0; x < resX; x++)
{
// [ -width / 2, width / 2 ]
float xPos = ((float)x / (resX - 1) - .5f) * width;
vertices[x + z * resX] = new Vector3(xPos, 0f, zPos);
}
}
#endregion
#region Normales
Vector3[] normales = new Vector3[vertices.Length];
for (int n = 0; n < normales.Length; n++)
normales[n] = Vector3.up;
#endregion
#region UVs
Vector2[] uvs = new Vector2[vertices.Length];
for (int v = 0; v < resZ; v++)
{
for (int u = 0; u < resX; u++)
{
uvs[u + v * resX] = new Vector2((float)u / (resX - 1), (float)v / (resZ - 1));
}
}
#endregion
#region Triangles
int nbFaces = (resX - 1) * (resZ - 1);
int[] triangles = new int[nbFaces * 6];
int t = 0;
for (int face = 0; face < nbFaces; face++)
{
// Retrieve lower left corner from face ind
int i = face % (resX - 1) + (face / (resZ - 1) * resX);
triangles[t++] = i + resX;
triangles[t++] = i + 1;
triangles[t++] = i;
triangles[t++] = i + resX;
triangles[t++] = i + resX + 1;
triangles[t++] = i + 1;
}
#endregion
mesh.vertices = vertices;
mesh.normals = normales;
mesh.uv = uvs;
mesh.triangles = triangles;
mesh.RecalculateBounds();
}
}
When you say "the plane is built from 15x15 meshes" you mean the plane is built from 15x15 squares. That whole plane is the mesh.
ResX and ResZ are how many points there are in each direction. You get one less square because you need two edges for the first square. You need another two for each square you add, but they can share an edge with the previous one so you need only one more.
To make your mesh clickable you need to add a mesh collider to your gameobject and assign the mesh you generate to it. Then, you can use the camera class to get a ray, put that in a raycast and if your raycast hits anything you can use the triangle index and the triangles array you created to get the three points of the triangle that was hit. In addition you can see which weight in the barycentric coordinates is bigger to know which exact vertex your click was closest to. And finally, now that you have the exact vertex you can modify its height.

Tiled Map Editor and draw order in Unity 3D

I have a similar problem to:
stackoverflow
But in my case I'm using Unity3D + TiledMapEditor + Tiled2Unity.
I'm loading my map to Unity3D by Tiled2Unity program and as a player parameter Order in Layer I can change easily by:
Renderer renderer = GetComponent<Renderer>();
renderer.sortingOrder = -(int)(transform.position.y * 100);
Object "map" can only change the parameter Order In Layer for the individual layers.
For example: floor = 0, wall = 1, collision = 2. I have no idea how to get to a single "tile" the map and change its Order In Layer because of where it is located. To map was drawn from top to bottom (The lower the Order in Layer increased).
The script hooked the object "map":
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Tiled2Unity
{
public class TiledMap : MonoBehaviour
{
public int NumTilesWide = 0;
public int NumTilesHigh = 0;
public int TileWidth = 0;
public int TileHeight = 0;
public float ExportScale = 1.0f;
// Note: Because maps can be isometric and staggered we simply can't multply tile width (or height) by number of tiles wide (or high) to get width (or height)
// We rely on the exporter to calculate the width and height of the map
public int MapWidthInPixels = 0;
public int MapHeightInPixels = 0;
public float GetMapWidthInPixelsScaled()
{
return this.MapWidthInPixels * this.transform.lossyScale.x * this.ExportScale;
}
public float GetMapHeightInPixelsScaled()
{
return this.MapHeightInPixels * this.transform.lossyScale.y * this.ExportScale;
}
private void OnDrawGizmosSelected()
{
Vector2 pos_w = this.gameObject.transform.position;
Vector2 topLeft = Vector2.zero + pos_w;
Vector2 topRight = new Vector2(GetMapWidthInPixelsScaled(), 0) + pos_w;
Vector2 bottomRight = new Vector2(GetMapWidthInPixelsScaled(), -GetMapHeightInPixelsScaled()) + pos_w;
Vector2 bottomLeft = new Vector2(0, -GetMapHeightInPixelsScaled()) + pos_w;
Gizmos.color = Color.red;
Gizmos.DrawLine(topLeft, topRight);
Gizmos.DrawLine(topRight, bottomRight);
Gizmos.DrawLine(bottomRight, bottomLeft);
Gizmos.DrawLine(bottomLeft, topLeft);
}
}
}
to better understand (because my level of English is poor):
mesh.png
map.png

Categories