I have procedurally generated Islands with lakes, its basically a 3D mesh that has points above the water line and points below it, any vertex/point below the water level is water, everything above it is solid ground.
From any point on the mesh I want to know the closest distance to this water.
What I ended up doing was creating an Array of Vector2s, the array contains all the points on the mesh that are below the water level.
Next I wish to cycle through these elements and compare them all to find the closest one to my selected point. I am using Vector2.Distance for this because I only want the distance in the XZ components and not going up/down (Y Component).
The problem is that for most points I select this works absolutely fine, giving correct results, but sometimes it doesn't take the closest water point but instead one that is further away, even though this closer water point is confirmed to be in the array of water points that are being compared to find the closest one.
here is my code:
chunk.Vertices = new Vertice[totalVertices];
for (int i = 0, z = 0; z <= chunkSizeZ; z++)
{
for (int x = 0; x <= chunkSizeX; x++, i++)
{
Vertice vert = new Vertice();
vert.index = i;
vert.position = new Vector3(chunkStartPosition.x + x,
chunkStartPosition.y,
chunkStartPosition.z + z);
vert.centerPosition = new Vector3(vert.position.x + 0.5f,
vert.position.y,
vert.position.z + 0.5f);
vert.centerPos2 = new Vector2(vert.position.x + 0.5f,
vert.position.z + 0.5f);
chunk.Vertices[i] = vert;
}
}
Here we get all the water positions:
for (int i = 0; i < totalVertices; i++)
{
if (chunk.Vertices[i].position.y > heightCorrection + tileColliderMinimumY)
{
worldVectorsClean.Add(chunk.Vertices[i].position);
worldIndexClean.Add(chunk.Vertices[i].index);
}
else
{
worldVectorsWater.Add(chunk.Vertices[i].centerPos2);
}
}
Every single tile then calls this function on the generator itself, but only AFTER the whole map and all water points are added. Because the generator keeps track of ALL waterpoints across all chunks otherwise each chunk will only compare its own waterpoints which doesn't work because water from another chunk can be closer but wont be compared to if we don't do it this way;
public float CalculateDistanceToWater(Vector2 pos)
{
var distance = 9001f;
foreach (Vector2 waterVector in worldVectorsWater)
{
var thisDistance = Vector2.Distance(pos, waterVector);
if (thisDistance < distance)
distance = thisDistance;
}
return distance;
}
Finally when we call it from
IEnumerator FindWater()
{
yield return new WaitForSeconds(Random.Range(0.8f, 2.55f));
var pos = new Vector2(transform.position.x, transform.position.z);
distanceToWater = ChunkGenerator.instance.CalculateDistanceToWater(pos);
}
Looking forward to some help on this.
Related
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.
We have 2 lists(black and red), each one contains multiple points in 3d space. We have to move each black point to a red point, and do this in such a way that the total distance to make the moves is the least it can be. The lists can be of different sizes.
Simple correct soltution in 2D space:
Incorrect solution:
If the sizes of the lists differ, then we have to either stack points on top of each other or split one point into multiple points.
Splitting example:
Stacking example:
Our best attempt at this problem follows these general steps:
If there are more red points than black points, pick the black point that's furthest from all of the red point and match it with a red point that is closest to its position and has not been matched yet.
Repeat step 1 until all of the black points are matched.
Iterate over the leftover red points and match each one to their respective closest black point, thus stacking them. The result will look something like this:
Note: if there is more black points than red points, then step one will look for the furthest red point and match it to its closest black point and proceed all the same with the colors swapped.
Some C# code:
private void SaveFrames(List<List<Vector3>> frameList) {
List<Dictionary<Vector3, List<Vector3>>> resultingPairs = new List<Dictionary<Vector3, List<Vector3>>>();
for (int iFrame = 0; iFrame < frameList.Count+1; iFrame++) {
List<Vector3> currentFrame = frameList[iFrame % frameList.Count];
List<Vector3> nextFrame = frameList[(iFrame + 1) % frameList.Count];
int maxIterations = Mathf.Min(currentFrame.Count, nextFrame.Count);
Dictionary<Vector3, List<Vector3>> pairs = new Dictionary<Vector3, List<Vector3>>();
HashSet<Vector3> takenRed = new HashSet<Vector3>();
HashSet<Vector3> takenBlack = new HashSet<Vector3>();
HashSet<Vector3> takenDestination = new HashSet<Vector3>();
bool moreRed = currentFrame.Count < nextFrame.Count;
if (moreRed) {
for (int i = 0; i < maxIterations; i++) {
// Find furthest black point from any red point
float distance = 0;
Vector3 furthestBlack = Vector3.zero;
foreach (Vector3 black in currentFrame) {
if (takenBlack.Contains(black)) continue;
foreach (var red in nextFrame) {
if (Vector3.Distance(black, red) > distance) {
distance = Vector3.Distance(black, red);
furthestBlack = black;
}
}
}
// Find the closest red point to the furthest black point
distance = float.MaxValue;
Vector3 closestRed = Vector3.zero;
foreach (var red in nextFrame) {
if (takenRed.Contains(red)) continue;
if (Vector3.Distance(furthestBlack, red) < distance) {
distance = Vector3.Distance(furthestBlack, red);
closestRed = red;
}
}
if (!pairs.ContainsKey(furthestBlack)) {
pairs[furthestBlack] = new List<Vector3>();
}
if (!takenDestination.Contains(closestRed)) {
pairs[furthestBlack].Add(closestRed);
takenBlack.Add(furthestBlack);
takenRed.Add(closestRed);
takenDestination.Add(closestRed);
}
// Debug.Log("Pair: " + furthestBlack.ToString() + " to " + closestRed.ToString());
}
} else {
for (int i = 0; i < maxIterations; i++) {
// Find furthest red point from any black point
float distance = 0;
Vector3 furthestRed = Vector3.zero;
foreach (Vector3 red in nextFrame) {
if (takenRed.Contains(red)) continue;
foreach (Vector3 black in currentFrame) {
if (Vector3.Distance(black, red) > distance) {
distance = Vector3.Distance(black, red);
furthestRed = red;
}
}
}
// Find the closest black point to the furthest red point
distance = float.MaxValue;
Vector3 closestBlack = Vector3.zero;
foreach (var black in currentFrame) {
if (takenBlack.Contains(black)) continue;
if (Vector3.Distance(furthestRed, black) < distance) {
distance = Vector3.Distance(furthestRed, black);
closestBlack = black;
}
}
if (!pairs.ContainsKey(closestBlack)) {
pairs[closestBlack] = new List<Vector3>();
}
if (!takenDestination.Contains(furthestRed)) {
pairs[closestBlack].Add(furthestRed);
takenBlack.Add(closestBlack);
takenRed.Add(furthestRed);
takenDestination.Add(furthestRed);
}
// Debug.Log("Pair: " + closestBlack.ToString() + " to " + furthestRed.ToString());
}
}
if (currentFrame.Count < nextFrame.Count) {
// For every nextFrame[i], find the closest black point and pair it.
for (int i = currentFrame.Count; i < nextFrame.Count; i++) {
float distance = float.MaxValue;
Vector3 closestBlack = Vector3.zero;
foreach (var black in currentFrame) {
if (Vector3.Distance(nextFrame[i], black) < distance) {
distance = Vector3.Distance(nextFrame[i], black);
closestBlack = black;
}
}
if (!pairs.ContainsKey(closestBlack)) {
pairs[closestBlack] = new List<Vector3>();
}
if (!takenDestination.Contains(nextFrame[i])) {
pairs[closestBlack].Add(nextFrame[i]);
takenDestination.Add(nextFrame[i]);
}
// Debug.Log("Pair: " + closestBlack.ToString() + " to " + nextFrame[i].ToString());
}
}
if (currentFrame.Count > nextFrame.Count) {
// For every currentFrame[i], find the closest red point and pair it.
for (int i = nextFrame.Count; i < currentFrame.Count; i++) {
float distance = float.MaxValue;
Vector3 closestRed = Vector3.zero;
foreach (var red in nextFrame) {
if (Vector3.Distance(currentFrame[i], red) < distance) {
distance = Vector3.Distance(currentFrame[i], red);
closestRed = red;
}
}
if (!pairs.ContainsKey(currentFrame[i])) {
pairs[currentFrame[i]] = new List<Vector3>();
}
if (!takenDestination.Contains(closestRed)) {
pairs[currentFrame[i]].Add(closestRed);
takenDestination.Add(closestRed);
}
// Debug.Log("Pair: " + currentFrame[i].ToString() + " to " + closestRed.ToString());
}
}
resultingPairs.Add(pairs);
}
}
This method works for simple shapes like cubes.
However, it starts acting up when the cube positions overlap in 3d space from ne set of points to another.
And it does even funkier stuff with more complex points:
I am not exactly sure why this breaks down and I could not come up with a simple 2D example of where this approach goes wrong.
We have tried 3 different methods over 3 very long days, and can not seem to find a solution to this seemingly simple problem.
You can interpret this as the Assignment problem, where the black points are the "agents", the red points are the "tasks" (or vice versa) and the distance between them is the cost.
The problem instance has a number of agents and a number of tasks. Any agent can be assigned to perform any task, incurring some cost that may vary depending on the agent-task assignment. It is required to perform all tasks by assigning exactly one agent to each task and exactly one task to each agent in such a way that the total cost of the assignment is minimized.
The Assignment problem can be solved in polynomial time using The Hungarian algorithm. Variations on the problem involve more tasks than agents, which you can apply to your special cases where the sizes of the lists differ.
If you want a "quick 'n dirty" solution that should give decent results, consider adapting your current algorithm to be a probabilistic one. Weight each nearby red point according to how far away it is from the black point, and pick one at random by their weights. You may want to square (or even cube) the distances, to discourage picking farther away points. Overall it should pick many of the same points as your original algorithm, but with a few differences here and there. Repeat as many times as feasible, and pick the best result.
If you want something a bit less hand-wavey, consider modeling the problem as an asymmetric traveling salesman problem. Connect each black point to each red point, with a directional edge of weight proportional to its euclidean distance between them. Then connect each red point to each black point, with a directional edge of weight 0. Then solve with an existing asymmetric TSP solver, and then add extra nodes + connect as normal if necessary. Note however, this will throw away of lot of useful information (for instance, that we don't particularly care which black node we connect with next), in exchange for being able to use existing software with tried and proven heuristics and optimizations.
I've created a 3 dimensional grid. I have two separate objects filling the spaces of this grid. I want to have one of the objects in one row but on randomly selected columns.
Has anyone done this before or can anyone point me in the right direction?
I'm using Unity and C#. Thank you.
Vector3 towerSize = new Vector3(3, 3, 3);
//create grid tower
for (int x = 0; x < towerSize.x; x++)
{
for (int z = 0; z < towerSize.z; z++)
{
for (int y = 0; y < towerSize.y; y++)
{
//spawn tiles and space them
GameObject obj = (GameObject)Instantiate(tiles);
obj.transform.position = new Vector3(x * 1.2f, y * 1.2f, z * 1.2f);
//add them all to a List
allTiles.Add(obj);
obj.name = "tile " + allTiles.Count;
}
}
}
There is the code for the grid. I tried to have two objects in a singular List move to those tiles but the random column objects get in the same columns when I do that with this code:
for (int i = 0; i < allCubes.Count; i++)
{
allCubes[i].transform.position = Vector3.MoveTowards(
allCubes[i].transform.position,
allTiles[i].transform.position, 10 * Time.deltaTime);
}
Then thought put the two types of cubes in separate Lists themselves. Which ended up being even more messy. haha Does posting that code help?
I know this is an extremely old question of mine. It was a project that got canceled. I randomly came across it and out of curiosity I decided to try to complete this particular issue I was having such trouble doing back then. And I did.
public Vector3 towerSize = new Vector3(3, 3, 3);
public GameObject tiles;
public GameObject randomTile;
//public variables for debugging purposes.
//no real need to be seen in inspector in final. cleaner too if they're hidden
public int randomSelectedTile;
public List<GameObject> allTiles;
void Start()
{
//create grid tower
for (int x = 0; x < towerSize.x; x++)
{
for (int z = 0; z < towerSize.z; z++)
{
for (int y = 0; y < towerSize.y; y++)
{
//spawn cubes and space them
GameObject obj = (GameObject)Instantiate(tiles);
obj.transform.position = new Vector3(x * 1.2f, y * 1.2f, z * 1.2f);
//add them all to a List
allTiles.Add(obj);
obj.name = "tile " + allTiles.Count;
}
}
}
//select a random cube in the list
randomSelectedTile = Random.Range(0, allTiles.Count);
//get the cube object to delete
GameObject deleteObj = allTiles.ElementAt(randomSelectedTile);
//spawn the random cube at the position of the cube we will delete
GameObject rndObj = (GameObject)Instantiate(randomTile);
rndObj.transform.position = deleteObj.transform.position;
//remove the element at that location
allTiles.RemoveAt(randomSelectedTile);
//insert the random cube at that element's location
allTiles.Insert(randomSelectedTile, rndObj);
//destroy the unwanted cube
Destroy(deleteObj);
}
It's nice to see how you've improved over time. Just in case anyone else would benefit from the solution. Again, I apologize for bringing it back up.
I'd like to ask whether there is code out there or if you can give me some help in writing some (C#, but I guess the maths is the same everywhere).
I'd like to specify a center point from which an equilateral triangle mesh is created and get the vertex points of these triangles. The center point should not be a face center, but a vertex itself.
A further input would be the size of the triangles (i.e side length) and a radius to which triangle vertices are generated.
The reason behind it is that I want to create a mesh which is centered nicely on the screen/window center with as little code as possible. I just find mesh generation code, but not a "radial outward propagation" example.
In the end, I'd like to have the subsequently farther away vertices being displaced in a logarithmic fashion, but I guess that's just an easy addition once the mesh code is there.
Can anybody help me with that? Thanks!
You need to specify two things, a radius and the direction that the first triangle points.
The radius will be the distance from the initial point to the vertices of the first triangle. All triangles will have the same radius.
The direction is some specification in radians. I will assume that 0 means pointing to the right (PI would be point to the left).
Finding the vertices of the first triangle can be done like this (pseudo-code, not language specific):
float theta = 0; // The direction, 0 means pointing to the right
float thetaInc = TWO_PI/3; // 3 because you want a triangle
for (int i = 0; i < 3; i++) {
vertX[i] = initialPointX+cos(theta)*radius;
vertY[i] = initialPointY+sin(theta)*radius;
theta += thetaInc;
}
There are many ways to find the center points of the neighboring triangles. One way would be to use the same code but initialize theta = TWO_PI/6, replace radius with foo (see math below), assign new center points of neighboring triangles in the for loop, and then use the same code with an appropriately rotated direction (theta += PI) to find the vertices of those triangles.
Distance from one triangle center to another only knowing radius:
hypotenuse = sqrt(sq(radius)+sq(radius));
halfHypotenuse = hypotenuse/2.0;
Pythagorean theorem to find distance from center of triangle to center of an edge: foo = sqrt(sq(radius)-sq(halfHypotenuse));
Final distance = foo*2.0;
Code to find the center points of the neighboring triangles:
float[] nx = new float[3];
float[] ny = new float[3];
float theta = TWO_PI/6;
float hyp = sqrt(sq(radius)+sq(radius));
float halfHyp = hyp/2.0;
float foo = sqrt((sq(radius)-sq(halfHyp)))*2.0;
for (int i = 0; i < 3; i++) {
nx[i] = initialPointX+cos(theta)*foo;
ny[i] = initialPointY+sin(theta)*foo;
theta += thetaInc;
}
Thank you very much for your answer. I will play around with your code - the propagation part will come handy for sure.
In the meantime I have played around with hexagons instead of triangles and this codes works fairly alright for the same purpose.:
//populate array from the centre hex, going outwards the desired number of hex rings
for (int i = 0; i < numberOfHexagonRings; i++)
{
for (double j = -i; j <= i; j++)
for (double k = -i; k <= i; k++)
for (double l = -i; l <= i; l++)
if ((Math.Abs(j) + Math.Abs(k) + Math.Abs(l) == i * 2) && (j + k + l == 0))
{
positionX = (int)(screenCenterX + ((double)sideLength * (l / 2 + j)));
positionY = (int)(screenCenterY + (3/2 * ((double)sideLength / Math.Sqrt(3)) * l));
I am making (another) MineCraft clone, and I've run into an interesting problem. I have a public enum that lists all the cube types a particular cube can be, and I have a 3d array that holds cubes. Each cube has a specific type, and I iterate through this array to get the vertices for each cube, then pass those vertices to a vertex buffer designated for a particular cube type. When I create a random array of cubes, or a single cube, and tell it what texture it should be everything draws as expected. I'm now trying to figure out how to draw a random "surface" of grass cubes, and fill everything below those on the y-axis with dirt cubes. The strangest thing is happening though, the top most cube is dirt and it fills all the bottom ones with grass cubes! When I disable the loop to fill the underground with dirt, the top most cube is displaying grass as intended.
Here is what I believe to be the relevant parts of the code. Here is where the cube type is set:
// Create a random surface level
Perlin perlin = new Perlin();
for (int x = 0; x < Game.ChunkWidth_X; x++)
{
for (int z = 0; z < Game.ChunkDepth_Z; z++)
{
double XVal = Convert.ToDouble(x) * 1.1;
double ZVal = Convert.ToDouble(z) * 1.1;
double YVal = Game.ChunkHeight_Y / 2 * 1.1;
double PerlinValue = perlin.GetValue(XVal, YVal, ZVal);
int YVal_new = Convert.ToInt32(YVal + (PerlinValue * 10));
if (YVal_new > Game.ChunkHeight_Y - 1) { YVal_new = Game.ChunkHeight_Y - 1; }
if (YVal_new < 0) { YVal_new = 0; }
// Set the grass cube
Cube NewCube = new Cube(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(x, YVal_new, z));
NewCube.cubeType = CubeType.Grass;
CubeGrid[x, YVal_new, z] = NewCube;
// Fill below it with dirt
for (int y = YVal_new - 1; y >= 0; y--)
{
Cube NewCube2 = new Cube(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(x, y, z));
NewCube2.cubeType = CubeType.Dirt;
CubeGrid[x, y, z] = NewCube2;
}
// Fill above it with air
for (int y = YVal_new + 1; y < Game.ChunkHeight_Y; y++)
{
Cube NewCube2 = new Cube(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(x, y, z));
NewCube2.cubeType = CubeType.Air;
CubeGrid[x, y, z] = NewCube2;
}
}
}
This is where I pull the vertices to put into the appropriate buffer:
Dictionary<CubeType, List<VertexPositionNormalTexture>> DrawableVertices = new Dictionary<CubeType, List<VertexPositionNormalTexture>>();
// Get the proper vertices for each cube type and put in the appropriate dictionary
for (int x = 0; x < Game.ChunkWidth_X; x++)
{
for (int z = 0; z < Game.ChunkDepth_Z; z++)
{
for (int y = 0; y < Game.ChunkHeight_Y; y++)
{
CubeGrid[x,y,z].CreateVertices();
string test = CubeGrid[x, y, z].cubeType.ToString();
foreach (VertexPositionNormalTexture TargetVertex in CubeGrid[x, y, z].DisplayableVertices)
{
if (!DrawableVertices.ContainsKey(CubeGrid[x, y, z].cubeType))
{
List<VertexPositionNormalTexture> NewList = new List<VertexPositionNormalTexture>();
NewList.Add(TargetVertex);
DrawableVertices.Add(CubeGrid[x, y, z].cubeType, NewList);
}
else
{
DrawableVertices[CubeGrid[x, y, z].cubeType].Add(TargetVertex);
}
}
}
}
}
Here is the second part of it:
foreach (KeyValuePair<CubeType, List<VertexPositionNormalTexture>> KVP in DrawableVertices)
{
VertexBuffer cubeBuffer = new VertexBuffer(device, typeof(VertexPositionNormalTexture), KVP.Value.Count, BufferUsage.WriteOnly);
cubeBuffer.SetData(KVP.Value.ToArray());
// Update our collection of vertex buffers
CubeType_VertexBuffers[KVP.Key] = cubeBuffer;
// Get the triangle count for the buffer
CubeType_TriangleCount[KVP.Key] = KVP.Value.Count / 3;
}
Lastly, here is my draw:
// Go through each vertex buffer we have created, and draw it.
foreach (KeyValuePair<CubeType, VertexBuffer> KVP in CubeType_VertexBuffers)
{
foreach (EffectPass pass in testEffect.CurrentTechnique.Passes)
{
if (CubeType_TriangleCount[KVP.Key] > 0) // if this buffer has triangles, draw it.
{
pass.Apply();
testEffect.View = camera.ViewMatrix;
testEffect.TextureEnabled = true;
testEffect.Projection = camera.ProjectionMatrix;
testEffect.World = worldMatrix;
testEffect.Texture = CubeType_Texture[KVP.Key];
device.SetVertexBuffer(KVP.Value);
device.DrawPrimitives(PrimitiveType.TriangleList, 0, CubeType_TriangleCount[KVP.Key]);
}
}
}
base.Draw(gameTime);
The weirdest thing is that when I manually set cube types everything draws with the proper texture as expected. What other things should I try to troubleshoot? I tried making a specific effect for each cube type to no avail.
After trying a bunch of random things in desperation, I found a fix for this. It turns out that if you use the same BasicEffect for different textures, it only uses the last texture assigned to it. I was iterating through a list of VertexBuffers and assigning a different texture for each one. By the time everything made it over to the video card, only the last texture used was rendered, or so it appears.
The solution was to create a separate BasicEffect for each texture I needed and assign only the VertexBuffers needed to the particular BasicEffect.