Vizualizator 3D točk iz zajema

Kazalo

Definicija naloge

Moja naloga je bila narediti računalniški program, ki bo iz datoteke prebral podatke o točkah, zajetih s pomočjo digitalizatorja, razvitega v laboratoriju LECAD. Program mora biti sposoben prikazati točke v treh dimenzijah in vhodno datoteko pretvoriti v format, primeren za uporabo v računalniških modelirnikih.

Zasnova rešitve

Odločil sem se za programiranje v programskem jeziku C, za 3D prikaz točk sem si pomagal z grafično knjižnico PHIGS, pri zapisu točk v datoteko, primerno za nadaljno uporabo v modelirnikih, pa sem se odločil za formata DXF in IGES.

Za format DXF sem se odločil zaradi velike razširjenosti tega formata, čeprav se uporablja v glavnem na PC računalnikih. Ravno zaradi omejenosti DXF formata sem izbral tudi IGES format. Uporabnik programa se lahko tako odloči za enega ali za drugega.

nazaj na vrh

Rešitev

Struktura programa

Odločil sem se, da bom pri programiranju uporabljal podprograme (funkcije), saj je tako program veliko bolj pregleden. Sprogramiral sem naslednje podprograme/funkcije:

V nadaljevanju poročila bom napisal nekaj o vsakem podprogramu.

nazaj na vrh

Branje podatkov o točkah iz datoteke

Vhodna datoteka je tekstovna. Vsebuje podatke o točkah, ki so razvrščeni tako, da so podatki o vsaki posamezni točki v eni vrstici. Najprej je navedena x koordinata točke, nato še koordinati y in z. Datoteka ima torej tri stolpce.

Za shranjevanje podatkov o točkah sem naredil nov tip t_tocka, ki je sam po sebi struktura, vsebuje pa podatke o vseh treh koordinatah točke.

typedef struct {
	float x;
	float y;
	float z;
} t_tocka;

t_tocka tocka[3000];

Kot je razvidno iz zadnje vrstice, sem v program vgradil omejitev 3000 točk, ki pa se lahko zelo enostavno tudi poveča.

Spodaj je izvorna koda tega podprograma. Datoteko bere tako, da bere posamezne nize, torej ne kar cele vrstice naenkrat. Ker je prebrani niz podatkovnega tipa char, ga je treba pred zapisom v polje točk pretvoriti v število. To se naredi s funkcijo niz_v_double. Nize bere v serijah po tri in jih sproti zapisuje v ustrezne elemente podatkovnega polja tocka. Ko prebere tretji zaporedni niz, se vrne na začetek zanke in poveča števec za ena (seveda le, če ni naletel na konec datoteke). Novo prebrane nize tako zapisuje v naslednji element polja tocka.

Na koncu podprogram zapiše vrednost števca v spremenljivko st_tock, ki se nato uporablja tudi v ostalih podprogramih.

#define BERI 	fscanf (fp, "%s", k)

void branje() {
	char k[80], ime_datoteke[256];

	printf ("Podaj ime datoteke, iz katere zelis brati podatke o tockah: ");
branje:
	scanf ("%s", ime_datoteke);
	fp = fopen (ime_datoteke, "r");
	if (fp == NULL) {
		printf ("Napaka pri odpiranju podane datoteke!\nPonovi vnos: ");
		goto branje;
	}
	i = 0;
	while (! feof(fp)) {
		i++;
		BERI;
		tocka[i].x = niz_v_float (k);
		BERI;
		tocka[i].y = niz_v_float (k);
		BERI;
		tocka[i].z = niz_v_float (k);
	}
	st_tock = i;
	tocke_prebrane = 1;
	meje ();
	fclose (fp);
}

Funkcije za pretvorbo niza v tip double ne bom opisoval, ampak jo samo podajam.

double niz_v_double (char k[]) {
	int i, predznak, decimala;
	double vrednost;
	for (i=0; k[i] == ' ' || k[i] == '\n' || k[i] == '\t'; i++);
	predznak = 1;
	if (k[i] == '+' || k[i] == '-')
		predznak = (k[i++] == '+') ?1 :-1;
	for (vrednost=0; k[i]>='0' && k[i]<='9'; i++)
		vrednost = 10 * vrednost + k[i] - '0';
	if (k[i] == '.') {
		++i;
		for (decimala=1; k[i]>='0' && k[i]<='9'; i++) {
			vrednost = 10 * vrednost + k[i] - '0';
			decimala *= 10;
		}
	}
	return predznak*vrednost/decimala;
}

Funkcija meje() nastavi meje uporabniškega pogleda knjižnice Phigs tako, da so vse točke vidne na ekranu.

void meje () {
	int i;
	float temp_x, temp_y, xm;
	xm = 0;
	for (i=1; i<=st_tock; i++) {
		temp_x = abs (trans_tocke_x (tocka[i]));
		xm = (temp_x > xm) ?temp_x :xm;
		temp_y = abs (trans_tocke_y (tocka[i]));
		xm = (temp_y > xm) ?temp_y :xm;
	}
	max_x = xm + xm/4;
	min_x = - max_x;
	min_y = - max_x;
	max_y = max_x;
}

nazaj na vrh

Izris točk na ekran

Ta podprogram izriše točke na ekran. Pri tem uporablja tudi funkciji za pretvorbo 3D koordinat v format, primeren za prikaz na zaslonu. To sta funkciji trans_tocke_x in trans_tocke_y, ki ju podrobneje opisuje naslednje podpoglavje. Podatki o točkah morajo seveda pred zagonom tega podprograma že shranjeni v polju tocka. To se preverja z globalno spremenljivko short int tocke_prebrane. Slednji se ob zagonu programa dodeli vrednost 0, v funkciji branje pa se ji dodeli vrednost 1, kar pomeni, da so točke že prebrane. Spremenljivko uporabljajo tudi nekatere izmed ostalih funkcij programa.

Koncept podprograma je naslednji. Pri klicu podprograma je treba navesti številko točke, ki jo želimo izrisati, in barvo, s katero jo želimo na ekranu prikazati. Podprogram torej izriše samo eno točko, ki jo določimo s številko ob klicu. Točko prikaže na ekranu s križcem.

Najprej se koordinate točke transformirajo v 2D format. Tako dobimo x in y koordinati točke na izris na zaslon. Naristai pa je treba križec, kar se naredi v vrsticah, ki sledijo transformaciji v 2D format. Podprogram za izris daljice uporablja ukaze knjižnice PHIGS.

void izris_tocke(int j, int barva) {
	int x, y;
	float dt;
	
	dt = max_x / 160;

	pset_line_colr_ind (barva);

	point_list.num_points = 2;
		
	x = trans_tocke_x (tocka[j]);
	y = trans_tocke_y (tocka[j]); 
	
	points[0].x = x - dt;
	points[0].y = y + dt;
	points[1].x = x + dt;
	points[1].y = y - dt;
		
	point_list.points = points;
	ppolyline (&point_list);
	
	point_list.num_points = 2;
		
	points[0].x = x - dt;
	points[0].y = y - dt;
	points[1].x = x + dt;
	points[1].y = y + dt;
		
	point_list.points = points;
	ppolyline (&point_list);
}

nazaj na vrh

Transformacija 3D koordinat za 2D prikaz

Točke v vhodni datoteki so podane v treh dimenzijah, zaslon pa je dvodimenzionalen. Če želimo prikaz 3D točk na zaslonu, je treba torej nekako ustvariti vtis trodimenzionalnosti. Tudi to je bil del moje naloge.

Za vtis tridimenzionalnosti sem uporabil dve rotaciji koordinatnega sistema, ki je na začetku (pred rotacijama) postavljen tako, da je y os na ekranu vodoravna, z pa navpična. x os kaže pravokotno iz ekrana proti nam. Dvodimenzionalni koordinatni sistem, ki predstavlja koordinate na ekranu, sem označil s koordinatama x(na začetku poravnana z osjo x) in y’ (na začetku poravnana z osjo z). Najprej se koordinatni sistem zavrti okoli osi z za kot FI, nato pa še okoli osi x za kot PSI. Na ta način dobimo vtis tridimenzionalnosti. Pri tem sem si pomagal z naslednjima slikama:

slika 1. 3D prikaz točke na 2D mediju

slika 2. Prikaz kotov j in y, potrebnih za izračun ustreznih 2D koordinat (levo pogled od zgoraj, desno pogled z leve)

Koordinata x’ je odvisna samo od kota FI, medtem ko je y’ odvisen od obeh kotov rotacije. Novi koordinati (x’ in y) izračunamo iz naslednjih formul:

h je pravokotna razdalja od projekcije točke na ravnino xy do osi x’. Iz zadnje formule je razvidno, da pride do težav ko je kot FI enak 90 stopinj. Takrat je izraz v oklepaju nedefiniran, saj je cos 90 enako 0, tan 90 pa je nedefiniran. Zaradi tega sem moral v funkciji trans_tocke_y uporabiti if stavek, da sem pri teh vrednostih transformacijo drugače definiral.

#define ST  PI/180
#define FI  fi*ST
#define PSI psi*ST

float trans_tocke_x (t_tocka tocka) {
  float temp;
  temp = - tocka.x * sin (FI) + tocka.y * cos (FI);
  return temp;
}

float trans_tocke_y (t_tocka tocka) {
  float temp;
  if (fi==90)  temp = tocka.z * cos (PSI) - tocka.y * sin (PSI);
  else if (fi==270)  temp = tocka.z * cos (PSI) + tocka.y * sin (PSI);
  else  temp = tocka.z * cos (PSI) -
	(trans_tocke_x (tocka) * tan (FI) + tocka.x / cos (FI)) * sin (PSI);
  return temp;
}

nazaj na vrh

Zapis točk v datoteko DXF formata

Format DXF je trenutno najbolj razširjen standard za izmenjavo tehniških risb med risarskimi programi, vendar velja to predvsem za PC računalnike. Vpeljalo ga je podjetje Autodesk s svojim programom AutoCAD. Njegova največja prednost je prav razširjenost, saj ga podpirajo tudi programi, ki v prvi vrsti niso namenjeni za tehniško risanje, kot sta na primer Corel DRAW! in Microsoft Word. Slabost DXF formata je v tem, da mora biti popoln DXF prevajalnik zmožen generirati pisave in z njimi tudi manipulirati, hkrati pa biti tudi sposoben prikaza dvodimenzionalnega prikaza 3D krivulj.

Obstajata dve obliki datotek DXF – binarna in tekstovna (ASCII). Binarna DXF datoteka je približno 25% krajša in 5-krat hitrejša za branje kot navadna ASCII datoteka. Kljub temu pa sem se jaz odločil za ASCII obliko DXF datoteke.

DXF datoteka je sestavljena iz več dvojic (dveh vrstic), ki se jim reče skupine. Vsaka skupina sestoji iz skupinske kode in skupinske vrednosti. Skupinska koda pove, kakšen podatek sledi v naslednji vrstici. Skupinska koda in vrednost sta med seboj ločena s prelomom vrstice.

Podatki v DXF datoteki so urejeni v naslednje štiri razdelke:

Splošna DXF datoteka zgleda nekako takole:

0 (skupinska koda, ki označuje začetek/konec datoteke, razdelkov, elementov ...)
SECTION	(skupinska vrednost, ki označuje začetek razdelka)
2		(skupinska koda, ki pove, da sledi ime)
HEADER	(ime razdelka)
.
.
.
0
ENDSEC	(označuje konec razdelka)
0
SECTION
2
TABLES
.
.
.
0
ENDSEC
0
SECTION
2
BLOCKS
.
.
.
0
ENDSEC
0
SECTION
2
ENTITIES
.
.
.
0
ENDSEC
0
EOF

V svojem programu sem imel opravka samo s točkami. Podatke o točkah se vnaša na naslednji način:

0
POINT
8
ime_risalnega_nivoja
10
x_koordinata_tocke
20
y_koordinata_tocke
30
z_koordinata_tocke

Zdaj pa samo še podajam podprogram za zapis točk v DXF format.

void tocka_v_DXF () {
	int 	i;
	char	ime_dxf[256], osnova[252];
	
	if (tocke_prebrane == 0) {
		printf ("\nOPOZORILO!\nPodatki o tockah niso dostopni!\nNajprej preberi 					tocke iz datoteke!\n");
		goto konec_funkcije;
	}
	
	printf ("\nPodaj ime DXF datoteke (koncnice .dxf NE dodaj!): ");
	scanf ("%s", osnova);
	sprintf (ime_dxf, "%s.dxf", osnova);
	dxf = fopen (ime_dxf, "w");
	
	/**** Zapis glave datoteke ****/
	fprintf (dxf, "0\nSECTION\n2\nHEADER\n9\n$CECOLOR\n62\n0\n0\nENDSEC\n");
	
	/**** Definicija layer-jev ****/
	fprintf (dxf, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n");
	
	/**** Zapis tock ****/
	fprintf (dxf, "0\nSECTION\n2\nENTITIES");
	for (i=1; i<=st_tock; i++) {
		fprintf (dxf, "\n0\nPOINT\n8\n0\n62\n256\n");
		fprintf (dxf, "10\n%f\n20\n%f\n30\n%f", tocka[i].x, tocka[i].y, tocka[i].z);
	}
	
	/**** Zapis konca datoteke ****/
	fprintf (dxf, "\n0\nENDSEC\n0\nEOF");
	
	fclose (dxf);
	
konec_funkcije:
}

nazaj na vrh

Zapis točk v datoteko IGES formata

IGES (Initial Graphics Exchange Specification) format je najbolj razširjen standard pri prenosu podatkov med posameznimi CAD sistemi. Za razliko od DXF formata to velja tudi za UNIX sisteme. Je nevtralen format, kar pomeni, da ni potrebna uporaba direktnih prenosnikov slik iz ene v drugo aplikacijo, ampak lahko IGES datoteko beremo s katerokoli CAD aplikacijo, ki standard podpira.

IGES datoteke so podobno kot DXF lahko tekstovne (ASCII) ali pa binarne. Največ se uporabljajo ASCII datoteke, najdemo pa tudi komprimirane ASCII IGES datoteke, ki jih je treba pred uporabo seveda dekomprimirati. Slabost IGES standarda je njegova zastarelost, saj je bil osnovan že konec 70-ih let. Problem je tudi veliko število različnih verzij, kar povzroča veliko problemov pri prenosu slik. Še zlasti je problematičen prenos 3D slik. Za moj problem, ko prenašam samo točke, pa je več kot zadovoljiv.

IGES datoteka je razdeljena v 5 sekcij:

Vsaka vrstica IGES datoteke ima natanko 80 znakov. V 73 stolpcu se v vsaki vrstici nahaja velika črka (S, G, D, P ali T), ki pove za katero sekcijo gre. Sledi ji številka vrstice v trenutni sekciji, ki je poravnana po desnem robu v 80. stolpec. Za lažje razumevanje podajam primere vrstic za D, P in T sekcijo:

Št. stolpca (za lažjo predstavo):
12345678223456783234567842345678523456786234567872345678823456789234567802345678

vrstica D sekcije:
     116       2               1       0                               0D      3
     116       2       5       1                                        D      4

vrstica P sekcije:
116,24.000000,-21.000000,30.000000,0,0;                                3P      2

vrstica T sekcije:
S      2G      1D    202P    101T      1                                T      1

Vsak tip entitete ima svojo kodo. Točka ima kodo 116, črta 110, tekst 212 itd. Ta koda je podana na začetku vrstice v D in P sekciji. V D sekciji sledi kodi podatek o številki vrstice P sekcije, v kateri se nahajajo podatki o tej entiteti. Nato se zvrstijo še ostali podatki. Ponavadi ima vsaka entiteta dve vrstici v D sekciji in eno ali več (odvisno od števila podatkov) v P sekciji.

V P sekciji se podatek o številki vrstice D sekcije, kjer se nahajajo podatki o trenutni entiteti, nahaja tik pred oznako P. Če je podatkov o entiteti preveč, da bi šli v eno vrstico, se jih napiše v več vrstic.

Podprogram za kreiranje IGES datoteke pa izgleda takole:

void tocka_v_IGES () {
	int 	i, j, dolzina;
	char	ime_iges[256], osnova[253], vrsta[80], p_sekcija[16];

	if (!(tocke_prebrane)) {
    printf ("\nOPOZORILO!\nPodatki o tockah niso dostopni!\nNajprej preberi tocke iz
                 datoteke!\n");
	  delay (2000);
	}
	else {
	  printf ("\nPodaj ime IGES datoteke (koncnice .ig NE dodaj!): ");
	  scanf ("%s", osnova);
	  sprintf (ime_iges, "%s.ig", osnova);
	  iges = fopen (ime_iges, "w");

	  /*** zacetna sekcija ***/
	  sprintf (vrsta, "IGES datoteka, kreirana s programom rpk98.exe. Avtor: Samo Gazvoda;     S      1");
	  fprintf (iges, "%s\n", vrsta);
	  sprintf (vrsta, "Vsebuje podatke o tockah, zajetih s 3D vizualizatorjem.                 S      2");
	  fprintf (iges, "%s\n", vrsta);
	
	  /*** globalna sekcija ***/
	  sprintf (vrsta, "RPK98.EXE; VERZIJA 1.0; KREIRANA IGES DATOTEKA; NAREJENO V LETU 1998    G      1");
	  fprintf (iges, "%s\n", vrsta);

	  /*** vhodna sekcija ***/
	  for (i=1; i<=st_tock; i++) {
	    j = i * 2 - 1;
	    sprintf (vrsta, "     116%8d               1       0                               0D%7d", i, j);
	    fprintf (iges, "%s\n", vrsta);
	    sprintf (vrsta, "     116       2       5       1                                        D%7d", ++j);
	    fprintf (iges, "%s\n", vrsta);
	  }

	  /*** parametricna sekcija ***/
	  for (i=1; i<=st_tock; i++) {
	    sprintf (vrsta, "116,%f,%f,%f,0,0;", tocka[i].x, tocka[i].y, tocka[i].z);
	    dolzina = strlen (vrsta);
	    for (j=dolzina; j<=63; j++) vrsta[j] = ' ';
	    vrsta [j] = '\0';
	    fprintf (iges, "%s", vrsta);
	    j = 2 * i - 1;
	    sprintf (p_sekcija, "%8dP%7d", j, i);
	    fprintf (iges, "%s\n", p_sekcija);
	  }
		
	  /*** zakljucna sekcija ***/
	  fprintf (iges, "S      2G      1D%7dP%7dT      1                                T      1", st_tock*2, st_tock);

	  fclose (iges);
	} /* if */
} /* funkcija */

nazaj na vrh

Zaključek

Glavna problematika moje naloge je bil prikaz točk na ekranu. Programiral sem v jeziku C, ki ima v osnovi dokaj slabo grafično podporo (vsaj verzija, ki jo imam doma). Tako sem se pri grafični predstavitvi točk odločil za knjižnico Phigs, saj je ta grafično zelo bogata. Morda je malo nerodno, ker je treba ukaze vpisovati v drugem oknu, kot se rišejo rezultati. Ravno zaradi tega sem v osnovi program naredil v tekstovnem načinu in v osnovni menu dodal opcijo za prikaz točk, ki vklopi grafični način. V grafičnem načinu lahko potem uporabnik rotira koordinatni sistem, označi točke in po potrebi skalira, če katera izmed točk pobegne z zaslona.

Pri izbiri formatov, v katere sem pretvoril vhodno datoteko točk, sem se odločil za DXF in IGES. Če hoče uporabnik uporabljati datoteko na PC sistemih, se uporabi DXF format, za uporabo na UNIX sistemih pa je bolje uporabiti IGES format. Na ta način sem pokril obe veliki veji računalniških sistemov.

Pri obeh formatih sem se odločil za ASCII obliko datoteke, saj je enostavnejša. Res je, da zasede veliko več prostora kot binarna in se tudi bere počasneje, vendar je ta program namenjen za prenašanje točk, ki imajo vsaka zase samo 3 osnovne podatke. Tudi ob večjem številu točk datoteka ni pretirano velika. Če pa bi slučajno prišlo do ogromnih velikosti, se datoteka še vedno lahko stisne.

V program sem vstavil omejitev števila točk na 3000. Po mojem je to več kot dovolj.

nazaj na vrh