/* Classe RAYTRACE 20000409 © alain.marty@wanadoo.fr */ import java.awt.*; import java.util.*; ///////////////////////////////////////////////////////////////////////////// public class RAYTRACE implements DESSIN { static final double EPSILON = 1.0e-10; static final double INFINI = 1.0e10; VEC F, L; // points observateur et lumiere Vector formes; Vector lumieres; int niveau_max; // niveau de recursion boolean avec_cube_scene, avec_plan, avec_spot, avec_ombre; public RAYTRACE() { init(); creer_modele(); } public final Panel controles() { return null; } public final void infos() { // "Raytrace: recursion = " + niveau_max; } public final boolean keyDown( Event e, int key ) { boolean bof = false; switch( key ) { case e.LEFT : niveau_max--; if (niveau_max<0) niveau_max = 0; bof = true; break; case e.RIGHT : niveau_max++; if (niveau_max>4) niveau_max = 4; bof = true; break; case 'c': avec_cube_scene = !avec_cube_scene; creer_modele(); bof = true; break; case 'p': avec_plan = !avec_plan; creer_modele(); bof = true; break; case 's': avec_spot = !avec_spot; creer_modele(); bof = true; break; case 'o': avec_ombre = !avec_ombre; creer_modele(); bof = true; break; case ' ' : init(); creer_modele(); bof = true; break; } return bof; } public final boolean mouseUp( Event e, double x, double y ) { return false; } public boolean action( Event e, Object o ) { return false; } public final void init() { niveau_max = 0; avec_cube_scene = false; avec_plan = false; avec_spot = false; avec_ombre = false; } public final void creer_modele() { lumieres = null; formes = null; lumieres = new Vector(); formes = new Vector(); F = new VEC ( 0.0, 0.0, 1.5 ); // z positif ecran -> observateur lumieres.addElement( new VEC ( -0.5, 0.5, 1.5 ) ); lumieres.addElement( new VEC ( 0.5, 0.5, 1.5 ) ); if (avec_spot) lumieres.addElement( new VEC ( 0.5, 0.5, -15 ) ); if (avec_cube_scene) cubix(); formes.addElement( new SPHERE( new COULEUR( 255, 0, 0 ), new VEC ( -0.2, -0.2, 0.0 ), 0.2 ) ); formes.addElement( new SPHERE( new COULEUR( 0, 255, 0 ), new VEC ( 0.2, -0.2, 0.0 ), 0.2 ) ); formes.addElement( new SPHERE( new COULEUR( 0, 0, 255 ), new VEC ( 0.2, 0.2, -0.3 ), 0.4 ) ); formes.addElement( new SPHERE( new COULEUR( 255, 255, 0 ), new VEC ( -0.125, 0.125, 0.375 ), 0.1 ) ); if (avec_plan) formes.addElement( new PLAN( new COULEUR( 127, 255, 255 ), new VEC ( 0.0, -0.7, 0.0 ), new VEC ( 0.1, 1.0, 0.0 ) ) ); } private final void cubix() { formes.addElement( new SPHERE( new COULEUR( 0, 0, 0 ), new VEC ( -0.5, -0.5, -0.5 ), 0.1 ) ); formes.addElement( new SPHERE( new COULEUR( 255, 0, 0 ), new VEC ( 0.5, -0.5, -0.5 ), 0.1 ) ); formes.addElement( new SPHERE( new COULEUR( 0, 255, 0 ), new VEC ( -0.5, 0.5, -0.5 ), 0.1 ) ); formes.addElement( new SPHERE( new COULEUR( 0, 0, 255 ), new VEC ( 0.5, 0.5, -0.5 ), 0.1 ) ); formes.addElement( new SPHERE( new COULEUR( 255, 255, 0 ), new VEC ( -0.5, -0.5, 0.5 ), 0.1 ) ); formes.addElement( new SPHERE( new COULEUR( 255, 0, 255 ), new VEC ( 0.5, -0.5, 0.5 ), 0.1 ) ); formes.addElement( new SPHERE( new COULEUR( 0, 255, 255 ), new VEC ( -0.5, 0.5, 0.5 ), 0.1 ) ); formes.addElement( new SPHERE( new COULEUR( 255, 255, 255 ), new VEC ( 0.5, 0.5, 0.5 ), 0.1 ) ); } public final int couleur( double x, double y ) // LE COEUR DU PROBLEME !! { VEC dir = VEC.diff( new VEC( x, -y, 0 ), F ); // rayon initial RAY ray = new RAY( F, dir ); // F: observateur COULEUR coul = new COULEUR(); // couleur initiale noire tracer_rayon( 0, ray, coul ); // calcul de la couleur return coul.argb(); // retourne la couleur } ///////////////////////////////////////////////////////////////////////////// private final void tracer_rayon( int niveau, RAY ray, COULEUR coul ) { RAY_FORME forme = plus_proche( ray ); if ( forme == null ) { if (niveau == 0) coul.set( 0, 0, 0 ); else coul.set( 0, 0, 1 ); } else { forme.calculer_intersection( ray ); forme.calculer_normale(); forme.orienter_normale( ray ); calculer_ambiant( forme, coul ); calculer_phong( forme, coul, ray ); calculer_reflection( forme, coul, ray, niveau + 1 ); coul.normaliser(); } } private final RAY_FORME plus_proche( RAY ray ) { RAY_FORME forme = null; double t_mini = INFINI; for (int i = 0; i < formes.size(); i++) { RAY_FORME f = (RAY_FORME) formes.elementAt( i ); double t = f.calculer_t_intersection( ray ); // if ( t > EPSILON && t < t_mini ) // un point visible { t_mini = t; forme = f; } } if ( forme != null ) forme.t_mini = t_mini; return forme; } private final boolean dans_ombre( RAY_FORME tete, RAY_FORME courant, RAY ray ) { if (!avec_ombre) return false; double t; int i0 = formes.indexOf( tete ); for ( int i = i0; i < formes.size(); i++ ) { RAY_FORME f = (RAY_FORME) formes.elementAt( i ); if (f != courant) { t = f.calculer_t_intersection( ray ); if (t > EPSILON ) // && t < 1 ) return true; } } return false; } private final void calculer_ambiant( RAY_FORME f, COULEUR coul ) { coul.r = (int) (f.ka * 255); coul.g = (int) (f.ka * 255); coul.b = (int) (f.ka * 255); } private final void calculer_phong( RAY_FORME f, COULEUR coul, RAY ray ) { for (int i = 0; i < lumieres.size(); i++ ) { VEC L = (VEC) lumieres.elementAt( i ); RAY ray2 = new RAY( f.PP, L ); RAY_FORME tete = (RAY_FORME) formes.firstElement(); if ( !dans_ombre( tete, f, ray2 ) ) { // diffus VEC lum = VEC.diff( L, f.PP ); lum.normalise(); double cosa = VEC.dot( f.NN, lum ); if (cosa>0) { coul.r += (int) (f.kd * cosa * f.couleur.r); coul.g += (int) (f.kd * cosa * f.couleur.g); coul.b += (int) (f.kd * cosa * f.couleur.b); } // speculaire VEC refl = VEC.reflechi( L, f.NN ); refl.normalise(); double specul = VEC.dot( ray.direction, refl ); if (specul>0) { specul = Math.pow( specul, f.kr ); coul.r += (int) (f.ks * specul * 255); coul.g += (int) (f.ks * specul * 255); coul.b += (int) (f.ks * specul * 255); } } } } private final void calculer_reflection( RAY_FORME f, COULEUR coul, RAY ray, int niveau ) { if ( niveau > niveau_max ) return; VEC R = VEC.reflechi( ray.direction, f.NN); R.normalise(); RAY ray2 = new RAY( f.PP, R ); // nouveau rayon COULEUR coul2 = new COULEUR(); tracer_rayon( niveau, ray2, coul2 ); // on repart pour un tour coul.r += (int) (f.ks*coul2.r); // coul2 est ajoutŽ ˆ coul coul.g += (int) (f.ks*coul2.g); coul.b += (int) (f.ks*coul2.b); } } ///////////////////////////////////////////////////////////////////////////// abstract class RAY_FORME { double ka = 0.3; double kd = 0.3; double ks = 0.3; int kr = 100; COULEUR couleur; VEC PP, NN; double t_mini; public RAY_FORME( COULEUR coul ) { couleur = new COULEUR( coul ); } public final void calculer_intersection( RAY ray ) { PP = ray.pointAt( t_mini ); } public final void orienter_normale( RAY ray ) { if ( VEC.dot( NN, ray.direction ) > 0 ) NN.moins(); } public abstract double calculer_t_intersection( RAY ray ); public abstract void calculer_normale(); } ///////////////////////////////////////////////////////////////////////////// class SPHERE extends RAY_FORME { VEC centre; double rayon; public SPHERE( COULEUR coul, VEC pos, double R ) { super( coul ); centre = new VEC (pos); rayon = R; } public final double calculer_t_intersection( RAY ray ) { double t1, t2, t; VEC OC = VEC.diff( centre, ray.origine ); double OC2 = VEC.dot( OC, OC); double OH = VEC.dot( ray.direction, OC); double d2 = OC2 - OH*OH; double HI2 = rayon*rayon - d2; if ( HI2<0 ) // pas d'intersection t = -1; else { double HI = Math.sqrt( HI2 ); t1 = OH - HI; t2 = OH + HI; if (t1 < RAYTRACE.EPSILON && t2 < RAYTRACE.EPSILON) t = -1; else if (t1 > RAYTRACE.EPSILON && t2 < RAYTRACE.EPSILON) t = t1; else if (t2 > RAYTRACE.EPSILON && t1 < RAYTRACE.EPSILON) t = t2; else t = Math.min( t1, t2 ); } return t; } public final void calculer_normale() { NN = VEC.diff( PP, centre ); NN.normalise(); } } ///////////////////////////////////////////////////////////////////////////// class PLAN extends RAY_FORME { VEC centre; public PLAN( COULEUR coul, VEC pos, VEC N ) { super( coul ); centre = new VEC (pos); NN = new VEC( N ); NN.normalise(); } public final double calculer_t_intersection( RAY ray ) { double t; double cosa = VEC.dot( ray.direction, NN ); if ( Math.abs( cosa ) < RAYTRACE.EPSILON ) // angle ­ 90¡ t = -1.0; else t = VEC.dot( VEC.diff( centre, ray.origine ), NN ) / cosa; return t; } public final void calculer_normale() { } } ///////////////////////////////////////////////////////////////////////////// class RAY { VEC origine; VEC direction; public RAY( VEC origine, VEC direction ) { this.origine = origine; this.direction = direction; direction.normalise(); } public final VEC pointAt( double t ) { return new VEC( origine.x + t*direction.x, origine.y + t*direction.y, origine.z + t*direction.z ); } } ///////////////////////////////////////////////////////////////////////////// class COULEUR { int r, g, b; public COULEUR() { r = 0; g = 0; b = 0; } public COULEUR( int r, int g, int b ) { this.r = r; this.g = g; this.b = b; } public COULEUR( COULEUR coul ) { this.r = coul.r; this.g = coul.g; this.b = coul.b; } public final void set( int r, int g, int b ) { this.r = r; this.g = g; this.b = b; } public final int argb() { return 0xff000000 | (r<<16) | (g<<8) | b; } public final void normaliser() { r = (r<0)? 0 : (r>255)? 255 : r; g = (g<0)? 0 : (g>255)? 255 : g; b = (b<0)? 0 : (b>255)? 255 : b; } } ///////////////////////////////////////////////////////////////////////////// class VEC { double x, y, z; public VEC() { x = 0; y = 0; z = 0; } public VEC( double x, double y, double z ) { this.x = x; this.y = y; this.z = z; } public VEC( VEC p ) { this.x = p.x; this.y = p.y; this.z = p.z; } // mŽthodes add, diff, mult agissant sur l'objet public final void add( double x, double y, double z ) { this.x += x; this.y += y; this.z += z; } public final void add( VEC p ) { this.x += p.x; this.y += p.y; this.z += p.z; } public final void diff( double x, double y, double z ) { this.x -= x; this.y -= y; this.z -= z; } public final void diff( VEC p ) { this.x -= p.x; this.y -= p.y; this.z -= p.z; } public final void mult( double d ) { this.x *= d; this.y *= d; this.z *= d; } public final void moins() { this.x = -this.x; this.y = -this.y; this.z = -this.z; } public final void normalise() { double d = 1.0/this.norm(); this.x *= d; this.y *= d; this.z *= d; } public final double norm() { return ( Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ) ); } // mŽthodes statiques ˆ prŽfixer par le nom de la classe : VEC.methode( p0, p1 ); public final static VEC add( VEC p0, VEC p1 ) { return new VEC( p0.x + p1.x, p0.y + p1.y, p0.z + p1.z ); } public final static VEC diff( VEC p0, VEC p1 ) { return new VEC( p0.x - p1.x, p0.y - p1.y, p0.z - p1.z ); } public final static VEC mult( double k, VEC p ) { return new VEC( k*p.x, k*p.y, k*p.z ); } public final static VEC milieu( VEC p0, VEC p1 ) { return new VEC( (p0.x + p1.x)*0.5, (p0.y + p1.y)*0.5, (p0.z + p1.z)*0.5 ); } public final static double dot( VEC p0, VEC p1 ) { return (p0.x * p1.x + p0.y * p1.y + p0.z * p1.z); } public final static VEC cross( VEC p0, VEC p1 ) { return new VEC( p0.y * p1.z - p0.z * p1.y, p0.z * p1.x - p0.x * p1.z, p0.x * p1.y - p0.y * p1.x ); } public final static VEC reflechi( VEC V, VEC N) { double cosa = - dot( V, N ); return new VEC( V.x + 2 * cosa * N.x, V.y + 2 * cosa * N.y, V.z + 2 * cosa * N.z ); } public final String toString() { return ( "[" + x + "/" + y + "/" + z + "]" ); } public final boolean keyDown( Event e, int k ) { switch( k ) { case 'x': x -= 0.1; return true; case 'X': x += 0.1; return true; case 'y': y -= 0.1; return true; case 'Y': y += 0.1; return true; case 'z': z -= 0.1; return true; case 'Z': z += 0.1; return true; case '\n': x = y = z = 0.0; return true; } return false; } } /////////////////////////////////////////////////////////////////////////////