Å tegne:>Klosser

Byggeklosser

Hva
bokser
Planlegging av en scene og uttegning av noen klosser. Bruk av matrisestacken og rotasjon av en scene

Denne modulen er bygget rundt noen variasjoner over et enkelt tema. Vi vil tegne ut noen klosser plassert på en flate. Vi introduserer tegning av en kloss, bruk av transformasjoner for å plasserer flere klosser, samt en interaktiv manipulasjon av scenen. Vi vil legge vekt på å planlegge scenen.

Kodeekstraktene i teksten nedenfor er i VC++, men burde være enkle å overføre til en Java-løsning. En Java-versjon er tilgjengelig, se referanser.

Vi skal lage en konstruksjon som er bygget opp av 3 klosser på en bordplate. Før vi går på selve konstruksjonen og programmet må vi finne ut hvordan vi skal tegne en kloss.

En kloss

OpenGL har i prinsipp ikke noe apparat for å handtere geometriske figurer. Noen slike finnes i glu-biblioteket (quadricene), men vi må være forberedte på selv å lage funksjoner som tegner ut ulike romfigurer. En kloss er nyttig å ha, så vi lager en klossfunksjon. Du kan betrakte dette som en nyttig øvelse i å konstruere uttegningsrutiner.

Utgangspunktet er følgende figur som viser en kloss i utbrettet tilstand:

kloss

Vi må tegne ut de 6 flatene F1..F6. Vi må passe på at vi holder styr på hva som er forsiden ( front ) og hva som er baksiden (back) på flatene. OpenGL skiller mellom dette og vi kan blant annet tildele ulike materialegenskaper til forsiden og baksiden. I dette tilfellet skal vi tegne en lukket boks, og konsentrerer oss om utsiden. Vi må være systematiske under uttegningen og vi bestemmer oss for at vi vil tegne hver flate slik at vi angir punktene med klokka, når vi betrakter flaten utenfra.

Da er flatene beskrevet slik:

F1: adcb F4: fcde
F2. abgh F5: ahed
F3: gfeh F6: bcfg

Vi må bestemme oss for hvordan vi skal forholde oss til klossens størrelse.

Vi kan enten la klossens dimensjoner og plassering inngå som parametrre til en kloss-funksjon. I så fall vil det kanskje være naturlig å velge et punkt, og utstrekningen i x-, y- og z-retningen som parametre. Dette vil gjøre rotasjon vanskelig.

Alternativt kan vi bestemme oss for å tegne en standardkloss som alltid har sidekanter på 1 og har et hjørne i origo. En slik strategi forutsetter at vi bruker transformasjonsmulighetene, skalering, translasjon og rotasjon, når vi skal bruke klossen i andre dimensjoner og på andre steder i rommet.

Vi velger den siste strategien og bestemmer oss for å legge origo i a, x-aksen langs ab, y-aksen langs ah og z-aksen langs ad. Punktkoordinatene blir:

a: 0,0,0 e: 1,0,1
b: 0,1,0 f: 1,1,1
c: 0,1,1 g: 1,1,0
d: 0,0,1 h: 1,0,0

Funksjonen DrawBox() blir slik:

void DrawBox()
{
  /* draws the sides of a unit cube (0,0,0)-(1,1,1) */
  glBegin(GL_POLYGON);/* f1: front */
    glNormal3f(-1.0f,0.0f,0.0f);
    glVertex3f(0.0f,0.0f,0.0f);
    glVertex3f(0.0f,0.0f,1.0f);
    glVertex3f(1.0f,0.0f,1.0f);
    glVertex3f(1.0f,0.0f,0.0f);
  glEnd();
  glBegin(GL_POLYGON);/* f2: bottom */
    glNormal3f(0.0f,0.0f,-1.0f);
    glVertex3f(0.0f,0.0f,0.0f);
    glVertex3f(1.0f,0.0f,0.0f);
    glVertex3f(1.0f,1.0f,0.0f);
    glVertex3f(0.0f,1.0f,0.0f);
  glEnd();
  glBegin(GL_POLYGON);/* f3:back */
    glNormal3f(1.0f,0.0f,0.0f);
    glVertex3f(1.0f,1.0f,0.0f);
    glVertex3f(1.0f,1.0f,1.0f);
    glVertex3f(0.0f,1.0f,1.0f);
    glVertex3f(0.0f,1.0f,0.0f);
  glEnd();
  glBegin(GL_POLYGON);/* f4: top */
    glNormal3f(0.0f,0.0f,1.0f);
    glVertex3f(1.0f,1.0f,1.0f);
    glVertex3f(1.0f,0.0f,1.0f);
    glVertex3f(0.0f,0.0f,1.0f);
    glVertex3f(0.0f,1.0f,1.0f);
  glEnd();
  glBegin(GL_POLYGON);/* f5: left */
    glNormal3f(0.0f,1.0f,0.0f);
    glVertex3f(0.0f,0.0f,0.0f);
    glVertex3f(0.0f,1.0f,0.0f);
    glVertex3f(0.0f,1.0f,1.0f);
    glVertex3f(0.0f,0.0f,1.0f);
  glEnd();
  glBegin(GL_POLYGON);/* f6: right */
    glNormal3f(0.0f,-1.0f,0.0f);
    glVertex3f(1.0f,0.0f,0.0f);
    glVertex3f(1.0f,0.0f,1.0f);
    glVertex3f(1.0f,1.0f,1.0f);
    glVertex3f(1.0f,1.0f,0.0f);
  glEnd();
}

Flere klosser

Vi ønsker å tegne ut tre bokser plassert på en flate, slik:

klosser

De tre klossene har samme størrelse (2,1,1), og de har alle sine kanter parallellt med koordinataksene. I tillegg skal vi ha en bordplate. Sett i xz-projeksjon:

klosser2

Vi vil framstille denne scenen med en lys grå bakgrunn.

InitializeRContext

Lyskilden og bakgrunnsfargen spesifiseres slik:

void CBoxView::InitializeRContext()
{
  ...
  // prepare ligthsource
  GLfloat ambient[] = {0.2f,0.2f,0.2f,1.0f };
  GLfloat diffuse[] = {1.0f,1.0f,1.0f,1.0f };
  GLfloat position[] = {4.0f,0.0f,6.0f,0.0f };
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHTING);
  ...
  // set background to light gray
  glClearColor(0.8f, 0.8f, 0.8f, 0.0f);
}

DrawScene

Så konsentrerer vi oss om selve uttegningen slik den er realisert i DrawScene:

void CBoxView::DrawScene()
{
  // Clear the color and depth buffers.
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  // Set up for scene
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  // position the eye relative to scene
  gluLookAt(-4.0f,-5.0f,6.0f,// eye
            0.0f,0.0f,0.0f,  // looking at
            0.0f,0.0f,1.0f   // is up
            );
  /////////////////////////////
  // Draw the construction
  // draw the "table"
  // set material for table
  m_MAT.Select_And_Do(m_BordMatIx);
  glPushMatrix();
  glTranslatef(-3.5f,-1.0f,-0.11f);
  glScalef(7.0f,3.0f,0.1f);
  DrawBox();
  glPopMatrix();
  // set material for boxes
  m_MAT.Select_And_Do(m_KlossMatIx);
  // the first box
  glPushMatrix();
  glTranslatef(-2.5f,0.0f,0.0f);
  glScalef(2.0f,1.0f,1.0f);
  DrawBox();
  glPopMatrix();
  // the second box
  glPushMatrix();
  glTranslatef(0.5f,0.0f,0.0f);
  glScalef(2.0f,1.0f,1.0f);
  DrawBox();
  glPopMatrix();
  // the box on top
  glTranslatef(-1.0f,0.0f,1.0f);
  glScalef(2.0f,1.0f,1.0f);
  DrawBox();
  // End draw the construction
  /////////////////////////////
  // finnish drawing
  glFlush();
}

Vi ser litt nærmere på hvordan marisestakken er brukt. Uttegningen av bordplata er som følger:

 glPushMatrix();
 glTranslatef(-1.0f,-1.0f,-0.11f);
 glScalef(7.0f,3.0f,0.1f);
 DrawBox();
 glPopMatrix();

Først tar vi vare på den aktuelle transformasjonsmatrisa ved å pushe den på stakken, glPushMatrix(). Når vi er ferdige med bordplata setter vi denne matrisa (toppen av stakken) tilbake som aktuelle transformasjonsmatrise, glPopMatrix(). Det vil si at det som skjer av transformasjoner mellom disse to funksjonskallene ikke har noen konsekvenser i fortsetningen.

Vi preparerer for selve uttegningen med to transformasjoner glTranslate() og glScale(). Dette er resultatet av følgende resonnement:

  1. Vi flytter origo ut til den posisjonen vi skal ha boksen.
  2. Vi skalerer opp boksen til den størrelsen vi ønsker

Se modulen 2D transf. for en diskusjon av forholdet mellom geometrisk resonnement og rekkefølgen på transformasjonene.

De tre boksene tegnes ut etter samme resonnement.

Interaktivitet

Vi ønsker å gi brukeren anledning til å rotere klossekonstruksjonen interaktivt, se OnLButtonDown, OnLButtonUp og OnMouseMove nedenfor.

Endringen fra forrige versjon er den delen som er kommentert som "bring construction in position". Koden bør være selvforklarende.

void CBox1View::DrawScene()
{
  // Clear the color and depth buffers.
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  // Set up for scene
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  // position the eye relative to scene
  gluLookAt(-4.0f,-5.0f,6.0f,// eye
            0.0f,0.0f,0.0f,  // looking at
            0.0f,0.0f,1.0f   // is up
            );
  //////////////////////////////////
  // bring construction in position
  glRotatef(m_xv,1.0f,0.0f,0.0f);
  // rotate around y-axis
  glRotatef(m_yv,0.0f,0.0f,1.0f);
  /////////////////////////////
  // Draw the construction
  ...
  // End draw the construction
  /////////////////////////////
  // finnish drawing
  glFlush();
}

Merk at transformasjonene for å bringe konstruksjonen i posisjon legges til konstruksjonsmatrisen på samme måte som de transformasjonene som legges til før hver kloss-uttegning. Det betyr at de innledende transformasjonenen forblir aktive for alle senere uttegninger.

Resonnementet ved posisjonering er helt parallellt til alle andre geometriske resonnementer, og kallene på transformasjonsfunksjonenen er på vanlig måte motsatt av det geometriske resonnementet, dersom vi ikke resonnerer med å flytte origo. De to variablene, m_vx og m_vy, er definert i CBox1View.h , de initialiseres i konstruktoren til h.h.v. -60 og 0, og de manipuleres i OnMouseMove(), se nedenfor.

OnLButtonDown

Koden børe være selvforklarende. m_PLast husker siste punkt i musebevegelsen.

void CBox1View::OnLButtonDown(UINT nFlags, CPoint point)
{
  // User has pressed left button.Make a note
  // of this and prepare for intuitive rotation of scene
  m_PLast=point;
  SetCapture();
  CView::OnLButtonDown(nFlags, point);
}

OnLButtonUp

void CBox1View::OnLButtonUp(UINT nFlags, CPoint point)
{
  // free capture and terminate rotation
  ReleaseCapture();
  CView::OnLButtonUp(nFlags, point);
}

OnMouseMove

Her er kjernen i bevegelsen. Beregningen av m_xv og m_yv kan gjøres på mange måter. Her er det slik at fullt utslag i musebevegelsen, over hele vinduet, tilsvarer 90 grader rotasjon. Denne følsomhetene kan lett endres.

void CBox1View::OnMouseMove(UINT nFlags, CPoint point)
{
  // if left button is down
  if(nFlags && MK_LBUTTON)
  {
    // and has moved a significant dist
    if((abs(m_PLast.x-point.x)>2)
     ||(abs(m_PLast.y-point.y)>2))
    {
      // then we find an intuitive rot angle
      CRect R;
      GetClientRect(& R);
      m_yv+=90.0f*((GLfloat (point.x-m_PLast.x))
            /GLfloat (R.Width()));
      m_xv+=90.0f*((GLfloat (point.y-m_PLast.y))
            /GLfloat (R.Height()));
      // rememeber point and force immediate redraw
      m_PLast=point;
      RedrawWindow();
    }
  }
}

Dersom uttegningen er svært tidkrevende vil selvsagt følelsen av interaktiv kontroll svekkes. Det er flere måter å ta tak i dette problemet på. En nærliggende variant er å begrense tegningen til en forenklet versjon av scenen når brukeren er aktiv. Dette fordrer et flagg som kan settes i OnLButtonDown og OnLButtonUp, og som leses i DrawScene.

Bokser i VRML

Du kan se en slik klosskonstruksjon skrevet i VRML dersom du har forberedt nettleseren for dette: Bokser som VRML.

Du kan laste ned Octaga som er en VRML/X3D leser/plugin fra Octaga.com [1] , eller du kan finne en annen her: VRML Plugin and Browser Detector [2] .

VRML (Virtual Reality Modelling Languge) er ikke tema i denne modulen, og modellen beskrives ikke nærmere.

Selve fila boxes.wrl ser slik ut: boxes.wrl

Referanser
1
OctagaOctagawww.octaga.com/14-04-2010
2
VRML Plugin and Browser Detectorcic.nist.gov/vrml/vbdetect.html14-04-2010
Vedlikehold
Revidert desember 2004, Børre Stenseth
Nytteverdi
... Hva mener du: 1 2 3 4 5 6