I need to set a sphere collider for a diamond-shape model. When I add the component the sphere doesn’t cover the entire shape. With the next code I manage to set the center and radius of the sphere based on the mesh center and extents magnitude:
However, it ends up covering more than necessary. Something to consider is that the pivot point of the Model is not centered, and the original rotation of the model may be in an almost random direction. Here’s a graphical example:
There is no easy solution to this problem. See [Bounding Sphere][1] for possible solutions / approximations.
edit
I found [this implementation of Ritter’s algorithm][2] and just ported it to C# / Unity:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public struct BoundingSphere
{
public Vector3 center;
public float radius;
public BoundingSphere(Vector3 aCenter, float aRadius)
{
center = aCenter;
radius = aRadius;
}
public static BoundingSphere Calculate(IEnumerable<Vector3> aPoints)
{
Vector3 xmin, xmax, ymin, ymax, zmin, zmax;
xmin = ymin = zmin = Vector3.one * float.PositiveInfinity;
xmax = ymax = zmax = Vector3.one * float.NegativeInfinity;
foreach(var p in aPoints)
{
if(p.x < xmin.x) xmin = p;
if(p.x > xmax.x) xmax = p;
if(p.y < ymin.y) ymin = p;
if(p.y > ymax.y) ymax = p;
if(p.z < zmin.z) zmin = p;
if(p.z > zmax.z) zmax = p;
}
var xSpan = (xmax - xmin).sqrMagnitude;
var ySpan = (ymax - ymin).sqrMagnitude;
var zSpan = (zmax - zmin).sqrMagnitude;
var dia1 = xmin;
var dia2 = xmax;
var maxSpan = xSpan;
if (ySpan > maxSpan)
{
maxSpan = ySpan;
dia1 = ymin; dia2 = ymax;
}
if (zSpan > maxSpan)
{
dia1 = zmin; dia2 = zmax;
}
var center = (dia1 + dia2) * 0.5f;
var sqRad = (dia2 - center).sqrMagnitude;
var radius = Mathf.Sqrt(sqRad);
foreach (var p in aPoints)
{
float d = (p - center).sqrMagnitude;
if(d > sqRad)
{
var r = Mathf.Sqrt(d);
radius = (radius + r) * 0.5f;
sqRad = radius * radius;
var offset = r - radius;
center = (radius * center + offset * p) / r;
}
}
return new BoundingSphere(center, radius);
}
}
Note: haven’t tested it yet It’s just a 1 to 1 translation. This could be a usage:
void Start()
{
MeshFilter mf = GetComponent<MeshFilter>();
Mesh m = mf.sharedMesh;
var sphere = BoundingSphere.Calculate(m.vertices);
var col = gameObject.AddComponent<SphereCollider>();
col.center = sphere.center;
col.radius = sphere.radius;
}
Keep in mind that this will use all vertices in the mesh. If you have unused vertices and just want to include “used” vertices you would have to iterate through the triangles and grab all referenced vertices. The easiest way is to linq like this:
void Start()
{
MeshFilter mf = GetComponent<MeshFilter>();
Mesh m = mf.sharedMesh;
var verts = m.vertices;
var tris = m.triangles;
var sphere = BoundingSphere.Calculate(tris.Select(i=>verts*));*
Here’s a c# conversion I did of a java library from GitHub - hbf/miniball: Fast computation of the smallest enclosing ball of a point set, in low or moderately high dimensions.
Warning: crappy code as it’s an almost straight conversion (and doc is unchanged)
Works pretty well, has a sample scene consisting of a rotated cylinder and a quad that are combined into a single mesh and then the bounding sphere generator is added (which calculates once on start and sets a spherecollider to results).