Ora che abbiamo caricato il nostro modello, aggiungiamo una serie di puntatori ai suoi bones, uno per ciascuna parte "mobile" del carro armato (ruote anteriori e posteriori, torretta, cannone, ecc.):
ModelBone leftBackWheelBone;
ModelBone rightBackWheelBone;
ModelBone leftFrontWheelBone;
ModelBone rightFrontWheelBone;
ModelBone leftSteerBone;
ModelBone rightSteerBone;
ModelBone turretBone;
ModelBone cannonBone;
ModelBone hatchBone;
Salviamo anche la loro trasformazione originale, in modo da poterla ripristinare o usare come punto di partenza per le animazioni:
Matrix leftBackWheelTransform;
Matrix rightBackWheelTransform;
Matrix leftFrontWheelTransform;
Matrix rightFrontWheelTransform;
Matrix leftSteerTransform;
Matrix rightSteerTransform;
Matrix turretTransform;
Matrix cannonTransform;
Matrix hatchTransform;
Salviamo adesso ciascun bone del modello nella rispettiva variabile, sia per tenere il codice ordinato che per evitare di appesantire inutilmente il carico computazionale della nostra GPU (a causa dell'accesso con stringhe):
leftBackWheelBone = tankModel.Bones["l_back_wheel_geo"];
rightBackWheelBone = tankModel.Bones["r_back_wheel_geo"];
leftFrontWheelBone = tankModel.Bones["l_front_wheel_geo"];
rightFrontWheelBone = tankModel.Bones["r_front_wheel_geo"];
leftSteerBone = tankModel.Bones["l_steer_geo"];
rightSteerBone = tankModel.Bones["r_steer_geo"];
turretBone = tankModel.Bones["turret_geo"];
cannonBone = tankModel.Bones["canon_geo"];
hatchBone = tankModel.Bones["hatch_geo"];
Infine, memorizziamo la posizione iniziale delle varie parti del nostro modello "a riposo" (le trasformazioni iniziali). Per far questo useremo la proprietà ModelBone.Transform
, che indica la posizione e la rotazione di ciascun bone relativamente al nodo gerarchicamente superiore (ad esempio la posizione del cannone rispetto alla torretta):
leftBackWheelTransform = leftBackWheelBone.Transform;
rightBackWheelTransform = rightBackWheelBone.Transform;
leftFrontWheelTransform = leftFrontWheelBone.Transform;
rightFrontWheelTransform = rightFrontWheelBone.Transform;
leftSteerTransform = leftSteerBone.Transform;
rightSteerTransform = rightSteerBone.Transform;
turretTransform = turretBone.Transform;
cannonTransform = cannonBone.Transform;
hatchTransform = hatchBone.Transform;
Adesso nel metodo Update possiamo impostare le nostre matrici di trasformazione e assegnarle ai nostri bones, in modo da ruotare o traslare le mesh corrispondenti. Per prima cosa, creiamo delle rotazioni cicliche basate sul trascorrere del tempo per muovere, rispettivamente, le ruote, la torretta, il cannone e il portello superiore:
var time = gameTime.TotalGameTime.TotalSeconds;
float WheelRotationValue = time * 5;
float SteerRotationValue = (float)Math.Sin(time * 0.75f) * 0.5f;
float TurretRotationValue = (float)Math.Sin(time * 0.333f) * 1.25f;
float CannonRotationValue = (float)Math.Sin(time * 0.25f) * 0.333f - 0.333f;
float HatchRotationValue = MathHelper.Clamp((float)Math.Sin(time * 2) * 2, -1, 0);
Poi usiamo questi valori per impostare le relative trasformazioni:
Matrix wheelRotation = Matrix.CreateRotationX(WheelRotationValue);
Matrix steerRotation = Matrix.CreateRotationY(SteerRotationValue);
Matrix turretRotation = Matrix.CreateRotationY(TurretRotationValue);
Matrix cannonRotation = Matrix.CreateRotationX(CannonRotationValue);
Matrix hatchRotation = Matrix.CreateRotationX(HatchRotationValue);
Infine, applichiamo le matrici di trasformazione al relativo bone impostando la proprietà Transform di ciascun ModelBone, sommando la nuova trasformazione a quella iniziale:
leftBackWheelBone.Transform = wheelRotation * leftBackWheelTransform;
rightBackWheelBone.Transform = wheelRotation * rightBackWheelTransform;
leftFrontWheelBone.Transform = wheelRotation * leftFrontWheelTransform;
rightFrontWheelBone.Transform = wheelRotation * rightFrontWheelTransform;
leftSteerBone.Transform = steerRotation * leftSteerTransform;
rightSteerBone.Transform = steerRotation * rightSteerTransform;
turretBone.Transform = turretRotation * turretTransform;
cannonBone.Transform = cannonRotation * cannonTransform;
hatchBone.Transform = hatchRotation * hatchTransform;
Giunti a questo punto, nel metodo Draw
impostiamo la matrice World per posizionare il modello nel mondo, la matrice View per posizionare e orientare la telecamera, nonché la matrice Projection per definire il view frustum.
Dal momento che abbiamo di fronte un modello composito, formato da numerosi bones, applichiamo la matrice World alla radice (root) della nostra gerachia, per poi "propagare" tale trasformazione ad ogni nodo della gerarchia (in pratica, ripercorriamo l'albero gerarchico e accumuliamo le trasformazioni di ogni nodo in modo ricorsivo). Il primo passaggio è piuttosto semplice:
tankModel.Root.Transform = world;
Il secondo passaggio richiede uno sforzo in più. Per ripercorrere il nostro albero, accumulando le trasformazioni di ogni nodo, è necessario innanzitutto creare un array di tipo Matrix composto da tanti elementi quanti sono i bones del nostro modello:
Matrix[] boneTransforms = new Matrix[tankModel.Bones.Count];
Adesso copiamo nel nostro array la posizione e la rotazione di ciascun bone utilizzando il metodo CopyAbsoluteBoneTransformsTo
esposto dalla classe ModelBone. Questo metodo "scorre" lungo l'albero gerarchico, prende le trasformazioni di ciascun bone, che come si è detto sono relative al nodo gerarchicamente superiore, e le rende relative alla root del modello (immagazzinandole nel nostro array):
tankModel.CopyAbsoluteBoneTransformsTo(boneTransforms);
Ora possiamo disegnare il nostro modello, non più utilizzando il semplice metodo Model.Draw, ma iterando tutte le meshes del modello e disegnandone una per una:
foreach (ModelMesh mesh in tankModel.Meshes)
Per ciascuna meshpart, applichiamo il relativo effetto:
foreach (BasicEffect effect in mesh.Effects)