Obligement - L'Amiga au maximum

Mardi 23 avril 2024 - 10:27  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in english


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : C - Graphismes 3D en C (les projections perspectives)
(Article écrit par Pascal Amiable et extrait d'Amiga News Tech - juillet 1990)


Bonjour à toutes et à tous ! Ce mois-ci, nous allons terminer l'étude des différentes projections planes par les projections perspectives. Et puis, comme il me restait un peu de temps, j'ai modifié le petit programme d'affichage d'objet 3D du numéro 23 pour permettre le calcul de la perspective et l'animation temps réel de cet objet (vous verrez cela se réalise très simplement).

Projections perspectives

Alors que les projections parallèles suivent les règles de la géométrie affine classique, les projections perspectives suivent leur propre géométrie. Dans une perspective, deux lignes parallèles quelconques ne sont plus parallèles. La longueur d'un segment est réduite par un facteur proportionnel à sa distance par rapport à l'observateur. Ceci implique deux choses :
  • Aucune information dimensionnelle ne peut être extraite d'une projection perspective.
  • Cette compression des distances produit un excellent effet de profondeur, très utile pour le rendu réaliste de scènes, car elle correspond à notre vision naturelle de l'espace.
En utilisant les coordonnées homogènes, la perspective peut être assimilée à une transformation classique de l'espace 3D vers l'espace 3D. Dès lors, il ne reste plus qu'à réaliser une projection orthographique et la projection perspective est réalisée (simple, non ?).

Lorsque la direction de vision est confondue avec un des axes principaux du repère objet, cette projection perspective est des plus simple. La figure 1 vous montre, de manière intuitive, le principe de modélisation de la perspective.

C

On peut s'imaginer que le plan de projection, c'est-à-dire l'écran de votre Amiga, est une vitre placée entre l'observateur et les objets. La projection perspective est alors simplement l'image des objets perçus par l'observateur à travers la vitre.

Comme on désire toujours obtenir les coordonnées de projections dans le plan de projection (écran), il est nécessaire de décrire l'ensemble des données utiles à une perspective, par rapport au plan de projection. Une perspective simple est définie par trois éléments :
  • Un centre de projection [0, 0, L, 1].
  • Une direction de vision [0, 0, -1, 0].
  • Un plan de projection Z=0.
Un point P [x y z w] se transforme en perspective en P' [x' y' z'w'] à l'aide de la matrice suivante :

C

A partir de cette matrice de transformation perspective, on obtient la matrice de projection perspective en multipliant cette matrice par une matrice de projection orthographique suivant z :

C

A partir de cette matrice de projection perspective, on peut calculer les coordonnées du point projeté :

C

Soit :
  • x'=x
  • y'=y
  • z'=0
  • w'=-a*z+w avec toujours a=-1/L.
On revient en coordonnées unitaires non homogène 2D pour affichage à l'écran. C'est-à-dire :
  • x"=x'/w'
  • y"=y'/w'
  • Soit : x"=x*(1/(-z/L+1))
  • y"=y"(1/(-z/L+1))
Ces équations correspondent à une perspective avec seul point de fuite.

Pour une direction de vision quelconque, c'est-à-dire d'une perspective générale on passe simplement le cas général au cas particulier ci-dessus en utilisant un changement de repère.

La matrice de transformation M Persp (phi,teta) est égale à la multiplication de la matrice M Persp(z) ci-dessus par une matrice de changement de repère M-1.

M-1 est composée de quatre matrices :
  • T-1 : translation à l'observateur.
  • R-1(z) : rotation d'axe z.
  • R-1(y) : rotation d'axe y.
  • E-1 : échange de coordonnées.
M-1 se calcule de la manière suivante :

M-1=E-1.R-1(y).R-1(z).T-1

C

La matrice de transformation générale se calcule donc assez simplement.

Mtranspersp(phi,teta)=Mtranspersp.M-1

On peut alors déterminer la matrice de projection perspective de la même manière en remplaçant Mtranspersp par la matrice Mprojpersp.

Mprojpersp(phi,teta)=Mprojpersp.M-1

C

Ce qui donne en calculant P'=P.Mprojpersp(phi,teta) :
  • x'=-x.sin(phi)+y.cos(phi)-Tx.w
  • y=-x.sin(teta).cos(phi)-y.sin(teta).cos(phi)
  • z'=0
  • w'=-(x.cos(teta).cos(phi))/L-(y.cos(teta).sin(phi))/L-(z.sin(teta))/L+((Tz+L).w)/L
On ramène l'ensemble en coordonnées unitaires 2D non homogènes à l'aide des formules x"=x'/w' et y"=y'/w' avec pour hypothèse w=1.

A partir des données de base :

Centre de projection `obsv' et Point vise 'vise' nous avons la direction de vision :

Direction=[xobsv-xvise,yobsv-yvise,zobsv-zvise,1]

...et sa norme vaut :

|Direction|=sqrt((xobsv-xvise)^2+(yobsv-yvise)^2+(zobsv-zvise)^2)

La projection sur le plan z=0 donne :

Dirproj=[xobsv-xvise,yobsv-yvise,0,1]

Ce vecteur peut être normalisé :

|Dirproj|=sqrt((xobsv-xvise)^2+(yobsv-yvise)^2)

Dès lors, nous avons directement les deux cosinus et sinus de la projection :
  • cos(phi)=(xobsv-xvise)/|Dirproj|
  • sin(phi)=(yobsv-yvise)/|Dirproj|
  • cos(teta)=|Dirproj|/|Direction|
  • sin(teta)=(zobsv-zvise)/|Direction|
Cette astuce évite le calcul des cosinus et sinus très gourmands en temps de calcul.

Affichage objet 3D

Bien, sur ces fortes paroles je vais arrêter la théorie et passer à la pratique en vous proposant une version remaniée du petit programme d'affichage d'objet en 3D du numéro 23. En prime et sans supplément, j'ai modifié le programme pour qu'il y est une petite animation de l'objet. Bien sûr, cette animation n'est pas parfaite mais ne désespérez pas, nous étudierons l'animation un peu plus tard.

/*
	Programmation : C - Graphismes 3D en C (les projections perspectives)
	Article écrit par Pascal Amiable et extrait d'Amiga News Tech - juillet 1990
	http://obligement.free.fr/articles/c_3denc_projections_perspectives.php

	Adapté à VBCC par Tygre, 2020/09/20
*/

/* Déclaration des fichiers inclus. */
#include <stdio.h> 
#include <math.h> 
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h> 
#include <graphics/rastport.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <exec/types.h>

/* Déclaration des constantses. */
#define INTUITION_REV 0L
#define GFX_REV 0L
#define CIAAPRA 0xBFE001 /* Adresse du port CIA A */

/* Déclaration des variables et structures globales */
extern double atof(); /* Mandatory with VBCC */

SHORT pointE1[100][2], pointE2[100][2];
float pointH3D[100][4];
SHORT *ligneE1[100][2], *ligneE2[100][2]; 

struct Screen *OpenScreen();
struct Screen *ecran = NULL;
struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase *GfxBase = NULL;
struct RastPort *rp = NULL;

SHORT nbrpoint = 0, nbrligne = 0;
float obs[4], vise[4], matrice[4][4];

/* Programme principal (ne fait pas grand chose) */
main()
{
	char *test;
	void chargepoints(), chargelignes(), init(), ouvreecran(), referme();
	void transforme(), affiche();

	test = (char *)CIAAPRA;

	chargepoints();
	chargelignes();
	init();
	ouvreecran();

	vise[0] = 0;
	vise[1] = 0; /* Point visé par l'observateur */
	vise[2] = 0;
	vise[3] = 1;
	
	obs[0] = 250;
	obs[1] = 250; /* Position de l'observateur */
	obs[2] = 0;
	obs[3] = 1;

	transforme(pointE1);
	WaitTOF();
	while(!((*test & 0x40) - 64)) /* Test du bouton de la souris */
	{
		affiche(1, ligneE1);
		obs[0]--;
		obs[1]--; /* Trajectoire linéaire */
		obs[2]++;
		transforme(pointE2);
		WaitTOF();
		affiche(0, ligneE1); /* Attention, il n'y a pas de clipping */
		affiche(1, ligneE2); /* ce qui explique des effects bizarres dans les points extrêmes */
		obs[0]--;
		obs[1]--;
		obs[2]++;
		transforme(pointE1);
		WaitTOF();
		affiche(0, ligneE2);
	}
	referme();
}

/* Fonction ouvrant les bibliothèques Intuition et Graphics */
void init()
{
	IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", INTUITION_REV);
	if (IntuitionBase == NULL)
	{
		printf("Mais où est donc passé intuition ?\n");
		exit(FALSE);
	}
	GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", GFX_REV);
	if (GfxBase == NULL)
	{
		printf("Mais où est donc passé graphics ?\n");
		exit(FALSE);
	}
}

/* Fonction ouvrant un simple écran et initialisant le Rastport */
void ouvreecran()
{
	struct NewScreen nouvecran;
	
	nouvecran.LeftEdge = 0;
	nouvecran.TopEdge = 0;
	nouvecran.Width = 320;
	nouvecran.Height = 256;
	nouvecran.Depth = 2;
	nouvecran.DetailPen = 0;
	nouvecran.BlockPen = 0;
	nouvecran.ViewModes = 0;
	nouvecran.Type = CUSTOMSCREEN;
	nouvecran.Font = NULL;
	nouvecran.DefaultTitle = NULL;
	nouvecran.Gadgets = NULL;
	nouvecran.CustomBitMap = NULL;
	
	ecran = OpenScreen(&nouvecran);
	if (ecran == NULL)
	{
		printf("L'écran ne veut pas apparaître\n");
		if (GfxBase) CloseLibrary(GfxBase);
		if (IntuitionBase) CloseLibrary(IntuitionBase);
		exit(FALSE);
	}
	rp = &(ecran->RastPort);
}

/* Fonction chargeant le fichier de points 3D */
void chargepoints()
{
	FILE *fich, *fopen();
	int i, j;
	char tab[100];

	fich = fopen("point.dat", "r");
	if (fich == NULL)
	{
		printf("Problème d'ouverture du fichier point.dat\n");
		exit(FALSE);
	}
	else
	{
		fgets(tab, 100, fich);
		nbrpoint = atoi(tab);
		printf("Chargement de la structure POINT en cours pour %d points...\n", nbrpoint);

		for(i = 0; i < nbrpoint; i++)
		{
			for(j = 0; j < 4; j++)
			{
				fgets(tab, 100, fich);
				pointH3D[i][j] = atof(tab);
			}
		}
	}
	fclose(fich);
}

/* Fonction chargeant le fichier de segments */
void chargelignes()
{
	FILE *fich, *fopen();
	int i, j;
	char tab[100];

	fich = fopen("ligne.dat", "r");
	if (fich == NULL)
	{
		printf("Problème d'ouverture du fichier ligne.dat\n");
		exit(FALSE);
	}
	else
	{
		fgets(tab, 100, fich);
		nbrligne = atoi(tab);
		printf("Chargement de la structure LIGNE en cours pour %d lignes...\n", nbrligne);

		for(i = 0; i < nbrligne; i++)
		{
			for(j = 0; j < 2; j++)
			{
				fgets(tab, 100, fich);
				ligneE1[i][j] = &pointE1[atoi(tab)][0];
				ligneE2[i][j] = &pointE2[atoi(tab)][0];
			}
		}
	}
	fclose(fich);
}

/* Fonction appliquant une projection perspective sur l'objet */
void transforme(pointE)
SHORT pointE[100][2];
{
	int i;
	float pointinter[4];
	void calculepersp();
	
	calculepersp();
	for(i = 0; i < nbrpoint; i++)
	{
		pointinter[0] = pointH3D[i][0] * matrice[0][0] + pointH3D[i][1] * matrice[1][0] + matrice[3][0];
		pointinter[1] = pointH3D[i][0] * matrice[0][1] + pointH3D[i][1] * matrice[1][1] + pointH3D[i][2] * matrice[2][1] + matrice[3][1];
		pointinter[3] = pointH3D[i][0] * matrice[0][3] + pointH3D[i][1] * matrice[1][3] + pointH3D[i][2] * matrice[2][3] + matrice[3][0];

		pointE[i][0] = (SHORT)(320 * (pointinter[0] / pointinter[3])) - 160;
		pointE[i][1] = (SHORT)(256 * (pointinter[1] / pointinter[3])) - 128;
	}
}

/* Fonction calculant la matrice de projection perspective */
void calculepersp()
{
	float distance, distproj, costheta, cosphi, sintheta, sinphi;
	float d[4];
	int i, j;
	
	/* Calcul du vecteur direction */
	for(i = 0; i < 3; i++)
		d[i] = obs[i] - vise[i];
	
	/* Calcul des cosinus, sinus et distances */
	distance = (float)sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]);
	distproj = (float)sqrt(d[0]*d[0] + d[1]*d[1]);
	costheta = distproj / distance;
	sintheta = d[2] / distance;
	cosphi   = d[0] / distproj;
	sinphi   = d[1] / distproj;
	
	/* Calcul de la matrice de projection perspective */
	matrice[0][0] = -sinphi;
	matrice[1][0] = cosphi;
	matrice[2][0] = 0;
	matrice[3][0] = -d[0];
	matrice[0][1] = -sintheta * cosphi;
	matrice[1][1] = -sintheta * sinphi;
	matrice[2][1] = costheta;
	matrice[3][1] = -d[1];
	matrice[0][2] = 0;
	matrice[1][2] = 0;
	matrice[2][2] = 0;
	matrice[3][2] = 0;
	matrice[0][3] = -(costheta * cosphi) / distance;
	matrice[1][3] = -(costheta * sinphi) / distance;
	matrice[2][3] = -sintheta / distance;
	matrice[3][3] = -(d[2] + distance) / distance;
}

/* Fonction affichant l'objet à l'écran */
void affiche(allume, ligne)
SHORT allume;
SHORT *ligne[100][2];
{
	int i;
	SetAPen(rp, allume);
	for(i = 0; i < nbrligne; i++)
	{
		/* Quelques tests pour éviter les débordements */
		if (ligne[i][0][0] < 0)		ligne[i][0][0] = 0;
		if (ligne[i][0][0] > 319)	ligne[i][0][0] = 319;
		if (ligne[i][1][0] < 0)		ligne[i][1][0] = 0;
		if (ligne[i][1][0] > 319)	ligne[i][1][0] = 319;
		if (ligne[i][0][1] < 0)		ligne[i][0][1] = 0;
		if (ligne[i][0][1] > 255)	ligne[i][0][1] = 255;
		if (ligne[i][1][1] < 0)		ligne[i][1][1] = 0;
		if (ligne[i][1][1] > 255)	ligne[i][1][1] = 255;
		
		Move(rp, ligne[i][0][0], ligne[i][0][1]);
		Draw(rp, ligne[i][1][0], ligne[i][1][1]);
	}
}

/* Fonction fermant l'écran et les bibliothèques */
void referme()
{
	if (ecran)         CloseScreen(ecran);
	if (IntuitionBase) CloseLibrary(IntuitionBase);
	if (GfxBase)       CloseLibrary(GfxBase);
}

Mise à jour de septembre 2020 : une archive contenant le listing adapté à vbcc, et avec l'exécutable compilé par VBCC, a été réalisée par Yann-Gaël Guéhéneuc et est disponible sur obligement.free.fr/files/ant3dgraphicsdrawlinesmoving.lha.

Annexe : bibliothèque de fonction

En examinant l'important courrier suscité par les articles sur le 3D en C et d'autres publications de mes confrères, les questions qui reviennent le plus souvent concernent l'utilité de passer par les bibliothèques de fonctions (library) disponibles dans l'environnement Amiga pour accéder aux routines systèmes. Je vais donc faire le point sur ce sujet.

Une bibliothèque de fonction est un ensemble de routines, que votre application peut utiliser et qui vous déchargent d'un certain travail, en général la programmation de fonctions de bas niveau (par exemple la gestion des ressources). Ces bibliothèques résident soit en ROM (exemple : graphics.library, intuition.library), soit sur disque (mathtrans.library, icon.library). A ce sujet, rien ne vous empêche de créer les vôtres.

Étudions maintenant l'accès à ces routines et plus particulièrement, celles intégrées en ROM. Les différentes routines système sont positionnées de manière séquentielle dans la ROM. On peut donc accéder directement à une fonction, quelle qu'elle soit, pour peu que l'on connaisse l'adresse de début de la routine (instruction "JSR" en assembleur ou par un pointeur de fonction en C). Mais cette méthode, utilisée (malheureusement) par certains programmeurs peu scrupuleux, est très dangereuse. En effet, l'adresse physique de cette routine peut évoluer d'une révision de système à l'autre (même si les concepteurs de l'Amiga font tout pour éviter cela, c'est parfois obligatoire) et votre programme qui fonctionnait très bien va aller rendre visite au Guru parce qu'il pointe désormais n'importe où dans la ROM et non plus sur la routine prévue.

Pour éviter cela, on va utiliser un adressage indirect et c'est là que la notion de bibliothèque va servir. Chaque série de routines est organisée en bibliothèque suivant un thème précis (exemple : graphics.library contient les routines graphiques). La bibliothèque principale est l'exec.library. En tête de cette bibliothèque, positionnée à une adresse absolue immuable, on trouve une table de pointeur. Il s'agit de pointeurs sur les fonctions d'Exec et sur les autres bibliothèques de la ROM. L'accès aux fonctions d'Exec se fait via cette table.

Par contre, l'accès aux fonctions des autres bibliothèques nécessite une étape supplémentaire, le chargement de la table de pointeurs lié à cette bibliothèque. Pour ce faire on utilise la fonction OpenLibrary() en lui passant comme paramètres le nom de la bibliothèque et son numéro de version. Une fois cette table chargée on accède aux fonctions, comme pour Exec. Dès lors, il n'y a plus de problème de compatibilité car à chaque révision de système la table des pointeurs est aussi remise à jour.

Une fois votre application terminée, il ne faut pas oublier de refermer les bibliothèques ouvertes à l'aide de CloseLibrary() avec comme paramètre l'adresse de la table des pointeurs de la bibliothèque.

Dans les différents programmes de la série le "3D C facile" des exemples d'ouverture et de fermeture des bibliothèques sont fournis. J'espère que cela répond à vos questions.


[Retour en haut] / [Retour aux articles] [Article précédent] / [Article suivant]