From 0a1a3d090f14697dc35add4914d7d5515e57fbf2 Mon Sep 17 00:00:00 2001 From: milang Date: Wed, 28 Aug 2019 15:29:06 +0200 Subject: [PATCH] add correct makefile for fxengine as lib --- Makefile | 45 ++++++++++++ build/src/event/keyboard.c.o | Bin 0 -> 880 bytes build/src/render/translate.c.o | Bin 0 -> 2936 bytes build/src/render/zbuffer.c.o | Bin 0 -> 768 bytes include/event/keyboard.h | 27 ++++++++ include/render/parameters.h | 17 +++++ include/render/translate.h | 61 ++++++++++++++++ include/render/zbuffer.h | 21 ++++++ libfxengine.a | Bin 0 -> 5042 bytes src/event/keyboard.c | 33 +++++++++ src/render/translate.c | 123 +++++++++++++++++++++++++++++++++ src/render/zbuffer.c | 42 +++++++++++ unused temp.tar.xz | Bin 0 -> 4740 bytes 13 files changed, 369 insertions(+) create mode 100644 Makefile create mode 100644 build/src/event/keyboard.c.o create mode 100644 build/src/render/translate.c.o create mode 100644 build/src/render/zbuffer.c.o create mode 100644 include/event/keyboard.h create mode 100644 include/render/parameters.h create mode 100644 include/render/translate.h create mode 100644 include/render/zbuffer.h create mode 100644 libfxengine.a create mode 100644 src/event/keyboard.c create mode 100644 src/render/translate.c create mode 100644 src/render/zbuffer.c create mode 100644 unused temp.tar.xz diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..36f0451 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# fxengine makefile +# fxengine needs gint (@Lephenixnoir) + +target ?= sh3eb-elf + +cflags := -m3 -mb -D FX9860G -ffreestanding -nostdlib -fstrict-volatile-bitfields -Wall \ + -Wextra -Os -I . + +lib := libfxengine.a +header := include/ + +prefix := $(shell $(target)-gcc -print-search-dirs | grep install \ + | sed 's/install: //') + +ifeq "$(prefix)" "" +$(error "Can't find install directory") +endif + +src := $(shell find src -name '*.c') + +obj := $(src:%=build/%.o) + + +all: $(lib) + +$(lib): $(obj) + $(target)-ar rcs $@ $^ + + +build/%.c.o: %.c + @ mkdir -p $(dir $@) + $(target)-gcc -c $< -o $@ $(cflags) + +clear: + @ rm -rf build + @ rm -f $(lib) + +%/: + mkdir -p $@ + + +install: + sh3eb-elf-ar -t $(lib) + cp $(lib) $(prefix) + cp -r $(header) $(prefix)include/fxengine \ No newline at end of file diff --git a/build/src/event/keyboard.c.o b/build/src/event/keyboard.c.o new file mode 100644 index 0000000000000000000000000000000000000000..ad82dd2b2e664fdd5205cf946ded1fd0c0ed4778 GIT binary patch literal 880 zcmb7CJ5S?45T3Pp*nkucia?;q0Yc$Gi;)lrM3IB=I9&u49g?+9jF94x5E}#)9TIZ}ve}F%LbOm%!<~WI(Uyz%zGsYU9n*{7!;^w%|!f#r1*GS>LIY0c#gar<@T z`t3X7fB(Z8Cie?ke^V1bntfXOwAEaLOm|vj3Z^w)FrPH5m^Ywb9ERBKjg8f8ZhLos zCc7dRq{Rg~WjuEmwBC~;z2i)DAo@NCjV`nXB9Hcng~*Nx-JGxL!?_!LHXl&pmUGgU+z;280cZ7XS=C z-wuw)dOg;qe^CvT1JVx~kvw7z`zRT>=K;vF3KU4k_d%9w)hhM;6lD=jy?feOv>C|~ zd~ajuCq0jhB!1y<3YqdKpJH_1(`}pIgK$aWbIzeN+Qle}IPEb3EA1-JhN?Kg-%n&Fp6?4flDI!;uQCW_i2DyRFI~?7 literal 0 HcmV?d00001 diff --git a/build/src/render/translate.c.o b/build/src/render/translate.c.o new file mode 100644 index 0000000000000000000000000000000000000000..ca6c2935abf2749b09763f8249adccb2ac0f5ec6 GIT binary patch literal 2936 zcmb7GYfMvD96#;l^14ig3(^Yg@-Pk`wDQQNXyO%oAQ(Ed4?oP5w&%7KTR>VD`Ggb0 z{4j%VPMvd`6EnBW1QU&)O}Z>**%G7%d1-VgGy7xdGc@vuZId58=U>)J+5?f_= zs$rEk;)xuHv_{+!#I!_kiBRbb>|$I&J2h~}!7h@=94tyBncYe&S}<-a(-6#Six$KY zI5O&c^=Fh^^;3g9WXq58JkvebLV4IJ^cTWC^^6eUGf${-&p@Q~{9yHP^}S8J$cOd7Pmm*Qr+lxPE04eKn`~k46jbEH>U+rQ+ainC?+V}^@C^(V zkXfFh|1?k5+vgehqq#(?7pcn;@`1MHMXYubBYk7#vdt2aK#40_u&rm$Nulup>60RAy>|&}k#!tAcrtdkGh>vpFO_w;ROOtpAW11PN#{nVeq-vnn=F_105;U`*i3GPvRj!39 z57h`P1{-~`eK4T1lMc8}}?7vbX1vf~MYDuK?p!qr)sXP|R9v0Xd22+oc%@OfMlk`b3w$N1@$?78jXKA z7fSo?tWcW)eg)=fu!0r5 zVKq;(vn*(rXt_G3i(jpN*i65g%D9^`n4!(=-mrN8__ZyO_v zp7@^atgWpsEZx~?T3xtNSFT&1;3tdG_c{r^4#k^Li{5hbkdwby10e~dcVs350uebI zIyp;qvMj4bXF4hX0RLnd3KVXc1l~)KqiM_f>I8kQEMLo#@--yr_a(@-1o=RMd^kZq z3i34w&CBsW1^f)SQd#~i!Tu~lYWOWtos{)ICFpO!%6|m?Rat*4L4RA8Q!^1F^%{bn zO1doH158RG$^K4To6lqO3pxj~V!`9LA*|!zb`oi|x)cZRA&>0f^t9XjJ~ws}L+t)oq#f-(Wc9h*2Fa6@99R>-i!Eq-8y2%SVIT{)i@5K2?< zWjJCAzKRedW7RD_LxeIF{1~Au8LOf8N_;h(ZC2(f z*!Ls_L!OE+bvhQ4Iu~aXmp^q8p&Z2*){-k@x+)f*-T+rg;Wx)((tIo?T^Fkfy*rNo z5FxGPtMhev{Wd$&`TU*I*rK3L!DG|;g~NWN!!|fKI=jz@be-*z;Ar=Hg*HF>U#$?V z$-LxVlLqUdjS>{UNixoZkzihuQ=7qU8{ib^#GmLgps$i;`Q4$9gB;)F`k-|{k~pGi^dJC|3;ExY#9Jm~ hIX93{$}#wi6C^hhi!`|6${L86b{Rf31j$Xb`xhu_f1&^Y literal 0 HcmV?d00001 diff --git a/build/src/render/zbuffer.c.o b/build/src/render/zbuffer.c.o new file mode 100644 index 0000000000000000000000000000000000000000..cc01f0ef7feb96be73f450a557572567316f2022 GIT binary patch literal 768 zcmb<-^>JflVq|~=Mg}b)8(H80kk8Cu0#>2Hz`?+Nk>eS|B;M!Tk9Q;_HwY#aHwYyd zD<(JzGzj;9D`a8dX5ds{=wPq^|A5Vf*Cghus0)XwPfK7@Kq8NmMoW;RhLePnbRthn zaH52wb`p=GwqaVo$U}xnBFV|c|Bcl;`4W>IH60~Y1weFyFp!jN2uu)e2m;y`{E(4> zJ3*LxJIH(ncV}lS1r2w`QEFaFYEe8?BtAJOHL(aov^cdSzMz7E zL9e(nwJDNlX`n(-Sg=7g zfMT=*$_7y&zkvYAKOl85zwZGFA_0&b2&e-w2M`ldrvp?2(gTYgka@tMf&-8o3?qmC w1)z8X5QE$c!XWnv05K;J!|Vhy83TZX1rWmk%zc7D0c3R`zna1n0co&00M`~_EC2ui literal 0 HcmV?d00001 diff --git a/include/event/keyboard.h b/include/event/keyboard.h new file mode 100644 index 0000000..e910d92 --- /dev/null +++ b/include/event/keyboard.h @@ -0,0 +1,27 @@ +#ifndef FE_KEYBOARD +#define FE_KEYBOARD + +#include +#include +#include + +/* FE_keyboard: gestion evenementielle du clavier + on peut assigner des callbacks à certains evènements définis dans gint + les arguments envoyés sont le code de la touche en question (event.key) + le type d'evenement (event.type) + void (*callback)(void) + la fonction à exécuter en cas de pression sur une touche + la fonction reload est appelée à la demande de l'utilisateur et appelle tous les callbacks dans l'ordre */ + +typedef void (*callback)(void); + +void event_keyboard_set_key(uint32_t matrix_code, uint32_t ev_type, callback function); + +// reload all key events and call callbacks +void event_keyboard_reload(); + +//void event_keyboard_start(); + +//void event_keyboard_stop(); + +#endif \ No newline at end of file diff --git a/include/render/parameters.h b/include/render/parameters.h new file mode 100644 index 0000000..02cb5bd --- /dev/null +++ b/include/render/parameters.h @@ -0,0 +1,17 @@ +#ifndef RENDER_PARAM +#define RENDER_PARAM + + +// Render param + +#define render_width 128 +#define render_height 64 +#define render_x_mid ((render_width - 1) / 2) // depends on screen width +#define render_y_mid ((render_height - 1) / 2) + +#define render_max_dist 3000 +#define render_min_dist 1 + + + +#endif \ No newline at end of file diff --git a/include/render/translate.h b/include/render/translate.h new file mode 100644 index 0000000..bef0e4d --- /dev/null +++ b/include/render/translate.h @@ -0,0 +1,61 @@ +#ifndef RENDER_TRANSLATE_H +#define RENDER_TRANSLATE_H + +#include +#include + +/* FE_position: + notion de point dans l'espace simple */ +typedef struct FE_integer_position FE_integer_position; +struct FE_integer_position +{ + int32_t x, + y, + z; +}; + +typedef struct FE_floating_position FE_floating_position; +struct FE_floating_position +{ + double x, + y, + z; +}; + + + +/* FE_point: + notion de point dans l'espace destiné à etre utilisé dans l'affichage */ +typedef struct FE_integer_point FE_integer_point; +struct FE_integer_point +{ + FE_integer_position real, + translated; +}; + + + +// applique la matrice de rotation et les deltas sur les coordonnées d'un point +void render_translate(FE_integer_point * point); + + + +// change la matrice de rotation et les deltas pour le cycle à venir +void render_set(const double dh, const double dv, const double roulis, const FE_integer_position * camera); + + + +// constantes mathématiques + +extern const double pi, pi2, pi_sur_2; + + +// fonctions mathématiques + +double modulo_2pi(double a); + +double cos(double angle); + +double sin(const double angle); + +#endif \ No newline at end of file diff --git a/include/render/zbuffer.h b/include/render/zbuffer.h new file mode 100644 index 0000000..d637339 --- /dev/null +++ b/include/render/zbuffer.h @@ -0,0 +1,21 @@ +#ifndef RENDER_ZBUFFER +#define RENDER_ZBUFFER + +#include +#include + +/** FE_zbuffer_clear + * effacer le z buffer pour un nouveau cycle de dessin + * TODO : ajouter effacement avec le DMA Controller pour les modèles ayant un processeur SH4-A +**/ +void render_zbuffer_clear(); + +#include +/** FE_zbuffer_set_dist + * change la distance d'un pixel du zbuffer + * retourne true si il faut dessiner le pixel + * retourne false si le pixel est déjà existant +**/ +bool render_zbuffer_set_px(uint32_t x, uint32_t y, uint32_t dist); // if you are allowed to draw the pixel on vram + +#endif \ No newline at end of file diff --git a/libfxengine.a b/libfxengine.a new file mode 100644 index 0000000000000000000000000000000000000000..bc449350cbdbcd02e1be50db95eb658b067bdba9 GIT binary patch literal 5042 zcmcf_ZERE5_1Z5ccAg;#FAzH)drn|96o~Uh2m!5*6CjX?5by^-K=!lWa}uzV5E}&h zldM6EN~=O?N4u`;SW~T=R*6cjy8Y-6v`kYsby6T5B!pI^p@8U1be$z06b=lm8E9daJI0Y;u}N zK}C_%RI%P+)V*C|dFgfo$X~b#I!x+lBsTw5cInQkEPejb`Khg>z>u#iXfjn4HJL8y zD_R{!!s^~(8O)=2ZxSz$o`}SR04a~g`jonpVz6n2@|biyM#@DYCXn(#G)l^Y zJ&_2G`Gdd)(m3`KwHNLTtqSV5LZBn=fC8ZV1AJCP!G72udFbyoYMZVH%4vblb3G81 zy{K?OPCu#*k=4b~^cYSrfITIH+6pW7-UMrl;pD4U; z`C`RliI=%x+`mew?l-ori6p{_!-+$Qt^{E_6XGhVIyADA?HCI%7cK?41?_7=PKo=A zrjj{V(*_Ir#$hd)lWU-1jb{tB)qZQ#0oFPu91gbsekT*=ZjswW9Nfi9BaYmc_2S@# zMA@68^;hbzZgI%8YxNWLUv8P)GE^TL6bGdd;MDA5?+}rZxKJb5#a*rqGZAh&Tc|#w z({18QG75IHFngwh6GvKDY0NXzF(@)SS&936JyLhEUz1=wz(l|ESD&6An(pK-6;~C( z><4l7-HC73{JZ~LhyuaC9?3!`MTUd+1^EUMPEowXAK+DEfYxJD~-YZFc z#T{IT5l7H6s;>hy?kL{1V#s2NUC9@xx3ki)h1t%EQAHr*yd|%{Xz?yM z^rvO-=?cEeZrG6do`usDG;Fx=wIwgLmg>6U*_QXL*3(H;BXbQlz>c0Hw$?+s%0If=3=Uv+lVG=XIy+p=W0GKfQ7H z+**AzYt7wvFEs5+bc|VRCbzopTDMvCu34aSo*8dk)+~?dx));Hi56HhaRmDR#~YW% ztV=*H-!mJ55xP0ow=lweaZH*HF|ypS=OK(U=YC){f}AxFhn};mpZDIiW?2hC_K{}> zRx2Li&{A<&Sw-yW`LFj8&d_Rxe#M?ykb)U3l6r{NdFQOltb8+j!m(C=!*BTRmicq@ zsM(s^U$=1m9P2t0$wg5R^FSJb#u1)fc#dK0$>-VsCr4STnq!6w1@EXlD7crJ)war( zFC)xZk^q+z-o3??fR`Ji;?5-vCX?sR;RnDo*Wt9r6IUOLS*c`9G%dKLSrc4xoEqSeWlhSXfgHWRMttp)bM@pxmIpq^WtA@UZ5UbMn2j}2@JxW6U zta5RKi=-e!MbmO|qaW6;33!=M&~B77LrKfU4X1&o1=T-fJrFTQ)O=T#d)&A*7gzIa zSq@?~3DmyLKtbIV3de9BY6#aPAdePJ&TjY!$fLkRTQE;YeK{(w$;C5I0&Nls1v-zk zoZa*;2so(_)UurV3a8KOC%&&Y)z+zhPdrV|jxhv&gq;Xb1~bXLWV}VrF1^i8|HU61 z$n{4Dmiwzq{LY*KZYXgigFt3yBd0U_=Wi@&T3U19-z%Do8(UAd2lfbNXW7ZU4P{PC zX`x^~xlgc^tP7Y+)>Vdv@}|hyykIaoU%lp~WkRrFZG-j4IavES_*w_r{oKG_7;WE_ zj<9}?{WIz(&;CBstew1hG;1Wcas61?6RGpQMD`0Ie);@PE=KZLd%8bKz9%l9&9j(b zjNyL4I~D;~R2x4yHw2sy%F(%hf + +static callback callbacks[3][6][10]={0}; + +static inline uint32_t get_x(const uint32_t matrix_code) +{ + return (matrix_code-1) % 0x10; +} + +static inline uint32_t get_y(const uint32_t matrix_code) +{ + return (matrix_code-1) / 0x10; +} + +void FE_keyboard_reload() +{ + key_event_t event; + while (1) + { + event=pollevent(); + event.type--; + if (event.type==-1) + break; + callback action = callbacks[event.type][get_x(event.key)][get_y(event.key)]; + if (action) + action(); + } +} + +void FE_keyboard_set_key(uint32_t matrix_code, uint32_t ev_type, callback function) +{ + callbacks[ev_type-1][get_x(matrix_code)][get_y(matrix_code)]=function; +} \ No newline at end of file diff --git a/src/render/translate.c b/src/render/translate.c new file mode 100644 index 0000000..b6b203f --- /dev/null +++ b/src/render/translate.c @@ -0,0 +1,123 @@ +#include + +#include +#include + +const double pi = 3.141592653589793238462643383279; +const double pi2 = pi * 2; +const double pi_sur_2 = pi / 2; + + +static double reducted_cos(const double a) +{ + double u= 1.0; + const double a2 = a * a; + for(int32_t p = 15; p>=1; p -= 2) + u = 1 - a2 / (p * p + p) * u; + return u; +} + +// return a with -pi<=api) + a -= pi2; + return a; +} + +static double cos_recursive(double angle) +{ + if (angle<0) + return cos_recursive(-angle); + if (angle>=pi_sur_2) + return -reducted_cos(angle - pi); + return reducted_cos(angle); // OK +} + +double cos(double angle) +{ + angle = modulo_2pi(angle); + return cos_recursive(angle); +} + +double sin(double angle) +{ + return cos(angle - pi_sur_2); +} + + +#define sgn(x) (x>=0?x:-x) + +static double matrice[3][3]= +{ + {0,0,0}, + {0,0,0}, + {0,0,0} +}; + +static FE_integer_position delta; + + +void render_translate(FE_integer_point * point) +{ + static FE_integer_position temp; + temp.x = point->real.x - delta.x; + temp.y = point->real.y - delta.y; + temp.z = point->real.z - delta.z; + + point->translated.x = (double)(matrice[0][0]*(double)temp.x + matrice[0][1]*(double)temp.y + matrice[0][2]*(double)temp.z); + point->translated.z = (double)(matrice[1][0]*(double)temp.x + matrice[1][1]*(double)temp.y + matrice[1][2]*(double)temp.z); + point->translated.y = (double)(matrice[2][0]*(double)temp.x + matrice[2][1]*(double)temp.y + matrice[2][2]*(double)temp.z); + + //point->translated.x*=10; + //point->translated.y*=10; + point->translated.x*=64; + point->translated.y*=64; + if (point->translated.z>0) + { + point->translated.x/=point->translated.z; + point->translated.y/=point->translated.z; + } + else + { + point->translated.x*=32768*sgn(point->translated.z); + point->translated.y*=32768*sgn(point->translated.z); + } + //(point->translated.x*1024)/point->translated.z; + //(point->translated.y*1024)/point->translated.z; + + point->translated.x+=render_x_mid; + point->translated.y+=render_y_mid; +} + +void render_set(const double dh, const double dv, const double roulis, const FE_integer_position * camera) +{ + const double A=cos(dv); + const double B=sin(dv); + + const double C=cos(roulis); + const double D=sin(roulis); + + const double E=cos(dh); + const double F=sin(dh); + + // raccourcis + const double AD=A*D, BD=B*D; + + matrice[0][0]=C*E; + matrice[0][1]=-C*F; + matrice[0][2]=D; + + matrice[1][0]=BD*E+A*F; + matrice[1][1]=-BD*F+A*E; + matrice[1][2]=-B*C; + + matrice[2][0]=-AD*E+B*F; + matrice[2][1]=AD*F+B*E; + matrice[2][2]=A*C; + + // assigner delta + memcpy(&delta, camera, sizeof(FE_integer_position)); +} \ No newline at end of file diff --git a/src/render/zbuffer.c b/src/render/zbuffer.c new file mode 100644 index 0000000..cfd6fab --- /dev/null +++ b/src/render/zbuffer.c @@ -0,0 +1,42 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + + + +static const int size_uint32 = render_width * render_height; + +static const int size_char = size_uint32 * sizeof(uint32_t); + +// zbuffer et clear val sont 32B alignés pour ajouter éventuellement le DMA + +static int32_t *zbuffer = (void *)0x88080000 - ((size_char / 32) * 32 + 1); + +GALIGNED(32) GSECTION(".rodata") static const int32_t clearval[8]={render_max_dist,render_max_dist,render_max_dist,render_max_dist,render_max_dist,render_max_dist,render_max_dist,render_max_dist}; + +void render_zbuffer_clear() +{ + uint32_t indice = 0; + + for (indice = 0; indice < size_uint32; indice ++) + zbuffer[indice] = render_max_dist; +} + +bool render_zbuffer_set_px(uint32_t x, uint32_t y, uint32_t dist) +{ + const int indice = x * render_height + y; + + if (zbuffer[indice]>dist && dist>=render_min_dist && dist<=render_max_dist) + { + zbuffer[indice] = dist; + return true; + } + + return false; +} \ No newline at end of file diff --git a/unused temp.tar.xz b/unused temp.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..da5c5652a042d38ec930bf03464ebfd78666a11b GIT binary patch literal 4740 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