Voici quelques fonctions pour faire des calculs sur des sphères :
- faire la conversion (longitude,latitude) <-> Vector3
- calculer la distance la plus courte entre deux points sur la sphère
- calculer les points d'intersections avec un segment
- conversion des coordonnées des points d'un cube vers une sphère, en conservant un écart à peu près constant entre les points,
- fonction inverse pour transposer les points de la sphère vers le cube
contrairement à la technique habituelle de normalisation des points du cube qui provoque une contraction au niveau des points correspondants aux coins du cube.
Si vous avez des suggestions d'optimisations ou d'ajouts, faites-le moi savoir
Code : Tout sélectionner
using UnityEngine;
using System.Collections;
public class SphereMath {
public static Vector3 CoordinatesToVector3(Vector2 coordinates, float radius = 1f){
return CoordinatesToVector3(coordinates.x,coordinates.y,radius);
}
public static Vector3 CoordinatesToVector3(float longitude, float latitude, float radius = 1f){
longitude *= Mathf.Deg2Rad;
latitude *= Mathf.Deg2Rad;
float r_e = Mathf.Cos(latitude);
float y_e = Mathf.Sin(latitude);
float x_s = r_e * Mathf.Sin(longitude) * -1;
float z_s = r_e * Mathf.Cos(longitude);
return new Vector3(x_s , y_e , z_s).normalized * radius;
}
public static Vector2 Vector3ToCoordinates(Vector3 position){
position = Vector3.Normalize(position);
float x_s = position.x;
float y_e = position.y;
float z_s = position.z;
float latitude = Mathf.Asin(y_e);
float r_e = Mathf.Cos(latitude);
float longitude = Mathf.Acos(x_s / r_e);
Vector2 coordinates = new Vector2(longitude,latitude) * Mathf.Rad2Deg;
if(z_s <= 0){
if(x_s <= 0){
coordinates.x = 180 - (coordinates.x - 90);
}else{
coordinates.x = -90 - coordinates.x;
}
}else{
coordinates.x -= 90;
}
return coordinates;
}
public static float ArcDistance(Vector3 positionA, Vector3 positionB, float radius){
if(Vector3.SqrMagnitude(positionA - positionB) == 0) return 0f;
return (Mathf.Acos (Vector3.Dot(positionA.normalized, positionB.normalized)) * Mathf.Rad2Deg * Mathf.PI * radius) / 180f;
}
public static int SegmentIntersections(Vector3 start, Vector3 end, Vector3 center, float radius, out Vector3 intersection1, out Vector3 intersection2){
Vector4 data = ComputeSegmentIntersection(start,end,center,radius);
intersection1 = Vector3.zero;
intersection2 = Vector3.zero;
if(data.w<0){
return 0;
}
float mu;
if(data.w==0){
mu = -data.y/(2*data.x);
intersection1 = start + mu*(end - start);
return 1;
}
if(data.w>0){
Vector3 pa = Vector3.zero;
Vector3 pb = Vector3.zero;
mu = (-data.y + Mathf.Sqrt( Mathf.Pow(data.y,2) - 4*data.x*data.z )) / (2*data.x);
pa = start + mu*(end-start);
mu = (-data.y - Mathf.Sqrt( Mathf.Pow(data.y,2) - 4*data.x*data.z )) / (2*data.x);
pb = start + mu*(end-start);
float da = Vector3.Distance(start,pa);
float db = Vector3.Distance(start,pb);
if(da<db){
intersection1 = pa;
intersection2 = pb;
}else{
intersection1 = pb;
intersection2 = pa;
}
return 2;
}
return 0;
}
private static Vector4 ComputeSegmentIntersection(Vector3 start, Vector3 end, Vector3 center, float radius){
Vector3 segment = end - start;
float a = segment.sqrMagnitude;
Vector3 offset = start - center;
float b = 2 * ( segment.x*offset.x + segment.y*offset.y + segment.z*offset.z );
float c = Mathf.Pow(center.x,2) + Mathf.Pow(center.y,2) + Mathf.Pow(center.z,2) + Mathf.Pow(start.x,2) + Mathf.Pow(start.y,2) + Mathf.Pow(start.z,2) - 2* ( center.x*start.x + center.y*start.y + center.z*start.z ) - Mathf.Pow(radius,2);
float i = b * b - 4 * a * c;
return new Vector4(a,b,c,i);
}
// from http://mathproofs.blogspot.fr/2005/07/mapping-cube-to-sphere.html
public static Vector3 CubeToSphere(Vector3 position, float radius = 0.5f){
Vector3 result = Vector3.zero;
float x2 = position.x * position.x;
float y2 = position.y * position.y;
float z2 = position.z * position.z;
result.x = position.x * Mathf.Sqrt (1 - y2*0.5f - z2*0.5f + (y2*z2)/3f);
result.y = position.y * Mathf.Sqrt (1 - z2*0.5f - x2*0.5f + (z2*x2)/3f);
result.z = position.z * Mathf.Sqrt (1 - x2*0.5f - y2*0.5f + (x2*y2)/3f);
return result * radius;
}
// thanks to Boby, the god of Maths
public static Vector3 SphereToCube(Vector3 position, float size = 0.5f){
int i0, i1, i2;
position = position.normalized;
float xs = position.x;
float ys = position.y;
float zs = position.z;
float sx = Mathf.Sign(xs);
float sy = Mathf.Sign(ys);
float sz = Mathf.Sign(zs);
float[] e = new float[3];
xs=Mathf.Abs(xs);
ys=Mathf.Abs(ys);
zs=Mathf.Abs(zs);
float x = xs;
float y = ys;
float z = zs;
float t;
if(y>=x && y>=z){
i0=0; i1=2; i2=1;
x=xs; y=zs; z=ys;
e[i2]=sy;
t = sy;
sy = sz;
sz = t;
}else if(z>=x){
i0=0; i1=1; i2=2;
x=xs; y=ys; z=zs;
e[i2]=sz;
}else{
i0=2; i1=1; i2=0;
x=zs; y=ys; z=xs;
e[i2]=sx;
t = sx;
sx = sz;
sz = t;
}
if(x<0.0001f){
e[i0] = 0f;
e[i1] = sy*Mathf.Sqrt(2f-2f*z*z);
position.x = e[0];
position.y = e[1];
position.z = e[2];
return position*size;
}else if(y<0.0001f){
e[i1] = 0f;
e[i0] = sx*Mathf.Sqrt(2f-2f*z*z);
position.x = e[0];
position.y = e[1];
position.z = e[2];
return position*size;
}
float x2 = x*x;
float y2 = y*y;
e[i1] = Mathf.Sqrt( 1.5f - x2 + y2 - Mathf.Sqrt( x2*x2 + y2*y2 - 3f*(x2+y2) - 2f*x2*y2 + 2.25f ) );
e[i0] = x / Mathf.Sqrt(0.5f - e[i1]*e[i1]*0.5f + e[i1]*e[i1]/3f);
e[i0] = Mathf.Abs(e[i0])*sx;
e[i1] = Mathf.Abs(e[i1])*sy;
position.x = e[0];
position.y = e[1];
position.z = e[2];
return position*size;
}
}
Et un bout de code pour tester, à coller sur une sphère (plus besoin de collider car maintenant la détection d'intersection est calculée via une fonction).
Clic gauche et clic droit pour placer les deux points servant à définir l'arc dont on veut mesurer la longueur, flèches du clavier pour faire pivoter la sphère.
L'intersection est symbolisée par un segment jaune (entrée) et un cyan (sortie), plus un gris entre les deux points d'intersection, à voir dans la vue "Scene", car ce segment est perpendiculaire à la caméra et donc invisible depuis celle-ci.
Code : Tout sélectionner
using UnityEngine;
using System.Collections;
public class DebugSphereMath : MonoBehaviour {
private Transform _transform;
//private Vector3 hitLocalPosition = Vector3.zero;
private Vector3 intersectPosition = Vector3.zero;
private Vector3 posA = Vector3.zero;
private Vector3 posB = Vector3.zero;
private float arcDist = 0f;
private float radius = 0.5f;
private Vector3 intersectA = Vector3.zero;
private Vector3 intersectB = Vector3.zero;
private Vector3 intersectDir = Vector3.zero;
private int intersectCount = 0;
void Start(){
_transform = transform;
radius = this.GetComponent<SphereCollider>().radius * _transform.localScale.x;
}
void Update () {
int i,j,k;
Vector3 p1,p2;
if(Input.GetKey(KeyCode.LeftArrow)) _transform.Rotate(0,30*Time.deltaTime,0,Space.World);
if(Input.GetKey(KeyCode.RightArrow)) _transform.Rotate(0,-30*Time.deltaTime,0,Space.World);
if(Input.GetKey(KeyCode.UpArrow)) _transform.Rotate(-30*Time.deltaTime,0,0,Space.World);
if(Input.GetKey(KeyCode.DownArrow)) _transform.Rotate(30*Time.deltaTime,0,0,Space.World);
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
// intersection par calcul
// intersectCount reçoit le nombre d'intersections (0,1 ou 2) entre la sphère et le segment spécifié
// intersectA reçoit le point d'entrée
// intersectB reçoit le point de sortie
// ici la longueur du segment est très grande pour être sûr d'avoir toujours 2 points d'intersection
Vector3 start = _transform.InverseTransformPoint(ray.origin);
Vector3 end = _transform.InverseTransformPoint(ray.origin+ray.direction*1000);
intersectCount = SphereMath.SegmentIntersections(start , end , _transform.position , radius , out intersectA, out intersectB);
if(intersectCount > 0){
intersectPosition = _transform.TransformPoint(intersectA);
intersectDir = _transform.InverseTransformDirection(ray.direction);
Debug.DrawLine(intersectPosition,intersectPosition + intersectPosition.normalized * radius,Color.red);
if(Input.GetMouseButton(0)) posA = intersectA;
if(Input.GetMouseButton(1)) posB = intersectA;
p1 = SphereMath.SphereToCube(intersectA,radius);
Debug.DrawLine(intersectPosition,_transform.TransformPoint(p1),Color.green);
}
p1 = _transform.TransformPoint(posA);
p2 = _transform.TransformPoint(posB);
Debug.DrawLine(p1,p1 + p1.normalized * 0.5f,Color.green);
Debug.DrawLine(p2,p2 + p2.normalized * 0.5f,Color.blue);
arcDist = 0f;
if(Vector3.SqrMagnitude(posB-posA) > 0){
int precision = 50;
Vector3 step = (p2 - p1) / precision;
Vector3 p3 = p2;
p2 = p1;
for(i=0;i<(precision*5);i++){
p2 += step;
p2 = p2.normalized * radius;
if(Vector3.Dot (step,p3-p2) < 0){
p2 = p3;
i = precision*6;
}
arcDist += Vector3.Distance(p1,p2);
Debug.DrawLine(p1,p2,new Color(1f,0.5f,0f));
p1 = p2;
}
}
if(intersectCount > 0){
p1 = _transform.TransformPoint(intersectA);
p2 = _transform.TransformPoint(intersectB);
Debug.DrawLine(p1,p2,Color.grey);
Debug.DrawLine(p1,p1 - _transform.TransformDirection(intersectDir) * 0.3f,Color.yellow);
Debug.DrawLine(p2,p2 + _transform.TransformDirection(intersectDir) * 0.3f,Color.cyan);
}
// cube to sphere
int subdiv = 5;
float v1,v2;
for(i=-subdiv;i<=subdiv;i++){
for(j=-subdiv;j<=subdiv;j++){
for(k=0;k<6;k++){
v1 = i/(float)subdiv;
v2 = j/(float)subdiv;
switch(k){
case 0:
p1.x = v1;
p1.y = v2;
p1.z = 1f;
break;
case 1:
p1.x = v1;
p1.y = v2;
p1.z = -1f;
break;
case 2:
p1.x = 1;
p1.y = v2;
p1.z = v1;
break;
case 3:
p1.x = -1;
p1.y = v2;
p1.z = v1;
break;
case 4:
p1.x = v1;
p1.y = 1;
p1.z = v2;
break;
case 5:
p1.x = v1;
p1.y = -1;
p1.z = v2;
break;
}
p1 = SphereMath.CubeToSphere(p1,radius);
p2 = _transform.TransformPoint(p1);
Debug.DrawRay(p2,p2.normalized*-0.01f,Color.white);
p2 = SphereMath.SphereToCube(p1,radius);
p2 = _transform.TransformPoint(p2);
Debug.DrawRay(p2,p2.normalized*0.01f,Color.red);
}
}
}
}
void OnGUI(){
if( intersectA.magnitude > 0){
Vector2 latlon = SphereMath.Vector3ToCoordinates(intersectA);
Vector3 pos = SphereMath.CoordinatesToVector3(latlon,radius);
float d = SphereMath.ArcDistance(posA,posB,radius);
GUI.Label(new Rect(5,5,200,200),"Longitude,Latitude : "+latlon.ToString()+"\n\n3D position :\nmath : "+(intersectA*100).ToString()+"\nconvert : "+(pos*100).ToString()+"\n\nArcDistance :\nmesh : "+arcDist.ToString()+"\nmath : "+d.ToString());
}
}
}