Come creare una Trackball mobile e un View Cube per orientarsi nell'ambiente 3D

#unity #3d

Alessandro Oddo
In un mio precedente articolo, ho scritto un tutorial su come Integrare una Trackball in Unity. Uno degli usi che si può fare di questa camera è la gestione di progetti di tipo CAD dove spesso è necessario avere sempre idea dell'orientamento degli assi. L'articolo completo potete leggerlo sul nostro blog https://www.netfarm.it/blog/blog-netfarm-10/post/trackball-in-unity-72
Partendo quindi dal progetto precedente, vedremo come rendere la camera trackball mobile sul piano XZ (su Unity ricordo che gli assi Y e Z sono invertiti) e dopo vedremo come implementare un View Cube che mostrerà la direzione degli assi in alto a destra del nostro progetto.

Movimento sul piano XZ della trackball

Lo script CameraManager prende al momento come input il tasto destro del mouse, quello che andremo a fare è Aggiungere altre due interazioni:
- Con il tasto sinistro vorremmo che la camera si muovesse lungo il piano XZ 
- Con il tasto centrale vorremmo cambiare l'altezza del nostro punto di pivot.
Come primo passaggio andiamo ad aggiungere al nostro scenario un terreno, questo renderà più semplice la fase di test quando la nostra funzione di drag funzionerà. Per fare questo:
- Sulla sinistra dentro 
Hierarchy cliccare con il tasto destro del mouse e dal menu selezionare 3D Object->Terrain



Per aggiungere il drag lungo l'asse XZ andiamo a creare una funzione DragCamera, questa funzione andrà richiamata nell'Update del Camera Manager così da essere chiamata ad ogni cambio di frame. Di seguito il codice che descrive questa funzione:

void Start()
{
dragOrigin = null;
terrainSize = terrain.terrainData.size.x;
}

void Update()
{
DragCamera();
RotateCamera();
}
...

private void DragCamera()
{
if (Input.GetMouseButtonDown(0))
{
dragOrigin = Input.mousePosition;
T0 = Time.timeSinceLevelLoad;
}
else if (Input.GetMouseButtonUp(0))
{
dragOrigin = null;
}

if (dragOrigin == null)
{
return;
}

DragCameraXZPlane();
}

private void DragCameraXZPlane()
{
float exp = (float) Math.Exp(-(Time.timeSinceLevelLoad - T0));
float smoot = (1.0f / (1.0f + exp)) - 0.5f;
Vector3 worldPos = Input.mousePosition - dragOrigin ?? Input.mousePosition;
Vector3 pos = worldPos.normalized;
Vector3 moveDirection = new Vector3();
var forward = transform.forward.normalized;
var right = transform.right.normalized;

moveDirection = new Vector3(right.x * pos.x, 0, right.z * pos.x);
moveDirection += new Vector3(forward.x * pos.y, 0, forward.z * pos.y);
moveDirection *= dragSensibility * smoot;

Vector3 newCamerapos = transform.position + moveDirection;
if (Math.Abs(newCamerapos.x) < terrainSize * 2 && Math.Abs(newCamerapos.z) < terrainSize * 2)
transform.position += moveDirection;
}
 
DragCamera non fa altro che controllare lo stato del mouse ed inizializzare la posizione di partenza da cui stiamo effettuando il drag, T0 è una variabile che verrà utilizzata per dare un effetto smooth al drag. Notare che la funzione che effettua realmente il drag viene chiamata solo se il tasto sinistro del mouse viene premuto.
DragCameraXZPlane controlla la direzione della camera, prende in input l'attuale posizione del mouse e applica un cambio di posizione del punto di pivot, notare che è stato aggiunto un controllo per evitare che la camera vada troppo distante dal terreno.


Movimento sul piano Y della trackball

Per aggiungere il movimento lungo l'asse Y dobbiamo associare il movimento lungo l'asse y al tasto centrale del mouse. Il metodo DragCamera verrà modificato come segue:



private void DragCamera()
{
if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(2))
{
moveVertical = Input.GetMouseButtonDown(2);
dragOrigin = Input.mousePosition;
T0 = Time.timeSinceLevelLoad;
}
if (Input.GetMouseButtonUp(2) || Input.GetMouseButtonUp(0))
{
moveVertical = false;
dragOrigin = null;
}

if (dragOrigin == null)
{
return;
}
if (moveVertical)
{
DragCameraY();
}else
DragCameraXZPlane();
}
 

Il metodo così modificato presenta una nuova variabile booleana appartenente al Camera Controller chiamata moveVertical che permette di capire se il movimento deve essere lungo l'asse verticale o lungo il piano. Il metodo DragCameraY semplicemente muove il pivot lungo l'asse Y come descritto dal seguente codice.


private void DragCameraY()
{
float exp = (float) Math.Exp(-(Time.timeSinceLevelLoad - T0));
float smoot = (1.0f / (1.0f + exp))-0.5f;

Vector3 worldPos = Input.mousePosition - dragOrigin ?? Input.mousePosition;
Vector3 pos = worldPos.normalized;
Vector3 moveDirection = new Vector3(0, pos.y, 0);
moveDirection *= dragSensibility * smoot;
transform.position += moveDirection;
}
 


Implementare lo Zoom

Per implementare lo zoom nella funzione Update dobbiamo aggiungere la chiamata ad un nuovo metodo chiamato ZoomCamera che si occuperà dello zoom tramite un effetto di scaling.

 
void Update()
{
DragCamera();
RotateCamera();
ZoomCamera();
}

...
void ZoomCamera()
{
float scrollFactor = Input.GetAxis("Mouse ScrollWheel");

if (scrollFactor != 0)
{
transform.localScale = transform.localScale * (1f - scrollFactor);
}
}
 


Creare un view Cube

Adesso che abbiamo una camera adatta alla gestione di progetti CAD possiamo occuparci di implementare una feature importante per l'utente finale, il View Cube. Per fare questo dobbiamo:
1. Aggiungere nella 
Hierarchy con il tasto destro un Canvas cliccando su UI->Canvas (come in figura) 
2. Aggiungere con oggetto figlio una Raw Image: per farlo cliccare sul Canvas appena creato quindi premere tasto destro e andare su UI->Raw Image.
La Raw Image così creata la rinominiamo in ViewCubeRaw e agganciamo questo elemento all'angolo in alto a destra del Canvas.

ViewCubeRaw si occuperà di renderizzare il view cube che necessita di una texture associata, quindi creiamone una: 
1. Andiamo dentro la cartella Assets/Resources dalla vista project e quindi cliccare con il tasto 
destro su Create->Render Texture.
2. Rinominiamo il file appena creato in 
ViewCubeRenderTexture e tornando sull'inspector di ViewCubeRaw associamola ad esso trascinandola sul campo texture. 

Terminati questi passaggi si ha la necessità di scrivere sulla texture in modo tale che renderizzi a video il ViewCube.
Sfruttiamo allora l'oggetto Cube creato nel primo tutorial e rinominiamolo View Cubequesto oggetto vogliamo che non venga visualizzato dalla Camera principale e per farlo dobbiamo cambiare il layer a cui appartiene:

1. Dal suo inspector in alto a destra cliccare sul dropdown layer e selezionare Add
Layer, da questa vista aggiungere un nuovo layer di nome ViewCubeLayer.
2. 
Dopo averlo fatto consiglio di controllare che l'oggetto Cube abbia adesso selezionato come layer ViewCubeLayer

Necessitiamo che la camera principale non renderizzi il ViewCube e per farlo si deve selezionare l'oggetto Main Camera e disattivare dall'inspector nella sulla Culling Mask il ViewCubeLayer. Adesso l'oggetto non risulterà più visibile. 

Ora dobbiamo creare un secondo oggetto di tipo Camera che all'opposto della Main Camera renderizzi a video solo il ViewCube.
Creiamo così un oggetto vuoto nella Hierarchy che chiamiamo OrbitViewCube a cui aggiungiamo un oggetto figlio di tipo Camera chiamato CameraViewCube che in questo caso nella sua Culling Mask avrà selezionato solo il ViewCubeLayer. 

Come Target Texture di CameraViewCube dall'inspector selezioniamo la ViewCubeRenderTexture, una volta fatto impostiamo uno sfondo trasparente per la CameraViewCube, per farlo selezionare da Clear Flags l'opzione Solid Color e impostiamo l'alpha del Background su trasparente.

Come ultimo passaggio vogliamo che la CameraViewCube si muovi seguendo la rotazione della MainCamera. Per farlo aggiungiamo un ulteriore parametro al CameraManager chiamato orbitViewCube che si agganci all'omonimo GameObject nella Hierarchy e applichiamo ad esso la stessa rotazione modificando così il codice.


public class CameraManager : MonoBehaviour
{
public GameObject orbitViewCube;
...
private void RotateCamera()
{
if (Input.GetMouseButton(1))
{
float xRotation = rotationSpeed * Input.GetAxis("Mouse X");
float yRotation = rotationSpeed * Input.GetAxis("Mouse Y");
float xAngle = transform.eulerAngles.x % 360f;
float newAngle = ((xAngle + yRotation));

if (Math.Cos(newAngle / 360) <= 0f)
yRotation = 0;

var angle = new Vector3(transform.eulerAngles.x + yRotation, transform.eulerAngles.y +             xRotation, 0);
transform.SetPositionAndRotation(transform.position, Quaternion.Euler(angle.x, angle.y, 0));
orbitViewCube.transform.SetPositionAndRotation(orbitViewCube.transform.position,             Quaternion.Euler(angle.x, angle.y, 0));
}
}

...
}
Il risultato finale permette di avere una camera completa di tutte le funzionalità e di potersi sempre orientare nel mondo 3D grazie al View Cube


Note Finali

Una ultima feature mancante al nostro View Cube che potrebbe essere interessante da approfondire sarebbe l'interazione con esso per ruotare automaticamente la vista, questa ultima feature farà parte dei prossimi tutorial.

Non perdere link Github da cui scaricare l'intero progetto 
https://github.com/netfarm/trackball_viewcube_unity