Suivez-nous sur X
|
|
|
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
|
|
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
|
|
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
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : Assembleur - Les étoiles 3D
(Article écrit par Jérôme Étienne et extrait d'Amiga News Tech - février 1992)
|
|
Salut, coucou et bonjour ! Pour bien commencer l'année, vous allez avoir droit au programme qui a permis à
beaucoup de faire de très jolis effets avec les étoiles, autrement dit vous avez compris : nous allons faire des étoiles en 3D.
Le principe est simple : on choisit des points et on les fait bouger.
Pour les faire bouger, il faut se servir des touches du pavé numérique : les touches de gauche servent à décrémenter
la vitesse, les touches du milieu à l'arrêter et les touches de droite à l'incrémenter. La première ligne de
chiffres contrôle la composante X de la vitesse, la seconde, la composante Y et la troisième, la composante Z.
Bon allez, sérieusement, la première étape consiste à choisir une centaine d'étoiles au hasard (sous-entendu, les
coordonnées X, Y et Z) dans un cube de côté 1024. D'ailleurs, vous trouverez dans le source une routine de hasard
à peu près convenable (la technique n'est pas de moi, mais j'ai oublié de qui c'était. De toute façon, je vous
expliquerai la routine ainsi que le principe plus tard). Donc, l'observateur se situe perpendiculairement à l'une
des faces du cube et la ruse consiste à bouger les étoiles elles-mêmes, et non pas le point d'observation.
Avantage : la gestion en est grandement simplifiée.
En effet, si vous allez vers l'avant, les étoiles vont avancer vers vous et vous allez fatalement rencontrer la
face opposée du cube. Conclusion, vous allez voir de moins en moins d'étoiles au fur et à mesure que vous avancerez
vers la face. Il faut donc éviter ce problème, sinon ça ne fait pas très beau. D'où l'avantage de la technique :
en effet, les étoiles qui vont nous dépasser et donc se trouver derrière nous, vont être remises de l'autre côté.
Exemple sur une droite : je suis en 1, l'étoile est en 4 ; quand j'avance, l'étoile vient en 3, puis en 2, en 1,
puis en 0. Au moment où elle atteint le 0, on la remet en 4 et donc je vois toujours une étoile arriver vers moi.
En faisant cela pour les 150 étoiles, vous obtenez le mega extra super d'enfer hyper géant... (à compléter)
effet d'univers 3D alors qu'en fait, vous restez bêtement devant une vulgaire boîte de 1024 de côté.
Vous devez vous demander pourquoi j'ai choisi 1024 comme taille du cube ? Oui, c'est vrai : pourquoi, Maître ?
Eh bien, disciple, pour deux raisons principales (les 36 037 autres étant mineures, quoi que la 8342
soit limite) : la première est que 1024 a l'immense privilège d'être une puissance de 2 (2 puissance 6 exactement)
et qu'en informatique, tout ce qui est relié aux puissances de 2 est d'une grande facilité de gestion. Dans notre
cas, nous avons pu, grâce à ce chiffre particulier, ne pas tester si le point sort ou non du cube :
en effet, nous n'avons qu'à faire sur chaque coordonnée une simple opération logique de type AND 1023,
et la méchante coordonnée qui s'aventure en 1027 par exemple est immédiatement remise en 3.
La seconde raison est que 150 étoiles est juste le nombre qu'il faut dans un cube de 1024 de côté, car si on avait
fait un cube de 2 puissance 32 de côté, on aurait vu une étoile tous les trois ans, ce qui aurait nettement
atténué l'effet du déplacement.
Je vais quand même vous expliquer la 8342e raison, car elle a son importance. En effet, quand on fait joujou
avec la 3D, l'échelle des coordonnées n'est pas du tout imposée par le calcul ou un autre facteur, elle est choisie
selon le goût du programmeur lors de la projection (tranformation des coordonnées 3D en 2D). J'aurais très bien
pu choisir un carré de 2 puissance 24 de côté et adapter les coordonnées des points en conséquence (NDLR :
et augmenter le nombre d'étoiles, ça n'aurait pas marché ?). Pour ce qui est de la gestion de la couleur en
fonction de la distance, cela se révèle assez simple : on se sert de la coordonnée Z de chaque étoile pour connaître
sa distance d'avec l'observateur. Les puristes auraient peut-être préféré faire le calcul suivant X+Y+Z.
En effet, cela aurait donné un chiffre proportionnel à la distance exacte entre chaque étoile et le centre de l'écran,
c'est-à-dire l'observateur... En utilisant la coordonnée Z, on obtient la distance entre chaque étoile et le plan
représenté par l'écran. L'erreur est donc réelle mais elle est si minime que personne ne s'en aperçoit.
De plus, on gagne un temps-machine considérable.
Pour gagner du temps lors de l'affichage (qui prend quand même environ 50% du temps d'exécution total),
j'ai fait une routine de point pour chacune des quatre couleurs utilisées. On y perd
en souplesse, mais on y gagne en rapidité et dans une démo, le temps machine prime. Autre astuce qui alourdit le
programme : chaque étoile doit bien évidemment être affichée puis effacée, et pour disposer de 4 couleurs
d'étoiles (plus le fond), il faut 3 plans de bits. Pour gagner un peu de temps, j'ai défini 4 plans de bits et j'installe la
palette de telle sorte que chacune des 4 couleurs corresponde à un plan de bits différent. Grâce à cela, je trace et efface
chaque étoile sur un seul plan, d'où un gain relativement important. Autre astuce (déjà utilisée des milliers de fois),
je précalcule les adresses de début de lignes ; cela me permet de ne pas avoir à faire une multiplication à chaque
fois que je trace un point.
Pour ceux qui n'ont jamais fait de 3D, je vais expliquer comment on parvient à rendre l'effet de perspective.
Tout part d'un principe simple et connu de tous : la perspective est une notion selon laquelle plus un objet est
loin de l'observateur, plus il lui apparaît petit. C'est donc cette notion qu'il faut simuler. Dans un premier temps,
on attribue à chaque étoile une coordonnée en trois composantes (X, Y, Z), ce qui permet de la situer dans l'espace.
X représente l'horizontale, Y la verticale et Z la profondeur. On s'aperçoit assez vite qu'il faut faire intervenir
Z dans les calculs, car la taille de l'objet dépend directement de la distance qui le sépare de l'observateur.
Le calcul que tout le monde utilise est la division de X et Y par Z (je ne sais pas s'il est juste par rapport
à la réalité, mais en tout cas, il correspond suffisamment bien pour que personne ne voie d'erreur).
Prenons un exemple concret : on est dans une voiture roulant en permanence à 50 km/h et on regarde un panneau
routier se trouvant 300 mètres devant. A 300 m, le panneau grossit lentement, à 100 m, il grossit plus vite et
à 20 m encore plus vite, donc le grossissement ne s'opère pas de façon linéaire et seule la division permet de donner
l'effet voulu. Détail : il faut avant de diviser X et Y, les multiplier par un gros chiffre, sinon on obtiendra un
objet de taille minuscule.
La phrase du mois : "Ton meilleur repos est le sommeil. Tu le provoques souvent intentionnellement, cependant tu
crains la mort, qui n'est rien de plus."
Aujourd'hui, je me sens généreux, je vais donc vous en donner une deuxième : "I'm not prejudice, I hate everybody."
* AUTEUR: j.etienne
* SUJET: deplacement d'etoiles en pseudo 3d
* ASSEMBLEUR: devpack 2.14
* UTILISATION: Les touches du pave numerique
OPT C- * opt case off
incdir 'include:'
include 'exec/exec_lib.i'
include 'hardware/custom.i'
custom = $dff000
execbase = 4
BPL_X = 320
BPL_Y = 256
BPL_DEPTH = 4
BPL_WIDTH = BPL_X/8
BPL_SIZE = BPL_WIDTH*BPL_Y
SCR_SIZE = BPL_SIZE*BPL_DEPTH
NB_STAR = 180
vsync: macro
.wait_vsync\@:
move.l vposr(a5),d0
and.l #$1ff00,d0
cmp.l #\1*$100,d0
bne.s .wait_vsync\@
endm
******************************************************
************** programme principal *****************
******************************************************
move.l (execbase).w,a6
lea custom,a5
CALLEXEC Forbid
move.w #$03e0,dmacon(a5) * all dma off except disk
move.w #(BPL_DEPTH<<12)+$200,bplcon0(a5)
clr.w bplcon1(a5)
clr.w bplcon2(a5)
move.w #BPL_WIDTH*(BPL_DEPTH-1),bpl1mod(a5)
move.w #BPL_WIDTH*(BPL_DEPTH-1),bpl2mod(a5)
move.w #$2981,diwstrt(a5) *\
move.w #$29c0,diwstop(a5) * > init un ecran 320*192
move.w #$0038,ddfstrt(a5) * > avec un mot en plus pour
move.w #$00d0,ddfstop(a5) */ le shift
bsr BUILD_COPLIST
move.l coplist_adr,cop1lc(a5) * > run my coplist
clr.w copjmp1(a5) */
move.w #$8380,dmacon(a5) * dma blitter,copper & bitplane on
* init color map
COLOR_1 = $fff
COLOR_2 = $bbb
COLOR_3 = $888
COLOR_4 = $444
DUMMY SET 0
move.w #0,color+DUMMY(a5)
DUMMY SET DUMMY+2
move.w #COLOR_4,color+DUMMY(a5)
DUMMY SET DUMMY+2
rept 2
move.w #COLOR_3,color+DUMMY(a5)
DUMMY SET DUMMY+2
endr
rept 4
move.w #COLOR_2,color+DUMMY(a5)
DUMMY SET DUMMY+2
endr
rept 8
move.w #COLOR_1,color+DUMMY(a5)
DUMMY SET DUMMY+2
endr
* init the first speed
move.w #3,SPEED_X
move.w #3,SPEED_Y
move.w #-4,SPEED_Z
* init the first coords
bsr INIT_TAB_STAR
MAIN_LOOP:
vsync $f
bsr BUILD_COPLIST
* double buffering for tab
move.l LOG_TAB_ADR_POINT,d0
move.l PHY_TAB_ADR_POINT,LOG_TAB_ADR_POINT
move.l d0,PHY_TAB_ADR_POINT
* double buffering for screen
move.l LOG_SCR_ADR,d0
move.l PHY_SCR_ADR,LOG_SCR_ADR
move.l d0,PHY_SCR_ADR
bsr GESTION_KEYBOARD
bsr CLEAR_STAR
bsr AFF_STAR
move.w #$f00,color(a5) *\
move.w #$2f,d0 * > cree une barre de couleur pour
dbf d0,* * > montrer le temps d'execution
clr.w color(a5) */
move.b $bfec01,d0 *\
not d0 * >capture the key wich is pressed
ror.b #1,d0 */ and put is code RAW in d0
cmp.b #$45,d0 *\
beq INIT_END */ sort si on press sur esc
btst #6,$bfe001
bne MAIN_LOOP
bra INIT_END
CLEAR_STAR: *******************************************************
* this routine clears all the old stars which were on this screen
move.l LOG_TAB_ADR_POINT(pc),a0
tst.l (a0) *\ dont clear the page before
beq.s .END_CLEAR_STAR */ the first stars
move.l #NB_STAR-1,d0
moveq #0,d1
.LOOP_CLEAR_STAR:
move.l (a0)+,a1
move.b d1,(a1)
dbf d0,.LOOP_CLEAR_STAR
.END_CLEAR_STAR
rts
**************************************************** END_CLEAR_STAR
GESTION_KEYBOARD: **************************************************
* this routine computes the speed according to the keyboard
ACC = 1
KEY_DEC_X = $3D
KEY_STP_X = $3e
KEY_INC_X = $3f
KEY_DEC_Y = $2d
KEY_STP_Y = $2e
KEY_INC_Y = $2f
KEY_DEC_Z = $1d
KEY_STP_Z = $1e
KEY_INC_Z = $1f
moveq #0,d0
move.b $bfec01,d0 *\
not d0 * >capture the key wich is pressed
ror.b #1,d0 */ and put is code RAW in d0
* gestion des touches pour le x
cmp.b #KEY_DEC_X,d0
bne.s .DONT_DEC_X
sub.w #ACC,SPEED_X
.DONT_DEC_X
cmp.b #KEY_STP_X,d0
bne.s .DONT_STP_X
clr.w SPEED_X
.DONT_STP_X
cmp.b #KEY_INC_X,d0
bne.s .DONT_INC_X
add.w #ACC,SPEED_X
.DONT_INC_X
* gestion des touches pour le y
cmp.b #KEY_DEC_Y,d0
bne.s .DONT_DEC_Y
sub.w #ACC,SPEED_Y
.DONT_DEC_Y
cmp.b #KEY_STP_Y,d0
bne.s .DONT_STP_Y
clr.w SPEED_Y
.DONT_STP_Y
cmp.b #KEY_INC_Y,d0
bne.s .DONT_INC_Y
add.w #ACC,SPEED_Y
.DONT_INC_Y
* gestion des touches pour le x
cmp.b #KEY_DEC_Z,d0
bne.s .DONT_DEC_Z
sub.w #ACC,SPEED_Z
.DONT_DEC_Z
cmp.b #KEY_STP_Z,d0
bne.s .DONT_STP_Z
clr.w SPEED_Z
.DONT_STP_Z
cmp.b #KEY_INC_Z,d0
bne.s .DONT_INC_Z
add.w #ACC,SPEED_Z
.DONT_INC_Z
rts
SPEED_X ds.w 1
SPEED_Y ds.w 1
SPEED_Z ds.w 1
*********************************************** END_GESTION_KEYBOARD
AFF_STAR: *****************************************************
* This routine computes the coord and displays each star
DELTA_Z = 250
move.l #NB_STAR-1,d7
move.l LOG_TAB_ADR_POINT(pc),a1
lea TAB_STAR(pc),a2
lea SPEED_X(pc),a3
move.w #$03ff,d4
move.w #$01ff,d3
.LOOP_EACH_STAR:
* compute the new coord for each star
movem.w (a2),d0-d2
add.w (a3),d0
add.w 2(a3),d1
add.w 4(a3),d2
and.w d4,d0 *\ quand une etoile sur du cube, on la
and.w d4,d1 * > remet dedans.
and.w d4,d2 */
move.w d0,(a2)+
move.w d1,(a2)+
move.w d2,(a2)+
* compute the transformation in plane
sub.w d3,d0
sub.w d3,d1
ext.l d0
ext.l d1
asl.l #7,d1
asl.l #7,d0
add.w #DELTA_Z,d2
divs d2,d0
divs d2,d1
add.w #BPL_X/2,d0
add.w #BPL_Y/2,d1
* clip the point: test if the point is in or out of screen
tst.w d0
blt .POINT_NOT_ON_SCREEN
cmp.w #BPL_X-1,d0
bgt .POINT_NOT_ON_SCREEN
tst.w d1
blt .POINT_NOT_ON_SCREEN
cmp.w #BPL_Y-1,d1
bgt .POINT_NOT_ON_SCREEN
* compute the adr and the shift for the point
move.l LOG_SCR_ADR,a0
lsl.w #2,d1
add.l ADR_LINE_ACORDING_TO_Y(pc,d1.w),a0
move.w d0,d1
lsr.w #3,d1
lea (a0,d1.w),a0
and.w #%111,d0
neg.w d0
addq.w #7,d0
* display the point
* the color is chosen according to the Z coord.
* test if this color 1
cmp.w #1*1024/4+DELTA_Z,d2
bgt.s .NOT_COLOR_1
lea 3*BPL_WIDTH(a0),a0
move.l a0,(a1)+ * save adr to clear this point later
bset d0,(a0)
dbf d7,.LOOP_EACH_STAR
rts
.NOT_COLOR_1
* test for color 2
cmp.w #2*1024/4+DELTA_Z,d2
bgt.s .NOT_COLOR_2
lea 2*BPL_WIDTH(a0),a0
move.l a0,(a1)+ * save adr to clear this point later
bset d0,(a0)
dbf d7,.LOOP_EACH_STAR
rts
.NOT_COLOR_2
* test for color 3
cmp.w #3*1024/4+DELTA_Z,d2
bgt.s .NOT_COLOR_3
lea 1*BPL_WIDTH(a0),a0
move.l a0,(a1)+ * save adr to clear this point later
bset d0,(a0)
dbf d7,.LOOP_EACH_STAR
rts
.NOT_COLOR_3
* color 4. no test because that's the last possibility
move.l a0,(a1)+ * save adr to clear this point later
bset d0,(a0)
dbf d7,.LOOP_EACH_STAR
rts
.POINT_NOT_ON_SCREEN
move.l LOG_SCR_ADR,(a1)+
dbf d7,.LOOP_EACH_STAR
rts
*************************************************** END_AFF_STAR
* Ce tableau contient l'adresse de chaque ligne relative au debut de l'ecran
ADR_LINE_ACORDING_TO_Y:
DUMMY SET 0
REPT BPL_Y
dc.l DUMMY
DUMMY SET DUMMY+BPL_WIDTH*BPL_DEPTH
ENDR
INIT_TAB_STAR: ******************************************************
* This routine computes the coords X Y Z of each star
bsr AM_INIT_RANDOM
lea TAB_STAR(pc),a0
move.w #NB_STAR-1,d1
.LOOP_INIT_STAR:
* choose X at random
bsr RANDOM
and.l #$3ff,d0
move.w d0,(a0)+
* choose Y at random
bsr RANDOM
and.l #$3ff,d0
move.w d0,(a0)+
* choose Z at random
bsr RANDOM
and.l #$3ff,d0
move.w d0,(a0)+
dbf d1,.LOOP_INIT_STAR
rts
*************************************************** END_INIT_TAB_STAR
RANDOM: ********************************************************************
* OUT: d0 = Random Number 24 bits
* t= a * ( z % q ) - r * int ( z / q )
* if t > 0
* z=t
* else
* z=t+m
* endif
* pour plus d'explication sur la routine voir st magazine n~45 page 108
RND_A equ $41a7
RND_M equ $7ffffff
RND_Q equ $f31d
RND_R equ $8b14
movem.l d1-d3,-(sp)
move.l #RND_Q,d0
move.l RND_Z,d1
moveq #0,d3
divu d0,d1
bvc.s .RESULT
move.l d1,d2
clr.w d1
swap d1
divu d0,d1
move.w d1,d3
move.w d2,d1
divu d0,d1
.RESULT:
move.l d1,d0
swap d1
move.w d3,d1
swap d1 * d1= q1 q0 = int ( z / q )
clr.w d0
swap d0 * d0= 0 r0
mulu #RND_A,d0 * d0= a * ( z % q )
move.w d1,d3
swap d3
mulu #RND_R,d3
swap d3
clr.w d3
mulu #RND_R,d1
add.l d1,d3
sub.l d3,d0
move.l d0,RND_Z
movem.l (sp)+,d1-d3
rts
**************************************************************** END_RANDOM
AM_INIT_RANDOM: ***********************************************************
* cette routine initialise la semence de RANDM
* Il est imperativement necessaire de lancer cette routine avant de se
* servir de RANDOM sinon RANDOM donnera toujours la meme suite de chiffre
* a chaque fois que vous lancerz le programme
move.l VPOSR+CUSTOM,d0
and.l #RND_M,d0
cmp.l #RND_M-1,d0
blt.s .SEMENCE_LT_RANDM_M
subq.l #2,d0
.SEMENCE_LT_RANDM_M:
addq.l #1,d0
move.l d0,RND_Z
rts
RND_Z: ds.l 1 * BUG GNAB GIB SI ON PLACE CETTE
* LIGNE PLUS HAUT LE PROGRAMME PLANTE. L'option o+ code l'instruction coe si
* si l'adr etait absolu court.
********************************************************** END_AM_INIT_RANDM
INIT_END: *********************************************************
* reactivation de l'ancienne coplist
lea GfxName(pc),a1 * nom de la library ds a1
moveq #0,d0 * version 0 (the last)
CALLEXEC OpenLibrary * lib graphique ouverte
move.l d0,a4 * adr de graphicbase ds a4
move.l 38(a4),cop1lc(a5) * chargement de l'adr de
clr.w copjmp1(a5) * l'old coplist et lancement
move.w #$83e0,dmacon(a5) * activation des canaux dma necessaires
CALLEXEC Permit * multi switching autorise
* et on retourne d'ou l'on vient.
moveq #0,d0 * flag d'erreur desactive
rts
GfxName: dc.b "graphics.library",0
EVEN
******************************************************* end_INIT_END
BUILD_COPLIST: ********************************************************
move.l COPLIST_ADR,a0
move.l PHY_SCR_ADR,d2
moveq #BPL_DEPTH-1,d0
move.w #bplpt,d1
.LOOP_INIT_BPL_IN_CLIST
move.w d1,(a0)+
addq.l #2,d1
swap d2
move.w d2,(a0)+
swap d2
move.w d1,(a0)+
addq.l #2,d1
move.w d2,(a0)+
add.l #BPL_WIDTH,d2
dbf d0,.LOOP_INIT_BPL_IN_CLIST
move.l #$fffffffe,(a0)+ * montre la fin de la clist
rts
********************************************************* END_BUILD_COPLIST
TAB_STAR: ds.w 3*NB_STAR
LOG_TAB_ADR_POINT: dc.l TAB_ADR_POINT_1
PHY_TAB_ADR_POINT: dc.l TAB_ADR_POINT_2
TAB_ADR_POINT_1: ds.l NB_STAR
TAB_ADR_POINT_2: ds.l NB_STAR
******** datas
* variables
LOG_SCR_ADR: dc.l ecran1
PHY_SCR_ADR: dc.l ecran2
COPLIST_ADR: dc.l coplist
section ZONE_CHIP,BSS_C
ecran1: ds.l (scr_size*2)/4
ecran2: ds.l (scr_size*2)/4
coplist: ds.l 1000*4 * taiile inconnue donc on prevoit gros
end
|
|