/* ** Copyright (C) 2005 Antonin Stefanutti ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ ///////////////////////////////////////////////////////////////////////////////////////// //an instance of this class is a face, in this case a convex polygon class Polygon3D { //viewport pointer private var vp:ViewPort; //parent object pointer private var o:Object3D; //vertices of the face (convex polygon) private var pt:Array; //color of the face (if not -> parent object color) private var c:Number; //containing plane public var P:Plane3D; //vertices buffers for clipping private var p0B:Array; private var p1B:Array; //edges buffers for scan-conversion private var leB:Array; private var reB:Array; private var nLeft:Number; private var nRight:Number; //S-Buffer pointer private var sB:SBuffer; //backface culling private var pbfc:Number; //backface culing status (if not -> parent object bfc status) public var bfc:Boolean; ///////////////////////////////////////////////////////////////////////////////////////// //constructor public function Polygon3D(vp:ViewPort,o:Object3D,pt:Array,c:Number,bfc:Boolean,p0B:Array,p1B:Array,leB:Array,reB:Array,sB:SBuffer) { //viewport address this.vp = vp; //object address this.o = o; //vertices of the face (convex polygon) this.pt = pt; //face color this.c = c; //backface culling status this.bfc = bfc; //vertices buffers addresses for clipping this.p0B = p0B; this.p1B = p1B; //edges buffers addresses for face (convex polygon) scan-conversion this.leB = leB; this.reB = reB; //s-buffer address this.sB = sB; //plane containing the face (convex polygon) with normal = (a,b,c) and argmin(dist(P,(0,0,0))) = -d.normal P = new Plane3D(); P.set(pt[0].x,pt[0].y,pt[0].z,pt[1].x,pt[1].y,pt[1].z,pt[2].x,pt[2].y,pt[2].z); } ///////////////////////////////////////////////////////////////////////////////////////// public function render():Void { var e:Point3D = vp.e; //backface culling pbfc = (P.a*(e.x+P.d*P.a) + P.b*(e.y+P.d*P.b) + P.c*(e.z+P.d*P.c))/Math.sqrt((e.x+P.d*P.a)*(e.x+P.d*P.a)+(e.y+P.d*P.b)*(e.y+P.d*P.b)+(e.z+P.d*P.c)*(e.z+P.d*P.c)); if ((pbfc < 0.0) || !((bfc == null) ? o.bfc : bfc)){ //viewing base vectors shortcuts var n:Point3D = vp.n; var u:Point3D = vp.u; var v:Point3D = vp.v; //viewing coordinates computation & clipping vs z = near plane var x:Number = pt[0].x - e.x; var y:Number = pt[0].y - e.y; var z:Number = pt[0].z - e.z; var xt:Number = x*u.x + y*u.y + z*u.z; var yt:Number = x*v.x + y*v.y + z*v.z; var zt:Number = x*n.x + y*n.y + z*n.z; var xt0:Number = xt; var yt0:Number = yt; var zt0:Number = zt; var xtd:Number = xt; var ytd:Number = yt; var ztd:Number = zt; var i:Number = 0; var j:Number = 0; if (-zt >= vp.near){ p0B[j++].set(xt,yt,zt); } var t:Number; var l:Number = pt.length; while(++i < l){ x = pt[i].x - e.x; y = pt[i].y - e.y; z = pt[i].z - e.z; xt = x*u.x + y*u.y + z*u.z; yt = x*v.x + y*v.y + z*v.z; zt = x*n.x + y*n.y + z*n.z; if (-ztd >= vp.near){ if (-zt >= vp.near){ p0B[j++].set(xt,yt,zt); } else { t = (ztd + vp.near)/(ztd - zt); p0B[j++].set((1-t)*xtd+t*xt,(1-t)*ytd+t*yt,-vp.near); } } else if (-zt >= vp.near){ t = (ztd + vp.near)/(ztd - zt); p0B[j++].set((1-t)*xtd+t*xt,(1-t)*ytd+t*yt,-vp.near); p0B[j++].set(xt,yt,zt); } xtd = xt; ytd = yt; ztd = zt; } if (j){ if (-ztd >= vp.near){ if (!(-zt0 >= vp.near)){ t = (ztd + vp.near)/(ztd - zt0); p0B[j++].set((1-t)*xtd+t*xt0,(1-t)*ytd+t*yt0,-vp.near); } } else if (-zt0 >= vp.near){ t = (ztd + vp.near)/(ztd - zt0); p0B[j++].set((1-t)*xtd+t*xt0,(1-t)*ytd+t*yt0,-vp.near); } } //clipping vs the 4 viewing frustrum planes (left,right,bottom,top) j = clip(p0B,j,vp.lp,p1B); j = clip(p1B,j,vp.rp,p0B); j = clip(p0B,j,vp.bp,p1B); j = clip(p1B,j,vp.tp,p0B); //if there are still some points (j) and the face (convex polygon) is a regular one in the sreen space //scan conversion of the face (convex polygon) and fill the s-buffer with each segment if (j && edges(p0B,j,leB,reB)) scan(); } } ///////////////////////////////////////////////////////////////////////////////////////// //compute the color of the face depending on the amount of light it receives private function light():Number { var c:Number = (this.c) ? this.c : o.color; //RGB values computation var r:Number = (c >> 16 & 255)/255; var g:Number = (c >> 8 & 255)/255; var b:Number = (c & 255)/255; //Compute HSV values var h:Number, s:Number, v:Number; var max:Number = Math.max(Math.max(r,g),b); var min:Number = Math.min(Math.min(r,g),b); v = max; s = (max != 0.0) ? ((max - min)/max) : 0.0; if (s == 0.0){ h = -1; } else { var d:Number = max - min; if (r == max) h = (g - b)/d; else if (g == max) h = 2.0 + (b - r)/d; else if (b == max) h = 4.0 + (r - g)/d; h *= 60; if (h < 0.0) h += 360; } //luminosity intensity coefficient var It:Number = o.Ka*vp.Ia + o.Kd*vp.Il*Math.max(Math.abs(pbfc),0) + o.Ks*vp.Il*Math.pow(2*pbfc*pbfc,2); //then change the luminosity & the saturation values v = Math.min(It*v,1.0); if (It*v > 1.0) s *= Math.max(1-0.8*Math.log(It*v),0); //and then compute the corresponding RGB values if (s == 0.0) { if (h == -1){ r = v; g = v; b = v; } else { trace("Error color s = 0 & h != undefined"); } } else { var f:Number,p:Number,q:Number,t:Number; if (h == 360) h = 0.0; h /= 60; f = h - Math.floor(h); p = v*(1.0 - s); q = v*(1.0 - s*f); t = v*(1.0 - s*(1.0 - f)); switch(Math.floor(h)){ case 0: r = v; g = t; b = p; break; case 1: r = q; g = v; b = p; break; case 2: r = p; g = v; b = t; break; case 3: r = p; g = q; b = v; break; case 4: r = t; g = p; b = v; break; case 5: r = v; g = p; b = q; break; } } return (int(r*255) << 16 | int(g*255) << 8 | int(b*255)); } ///////////////////////////////////////////////////////////////////////////////////////// //scan conversion of the face (convex polygon) described by the l vertices in the //array pI and fill the S-Buffer with each scan line private function scan():Void { //1/z linear interpolation constants computation var a:Number = P.a*vp.u.x + P.b*vp.u.y + P.c*vp.u.z; var b:Number = P.a*vp.v.x + P.b*vp.v.y + P.c*vp.v.z; var c:Number = P.a*vp.n.x + P.b*vp.n.y + P.c*vp.n.z; var d:Number = -(a*p0B[0].x + b*p0B[0].y + c*p0B[0].z); var dzdx:Number = a/(vp.f*d); var dzdy:Number = -b/(vp.f*d); var z0:Number = -dzdx*vp.width/2-dzdy*vp.height/2-c/d; //scan conversion var L:Array; var R:Array; var nL:Number; var nR:Number; if (pbfc < 0.0) { L = leB; R = reB; nL = nLeft; nR = nRight; } else { L = reB; R = leB; nL = nRight; nR = nLeft; } var lx:Number = L[0].x; var rx:Number = R[0].x; var ldxdy:Number = L[0].dxdy; var rdxdy:Number = R[0].dxdy; var ldy:Number = L[0].dy; var rdy:Number = R[0].dy; var y:Number = L[0].y; var z:Number = z0 + dzdy*y; var i:Number = 0; var j:Number = 0; var s:Segment3D = new Segment3D(); s.dzdx = dzdx; s.o = o; s.c = light(); while(1){ if (!(y % sB.s) && (lx < rx)) { s.x0 = lx; s.x1 = rx; s.z0 = z; sB.ins(s,y); } if(--ldy == 0){ if(++i == nL) return; lx = L[i].x; ldxdy = L[i].dxdy; ldy = L[i].dy; } else{ lx += ldxdy; } if(--rdy == 0){ if(++j == nR) return; rx = R[j].x; rdxdy = R[j].dxdy; rdy = R[j].dy; } else{ rx += rdxdy; } z += dzdy; y++; } } ///////////////////////////////////////////////////////////////////////////////////////// //prepare the scan conversion of the face (convex polygon) described // by the l vertices in the array pI //and in the same time screen projection private function edges(pI:Array,l:Number):Number { var minY:Number = 0x10000000; var maxY:Number = -0x10000000; var nb:Number; var nt:Number; var s:Number; var y:Number; var i:Number = -1; while(++i < l){ s = vp.f/(-pI[i].z); y = (-pI[i].y*s + vp.height/2); if(y < minY){nt = i; minY = y;} if(y > maxY){nb = i; maxY = y;} } if(Math.ceil(minY) >= Math.ceil(maxY)) return 0; nLeft = 0; i = nt; s = vp.f/(-pI[i].z); var x0:Number = pI[i].x*s + vp.width/2; var y0:Number = -pI[i].y*s + vp.height/2; var x1:Number; var y1:Number; var d:Number; var dxdy:Number; while(i != nb){ i = (i) ? i-1 : l-1; s = vp.f/(-pI[i].z); x1 = pI[i].x*s + vp.width/2; y1 = -pI[i].y*s + vp.height/2; if (d = Math.ceil(y1)-Math.ceil(y0)){ dxdy = (x1-x0)/(y1-y0); leB[nLeft++].set(x0+(Math.ceil(y0)-y0)*dxdy,Math.ceil(y0),dxdy,d); } x0 = x1; y0 = y1; } nRight = 0; i = nt; s = vp.f/(-pI[i].z); x0 = pI[i].x*s + vp.width/2; y0 = -pI[i].y*s + vp.height/2; while(i != nb){ i = (i+1) % l; s = vp.f/(-pI[i].z); x1 = pI[i].x*s + vp.width/2; y1 = -pI[i].y*s + vp.height/2; if (d = Math.ceil(y1)-Math.ceil(y0)){ dxdy = (x1-x0)/(y1-y0); reB[nRight++].set(x0+(Math.ceil(y0)-y0)*dxdy,Math.ceil(y0),dxdy,d); } x0 = x1; y0 = y1; } return 1; } ///////////////////////////////////////////////////////////////////////////////////////// //clip the face (convex polygon) described by the l points in the array pI //against the plane p and store the result in the array p0 and //return the number of vertices of the clipped face (convex polygon) private function clip(pI:Array,l:Number,p:Plane3D,pO:Array):Number { var i:Number = 0; var j:Number = 0; var x:Number = pI[0].x; var y:Number = pI[0].y; var z:Number = pI[0].z; var x0:Number = x; var y0:Number = y; var z0:Number = z; var xd:Number = x; var yd:Number = y; var zd:Number = z; var t:Number; var a:Number; var b:Number; if (x*p.a+y*p.b+z*p.c+p.d <= 0){ pO[j++].set(x,y,z); } while(++i < l){ x = pI[i].x; y = pI[i].y; z = pI[i].z; if ((a=xd*p.a+yd*p.b+zd*p.c+p.d) <= 0){ if ((b=x*p.a+y*p.b+z*p.c+p.d) <= 0){ pO[j++].set(x,y,z); } else { t = a/(a-b); pO[j++].set((1-t)*xd+t*x,(1-t)*yd+t*y,(1-t)*zd+t*z); } } else if ((b=x*p.a+y*p.b+z*p.c+p.d) <= 0){ t = a/(a-b); pO[j++].set((1-t)*xd+t*x,(1-t)*yd+t*y,(1-t)*zd+t*z); pO[j++].set(x,y,z); } xd = x; yd = y; zd = z; } if (j){ if ((a=xd*p.a+yd*p.b+zd*p.c+p.d) <= 0){ if (!((b=x0*p.a+y0*p.b+z0*p.c+p.d) <= 0)){ t = a/(a-b); pO[j++].set((1-t)*xd+t*x0,(1-t)*yd+t*y0,(1-t)*zd+t*z0); } } else if ((b=x0*p.a+y0*p.b+z0*p.c+p.d) <= 0){ t = a/(a-b); pO[j++].set((1-t)*xd+t*x0,(1-t)*yd+t*y0,(1-t)*zd+t*z0); } } return j; } ///////////////////////////////////////////////////////////////////////////////////////// }