I'm creating a minecraft like voxel engine thing in xna, and have started about implementing "infinite" world but have ran into a few problems. One such problem is that the following line always seems to apply the oppisite (eg if the player moved X+ REGION_SIZE_X it would assign Direction.X_DECREASING instead of the expected Direction.X_INCREASING).
Thread regionLoader;
public void CheckPlayer()
{
Direction direction = Direction.MAX;
float distancetraveledx = player.Origin.X - player.Position.X;
float distancetraveledy = player.Origin.Y - player.Position.Y;
float distancetraveledz = player.Origin.Z - player.Position.Z;
if (distancetraveledx > Variables.REGION_SIZE_X)
{
direction = Direction.XIncreasing;
player.Position = new Vector3(player.Origin.X, player.Position.Y, player.Position.Z);
}
else if (distancetraveledx < -Variables.REGION_SIZE_X)
{
direction = Direction.XDecreasing;
player.Position = new Vector3(player.Origin.X, player.Position.Y, player.Position.Z);
}
else if (distancetraveledz > Variables.REGION_SIZE_Z)
{
direction = Direction.ZIncreasing;
player.Position = new Vector3(player.Position.X, player.Position.Y, player.Origin.Z);
}
else if (distancetraveledz < -Variables.REGION_SIZE_Z)
{
direction = Direction.ZDecreasing;
player.Position = new Vector3(player.Position.X, player.Position.Y, player.Origin.Z);
}
if (direction != Direction.MAX)
{
regionManager.direction = direction;
regionLoader = new Thread(new ThreadStart(regionManager.ShiftRegions));
regionLoader.Start();
}
}
So what this is doing is checking if the player has moved REGION_SIZE from it's origin and if it has, reset the component of the position which has moved over that boundary.
This then calls the following function:
public void ShiftRegions()
{
// Future and current arrays are to avoid moving elements twice (or maybe I am thinking about it in the wrong way...)
future_regions = new Region[(int)Variables.RENDER_DISTANCE, (int)Variables.RENDER_DISTANCE, (int)Variables.RENDER_DISTANCE];
for (short j = 0; j < future_regions.GetLength(0); j++)
{
for (short m = 0; m < future_regions.GetLength(1); m++)
{
for (short i = 0; i < future_regions.GetLength(2); i++)
{
future_regions[j, m, i] = new Region(world, new Vector3i(new Vector3(j, m, i)));
switch (direction)
{
// This appears to work as XDecreasing
case Direction.XIncreasing:
// If it is not at the back
if (j != future_regions.GetLength(0) - 1)
{
// Make the regions look like they are scrolling backward
future_regions[j, m, i] = regions[j + 1, m, i];
future_regions[j, m, i].Index = new Vector3i((uint)j, (uint)m, (uint)i);
// In the next call the vertex buffer will be regenerated thus placing the "blocks" in the correct position
future_regions[j, m, i].Dirty = true;
}
// If it is the front most region to which the player is traveling ing
if (j == 0)
{
// New the region setting the Generated flags to false thus allowing it to be regenerated
future_regions[j, m, i] = new Region(world, new Vector3i(new Vector3(j, m, i)));
}
// If it is at the back of the regions
if (j == future_regions.GetLength(0) - 1)
{
//store region
}
break;
case Direction.XDecreasing:
break;
}
}
}
}
generator.build(ref future_regions);
direction = Direction.MAX;
this.regions = future_regions;
}
This actually moves the regions which causes the scrolling effect and the new regions which appear at the front. Which doesn't seem to work.
I was thinking istead of actually "moving" the regions i could just assign different offsets and move in the world matrix but when I do so i just get a blue screen...
Here is the rest of the code:
Generator class function:
public virtual void build(ref Region[,,] regions)
{
for (short j = 0; j < regions.GetLength(0); j++)
{
for (short m = 0; m < regions.GetLength(1); m++)
{
for (short i = 0; i < regions.GetLength(2); i++)
{
OutputBuffer.Add("Generating...");
if (regions[j, m, i].Generated == false)
{
regions[j, m, i].Dirty = true;
regions[j, m, i].Generated = true;
for (short x = 0; x < Variables.REGION_SIZE_X; x++)
{
for (short y = 0; y < Variables.REGION_SIZE_Y; y++)
{
for (short z = 0; z < Variables.REGION_SIZE_Z; z++)
{
regions[j, m, i].Blocks[x, y, z] = new Block();
Vector3i relativeBlock = new Vector3i(new Vector3(x + Variables.REGION_SIZE_X * j, y + Variables.REGION_SIZE_Y * m, z + Variables.REGION_SIZE_Z * i));
if (x == 0 || x == 16 || x == 32 || z == 0 || z == 16 || z == 32 || y == 0 || y == 16 || y == 32)
{
regions[j, m, i].Blocks[x, y, z].BlockType = BlockType.dirt;
}
else
{
regions[j, m, i].Blocks[x, y, z].BlockType = BlockType.grass;
}
}
}
}
}
}
}
}
}
and the code that checks if the region's buffers need to be rebuilt:
public void Update(World world)
{
this.world = world;
for (short j = 0; j < regions.GetLength(0); j++)
{
for (short m = 0; m < regions.GetLength(1); m++)
{
for (short i = 0; i < regions.GetLength(2); i++)
{
if (regions[j, m, i].Dirty &&
regions[j, m, i].Generated)
{
regions[j, m, i].BuildVertexBuffers();
}
}
}
}
}
By the way, if Dirty is set to true this means that the buffers need to be regenerated.
Any ideas why this is not creating new regions at the front and why it is not scrolling properly?
EDIT: I was just thinking logically and that my idea with the changing the regions position in the array will not change it's world position, and like I said above with transforming them to the correct positions instead of copying them - that seems like the most logical step. Well the thing is I may have to copy some to different places in the regions array because the array may become spaghetti in no time if you just transform them...
Thanks, Darestium
Just looking at this part
float distancetraveledx = player.Origin.X - player.Position.X;
You're subtracting position from origin, you should probably reverse the order and do this
float distancetraveledx = player.Position.X - player.Origin.X;
For example, if the player has moved from (0, 0, 0) to (10, 0, 0), the player has moved 10 units in the X direction, but your 'distancetraveledx' would give you 0 - 10, or -10.
Unrelated to your question:
Your variables will be easier to use if you follow CamelCase, with the first letter lower or upper-case:
float distanceTraveledX = player.Position.X - player.Origin.X;
In C# the convention is to have class names with the first letter capitalized, and variables with the first letter lower-case, like such:
MyClass myClass;
Related
I have a problem with sorting points by the angle they create with the X axis. The points look like this
Here is the code I have:
public static List<Point> SortPoints(List<Point> points)
{
List<Point> result = new List<Point>();
List<KeyValuePair<Point, double>> valuePairs = new List<KeyValuePair<Point, double>>();
foreach (var point in points)
{
valuePairs.Add(new KeyValuePair<Point, double>(point, Math.Atan2(point.Y, point.X)));
}
valuePairs = valuePairs.OrderByDescending(x => x.Value).ToList();
foreach (var valuepair in valuePairs)
{
result.Add(valuepair.Key);
}
return result;
}
It should sort points in the way, they close up. It works for most points, but it doesn't for some of them. It crashes mostly on these fragments:
Is my thinking correct for that kind of problem or do I miss something? I am still new to geometry in programming.
C# TRANSLATION
public static List<Point> SortPoints(List<Point> points)
{
double[] pnt = new double[points.Count * 2];
for (int z = 0, ii = 0; z < points.Count; z++, ii += 2)
{
pnt[ii] = points[z].X;
pnt[ii + 1] = points[z].Y;
}
int n2 = pnt.Length;
int n = n2 >> 1;
int[] idx = new int[n];
int i, j, k, i0, i1, e, m;
double a, x0, y0, x1, y1, x, y;
double[] ang = new double[n];
int[] flag = new int[n];
const double deg = Math.PI / 180.0;
const double thr = 0.02 * deg;
for (i = 0; i < n; i++) idx[i] = i + i;
x0 = x1 = pnt[0];
y0 = y1 = pnt[1];
for (i = 0; i < n2;)
{
x = pnt[i]; i++;
y = pnt[i]; i++;
if (x0 > x) x0 = x;
if (x1 < x) x1 = x;
if (y0 > y) y0 = y;
if (y1 < y) y1 = y;
}
x = 0.5 * (x0 + x1);
y = 0.5 * (y0 + y1);
for (i = 0, j = 0; i < n; i++, j += 2)
{
a = Math.Atan2(pnt[j + 1] - y, pnt[j + 0] - x);
ang[i] = a;
}
for (e = 1, j = n; e != 0; j--) // loop until jo swap occurs
for (e = 0, i = 1; i < j; i++) // proces unsorted part of array
if (ang[idx[i - 1] >> 1] > ang[idx[i] >> 1]) // condition if swap needed
{ e = idx[i - 1]; idx[i - 1] = idx[i]; idx[i] = e; e = 1; }
for (i = 0; i < n; i++) flag[i] = 0;
for (e = 0, j = 1, i = 1; i < n; i++)
if (Math.Abs(ang[idx[i] >> 1] - ang[idx[i - 1] >> 1]) < thr)
{ flag[idx[i] >> 1] = j; flag[idx[i - 1] >> 1] = j; e = 1; }
else if (e != 0) { e = 0; j++; }
if (e != 0) j++; m = j;
x = x0 + (0.3 * (x1 - x0));
y = 0.5 * (y0 + y1);
for (i = 0, j = 0; i < n; i++, j += 2)
if (flag[i] != 0) // only for problematic zones no need to recompute finished parts
{
a = Math.Atan2(pnt[j + 1] - y, pnt[j + 0] - x); // this might need handling edge cases of atan2
ang[i] = a;
}
for (k = 0; k < n;)
{
for (; k < n; k++) if (flag[idx[k] >> 1] != 0) // zone start
{
i0 = i1 = k;
for (; k < n; k++) if (flag[idx[k] >> 1] != 0) i1 = k;// // zone end
else break;
// index (bubble) sort idx[] asc by ang[]
if (i0 != i1)
for (e = 1, j = i1 - i0 + 1; e > 0; j--) // loop until jo swap occurs
for (e = 0, i = i0 + 1; i < i0 + j; i++) // proces unsorted part of array
if (ang[idx[i - 1] >> 1] > ang[idx[i] >> 1]) // condition if swap needed
{ e = idx[i - 1]; idx[i - 1] = idx[i]; idx[i] = e; e = 1; } // swap and allow to process array again
// different center for atan2 might reverse the ang order
// so test if start or end of zone is closer to the point before it
j = i0 - 1; if (j < 0) j = n - 1; // 45 deg is never at start or end of data so this should never happen
x = pnt[idx[j] + 0] - pnt[idx[i0] + 0];
y = pnt[idx[j] + 1] - pnt[idx[i0] + 1];
a = (x * x) + (y * y);
x = pnt[idx[j] + 0] - pnt[idx[i1] + 0];
y = pnt[idx[j] + 1] - pnt[idx[i1] + 1];
x = (x * x) + (y * y);
// reverse if not in correct order
if (x < a) for (; i0 < i1; i0++, i1--)
{ j = idx[i0]; idx[i0] = idx[i1]; idx[i1] = j; }
}
}
List<Point> result = new List<Point>();
for (int h = 0; h < pnt.Length - 1; h += 2)
{
result.Add(new Point(pnt[h], pnt[h + 1]));
}
return result;
}
Ok I got it working sorting by atan2 angle by using 2 centers only (no need for 3 as I can detect the problem zones directly from the first center alone). This is the algorithm (shape must not self intersect and angle around selected centers must be monotonic !!!):
compute BBOX (x0,y0,x1,y1) for your data
this is needed to properly compute correct center locations for atan2 usage
compute angle by atan2 for each point using BBOX center as center
the center should be inside your shape so center of BBOX is the obvious choice. However as Yves Daoust pointed out this will not work for arbitrary concave shapes only for those shapes and centers where the angle is monotonic.
sort your points by this angle
detect problematic zones
simply in problematic zones the consequent points after the sort has almost the same angle so just threshold that.
compute atan2 angle for each problem zone with different center
again center must be inside ... and should be shifted in any of the multiple of 90 degrees angle from original center. I chose shift toward x0 by 20% of shape x size. The bigger the shift the more ang difference the problem zones will get.
sort the problem zones by new angle
reverse problem zone order after sort if needed
the shifted center might cause the angle direction reversal in comparison to original angles. So after sort if you compute distance between point before zone and zone first and last point if the last point of zone is closer it means you need to reverse the zone points order.
Here preview of output:
Here C++/OpenGL/VCL code for this:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
#include "data.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
const int n2=sizeof(pnt)/sizeof(pnt[0]); // size of pnt[]
const int n=n2>>1; // point in pnt[]
int idx[n]; // index sort
int ix=1055; // just debug X cursor on mouse wheel
//---------------------------------------------------------------------------
void compute()
{
int i,j,k,i0,i1,e,m;
float a,x0,y0,x1,y1,x,y;
float ang[n]; // atan2 angles per point
int flag[n]; // 0 or problem zone ix per point
const float deg=M_PI/180.0;
const float thr=0.02*deg;
// shuffle input data for debug as its already ordered
for (i=0;i<n2;)
{
j=Random(n2)&0xFFFFFFFE;
a=pnt[i]; pnt[i]=pnt[j]; pnt[j]=a; i++; j++;
a=pnt[i]; pnt[i]=pnt[j]; pnt[j]=a; i++; j++;
}
// init index sort table
for (i=0;i<n;i++) idx[i]=i+i;
// compute BBOX of data
x0=x1=pnt[0];
y0=y1=pnt[1];
for (i=0;i<n2;)
{
x=pnt[i]; i++;
y=pnt[i]; i++;
if (x0>x) x0=x;
if (x1<x) x1=x;
if (y0>y) y0=y;
if (y1<y) y1=y;
}
// compute atan2 for center set to center of BBOX
x=0.5*(x0+x1);
y=0.5*(y0+y1);
for (i=0,j=0;i<n;i++,j+=2)
{
a=atan2(pnt[j+1]-y,pnt[j+0]-x); // this might need handling edge cases of atan2
ang[i]=a;
}
// index (bubble) sort idx[] asc by ang[]
for (e=1,j=n;e;j--) // loop until no swap occurs
for (e=0,i=1;i<j;i++) // process unsorted part of array
if (ang[idx[i-1]>>1]>ang[idx[i]>>1]) // condition if swap needed
{ e=idx[i-1]; idx[i-1]=idx[i]; idx[i]=e; e=1; } // swap and allow to process array again
// detect/label problematic zones m = number of zones +1
for (i=0;i<n;i++) flag[i]=0;
for (e=0,j=1,i=1;i<n;i++)
if (fabs(ang[idx[i]>>1]-ang[idx[i-1]>>1])<thr)
{ flag[idx[i]>>1]=j; flag[idx[i-1]>>1]=j; e=1; }
else if (e){ e=0; j++; }
if (e) j++; m=j;
// compute atan2 for center shifted toward x0
// so it still inside but not too close to (0,0)
// so there is some ang diference on problematic zones
x=x0+(0.3*(x1-x0));
y=0.5*(y0+y1);
for (i=0,j=0;i<n;i++,j+=2)
if (flag[i]) // only for problematic zones no need to recompute finished parts
{
a=atan2(pnt[j+1]-y,pnt[j+0]-x); // this might need handling edge cases of atan2
ang[i]=a;
}
// loop through problematic zones
for (k=0;k<n;)
{
for (;k<n;k++) if (flag[idx[k]>>1]) // zone start
{
i0=i1=k;
for (;k<n;k++) if (flag[idx[k]>>1]) i1=k; // zone end
else break;
// index (bubble) sort idx[] asc by ang[]
if (i0!=i1)
for (e=1,j=i1-i0+1;e;j--) // loop until no swap occurs
for (e=0,i=i0+1;i<i0+j;i++) // process unsorted part of array
if (ang[idx[i-1]>>1]>ang[idx[i]>>1]) // condition if swap needed
{ e=idx[i-1]; idx[i-1]=idx[i]; idx[i]=e; e=1; } // swap and allow to process array again
// different center for atan2 might reverse the ang order
// so test if start or end of zone is closer to the point before it
j=i0-1; if (j<0) j=n-1; // 45 deg is never at start or end of data so this should never happen
x=pnt[idx[j]+0]-pnt[idx[i0]+0];
y=pnt[idx[j]+1]-pnt[idx[i0]+1];
a=(x*x)+(y*y);
x=pnt[idx[j]+0]-pnt[idx[i1]+0];
y=pnt[idx[j]+1]-pnt[idx[i1]+1];
x=(x*x)+(y*y);
// reverse if not in correct order
if (x<a) for (;i0<i1;i0++,i1--)
{ j=idx[i0]; idx[i0]=idx[i1]; idx[i1]=j; }
}
}
}
//---------------------------------------------------------------------------
void gl_draw()
{
int i,j;
float a,da=1.0/float(n-1),x,y,r;
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
// set view to 2D
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glScalef(1.0/120.0,1.0/120.0,1.0);
// render points from list
glBegin(GL_POINTS);
// glBegin(GL_LINE_STRIP);
// glBegin(GL_TRIANGLE_FAN);
glColor3f(0.0,0.0,0.0);
glVertex2f(0.0,0.0);
for (a=0.0,i=0;i<n;i++,a+=da)
{
glColor3f(a,a,a);
glVertex2fv(pnt+idx[i]);
}
glEnd();
// render debug index (on mouse wheel)
x=pnt[idx[ix]+0];
y=pnt[idx[ix]+1];
r=5.0;
glBegin(GL_LINES);
glColor3f(0.0,1.0,0.0);
glVertex2f(x-r,y-r);
glVertex2f(x+r,y+r);
glVertex2f(x-r,y+r);
glVertex2f(x+r,y-r);
glEnd();
glFinish();
SwapBuffers(hdc);
Form1->Caption=ix;
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
// Init of program
gl_init(Handle); // init OpenGL
compute();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
// Exit of program
gl_exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
// repaint
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
// resize
gl_resize(ClientWidth,ClientHeight);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
{
Handled=true;
int dix=1; if (Shift.Contains(ssShift)) dix=10;
if (WheelDelta>0){ ix+=dix; if (ix>=n) ix= 0; }
if (WheelDelta<0){ ix-=dix; if (ix< 0) ix=n-1; }
gl_draw();
}
//---------------------------------------------------------------------------
Just ignore the VCL and OpenGL stuff. The only important stuff here is the function compute() which works as described above. The global variables just above it. I used index sort however so the points order is not changed at all instead idx[i] holds the index of i-th point in the input data. I wanted to keep this as simple as I could so no dynamic allocations nor containers or funny stuff... I used your data as input in form:
float pnt[]=
{
-98.622,0.4532042,
-98.622,1.64291,
-98.612097,3.0877569,
...
-98.618994,-3.2649391,
-98.6260115,-1.9205891
};
The gl_simple.h I used for OpenGL can be found here:
complete GL+GLSL+VAO/VBO C++ example
I tested this by using the cursor (green cross on image) and mouse wheel through the whole shape looking for jumping back and forward ... In previous versions I got the flag[] array as global and rendered different colors for the problem zones so I can debug directly them and not looking for whole shape again and again ... Also I used bubble sort ... in case you got huge data use quick sort instead... however this data you provided is computed instantly on my old computer so I didn't bother to add recursion.
For arbitrary concave non self-intersecting shapes you need to use different approach like for example connected component analysis:
for each point compute its 2 nearest neighbor points
this is very slow and should be speed-ed up by using spatial sorting of points to lower the complexity.
in case of very non uniform sampling the two closest points should not lie on or near the same direction!!! So dot between their unit direction should be as far from +1.0 as it can.
select any start point and add it to output
select one of its yet unused neighbors and add it to output
set link between selected and its predecessing point as used.
loop #4 until you get to the starting point again
I'm trying to make a list using this class:
public class Edges
{
public int no_piece;
public int edge;
public int[,] edge_pixels;
}
This is to assemble a puzzle. I am trying to save the characteristics of each piece this way: no_piece - the number of the piece /// edge - which edge (top as '0', left as '1', bottom as '2' and right as '3') /// edge_pixels - the number of pixels for each channel, for example:
edge_pixels[0,x] would have the values of each pixel of an edge, of n piece);
This is the way I tried;
List<Edges> edges_list = new List<Edges>();
for (i = 0; i < number of pieces; i++)
{
for (y = vector_coordinates[i,1]; y < vector_coordinates[i,3]; y++)
{//vectors with top and bottom coordinates of the piece in the original image
for (x = vector_coordinates[i,0]; x < vector_coordinates[i,2]; x++)
{// the same goes for x but left and right
if (y == vector_coordinates[i, 1]) //top
{
for (aux_rgb = 0; aux_rgb < 3; aux_rgb++)
{
Edges edge = new Edges();
edge.no_piece = i;
edge.edge = 1;
edge.edge_pixels[aux_rgb, aux_count_pixel] = (int)dataPtr[aux_rgb];
edges_list.Add(edge);
}
}
aux_count_pixel++;
}
(...)
But it doesn't work and I donĀ“t understand why. I'm not sure if I made myself clear. Thank you
Try following.
List<Edges> edges_list = new List<Edges>();
for (i = 0; i < number of pieces; i++)
{
for (y = vector_coordinates[i,1]; y < vector_coordinates[i,3]; y++)
{//vectors with top and bottom coordinates of the piece in the original image
for (x = vector_coordinates[i,0]; x < vector_coordinates[i,2]; x++)
{// the same goes for x but left and right
if (y == vector_coordinates[i, 1]) //top
{
Edges edge = new Edges();
edges_list.Add(edge);
edge.no_piece = i;
for (aux_rgb = 0; aux_rgb < 3; aux_rgb++)
{
edge.edge = 1;
edge.edge_pixels[aux_rgb, aux_count_pixel] = (int)dataPtr[aux_rgb];
}
}
aux_count_pixel++;
}
I'm attempting to create a turn based strategy game using a 3D HexGrid map, I've implemented dijkstra's algorithm but it doesn't run 100% efficiently and I can't work out why. I also attempted to implement A* but have had to stop as I can't work out how to properly implement it, so any help with that would also be massively appreciated.
My unit passes it's GameObject and the Vector3 of it's target to the generate path function and each Node in the graph list is populated with its x,y,z and all of it's neighbors.
The inefficiencies are such that when moving; in a -X direction when on an odd Z plane or in a +X when on an even Z plane, an extra step is made, shown in the screenshots. Another Inefficiency is that when moving in the Z plane an extra step is often taken as the code seems to prefer keeping it's X value the same for as long as possible before approaching on the Z plane. This is leading to the unit being 1 tile further from the goal when it starts it's Z movement than it would have been has it moved 1 X negatively to start with.
I'll add my path generation code, neighbor generation code and my node class code as well as screenshots of where the inefficiencies are occurring as I know my explanations are unclear at best. The neighbor code ensures that the highest adjacent tile is the one stored as the neighbor (it also has to search through types as i have a variety of tile types.
Thank you so much in advance, to anyone that might be able to offer some help or insight in to what is going wrong.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System;
using System.Linq;
public class UnitMasterScript : MonoBehaviour {
// private int Number_of_neighbours = 6;
private int Number_of_neighbours = 6;
private GameObject[] Neighbours;
Node[,,] graph;
public bool MapMakerDone = false;
void Update()
{
if (MapMakerDone == true)
{
// Wait for MapMaker to change MapMakerDone to true then allow rest of script to run
GameObject Map = GameObject.Find("MapMaker");
int WidthVal = Map.GetComponent<MapMakerFromFile>().WidthVal;
int HeightVal = Map.GetComponent<MapMakerFromFile>().HeightVal;
int DepthVal = Map.GetComponent<MapMakerFromFile>().DepthVal;
// Graph of node generation code
// Code to find which hex is to each side of this hex
// Need to find hex to left, right, ul, ur, ll, lr
// Need to find hex at the top of the stack adjacent
// 0:L 1:R 2:UL 3:UR 4:LL 5:LR
graph = new Node[WidthVal, HeightVal, DepthVal];
for (int x = 0; x < WidthVal; x++)
{
for (int y = 0; y < HeightVal; y++)
{
for (int z = 0; z < DepthVal; z++)
{
graph[x, y, z] = new Node();
graph[x, y, z].x = x;
graph[x, y, z].y = y;
graph[x, y, z].z = z;
}
}
}
for (int x = 0; x < WidthVal; x++)
{
for (int y = 0; y < HeightVal; y++)
{
for (int z = 0; z < DepthVal; z++)
{
// Set up the x and y for the neighbour as the source so it can be used
int neighbourX = x;
int neighbourY = 0; // must always start from 0 to ensure a downstep isn't missed
int neighbourZ = z;
int neighbourType = 0;
int correct_type = 0;
GameObject Hex_Present_checker = null;
// First needs to check if there even is a tile at this coordinate location
for (neighbourType = 0; neighbourType < 5; neighbourType++)
{
Hex_Present_checker = GameObject.Find("Hex_" + x + "_" + y + "_" + z + "_" + neighbourType);
if (Hex_Present_checker != null)
{
correct_type = neighbourType;
}
Hex_Present_checker = null;
}
if (correct_type != 0)
{
neighbourType = correct_type;
// int Number_of_neighbours = 6;
// GameObject[] Neighbours;
Neighbours = new GameObject[Number_of_neighbours];
// For each value of each tile in neighbours find what the tile coordinates are in XYZ
for (int q = 0; q < Number_of_neighbours; q++)
{
// Finds X and Z values of the neighbours
if (q < 2)
{
if (q == 0) { neighbourX = x + 1; }
if (q == 1) { neighbourX = x - 1; }
}
if (z % 2 == 1)
{
if (q == 2) { neighbourX = x; neighbourZ = z + 1; }
if (q == 3) { neighbourX = x + 1; neighbourZ = z + 1; }
if (q == 4) { neighbourX = x; neighbourZ = z - 1; }
if (q == 5) { neighbourX = x + 1; neighbourZ = z - 1; }
}
else
{
if (q == 2) { neighbourX = x - 1; neighbourZ = z + 1; }
if (q == 3) { neighbourX = x; neighbourZ = z + 1; }
if (q == 4) { neighbourX = x - 1; neighbourZ = z - 1; }
if (q == 5) { neighbourX = x; neighbourZ = z - 1; }
}
// Checks for the highest tile for the XZ coordinate and gets its Y value
GameObject potential_highest_ring;
int highest_Y = 0;
int correct_neighbour_type = 0;
for (neighbourY = 0; neighbourY < HeightVal; neighbourY++)
{
for (neighbourType = 0; neighbourType < 5; neighbourType++)
{
potential_highest_ring = null;
potential_highest_ring = GameObject.Find("Hex_" + neighbourX + "_" + neighbourY + "_" + neighbourZ + "_" + neighbourType);
if (potential_highest_ring != null)
{
highest_Y = neighbourY;
correct_neighbour_type = neighbourType;
}
}
}
// Need to check if there is a neighbour at the given coordinates
// Debug.Log("Hex_" + neighbourX + "_" + highest_Y + "_" + neighbourZ + "_" + neighbourType);
Neighbours[q] = GameObject.Find("Hex_" + neighbourX + "_" + highest_Y + "_" + neighbourZ + "_" + correct_neighbour_type);
// While there is a neighbour in the neighbours array
// add it's coordinates to the graph node as a part of its neighbours sublist
if (Neighbours[q] != null)
{
graph[x, y, z].neighbours.Add(graph[neighbourX, highest_Y, neighbourZ]);
}
}
}
}
}
}
MapMakerDone = false;
}
}
// List<Node> currentPath = null;
public List<Node> GeneratePathTo(GameObject SelectedUnit, Vector3 targetVec)
{
// Dijkstra's Algorithm Implementation
Dictionary<Node, float> dist = new Dictionary<Node, float>();
Dictionary<Node, Node> prev = new Dictionary<Node, Node>();
List<Node> unvisited = new List<Node>();
Node source = graph[
SelectedUnit.GetComponent<UnitBasicScript>().HexX,
SelectedUnit.GetComponent<UnitBasicScript>().HexY,
SelectedUnit.GetComponent<UnitBasicScript>().HexZ];
// TargetNode float to int conversion
int targetVecXInt = (int)targetVec.x;
int targetVecYInt = (int)targetVec.y;
int targetVecZInt = (int)targetVec.z;
Node targetNode = graph[
targetVecXInt,
targetVecYInt,
targetVecZInt];
// Debug.Log(targetVecXInt + "_" + targetVecYInt + "_" + targetVecZInt);
dist[source] = 0;
prev[source] = null;
// Initialise everything to have infinity distance since no other
information available
// Some nodes might not eb erachable therefore infinity is reasonable
foreach (Node v in graph)
{
if (v != source)
{
dist[v] = Mathf.Infinity;
prev[v] = null;
}
unvisited.Add(v);
}
while (unvisited.Count > 0)
{
// u is unvisited node with shortest distance
Node u = null;
foreach (Node possibleU in unvisited)
{
if (u == null || dist[possibleU] < dist[u])
{
u = possibleU;
}
}
unvisited.Remove(u);
if (u == targetNode)
{
break;
}
foreach (Node v in u.neighbours)
{
float alt = dist[u] + u.Distanceto(v);
if (alt < dist[v])
{
dist[v] = alt;
prev[v] = u;
}
}
}
if (prev[targetNode] == null)
{
// No route to target
return null;
}
List<Node> currentPath = new List<Node>();
Node curr = targetNode;
while (prev[curr] != null)
{
currentPath.Add(curr);
curr = prev[curr];
}
currentPath.Reverse();
return currentPath;
} // End of generate path function
public class Node
{
public int x = 0;
public int y = 0;
public int z = 0;
public List<Node> neighbours;
public Node()
{
neighbours = new List<Node>();
}
public float Distanceto(Node n)
{
if (n == null)
{
Debug.Log("Error");
}
return Vector2.Distance(
new Vector3(x, y, z),
new Vector3(n.x, n.y, n.z));
}
}
}
That concludes the code, I understand everything within monobehaviour has to be indented and it is in my code but upon copying into stackoverflow it lost that indentation. Next are the pictures showing the incorrect paths the units take.
https://imgur.com/a/wEChdq3
If any other information is needed please let me know and I will be more than happy to provide it. Thank you so much again!
You are using a List instead of a priority queue, which is massively inefficient. Also, since your grid has a simple heuristic, you should consider using A* which will be much faster.
Despite all the glaring inefficiencies which i still haven't resolved, I have resolved the problems with the algorithm implementation. I was getting the distance between the tile grid coordinates which didn't take into account that on a hex grid a diagonal movement has the exact same distance cost as a horizontal movement. Therefore the solution was to get the distance once the node grid coordinates had been converted to world coordinates as this will ensure all distances between adjacent tiles are equal.
Hope this helps if anyone gets stuck with the same problem!
I've tried to reimplement the Fast Graphics Gems Ray/AABB Intersection Method in C#:
// Based on "Fast Ray-Box Intersection" algorithm by Andrew Woo, "Graphics Gems", Academic Press, 1990
public unsafe Vector? IntersectionWith(Cuboid other) {
const int NUM_DIMENSIONS = 3;
Assure.Equal(NUM_DIMENSIONS, 3); // If that value is ever changed, this algorithm will need some maintenance
const byte QUADRANT_MIN = 0;
const byte QUADRANT_MAX = 1;
const byte QUADRANT_BETWEEN = 2;
// Step 1: Work out which direction from the start point to test for intersection for all 3 dimensions, and the distance
byte* quadrants = stackalloc byte[NUM_DIMENSIONS];
float* candidatePlanes = stackalloc float[NUM_DIMENSIONS];
float* cuboidMinPoints = stackalloc float[NUM_DIMENSIONS];
float* cuboidMaxPoints = stackalloc float[NUM_DIMENSIONS];
float maxDistance = Single.NegativeInfinity;
byte maxDistanceDimension = 0;
bool startPointIsInsideCuboid = true;
cuboidMinPoints[0] = other.X;
cuboidMinPoints[1] = other.Y;
cuboidMinPoints[2] = other.Z;
cuboidMaxPoints[0] = other.X + other.Width;
cuboidMaxPoints[1] = other.Y + other.Height;
cuboidMaxPoints[2] = other.Z + other.Depth;
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
if (StartPoint[i] < cuboidMinPoints[i]) {
quadrants[i] = QUADRANT_MIN;
candidatePlanes[i] = cuboidMinPoints[i];
startPointIsInsideCuboid = false;
}
else if (StartPoint[i] > cuboidMaxPoints[i]) {
quadrants[i] = QUADRANT_MAX;
candidatePlanes[i] = cuboidMaxPoints[i];
startPointIsInsideCuboid = false;
}
else {
quadrants[i] = QUADRANT_BETWEEN;
}
}
if (startPointIsInsideCuboid) return StartPoint;
// Step 2: Find farthest dimension from cuboid
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
// ReSharper disable once CompareOfFloatsByEqualityOperator Exact check is desired here: Anything other than 0f is usable
if (quadrants[i] != QUADRANT_BETWEEN && Orientation[i] != 0f) {
float thisDimensionDist = (candidatePlanes[i] - StartPoint[i]) / Orientation[i];
if (thisDimensionDist > maxDistance) {
maxDistance = thisDimensionDist;
maxDistanceDimension = i;
}
}
}
if (maxDistance < 0f) return null;
if (maxDistance - Length > MathUtils.FlopsErrorMargin) return null;
float* intersectionPoint = stackalloc float[NUM_DIMENSIONS];
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
if (maxDistanceDimension == i) {
intersectionPoint[i] = StartPoint[i] + maxDistance * Orientation[i];
if (cuboidMinPoints[i] - intersectionPoint[i] > MathUtils.FlopsErrorMargin || intersectionPoint[i] - cuboidMaxPoints[i] > MathUtils.FlopsErrorMargin) return null;
}
else intersectionPoint[i] = candidatePlanes[i];
}
Vector result = new Vector(intersectionPoint[0], intersectionPoint[1], intersectionPoint[2]);
if (!IsInfiniteLength && Vector.DistanceSquared(StartPoint, result) > Length * Length) return null;
else return result;
}
However, although it sort of works, I'm getting incorrect results on the following part of a unit test:
Cuboid cuboid = new Cuboid(frontBottomLeft: new Vector(0f, 7.1f, 0f), width: 0f, height: 5f, depth: 0f);
Ray testRayC = new Ray(startPoint: new Vector(30f, 30f, 30f), orientation: new Vector(-1f, -1f, -1f));
Assert.AreEqual(
null,
testRayC.IntersectionWith(cuboid)
);
I am expecting null from the call to testRayC.IntersectionWith(cuboid), but instead it returns a Vector(0, 12.1, 0), which is not a point on the ray at all.
So is it just a case of adding a final check that the calculated point is on the ray? Or (and this is what I suspect), have I made an error in transcribing the code? I have double and triple checked but didn't see anything obvious...
The problem in your code is when you do if (maxDistanceDimension == i) {. The original code checks if (whichPlane != i) {. I don't have your data structures, but a fix should look like:
for (byte i = 0; i < NUM_DIMENSIONS; ++i)
{
if (maxDistanceDimension != i)
{
intersectionPoint[i] = StartPoint[i] + maxDistance * Orientation[i];
if (intersectionPoint[i] < cuboidMinPoints[i] - MathUtils.FlopsErrorMargin || intersectionPoint[i] > cuboidMaxPoints[i] + MathUtils.FlopsErrorMargin)
return null;
}
else
{
intersectionPoint[i] = candidatePlanes[i];
}
}
Next, the following isn't in the original code. What is this for?
if (maxDistance - Length > MathUtils.FlopsErrorMargin)
return null;
If you are trying to check if the hit is within the extent of the ray, this may be a bug. Given that your Orientation does not appear to be normalized, maxDistance is not necessarily in units of length. This may not matter in the original algorithm, but if you are going to check maxDistance against some other length you need to normalize Orientation (make it dimensionless) so that
thisDimensionDist = (candidatePlanes[i] - StartPoint[i]) / Orientation[i];
will have units of length.
Incidentally, in the original I think the following is wrong:
if(inside) {
coord = origin;
return (TRUE);
}
Assuming this code is c and not c++, this simply sets the the coord pointer to have the same reference as the origin pointer, which will have no effect on the caller. This issue doesn't apply to your version, however.
Also, in the course of looking at this, I made a more literal c# transcription of the algorithm here:
public static class RayXCuboid
{
enum HitQuadrant
{
Right = 0,
Left = 1,
Middle = 2,
}
const int Dimension = 3;
[Conditional("DEBUG")]
static void AssertValidArguments<TDoubleList>(params TDoubleList[] args) where TDoubleList : IList<double>
{
Debug.Assert(Dimension == 3);
foreach (var list in args)
Debug.Assert(list != null && list.Count == Dimension);
}
public static bool HitBoundingBox<TDoubleList>(TDoubleList minB, TDoubleList maxB, TDoubleList origin, TDoubleList dir, TDoubleList coord) where TDoubleList : IList<double>
{
AssertValidArguments(minB, maxB, origin, dir, coord);
HitQuadrant[] quadrant = new HitQuadrant[Dimension];
double[] maxT = new double[Dimension];
double[] candidatePlane = new double[Dimension];
/* Find candidate planes; this loop can be avoided if
rays cast all from the eye(assume perpsective view) */
bool inside = true;
for (int i = 0; i < Dimension; i++)
if (origin[i] < minB[i])
{
quadrant[i] = HitQuadrant.Left;
candidatePlane[i] = minB[i];
inside = false;
}
else if (origin[i] > maxB[i])
{
quadrant[i] = HitQuadrant.Right;
candidatePlane[i] = maxB[i];
inside = false;
}
else
{
quadrant[i] = HitQuadrant.Middle;
}
/* Ray origin inside bounding box */
if (inside)
{
CopyTo(origin, coord);
return true;
}
/* Calculate T distances to candidate planes */
for (int i = 0; i < Dimension; i++)
if (quadrant[i] != HitQuadrant.Middle && dir[i] != 0.0)
maxT[i] = (candidatePlane[i] - origin[i]) / dir[i];
else
maxT[i] = -1.0;
/* Get largest of the maxT's for final choice of intersection */
int whichPlane = 0;
for (int i = 1; i < Dimension; i++)
if (maxT[whichPlane] < maxT[i])
whichPlane = i;
/* Check final candidate actually inside box */
if (maxT[whichPlane] < 0.0)
{
FillWithDefault(coord);
return false;
}
for (int i = 0; i < Dimension; i++)
if (whichPlane != i)
{
coord[i] = origin[i] + maxT[whichPlane] * dir[i];
if (coord[i] < minB[i] || coord[i] > maxB[i])
{
FillWithDefault(coord);
return false;
}
}
else
{
coord[i] = candidatePlane[i];
}
return true; /* ray hits box */
}
static void FillWithDefault<T>(IList<T> list)
{
for (int i = 0; i < list.Count; i++)
list[i] = default(T);
}
static void CopyTo<T>(IList<T> from, IList<T> to)
{
int arrayIndex = 0;
foreach (var item in from)
to[arrayIndex++] = item;
}
}
I'm creating a simple XNA C# top-down game. In the game you will be followed by an enemy around a maze like map. ATM I have it set so that when the game starts the enemy finds the shortest path to you (which it does). A new path is found every second, if the player has moved.
The game always finds a route to you, but seems to have trouble finding the shortest route to a moving target, sometimes entering a loop whiles you're moving, and when you stop, the enemy travels half way round the map to find you, even if you're only a few steps away. It doesn't appear to be the heuristic, as I removed that (making it dijkstra) and it still appeared to have the same problem. I assume it's something simple that I overlooked, but am having trouble figuring out what.
Thanks in advance
class PathFinder
{
private Cell[,] map = Game1.cellArray;
public List<Cell> calculate(int currX, int currY, int destX, int destY)
{
resetMap();
Debug.WriteLine("starting over");
List<Cell> path = new List<Cell>();
List<Cell> openList = new List<Cell>();
List<Cell> closedList = new List<Cell>();
Cell startPos = map[currY, currX];
Cell endPos = map[destY, destX];
startPos.closed = true;
closedList.Add(startPos);
Cell curPos = startPos;
while (!closedList.Contains(endPos))
{
//add adjacent nodes to list, discover their scores
foreach (Cell v in getAdj(curPos))
{
if (v.walkable == true && v.closed == false)
{
if (!openList.Contains(v))
{
v.parent = curPos;
v.H = getH(v, endPos);
v.G = v.parent.G + 10;
v.F = v.G + v.H;
openList.Add(v);
}
//if square already on list would benefit from current tile being parent, make it so
else if (curPos.G + 10 < v.G)
{
v.parent = curPos;
v.G = v.parent.G + 10;
v.F = v.G + v.H;
}
}
}
if (openList.Count <= 1) {/*Console.WriteLine("Returned NULL");*/ return null; }
curPos = openList[1];
curPos.closed = true;
closedList.Add(curPos);
openList[1] = openList[openList.Count - 1];
openList.RemoveAt(openList.Count - 1);
openList.OrderBy(o => o.F);
openList.Reverse();
}
//backtrack to discover path
Cell curNode = endPos;
path.Add(endPos);
while (curNode != startPos)
{
curNode = curNode.parent;
path.Add(curNode);
Debug.WriteLine("Path// X: " + curNode.xLocation + " Y: " + curNode.yLocation);
}
path.Reverse();
return path;
}
//finds heuristic of current square by checking distance to destination ignoring walls
private int getH(Cell curPos, Cell endPos)
{
int diffX = curPos.xLocation - endPos.xLocation;
int diffY = curPos.yLocation - endPos.yLocation;
if (diffX < 0)
{
diffX *= -1;
}
if (diffY < 0)
{
diffY *= -1;
}
return ((diffX + diffY) * 10);
}
//get list of adjacent Cells
private List<Cell> getAdj(Cell curPos)
{
List<Cell> adjList = new List<Cell>();
if (curPos.xLocation - 1 >= 0){
adjList.Add(map[curPos.yLocation, curPos.xLocation - 1]);
}
if (curPos.xLocation < 19)
{
adjList.Add(map[curPos.yLocation, curPos.xLocation + 1]);
}
if (curPos.yLocation - 1 >= 0)
{
adjList.Add(map[curPos.yLocation - 1, curPos.xLocation]);
}
if (curPos.yLocation < 19)
{
adjList.Add(map[curPos.yLocation + 1, curPos.xLocation]);
}
return adjList;
}
private void resetMap()
{
//reset Cells to default values
for (int r = 0; r < map.GetLength(1); r++)
{
for (int c = 0; c < map.GetLength(0); c++)
{
map[r, c].reset();
}
}
}
}
Just worked out the problem. Wasn't sorting before changing the currently selected tile. I had assumed I was scoring them incorrectly lol.