E' con dispiacere che informo, chi è interessato, chi mi segue, e quei pochi che capitano su queste pagine (forse pure per errore), che non credo aggiungerò altri tutorial/codice/guide su XNA per minimo un mese.
Motivo?
Studio, università, lavori da consegnare, progetti da completare e discutere, problemi vari, e tanto altro ancora!
Se ce la farò vedrò di pubbliacare qualche post di commento/recensione, ma non prometto nulla.
Per chi volesse contattarmi o simili vi rammento che sono su msn e rispondo a questo indirizzo e-mail.
odinoa@hotmail.it
Saluti!
Continua a leggere!
FUUUUUU.... cioè quando non ti resta altro da fare!
Pubblicato da Odino alle venerdì, novembre 13, 2009
Vi è mai capitato di non sapere dove sbattere la testa?
Di avere delle cose da fare ma altre vi si sovrappongono e dovete rimandare?
Oppure solo di provare una forte rabbia per qualcosa che non va per il verso giusto?
Bè ecco come mi sento quando provo cose del genere!
Sinceramente non saprei in qualche altro modo esprimere la rabbia e la frustrazione che alle volte si prova!
E questa immagine rende bene come ci si sente XD
Il soggetto non è mio! Il FUUUU come viene chiamato è nato sulle Chan (se ricordo bene) e avendo passato una giornata pesa, persa a compilare della cavolo di librerie che non ne volevano sapere di partire, quando poi ce l'ho fatta per liberarmi un po' ho fatto questa... cosa (XD) in zBrush.
Circa 40 minuti di lavoro... o meno... bo non lo so (intanto facevo anche altre cavolate).
Se qualcuno lo vuole mi chieda il file e glielo invio per posta elettronica.
Buon FUUUU a tutti!
Meglio un FUUUU che spaccare i mobili di casa XD
Alla prossima!
Continua a leggere!
Di avere delle cose da fare ma altre vi si sovrappongono e dovete rimandare?
Oppure solo di provare una forte rabbia per qualcosa che non va per il verso giusto?
Bè ecco come mi sento quando provo cose del genere!
Sinceramente non saprei in qualche altro modo esprimere la rabbia e la frustrazione che alle volte si prova!
E questa immagine rende bene come ci si sente XD
Il soggetto non è mio! Il FUUUU come viene chiamato è nato sulle Chan (se ricordo bene) e avendo passato una giornata pesa, persa a compilare della cavolo di librerie che non ne volevano sapere di partire, quando poi ce l'ho fatta per liberarmi un po' ho fatto questa... cosa (XD) in zBrush.
Circa 40 minuti di lavoro... o meno... bo non lo so (intanto facevo anche altre cavolate).
Se qualcuno lo vuole mi chieda il file e glielo invio per posta elettronica.
Buon FUUUU a tutti!
Meglio un FUUUU che spaccare i mobili di casa XD
Alla prossima!
Continua a leggere!
The Xna-Way: Tutorial 9: Fractal Generator
Pubblicato da Odino alle martedì, novembre 10, 2009
Come avevo preannunciato nell'ultimo post, ho realizzato un piccolo componente che genera alcuni frattali.
Il tutto è scritto in .Net ed utilizza gli strumenti forniti da XNA per creare e manipolare le immagini usate per la generazione dei frattali.
Il "programma" che illustrerò è abbastanza lunghetto rispetto agli altri, ma vedrò nel possibile di spiegarlo al meglio delle mie capacità.
Come sempre vi esorto a farmi notare se ho detto delle boiate immani!
Nota: nell'ultimo post avevo fatto vedere diverse immagini di frattali realizzate con il codice che andrò ad illustrare, però il codice che vi fornirò adesso potrà generare un solo tipo di frattale, e cioè il frattale di Mandelbrot.
Gli altri non li metterò per ora, anche se il codice è molto simile a quello che userò qua.
Intanto cominciamo col dire come ho pensato il tutto:
>il calcolo che dobbiamo fare è dato dalla successione:
z(n+1) = z(n)^2 + c
dove z è il valore del punto che vogliamo calcolare, e c è una costante che dobbiamo sommare di volta in volta
per z(0) valore sarà c
per z(1) il valore sarà c^2 + c
e così via.
>i punti su cui calcoliamo la successione sono quelli del piano complesso, quindi dovremo fare tutti i calcoli utilizzando l'algebra dei numeri complessi
>il valore di c cambia a seconda di quale punto del piano complesso stiamo calcolando il valore
>un punto appertiene all'insieme di Mandelbrot se Zn è in modulo minore di 2, dove n indica per quante volte dobbiamo ripetere il calcolo prima di considerare il punto in questione appartenente all'insieme. Quindi se dopo un numero i di ripetizioni del calcolo della successione il valore supera 2, il punto non appartiene all'insieme e verrà colorato in modo particolare, mentre se il punto appartiene all'insieme sarà colorato di nero.
NOTA: per la gestione dei calcoli con i numeri complessi ho usato una classe che ho trovato su internet, del quale ho fatto brutalmente copy-paste. Lo che di solito non si fa così, ma non era la parte dui numeri complessi che mi interessava implementare. Sicuramente l'uso di questa classe può aggiungere un po' di tempo di calcolo in più, quindi per velocizzare avrei potuto implementare il calcolo a manina, ma la "complessità" dei numeri complessi (scusate il gioco di parole XD) mi ha tolto la voglia.
Avrò quindi bisono di sapere in quale zona del piano complesso sto andando a calcolare la successione, quindi dovrò definire il limite inferiore e superiore per l'asse reale (considerato l'asse delle ascisse) e l'asse complesso (considerato delle ordinate).
Poi dovrò sapere quanto è lungo il lato delle ascisse e delle ordinate del piano (cioè la differenza tra il limite superiore ed inferiore degli assi), dato che questi valori poi mi servono per calcolare il valore di incremento. Si deve considerare che la texture (che rappresenta il frattale) sarà una "approssimazione" del piano complesso, e dato che non posso avere tanti pixel quanti sono i punti del piano (dato che sarebbero infiniti) devo assegnare ad ogni pixel un punto del piano e calcolare il valore per quel punto, e per fare ciò, quando passo da un pixel al successivo, devo sapere di quando avando/devo incrementare per passare al punto successivo.
Spieghiamo meglio questa cosa dei pixel...
Come forse saprete i punti del piano sono infiniti, mentre per quanto possano essere piccoli e fitti i pixel di un'immagine essi sono in numero ben finito.
Se ipotizzo i pixel dell'immagine come una "discretizzazione" del piano complesso (passatemi questo brutto termine che a molti non piace), allora devo assegnare ad ogni pixel un certo punto del piano, fare i calcoli su quel punto, e colorare il pixel a seconda del risultato ottenuto.
Ora prendiamo il primo pixel dell'immagine, quello che sta in alto a sinistra per interci (perchè nelle immagini l'asse delle X va da sinistra a destra e quello delle Y dall'alto verso il basso), e diamogli come punto quello identificato dall'estremo inferiore per l'asse X e dall'estremo superiore per l'alle Y. Quando poi vogliamo passare al punto successivo (per ora considero lo spostamento solo sull'asse reale, cioè delle X) dovrò incrementare di una certa quantità.
Questa quantità che devo sommare per passare da un punto all'altro la posso calcolare sapendo quanto è lungo il lato reale che sto prendendo in cosiderazione (quindi estremo superiore di X - estremo inferirio di X) e va diviso per la larghezza dell'immagine (in pixel naturalmente). In questo modo per passare da un punto a l'altro si fa una semplice somma.
Notate che la stessa cosa va fatta/calcolta per l'asse delle Y, trovando la quantità di incremento dividendo il lato verticale del piano per il numero di pixel dell'altezza.
Poi ho bisongno del massimo numero di iterazioni da calcolare prima di considerare il punto come appartenente all'insieme. Ricordo che un punto viene considerato parte dell'insieme di Madelbrot e quindi colorato di nero, solo se dopo N iterazioni (con N numero massimo di iterazioni) del calcolo della successione, il valore rispetta ancora la legge.
Naturalmente avrò bisogno anche di altre variabili: per esempio dovrò poter memorizzare il valore di ogni punto del piano di cui sto calcolando la successione, il numero di iterazioni fatte per ogni punto calcolato, e intanto che ci siamo anche per quali indici/pixel/punto devo ancora continuare a calcolare la successione.
Le variabili definite a questo scopo sono:
NOTA: il perchè ho usato una queue (struttura dati che implementa una coda, cioè una "lista" di elementi con estrazione in testa testa ed inserimento in coda) sarà chiaro in seguito.
La classe MandelBrotFractal è figlia di GameComponent, in questo modo potrà avere il suo metodo update e eseguire parte del calcolo ad ogni chiamata del metodo. Inoltre implementerà un'interfaccia da me definita che esporrà i metodi per agire sul frattale.
Il significato mi pare abbastanza esplicito, comunque quando si vedrà come vengono usati i metodi sarà ancora più chiaro.
Il codice completo della classe che genera il MandelBrot è questo:
Show/Hide
Preferisco soffermarmi sul metodo calcolaFrattale() e su quello che fa:
quello che facciamo è controllare quanti elementi sono presenti nella coda degli indici (punti) per cu idevo calcolare il prossimo passo della successione (in questo modo so quante estrazioni fare).
Quindi comincio con estrarre l'elemento in testa alla coda, e calcolati l'indice di riga e di colonna trovo il valore della costante C da usare nel calcolo della successione per quel punto.
Il valore di C cambia a seconda del punto che stiamo considerando.
Poi recuperiamo il valore precedente per Z, e controlliamo se in modulo è minore di 4, o se non abbiamo già eseguito per il quel punto il massimo numero di iterazioni.
Se il punto ha valore in modulo maggiore di 4 non fa parte dell'insieme, quindi va colorato in modo oppurtuno. Se invece ho fatto il massimo numero di iterazioni e il valore del punto è ancora minore di 4 lo considero come appartenente all'insieme di Madelbrot e lo coloro di nero.
Per la colorazione del punto, uso il rapporto tra il numero di iterazioni fatte prima di trovare che il punto diverge e il numero massimo di iterazioni come parametro nella funzione che trasforma un colore in scala HSV in RGB (dove il rapporto è usato come H, cioè Hue, cioè la tonalità del colore).
Cambiando il modo in cui si usa il numero di iterazioni fatte nel calcolo del colore del pixel possono ottenere scale di colore diverse.
Come potete vedere osservando il codice, ogni volta che eseguiamo il metodo calcolaFrattale, viene calcolato un passo per ogni punto che ancora non è stato considerato non appartenente all'insieme. Questo vuol dire che mano a mano che si procede nel calcolo avrò sempre meno punti su cui calcolare la successione, e quindi il calcolo tenderà a diventare più veloce!
NOTA: perchè per ogni esecuzione del metodo calcolo un solo passo? Non posso calcolare tutto il frattale in una volta sola?
Certo si poteva fare anche in quel modo, ma poi non avrei potuto renderizzare lo stato intermedio del calcolo: in parole semplici avrei dovuto aspettare che il calcolo fosse concluso per poter far vedere il frattale, mentre in questo modo lo posso renderizzare anche quando è in fase di costruzione. In questo modo l'attesa per la completazione del frattale sarà più sopportabile ^^
Diamo adesso una piccola spiegazione sull'implementazione del metodo UpdateFractal():
questo metodo non fa altro che aggiornare lo stato del componente con i valori passati, e fa ripartire il cacolo del frattale con i nuovi dati.
Adesso io volevo (ed ho poi fatto) un effetto di transizione tra un'immagine di un fratale e quella successiva, solo che non "sapevo" dove metterla e alla fine l'ho messa in questo metodo.
Analizziamo in dettaglio ciò:
in questo pezzo di codice scorro il vettore che contiene il colore di ogni pixel dell'immagine. All'inizio impostavo tutti i pixel a nero, solo che così il passaggio tra un'immagine e la successiva era troppo netta (almeno per i miei gusti), e la cancellazione istantanea dell'immagine mi dava noia.
Quindi ho deciso di scurire tutta l'immagine facendo una semplice interpolazione tra il colore attuale de pixel ed il colore nero. Successivamente, quando il calcolo del frattale verrà riavviato, si andrà a lavorare di nuovo sulla stessa texture, colorando in modo diverso i pixel. Si avrà quindi un effetto di transizione tra un'immagine e l'altra.
Spero di essere stato chiaro fino ad ora, dato che adesso viene la parte più incasinata, e cioè gestire ed agire sul frattale tramite gli strumenti offerti da XNA.
Quello che voglio fare è riuscire, con il mouse, a selezionare una zona del frattale su cui fare zoom, quindi modificare gli estremi della zona del piano su cui stiamo calcolando la successione.
Voglio poi poter incrementare e drecrementare il numero massimo di iterazioni da fare come la risoluzione della texture su cui calcolo il frattale (più pixel ho più punti dovrò considerare, quindi avrò una maggiore precisione).
Invece di stare a schiaffarvi qui il codice dell'applicazione ve lo lascio la leggere direttamente dal sorgente, dato che la cosa che mi ero proposto di fare era spiegarvi come avevo implementanto il calcolo del frattale.
Però il codice sarà abbastanza commentanto, in questo modo non dovreste avere difficoltà!
Ecco il link al file
XNA-Mandelbrot.rar
Spero di essere stato il più chiaro possibile...
in caso non lo sia stato (cosa più che probabile XD) non esitate a chidere chiarimenti, a dirmi dove ho sbagliato, o anche a mandarmi a quel paese se volete!
Ciauz! Alla prossima da Odino!
Continua a leggere!
Il tutto è scritto in .Net ed utilizza gli strumenti forniti da XNA per creare e manipolare le immagini usate per la generazione dei frattali.
Il "programma" che illustrerò è abbastanza lunghetto rispetto agli altri, ma vedrò nel possibile di spiegarlo al meglio delle mie capacità.
Come sempre vi esorto a farmi notare se ho detto delle boiate immani!
Nota: nell'ultimo post avevo fatto vedere diverse immagini di frattali realizzate con il codice che andrò ad illustrare, però il codice che vi fornirò adesso potrà generare un solo tipo di frattale, e cioè il frattale di Mandelbrot.
Gli altri non li metterò per ora, anche se il codice è molto simile a quello che userò qua.
Intanto cominciamo col dire come ho pensato il tutto:
>il calcolo che dobbiamo fare è dato dalla successione:
z(n+1) = z(n)^2 + c
dove z è il valore del punto che vogliamo calcolare, e c è una costante che dobbiamo sommare di volta in volta
per z(0) valore sarà c
per z(1) il valore sarà c^2 + c
e così via.
>i punti su cui calcoliamo la successione sono quelli del piano complesso, quindi dovremo fare tutti i calcoli utilizzando l'algebra dei numeri complessi
>il valore di c cambia a seconda di quale punto del piano complesso stiamo calcolando il valore
>un punto appertiene all'insieme di Mandelbrot se Zn è in modulo minore di 2, dove n indica per quante volte dobbiamo ripetere il calcolo prima di considerare il punto in questione appartenente all'insieme. Quindi se dopo un numero i di ripetizioni del calcolo della successione il valore supera 2, il punto non appartiene all'insieme e verrà colorato in modo particolare, mentre se il punto appartiene all'insieme sarà colorato di nero.
NOTA: per la gestione dei calcoli con i numeri complessi ho usato una classe che ho trovato su internet, del quale ho fatto brutalmente copy-paste. Lo che di solito non si fa così, ma non era la parte dui numeri complessi che mi interessava implementare. Sicuramente l'uso di questa classe può aggiungere un po' di tempo di calcolo in più, quindi per velocizzare avrei potuto implementare il calcolo a manina, ma la "complessità" dei numeri complessi (scusate il gioco di parole XD) mi ha tolto la voglia.
Avrò quindi bisono di sapere in quale zona del piano complesso sto andando a calcolare la successione, quindi dovrò definire il limite inferiore e superiore per l'asse reale (considerato l'asse delle ascisse) e l'asse complesso (considerato delle ordinate).
Poi dovrò sapere quanto è lungo il lato delle ascisse e delle ordinate del piano (cioè la differenza tra il limite superiore ed inferiore degli assi), dato che questi valori poi mi servono per calcolare il valore di incremento. Si deve considerare che la texture (che rappresenta il frattale) sarà una "approssimazione" del piano complesso, e dato che non posso avere tanti pixel quanti sono i punti del piano (dato che sarebbero infiniti) devo assegnare ad ogni pixel un punto del piano e calcolare il valore per quel punto, e per fare ciò, quando passo da un pixel al successivo, devo sapere di quando avando/devo incrementare per passare al punto successivo.
Spieghiamo meglio questa cosa dei pixel...
Come forse saprete i punti del piano sono infiniti, mentre per quanto possano essere piccoli e fitti i pixel di un'immagine essi sono in numero ben finito.
Se ipotizzo i pixel dell'immagine come una "discretizzazione" del piano complesso (passatemi questo brutto termine che a molti non piace), allora devo assegnare ad ogni pixel un certo punto del piano, fare i calcoli su quel punto, e colorare il pixel a seconda del risultato ottenuto.
Ora prendiamo il primo pixel dell'immagine, quello che sta in alto a sinistra per interci (perchè nelle immagini l'asse delle X va da sinistra a destra e quello delle Y dall'alto verso il basso), e diamogli come punto quello identificato dall'estremo inferiore per l'asse X e dall'estremo superiore per l'alle Y. Quando poi vogliamo passare al punto successivo (per ora considero lo spostamento solo sull'asse reale, cioè delle X) dovrò incrementare di una certa quantità.
Questa quantità che devo sommare per passare da un punto all'altro la posso calcolare sapendo quanto è lungo il lato reale che sto prendendo in cosiderazione (quindi estremo superiore di X - estremo inferirio di X) e va diviso per la larghezza dell'immagine (in pixel naturalmente). In questo modo per passare da un punto a l'altro si fa una semplice somma.
Notate che la stessa cosa va fatta/calcolta per l'asse delle Y, trovando la quantità di incremento dividendo il lato verticale del piano per il numero di pixel dell'altezza.
Poi ho bisongno del massimo numero di iterazioni da calcolare prima di considerare il punto come appartenente all'insieme. Ricordo che un punto viene considerato parte dell'insieme di Madelbrot e quindi colorato di nero, solo se dopo N iterazioni (con N numero massimo di iterazioni) del calcolo della successione, il valore rispetta ancora la legge.
Naturalmente avrò bisogno anche di altre variabili: per esempio dovrò poter memorizzare il valore di ogni punto del piano di cui sto calcolando la successione, il numero di iterazioni fatte per ogni punto calcolato, e intanto che ci siamo anche per quali indici/pixel/punto devo ancora continuare a calcolare la successione.
Le variabili definite a questo scopo sono:
protected double infx = -2, supx = 2, infy = -2, supy = 2; //limiti del piano complesso su cui calcoare la successione
protected double latox, latoy, incx, incy; //lunghezza dei lati e di quando devo incrementare il valore
//da un pixel e l'altro
protected int maxIteration = 250; //numero massimo di iterazione
protected Texture2D fractal; //texture
protected bool complete = true; //indica se il calcolo è completo o no
protected int width, height; //altezza e larghezza della texture
protected Color[] pixData; //vettore contenente il colore dei pixel della texture
protected int total; // numero totale dei pixel della texture
protected Queue<int> indici = new Queue<int>(); //indice del pixel che devo calcolare
protected int[,] iterazioni; //numero di iterazioni per ogni punto
protected Cmplx[,] valori; //valore di ogni punto
protected Cmplx zCm = new Cmplx(), cCm = new Cmplx(); //numeri complessi per il calcolo
NOTA: il perchè ho usato una queue (struttura dati che implementa una coda, cioè una "lista" di elementi con estrazione in testa testa ed inserimento in coda) sarà chiaro in seguito.
La classe MandelBrotFractal è figlia di GameComponent, in questo modo potrà avere il suo metodo update e eseguire parte del calcolo ad ogni chiamata del metodo. Inoltre implementerà un'interfaccia da me definita che esporrà i metodi per agire sul frattale.
public interface FractalI
{
Texture2D Fractal { get; }
void resizeTexture(int width, int height);
bool Complete { get; }
int Width { get; }
int Heigth { get; }
void UpdateFractalDouble(double infx, double supx, double infy, double supy, int MAX);
}
Il significato mi pare abbastanza esplicito, comunque quando si vedrà come vengono usati i metodi sarà ancora più chiaro.
Il codice completo della classe che genera il MandelBrot è questo:
Show/Hide
public class MandelBrotFractal : Microsoft.Xna.Framework.GameComponent, FractalI
{
protected double infx = -2, supx = 2, infy = -2, supy = 2; //limiti del piano complesso su cui calcoare la successione
protected double latox, latoy, incx, incy; //lunghezza dei lati e di quando devo incrementare il valore
//da un pixel e l'altro
protected int maxIteration = 250; //numero massimo di iterazione
protected Texture2D fractal; //texture
protected bool complete = true; //indica se il calcolo è completo o no
protected int width, height; //altezza e larghezza della texture
protected Color[] pixData; //vettore contenente il colore dei pixel della texture
protected int total; // numero totale dei pixel della texture
protected Queue<int> indici = new Queue<int>(); //indice del pixel che devo calcolare
protected int[,] iterazioni; //numero di iterazioni per ogni punto
protected Cmplx[,] valori; //valore di ogni punto
protected Cmplx zCm = new Cmplx(), cCm = new Cmplx(); //numeri complessi per il calcolo
public MandelBrotFractal(Game game, double infx, double supx, double infy, double supy, int maxInt)
: base(game)
{
//prendo i valori della larghezza e altezza della texture
width = this.Game.GraphicsDevice.PresentationParameters.BackBufferWidth;
height = this.Game.GraphicsDevice.PresentationParameters.BackBufferHeight;
//numero totale di pixel
total = width * height;
pixData = new Color[total]; //creo l'array per i colori dei pixel
//creo la texture
fractal = new Texture2D(this.Game.GraphicsDevice,
width,
height, 1,
TextureUsage.None, SurfaceFormat.Color);
//creo la matrice che conterrà il numero di iterazioni fatte per ogni punto
iterazioni = new int[height, width];
//creo la matrice di numeri complessi che conterrà il valore di ogni punto del piano che sto calcolando
//e di volta in volta il calcolo parziale
valori = new Cmplx[height, width];
for (int i = 0; i < height; i++)
for (int j = 0; j < width; j++)
valori[i, j] = new Cmplx();
}
public override void Initialize()
{
base.Initialize();
}
public override void Update(GameTime gameTime)
{
if (!complete)
{
//se il frattale è da completare eseguo il calcolo
calcolaFrattale();
}
base.Update(gameTime);
}
protected virtual Cmplx calcola(Cmplx z, Cmplx c)
{
//calcolo il valore
Cmplx ris = z.sqr().add(c);
return ris;
}
protected Cmplx ris;
private void calcolaFrattale()
{
//variabili usate per inizializzare c
double cre, cim;
int indice = 0;
//memorizzo quanti indici devo calcolare
int count = indici.Count;
for(int i = 0; i < count; i++) //faccio count estrazioni dalla coda
{
indice = indici.Dequeue();
//calcolo il corrispettivo indice di riga e di colonna
int r = indice / width;
int c = indice % width;
//calcolo il valore di c da usare (potrei memorizzarlo in una matrice?)
cre = infx + (c * incx);
cim = infy + (r * incy);
//recupero il valore di z
zCm = valori[r, c];
//controllo che il valore sia in modulo minore di 4 e che non abbia già fatto il max num di iterazioni
if (zCm.modsq() <= 4 && iterazioni[r, c] < maxIteration)
{
cCm.set(cre, cim);
ris = calcola(zCm, cCm); //eseguo il calcolo
valori[r, c] = ris; //aggiorno
iterazioni[r, c]++; //aggiorno
indici.Enqueue(indice); //reinserisco l'indice nella coda per il prossimo calcolo
}
else
{
if (iterazioni[r, c] == maxIteration)
{
//il punto fa parte dell'insieme: lo coloro di nero
pixData[indice] = Color.Black;
}
else
{
//il punto non fa parte dell'insieme
float val = (float)iterazioni[r, c] / maxIteration;
int h, s, v;
HsvToRgb(360.0f * val, 1.0f, 1.0f, out h, out s, out v);
//lo coloro in modo opportuno
pixData[indice] = new Color((Byte)h, (Byte)s, (Byte)v);
//Nota: cambiando come si calcola il colore posso ottenere scale di colore diverse
}
}
}
try
{
fractal.SetData(pixData);//aggiorno la texture
}
catch (Exception e)
{ }
if (indici.Count == 0)
{
//se la coda risulta vuota vuol dire che ho finito di calcolare
this.Game.Window.Title = "Render complete";
complete = true;
}
}
private Color makeColor(float r, float g, float b)
{
System.Drawing.Color color = System.Drawing.Color.FromArgb((int)(r), (int)(g), (int)(b));
return new Color(color.R, color.G, color.B);
}
//qui c'è la funzione per il passaggio dal HSV a RGB
//guardate i sorgenti
int Clamp(int i)
{
if (i < 0) return 0;
if (i > 255) return 255;
return i;
}
#region FracalI Membri di
public Texture2D Fractal
{
get { return fractal; }
}
/// <summary>
/// aggiorna il calcolo del frattale dando gli estremi del piano su cui calcolare e il massimo numero di iterazioni
/// </summary>
/// <param name="infx"></param>
/// <param name="supx"></param>
/// <param name="infy"></param>
/// <param name="supy"></param>
/// <param name="maxIt"></param>
public virtual void UpdateFractalDouble(double infx, double supx, double infy, double supy, int maxIt)
{
this.infx = infx;
this.supx = supx;
this.infy = infy;
this.supy = supy;
this.maxIteration = maxIt;
latox = Math.Abs(supx - infx);
latoy = Math.Abs(supy - infy);
incx = latox / width;
incy = latoy / height;
this.Game.Window.Title = "Render WIP";
complete = false;
indici.Clear();
for (int i = 0; i < total; i++)
{
int r = i / width;
int c = i % width;
indici.Enqueue(i);
iterazioni[r, c] = 0;
valori[r, c].set(0, 0);
pixData[i] = Color.Lerp(pixData[i], Color.Black, 0.7f);
}
fractal.SetData(pixData);
}
public bool Complete
{
get { return complete; }
}
public int Width
{
get { return width; }
}
public int Heigth
{
get { return height; }
}
/// <summary>
/// ridimenziona la texture su cui andiamo a calcolare il frattale
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
public void resizeTexture(int width, int height)
{
//aggiorna i valori
this.width = width;
this.height = height;
total = width * height;
//ricrea il vettore dei colori
pixData = new Color[total];
//elimina la vecchia texture e la ricrea
fractal.Dispose();
fractal = new Texture2D(this.Game.GraphicsDevice,
width,
height, 1,
TextureUsage.None, SurfaceFormat.Color);
//ricrea la matrice con il numero di iterazioni fatte per ogni punto
iterazioni = new int[height, width];
//ricrea la matrice di numeri complessi per memorizzare il valore di ogni punto
valori = new Cmplx[width, height];
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
valori[i, j] = new Cmplx();
//forzo l'aggiornamento
UpdateFractalDouble(infx, supx, infy, supy, maxIteration);
}
#endregion
}
Preferisco soffermarmi sul metodo calcolaFrattale() e su quello che fa:
quello che facciamo è controllare quanti elementi sono presenti nella coda degli indici (punti) per cu idevo calcolare il prossimo passo della successione (in questo modo so quante estrazioni fare).
Quindi comincio con estrarre l'elemento in testa alla coda, e calcolati l'indice di riga e di colonna trovo il valore della costante C da usare nel calcolo della successione per quel punto.
Il valore di C cambia a seconda del punto che stiamo considerando.
Poi recuperiamo il valore precedente per Z, e controlliamo se in modulo è minore di 4, o se non abbiamo già eseguito per il quel punto il massimo numero di iterazioni.
Se il punto ha valore in modulo maggiore di 4 non fa parte dell'insieme, quindi va colorato in modo oppurtuno. Se invece ho fatto il massimo numero di iterazioni e il valore del punto è ancora minore di 4 lo considero come appartenente all'insieme di Madelbrot e lo coloro di nero.
Per la colorazione del punto, uso il rapporto tra il numero di iterazioni fatte prima di trovare che il punto diverge e il numero massimo di iterazioni come parametro nella funzione che trasforma un colore in scala HSV in RGB (dove il rapporto è usato come H, cioè Hue, cioè la tonalità del colore).
Cambiando il modo in cui si usa il numero di iterazioni fatte nel calcolo del colore del pixel possono ottenere scale di colore diverse.
Come potete vedere osservando il codice, ogni volta che eseguiamo il metodo calcolaFrattale, viene calcolato un passo per ogni punto che ancora non è stato considerato non appartenente all'insieme. Questo vuol dire che mano a mano che si procede nel calcolo avrò sempre meno punti su cui calcolare la successione, e quindi il calcolo tenderà a diventare più veloce!
NOTA: perchè per ogni esecuzione del metodo calcolo un solo passo? Non posso calcolare tutto il frattale in una volta sola?
Certo si poteva fare anche in quel modo, ma poi non avrei potuto renderizzare lo stato intermedio del calcolo: in parole semplici avrei dovuto aspettare che il calcolo fosse concluso per poter far vedere il frattale, mentre in questo modo lo posso renderizzare anche quando è in fase di costruzione. In questo modo l'attesa per la completazione del frattale sarà più sopportabile ^^
Diamo adesso una piccola spiegazione sull'implementazione del metodo UpdateFractal():
questo metodo non fa altro che aggiornare lo stato del componente con i valori passati, e fa ripartire il cacolo del frattale con i nuovi dati.
Adesso io volevo (ed ho poi fatto) un effetto di transizione tra un'immagine di un fratale e quella successiva, solo che non "sapevo" dove metterla e alla fine l'ho messa in questo metodo.
Analizziamo in dettaglio ciò:
for (int i = 0; i < total; i++)
{
int r = i / width;
int c = i % width;
indici.Enqueue(i);
iterazioni[r, c] = 0;
valori[r, c].set(0, 0);
pixData[i] = Color.Lerp(pixData[i], Color.Black, 0.7f);
}
in questo pezzo di codice scorro il vettore che contiene il colore di ogni pixel dell'immagine. All'inizio impostavo tutti i pixel a nero, solo che così il passaggio tra un'immagine e la successiva era troppo netta (almeno per i miei gusti), e la cancellazione istantanea dell'immagine mi dava noia.
Quindi ho deciso di scurire tutta l'immagine facendo una semplice interpolazione tra il colore attuale de pixel ed il colore nero. Successivamente, quando il calcolo del frattale verrà riavviato, si andrà a lavorare di nuovo sulla stessa texture, colorando in modo diverso i pixel. Si avrà quindi un effetto di transizione tra un'immagine e l'altra.
Spero di essere stato chiaro fino ad ora, dato che adesso viene la parte più incasinata, e cioè gestire ed agire sul frattale tramite gli strumenti offerti da XNA.
Quello che voglio fare è riuscire, con il mouse, a selezionare una zona del frattale su cui fare zoom, quindi modificare gli estremi della zona del piano su cui stiamo calcolando la successione.
Voglio poi poter incrementare e drecrementare il numero massimo di iterazioni da fare come la risoluzione della texture su cui calcolo il frattale (più pixel ho più punti dovrò considerare, quindi avrò una maggiore precisione).
Invece di stare a schiaffarvi qui il codice dell'applicazione ve lo lascio la leggere direttamente dal sorgente, dato che la cosa che mi ero proposto di fare era spiegarvi come avevo implementanto il calcolo del frattale.
Però il codice sarà abbastanza commentanto, in questo modo non dovreste avere difficoltà!
Ecco il link al file
XNA-Mandelbrot.rar
Spero di essere stato il più chiaro possibile...
in caso non lo sia stato (cosa più che probabile XD) non esitate a chidere chiarimenti, a dirmi dove ho sbagliato, o anche a mandarmi a quel paese se volete!
Ciauz! Alla prossima da Odino!
Continua a leggere!
XNA - Frattali
Pubblicato da Odino alle lunedì, ottobre 26, 2009
Tutto ciò è colpa di un mio professore dell'università...
E' lui che ha fatto accadere tutto ciò ?_?
Non ve la prendete con me ?_?
Seguendo il corso di Costruzione di Interfacce il nostro professore ci ha fatto vedere un'applicazione che fatto lui: si tratta di un programma che permette di calcolare e visualizzare il frattale di Mandelbrot.
Il frattale di MandelBrot è uno dei più famosi frattali (lo so dire ora, ma prima che ce lo facesse vedere lui manco sapevo chi fosse XD), e devo dire che con i frattali ci si può "divertire". Tutto ciò mi ha fatto tornare alla mente quando, da piccolo, mio padre mi fece vedere un programma per calcolare queste strane immagini, dove io potevo selezionare una parte piccola piccola, e l'immagine non "perdeva di dettaglio"... Ah beata innocenza XD
Sta di fatto che dopo aver visto questo programma fatto dal nostro professore mi sono deciso a farne uno mio. Dato che sto lavorando con XNA ho deciso di farne uno che utilizzasse XNA per calcolare e visualizzare i frattali.
Ci sto ancora lavorando, e devo implementare alcuni particolari, ma direi che il grosso è fatto.
Quindi se vi chiedete che fine ho fatto, o sono dentro un frattale(XD) o sto studiando per un esame (che voglia eh XD).
Intanto vi lascio con qualche immagine ottenuta da questo generatore di frattali che ho fatto ^_^
Spero vi piacciano :)
Quando sarà finito posterò qualcosa del codice che ho fatto!
A presto :)
Continua a leggere!
E' lui che ha fatto accadere tutto ciò ?_?
Non ve la prendete con me ?_?
Seguendo il corso di Costruzione di Interfacce il nostro professore ci ha fatto vedere un'applicazione che fatto lui: si tratta di un programma che permette di calcolare e visualizzare il frattale di Mandelbrot.
Il frattale di MandelBrot è uno dei più famosi frattali (lo so dire ora, ma prima che ce lo facesse vedere lui manco sapevo chi fosse XD), e devo dire che con i frattali ci si può "divertire". Tutto ciò mi ha fatto tornare alla mente quando, da piccolo, mio padre mi fece vedere un programma per calcolare queste strane immagini, dove io potevo selezionare una parte piccola piccola, e l'immagine non "perdeva di dettaglio"... Ah beata innocenza XD
Sta di fatto che dopo aver visto questo programma fatto dal nostro professore mi sono deciso a farne uno mio. Dato che sto lavorando con XNA ho deciso di farne uno che utilizzasse XNA per calcolare e visualizzare i frattali.
Ci sto ancora lavorando, e devo implementare alcuni particolari, ma direi che il grosso è fatto.
Quindi se vi chiedete che fine ho fatto, o sono dentro un frattale(XD) o sto studiando per un esame (che voglia eh XD).
Intanto vi lascio con qualche immagine ottenuta da questo generatore di frattali che ho fatto ^_^
Spero vi piacciano :)
Quando sarà finito posterò qualcosa del codice che ho fatto!
A presto :)
Continua a leggere!
The Xna-Way: Tutorial 8: Screen Shots del nostro gioco!
Pubblicato da Odino alle giovedì, ottobre 22, 2009
Aggiornamento al 16/04/2011:
con l'aggiornamento alla versione 4 di Xna questo codice non è più funzionante, dato che alcune delle classi ed oggetti che vengono utilizzate sono state eliminate o è stato rivisto il loro funzionamento.
Quale è una cosa di cui oggi come oggi non si può fare a meno in un videogame?
Bè una delle tante è la possiibilità di salvare la schermata di gioco, di farne una foto e di conservarla, in modo da mostrare ai nostri cari amici (nerd anche loro come noi) le nostre peripezie XD
Questa cosa oltre ad essere una chicca che oramai cerchiamo in quasi ogni gioco mi tornerà utile anche per catturare schermate di gioco per i piccoli tutorial che farò.
Mi sono quindi messo di buzzo buono, e studiando degli esempi che ho trovato su Xna-club, esempi che tra l'altro trattavano effetti di post processing come bloom, e ho tirato fuori il mio piccolo componente.
L'idea su cui mi sono basato è questa: XNA ci fornisce un ciclo di esecuzione nel quale vengono invocati in continuazione i metodi Update e Draw dei components che fanno parte del nostro gioco.
Ora uno screen shot va salvato quando la scena è stata completamtente renderizzata. Quindi, ragionevolmente, dovremmo salvarla dopo che sono stati eseguiti tutti i metodi Draw dei nostri componenti.
Ma come possiamo essere sicuri o controllare questa cosa?
La cosa più semplice ed immediata da fare è capire che la scena che noi vogliamo salvare, è "completa" quando siamo nel metodo update. Cioè saremo "indietro" di un frame quando andremo a salvare l'immagine, gli oggetti non saranno stati spostati, ma nel contempo il rendering della scena non sarà ancora incominciato, e quindi avremo (da qualche parte in memoria) il render completo della scena al frame precedente!
Quello che dobbiamo fare è semplicemtente recuperarlo e salvarlo.
Ecco la classe, il component, che fa questo:
Show/Hide
Al costruttore gli viene passato il percorso della directory in cui salvare le immagini, oppure solo il riferimento alla Game, ed in questo secondo caso la directory di default per il salvataggio delle immagini è la cartella immagini dell'utente.
Come si vede si recupera anche il riferimento al servizio per l'input.
In Initialize creo un ResolveTexture2D delle stesse dimensioni e formato del backBuffer del gioco.
Nel metodo update controlliamo se il tasto Stamp (o printScree, dipende da come lo si vuole chiamare) è stato premuto.
In caso positivo tramite ResolveBackBuffer recupero il backBuffer del frame precedente (e quindi il render completo della scena precente) e lo metto in resolveTarget.
Dato che resolve target è un tipo particolare di Texture2D posso forzare con un cast il suo tipo ad essere Texture2D appunto, ed usare quindi il metodo save per salvare su disco l'immagine.
Tutto qua. Così possiamo salvare semplicemente gli screen del nostro gioco.
Nel costruttore di Game1 si deve aggiungere le seguenti righe:
ed siamo veramente alla fine!
Non allego questa volta il file con il codice, dato che la classe è piccola e completamente inserita nel post.
Spero vi sia utile.
Alla prossima!
Continua a leggere!
con l'aggiornamento alla versione 4 di Xna questo codice non è più funzionante, dato che alcune delle classi ed oggetti che vengono utilizzate sono state eliminate o è stato rivisto il loro funzionamento.
Quale è una cosa di cui oggi come oggi non si può fare a meno in un videogame?
Bè una delle tante è la possiibilità di salvare la schermata di gioco, di farne una foto e di conservarla, in modo da mostrare ai nostri cari amici (nerd anche loro come noi) le nostre peripezie XD
Questa cosa oltre ad essere una chicca che oramai cerchiamo in quasi ogni gioco mi tornerà utile anche per catturare schermate di gioco per i piccoli tutorial che farò.
Mi sono quindi messo di buzzo buono, e studiando degli esempi che ho trovato su Xna-club, esempi che tra l'altro trattavano effetti di post processing come bloom, e ho tirato fuori il mio piccolo componente.
L'idea su cui mi sono basato è questa: XNA ci fornisce un ciclo di esecuzione nel quale vengono invocati in continuazione i metodi Update e Draw dei components che fanno parte del nostro gioco.
Ora uno screen shot va salvato quando la scena è stata completamtente renderizzata. Quindi, ragionevolmente, dovremmo salvarla dopo che sono stati eseguiti tutti i metodi Draw dei nostri componenti.
Ma come possiamo essere sicuri o controllare questa cosa?
La cosa più semplice ed immediata da fare è capire che la scena che noi vogliamo salvare, è "completa" quando siamo nel metodo update. Cioè saremo "indietro" di un frame quando andremo a salvare l'immagine, gli oggetti non saranno stati spostati, ma nel contempo il rendering della scena non sarà ancora incominciato, e quindi avremo (da qualche parte in memoria) il render completo della scena al frame precedente!
Quello che dobbiamo fare è semplicemtente recuperarlo e salvarlo.
Ecco la classe, il component, che fa questo:
Show/Hide
public class ScreenShots : Microsoft.Xna.Framework.GameComponent
{
ResolveTexture2D resolveTarget;
string path;
InputI inputDevice;
public ScreenShots(Game game)
: this(game, System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyPictures))
{
}
public ScreenShots(Game game, string path)
: base(game)
{
this.path = path;
inputDevice = (InputI)game.Services.GetService(typeof(InputI));
}
public override void Initialize()
{
PresentationParameters pp = Game.GraphicsDevice.PresentationParameters;
SurfaceFormat format = pp.BackBufferFormat;
resolveTarget = new ResolveTexture2D(Game.GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, 1,
format);
base.Initialize();
}
public override void Update(GameTime gameTime)
{
if (inputDevice.wasPressed(Keys.PrintScreen))
{
Game.GraphicsDevice.ResolveBackBuffer(resolveTarget);
DateTime time = DateTime.Now;
try
{
((Texture2D)resolveTarget).Save(path + @"\screen" + time.Ticks.ToString() + ".jpg", ImageFileFormat.Jpg);
}
catch (Exception e)
{
}
}
base.Update(gameTime);
}
Al costruttore gli viene passato il percorso della directory in cui salvare le immagini, oppure solo il riferimento alla Game, ed in questo secondo caso la directory di default per il salvataggio delle immagini è la cartella immagini dell'utente.
Come si vede si recupera anche il riferimento al servizio per l'input.
In Initialize creo un ResolveTexture2D delle stesse dimensioni e formato del backBuffer del gioco.
Nel metodo update controlliamo se il tasto Stamp (o printScree, dipende da come lo si vuole chiamare) è stato premuto.
In caso positivo tramite ResolveBackBuffer recupero il backBuffer del frame precedente (e quindi il render completo della scena precente) e lo metto in resolveTarget.
Dato che resolve target è un tipo particolare di Texture2D posso forzare con un cast il suo tipo ad essere Texture2D appunto, ed usare quindi il metodo save per salvare su disco l'immagine.
Tutto qua. Così possiamo salvare semplicemente gli screen del nostro gioco.
Nel costruttore di Game1 si deve aggiungere le seguenti righe:
screenShot = new ScreenShots(this);
Components.Add(screenShot);
ed siamo veramente alla fine!
Non allego questa volta il file con il codice, dato che la classe è piccola e completamente inserita nel post.
Spero vi sia utile.
Alla prossima!
Continua a leggere!
The Xna-Way: Tutorial 7: Collisioni con BoundingSphere
Pubblicato da Odino alle sabato, ottobre 17, 2009
Dopo molto, troppo, tempo eccomi di nuovo qua a parlare di qualcosa inerente ad XNA.
Questa volta voglio un po' parla delle collisioni, cosa che fa impazzire ogni programmatore di Video Giochi.
Anche io sono impazzito (più di quanto non fossi prima) quando mi sono addentrato in questo campo, e devo dire che ne sono rimasto un po' spaventato.
Come si possono calcolare oggi come oggi le collisioni tra 2 oggetti?
Bè un esempio di ciò l'ho usato per calcolare quali oggetti erano intersecati dal bounding Frustum della telecamera e quindi filtrare quelli da renderizzare da quelli che non lo erano.
Questo è un esempio di collisioni fatto con i VOLUMI: ho usato un sfera (per la precisione una BoundingSphere) per approssimare il volume degli oggetti presenti nella scena (ci dovrà essere una sfera per ogni oggetto, o nel caso di oggetti replicati una sola sfera che verrà riposizionata e aggiornata per ogni oggetto di cui voglio calcolare la collisione).
Come qualcuno potrà pensare, calcolare le collisione di 2 oggetti approssimando il loro volume con delle sfere può portare a delle approssimazioni tali da avere un grosso grado di imprecisione del calcolare l'effettiva collisione tra due oggetti.
Se il nostro oggetto è una sfera allora la sua BS (abbreviazione di BoundigSphere) coinciderà esattamente con l'oggetto, ma già nel caso di un cubo esso verrà contenuto per intero all'interno della sfera, così con oggetti più complessi!
Ecco un esempio di quando ho detto:
Come possiamo vedere la navicella e il meteorite sono completamente contenuti all'interno delle loro sfere, anche se buona parte delle sfere non sono occupate dal volume del modello.
Come si fa per calcolare le collissioni? La collisione viene calcolata controllando se le due sfere si toccano o si intersecano l'una con l'altra. Ho una collisione anche se una sfera è contenuta interamente in un'altra.
XNA ci offre la classe BoundingSphere la quale è caratterizzata da una posizione e da un raggio. Le BS sono comode perchè anche se muoviamo o ruotiamo l'oggetto esso sarà sempre contenuto all'interno della stessa sfera, cosa che non accade con i BoundingBox (altro tipo di volumi per approssimazione), i quali sono dei parallelepipedi. Nel caso di una rotazione dobbiamo ricalcolare il rettangolo che contiene il nostro modello.
L'unico caso in cui dobbiamo modificare la sfera è quando scaliamo il nostro oggetto, dato che dovremo anche aumentare/diminuire il raggio della sfera.
In tutto questo tempo ho cercato a giro metodi "semplici" per il calcolo della collisione tra due oggetti nel modo più preciso possibile, cioè del tipo poligono contro poligono, vertice su poligono, etc, ma come potete immagianare con scarsi risultati.
Ho provato a farne uno di mio, ma non funzionava bene ed il calcolo era talmente peso che già per modelli a basso numero di poligoni il frame rate si abbassava enormemente.
Ho decico quindi per ora di dedicarmi a qualcosa di meno complicato e di continuare a scrivere qualcosa. Poi quando avrò maggiori conoscenze continuerò in seguito ad approfondire questo argomento.
Quello che mi sono proposto di fare è quanto segue: alla creazione del modello ho deciso di estrarre dalle informazioni la boundingSphere caricata, e di tenerla aggiornata secondo le trasformazioni dell'oggetto. Esporrò poi un metodo per permettere di controllare se due dati modelli stanno collidendo con le boundingSphere. Forse non è proprio il caso di utilizzare una classe, ma utilizzare un'interfaccia che esponga queste proprietà comuni agli oggetti di cui vogliamo controllare le collisioni.
Quello che ho fatto è stato definire la seguente interfaccia:
Questa interfaccia contiene una proprietà e un metodo: la proprietà ritorna la boundingSphere relativa all'oggetto, mentre il metodo controlla se per due oggetti che implementano l'interfaccia ci sia o meno collisione.
Da notare che in questo modo non vincolo il tipo degli oggetti tra cui posso calcolare la collisione, basta che implementino l'interfaccia e posso calcoare tutto.
Certo è un poco spoglia, ma nessuno ci vieta in futuro, quando ne avremo bisogno, di aggiungere nuove funzionalità a questa interfaccia (aggiornando poi naturamente le classi che le implementano).
Ho deciso di utilizzare un'interfaccia anche per dare libera scelta di implementare in modo diverso le funzionalità da una classe all'altra.
Vediamo le modifiche fatte nella classe Ship:
Codice da aggiungere a livello di classe->
Codice da aggiungere nel costruttore->
Codice da aggiungere nel metodo update (in fondo prima di base.Update(gameTime);)->
OK!
In questo modo abbiamo aggiunto nella classe i campi necessari a gestire le boundingSphere, abbiamo implementato i metodi richiesti.
Nel costruttore creaiamo la boundingSphere come la sfera più grande che può contente tutte le boundingSphere di ogni singola parte del nostro modello (per ora non mi interessa di calcolare possibili sottocollisioni con le singole parti del modello, mi accontento di un calcolo più grossolano, se volete specializzare fate voi, io mi per ora mi accontento di capire il concetto ^^).
Mi salvo anche il raggio iniziale delle sfera.
Nel metodo update riposiziono la sfera in base alla posizione del modello e riaggiorno il raggio (nel caso sia stato scalato il modello).
Come faccio a disegnare la sfera e a farla visualizzare?
Ho usato una classe statica trovata qua:
Rendering Bounding Spheres
Passando al metodo render la sfera (più altri parametri) essa sarà renderizzata per noi. Certo è un po' pesante quando si deve disegnare molte sfere, quindi non esageriamo! (o se proprio volete renderizzare tutte le sfere vi consiglio di abbassare la risoluzione delle stesse: nel metodo render della classe statica andate a cambiare il valore 30 presente nella chiamata a InitializeGraphics da a 0, le sfere saranno leggermente più spigolose ma ne guadagnarete in prestazioni!).
Quindi nel metodo draw della nostra classe Ship, dopo che abbiamo disegnato il modello aggiungete la seguente riga:
BoundingSphereRenderer.Render(sphere, this.Game.GraphicsDevice, myCamera.View, myCamera.Projection, Color.Yellow);
Ora se lanciate il gioco dovreste vedere una sfera approssimata da due cerchi, di colore giallo.
Ora c'è da fare delle modifiche alla classe MeteorManager:
Codice da aggiungere a livello di classe->
Cosa fa il metodo CheckSPhereCollision?
Prende l'indice della cella in cui siamo, scorre le celle adiacenti a noi, e controlla (riposizionando la sfera e riscalandola) se c'è una collisione con uno degli elementi. Se ne trova uno con cui ho collisione ritorna true, altrimenti se dopo aver controllato tutti gli elementi possibili non ho trovato nessuna collisione ritorno false.
Il costruttore va modificato con le stesse righe aggiunte nel costruttore di Ship.
Il metodo update non va cambiato, dato che per ora i nostri meteoriti devono star fermi.
Andiamo al metodo draw: questa riga va aggiunta nel ramo più interno, all'interno del IF che fa la verifica se la sfera è o meno intersecata dal frustum della telecamera->
BoundingSphereRenderer.Render(sphere, this.Game.GraphicsDevice, myCamera.View, myCamera.Projection, Color.Purple);
Da notare che ora la sphere a cui faccio riferimento non è più quella creata localmente nel Draw, ma quella a livello di classe. In questo modo non andremo a creare un nuovo oggetto ogni volta che viene eseguito il metodo Draw, con un miglioramento dell'utilizzo della memoria direi (su pc non si noterà, ma credo che sulla 360 forse si...).
Per rendere le cose un po' più "carine" ho deciso di fare ciò:
- nel metodo draw del MeteorManager ho diminuito la variabile val a 5
- nel LoadContent di Game1 ho portato il numero di meteoriti per cella da 5 a 3 (per alleggerire il calcolo)
-nella classe Ship ho aggiunto:
public Color colore = Color.Yellow;
-> nel metodo Update di Game1 ho aggiunto:
In questo modo quando si rileva una collisione il colore della sfera della nostra nave diverrà rosso, mentre tornerà giallo quando non collidiamo con nulla!
Dovrebbe essere tutto! :D
Se qualcosa non va c'è sempre il file allegato!
E se trovate qualche errore fatemelo notare così correggo ^__^
XNA-tut7.rar
Argomenti trattati:
> Collisioni con BoundingSphere
Se leggete o scaricate i sorgenti lasciate un commento plz, almeno mi rendo conto di come vanno le cose ^^
Alla prossima!
Continua a leggere!
Questa volta voglio un po' parla delle collisioni, cosa che fa impazzire ogni programmatore di Video Giochi.
Anche io sono impazzito (più di quanto non fossi prima) quando mi sono addentrato in questo campo, e devo dire che ne sono rimasto un po' spaventato.
Come si possono calcolare oggi come oggi le collisioni tra 2 oggetti?
Bè un esempio di ciò l'ho usato per calcolare quali oggetti erano intersecati dal bounding Frustum della telecamera e quindi filtrare quelli da renderizzare da quelli che non lo erano.
Questo è un esempio di collisioni fatto con i VOLUMI: ho usato un sfera (per la precisione una BoundingSphere) per approssimare il volume degli oggetti presenti nella scena (ci dovrà essere una sfera per ogni oggetto, o nel caso di oggetti replicati una sola sfera che verrà riposizionata e aggiornata per ogni oggetto di cui voglio calcolare la collisione).
Come qualcuno potrà pensare, calcolare le collisione di 2 oggetti approssimando il loro volume con delle sfere può portare a delle approssimazioni tali da avere un grosso grado di imprecisione del calcolare l'effettiva collisione tra due oggetti.
Se il nostro oggetto è una sfera allora la sua BS (abbreviazione di BoundigSphere) coinciderà esattamente con l'oggetto, ma già nel caso di un cubo esso verrà contenuto per intero all'interno della sfera, così con oggetti più complessi!
Ecco un esempio di quando ho detto:
Come possiamo vedere la navicella e il meteorite sono completamente contenuti all'interno delle loro sfere, anche se buona parte delle sfere non sono occupate dal volume del modello.
Come si fa per calcolare le collissioni? La collisione viene calcolata controllando se le due sfere si toccano o si intersecano l'una con l'altra. Ho una collisione anche se una sfera è contenuta interamente in un'altra.
XNA ci offre la classe BoundingSphere la quale è caratterizzata da una posizione e da un raggio. Le BS sono comode perchè anche se muoviamo o ruotiamo l'oggetto esso sarà sempre contenuto all'interno della stessa sfera, cosa che non accade con i BoundingBox (altro tipo di volumi per approssimazione), i quali sono dei parallelepipedi. Nel caso di una rotazione dobbiamo ricalcolare il rettangolo che contiene il nostro modello.
L'unico caso in cui dobbiamo modificare la sfera è quando scaliamo il nostro oggetto, dato che dovremo anche aumentare/diminuire il raggio della sfera.
In tutto questo tempo ho cercato a giro metodi "semplici" per il calcolo della collisione tra due oggetti nel modo più preciso possibile, cioè del tipo poligono contro poligono, vertice su poligono, etc, ma come potete immagianare con scarsi risultati.
Ho provato a farne uno di mio, ma non funzionava bene ed il calcolo era talmente peso che già per modelli a basso numero di poligoni il frame rate si abbassava enormemente.
Ho decico quindi per ora di dedicarmi a qualcosa di meno complicato e di continuare a scrivere qualcosa. Poi quando avrò maggiori conoscenze continuerò in seguito ad approfondire questo argomento.
Quello che mi sono proposto di fare è quanto segue: alla creazione del modello ho deciso di estrarre dalle informazioni la boundingSphere caricata, e di tenerla aggiornata secondo le trasformazioni dell'oggetto. Esporrò poi un metodo per permettere di controllare se due dati modelli stanno collidendo con le boundingSphere. Forse non è proprio il caso di utilizzare una classe, ma utilizzare un'interfaccia che esponga queste proprietà comuni agli oggetti di cui vogliamo controllare le collisioni.
Quello che ho fatto è stato definire la seguente interfaccia:
public interface CollisionI
{
BoundingSphere _BoundingSphere { get; }
bool CheckSPhereCollision(CollisionI obj);
}
Questa interfaccia contiene una proprietà e un metodo: la proprietà ritorna la boundingSphere relativa all'oggetto, mentre il metodo controlla se per due oggetti che implementano l'interfaccia ci sia o meno collisione.
Da notare che in questo modo non vincolo il tipo degli oggetti tra cui posso calcolare la collisione, basta che implementino l'interfaccia e posso calcoare tutto.
Certo è un poco spoglia, ma nessuno ci vieta in futuro, quando ne avremo bisogno, di aggiungere nuove funzionalità a questa interfaccia (aggiornando poi naturamente le classi che le implementano).
Ho deciso di utilizzare un'interfaccia anche per dare libera scelta di implementare in modo diverso le funzionalità da una classe all'altra.
Vediamo le modifiche fatte nella classe Ship:
Codice da aggiungere a livello di classe->
//dati per le collisioni
BoundingSphere sphere;
float radius;
public Color colore = Color.Yellow;
public BoundingSphere _BoundingSphere
{
get { return sphere; }
}
public bool CheckSPhereCollision(CollisionI obj)
{
return sphere.Intersects(obj._BoundingSphere);
}
Codice da aggiungere nel costruttore->
sphere = new BoundingSphere();
foreach (ModelMesh mesh in this.myModel.Meshes)
{
foreach (ModelMeshPart mp in mesh.MeshParts)
mp.Effect = currentEffect;
sphere = BoundingSphere.CreateMerged(sphere, mesh.BoundingSphere);
}
radius = sphere.Radius;
Codice da aggiungere nel metodo update (in fondo prima di base.Update(gameTime);)->
sphere.Center = position;
sphere.Radius = radius * scale;
OK!
In questo modo abbiamo aggiunto nella classe i campi necessari a gestire le boundingSphere, abbiamo implementato i metodi richiesti.
Nel costruttore creaiamo la boundingSphere come la sfera più grande che può contente tutte le boundingSphere di ogni singola parte del nostro modello (per ora non mi interessa di calcolare possibili sottocollisioni con le singole parti del modello, mi accontento di un calcolo più grossolano, se volete specializzare fate voi, io mi per ora mi accontento di capire il concetto ^^).
Mi salvo anche il raggio iniziale delle sfera.
Nel metodo update riposiziono la sfera in base alla posizione del modello e riaggiorno il raggio (nel caso sia stato scalato il modello).
Come faccio a disegnare la sfera e a farla visualizzare?
Ho usato una classe statica trovata qua:
Rendering Bounding Spheres
Passando al metodo render la sfera (più altri parametri) essa sarà renderizzata per noi. Certo è un po' pesante quando si deve disegnare molte sfere, quindi non esageriamo! (o se proprio volete renderizzare tutte le sfere vi consiglio di abbassare la risoluzione delle stesse: nel metodo render della classe statica andate a cambiare il valore 30 presente nella chiamata a InitializeGraphics da a 0, le sfere saranno leggermente più spigolose ma ne guadagnarete in prestazioni!).
Quindi nel metodo draw della nostra classe Ship, dopo che abbiamo disegnato il modello aggiungete la seguente riga:
BoundingSphereRenderer.Render(sphere, this.Game.GraphicsDevice, myCamera.View, myCamera.Projection, Color.Yellow);
Ora se lanciate il gioco dovreste vedere una sfera approssimata da due cerchi, di colore giallo.
Ora c'è da fare delle modifiche alla classe MeteorManager:
Codice da aggiungere a livello di classe->
BoundingSphere sphere;
float radius;
public BoundingSphere _BoundingSphere
{
get { throw new NotImplementedException(); }
}
public bool CheckSPhereCollision(CollisionI obj)
{
int val = 1;
for (int i = (cameraR - val); i <= this.cameraR + val; i++)
{
for (int j = (this.cameraC - val); j <= this.cameraC + val; j++)
{
for (int k = (this.cameraP - val); k <= this.cameraP + val; k++)
{
int r = i % cells;
int c = j % cells;
int p = k % cells;
if (r < 0)
r = cells + r;
if (c < 0)
c = cells + c;
if (p < 0)
p = cells + p;
foreach (Trasformation tras in gameWorld[r % cells, c % cells, p % cells].myList)
{
sphere.Center = tras.position;
sphere.Radius = radius * tras.scale;
if (sphere.Intersects(obj._BoundingSphere))
return true;
}
}
}
}
return false;
}
Cosa fa il metodo CheckSPhereCollision?
Prende l'indice della cella in cui siamo, scorre le celle adiacenti a noi, e controlla (riposizionando la sfera e riscalandola) se c'è una collisione con uno degli elementi. Se ne trova uno con cui ho collisione ritorna true, altrimenti se dopo aver controllato tutti gli elementi possibili non ho trovato nessuna collisione ritorno false.
Il costruttore va modificato con le stesse righe aggiunte nel costruttore di Ship.
Il metodo update non va cambiato, dato che per ora i nostri meteoriti devono star fermi.
Andiamo al metodo draw: questa riga va aggiunta nel ramo più interno, all'interno del IF che fa la verifica se la sfera è o meno intersecata dal frustum della telecamera->
BoundingSphereRenderer.Render(sphere, this.Game.GraphicsDevice, myCamera.View, myCamera.Projection, Color.Purple);
Da notare che ora la sphere a cui faccio riferimento non è più quella creata localmente nel Draw, ma quella a livello di classe. In questo modo non andremo a creare un nuovo oggetto ogni volta che viene eseguito il metodo Draw, con un miglioramento dell'utilizzo della memoria direi (su pc non si noterà, ma credo che sulla 360 forse si...).
Per rendere le cose un po' più "carine" ho deciso di fare ciò:
- nel metodo draw del MeteorManager ho diminuito la variabile val a 5
- nel LoadContent di Game1 ho portato il numero di meteoriti per cella da 5 a 3 (per alleggerire il calcolo)
-nella classe Ship ho aggiunto:
public Color colore = Color.Yellow;
-> nel metodo Update di Game1 ho aggiunto:
if (mman.CheckSPhereCollision(myShip))
myShip.colore = Color.Red;
else
myShip.colore = Color.Yellow;
In questo modo quando si rileva una collisione il colore della sfera della nostra nave diverrà rosso, mentre tornerà giallo quando non collidiamo con nulla!
Dovrebbe essere tutto! :D
Se qualcosa non va c'è sempre il file allegato!
E se trovate qualche errore fatemelo notare così correggo ^__^
XNA-tut7.rar
Argomenti trattati:
> Collisioni con BoundingSphere
Se leggete o scaricate i sorgenti lasciate un commento plz, almeno mi rendo conto di come vanno le cose ^^
Alla prossima!
Continua a leggere!
The Xna-Way: Tutorial 6: Orientare oggetto nella direzione in cui si muove e telecamera che lo insegue
Pubblicato da Odino alle venerdì, settembre 11, 2009
Questa volta il compito che mi sono proposto è stato un po' più difficile:
avendo creato un decente MeteorManager questa volta volevo muovermi attraverso questo fitto campo di meteoriti con una navicella spaziale.
Sono partito col pensare a ciò:
- per ora non mi interessa calcolare le collisioni con i meteoriti
- la telecamera sarà in 3° persona, quindi ci troveremo alle spalle della nostra astronave (altrimenti avevamo già la telecamera in prima persona per muoverci)
- la telecamera dovrà seguire il nostro vascello spaziale
- dovremo far muovere nello spazio il nostro mezzo di trasporto
tutto questo ci porta a dover modificare la nostra telecamera per implementare "l'inseguimento" della nostra astronave. Avremo quella che chiamo Follow Camera.
Anche per il nostro vascello avremo il nostro bel da fare: non dovremmo solo farlo muovere e ruotare nel mondo 3D, ma dovremo farlo muovere nella direzione in cui "guarda", quindi movimenti e rotazioni dovranno essere fatti in modo particolare lavorando sulle direzioni, cosa un po' complicata e astrusa ma fattibile.
Per prima cosa è meglio fare la modifica alla classe Camera in modo da implementare l'inseguimento di un determinato bersaglio. In questo modo quando poi andremo a far muovere il nostro oggetto nello spazio la telecamera gli starà dietro e noi non avremo problemi ad osservarlo ^_^
Preso il progetto dell'ultimo post, andiamo nella classe Camesa.cs e sotto il metodo UpdateFreeCamera() andiamo a mettere il seguente metodo:
Per non avere errori dovremo aggiungere le seguenti variabili di classe:
Cosa fa il metodo?
Il metodo crea una matrice identità e ne imposta i valori per le direzioni che rappresentano il davanti, l'alto e la destra.
Direction e up da dove vengono? Sono dati e assegnati tramite l'oggetto che vogliamo inseguire: cioè direction rappresenta la direzione in cui si sta muoveno il nostro oggetto, mentre up è la direzione che il nostro bersaglio considera come l'alto. In questo modo potremmo mantenere costati le rotazioni!
La destra viene calcolata come prodotto tra i due vettori.
Dopo trasformo l'offest usando la matrice, e sommo questo offest alla posizione del punto osservato e assegno il risultato alla posizione.
Quello che manca da fare ora è la classe per gestire la nostra navicella spaziale.
Ecco il file Ship.cs, comunque presente nell'archivio allegato:
Ship.cs Show/Hide
Il file va inserito nel progetto Meteor e non nella libreria, dato che, come il MeteorManager, questo oggetto è specifico per questa applicazione.
Il pezzo saliente è il metodo Update, il resto è roba già vista!
Cosa fa questo benedetto metodo?
Prima di tutto riazzera il valore della rotazione per yaw e pitch, e anche val.
Val indica se la barra spaziatrice è premuta o meno, e quindi se dobbiamo muoverci o no.
Con la pressione delle frecce direzionali imposto i valori per le rotazioni, e con R riporto il tutto allo stato iniziale.
Con
faccio in modo che se mi trovo a testa in giù e premo verso destra, continuo a girare ancora verso destra non verso sinitra.
Dopo di chè calcolola matrice di rotazione (nota: l'angolo per pitch è calcolato passando l'asse di rotazione), e uso tale matrice per trasformare la direzione e l'up.
Ho poi il calcolo della forza applicata all'oggeto.
Sono formule fisiche. Forza = massa * accellerazione.
So che la formula per calcolare la velocità è sbagliata (anche se quella giusta è riportata come commentata). Ma ora come ora non mi interessa la correttezza delle fisica.
Alla fine poi ho la creazione della world matrix per la nostra navicella, fatta in modo molto simile a come abbiamo costruito quella per la telecamera.
Cosa manca da fare per far funzionare il tutto?
Aggiungere il file per il modello 3D e la texture al progetto, creare la nostra astronave, aggiungerla ai components e disegnarla.
Poi dobbiamo modificare leggermente le impostazioni della telecamera.
Cominciamo: nel costruttore di Game1.cs modifichiamo il codice per la telecamera come segue
Impostiamo la distanza dal punto di osservazione e cambiamo il tipo di telecamera.
Nel metodo LoadContent() aggiungiamo questo:
Dove myShip è l'oggetto per la nostra astronave, che dobbiamo aver definito nella classe. Lo scalo ad un fattore 0.1 perchè altrimenti sarebbe troppo grande.
Nel metodo Draw dopo il render per il meteorManager aggiungiamo questo:
myShip.Draw(gameTime);
E nel metodo Update questo:
In modo che se la telecamera è di tipo follow, si aggiorna il suo target alla posizione della nostra navicella (ma potrebbe essere benissimo qualsiasi cosa!), e settiamo up e direction con i valori provenienti dal nostro oggetto myShip.
Dovrebbe essere tutto! :D
Se qualcosa non va c'è sempre il file allegato!
E se trovate qualche errore fatemelo notare così correggo ^__^
XNA-tut6.rar
Argomenti trattati:
> muovere un oggetto lungo la direzione che sta osservando
> telecamera che insegue il nostro oggetto
Alla prossima!
Continua a leggere!
avendo creato un decente MeteorManager questa volta volevo muovermi attraverso questo fitto campo di meteoriti con una navicella spaziale.
Sono partito col pensare a ciò:
- per ora non mi interessa calcolare le collisioni con i meteoriti
- la telecamera sarà in 3° persona, quindi ci troveremo alle spalle della nostra astronave (altrimenti avevamo già la telecamera in prima persona per muoverci)
- la telecamera dovrà seguire il nostro vascello spaziale
- dovremo far muovere nello spazio il nostro mezzo di trasporto
tutto questo ci porta a dover modificare la nostra telecamera per implementare "l'inseguimento" della nostra astronave. Avremo quella che chiamo Follow Camera.
Anche per il nostro vascello avremo il nostro bel da fare: non dovremmo solo farlo muovere e ruotare nel mondo 3D, ma dovremo farlo muovere nella direzione in cui "guarda", quindi movimenti e rotazioni dovranno essere fatti in modo particolare lavorando sulle direzioni, cosa un po' complicata e astrusa ma fattibile.
Per prima cosa è meglio fare la modifica alla classe Camera in modo da implementare l'inseguimento di un determinato bersaglio. In questo modo quando poi andremo a far muovere il nostro oggetto nello spazio la telecamera gli starà dietro e noi non avremo problemi ad osservarlo ^_^
Preso il progetto dell'ultimo post, andiamo nella classe Camesa.cs e sotto il metodo UpdateFreeCamera() andiamo a mettere il seguente metodo:
private void updateFollowCamera(GameTime gameTime)
{
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
//creo la matrice temporanea
Matrix tmp = Matrix.Identity;
//imposto la direzione di osservazione, la direzione dell'alto e la direzione della destra
tmp.Forward = direction;
tmp.Up = up;
tmp.Right = Vector3.Cross(up, direction); //questa è calcolata come prodotto tra due vettori
Vector3 t;
//trasformo la distanza (offset) dal punto di osservazione con la matrice calcolata
Vector3.Transform(ref offset, ref tmp, out t);
Vector3 positionTmp;
//calcolo la posizione che vogliamo far avere alla nostra telecamera sommando al punto che siamo osservando
//la posizione trasformata lungo la matrice
Vector3.Add(ref target, ref t, out positionTmp);
//aggiorno la posizione finale
position = positionTmp;
}
Per non avere errori dovremo aggiungere le seguenti variabili di classe:
//variabili per l'inseguimento del target
protected Vector3 offset = Vector3.Zero;
protected Vector3 direction = Vector3.Forward
Cosa fa il metodo?
Il metodo crea una matrice identità e ne imposta i valori per le direzioni che rappresentano il davanti, l'alto e la destra.
Direction e up da dove vengono? Sono dati e assegnati tramite l'oggetto che vogliamo inseguire: cioè direction rappresenta la direzione in cui si sta muoveno il nostro oggetto, mentre up è la direzione che il nostro bersaglio considera come l'alto. In questo modo potremmo mantenere costati le rotazioni!
La destra viene calcolata come prodotto tra i due vettori.
Dopo trasformo l'offest usando la matrice, e sommo questo offest alla posizione del punto osservato e assegno il risultato alla posizione.
Quello che manca da fare ora è la classe per gestire la nostra navicella spaziale.
Ecco il file Ship.cs, comunque presente nell'archivio allegato:
Ship.cs Show/Hide
public class Ship : Microsoft.Xna.Framework.GameComponent
{
Model myModel;
Matrix world;
InputI inputDevice;
CameraI myCamera;
//Dati per il rendering
Texture2D text;
Effect currentEffect = null;
public Effect CurrentEffect
{
get { return currentEffect; }
set { currentEffect = value; }
}
//Dati per le trasformazioni
Vector3 position = Vector3.Zero;
public Vector3 Position
{
get { return position; }
set { position = value; }
}
float scale = 1.0f;
public float Scale
{
get { return scale; }
set { scale = value; }
}
//Dati per l'orientamento verso la direzione di movimento
Vector3 destra = Vector3.Right;
Vector3 up = Vector3.Up;
public Vector3 Up
{ get { return up; } }
Vector3 direction = Vector3.Forward;
public Vector3 Direction
{ get { return direction; } }
float pitch, yaw;
float val;
Vector3 velocità;
public Ship(Game game, string myModel, string texture, Effect effect)
: base(game)
{
this.myModel = Game.Content.Load<Model>(myModel);
this.text = Game.Content.Load<Texture2D>(texture);
myCamera = (CameraI)game.Services.GetService(typeof(CameraI));
inputDevice = (InputI)game.Services.GetService(typeof(InputI));
currentEffect = effect;
foreach (ModelMesh mesh in this.myModel.Meshes)
foreach (ModelMeshPart mp in mesh.MeshParts)
mp.Effect = currentEffect;
}
public override void Initialize()
{
base.Initialize();
}
public override void Update(GameTime gameTime)
{
float elaps = (float)gameTime.ElapsedRealTime.TotalSeconds;
yaw = 0;
pitch = 0;
val = 0;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Space))
val = 1.0f;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Right))
yaw -= 3f * elaps;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Left))
yaw += 3f * elaps;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Up))
pitch -= 3f * elaps;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Down))
pitch += 3f * elaps;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.R))
{
yaw = 0.0f;
pitch = 0.0f;
direction = Vector3.Forward;
up = Vector3.Up;
destra = Vector3.Right;
position = Vector3.Zero;
}
//se l'astronave a testa in giù devo invertire il valore di yaw
if (up.Y < 0)
yaw = -yaw;
//calcolo la matrice di rotazione data dagli angoli yaw e pitch
Matrix rotationMatrix = Matrix.CreateFromAxisAngle(destra, pitch) *
Matrix.CreateRotationY(yaw);
//trasformo la direzione e l'alto con la matrice calcolata
Vector3.TransformNormal(ref direction, ref rotationMatrix, out direction);
Vector3.TransformNormal(ref up, ref rotationMatrix, out up);
//normalizzo
direction.Normalize();
up.Normalize();
//calcolo i vettori destra e up come prodotto tra vettori
Vector3.Cross(ref direction, ref up, out destra);
Vector3.Cross(ref destra, ref direction, out up);
//calcolo la forza, l'accellerazione e la velocità
Vector3 forza = direction * val * 2400000;
Vector3 accellerazione = forza / 2 ;
velocità = accellerazione * elaps;
/*
//ricorda di diminuire il fatto moltiplicativo nel cacolo della forza
velocità += accellerazione * elaps;
//per farlo fermare nel caso si usasse il secondo metodo
velocità *= 0.97f;*/
position += velocità * elaps;
//----------------------------------
world = Matrix.Identity;
world.Forward = direction;
world.Up = up;
world.Right = destra;
world *= Matrix.CreateScale(scale);
world *= Matrix.CreateTranslation(position);
base.Update(gameTime);
}
public void Draw(GameTime gameTime)
{
currentEffect.Parameters["Color"].SetValue(Color.White.ToVector4());
currentEffect.Parameters["Projection"].SetValue(myCamera.Projection);
currentEffect.Parameters["View"].SetValue(myCamera.View);
currentEffect.Parameters["ColorMap"].SetValue(text);
currentEffect.Parameters["World"].SetValue(world);
foreach (ModelMesh mesh in myModel.Meshes)
mesh.Draw();
}
}
Il file va inserito nel progetto Meteor e non nella libreria, dato che, come il MeteorManager, questo oggetto è specifico per questa applicazione.
Il pezzo saliente è il metodo Update, il resto è roba già vista!
Cosa fa questo benedetto metodo?
Prima di tutto riazzera il valore della rotazione per yaw e pitch, e anche val.
Val indica se la barra spaziatrice è premuta o meno, e quindi se dobbiamo muoverci o no.
Con la pressione delle frecce direzionali imposto i valori per le rotazioni, e con R riporto il tutto allo stato iniziale.
Con
if (up.Y < 0)
yaw = -yaw;
faccio in modo che se mi trovo a testa in giù e premo verso destra, continuo a girare ancora verso destra non verso sinitra.
Dopo di chè calcolola matrice di rotazione (nota: l'angolo per pitch è calcolato passando l'asse di rotazione), e uso tale matrice per trasformare la direzione e l'up.
Ho poi il calcolo della forza applicata all'oggeto.
Sono formule fisiche. Forza = massa * accellerazione.
So che la formula per calcolare la velocità è sbagliata (anche se quella giusta è riportata come commentata). Ma ora come ora non mi interessa la correttezza delle fisica.
Alla fine poi ho la creazione della world matrix per la nostra navicella, fatta in modo molto simile a come abbiamo costruito quella per la telecamera.
Cosa manca da fare per far funzionare il tutto?
Aggiungere il file per il modello 3D e la texture al progetto, creare la nostra astronave, aggiungerla ai components e disegnarla.
Poi dobbiamo modificare leggermente le impostazioni della telecamera.
Cominciamo: nel costruttore di Game1.cs modifichiamo il codice per la telecamera come segue
myCamera = new Camera(this);
myCamera.FarPlane = 100000;
myCamera.Offset = new Vector3(0, 100, 500);
myCamera.Mode = CameraMode.Follow;
Components.Add(myCamera);
Impostiamo la distanza dal punto di osservazione e cambiamo il tipo di telecamera.
Nel metodo LoadContent() aggiungiamo questo:
myShip = new Ship(this, @"Models\p2_wedge", @"Textures\wedge_p2_diff_v1", defaultEffect);
myShip.Scale = 0.1f;
Components.Add(myShip);
Dove myShip è l'oggetto per la nostra astronave, che dobbiamo aver definito nella classe. Lo scalo ad un fattore 0.1 perchè altrimenti sarebbe troppo grande.
Nel metodo Draw dopo il render per il meteorManager aggiungiamo questo:
myShip.Draw(gameTime);
E nel metodo Update questo:
if (myCamera.Mode == CameraMode.Follow)
{
myCamera.Target = myShip.Position;
myCamera.Up = myShip.Up;
myCamera.Direction = myShip.Direction;
}
In modo che se la telecamera è di tipo follow, si aggiorna il suo target alla posizione della nostra navicella (ma potrebbe essere benissimo qualsiasi cosa!), e settiamo up e direction con i valori provenienti dal nostro oggetto myShip.
Dovrebbe essere tutto! :D
Se qualcosa non va c'è sempre il file allegato!
E se trovate qualche errore fatemelo notare così correggo ^__^
XNA-tut6.rar
Argomenti trattati:
> muovere un oggetto lungo la direzione che sta osservando
> telecamera che insegue il nostro oggetto
Alla prossima!
Continua a leggere!
The Xna-Way: Tutorial 5: Meteor Manager & Free Camera
Pubblicato da Odino alle mercoledì, settembre 09, 2009
Eccomi dopo un po' di tempo con un altro post sull'XNA e un suo utilizzo.
Questa volta mi sono detto: "voglio avere una scena con tanti, ma tanti oggetti!"
Ah, quando il masochismo non ha limiti...
Qualcosa ho comunque fatto, anche se non è proprio un granchè...
Quello che mi è venuto in mente, in modo da poterlo poi riusare anche in futuro per una qualche demo simile ad un Asteroid, è di avere un gestore di meteoriti!
Cioè questo gestore ci deve la possibilità di renderizzare a schermo tanti meteoriti.
E vogliamo pure dare la possibilità di muoversi in questo mondo no?
Quindi dovremo fare delle modifiche alla telecamera, in modo che ci pemetta di spostarci in questo universo popolato unicamente da meteoriti!
Allora per il metorManager creremo un file in un progetto per un gameWindows a se stante, mentre il file della della telecamera che andremo a modifcare è sempre quello della myLibrary.
Prima di tutto vediamo come strutturare questo fantomatico MeteorManager. Io l'ho pensato in questo modo:
-il mondo sarà visto come un cubo, dove all'interno potremo mettere i nostri meteoriti
-il mondo sarà diviso in celle: ad ogni cella sarà assegnata una sezione del mondo, e dentro ogni sezione ci sarà la lista degli elementi che sono contenuti dentro tale sezione
Perchè le sezioni? Perchè se ho un mondo molto grande potrei dover renderizzare tanti troppi elementi a schermo, e questo rallenterebbe di molto il numero di frame per secondo. Dividendo in sezioni il mondo potremmo decidere di renderizzare solo quelle adiacenti a noi.
Inoltre se poi in futuro dovremmo calcolare la collisione di una fantomatica astronave con un meteorite, invece di controllare le collisioni con tutti i meteoriti della scena (cosa veramente masochistica, che poterebbe via tantissimo tempo) potremmo semplicemente farlo con quelli adiacetni alla sezione in cui ci troviamo.
Pensante a quanto tempo occorre scorrere un cubo in cui ogni lato è diviso in 20 sezioni!
Cioè ci sono 20*20*20 sezioni da controllare! Ognuna con più elementi!
Avendo suddiviso un mondo cubico in sezioni ho deciso di usare un vettore a 3 dimensioni per gestire il gameWorld.
MeteorManager.cs Show/Hide
Diamo una spiegazione di quello che ho fatto nei vari punti!
Quello che fa il costruttore è questo:
assegna al currentEffect l'effetto passato (gli effect sono usati per caricare e gestire codice HLSL, che serve per renderizzare gli elementi della scena, ottenere effetti particolari etc; in questo esempio ho usato un mio piccolo effect che non fa altro che prendere la texture ed applicarla all'oggetto da renderizzare, senza calcolare nessuna luce, questo perchè è più veloce e semplice del basic Effect standard, e qui voglio andare ad ottenere più FPS possibili. Il file è allegato nella soluzione, ma qui non darò spiegazione di come funziona).
Poi carica il modello e la texture passate. Assegna il valore alle variabili che mi dicono quanto è grande il mondo.
Si recupera poi i riferimenti ai servizi della telecamera e del gestore dell'input.
GameWorld è la matrice cubica che andrà a contenere il mondo: ogni cella rappresenta una sezione, e ogni sezione avrò la sua lista di elementi.
Il metodo makeWorld, per quanto possa disorientare, non fa altro che creare e posizionare in modo radom num elementi per ogni sezione. A voi il compito di guardarlo o di chiedere qualcosa che non vi è chiaro qui.
Concentriamoci un attimo sul metodo checkCameraPosition invece, che è più curioso secondo me...
Questo metodo prende la posizione della telecamera e memorizza in 3 variabili l'indice di riga, colonna e profondità in cui si trova la telecamera.
Come si fa ad otterere tale risultato?
Prendo la variazione relativa ad ogni cella (cioè quanto spazio sulla dimensione X, per esempio, copre una cella). Poi prendo la posizione X della telecamera, la divido per questa variazione, e a tale risultato ci sommo la metà delle celle. La somma è necessaria perchè voglio che la posizione 0,0,0 sia il centro del mio mondo cubico!
Se non facessi così sarei invece posizionato ad un vertice del mondo.
Il metodo draw adesso, che non sto a riportare perchè è lungotto :O
Allora: nelle prime righe si assegna all'effetto usato per il rendering i valori opportuni, quindi la viewMatrix della telecamera, la projectionMatrix della telecamera, la texture dell'oggetto, e un colore.
Poi cominciamo a scorrere le mesh che compongono il nostro oggetto da renderizzare, in questo caso il nostro meteorite, che dovrà essere replicato tante volte.
Si crea una nuova BoundingSphere con centro e raggio presi dalla BoundingSphere della mesh in esame.
Le BoundingSphere sono usate per calcolare le collisioni tra volumi sferici, ma qui le userò per una cosa diversa ma molto utile ^__^
Poi cominciamo a scorrere le celli adiacenti alla posizione in cui si trova la telecamera: più val è grande maggiore è il numero di celle che considero adiacenti.
Poi riassetto gli indici, in modo che non vadano fuori dai limiti consentiti.
Scorro poi la lista di elementi della sezione data dagli indici r,c,p.
Modifico il centro ed il raggio della BoundingSphere, e calcolo se la sfera o contenuta o interseca il BoundingFrustum della telecamera e solo se lo è renderizzo il meteorite.
Perchè faccio ciò?
Perchè anche se renderizziamo solo le celle vicine a noi, eseguo di solito il draw anche per oggetti che mi sono alle spalle e che io non vedo. Facendo questo sprecherei tempo in rendering inutili, che vengono calcolati, ma il cui risultato non è visualizzato a schermo.
Il frustum della telecamera lo possiamo vedere come un cono che parte dal nostro punto di osservazione, o meglio un tronco di cono, che comincia alla distanza nearPlane e termina a farPlane.
Dato che prima di giungere a questo risultato ho fatto diverse prove, se non usassi il boundingFrustum otterrei con val = 8 qualcosa come soli 5 FPS. Con invece riesco ad arrivare a 70 FPS più o meno stabili! Un buon risultato!
Insomma quello che facciamo è controllare se il volume dato dalla boundingSphere adeguatamente settata è contenuto completamente o in parte nel cono visivo della telecamera, e solo se lo è renderizzo l'oggetto. E' molto più conveniente fare questo controllo che fare un rendering inutile!
E poi perchè usare le BoundingSphere? Perchè è un metodo comodo, semplice ed economico per calcolare il volume di un oggetto, perchè calcolarne il volume esatto controllando i poligoni/vertici è troppo oneroso secondo me (e poi non lo so fare XD).
E come lo calcolo il frustum?
Per questo bisogna andare a modifcare la telecamera!
Ecco il file della telecamera con tutte le modifiche apportate
Camera.cs Show/Hide
Le parti importante da considerare sono:
l'enumeratore CameraMode: questo contiene i vari tipi di telecamera che mi sono venuti in mente. Per ora noi useremo solo Free, ma nulla vieta di implementare anche gli altri o di aggiungerne di nuovi!
Come si può vedere ho anche aggiunto alcuni campi all'interfaccia CameraI.
Aggiunta importate è la CameraBoundingFrustum, che verrà calcolato nel metodo Update.
Ora passiamo al metodo Update: per prima cosa andiamo a controllare il tipo di telecamera, e se è di tipo Free adiamo a chiamare il metodo per aggiornarla.
Poi si calcola la view e la projection matrix ed infine il boundigFrustum, dato dal prodotto delle view per la projection.
Vediamo il metodo updateFreeCamera:
MovementRate e RotationRate sono due variabili definite nella classe e ci dicono quanto velocemente si muove e ruota la nostra telecamera.
A seconda dei tasti che premiamo andiamo a modifcare i valori di Pitch e Yaw (che stanno rispettivamente per l'angolo sull'asse X ed Y), oppure a spostare la posizone della telecamera.
Usando yaw e pitch creo una matrice di rotazione, che uso per trasformare (cioè per spostare lungo la direzione data dalla matrice) la posizione della telecamera.
La stessa cosa la faccio per la posizione del punto che sto osservando, cioè per il target.
Come possiamo vedere in questo metodo non c'è il calcolo per la creazione della view e della projection: qui si aggiorna solo il valore della posizione e del target, la creazione viene fatta nel metodo update.
E questo è tutto.
Ora si deve solo andare in Game1.cs e mettere questo nel metodo Initialize:
Le due variabili vanno naturalmente create prima ^^
Questo ci dice di caricare il file Default.fx, e di creare il MeteorManager che avrà 5 elementi per ogni sezione.
Dato che il MeteorManager è un GameComponent e non un DrawableGameComponent bisogna dirgli noi quando renderizzarlo!
Quindi nel metodo Draw di Game1.cs va messo questo:
mman.Draw(gameTime);
Ma veramente serve tutta questa palla del CameraBoundingFrustum, delle collisioni e tutto il resto?
Fate una prova:
Andate nel file del MeteorManager, nel Draw(..) mettere val = 8 e commentate la riga dove fate il controllo sul risultato del CameraBoundingFrustum.Contains(..).
Io riesco ad ottenere soltanto 5-6 FPS.
Facendo il controllo passo a circa 70.
Voi che dite? Serve?
Per me si ^___^
Poi almeno così si è visto come creare un manager per un insieme di oggetti, e come cominciare a buttare giù una semplice struttura per un gioco... qui il compito è..... contatare tutti gli asteroidi XD Sai che spasso XD
Scherzi a parte, potrebbe comunque essere un'idea di base per la struttura di un semplice gioco!
Ci dovrebbero essere altri metodi per semplificare il rendering di istanze multiple dello stesso elemento, ma ancora non sono in grado di applicarli.
Spero vi sia stato utile tutto questo.
Se ci sono cose che non tornanto o sbagliate o che si possono fare meglio (il che è molto probabile) ditelo please :)
Alla prossima!
Intanto ecco i file allegati!
XNA-tut5.rar
Argomenti trattati:
Free camera
Camera Bounding Frustum Continua a leggere!
Questa volta mi sono detto: "voglio avere una scena con tanti, ma tanti oggetti!"
Ah, quando il masochismo non ha limiti...
Qualcosa ho comunque fatto, anche se non è proprio un granchè...
Quello che mi è venuto in mente, in modo da poterlo poi riusare anche in futuro per una qualche demo simile ad un Asteroid, è di avere un gestore di meteoriti!
Cioè questo gestore ci deve la possibilità di renderizzare a schermo tanti meteoriti.
E vogliamo pure dare la possibilità di muoversi in questo mondo no?
Quindi dovremo fare delle modifiche alla telecamera, in modo che ci pemetta di spostarci in questo universo popolato unicamente da meteoriti!
Allora per il metorManager creremo un file in un progetto per un gameWindows a se stante, mentre il file della della telecamera che andremo a modifcare è sempre quello della myLibrary.
Prima di tutto vediamo come strutturare questo fantomatico MeteorManager. Io l'ho pensato in questo modo:
-il mondo sarà visto come un cubo, dove all'interno potremo mettere i nostri meteoriti
-il mondo sarà diviso in celle: ad ogni cella sarà assegnata una sezione del mondo, e dentro ogni sezione ci sarà la lista degli elementi che sono contenuti dentro tale sezione
Perchè le sezioni? Perchè se ho un mondo molto grande potrei dover renderizzare tanti troppi elementi a schermo, e questo rallenterebbe di molto il numero di frame per secondo. Dividendo in sezioni il mondo potremmo decidere di renderizzare solo quelle adiacenti a noi.
Inoltre se poi in futuro dovremmo calcolare la collisione di una fantomatica astronave con un meteorite, invece di controllare le collisioni con tutti i meteoriti della scena (cosa veramente masochistica, che poterebbe via tantissimo tempo) potremmo semplicemente farlo con quelli adiacetni alla sezione in cui ci troviamo.
Pensante a quanto tempo occorre scorrere un cubo in cui ogni lato è diviso in 20 sezioni!
Cioè ci sono 20*20*20 sezioni da controllare! Ognuna con più elementi!
Avendo suddiviso un mondo cubico in sezioni ho deciso di usare un vettore a 3 dimensioni per gestire il gameWorld.
MeteorManager.cs Show/Hide
public struct Trasformation
{
public Vector3 position;
public float angleX, angleY, angleZ;
public float scale;
public Matrix world;
public void Update()
{
world = Matrix.CreateScale(scale) * Matrix.CreateFromYawPitchRoll(angleY, angleX, angleZ) *
Matrix.CreateTranslation(position);
}
}
public struct Sezione
{
public Vector2 xRange, yRange, zRange;
public List<Trasformation> myList;
}
/// <summary>
/// This is a game component that implements IUpdateable.
/// </summary>
public class MeteorManager : Microsoft.Xna.Framework.GameComponent
{
CameraI myCamera;
InputI inputDevice;
Model myModel;
Texture2D text;
Sezione[, ,] gameWorld;
//servono per dire in quale riga, colonna e profondità si trova la telecamera
int cameraR, cameraC, cameraP;
Effect currentEffect = null;
public Effect CurrentEffect
{
get { return currentEffect; }
set { currentEffect = value; }
}
//variabili per la dimensione del mondo
float xRange, yRange, zRange;
//numero di celle per ogni dimensione
int cells = 20;
public MeteorManager(Game game, Effect effect, int num, float xRange, float yRange, float zRange,
string myModel, string texture)
: base(game)
{
this.currentEffect = effect; //assegno l'effetto usato per il rendering
this.myModel = Game.Content.Load<Model>(myModel); //carico il modello
foreach (ModelMesh mesh in this.myModel.Meshes)
foreach (ModelMeshPart mp in mesh.MeshParts)
mp.Effect = effect; //assegno l'effetto ad ogni parte del modello
this.text = Game.Content.Load<Texture2D>(texture);//carico la texture che voglio usare
//assegno le variabili per la dimensione del mondo
this.xRange = xRange;
this.yRange = yRange;
this.zRange = zRange;
myCamera = (CameraI)game.Services.GetService(typeof(CameraI)); //recupero il servizio per la telecamera
inputDevice = (InputI)game.Services.GetService(typeof(InputI));//e quello per l'input
gameWorld = new Sezione[cells, cells, cells];//creo la matrice del mondo
makeWorld(num); //vado a creare i mondo
}
private void makeWorld(int num)
{
//calcolo quando spazio deve coprire ogni cella
float ix = xRange + xRange;
ix /= cells;
float iy = yRange + yRange;
iy /= cells;
float iz = zRange + zRange;
iz /= cells;
Random rand = new Random();
for (int i = 0; i < cells; i++)
{
for (int j = 0; j < cells; j++)
{
for (int k = 0; k < cells; k++)
{
//per ogni cella vado a inserire quale zona del mondo copre
gameWorld[i, j, k].xRange = new Vector2(i * ix - xRange, (i + 1) * ix - xRange);
gameWorld[i, j, k].yRange = new Vector2(j * iy - yRange, (j + 1) * iy - yRange);
gameWorld[i, j, k].zRange = new Vector2(k * iz - zRange, (k + 1) * iz - zRange);
//vado a creare la lista per la posizione dei meteoriti contenuti nella lista
gameWorld[i, j, k].myList = new List<Trasformation>();
for (int a = 0; a < num; a++)
{
Trasformation t = new Trasformation();
//creo la posizione all'interno della zona associata
t.position.X = rand.Next((int)gameWorld[i, j, k].xRange.X, (int)gameWorld[i, j, k].xRange.Y);
t.position.Y = rand.Next((int)gameWorld[i, j, k].yRange.X, (int)gameWorld[i, j, k].yRange.Y);
t.position.Z = rand.Next((int)gameWorld[i, j, k].zRange.X, (int)gameWorld[i, j, k].zRange.Y);
//angolo casuale
t.angleX = rand.Next(0, 360);
t.angleY = rand.Next(0, 360);
t.angleZ = rand.Next(0, 360);
//scala = 1
t.scale = 1;
//creo la world matrix per quel particolare meteorite
t.world = Matrix.CreateScale(t.scale) *
Matrix.CreateFromYawPitchRoll(t.angleY, t.angleX, t.angleZ) *
Matrix.CreateTranslation(t.position);
gameWorld[i, j, k].myList.Add(t);
}
}
}
}
}
public override void Initialize()
{
base.Initialize();
}
public override void Update(GameTime gameTime)
{
checkCameraPosition();
base.Update(gameTime);
}
public void Draw(GameTime gameTime)
{
currentEffect.Parameters["View"].SetValue(myCamera.View);
currentEffect.Parameters["Projection"].SetValue(myCamera.Projection);
currentEffect.Parameters["ColorMap"].SetValue(text);
currentEffect.Parameters["Color"].SetValue(Color.Green.ToVector4());
foreach (ModelMesh mesh in myModel.Meshes)
{
BoundingSphere s = new BoundingSphere(mesh.BoundingSphere.Center, mesh.BoundingSphere.Radius);
float radius = s.Radius;
int val = 8;
for (int i = (cameraR - val); i <= this.cameraR + val; i++)
{
for (int j = (this.cameraC - val); j <= this.cameraC + val; j++)
{
for (int k = (this.cameraP - val); k <= this.cameraP + val; k++)
{
int r = i % cells;
int c = j % cells;
int p = k % cells;
if(r < 0)
r = cells + r;
if(c < 0)
c = cells + c;
if(p < 0)
p = cells + p;
foreach (Trasformation tras in gameWorld[r % cells, c % cells, p % cells].myList)
{
s.Center = tras.position;
s.Radius = radius * tras.scale;
ContainmentType ris = myCamera.CameraBoundingFrustum.Contains(s);
if (ris == ContainmentType.Contains || ris == ContainmentType.Intersects)
{
currentEffect.Parameters["World"].SetValue(tras.world);
mesh.Draw();
}
}
}
}
}
}
}
private void checkCameraPosition()
{
float var = (xRange + xRange) / cells;
cameraR = (int)(myCamera.Position.X / var) + cells / 2;
var = (yRange + yRange) / cells;
cameraC = (int)(myCamera.Position.Y / var) + cells / 2; ;
var = (zRange + zRange) / cells;
cameraP = (int)(myCamera.Position.Z / var) + cells / 2; ;
}
}
Diamo una spiegazione di quello che ho fatto nei vari punti!
public MeteorManager(Game game, Effect effect, int num, float xRange, float yRange, float zRange,
string myModel, string texture)
: base(game)
{
this.currentEffect = effect; //assegno l'effetto usato per il rendering
this.myModel = Game.Content.Load<Model>(myModel); //carico il modello
foreach (ModelMesh mesh in this.myModel.Meshes)
foreach (ModelMeshPart mp in mesh.MeshParts)
mp.Effect = effect; //assegno l'effetto ad ogni parte del modello
this.text = Game.Content.Load<Texture2D>(texture);//carico la texture che voglio usare
//assegno le variabili per la dimensione del mondo
this.xRange = xRange;
this.yRange = yRange;
this.zRange = zRange;
myCamera = (CameraI)game.Services.GetService(typeof(CameraI)); //recupero il servizio per la telecamera
inputDevice = (InputI)game.Services.GetService(typeof(InputI));//e quello per l'input
gameWorld = new Sezione[cells, cells, cells];//creo la matrice del mondo
makeWorld(num); //vado a creare i mondo
}
Quello che fa il costruttore è questo:
assegna al currentEffect l'effetto passato (gli effect sono usati per caricare e gestire codice HLSL, che serve per renderizzare gli elementi della scena, ottenere effetti particolari etc; in questo esempio ho usato un mio piccolo effect che non fa altro che prendere la texture ed applicarla all'oggetto da renderizzare, senza calcolare nessuna luce, questo perchè è più veloce e semplice del basic Effect standard, e qui voglio andare ad ottenere più FPS possibili. Il file è allegato nella soluzione, ma qui non darò spiegazione di come funziona).
Poi carica il modello e la texture passate. Assegna il valore alle variabili che mi dicono quanto è grande il mondo.
Si recupera poi i riferimenti ai servizi della telecamera e del gestore dell'input.
GameWorld è la matrice cubica che andrà a contenere il mondo: ogni cella rappresenta una sezione, e ogni sezione avrò la sua lista di elementi.
Il metodo makeWorld, per quanto possa disorientare, non fa altro che creare e posizionare in modo radom num elementi per ogni sezione. A voi il compito di guardarlo o di chiedere qualcosa che non vi è chiaro qui.
Concentriamoci un attimo sul metodo checkCameraPosition invece, che è più curioso secondo me...
private void checkCameraPosition()
{
float var = (xRange + xRange) / cells;
cameraR = (int)(myCamera.Position.X / var) + cells / 2;
var = (yRange + yRange) / cells;
cameraC = (int)(myCamera.Position.Y / var) + cells / 2; ;
var = (zRange + zRange) / cells;
cameraP = (int)(myCamera.Position.Z / var) + cells / 2; ;
}
Questo metodo prende la posizione della telecamera e memorizza in 3 variabili l'indice di riga, colonna e profondità in cui si trova la telecamera.
Come si fa ad otterere tale risultato?
Prendo la variazione relativa ad ogni cella (cioè quanto spazio sulla dimensione X, per esempio, copre una cella). Poi prendo la posizione X della telecamera, la divido per questa variazione, e a tale risultato ci sommo la metà delle celle. La somma è necessaria perchè voglio che la posizione 0,0,0 sia il centro del mio mondo cubico!
Se non facessi così sarei invece posizionato ad un vertice del mondo.
Il metodo draw adesso, che non sto a riportare perchè è lungotto :O
Allora: nelle prime righe si assegna all'effetto usato per il rendering i valori opportuni, quindi la viewMatrix della telecamera, la projectionMatrix della telecamera, la texture dell'oggetto, e un colore.
Poi cominciamo a scorrere le mesh che compongono il nostro oggetto da renderizzare, in questo caso il nostro meteorite, che dovrà essere replicato tante volte.
Si crea una nuova BoundingSphere con centro e raggio presi dalla BoundingSphere della mesh in esame.
Le BoundingSphere sono usate per calcolare le collisioni tra volumi sferici, ma qui le userò per una cosa diversa ma molto utile ^__^
Poi cominciamo a scorrere le celli adiacenti alla posizione in cui si trova la telecamera: più val è grande maggiore è il numero di celle che considero adiacenti.
Poi riassetto gli indici, in modo che non vadano fuori dai limiti consentiti.
Scorro poi la lista di elementi della sezione data dagli indici r,c,p.
Modifico il centro ed il raggio della BoundingSphere, e calcolo se la sfera o contenuta o interseca il BoundingFrustum della telecamera e solo se lo è renderizzo il meteorite.
Perchè faccio ciò?
Perchè anche se renderizziamo solo le celle vicine a noi, eseguo di solito il draw anche per oggetti che mi sono alle spalle e che io non vedo. Facendo questo sprecherei tempo in rendering inutili, che vengono calcolati, ma il cui risultato non è visualizzato a schermo.
Il frustum della telecamera lo possiamo vedere come un cono che parte dal nostro punto di osservazione, o meglio un tronco di cono, che comincia alla distanza nearPlane e termina a farPlane.
Dato che prima di giungere a questo risultato ho fatto diverse prove, se non usassi il boundingFrustum otterrei con val = 8 qualcosa come soli 5 FPS. Con invece riesco ad arrivare a 70 FPS più o meno stabili! Un buon risultato!
Insomma quello che facciamo è controllare se il volume dato dalla boundingSphere adeguatamente settata è contenuto completamente o in parte nel cono visivo della telecamera, e solo se lo è renderizzo l'oggetto. E' molto più conveniente fare questo controllo che fare un rendering inutile!
E poi perchè usare le BoundingSphere? Perchè è un metodo comodo, semplice ed economico per calcolare il volume di un oggetto, perchè calcolarne il volume esatto controllando i poligoni/vertici è troppo oneroso secondo me (e poi non lo so fare XD).
E come lo calcolo il frustum?
Per questo bisogna andare a modifcare la telecamera!
Ecco il file della telecamera con tutte le modifiche apportate
Camera.cs Show/Hide
public enum CameraMode
{
Fixed,
Free,
Rotate,
Follow
}
public interface CameraI
{
Matrix View
{ get; }
Matrix Projection
{ get; }
Vector3 Position
{ get; set; }
Vector3 Up
{ get; set; }
Vector3 Target
{ get; set; }
float NearPlane
{ get; set; }
float FarPlane
{ get; set; }
CameraMode Mode
{ get; set; }
BoundingFrustum CameraBoundingFrustum
{ get; }
}
public class Camera : Microsoft.Xna.Framework.GameComponent, CameraI
{
//riferimento al controllo dell'input
protected InputI inputDevice;
//varibili per la telecamera
protected Matrix projection;
protected Matrix view;
protected Vector3 position = Vector3.Zero;
protected Vector3 target = Vector3.Zero;
protected Vector3 up = Vector3.Up;
protected float aspectRatio;
protected float nearPlane;
protected float farPlane;
protected BoundingFrustum cameraFrustum;
//varibili per lo spostamento e per la rotazione
protected float yaw, pitch;
protected Vector3 movimento;
protected float rotationRate = 100.0f;
protected float movementRate = 15000.0f;
protected CameraMode typeOfCamera = CameraMode.Fixed;
public Camera(Game game)
: base(game)
{
inputDevice = (InputI)game.Services.GetService(typeof(InputI));
game.Services.AddService(typeof(CameraI), this);
typeOfCamera = CameraMode.Fixed;
nearPlane = 1.0f;
farPlane = 100000f;
}
public override void Initialize()
{
base.Initialize();
aspectRatio = (float)Game.GraphicsDevice.Viewport.Width /
(float)Game.GraphicsDevice.Viewport.Height;
Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, nearPlane, farPlane, out projection);
Matrix.CreateLookAt(ref position, ref target, ref up, out view);
cameraFrustum = new BoundingFrustum(view * projection);
}
private void updateFreeCamera(GameTime gameTime)
{
movimento = Vector3.Zero;
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
if(inputDevice.actualKeyboardState.IsKeyDown(Keys.Up))
pitch -= rotationRate * delta;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Down))
pitch += rotationRate * delta;
if (pitch > 89)
pitch = 89;
if (pitch < -89)
pitch = -89;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Right))
yaw -= rotationRate * delta;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Left))
yaw += rotationRate * delta;
if (yaw > 360)
yaw -= 360;
if (yaw < 0)
yaw += 360;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.W))
movimento.Z -= movementRate * delta;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.S))
movimento.Z += movementRate * delta;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.A))
movimento.X -= movementRate * delta;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.D))
movimento.X += movementRate * delta;
Matrix rotation = Matrix.CreateFromYawPitchRoll(MathHelper.ToRadians(yaw),
MathHelper.ToRadians(pitch), 0.0f);
Vector3.TransformNormal(ref movimento, ref rotation, out movimento);
position += movimento;
Vector3 transformedReference;
Vector3 dir = new Vector3(0, 0, -1);
Vector3.TransformNormal(ref dir, ref rotation, out transformedReference);
Vector3.Add(ref position, ref transformedReference, out target);
}
public override void Update(GameTime gameTime)
{
if (typeOfCamera == CameraMode.Free)
updateFreeCamera(gameTime);
aspectRatio = (float)Game.GraphicsDevice.Viewport.Width /
(float)Game.GraphicsDevice.Viewport.Height;
Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, nearPlane, farPlane, out projection);
Matrix.CreateLookAt(ref position, ref target, ref up, out view);
cameraFrustum.Matrix = view * projection;
base.Update(gameTime);
}
#region CameraI Membri di
public Matrix View
{
get { return view; }
}
public Matrix Projection
{
get { return projection; }
}
public Vector3 Position
{
get { return position; }
set { position = value; }
}
public Vector3 Up
{
get { return up; }
set { up = value; }
}
public Vector3 Target
{
get { return target; }
set { target = value; }
}
public float NearPlane
{
get { return nearPlane; }
set { nearPlane = value; }
}
public float FarPlane
{
get { return farPlane; }
set { farPlane = value; }
}
public CameraMode Mode
{
get { return typeOfCamera; }
set { typeOfCamera = value; }
}
public BoundingFrustum CameraBoundingFrustum
{
get { return cameraFrustum; }
}
#endregion
}
Le parti importante da considerare sono:
l'enumeratore CameraMode: questo contiene i vari tipi di telecamera che mi sono venuti in mente. Per ora noi useremo solo Free, ma nulla vieta di implementare anche gli altri o di aggiungerne di nuovi!
Come si può vedere ho anche aggiunto alcuni campi all'interfaccia CameraI.
Aggiunta importate è la CameraBoundingFrustum, che verrà calcolato nel metodo Update.
Ora passiamo al metodo Update: per prima cosa andiamo a controllare il tipo di telecamera, e se è di tipo Free adiamo a chiamare il metodo per aggiornarla.
Poi si calcola la view e la projection matrix ed infine il boundigFrustum, dato dal prodotto delle view per la projection.
Vediamo il metodo updateFreeCamera:
private void updateFreeCamera(GameTime gameTime)
{
movimento = Vector3.Zero;
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
if(inputDevice.actualKeyboardState.IsKeyDown(Keys.Up))
pitch -= rotationRate * delta;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Down))
pitch += rotationRate * delta;
if (pitch > 89)
pitch = 89;
if (pitch < -89)
pitch = -89;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Right))
yaw -= rotationRate * delta;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.Left))
yaw += rotationRate * delta;
if (yaw > 360)
yaw -= 360;
if (yaw < 0)
yaw += 360;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.W))
movimento.Z -= movementRate * delta;
if (inputDevice.actualKeyboardState.IsKeyDown(Keys.S))
movimento.Z += movementRate * delta;
Matrix rotation = Matrix.CreateFromYawPitchRoll(MathHelper.ToRadians(yaw),
MathHelper.ToRadians(pitch), 0.0f);
Vector3.Transform(ref movimento, ref rotation, out movimento);
position += movimento;
Vector3 transformedReference;
Vector3 dir = new Vector3(0, 0, -1);
Vector3.Transform(ref dir, ref rotation, out transformedReference);
Vector3.Add(ref position, ref transformedReference, out target);
}
MovementRate e RotationRate sono due variabili definite nella classe e ci dicono quanto velocemente si muove e ruota la nostra telecamera.
A seconda dei tasti che premiamo andiamo a modifcare i valori di Pitch e Yaw (che stanno rispettivamente per l'angolo sull'asse X ed Y), oppure a spostare la posizone della telecamera.
Usando yaw e pitch creo una matrice di rotazione, che uso per trasformare (cioè per spostare lungo la direzione data dalla matrice) la posizione della telecamera.
La stessa cosa la faccio per la posizione del punto che sto osservando, cioè per il target.
Come possiamo vedere in questo metodo non c'è il calcolo per la creazione della view e della projection: qui si aggiorna solo il valore della posizione e del target, la creazione viene fatta nel metodo update.
E questo è tutto.
Ora si deve solo andare in Game1.cs e mettere questo nel metodo Initialize:
defaultEffect = Content.Load<Effect>(@"Effects\Default");
mman = new MeteorManager(this, defaultEffect, 5, maxX,
maxY, maxZ, @"Models\asteroid1", @"Textures\asteroid1");
Components.Add(mman);
Le due variabili vanno naturalmente create prima ^^
Questo ci dice di caricare il file Default.fx, e di creare il MeteorManager che avrà 5 elementi per ogni sezione.
Dato che il MeteorManager è un GameComponent e non un DrawableGameComponent bisogna dirgli noi quando renderizzarlo!
Quindi nel metodo Draw di Game1.cs va messo questo:
mman.Draw(gameTime);
Ma veramente serve tutta questa palla del CameraBoundingFrustum, delle collisioni e tutto il resto?
Fate una prova:
Andate nel file del MeteorManager, nel Draw(..) mettere val = 8 e commentate la riga dove fate il controllo sul risultato del CameraBoundingFrustum.Contains(..).
Io riesco ad ottenere soltanto 5-6 FPS.
Facendo il controllo passo a circa 70.
Voi che dite? Serve?
Per me si ^___^
Poi almeno così si è visto come creare un manager per un insieme di oggetti, e come cominciare a buttare giù una semplice struttura per un gioco... qui il compito è..... contatare tutti gli asteroidi XD Sai che spasso XD
Scherzi a parte, potrebbe comunque essere un'idea di base per la struttura di un semplice gioco!
Ci dovrebbero essere altri metodi per semplificare il rendering di istanze multiple dello stesso elemento, ma ancora non sono in grado di applicarli.
Spero vi sia stato utile tutto questo.
Se ci sono cose che non tornanto o sbagliate o che si possono fare meglio (il che è molto probabile) ditelo please :)
Alla prossima!
Intanto ecco i file allegati!
XNA-tut5.rar
Argomenti trattati:
Free camera
Camera Bounding Frustum Continua a leggere!
Iscriviti a:
Post (Atom)