|
|
|
cignoni@iei.pi.cnr.it |
|
http://vcg.iei.pi.cnr.it/~cignoni |
|
|
|
|
|
|
XML read e write |
|
Scene Graph design |
|
Adattare lo scene graph |
|
|
|
|
|
|
Due classi: |
|
XmlDoc |
|
Rappresenta tutto un documento xml dopo che è
stato parsato da un file |
|
XmlDoc xd; xd.read(“myfile.xml”); |
|
La radice dell’albero xml è in Xd.start; |
|
Xml nodo dell’albero; |
|
Ogni nodo contiene: |
|
string id; // il tag |
|
string content; |
|
map<string,string> attr; |
|
vector<Xml> children; |
|
|
|
|
|
|
Nota: la classe xml quando parsa un file fa dei
memory leaks… |
|
Colpa del parser che usa una serie di variabili
globali (buffers) che non vengono deallocate alla fine. |
|
Non pericoloso. |
|
|
|
|
|
La scena la assumo strutturata come un albero |
|
Assumo che ogni nodo sia riferito solo una
volta. |
|
Due classi principali |
|
CSG:
contiene l’intero scene graph (la radice) |
|
CSGNode: generico nodo dello scene graph, lo
faccio come classe astratta; |
|
|
|
|
Nel doc aggiungiamo un membro: |
|
CSG Scene; |
|
OnNewDocument: aggiungiamo la costruzione dello
scene graph |
|
Scene.root.Sons.push_back(new
CSGAnimZPrecession()); |
|
MoebiusStrip *ms=new MoebiusStrip(); |
|
ms->Generate(); |
|
Scene.root.Sons.push_back(ms);\ |
|
|
|
Notare come si crei apposta un oggetto MoebiusStrip
con la new anziché passargli l’indirizzo del membro del doc. Questo per
evitare che poi il distruttore del gruppo faccia pasticci cercando di
disallocare qualcosa non allocato con la new |
|
|
|
|
Nella OnDraw della classe GLView buttiamo via
tutto quello dopo la trasf della trackball e aggiungiamo: |
|
|
|
pd->Scene.root.glDraw(pd->ElapsedSec); |
|
|
|
Si dovrebbe fare la stessa cosa con tutto il
resto (omini, pallina ecc) |
|
|
|
|
|
|
|
Dopo che si è parsato un file xml, occorre fare
a mano la creazione delle classi corrispondenti ai vari nodi |
|
Molto facile se si fa tutto con la stessa
interfaccia. |
|
Per ogni classe che si vuole rendere persistente
si aggiunge due funzioni |
|
XMLWrite(FILE *fp) |
|
XMLRead(Xml& xml); |
|
E si usa tutto ricorsivamente |
|
|
|
|
Seguiamo la via del praticone: |
|
Aggiungiamo al nodo base |
|
|
|
virtual void XMLWrite(FILE *fp)=0; |
|
|
|
Che ci obbliga ad implementare tale metodo in
TUTTI gli altri nodi da esso derivati |
|
|
|
void CSGGroup::XMLWrite(FILE *fp) |
|
{ |
|
fprintf(fp,"<CSGGroup >\n"); |
|
for(iterator
i=Sons.begin();i!=Sons.end();++i) |
|
(*i)->XMLWrite(fp); |
|
fprintf(fp,"</CSGGroup>\n"); |
|
} |
|
|
|
|
|
|
void CSGAnimZPrecession::XMLWrite(FILE *fp) |
|
{ |
|
fprintf(fp,"<CSGAnimZPrecession\n"); |
|
fprintf(fp," DeclinationDeg = \"%f\“\n”, DeclinationDeg); |
|
fprintf(fp,"
AngularSpeedDPS = \"%f\“\n”, AngularSpeedDPS
); |
|
fprintf(fp,"
StartAngleDeg = \"%f\“\n”, StartAngleDeg); |
|
fprintf(fp,"/>\n"); |
|
} |
|
void MoebiusStrip::XMLWrite(FILE *fp) |
|
{ |
|
fprintf(fp,"<MoebiusStrip\n"); |
|
fprintf(fp," BlockNum = \"%i\"\n",BlockNum); |
|
fprintf(fp,"/>\n"); |
|
} |
|
Il parsing alla prossima puntata! |
|
|
|
|
|
|
|
Il meccanismo di base è che un si passa ad un
oggetto un elemento xml da cui l’oggetto pesca tutto quello che gli serve
(simmetrico della write) |
|
Difficoltà |
|
gestione puntatori |
|
funzioni virtuali… |
|
Qui abbiamo entrambe… |
|
In più dobbiamo riuscire a mappare tutto quello
che avevamo nella nostra app come nodi dello scene graph. |
|
|
|
|
|
Nel nodo del gruppo c’è una lista di puntatori a
generici |
|
list<CSGNode *> Sons; |
|
I meccanismi di inheritance e polimorfismo del
c++ ci aiutano a far si che ogni nodo sappia come comportarsi: |
|
Come disegnarsi |
|
Come salvarsi |
|
|
|
|
|
|
|
La lettura è più problematica perchè in realtà
facciamo due cose: |
|
Creazione della struttura dello scene graph |
|
Settaggio dei parametri dei vari nodi |
|
Il meccanismo |
|
Virtual void myClass::XMLRead(Xmlelem& xml) |
|
Funziona bene se ho già la struttura dell’albero |
|
Ogni nodo non standard sa come leggersi perchè
lo scrivo io |
|
|
|
|
|
|
|
|
Schema base: |
|
Lettura fatta tramite funzione virtuale pura |
|
Ogni classe ha la propria funzione XMLRead |
|
Controllo che il tag dell’elemento che mi è
stato passato sia compatibile con me. |
|
Per ogni membro della classe cerco l’attributo
corrispondente e se non c’è metto un valore di default |
|
|
|
|
|
|
|
Nel progettare lo scene graph voglio che sia
estendibile. |
|
Deve essere possibile definire nuovi classi di
nodi (e.g. md2 models) senza dover cambiare nulla nelle strutture di base |
|
Nel ricostruire l’albero dello scene graph il
problema è che la creazione dei nodi viene fatta dal nodo gruppo. |
|
Il nodo gruppo non può conoscere tutti i
possibili nodi futuri. |
|
|
|
|
|
Una soluzione ragionevole consiste
nell’incapsulare la conoscenza di tutti i nodi possibili invece che nel
gruppo, nello scene graph stesso |
|
Assunzione ragionevole lo scene graph conosce
tutti I possibili tipi di nodi |
|
Chi definisce nuovi tipi di nodi deve
subclassare lo scene graph |
|
Perchè non subclassare il gruppo? |
|
Perchè non è una classe finale. |
|
Posso immaginare classi derivate da group (e.g.
switch). |
|
|
|
|
|
Per rendere flessibile la creazione aggiungo
allo scene graph una funzione che mi crea dinamicamente un nuovo nodo di un
tipo specificato a runtime |
|
CSGNode *CSG::Allocate(const string
&classname) |
|
Se voglio nuovi nodi basta subclassare CSG e
ridefinire Allocate per gestire nuovi nomi di classi |
|
Secondo vantaggio: la scene può sapere tutto
della vita dei suoi figli |
|
|
|
|
CSGNode *CSG::Allocate(const string
&classname) |
|
{ |
|
CSGNode
*pt=0; |
|
if(classname=="CSGGroup")
pt= new CSGGroup; |
|
if(classname=="CSGTransformation") pt = new
CSGTransformation; |
|
if(classname=="CSGAnimRotation") pt = new CSGAnimRotation; |
|
if(classname=="CSGAnimZPrecession") pt = new
CSGAnimZPrecession; |
|
return
pt; |
|
} |
|
|
|
|
|
|
|
A questo punto iniziamo a specializzare il
nostro scene graph. |
|
Deve gestire nuovi tipi di nodi |
|
È vincolato ad avere un solo nodo di tipo
Moebius che voglio riferire facilmente. |
|
Mi tengo un membro puntatore a Moebius strip |
|
|
|
class CSGMb : public CSG |
|
{ |
|
public: |
|
CSGMb() {m=0;} |
|
virtual CSGNode *Allocate(const string
&classname); |
|
MoebiusStrip *m; |
|
}; |
|
|
|
|
|
|
CSGNode *CSGMb::Allocate(const string
&classname) |
|
{ |
|
if(classname=="MoebiusStrip") { |
|
assert(m==0); |
|
m=new MoebiusStrip; |
|
return m; |
|
} |
|
CSGNode *nn=CSG::Allocate(classname); |
|
if(nn)
return nn; |
|
return 0; |
|
} |
|
Il membro puntatore a moebius è quello che uso
nel doc |
|
|
|
|
|
|
|
Cosi’come abbiamo ristrutturato ora non funziona
più. Perché? |
|
Il Picking era membro di moebius e per
funzionare assumeva che venisse fatto partire con la matrice di
modellazione come durante il rendering |
|
Adesso abbiamo una popmatrix alla fine che
toglie il moto di precessione |
|
Ogni gruppo è isolato. |
|
|
|
|
|
|
|
Il picking è una feature dello scene graph. |
|
Ogni nodo che disegna qualcosa è responsabile di
mettere il suo nome all’inizio |
|
lasciare sullo stack dei nomi alla fine qualcosa
di sensato (-1). |
|
Problema: possibili collisioni di nomi. |
|
Si dovrebbe usare anche lo stack dei nomi |
|
Insieme dei nomi locale al gruppo. |
|
|
|
|
|
int CSG::Pick(int x, int y, const float
&DocTime) |
|
Notare che: |
|
Ha bisogno del tempo |
|
Funziona purché sia fatta partire nello stesso
stato di quando si fa partire glDraw della scena intera |
|
|
|
|
|
|
|
|
Facciamo una classe derivata da CIMesh e da
CSGNode |
|
class CSGSphereMesh : public CSGNode, CIMesh |
|
{ |
|
public: |
|
float Radius; |
|
int Parallel; |
|
int Meridian; |
|
CSGSphereMesh(){}; |
|
virtual ~CSGSphereMesh(void){}; |
|
|
|
virtual void glDraw(const float DocTime); |
|
virtual void XMLWrite(FILE *fp); |
|
virtual void XMLRead(Xml &xml, CSG
*Base); |
|
}; |
|
|
|
|
|
|
CSGSphereMesh(float r,int p,int m) |
|
{ |
|
Radius=r; |
|
Parallel=p; |
|
Meridian=m; |
|
CIShape::Sphere(*this,r,p,m); |
|
}; |
|
|
|
|
|
|
void CSGSphereMesh::glDraw(const float DocTime){ |
|
Draw<true,true>(); |
|
} |
|
void CSGSphereMesh:: XMLWrite(FILE *fp) |
|
{ |
|
fprintf(fp,"<CSGSphereMesh\n"); |
|
fprintf(fp," Radius = \"%f\" Parallel = \"%i\"
Meridian= \"%i\"\n", |
|
Radius,Parallel,Meridian); |
|
fprintf(fp,"/>\n"); |
|
} |
|
void CSGSphereMesh::XMLRead(Xml &xml, CSG
*Base) |
|
{ |
|
assert(xml.id == "CSGSphereMesh"); |
|
Radius
=
(float)atof(xml["Radius"].c_str()); |
|
Parallel =
atoi(xml["Parallel"].c_str()); |
|
Meridian =
atoi(xml["Meridian"].c_str()); |
|
CIShape::Sphere(*this,Radius,Parallel,Meridian); |
|
} |
|
|
|
|
|
|
Avendo aggiunto al un nuovo nodo allo scene
graph devo ricordarmi di gestirlo nella allocate |
|
|
|
CSGNode *CSGMb::Allocate(const string
&classname) |
|
{ |
|
if(classname=="MoebiusStrip") { |
|
assert(m==0); |
|
m=new MoebiusStrip; |
|
return m; |
|
} |
|
if(classname=="CSGSphereMesh") { |
|
return new CSGSphereMesh; |
|
} |
|
CSGNode *nn=CSG::Allocate(classname); |
|
if(nn)
return nn; |
|
return 0; |
|
} |
|
|
|
|
|
|
Aggiungiamo un nuovo nodo che gestisca il
movimento di un oggetto |
|
Avevamo una classe MoebiusStrip::pos che faceva
gran parte del lavoro sporco |
|
Tipico uso: |
|
MoebiusStrip::Pos pp; |
|
pp.CurSide=0; |
|
pp.ThetaDeg=45+CurAngleDeg/2; |
|
pd->m.Transform(pp); |
|
|
|
|
|
|
void MoebiusStrip::Transform(const
MoebiusStrip::Pos &p) |
|
{ |
|
// di quanto si gira per ogni turn (e.g. |
|
float TurnStepDeg = (360.0f/SideNum); |
|
// di quanto si avvolge per ogni degree |
|
float TwistPerDeg = (TurnStepDeg * Turns
)/360.0f; |
|
float
HalfFilletAngleDeg=(TurnStepDeg*FilletRatio)/2; |
|
float BaseAngleDeg = StartTwistDeg-TurnStepDeg/2.0f; |
|
|
|
// Rotazione Intorno all'asse principale |
|
glRotatef(p.ThetaDeg,
0,0,1); |
|
glTranslatef(Radius,0,0); |
|
|
|
// Avvolgimento della striscia. |
|
glRotatef(TurnStepDeg*p.CurSide+p.ThetaDeg*TwistPerDeg,
0,1,0); |
|
// Spostamento verso la superficie del’nastro |
|
glTranslatef(Cos(ToRad(BaseAngleDeg+HalfFilletAngleDeg))*InnerRadius,0,0); |
|
} |
|
|
|
|
|
|
Al solito |
|
class CSGAnimMoebiusTransf : public CSGNode |
|
{ |
|
public: |
|
CSGMb *b; |
|
virtual ~CSGAnimMoebiusTransf(void){}; |
|
float AngularSpeedDPS; //Degree Per Sec; |
|
float StartAngleDeg; |
|
int StartSide; |
|
|
|
virtual void glDraw(const float DocTime); |
|
virtual void XMLWrite(FILE *fp); |
|
virtual void XMLRead(Xml &xml, CSG
*Base); |
|
}; |
|
|
|
|
|
|
|
Notate come sia usato il puntatore alla scene
dentro a questo nodo per recuperare la moebius strip; |
|
Non tutti I nodi hanno bisogno di sapere qual’è
lo scene graph cui appartengono |
|
Un nodo può appartenere a più scene graphs? |
|
|
|
void CSGAnimMoebiusTransf::glDraw(const float
DocTime) |
|
{ |
|
MoebiusStrip::Pos pp; |
|
float CurAngleDeg = StartAngleDeg + DocTime*AngularSpeedDPS; |
|
pp.CurSide=StartSide; |
|
pp.ThetaDeg=CurAngleDeg; |
|
b->m->Transform(pp); |
|
} |
|
|
|
|
|
|
void CSGAnimMoebiusTransf:: XMLWrite(FILE *fp) |
|
{ |
|
fprintf(fp,"<CSGAnimMoebiusTransf\n"); |
|
fprintf(fp," AngularSpeedDPS = \"%f\" " |
|
" StartAngleDeg =
\"%f \" " |
|
" StartSide =
\"%f\"\n", |
|
AngularSpeedDPS, StartAngleDeg, StartSide); |
|
fprintf(fp,"/>\n"); |
|
} |
|
|
|
void CSGAnimMoebiusTransf::XMLRead(Xml &xml,
CSG *Base) |
|
{ |
|
assert(xml.id == "CSGAnimMoebiusTransf"); |
|
AngularSpeedDPS =
(float)atof(xml["AngularSpeedDPS"].c_str()); |
|
StartAngleDeg = (float)atof(xml["StartAngleDeg"].c_str()); |
|
StartSide =
atoi(xml["StartSide"].c_str()); |
|
b=(CSGMb *)Base; |
|
} |
|
|
|
|
BOOL CMBDoc::OnNewDocument() |
|
{ |
|
… |
|
Scene.root.Sons.push_back(new
CSGAnimZPrecession()); |
|
Scene.m=new MoebiusStrip(); |
|
m=Scene.m; |
|
m->Generate(); |
|
Scene.root.Sons.push_back(m); |
|
CSGAnimMoebiusTransf *ma=new
CSGAnimMoebiusTransf; |
|
ma->b=&Scene; |
|
ma->StartAngleDeg=0; |
|
ma->AngularSpeedDPS=40; |
|
Scene.root.Sons.push_back(ma); |
|
Scene.root.Sons.push_back(new
CSGSphereMesh(.5f,20,40)); |
|
` |
|
return TRUE; |
|
} |
|
|
|
|
|
|
<CSGGroup > |
|
<CSGAnimZPrecession |
|
DeclinationDeg = "30.0" AngularSpeedDPS = "10.0"
StartAngleDeg= "0.0" |
|
/> |
|
<MoebiusStrip |
|
SideNum =
"2" Turns = "1.0" |
|
Radius =
"4.0" BlockNum = "12" |
|
InnerRadius =
"0.800000"
FilletRatio =
"0.100000" |
|
StartTwistDeg = "0.0"
StepNum = "4" |
|
/> |
|
<CSGGroup > |
|
<CSGAnimMoebiusTransf |
|
AngularSpeedDPS = "40.0" StartAngleDeg = "0"
StartSide="1"/> |
|
<CSGAnimRotation |
|
AxisX =
"0.0" AxisY = "0.0" AxisZ = "1.0"
AngularSpeedDPS = "720.0" StartAngleDeg= "20.0" |
|
/> |
|
<CSGSphereMesh |
|
Radius
= "0.500000" Parallel = "20" Meridian= "40" |
|
/> |
|
</CSGGroup> |
|
<CSGGroup > |
|
<CSGAnimMoebiusTransf |
|
AngularSpeedDPS = "40.0" StartAngleDeg = "80"
StartSide="0" |
|
/> |
|
<CSGSphereMesh |
|
Radius
= "0.500000" Parallel = "20" Meridian= "40" |
|
/> |
|
</CSGGroup> |
|
</CSGGroup> |
|
|
|