[C#] Sphere Math

Cette section est destinée aux scripts partagés par la communauté. Chaque post est destiné à un script. Suivez bien les recommandations.
Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

[C#] Sphere Math

Message par Alesk » 23 Nov 2013 11:19

Hello !

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());
			
		}
		
	}
	
}


Dernière édition par Alesk le 27 Nov 2013 22:09, édité 3 fois.

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: [C#] Sphere Math

Message par Alesk » 24 Nov 2013 03:46

Hop ! Ajout du calcul des intersections avec un segment de droite, du coup, plus besoin d'utiliser un collider.

Avatar de l’utilisateur
ZJP
Messages : 5748
Inscription : 15 Déc 2009 06:00

Re: [C#] Sphere Math

Message par ZJP » 24 Nov 2013 16:44

Merci, voilà qui tombe à point. 8-)

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: [C#] Sphere Math

Message par Alesk » 24 Nov 2013 22:37

De rien ! Si tu as besoin d'autre chose... ;)

Avatar de l’utilisateur
ZJP
Messages : 5748
Inscription : 15 Déc 2009 06:00

Re: [C#] Sphere Math

Message par ZJP » 24 Nov 2013 23:03

Alesk a écrit :De rien ! Si tu as besoin d'autre chose... ;)
...je ne manquerai pas de le demander. :D

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: [C#] Sphere Math

Message par Alesk » 25 Nov 2013 01:27

Ajout de la fonction CubeToSphere, trouvée ici : http://mathproofs.blogspot.fr/2005/07/m ... phere.html
... qui permet de conserver un écart à peu près constant entre les points sur la sphère, contrairement à la technique habituelle de normalisation des points du cube qui provoque une contraction au niveau des points correspondants aux coins du cube.

ça peut être bien pratique pour mapper un cube map sur une sphère pour obtenir une planète sans déformations notables de la texture
Dernière édition par Alesk le 25 Nov 2013 10:49, édité 1 fois.

Avatar de l’utilisateur
yoyoyaya
Messages : 1656
Inscription : 30 Mai 2011 13:14
Localisation : PAAAAARTOUUUU
Contact :

Re: [C#] Sphere Math

Message par yoyoyaya » 25 Nov 2013 03:02

Je n'en aurais surement jamais besoin (m'enfin, on ne sait jamais ^^) mais en tout cas, merci pour le partage ;)
ImageImage

Avatar de l’utilisateur
axel
Messages : 1924
Inscription : 26 Avr 2012 09:10
Localisation : Lille - Dunkerque
Contact :

Re: [C#] Sphere Math

Message par axel » 25 Nov 2013 09:25

Merci Alesk :)

Avatar de l’utilisateur
Franck
Bricoleur
Bricoleur
Messages : 2884
Inscription : 08 Jan 2011 18:43
Localisation : Tours

Re: [C#] Sphere Math

Message par Franck » 25 Nov 2013 09:54

yoyoyaya a écrit :... merci pour le partage ;)
+1
Dés fois j'bug, dés fois j'bug pas.

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: [C#] Sphere Math

Message par Alesk » 25 Nov 2013 10:52

ça me fait plaisir ;) surtout que je ne suis pas très actif ces derniers temps...
Tout ça vient de vieilles fonctions que je dépoussière en les extrayant d'anciens projets, en l'occurrence ici c'était un clone de google earth.
Si ça vous dit, je peux aussi vous rajouter les fonctions pour afficher les tiles représentant les différents niveaux de zooms des textures

Répondre

Revenir vers « Scripts »