Bonjour à tous,
c'est mon tout premier programme en C posté ici, donc un peu d'indulgence ;-)
Il s'agit d'un programme de démonstration OpenGl/GLUT qui affiche une famille d'IFS (Iterated Function System) d'une manière que l'auteur (moi!) espère harmonieuse. Ce sont des ensembles fractaux, colorés et animés dans le temps.
Le programme essaie d'adapter la qualité (complexité) de l'image aux performances de la machine, ainsi que la densité de l'image pour qu'elle reste à peu près uniforme. Au démarrage l'image est peu détaillée, elle va progressivement le devenir au fur et à mesure que le programme détecte que les performances de la machine sont suffisantes pour suivre (en général au bout d'une quinzaine de secondes).
Il ne faut pas demander plus de frames par secondes que la fréquence de l'écran en mode DoubleBuffered (sinon le programme va croire que la machine rame et diminuer la qualité).
Le source a été pensé pour pouvoir aussi être compilé en tant qu'écran de veille (uniquement sous Windows): il suffit de définir
#define SCREENSAVER
pour que ça le compile en écran de veille (il faudra aussi changer l'extension du fichier compilé en .scr, et faire un clic droit dessus pour l'installer sous Windows).
En mode normal (pas SCREENSAVER) ou en mode configuration de l'écran de veille il faut appuyer sur ESCAPE pour afficher/cacher le menu. Les paramètres peuvent être enregistrés dans un fichier ini du même nom que le programme, et automatiquement chargés au démarrage (si le fichier existe).
Il y a encore deux options de compilation supplémentaire: USE_TEXTURE et USE_MULTIPLE_TEXTURES, la seconde nécessite la première. Avec USE_TEXTURE il est possibe de faire un motion blur plus "smooth" utilisant une texture. La seconde nécessite OpenGl 1.1 au moins, et utilise 2 textures pour le motion blur et le blending par couches, afin de permettre un filtrage des couleurs plus riche. À réserver aux cartes graphiques récentes...
Noter que pour pouvoir compiler il faut que vous ayez installé GLUT. Par exemple comme expliqué ici sous dev-cpp:
http://people.bath.ac.uk/ab8lam/computing/DevCpp.htm.
Ou encore pour d'autres compilateurs:
http://www.math.u-psud.fr/~feuvrier/enseignement/2007/M2/site005.html
En théorie le source compile tant sous Windows que sous Linux (à condition de mettre les bonnes lib, comme indiqué dans les liens).
J'ai mis le .exe (renommé en .ex_) et le .scr (l'écran de veille compilé) dans le zip au cas où...
Si vous avez des commentaires, n'hésitez pas!
Source / Exemple :
#define USE_COS_TABLE
#define USE_TEXTURE
#define USE_MULTIPLE_TEXTURES
//#define SCREENSAVER
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include "glut_import.h"
#include "menu.h"
/*********************************************************************
#define FPS_COUNTER_TRIGGER 15
#define FPS_COUNTER_SMOOTH 0.3
#define PROXY_SIZE 32
#define PROXY_QUALITY 5
#define MIN_COLOR (10./256.)
#define MIN_POINT_COUNT 300
#define INI_FILENAME "AFC.ini"
#ifdef USE_MULTIPLE_TEXTURES
# ifndef USE_TEXTURE
# error You cannot specify USE_MULTIPLE_TEXTURES without specifying USE_TEXTURE
# endif
# define INVALID_TEXTURE_INDEX 0xFFFFFFFF
#endif
/*********************************************************************
#ifdef __WIN32__
# ifdef SCREENSAVER
# define SCREENSAVER_MODE_SHOW 0x1
# define SCREENSAVER_MODE_CONFIGURE 0x2
# define SCREENSAVER_MODE_PREVIEW 0x4
# define MAX_SCREENSAVER_MOUSE_OFFSET 10
# endif
# include <windows.h>
# define SLEEP(_DELAY) Sleep(_DELAY)
# define TIME_GRANULARITY 12
#else
# ifdef SCREENSAVER
# error Usage of SCREENSAVER reserved for Windows
# endif
# include <unistd.h>
# define SLEEP(_DELAY) usleep(1000*_DELAY)
# define TIME_GRANULARITY 6
#endif
/*********************************************************************
#ifdef USE_COS_TABLE
# define COS_TABLE_QUALITY 0x1000
float CosTable[COS_TABLE_QUALITY];
void initCos(void){
int i;
for (i=0;i<COS_TABLE_QUALITY;i++)
CosTable[i]=cos(PI*i/COS_TABLE_QUALITY);
}
float cosTable(float x){
int n;
if (x<0)
x=-x;
n=((int) roundf(COS_TABLE_QUALITY*x/PI)) % (2*COS_TABLE_QUALITY);
if (n<COS_TABLE_QUALITY)
return CosTable[n];
else
return CosTable[2*COS_TABLE_QUALITY-1-n];
}
# define COS(_X) cosTable((_X))
# define SIN(_X) cosTable((_X)-0.5*PI)
#else
# define COS(_X) cos((_X))
# define SIN(_X) sin((_X))
#endif
/*********************************************************************
#define ASSERT(_X,_S) if (!(_X)) {printf("%s\n",_S);exit(-1);}
#define MEMCHECK(_X) if (!(_X)) {printf("Memory allocation failed\n");exit(-1);}
/*********************************************************************
typedef struct{
int Count,Degree;
float *Matrix;
} transformation;
#ifdef USE_TEXTURE
typedef struct{
# ifdef USE_MULTIPLE_TEXTURES
unsigned int Index;
# endif
int ImageWidth,ImageHeight,Width,Height;
} texture;
#endif
#pragma pack(push,1)
#pragma pack(1)
typedef struct{
unsigned char R,G,B,A;
float X,Y,Z;
} point;
#pragma pack(pop)
/*********************************************************************
struct {
struct {
int Index,Width,Height,FullScreen,DesiredFullScreen,DoubleBuffered,DesiredDoubleBuffered,MultiSample,DesiredMultiSample,ChangeMode;
#ifdef SCREENSAVER
int ScreenSaverMode,MouseX,MouseY;
#endif
#ifdef USE_TEXTURE
texture TextureMotion;
# ifdef USE_MULTIPLE_TEXTURES
texture TextureAccum;
# endif
#endif
} Window;
struct {
int Tick,PeriodStart,PeriodLength,TickTime;
float Elasticity,Map,FPS,DesiredFPS;
} Time;
struct {
transformation T1,T2;
int MinDegree,MaxDegree;
} Transformations;
struct {
int Count,DesiredCount,Rounded;
float Size;
point *Data;
} Points;
struct {
float Alpha,SpaceFrequency,TimeFrequency;
} ColorMap;
struct {
int BlendMethod,LimitCPUUsage,MotionBlur,Accumulation;
float Luminance,Sharpness,Attenuation;
} Display;
struct {
float Density;
char sFrame[64],sTime[64],sFPS[64],sPointCount[64],sDensity[64],sPointSize[64],sAlpha[64],sCycle[64],sAttractor1[64],sAttractor2[64];
} Statistics;
menu *MainMenu;
} ApplicationData={
{-1,1,1,0,0,1,1,0,0,0
#ifdef SCREENSAVER
,0,-1,-1
#endif
#ifdef USE_TEXTURE
# ifdef USE_MULTIPLE_TEXTURES
,{INVALID_TEXTURE_INDEX,0,0,0,0},{INVALID_TEXTURE_INDEX,0,0,0,0}
# else
,{0,0,0,0}
# endif
#endif
},
{0,0,5000,0,5,0,50,40},
{{0,0,NULL},{0,0,NULL},0,20},
{0,2*MIN_POINT_COUNT,1,1,NULL},
{0,1,1},
{0,0,0,0,1,1,0.1},
{0,"","","","","","","","","",""}
};
/*********************************************************************
void evolveWindow(void);
void evolveAlloc(void);
void evolveTime(void);
void evolvePoints(void);
void evolveProxy(void);
void evolveColorMap(void);
void evolve(void);
void displayFunc(void);
void reshapeFunc(int,int);
void idleFunc(void);
void keyboardFunc(unsigned char,int,int);
void specialFunc(int,int,int);
void mouseFunc(int,int,int,int);
#ifdef SCREENSAVER
void passiveMotionFunc(int,int);
#endif
void readIniFile(void);
void writeIniFile(void);
void fullScreenChange(int);
void fullScreenParamChange(int);
void doubleBufferedChange(int);
void multiSampleChange(int);
#ifdef USE_TEXTURE
void motionBlurChange(int);
#endif
void desiredFPSChange(int);
void limitCPUUsageChange(int);
void resetExecute(void);
void saveExecute(void);
void quitExecute(void);
void roundedPointsChange(int);
void blendMethodChange(int);
void luminanceChange(float);
void sharpnessChange(float);
void attenuationChange(float);
void spaceFrequencyChange(float);
void timeFrequencyChange(float);
void periodChange(float);
void elasticityChange(float);
void minDegreeChange(int);
void maxDegreeChange(int);
/*********************************************************************
SEPARATOR(Separator);
CHECKBOX(CheckBoxFullScreen,"Fullscreen (F)",0,fullScreenChange);
static char *ListBoxResolution_Items[6]={"default","320x240","640x480","800x600","1024x768","1280x1024"};
LISTBOX(ListBoxResolution,"Fullscreen resolution :",0,ListBoxResolution_Items,fullScreenParamChange);
static char *ListBoxBPP_Items[4]={"default","16","24","32"};
LISTBOX(ListBoxBPP,"Fullscreen color depth (BPP) :",0,ListBoxBPP_Items,fullScreenParamChange);
static char *ListBoxFrequency_Items[11]={"default","50","60","65","70","72","75","80","85","90","100"};
LISTBOX(ListBoxFrequency,"Fullscreen refresh rate (Hz) :",0,ListBoxFrequency_Items,fullScreenParamChange);
CHECKBOX(CheckBoxDoubleBuffered,"Double buffered (D)",1,doubleBufferedChange);
CHECKBOX(CheckBoxMultiSample,"Multi sample (M)",0,multiSampleChange);
#ifdef USE_TEXTURE
# ifdef USE_MULTIPLE_TEXTURES
static char *ListBoxMotionBlur_Items[7]={"Single buffer","Double buffer","Triple buffer (2x filter)",
"Triple buffer (3x filter)","Triple buffer (4x filter)","Triple buffer (5x filter)",
"Triple buffer (6x filter)"};
LISTBOX(ListBoxMotionBlur,"Motion blur",1,ListBoxMotionBlur_Items,motionBlurChange);
# else
CHECKBOX(CheckBoxMotionBlur,"High quality motion blur",1,motionBlurChange);
# endif
#endif
ITRACKBAR(FTrackBarDesiredFPS,"Requested frame rate: %d",5,120,40,1,desiredFPSChange);
CHECKBOX(CheckBoxLimitCPUUsage,"Limit CPU usage",0,limitCPUUsageChange);
#ifdef SCREENSAVER
STATICTEXT(StaticTextReset,"Reset settings (R)",1,resetExecute);
STATICTEXT(StaticTextSave,"Save settings and exit (S)",1,saveExecute);
STATICTEXT(StaticTextQuit,"Cancel (Q)",1,quitExecute);
# ifdef USE_TEXTURE
static menuitem *MenuPageAFC_Items[14]={
# else
static menuitem *MenuPageAFC_Items[13]={
# endif
#else
STATICTEXT(StaticTextSave,"Save settings (S)",1,saveExecute);
STATICTEXT(StaticTextReset,"Reset settings",1,resetExecute);
STATICTEXT(StaticTextQuit,"Quit (Q)",1,quitExecute);
# ifdef USE_TEXTURE
static menuitem *MenuPageAFC_Items[15]={
# else
static menuitem *MenuPageAFC_Items[14]={
# endif
#endif
&CheckBoxFullScreen,
&ListBoxResolution,
&ListBoxBPP,
&ListBoxFrequency,
&Separator,
&CheckBoxDoubleBuffered,
&CheckBoxMultiSample,
#ifdef USE_TEXTURE
# ifdef USE_MULTIPLE_TEXTURES
&ListBoxMotionBlur,
# else
&CheckBoxMotionBlur,
# endif
#endif
&FTrackBarDesiredFPS,
&CheckBoxLimitCPUUsage,
&Separator,
#ifdef SCREENSAVER
&StaticTextReset,
&StaticTextSave,
&StaticTextQuit
#else
&StaticTextSave,
&StaticTextReset,
&Separator,
&StaticTextQuit
#endif
};
MENUPAGE(MenuPageAFC,"AFC Version 1.0",MenuPageAFC_Items);
CHECKBOX(CheckBoxRoundedPoints,"Rounded points",1,roundedPointsChange);
static char *ListBoxBlendMethod_Items[3]={"Logarithmic","Linear","Maximum"};
LISTBOX(ListBoxBlendMethod,"Blend color method :",0,ListBoxBlendMethod_Items,blendMethodChange);
FTRACKBAR(FTrackBarLuminance,"Luminance : %.0f%%",0,200,100,1,luminanceChange);
FTRACKBAR(FTrackBarSharpness,"Sharpness : %.0f%%",0,200,100,1,sharpnessChange);
FTRACKBAR(FTrackBarAttenuation,"Attenuation : %.1f%%",0.1,100,10,0.1,attenuationChange);
FTRACKBAR(FTrackBarSpaceFrequency,"Colormap space frequency : %.2f",0,50,1,0.02,spaceFrequencyChange);
FTRACKBAR(FTrackBarTimeFrequency,"Colormap time frequency : %.2f",0,20,1,0.02,timeFrequencyChange);
static menuitem *MenuPageDisplay_Items[9]={
&CheckBoxRoundedPoints,
&ListBoxBlendMethod,
&Separator,
&FTrackBarLuminance,
&FTrackBarSharpness,
&FTrackBarAttenuation,
&Separator,
&FTrackBarSpaceFrequency,
&FTrackBarTimeFrequency
};
MENUPAGE(MenuPageDisplay,"DISPLAY",MenuPageDisplay_Items);
FTRACKBAR(FTrackBarPeriod,"Cycle length : %.1f sec",0.5,20,5,0.1,periodChange);
FTRACKBAR(FTrackBarElasticity,"Cycle elasticity : %.1f",0.1,30,5,0.1,elasticityChange);
ITRACKBAR(ITrackBarMinDegree,"Min attractor degree : %d",3,15,6,1,minDegreeChange);
ITRACKBAR(ITrackBarMaxDegree,"Max attractor degree : %d",3,15,6,1,maxDegreeChange);
static menuitem *MenuPageAttractor_Items[4]={&FTrackBarPeriod,&FTrackBarElasticity,&ITrackBarMinDegree,&ITrackBarMaxDegree};
MENUPAGE(MenuPageAttractor,"ATTRACTOR",MenuPageAttractor_Items);
STATICTEXT(StaticTextFrame,ApplicationData.Statistics.sFrame,0,NULL);
STATICTEXT(StaticTextTime,ApplicationData.Statistics.sTime,0,NULL);
STATICTEXT(StaticTextFPS,ApplicationData.Statistics.sFPS,0,NULL);
STATICTEXT(StaticTextPointCount,ApplicationData.Statistics.sPointCount,0,NULL);
STATICTEXT(StaticTextDensity,ApplicationData.Statistics.sDensity,0,NULL);
STATICTEXT(StaticTextPointSize,ApplicationData.Statistics.sPointSize,0,NULL);
STATICTEXT(StaticTextAlpha,ApplicationData.Statistics.sAlpha,0,NULL);
STATICTEXT(StaticTextCycle,ApplicationData.Statistics.sCycle,0,NULL);
STATICTEXT(StaticTextAttractor1,ApplicationData.Statistics.sAttractor1,0,NULL);
STATICTEXT(StaticTextAttractor2,ApplicationData.Statistics.sAttractor2,0,NULL);
static menuitem *MenuPageStatistics_Items[12]={
&StaticTextFrame,
&StaticTextTime,
&StaticTextFPS,
&Separator,
&StaticTextPointCount,
&StaticTextDensity,
&StaticTextPointSize,
&StaticTextAlpha,
&Separator,
&StaticTextCycle,
&StaticTextAttractor1,
&StaticTextAttractor2
};
MENUPAGE(MenuPageStatistics," STATISTICS ",MenuPageStatistics_Items);
static menupage *MainMenu_Pages[4]={&MenuPageAFC,&MenuPageDisplay,&MenuPageAttractor,&MenuPageStatistics};
MENU(MainMenu,MainMenu_Pages);
/*********************************************************************
int pgcd(int x,int y){
if (y)
return pgcd(y,x%y);
else
return x;
}
int ppcm(int x,int y){
return (x*y)/pgcd(x,y);
}
int nextPowerOfTwo(int x){
int u=1;
while (u<x)
u*=2;//<<=1;
return u;
}
float mapExp(float x,float t){
if (x<0.5)
return 0.5*pow(2*x,t);
else
return 1-0.5*pow(2-2*x,t);
}
/*********************************************************************
#ifdef USE_TEXTURE
void copyTexture(texture *T){
# ifdef USE_MULTIPLE_TEXTURES
if (T->Index==INVALID_TEXTURE_INDEX)
glGenTextures(1,&T->Index);
glBindTexture(GL_TEXTURE_2D,T->Index);
# endif
if (ApplicationData.Window.Width!=T->ImageWidth || ApplicationData.Window.Height!=T->ImageHeight){
T->ImageWidth=ApplicationData.Window.Width;
T->ImageHeight=ApplicationData.Window.Height;
T->Width=nextPowerOfTwo(T->ImageWidth);
T->Height=nextPowerOfTwo(T->ImageHeight);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,T->Width,T->Height,0,GL_RGB,GL_UNSIGNED_BYTE,NULL);
}
glCopyTexSubImage2D(GL_TEXTURE_2D,0,0,0,0,0,T->ImageWidth,T->ImageHeight);
}
int paintTexture(texture *T,int Mode){
# ifdef USE_MULTIPLE_TEXTURES
if (T->Index!=INVALID_TEXTURE_INDEX && T->Width && T->Height){
# else
if (T->Width && T->Height){
# endif
float u=(float) T->ImageWidth/T->Width,v=(float) T->ImageHeight/T->Height;
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPushMatrix();
# ifdef USE_MULTIPLE_TEXTURES
glBindTexture(GL_TEXTURE_2D,T->Index);
# endif
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glTexCoord2f(0,0);
glVertex2f(-1,-1);
glTexCoord2f(0,v);
glVertex2f(-1,1);
glTexCoord2f(u,v);
glVertex2f(1,1);
glTexCoord2f(u,0);
glVertex2f(1,-1);
glEnd();
glDisable(GL_TEXTURE_2D);
glPopMatrix();
glPopAttrib();
return 1;
}
return 0;
}
void deleteTexture(texture *T){
# ifdef USE_MULTIPLE_TEXTURES
if (T->Index!=INVALID_TEXTURE_INDEX){
glDeleteTextures(1,&T->Index);
T->Index=INVALID_TEXTURE_INDEX;
# endif
T->Width=0;
T->Height=0;
T->ImageWidth=0;
T->ImageHeight=0;
# ifdef USE_MULTIPLE_TEXTURES
}
# endif
}
#endif
/*********************************************************************
void allocTransformation(transformation *t){
int i,*T;
if (ApplicationData.Transformations.MaxDegree==ApplicationData.Transformations.MinDegree)
t->Degree=ApplicationData.Transformations.MinDegree;
else
t->Degree=ApplicationData.Transformations.MinDegree+(rand() % (1+ApplicationData.Transformations.MaxDegree-ApplicationData.Transformations.MinDegree));
t->Count=t->Degree-(rand() % (t->Degree/2));
if (t->Count==t->Degree)
t->Count--;
MEMCHECK(T=(int *) calloc(t->Degree,sizeof(int)));
MEMCHECK(t->Matrix=(float *) malloc(6*t->Count*sizeof(float)));
for (i=0;i<t->Count;i++){
float o=2*PI*(rand() % t->Degree)/t->Degree,u=(rand() % 2)-0.5,v=-0.5;
int k=-1,j=1+(rand() % (t->Degree-i));
t->Matrix[6*i]=u*cos(o);
t->Matrix[6*i+1]=-v*sin(o);
t->Matrix[6*i+3]=u*sin(o);
t->Matrix[6*i+4]=v*cos(o);
while (j){
k++;
if (!T[k])
j--;
}
T[k]=1;
o=PI*(1.+2*k)/t->Degree;
t->Matrix[6*i+2]=0.5*cos(o);
t->Matrix[6*i+5]=0.5*sin(o);
}
free(T);
}
void freeTransformation(transformation *t){
free(t->Matrix);
t->Count=0;
}
void applyTransformation(transformation *t,int r,float *x,float *y){
float u=t->Matrix[6*r]*(*x)+t->Matrix[6*r+1]*(*y)+t->Matrix[6*r+2];
- y=t->Matrix[6*r+3]*(*x)+t->Matrix[6*r+4]*(*y)+t->Matrix[6*r+5];
- x=u;
}
/*********************************************************************
void createWindow(void){
if (ApplicationData.Window.Index==-1){
int m=GLUT_RGBA;
char s[64]="";
if (ApplicationData.Window.DesiredDoubleBuffered)
m|=GLUT_DOUBLE;
ApplicationData.Window.DoubleBuffered=ApplicationData.Window.DesiredDoubleBuffered;
if (ApplicationData.Window.DesiredMultiSample)
m|=GLUT_MULTISAMPLE;
ApplicationData.Window.MultiSample=ApplicationData.Window.DesiredMultiSample;
glutInitDisplayMode(m);
if (ApplicationData.Window.DesiredFullScreen){
if (ListBoxResolution.Data.ListBox->ItemIndex)
strcat(s,ListBoxResolution.Data.ListBox->Items[ListBoxResolution.Data.ListBox->ItemIndex]);
if (ListBoxBPP.Data.ListBox->ItemIndex){
strcat(s,":");
strcat(s,ListBoxBPP.Data.ListBox->Items[ListBoxBPP.Data.ListBox->ItemIndex]);
}
if (ListBoxFrequency.Data.ListBox->ItemIndex){
strcat(s,"@");
strcat(s,ListBoxFrequency.Data.ListBox->Items[ListBoxFrequency.Data.ListBox->ItemIndex]);
}
if (*s){
glutGameModeString(s);
if (!glutGameModeGet(GLUT_GAME_MODE_POSSIBLE)){
printf("Unsupported display mode (%s). Fullscreen has been canceled.\n",s);
ApplicationData.Window.DesiredFullScreen=0;
}
}
}
CheckBoxFullScreen.Data.CheckBox->Checked=ApplicationData.Window.DesiredFullScreen;
if (ApplicationData.Window.DesiredFullScreen)
ApplicationData.Window.Index=glutEnterGameMode();
else {
glutInitWindowSize(640,480);
glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-640)/2,(glutGet(GLUT_SCREEN_HEIGHT)-480)/2);
ApplicationData.Window.Index=glutCreateWindow("AFC");
}
ApplicationData.Window.FullScreen=ApplicationData.Window.DesiredFullScreen;
ApplicationData.Window.ChangeMode=0;
#ifdef USE_TEXTURE
deleteTexture(&ApplicationData.Window.TextureMotion);
# ifdef USE_MULTIPLE_TEXTURES
deleteTexture(&ApplicationData.Window.TextureAccum);
# endif
#endif
#ifdef SCREENSAVER
if (ApplicationData.Window.ScreenSaverMode==SCREENSAVER_MODE_SHOW)
glutSetCursor(GLUT_CURSOR_NONE);
else
#endif
glutSetCursor(GLUT_CURSOR_CROSSHAIR);
glutDisplayFunc(displayFunc);
glutReshapeFunc(reshapeFunc);
glutIdleFunc(idleFunc);
glutKeyboardFunc(keyboardFunc);
glutSpecialFunc(specialFunc);
glutMouseFunc(mouseFunc);
#ifdef SCREENSAVER
glutPassiveMotionFunc(passiveMotionFunc);
#endif
}
}
void destroyWindow(void){
if (ApplicationData.Window.Index!=-1){
if (ApplicationData.Window.FullScreen)
glutLeaveGameMode();
else
glutDestroyWindow(ApplicationData.Window.Index);
}
ApplicationData.Window.Index=-1;
}
/*********************************************************************
float u1,u2,u3,u4,u5,u6;
void evolveWindow(void){
if (ApplicationData.Window.MultiSample^ApplicationData.Window.DesiredMultiSample ||
ApplicationData.Window.DoubleBuffered^ApplicationData.Window.DesiredDoubleBuffered ||
ApplicationData.Window.FullScreen^ApplicationData.Window.DesiredFullScreen ||
ApplicationData.Window.ChangeMode) {
destroyWindow();
createWindow();
}
}
void evolveAlloc(void){
if (ApplicationData.Points.Count!=ApplicationData.Points.DesiredCount){
int i;
MEMCHECK(ApplicationData.Points.Data=(point *) realloc(ApplicationData.Points.Data,ApplicationData.Points.DesiredCount*sizeof(point)));
if (!ApplicationData.Points.Count){
float o=2*PI*rand()/RAND_MAX,r=(float) rand()/RAND_MAX;
ApplicationData.Points.Data[0].X=r*cos(o);
ApplicationData.Points.Data[0].Y=r*sin(o);
ApplicationData.Points.Data[0].Z=0;
ApplicationData.Points.Data[0].A=255;
}
for (i=ApplicationData.Points.Count;i<ApplicationData.Points.DesiredCount;i++)
ApplicationData.Points.Data[i]=ApplicationData.Points.Data[0];
ApplicationData.Points.Count=ApplicationData.Points.DesiredCount;
}
}
void evolveTime(void){
int t=glutGet(GLUT_ELAPSED_TIME),u=(t-ApplicationData.Time.PeriodStart)/ApplicationData.Time.PeriodLength;
while (u--){
freeTransformation(&ApplicationData.Transformations.T1);
ApplicationData.Transformations.T1=ApplicationData.Transformations.T2;
allocTransformation(&ApplicationData.Transformations.T2);
ApplicationData.Time.PeriodStart+=ApplicationData.Time.PeriodLength;
}
ApplicationData.Time.Map=mapExp((float) (t-ApplicationData.Time.PeriodStart)/ApplicationData.Time.PeriodLength,ApplicationData.Time.Elasticity);
if ((++ApplicationData.Time.Tick & FPS_COUNTER_TRIGGER)==FPS_COUNTER_TRIGGER){
char s[64];
float r;
ApplicationData.Time.FPS=(1-FPS_COUNTER_SMOOTH)*1000*(1+FPS_COUNTER_TRIGGER)/(t+1-ApplicationData.Time.TickTime)+FPS_COUNTER_SMOOTH*ApplicationData.Time.FPS;
ApplicationData.Time.TickTime=t;
r=ApplicationData.Time.FPS/ApplicationData.Time.DesiredFPS;
if (r>1.05)
ApplicationData.Points.DesiredCount+=ApplicationData.Points.DesiredCount*(1-1/r);
if (r<0.95)
ApplicationData.Points.DesiredCount-=ApplicationData.Points.DesiredCount*(1-r);
if (ApplicationData.Points.DesiredCount<MIN_POINT_COUNT){
ApplicationData.Points.DesiredCount=MIN_POINT_COUNT;
printf("Your system is TOO SLOW to run this program at the requested frame rate. Please lower it to improve image quality (its value MUST be less than the screen refresh rate if you are using double buffered mode, since the display cannot go faster than the screen in this case).\n");
}
sprintf(s,"AFC (%.0f FPS , %d points)\0",ApplicationData.Time.FPS,ApplicationData.Points.Count);
glutSetWindowTitle(s);
}
}
void evolvePoints(void){
int i;
for (i=0;i<ApplicationData.Points.Count;i++){
int r=rand(),r1=r % ApplicationData.Transformations.T1.Count,r2=r % ApplicationData.Transformations.T2.Count;
point *p=ApplicationData.Points.Data+i;
float u=p->X,v=p->Y,s=ApplicationData.Time.Map,t=1-ApplicationData.Time.Map;
applyTransformation(&ApplicationData.Transformations.T1,r1,&p->X,&p->Y);
applyTransformation(&ApplicationData.Transformations.T2,r2,&u,&v);
p->X=t*p->X+s*u;
p->Y=t*p->Y+s*v;
p->Z=0.2*p->Z+0.8*(t*r1+s*r2);
p->R=85.*ApplicationData.ColorMap.Alpha*(2+COS(ApplicationData.ColorMap.SpaceFrequency*u1*p->Z+u4));
p->G=85.*ApplicationData.ColorMap.Alpha*(2+COS(ApplicationData.ColorMap.SpaceFrequency*u2*p->Z+u5));
p->B=85.*ApplicationData.ColorMap.Alpha*(2+COS(ApplicationData.ColorMap.SpaceFrequency*u3*p->Z+u6));
}
}
void evolveProxy(void){
int i,j,k=0,T[PROXY_SIZE][PROXY_SIZE];
float U[2]={1,1},u=0;
float r=(float) (1+ApplicationData.Window.Width*ApplicationData.Window.Height)/(1+ApplicationData.Points.Count);
for (i=0;i<PROXY_SIZE;i++)
for (j=0;j<PROXY_SIZE;j++)
T[i][j]=0;
i=PROXY_SIZE*PROXY_SIZE*PROXY_QUALITY;
if (i>=ApplicationData.Points.Count)
i=ApplicationData.Points.Count-1;
for (;i>=0;i--)
T[(int) (PROXY_SIZE*(ApplicationData.Points.Data[i].X+1)*0.4999)][(int) (PROXY_SIZE*(ApplicationData.Points.Data[i].Y+1)*0.4999)]++;
for (i=0;i<PROXY_SIZE;i++)
for (j=0;j<PROXY_SIZE;j++)
if (T[i][j])
k++;
ApplicationData.Statistics.Density=(float) k/(PROXY_SIZE*PROXY_SIZE);
ApplicationData.Points.Size=(2.01-ApplicationData.Display.Sharpness)*sqrt(ApplicationData.Statistics.Density*r);
if (ApplicationData.Points.Rounded){
glGetFloatv(GL_POINT_SIZE_RANGE,U);
if (U[1]>U[0]+0.01){
ApplicationData.Points.Size*=0.5*PI;
if (ApplicationData.Points.Size<U[0])
ApplicationData.Points.Size=U[0];
if (ApplicationData.Points.Size>U[1])
ApplicationData.Points.Size=U[1];
glGetFloatv(GL_POINT_SIZE_GRANULARITY,&u);
if (u>0)
ApplicationData.Points.Size=U[0]+roundf((ApplicationData.Points.Size-U[0])/u)*u;
} else
ApplicationData.Points.Size=U[0];
} else
ApplicationData.Points.Size=roundf(ApplicationData.Points.Size);
if (ApplicationData.Points.Size<1)
ApplicationData.Points.Size=1;
if (ApplicationData.Display.BlendMethod<2){
ApplicationData.ColorMap.Alpha=ApplicationData.Display.Attenuation*ApplicationData.Display.Luminance*r*ApplicationData.Statistics.Density/pow(ApplicationData.Points.Size,2);
if (ApplicationData.ColorMap.Alpha>1)
ApplicationData.ColorMap.Alpha=1;
if (ApplicationData.Display.MotionBlur>=2)
ApplicationData.ColorMap.Alpha=sqrt(ApplicationData.ColorMap.Alpha);
if (ApplicationData.ColorMap.Alpha<MIN_COLOR)
ApplicationData.ColorMap.Alpha=MIN_COLOR;
} else
ApplicationData.ColorMap.Alpha=ApplicationData.Display.Luminance;
}
void evolveColorMap(void){
float t=0.001*ApplicationData.ColorMap.TimeFrequency*glutGet(GLUT_ELAPSED_TIME);
u1=2+cos(+0.12*t);
u2=2+cos(+0.13*t);
u3=2+cos(-0.16*t);
u4=+1.9*(t+cos(+1.0*t));
u5=+1.4*(t-cos(-1.2*t));
u6=-1.1*(t+cos(-1.4*t));
}
void evolveStatistics(void){
if (ApplicationData.MainMenu->Active){
int t=glutGet(GLUT_ELAPSED_TIME)/1000;
sprintf(ApplicationData.Statistics.sFrame,"Frame : 0x%X",ApplicationData.Time.Tick);
sprintf(ApplicationData.Statistics.sTime,"Time : %.2d:%.2d:%.2d",t/3600,(t/60) % 60,t % 60);
sprintf(ApplicationData.Statistics.sFPS,"FPS : %.0f",ApplicationData.Time.FPS);
sprintf(ApplicationData.Statistics.sPointCount,"Points count : %d",ApplicationData.Points.Count);
sprintf(ApplicationData.Statistics.sDensity,"Surface : %.0f%%",ApplicationData.Statistics.Density*100);
sprintf(ApplicationData.Statistics.sPointSize,"Point size : %.2f",ApplicationData.Points.Size);
sprintf(ApplicationData.Statistics.sAlpha,"Alpha : %.1f%%",ApplicationData.ColorMap.Alpha*100);
sprintf(ApplicationData.Statistics.sCycle,"Cycle : %.0f%%",ApplicationData.Time.Map*100);
sprintf(ApplicationData.Statistics.sAttractor1,"Attractor #1 : %d/%d",ApplicationData.Transformations.T1.Count,ApplicationData.Transformations.T1.Degree);
sprintf(ApplicationData.Statistics.sAttractor2,"Attractor #2 : %d/%d",ApplicationData.Transformations.T2.Count,ApplicationData.Transformations.T2.Degree);
}
}
void evolve(void){
evolveWindow();
evolveAlloc();
evolveTime();
evolvePoints();
evolveProxy();
evolveColorMap();
evolveStatistics();
}
/*********************************************************************
void displayFunc(void){
float t=glutGet(GLUT_ELAPSED_TIME);
glEnable(GL_BLEND);
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPointSize(ApplicationData.Points.Size);
if (ApplicationData.Points.Rounded)
glEnable(GL_POINT_SMOOTH);
else
glDisable(GL_POINT_SMOOTH);
#ifdef USE_TEXTURE
switch (ApplicationData.Display.MotionBlur){
case 0:
#endif
glBlendFunc(GL_ZERO,GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0,0,0,ApplicationData.Display.Attenuation);
glRectf(-1,-1,1,1);
#ifdef USE_TEXTURE
break;
case 1:
glBlendFunc(GL_ONE,GL_ZERO);
glColor3f(1-ApplicationData.Display.Attenuation,1-ApplicationData.Display.Attenuation,1-ApplicationData.Display.Attenuation);
if (!paintTexture(&ApplicationData.Window.TextureMotion,GL_MODULATE))
glClear(GL_COLOR_BUFFER_BIT);
break;
}
#endif
switch(ApplicationData.Display.BlendMethod){
case 0:
glBlendFunc(GL_ONE_MINUS_DST_COLOR,GL_ONE);
break;
case 1:
glBlendFunc(GL_ONE,GL_ONE);
break;
case 2:
glBlendFunc(GL_ONE,GL_ZERO);
break;
}
glInterleavedArrays(GL_C4UB_V2F,sizeof(point),ApplicationData.Points.Data);
#ifdef USE_TEXTURE
switch (ApplicationData.Display.MotionBlur){
int i;
case 0:
case 1:
#endif
glDrawArrays(GL_POINTS,0,ApplicationData.Points.Count);
#ifdef USE_TEXTURE
break;
# ifdef USE_MULTIPLE_TEXTURES
default:{
float f=ApplicationData.Display.BlendMethod<2?ApplicationData.ColorMap.Alpha:ApplicationData.Display.Attenuation/ApplicationData.Display.MotionBlur;///ApplicationData.Display.MotionBlur;
for (i=0;i<ApplicationData.Display.MotionBlur;i++){
int a=i*ApplicationData.Points.Count/ApplicationData.Display.MotionBlur,b=(i+1)*ApplicationData.Points.Count/ApplicationData.Display.MotionBlur-a;
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_POINTS,a,b);
glPushAttrib(GL_ALL_ATTRIB_BITS);
copyTexture(&ApplicationData.Window.TextureAccum);
glBlendFunc(GL_ONE,GL_ZERO);
if (i){
glColor3f(1,1,1);
paintTexture(&ApplicationData.Window.TextureMotion,GL_MODULATE);
} else {
glColor3f(1-ApplicationData.Display.Attenuation,1-ApplicationData.Display.Attenuation,1-ApplicationData.Display.Attenuation);
if (!paintTexture(&ApplicationData.Window.TextureMotion,GL_MODULATE))
glClear(GL_COLOR_BUFFER_BIT);
}
glColor3f(f,f,f);
glBlendFunc(GL_ONE,GL_ONE);
paintTexture(&ApplicationData.Window.TextureAccum,GL_MODULATE);
if (i<ApplicationData.Display.MotionBlur)
copyTexture(&ApplicationData.Window.TextureMotion);
glPopAttrib();
}
break;
}
# endif
}
#endif
glPopAttrib();
menuDisplay(ApplicationData.MainMenu,ApplicationData.Window.Width,ApplicationData.Window.Height);
#ifdef USE_TEXTURE
switch (ApplicationData.Display.MotionBlur){
case 0:
break;
case 1:
copyTexture(&ApplicationData.Window.TextureMotion);
break;
# ifdef USE_MULTIPLE_TEXTURES
default:
copyTexture(&ApplicationData.Window.TextureMotion);
break;
# endif
}
#endif
glFlush();
glutSwapBuffers();
}
void reshapeFunc(int width,int height){
if (!(ApplicationData.Window.Width=width))
ApplicationData.Window.Width=1;
if (!(ApplicationData.Window.Height=height))
ApplicationData.Window.Height=1;
glViewport(0,0,width,height);
}
void idleFunc(void){
evolve();
glutPostRedisplay();
if (ApplicationData.Display.LimitCPUUsage)
SLEEP(TIME_GRANULARITY);
}
void keyboardFunc(unsigned char key,int x,int y){
#ifdef SCREENSAVER
if (ApplicationData.Window.ScreenSaverMode==SCREENSAVER_MODE_SHOW)
exit(0);
#endif
switch (key){
case 'd':
ApplicationData.Window.DesiredDoubleBuffered=!ApplicationData.Window.DesiredDoubleBuffered;
CheckBoxDoubleBuffered.Data.CheckBox->Checked=ApplicationData.Window.DesiredDoubleBuffered;
break;
case 'f':
ApplicationData.Window.DesiredFullScreen=!ApplicationData.Window.DesiredFullScreen;
CheckBoxFullScreen.Data.CheckBox->Checked=ApplicationData.Window.DesiredFullScreen;
break;
case 'm':
ApplicationData.Window.DesiredMultiSample=!ApplicationData.Window.DesiredMultiSample;
CheckBoxMultiSample.Data.CheckBox->Checked=ApplicationData.Window.DesiredMultiSample;
break;
case 'q':
quitExecute();
break;
#ifdef SCREENSAVER
case 'r':
resetExecute();
break;
#endif
case 's':
saveExecute();
break;
default:
menuKeyboard(ApplicationData.MainMenu,key);
}
}
void specialFunc(int key,int x,int y){
#ifdef SCREENSAVER
if (ApplicationData.Window.ScreenSaverMode==SCREENSAVER_MODE_SHOW)
exit(0);
#endif
menuSpecial(ApplicationData.MainMenu,key);
}
void mouseFunc(int button,int state,int x,int y){
int i;
float u=1-2.*x/ApplicationData.Window.Width,v=1-2.*y/ApplicationData.Window.Height;
#ifdef SCREENSAVER
if (ApplicationData.Window.ScreenSaverMode==SCREENSAVER_MODE_SHOW)
exit(0);
#endif
for (i=0;i<ApplicationData.Points.Count;i++){
ApplicationData.Points.Data[i].X=u;
ApplicationData.Points.Data[i].Y=v;
}
};
#ifdef SCREENSAVER
void passiveMotionFunc(int x,int y){
if (ApplicationData.Window.ScreenSaverMode==SCREENSAVER_MODE_SHOW){
if (ApplicationData.Window.MouseX==-1){
ApplicationData.Window.MouseX=x;
ApplicationData.Window.MouseY=y;
} else {
if (abs(ApplicationData.Window.MouseX-x)+abs(ApplicationData.Window.MouseY-y)>MAX_SCREENSAVER_MOUSE_OFFSET)
exit(0);
}
}
};
#endif
/*********************************************************************
void readIniFile(void){
FILE *f;
if (f=fopen(INI_FILENAME,"r")){
menuLoad(ApplicationData.MainMenu,f);
fclose(f);
} else
printf("Cannot read file "INI_FILENAME"\n");
menuInit(ApplicationData.MainMenu);
}
void writeIniFile(void){
FILE *f;
if (f=fopen(INI_FILENAME,"w")){
menuSave(ApplicationData.MainMenu,f);
fclose(f);
} else
printf("Cannot write file" INI_FILENAME "\n");
}
/*********************************************************************
void fullScreenChange(int checked){
#ifdef SCREENSAVER
if (ApplicationData.Window.ScreenSaverMode==SCREENSAVER_MODE_SHOW)
ApplicationData.Window.DesiredFullScreen=1;
else
#endif
ApplicationData.Window.DesiredFullScreen=checked;
}
void fullScreenParamChange(int index){
if (ApplicationData.Window.FullScreen)
ApplicationData.Window.ChangeMode=1;
}
void doubleBufferedChange(int checked){
ApplicationData.Window.DesiredDoubleBuffered=checked;
}
void multiSampleChange(int checked){
ApplicationData.Window.DesiredMultiSample=checked;
}
#ifdef USE_TEXTURE
# ifdef USE_MULTIPLE_TEXTURES
void motionBlurChange(int index){
ApplicationData.Display.MotionBlur=index;
}
# else
void motionBlurChange(int checked){
ApplicationData.Display.MotionBlur=checked;
}
# endif
#endif
void desiredFPSChange(int position){
ApplicationData.Time.DesiredFPS=position;
}
void limitCPUUsageChange(int checked){
ApplicationData.Display.LimitCPUUsage=checked;
}
void resetExecute(void){
FILE *f;
if (f=fopen(INI_FILENAME,"w")){
fclose(f);
printf("Settings reset, restart the application\n");
}
#ifdef SCREENSAVER
exit(0);
#endif
}
void saveExecute(void){
writeIniFile();
#ifdef SCREENSAVER
exit(0);
#endif
}
void quitExecute(void){
exit(0);
}
void roundedPointsChange(int checked){
ApplicationData.Points.Rounded=checked;
}
void blendMethodChange(int index){
ApplicationData.Display.BlendMethod=index;
}
void luminanceChange(float position){
ApplicationData.Display.Luminance=position*0.01;
}
void sharpnessChange(float position){
ApplicationData.Display.Sharpness=position*0.01;
}
void attenuationChange(float position){
ApplicationData.Display.Attenuation=position*0.01;
}
void spaceFrequencyChange(float position){
ApplicationData.ColorMap.SpaceFrequency=position;
}
void timeFrequencyChange(float position){
ApplicationData.ColorMap.TimeFrequency=position;
}
void periodChange(float position){
ApplicationData.Time.PeriodLength=1000*position;
}
void elasticityChange(float position){
ApplicationData.Time.Elasticity=position;
}
void minDegreeChange(int position){
if (position>ApplicationData.Transformations.MaxDegree)
ITrackBarMinDegree.Data.ITrackBar->Position=ApplicationData.Transformations.MaxDegree;
else
ApplicationData.Transformations.MinDegree=position;
}
void maxDegreeChange(int position){
if (position<ApplicationData.Transformations.MinDegree)
ITrackBarMaxDegree.Data.ITrackBar->Position=ApplicationData.Transformations.MaxDegree;
else
ApplicationData.Transformations.MaxDegree=position;
}
/*********************************************************************
#ifdef SCREENSAVER
int strcmpleft(char *s,char *t){
while (*s && *s==*t){
s++;
t++;
}
return !*s;
}
int findstr(char *s,char *T[]){
char *t;
while (t=*(T++))
if (strcmpleft(t,s))
return 1;
return 0;
}
int screenSaverMode(int argc,char *argv[]){
char *sConfigureFlags[7]={"-C","/C","C","-c","/c","c",NULL},*sPreviewFlags[7]={"-P","/P","P","-p","/p","p",NULL};
int i;
for (i=1;i<argc;i++){
if (findstr(argv[i],sConfigureFlags))
return SCREENSAVER_MODE_CONFIGURE;
if (findstr(argv[i],sPreviewFlags))
return SCREENSAVER_MODE_PREVIEW;
}
return SCREENSAVER_MODE_SHOW;
}
#endif
int main(int argc,char *argv[]){
#ifdef SCREENSAVER
switch(ApplicationData.Window.ScreenSaverMode=screenSaverMode(argc,argv)){
case SCREENSAVER_MODE_CONFIGURE:
MainMenu.Active=1;
break;
case SCREENSAVER_MODE_PREVIEW:
return 0;
}
#endif
srand(time(NULL));
#ifdef USE_COS_TABLE
initCos();
#endif
ApplicationData.MainMenu=&MainMenu;
readIniFile();
allocTransformation(&ApplicationData.Transformations.T1);
allocTransformation(&ApplicationData.Transformations.T2);
glutInit(&argc,argv);
createWindow();
printf("Entering main loop, press [Escape] for menu, use arrows to navigate.\n");
glutMainLoop();
return 0;
}
Conclusion :
Signification des options:
[Options générales]
-Fullscreen: active/désactive le mode full screen, avec possibilité de spécifier le mode d'affichage
-Double buffered: indique si la fenêtre est doublebuffered (moins de clignotements)
-Multi sample: active le mode multisample (lignes et points plus lisses)
-Motion blur: indique le degré de lissage temporel
-Requested frame rate: nombre d'images par secondes désirées (éviter de dépasser la fréquence de l'écran lorsque la fenêtre est double buffered)
-Limit CPU usage: tente de limiter un peu la consommation de resources
-Save settings: enregistre tout dans un fichier ini
-Reset settings: remet à zéro le fichier ini (nécessite de redémarrer le programme pour être pris en compte)
-Quit: quitter
[Display]
-Rounded points: indique si les points doivent être antialiasés (plus lisses)
-Blend color method: indique quelle méthode de mélange de couleurs utiliser
-Luminance: brillance globale de l'affichage
-Sharpness: détail de l'attracteur
-Attenuation: atténuation temporelle du motion blur
-Colormap space frequency: fréquence spatiale (nombre de couleurs simultanées) de la palette de couleurs
-Colormap time frequency: fréquence temporelle (variation dans le temps) de la palette
[Attractor]
-Cycle length: longueur du cycle de transfert d'un attracteur à l'autre
-Cycle elasticity: "élasticité" du transfert (oscillations plus ou moins brusques)
-Min attractor degree: complexité minimale des attracteurs
-Max attractor degree: complexité maximale des attracteurs
8 janv. 2008 à 19:06
des Fractales Linéaires !
Y'en a tellement ! tu les génères aléatoirement ?
Où sont les sierpinski, la fougère.. ?
En tout cas j'adore, c'est très beau et très fluide !
8 janv. 2008 à 21:24
content que ça te plaise!
C'est la fonction allocTransformation qui se charge de générer la matrice des applications affines utilisées pour l'attracteur.
Les fractales sont définies à partir d'un polygone régulier P de centre O à n côtés (n>=4). On considère:
-r l'homothétie de rapport 1/2
-T l'ensemble des translations qui transforment O en l'un des sommets de P (il y en a n)
-I le groupe des isométries qui laissent O invariant (il y en a 2*(n+1))
-k un entier compris entre n/2 et n
L'attracteur est défini en prenant k applications affines f1,...,fk contractantes obtenues en composant les 3 applications r, tj et ij (fj = r o tj o ij) où, pour 1<=j<=k:
-ij est une isométrie quelconque de I
-tj est une translation de T (mais on en choisit une différente pour chacune des f1,...,fk)
Pour k fixé ça laisse n!*(2n+2)^k/((n-k)!) choix possibles pour les k contractions.
Pour calculer le nombre total d'attracteurs que le programme peut représenter, il faut sommer cette quantité pour 4<=n<=nmax et n/2<=k<=n-1 où nmax est le degré maximal de l'attracteur (qu'on peut changer dans le menu). Par défaut nmax vaut 6, ce qui fait un total de plus de 800 millions d'attracteurs différents (en théorie) mais certains sont comptés plusieurs fois (à une rotation/symétrie près). Je crois que le nombre total (sans compter ceux qui sont identiques à une isométrie près) est proche de 50 millions (mais je n'ai pas vérifié en détails).
Les sierspinskis apparaissent parfois, il faut être patient :-)
La fougère par contre, je ne crois pas qu'elle puisse apparaitre étant données les familles de contractions utilisées...
À tout instant il y a un mélange de 2 attracteurs générés comme expliqué plus haut, au début de la période c'est le premier, puis on évolue continument vers le second, puis à la fin de la période le second devient le premier, un nouvel attracteur est généré à la place du second et on recommence.
8 janv. 2008 à 21:42
Si j'ai le temps je corrigerai ce problème en accumulant des couches de textures.
12 janv. 2008 à 16:59
rien a ajouté
14 janv. 2008 à 11:53
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.