- Home /
Bottom area of a 3D model? Raycast?
Hi! I'm working on a system that is like any common RTS. You press a building and put it down where ever you want.
Too be able to level the ground (heightmap) where I put a building I have to know which points to edit (I want to level the area to avoid some parts going through the terrain). So how would I go about to get the area (and corresponding points) of a 3D model/gameobject?
Should I just use Raycast over a large area around the 3D model, see which ones hit terrain and which hit the 3D model. Then use the ones that hit the 3D model as the points to edit in the heightmap? This feels very inefficient though, especially when I don't know which size each model is (so I don't know for sure what the smallest possible area to search is). So if raycasts is the best way, how can I optimize it based on model-size?
Answer by cjdev · Aug 27, 2015 at 10:23 PM
Personally I wouldn't use raycasting here as you already have the position data of the model and of the terrain. Basically I'd compare the verts in the model to the terrain mesh and get the ones that are closest to the model. An implementation might go something like this (please note this is very much untested):
GameObject building = GameObject.Find("Building");
GameObject terrain = GameObject.Find("Terrain");
Mesh mesh = building.GetComponent<MeshFilter>().mesh;
List<Vector3> verts = mesh.vertices.ToList();
int minX = (int)verts.Min(v => v.x);
int minZ = (int)verts.Min(v => v.z);
int maxX = (int)verts.Max(v => v.x);
int maxZ = (int)verts.Max(v => v.z);
//Note: this assumes your terrain is 0,0 based and unscaled, add offsets as needed
float[,] terrainHeights = terrain.GetComponent<Terrain>().terrainData.GetHeights(minX, minZ, maxX - minX, maxZ - minZ);
List<Vector3> terrainVerts = new List<Vector3>();
//Populates a list of terrain vertices from the hieghts list
for(int i = minX; i < maxX; i++)
{
for(int j = minZ; j < maxZ; j++)
{
terrainVerts.Add(new Vector3(i, terrainHeights[i - minX, j - minZ], j));
}
}
//Removes all terrain verts farther than 1 from model verts
//Could be implemented with loops instead of LINQ
//These are the verts you need to modify in your terrain
terrainVerts.RemoveAll(v => verts.Exists(x => Vector3.Distance(v, x) > 1f));
Thanks a bunch for such an elaborate answer!
I get an array index is out of range-error on the last part (terrainVerts.Add...) and can't for the life of me figure out what is causing it.
EDIT: After some time I finally remembered that heightmap coodinates are indexed [y,x] (the reverse, for some (to me) illogical reason). Flipped the "terrainHeights[i - $$anonymous$$X, j - $$anonymous$$Z]" to "terrainHeights[j - $$anonymous$$Z, i - $$anonymous$$X]" and now I don't get any errors :) Will mark you answer as correct now!
Hmm... Apparently the "Remove" part removes ALL the elements in the list. First I thought it had to do with the height not being scaled to world-coords (so I scaled it by *terrainData.size.y). But that didn't work. Then I changed it to only compare x and z coordinates with this: terrainVerts.RemoveAll(v => verts.Exists(x => $$anonymous$$athf.Abs(v.x - x.x) > 1f));
but it still removes all elements.
EDIT: Realised this was wrong and did an good ol' pythagoras (comparing points on x- and z-axis), but only get 3 elements that fullfill the requirement. I guess this is because the code only checks for any element in both lists that agree to the condition, and then remove it. So if point B and A are at a greater distance than 1 then they are removed, but B is within the distance of 1 to C so we don't want it to be removed.
Yeah sorry about that, I wasn't sure if the Linq was right without testing it. You're probably better off just using good old fashioned for loops but you might try something like this (still untested though):
var result = from i in terrainVerts
from j in verts
where Vector3.Distance(i, j) < 1f
select i;
That worked perfectly! I just changed the condition to $$anonymous$$athf.Sqrt($$anonymous$$athf.Pow((i.x - j.x),2f) + $$anonymous$$athf.Pow((i.z - j.z),2f)) < 1f
since I don't really care about the y-axis. (I also noticed earlier when I was reworking your code that my mesh was hollowed at the bottom, so just matching the x- and z-axis might be better for the models I'm using, although I probably will get many duplicates. So your way is totally better if the model allows it).
EDIT: $$anonymous$$ight add that you can use HashSet or List.Distinct() to get rid of duplicates. Went from a list of about 3700 vectors to 117 (with both methods, same 3D model).