/*
* This file is part of 3DzzD http://dzzd.net/.
*
* Released under LGPL
*
* 3DzzD is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 3DzzD 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with 3DzzD.  If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2005 - 2009 Bruno Augier
*/

package net.dzzd.core;

import net.dzzd.access.*;
import net.dzzd.utils.*;
import net.dzzd.DzzD;

import java.awt.*;


public final class Render3DSW extends Render3D
{	
	//Render size related variables
	private int 		renderPixelWidth;		//Rendered image width in pixel
	private int 		renderPixelHeight;		//Rendered image height in pixel
	private double 		renderPixelWidthDiv2;	//Rendered image width in pixel divided by 2
	private double 		renderPixelHeightDiv2;	//Rendered image height in pixel divided by 2
	
	//Buffers	 
	private int zBuffer[];			//Segment zBuffer
	private short zBufferO[];			//Object ID buffer related to segment zBuffer 
	private int zBufferP[];			//Face ID buffer related to segment zBuffer
	private int zBufferF[];			//Face ID segment buffer used for drawing and made with zBuffer once all faces have been past to zBuffer
	//private int zBufferFA[];		//Face ID segment buffer used for drawing and made with zBufferAlpha once all faces have been past to zBufferAlpha
	

	//Rendered image information
	private int nbRenderedFace;		//Number of rendered faces (with at least one visible pixel on rendered frame) 
	private int nbRenderedMesh;		//Number of rendered meshes (with at least one visible pixel on rendered frame) 

	//Face to scanline converter variables
	private int vLinesBufferMin[];
	private double vLinesBufferMinX[];
	private double vLinesBufferMinIncX[];
	private double vLinesBufferMinIz[];
	private double vLinesBufferMinIncIz[];
	
	private int vLinesBufferMax[];
	private double vLinesBufferMaxX[];
	private double vLinesBufferMaxIncX[];
	private double vLinesBufferMaxIz[];
	private double vLinesBufferMaxIncIz[];
	
	private int hLinesBufferMin;				//Minimum Y value found for current face scanline conversion
	private int hLinesBufferMax;				//Maximum Y value found for current face scanline conversion
	
   
	private int zBufferMode;					//zBuffer mode

	private boolean zBufferOcclusionTest;		//zBuffer occlusion test enabled
	
	//
	private double noFaceZ;
	private double noFaceIZ;
	private double noFaceX1;
	private int noFaceY1;
	private double noFaceX2;
	private int noFaceY2;
	
	final static int ZB_WRITE = 1;	
	final static int ZB_ALPHA = 2;
	final static int ZB_TEST = 4;
	
	Drawer drawer;
	
	int lastRenderBackgroundOffset;		//Background last render offset
	int firstRenderBackgroundOffset;	//Background first render offset
	
	
	//Face3D firstAlphaFace;
    //Face3D lastAlphaFace;
    
    private CompiledMesh3D firstMeshToRender;
    private CompiledMesh3D compiledMeshes[];
    private CompiledMaterial compiledMaterials[];
    private CompiledTexture compiledTextures[];
    
    
    class CompiledTexture
    {
    	
    	
    }
    
    class CompiledFace
    {
    	Face3D face;
    	CompiledFace nextFaceToRender;
    	CompiledMaterial compiledMaterial;
    	
    	int lastRenderImage;
    	int firstRenderOffset;
    	int lastRenderOffset;
    	int lastZbufferImage;
    	
		float p0tx,p0ty,p0tz;		//Face tangent for vertex p0
		float p1tx,p1ty,p1tz;		//Face tangent for vertex p1
		float p2tx,p2ty,p2tz;		//Face tangent for vertex p2
		
		float p0bx,p0by,p0bz;		//Face bitangent for vertex p0
		float p1bx,p1by,p1bz;		//Face bitangent for vertex p1
		float p2bx,p2by,p2bz;		//Face bitangent for vertex p2	
		
		//SCREEN SPACE
		
		double iZA,iZB,iZC,iD;  
		double a,b,c;
		double zMin,zMax;
    		
    	CompiledFace(Face3D face)
    	{
    		this.face=face;
    		this.nextFaceToRender=null;
    		this.lastRenderImage=-1;
    		this.firstRenderOffset=-1;
    		this.lastRenderOffset=-1;
    		this.lastZbufferImage=-1;
    		
    		
    		this.computeTBN();
    		
    		if(face.material!=null)
    			this.compiledMaterial=compiledMaterials[face.material.id];
    		else
    			this.compiledMaterial=compiledMaterials[0];
    			
    	}
    	
    	//Compute face plane equation and variables in camera space
    	public void computeScreenSpace()
    	{
    		Render3DSW r=Render3DSW.this;
			double onx=this.face.p0.x;
			double ony=this.face.p0.y;
			double onz=this.face.p0.z;
						
			double nx=this.face.pa+onx;
			double ny=this.face.pb+ony;
			double nz=this.face.pc+onz;
			
			double xo=ox+r.axx*onx+r.ayx*ony+r.azx*onz;
			double yo=oy+r.axy*onx+r.ayy*ony+r.azy*onz;
			double zo=oz+r.axz*onx+r.ayz*ony+r.azz*onz;			
				
			double pa=ox+r.axx*nx+r.ayx*ny+r.azx*nz;
			double pb=oy+r.axy*nx+r.ayy*ny+r.azy*nz;
			double pc=oz+r.axz*nx+r.ayz*ny+r.azz*nz;
		
			this.a=pa-xo;
			this.b=pb-yo;
			this.c=pc-zo;	
			double d=-(a*xo+b*yo+c*zo);
	
			this.iD=1.0/d;
			double iDiFocus=this.iD*iFocus;

	
			this.iZA=-this.a*iDiFocus;
			this.iZB=-this.b*iDiFocus;
			this.iZC=-this.c*this.iD;	   		
    	}
    	
    	//Compute texture axis (TBN Matrix) in object space
    	void computeTBN()
    	{
    		
			double t2t1=this.face.u1-this.face.u0;
			double b2b1=this.face.v1-this.face.v0;
			double t3t1=this.face.u2-this.face.u0;
			double b3b1=this.face.v2-this.face.v0;
			
			
			//Compute tangent vector	
			Point3D T1=new Point3D();
			T1.copy(this.face.p1);
			T1.sub(this.face.p0);
			T1.mul(-b3b1);
			
			Point3D T2=new Point3D();
			T2.copy(this.face.p2);
			T2.sub(this.face.p0);
			T2.mul(-b2b1);
			
			T1.sub(T2);
			
			T1.normalize();
			T1.mul(-1);
			
			
			this.p0tx=(float)T1.x;
			this.p0ty=(float)T1.y;
			this.p0tz=(float)T1.z;
			this.p1tx=(float)T1.x;
			this.p1ty=(float)T1.y;
			this.p1tz=(float)T1.z;
			this.p2tx=(float)T1.x;
			this.p2ty=(float)T1.y;
			this.p2tz=(float)T1.z;	
			
			//Compute bitangent vector	
			
			Point3D B1=new Point3D();
			B1.copy(this.face.p1);
			B1.sub(this.face.p0);
			B1.mul(t3t1);
			
			Point3D B2=new Point3D();
			B2.copy(this.face.p2);
			B2.sub(this.face.p0);
			B2.mul(t2t1);
			
			B1.sub(B2);
			
			B1.normalize();
			
			
			this.p0bx=(float)B1.x;
			this.p0by=(float)B1.y;
			this.p0bz=(float)B1.z;
			this.p1bx=(float)B1.x;
			this.p1by=(float)B1.y;
			this.p1bz=(float)B1.z;
			this.p2bx=(float)B1.x;
			this.p2by=(float)B1.y;
			this.p2bz=(float)B1.z;	
    	}
    }
    
    class CompiledMesh3DOctree
    {
    	int lastVisibleImage;
    	Mesh3DOctree tree;
    	
    	CompiledMesh3DOctree(Mesh3DOctree tree)
    	{
    		this.tree=tree;
    		this.lastVisibleImage=-1;
    	} 	
    	
    }
    
	class CompiledMesh3D
	{
		Mesh3D mesh;
		CompiledMesh3D nextMeshToRender;
		int lastRenderImage;	
		int nbRenderFace;
	
	
		int cameraPositionEvaluated[];		//Last screen/caméra image number for wich position has been updated
		double xs[];						//When rendering screen x position (only if z>zMin)
		double ys[];						//When rendering screen x position (only if z>zMin)
		double iz[];						//When rendering 1/z position (only if z>zMin)
		double xp[];						//When rendering x position in camera
		double yp[];						//When rendering y position in camera
		double zp[];						//When rendering z position in camera

		
		CompiledFace compiledFaces[];		
		CompiledFace firstFaceToRender;
		CompiledMesh3DOctree compiledMesh3DOctrees[];
		
		CompiledMesh3D(Mesh3D mesh)
		{
			this.mesh=mesh;
			this.nextMeshToRender=null;
			this.nbRenderFace=0;
			this.lastRenderImage=-1;
			int nbVertex=mesh.getNbVertex3D();
			this.compiledFaces=new CompiledFace[mesh.getNbFace3D()];
			this.cameraPositionEvaluated=new int[nbVertex];
			for(int n=0;n<this.cameraPositionEvaluated.length;n++)
				this.cameraPositionEvaluated[n]=-1;
				
			this.xs=new double[nbVertex];
			this.ys=new double[nbVertex];
			this.iz=new double[nbVertex];
			this.xp=new double[nbVertex];
			this.yp=new double[nbVertex];
			this.zp=new double[nbVertex];
			
			
			this.compiledMesh3DOctrees=null;
			for(int n=0;n<mesh.getNbFace3D();n++)
				this.compiledFaces[n]=new CompiledFace((Face3D) mesh.getFace3D(n));
				
				
			//Smooth TBN matrix
			
			
			if(mesh.getMesh3DOctree()!=null)
			{
				int nbChildrens=1+mesh.octree.getNbChildren(true);
				this.compiledMesh3DOctrees=new CompiledMesh3DOctree[nbChildrens];
				IMesh3DOctree m[]=mesh.octree.getMesh3DOctreeArray(new Mesh3DOctree[nbChildrens]);
				for(int x=0;x<nbChildrens;x++)
					this.compiledMesh3DOctrees[x]=new CompiledMesh3DOctree((Mesh3DOctree)m[x]);
				
			}
		}
		
	}	
	
	class CompiledMaterial
	{
		private static final int MAT_SIZE=2048;
		Material material;
		int specularLightMap[];
		
		CompiledMaterial(Material material)
		{
			this.material=material;
			this.specularLightMap=new int[MAT_SIZE+1];
			this.initSpecularLightMap();
		}
		
		public void initSpecularLightMap()
		{	
			for(int n=0;n<MAT_SIZE;n++)
			{	
				//Sans remplacement de l'ambiant par une lumiere diffuse (fonctionnement normal)
				int sin=(n-(MAT_SIZE>>1)); 
				double lightdouble=2.0*((double)sin)/(MAT_SIZE>>1);//>>1);
				
				//TODO: PATCH a virer : Avec remplacement de l'ambiant par une lumiere diffuse (eclaire les partie normalement en  lumiere ambiant seulement)
				//int sin=(n-(MAT_SIZE>>1));
				//double lightdouble=0.25+1.5*((double)sin)/(MAT_SIZE>>1);//>>1);				
				//this.material.selfIlluminationLevel=10;
				
				//FIN PATCH A VIRER
				
				if(lightdouble<0.0) lightdouble=0.0;
				if(lightdouble>1.0) lightdouble=1.0; 
				
				int light=((int)(lightdouble*255.0));
				light+=(this.material.selfIlluminationLevel*255)/100;
				if(light>255)
				light=255;
				if(this.material.specularLevel!=0)
				{				
					double redSpecularD=(this.material.specularColor&0xFF0000)>>16;				
					double greenSpecularD=(this.material.specularColor&0xFF00)>>8;
					double blueSpecularD=(this.material.specularColor&0xFF);
					int specularLight=specularLight=(int)(Math.pow(lightdouble,this.material.specularPower)*this.material.specularLevel);
				
					redSpecularD*=specularLight/256.0;
					greenSpecularD*=specularLight/256.0;
					blueSpecularD*=specularLight/256.0;
		
					int redSpecular=(int)redSpecularD;		
					int greenSpecular=(int)greenSpecularD;		
					int blueSpecular=(int)blueSpecularD;			
		
					if(redSpecular<0) redSpecular=0;		
					if(greenSpecular<0) greenSpecular=0;		
					if(blueSpecular<0)	blueSpecular=0;			
		
					if(redSpecular>255)		redSpecular=255;		
					if(greenSpecular>255)  	greenSpecular=255;		
					if(blueSpecular>255)	blueSpecular=255;
		
					this.specularLightMap[n]=(light<<24)|(((redSpecular<<16)|(greenSpecular<<8)|(blueSpecular)));
				}
				else
					this.specularLightMap[n]=light<<24;
			}		
		}

	}
		
	public Render3DSW()
	{
		super();
		this.directInput=new DirectInput(this.canvas);
		this.drawer=new Drawer();
		this.initCompiledBuffers();
	}
	
	private final void initCompiledBuffers()
	{
		this.compiledMeshes=new CompiledMesh3D[65536];
		this.compiledMaterials=new CompiledMaterial[1024];
		this.compiledTextures=new CompiledTexture[1024];		
		this.firstMeshToRender=null;
		this.firstRenderBackgroundOffset=-1;
		this.resetBuffers();
	}
	
	public void reset()
	{
		//this.initCompiledBuffers();
	}
	
	public final void clearScene(IScene scene)
	{
		super.clearScene(scene);
		this.initCompiledBuffers();		
	}
	
	protected final void disposeMesh3D(IMesh3D mesh)
	{
		Log.log("Render3DSW.disposeMesh3D() : " + mesh.getName());
		int n=mesh.getId();
		CompiledMesh3D nextCompiledMesh=this.compiledMeshes[n+1];
		do
		{
			this.compiledMeshes[n++]=nextCompiledMesh;
			nextCompiledMesh=this.compiledMeshes[n+1];
		}
		while(nextCompiledMesh!=null);
		
		this.firstMeshToRender=null; //disable frame coherence
	}
	
	protected final void disposeCamera3D(ICamera3D camera)
	{
		//Log.log(this.getClass(),"Dispose Camera3D (SOFTWARE) : " + camera.getName());
	}	
	
	protected final void disposeLight3D(ILight3D light)
	{
		//Log.log(this.getClass(),"Dispose Light3D (SOFTWARE) : " + light.getName());
	}		
	
	protected final void disposeTexture(ITexture texture)
	{
		//Log.log(this.getClass(),"Dispose ITexture (SOFTWARE) : " + texture.getName());
	}			
	
	protected final void disposeMaterial(IMaterial material)
	{
		//Log.log(this.getClass(),"Dispose Material (SOFTWARE) : " + material.getName());
		int n=material.getId();
		CompiledMaterial nextCompiledMaterial=this.compiledMaterials[n+1];
		do
		{
			this.compiledMaterials[n++]=nextCompiledMaterial;
			nextCompiledMaterial=this.compiledMaterials[n+1];
		}
		while(nextCompiledMaterial!=null);
	}				
	
	protected final void compileMesh3D(IMesh3D mesh)
	{
		super.compileMesh3D(mesh);
		//Log.log("Compile Mesh3D (Software):" + mesh.getName());
		this.compiledMeshes[mesh.getId()]=new CompiledMesh3D((Mesh3D)mesh);		
	}
	
	protected final void compileMaterial(IMaterial material)
	{
		super.compileMaterial(material);
		//Log.log(this, "Compile Material (Software):" + material.getName());
		CompiledMaterial m=new CompiledMaterial((Material)material);
		//m.initSpecularLightMap();
		this.compiledMaterials[material.getId()]=m;
	}

	public final void setSize(int viewPixelWidth,int viewPixelHeight,int maxAntialias)
	{
		super.setSize(viewPixelWidth,viewPixelHeight,maxAntialias);

		this.renderPixelWidth=this.viewPixelWidth;
		this.renderPixelHeight=this.viewPixelHeight;
		
			
		if((this.antialias&2)!=0)	
			this.renderPixelWidth=this.viewPixelWidth<<1;
			
		if((this.antialias&4)!=0)				
			this.renderPixelHeight=this.viewPixelHeight<<1;	
			
		this.minXValue=0;
		this.maxXValue=this.renderPixelWidth-1;
		this.minYValue=0;	
		this.maxYValue=this.renderPixelHeight-1;	
		this.renderPixelWidthDiv2=this.renderPixelWidth>>1;
		this.renderPixelHeightDiv2=this.renderPixelHeight>>1;
	
		this.initBuffers();
		this.drawer.setBuffers(this,this.zBuffer,this.zBufferF);
		//this.resetBuffers();		
	}
	
	private final void renderFrameCoherence(IScene3D scene)
	{
		this.zBufferOcclusionTest=false;
		
		CompiledMesh3D compiledMesh=this.getFirstMeshToRender();
	
		while(compiledMesh!=null)
		{
			Mesh3D mesh=compiledMesh.mesh;
			if(mesh.isVisible())
			{
				if((mesh.renderMode&DzzD.RM_LIGHT)!=0)
					this.prepareMesh3DLocalLight3DBuffer(scene,mesh);
				
				if(mesh.getMesh3DViewGenerator()==null)
				{
					this.setCurrentMesh3D(mesh);
					CompiledFace compiledFace=compiledMesh.firstFaceToRender;
					while(compiledFace!=null)
					{
						this.setFaces3DToZBuffer(mesh.getFaces3D(),compiledFace.face.id);
						compiledFace.lastZbufferImage=this.numImage;
						compiledFace=compiledFace.nextFaceToRender;
					}
				}
			}
			compiledMesh=this.getNextMeshToRender(compiledMesh);
		}
		this.zBufferOcclusionTest=true;		
	}
	
	protected final void startFrame(IScene3D scene)
	{
		this.drawer.setRender3DSW(this);
		this.resetBuffers();
		this.zBufferMode = ZB_WRITE;
		//this.firstAlphaFace=null;
		//this.lastAlphaFace=null;
		//this.renderFrameCoherence(scene);		
	}
	
	protected final void renderFrame(IScene3D scene)
	{
		super.renderFrame(scene);
	}	
	
	protected final void endFrame(IScene3D scene)
	{		
		if(!this.isPixelUpdateEnabled)
			return;
			
		this.prepareFaceBuffer(scene);

		if(scene.isBackgroundEnabled())
			this.renderBackground(scene);
		
		this.drawMesh3D(scene);
		
		this.drawer.antialiasPixels();
		
		if(this.isScreenUpdateEnabled)
			this.drawer.drawPixelsOnCanvas(this.canvas);
	}
	
	private final void initBuffers()
	{					
		this.zBuffer=new int[this.renderPixelHeight*this.renderPixelWidth];
		this.zBufferO=new short[this.renderPixelHeight*this.renderPixelWidth];
		this.zBufferP=new int[this.renderPixelHeight*this.renderPixelWidth];
		this.zBufferF=new int[this.renderPixelHeight*this.renderPixelWidth];
		//this.zBufferFA=new int[this.renderPixelHeight*this.renderPixelWidth];
		
		this.vLinesBufferMin=new int[this.renderPixelHeight];
		this.vLinesBufferMinX=new double[this.renderPixelHeight];
		this.vLinesBufferMinIncX=new double[this.renderPixelHeight];
		this.vLinesBufferMinIz=new double[this.renderPixelHeight];
		this.vLinesBufferMinIncIz=new double[this.renderPixelHeight];
		
		this.vLinesBufferMax=new int[this.renderPixelHeight];
		this.vLinesBufferMaxX=new double[this.renderPixelHeight];
		this.vLinesBufferMaxIncX=new double[this.renderPixelHeight];
		this.vLinesBufferMaxIz=new double[this.renderPixelHeight];
		this.vLinesBufferMaxIncIz=new double[this.renderPixelHeight];
				
	
		
	}
	
	private final void resetBuffers()
	{
		int yOfs=0;
		for(int y=0;y<this.renderPixelHeight;y++)
		{
			this.zBuffer[yOfs]=this.renderPixelWidth;
			this.zBufferO[yOfs]=-1;
			this.zBufferP[yOfs]=-1;
			yOfs+=this.renderPixelWidth;
		}
		
	}	
	
	public final void setAntialiasLevel(int level)
	{
		if(level==this.antialias)
			return;
		
		this.firstMeshToRender=null;
		this.firstRenderBackgroundOffset=-1;
		super.setAntialiasLevel(level);
		
	}

	protected final void renderBackground(IScene3D scene)
	{
		if(this.firstRenderBackgroundOffset==-1)
			return;		
		this.drawer.drawBackground(this.firstRenderBackgroundOffset,this.lastRenderBackgroundOffset,scene.getBackgroundColor());
	
	}



	protected final void setMesh3DToZBuffer(IMesh3D mesh)
	{
		CompiledMesh3D cm=compiledMeshes[mesh.getId()];
		//Log.log(" " +(cm.lastRenderImage-this.numImage));
		if(this.zBufferOcclusionTest && (this.numImage-cm.lastRenderImage)>1)
		{
		
			IPoint3D center=mesh.getCenter();
			double cx=center.getX();;
			double cy=center.getY();
			double cz=center.getZ();
			double radius=mesh.getSphereBox();
		
			if(!isSphereVisibleInZBuffer(cx,cy,cz,radius))
				return;
		}
		this.setFaces3DToZBuffer(mesh.getFaces3D(),-1);
	}
	
	protected void _setMesh3DOctreeToZBuffer(Mesh3D ob,Mesh3DOctree tree)
	{
		
		CompiledMesh3D cm=compiledMeshes[ob.getId()];
		
		
		//Log.log(" " +(cm.lastRenderImage-this.numImage));
		if(this.zBufferOcclusionTest && (this.numImage-cm.lastRenderImage)>1)
		{
		
			IPoint3D center=ob.getCenter();
			double cx=center.getX();;
			double cy=center.getY();
			double cz=center.getZ();
			double radius=ob.getSphereBox();
			
			if(!this.isSphereVisible(cx,cy,cz,radius))
				return;
				
			if(!isSphereVisibleInZBuffer(cx,cy,cz,radius))
				return;
		}
		
				
		double cx=tree.center.x;
		double cy=tree.center.y;
		double cz=tree.center.z;
		double radius=tree.visibilitySphereBoxRadius;
		if(this.isSphereVisible(cx,cy,cz,radius))
		{
			if(tree.getNbFace3D()>0)
				this.setMesh3DOctreeToZBuffer(tree);

			for(int x=0;x<tree.getNbChildren(false);x++)
				if(tree.childrens[x]!=null)
					this._setMesh3DOctreeToZBuffer(ob,tree.childrens[x]);
		}		
	}	
		
	protected final void setMesh3DOctreeToZBuffer(IMesh3DOctree tree)
	{		
		this.setFaces3DToZBuffer(tree.getFaces3D(),-1);
	}	

	protected final boolean isSphereVisible(double cx,double cy,double cz,double radius)
	{
		boolean visible=super.isSphereVisible(cx,cy,cz,radius);

		//if(!this.zBufferOcclusionTest || visible==0)
		//	return visible;
		
		//visible=isSphereVisibleInZBuffer(cx,cy,cz,radius);	
		
		return visible;	
	}
	
	private final boolean isSphereVisibleInZBuffer(double cx,double cy,double cz,double radius)
	{
		double cpx=ox+axx*cx+ayx*cy+azx*cz;	//Compute from object space to camera space
		double cpy=oy+axy*cx+ayy*cy+azy*cz;
		double cpz=oz+axz*cx+ayz*cy+azz*cz;	

		//IF IN SPHERE SET VISIBLE
		if(cpx*cpx+cpy*cpy+cpz*cpz<=(radius*radius))
			return true;

		double pzmin=cpz-radius;
		if(pzmin<=this.zMin) return true;
		
		
		double pzmax=cpz+radius;	
		double piZ=1.0/cpz;
		double piZmin=1.0/pzmin;	
		double piZmax=1.0/pzmax;
				
		//Compute pxmin,pxmax,pymin,pymax
		int px1=(int)((cpx-radius)*this.screenZoomXFocus*piZmin+this.renderPixelWidthDiv2);
		int px2=(int)((cpx+radius)*this.screenZoomXFocus*piZmin+this.renderPixelWidthDiv2);
		int px3=(int)((cpx-radius)*this.screenZoomXFocus*piZmax+this.renderPixelWidthDiv2);
		int px4=(int)((cpx+radius)*this.screenZoomXFocus*piZmax+this.renderPixelWidthDiv2);		
		int py1=(int)((cpy-radius)*this.screenZoomXFocus*piZmin+this.renderPixelHeightDiv2);
		int py2=(int)((cpy+radius)*this.screenZoomXFocus*piZmin+this.renderPixelHeightDiv2);
		int py3=(int)((cpy-radius)*this.screenZoomXFocus*piZmax+this.renderPixelHeightDiv2);
		int py4=(int)((cpy+radius)*this.screenZoomXFocus*piZmax+this.renderPixelHeightDiv2);	
			
		int pxmin=px1;
		if(px2<pxmin) pxmin=px2;
		if(px3<pxmin) pxmin=px3;
		if(px4<pxmin) pxmin=px4;
		
		int pxmax=px1;
		if(px2>pxmax) pxmax=px2;
		if(px3>pxmax) pxmax=px3;
		if(px4>pxmax) pxmax=px4;
		
		int pymin=py1;
		if(py2<pymin) pymin=py2;
		if(py3<pymin) pymin=py3;
		if(py4<pymin) pymin=py4;
		
		int pymax=py1;
		if(py2>pymax) pymax=py2;
		if(py3>pymax) pymax=py3;
		if(py4>pymax) pymax=py4;
		
		if(this.minXValue>pxmax) return false;
		if(this.maxXValue<pxmin) return false;
		if(this.minYValue>pymax) return false;
		if(this.maxYValue<pymin) return false;		
		
		if(this.minXValue>pxmin) pxmin=this.minXValue;
		if(this.maxXValue<pxmax) pxmax=this.maxXValue; 
		if(this.minYValue>pymin) pymin=this.minYValue; 
		if(this.maxYValue<pymax) pymax=this.maxYValue;
		
		//TODO: optimize on size remove if sizeY to large
		double sizeX=pxmax-pxmin;//radius*this.screenZoomXFocus*piZmin;
		double sizeY=pymax-pymin;//radius*this.screenZoomYFocus*piZmin;
		if(sizeX*sizeY>renderPixelWidthDiv2*renderPixelHeightDiv2)	
			return true;
		
		
		return isSquareVisibleInZBuffer(pxmin,pymin,pxmax,pymax,piZmin);
	}
	
	double iZTest;
	private final boolean isSquareVisibleInZBuffer(int pxmin,int pymin,int pxmax,int pymax,double piZ)
	{		
	
		this.hLinesBufferMin=pymin;
		this.hLinesBufferMax=pymax;
		
		this.vLinesBufferMin[pymin]=pymax;
		this.vLinesBufferMinX[pymin]=pxmin;
		this.vLinesBufferMinIncX[pymin]=0.0;
		this.vLinesBufferMinIz[pymin]=piZ;
		this.vLinesBufferMinIncIz[pymin]=0.0;
		
		this.vLinesBufferMax[pymin]=pymax;
		this.vLinesBufferMaxX[pymin]=pxmax;
		this.vLinesBufferMaxIncX[pymin]=0.0;
		this.vLinesBufferMaxIz[pymin]=piZ;
		this.vLinesBufferMaxIncIz[pymin]=0.0;
		
		this.iZTest=piZ;


		int zBufferMode=this.zBufferMode;
		this.zBufferMode=ZB_TEST;
		int result=pasteHLine(null);
		
		
	/*
		Graphics g=this.canvas.getGraphics();
		if(result==0)
		{
		
			g.setColor(Color.RED);
		}
		else
			g.setColor(Color.BLUE);
			
		g.drawRect(pxmin,renderPixelHeight-pymax,pxmax-pxmin,pymax-pymin);
	*/
		
		this.zBufferMode=zBufferMode;
		
		//return 1;
		return (result==1);	
	}	

	private final CompiledFace getCompiledFace(int meshId,int faceId)
	{
		return this.compiledMeshes[meshId].compiledFaces[faceId];
	}
	int r=0;
	int r2=0;
	protected final int setFaces3DToZBuffer(IFace3D faceArray[],int faceNum)
	{
		
		int result=0;

		Face3D[] faceList=(Face3D[])faceArray;

		double clipzmin1xs=0;
		double clipzmin1ys=0;
		double clipzmin2xs=0;
		double clipzmin2ys=0;
		double x;
		double y;
		double z;
		
		//Log.log("setFaces3DToZBuffer"+this.numImage);
		Vertex3D p;
		
		
		int nPol=faceList.length; 
		int numP=0;	
		if(faceNum>=0)
		{
			numP=faceNum;
			nPol=faceNum+1;	
		}
		
		//int n=0;
		
		for(;numP<nPol;numP++)
		{
			
			Face3D po=faceList[numP];
			/*
			if(((this.zBufferMode & ZB_ALPHA) == 0) && (po.material!=null && po.material.alphaLevel != 0))
			{
				po.nextAlphaFace=null;
				if(this.firstAlphaFace==null)
				{
					this.firstAlphaFace=po;
					this.lastAlphaFace=po;	
				}
				else
				{
					po.nextAlphaFace=this.firstAlphaFace;
					this.firstAlphaFace=po;
				}
				continue;
			}*/			
			
			int meshId=po.object.id;
			CompiledFace compiledFace=this.getCompiledFace(meshId,po.id);
			CompiledMesh3D compiledMesh=this.compiledMeshes[meshId];
			
			if(compiledFace.lastZbufferImage==this.numImage)
				continue;
				
			compiledFace.lastZbufferImage=this.numImage;
			
			
			if(compiledFace.lastRenderImage==this.numImage)
				continue;

			
			//Compute z distance of camera from face, negative value mean camera is behind face
			//We use -10 rather than 0 to void preciion bug
			//double dPlan=po.pa*px+po.pb*py+po.pc*pz+po.pd;
	
	
			//double dPlan=po.pa*px+po.pb*py+po.pc*pz+po.pd;
			//if(dPlan<0.0)
			{
				//r2++;
				//if(r2%10000==0)
				//System.out.println(r2+" R2  "+r2/(this.numImage+1));
				
			//	continue;
				
			}
		
			
			//double dPlan2=po.pa*px+po.pb*py+po.pc*pz+po.pd;
			//double dPlan=po.pa*px+po.pb*py+po.pc*pz+po.pd;
			
			//this cause some artefacts but remove half of the faces on meshes... 
			
			//TODO: a revoir pour enlever les faces vu de dos, enlevé pour le moment car cause pas mal de bug d'affichage
			/*
			double dPlan2=po.pa*(po.center.x-px)+po.pb*(po.center.y-py)+po.pc*(po.center.z-pz);
			
			if(dPlan2>0.0)
			{
				//System.out.println(po.pa*po.pa+po.pb*po.pb+po.pc*po.pc);
				continue;
			}
			*/
			
			
			double yPMin=maxYValue;
			double yPMax=minYValue;
			double xPMin=maxXValue;
			double xPMax=minXValue;
			double zPMin=this.zMax;
			double zPMax=this.zMin;
			
			double xs0,ys0,iz0;
			double xs1,ys1,iz1;
			double xs2,ys2,iz2;
			
			double xp0,yp0,zp0;
			double xp1,yp1,zp1;
			double xp2,yp2,zp2;

			hLinesBufferMin=(Integer.MAX_VALUE-1);
			hLinesBufferMax=-(Integer.MAX_VALUE-1);	
								
			//Compute face center in camera space and 
			Point3D center=po.center;
			x=center.x;
			y=center.y;
			z=center.z;				
			double cx=ox+axx*x+ayx*y+azx*z;
			double cy=oy+axy*x+ayy*y+azy*z;
			double cz=oz+axz*x+ayz*y+azz*z;
	
			double radius=po.sphereBox;
			double pzmin=cz-radius;
			double pzmax=cz+radius;
			
			//Return if face surrounding sphereBox not in camera frustrum
			
			//FAR CLIP	
			if(pzmax<=this.zMin)
				continue;
	
			//NEAR CLIP		
			if(pzmin>=this.zMax)
				continue;
			
			//RIGHT CLIP
			if((RA*cx+RB*cy+RC*cz)>radius)
				continue;
	
			//LEFT CLIP
			if((LA*cx+LB*cy+LC*cz)>radius)
				continue;
	
			//UP CLIP
			if((UA*cx+UB*cy+UC*cz)>radius)
				continue;
	
			//DOWN CLIP
			if((DA*cx+DB*cy+DC*cz)>radius)
				continue;	
				
								
							
				int pid;
				int screenPosComputedImage[]=compiledMesh.cameraPositionEvaluated;
				double xs[]=compiledMesh.xs;	
				double ys[]=compiledMesh.ys;
				double iz[]=compiledMesh.iz;
				double xp[]=compiledMesh.xp;
				double yp[]=compiledMesh.yp;
				double zp[]=compiledMesh.zp;
				
				
				p=po.p0;
				pid=p.id;
				if(screenPosComputedImage[pid]!=this.numImage)
				{
					screenPosComputedImage[pid]=this.numImage;
					x=p.x;
					y=p.y;
					z=p.z;				
					xp0=ox+axx*x+ayx*y+azx*z;
					yp0=oy+axy*x+ayy*y+azy*z;
					zp0=oz+axz*x+ayz*y+azz*z;
					iz0=1/zp0;
					xs0=(xp0*this.screenZoomXFocus*iz0+this.renderPixelWidthDiv2);	//Calcul x1 sur l'ecran
					ys0=(yp0*this.screenZoomYFocus*iz0+this.renderPixelHeightDiv2);	//Calcul y1 sur l'ecran		
					if(zp0<0)
					{	
						xs0=-xs0;
						ys0=-ys0;						
					}					
					xs[pid]=xs0;						
					ys[pid]=ys0;						
					iz[pid]=iz0;
					zp[pid]=zp0;
					xp[pid]=xp0;
					yp[pid]=yp0;
					zp[pid]=zp0;
				}
				else
				{
					xs0=xs[pid];	
					ys0=ys[pid];
					iz0=iz[pid];
					xp0=xp[pid];
					yp0=yp[pid];										
					zp0=zp[pid];

				}	
					
				p=po.p1;
				pid=p.id;
				if(screenPosComputedImage[pid]!=this.numImage)
				{
					screenPosComputedImage[pid]=this.numImage;
					x=p.x;
					y=p.y;
					z=p.z;				
					xp1=ox+axx*x+ayx*y+azx*z;
					yp1=oy+axy*x+ayy*y+azy*z;
					zp1=oz+axz*x+ayz*y+azz*z;
					iz1=1/zp1;
					xs1=(xp1*this.screenZoomXFocus*iz1+this.renderPixelWidthDiv2);	//Calcul x1 sur l'ecran
					ys1=(yp1*this.screenZoomYFocus*iz1+this.renderPixelHeightDiv2);	//Calcul y1 sur l'ecran		
					if(zp1<0)
					{	
						xs1=-xs1;
						ys1=-ys1;						
					}					
					xs[pid]=xs1;						
					ys[pid]=ys1;
					iz[pid]=iz1;
					xp[pid]=xp1;
					yp[pid]=yp1;
					zp[pid]=zp1;
				}
				else
				{
					xs1=xs[pid];	
					ys1=ys[pid];
					iz1=iz[pid];
					xp1=xp[pid];
					yp1=yp[pid];										
					zp1=zp[pid];
				}
				
				p=po.p2;
				pid=p.id;	
				if(screenPosComputedImage[pid]!=this.numImage)
				{
					screenPosComputedImage[pid]=this.numImage;
					x=p.x;
					y=p.y;
					z=p.z;				
					xp2=ox+axx*x+ayx*y+azx*z;
					yp2=oy+axy*x+ayy*y+azy*z;
					zp2=oz+axz*x+ayz*y+azz*z;
					iz2=1/zp2;
					xs2=(xp2*this.screenZoomXFocus*iz2+this.renderPixelWidthDiv2);
					ys2=(yp2*this.screenZoomYFocus*iz2+this.renderPixelHeightDiv2);	
					if(zp2<0)
					{	
						xs2=-xs2;
						ys2=-ys2;						
					}					
					xs[pid]=xs2;						
					ys[pid]=ys2;
					iz[pid]=iz2;
					xp[pid]=xp2;
					yp[pid]=yp2;
					zp[pid]=zp2;
				}
				else
				{
					xs2=xs[pid];	
					ys2=ys[pid];
					iz2=iz[pid];
					xp2=xp[pid];
					yp2=yp[pid];										
					zp2=zp[pid];

				}									

				
				if(zp0>zPMax) zPMax=zp0;
				if(zp0<zPMin) zPMin=zp0;
				if(zp1>zPMax) zPMax=zp1;
				if(zp1<zPMin) zPMin=zp1;
				if(zp2>zPMax) zPMax=zp2;																
				if(zp2<zPMin) zPMin=zp2;
								
				
				if(zPMax<=this.zMin) continue;
				if(zPMin>=this.zMax) continue;
				
												
				if(ys0<yPMin) yPMin=ys0;
				if(ys0>yPMax) yPMax=ys0;
				if(ys1<yPMin) yPMin=ys1;
				if(ys1>yPMax) yPMax=ys1;					
				if(ys2<yPMin) yPMin=ys2;
				if(ys2>yPMax) yPMax=ys2;
				
				if(yPMax<=minYValue) continue;
				if(yPMin>=maxYValue) continue;

				if(yPMax<=(yPMin+0.0005)) continue;
			
				if(xs0<xPMin) xPMin=xs0;
				if(xs0>xPMax) xPMax=xs0;
				if(xs1<xPMin) xPMin=xs1;
				if(xs1>xPMax) xPMax=xs1;					
				if(xs2<xPMin) xPMin=xs2;
				if(xs2>xPMax) xPMax=xs2;
				
				if(xPMax<=minXValue) continue;
				if(xPMin>=maxXValue) continue;
					
				if(xPMax<=(xPMin+0.0005)) continue;
				
				if(zPMin>=this.zMin)
				{
					setHLigne(xs0,ys0,iz0,xs1,ys1,iz1);
					setHLigne(xs1,ys1,iz1,xs2,ys2,iz2);
					setHLigne(xs2,ys2,iz2,xs0,ys0,iz0);
				}
				else			
				{
					int clipZMin=0;
					int nVertex=0;
					Vertex3D p1=po.p0;
					Vertex3D p2=po.p1;
					double xsa=xs0;
					double ysa=ys0;
					double iza=iz0;
					double xpa=xp0;
					double ypa=yp0;
					double zpa=zp0;
					double xsb=xs1;
					double ysb=ys1;
					double izb=iz1;
					double xpb=xp1;
					double ypb=yp1;						
					double zpb=zp1;				

					while(nVertex<3)
					{
						
						if(zpa>=this.zMin&&zpb>=this.zMin)
						{					
							setHLigne(xsa,ysa,iza,xsb,ysb,izb);									
						}
						else
							if(zpa<zMin&&zpb>=zMin)
							{
								double dx,dy,dz;
								dx=xpb-xpa;
								dy=ypb-ypa;
								dz=zpb-zpa;
								double idz=1.0/dz;
								double tX=(zMin-zpa)*dx*idz+xpa;
								double tY=(zMin-zpa)*dy*idz+ypa;
														
								clipzmin1xs=(tX*this.screenZoomXFocus*this.iZMin+this.renderPixelWidthDiv2);	//Calcul x1 clipzmin sur l'ecran
								clipzmin1ys=(tY*this.screenZoomYFocus*this.iZMin+this.renderPixelHeightDiv2);	//Calcul y1 clipzmin sur l'ecran	
								setHLigne(clipzmin1xs,clipzmin1ys,this.iZMin,xsb,ysb,izb);
								clipZMin=1;
							}
							else
								if(zpb<zMin&&zpa>=zMin)
								{
									double dx,dy,dz;
									dx=xpa-xpb;
									dy=ypa-ypb;
									dz=zpa-zpb;
									double idz=1.0/dz;
									double tX=(zMin-zpb)*dx*idz+xpb;
									double tY=(zMin-zpb)*dy*idz+ypb;
									clipzmin2xs=(tX*this.screenZoomXFocus*this.iZMin+this.renderPixelWidthDiv2);	//Calcul x2 clipzmin sur l'ecran
									clipzmin2ys=(tY*this.screenZoomYFocus*this.iZMin+this.renderPixelHeightDiv2);	//Calcul y2 clipzmin sur l'ecran	
									setHLigne(xsa,ysa,iza,clipzmin2xs,clipzmin2ys,this.iZMin);						
									clipZMin=1;
								}	
								
						p1=p2;
						xsa=xsb;
						ysa=ysb;
						iza=izb;
						xpa=xpb;
						ypa=ypb;												
						zpa=zpb;
						if(nVertex==0)
						{
							p2=po.p2;
							xsb=xs2;
							ysb=ys2;
							izb=iz2;
							xpb=xp2;
							ypb=yp2;														
							zpb=zp2;
							
						}
						else
						{
							p2=po.p0;
							xsb=xs0;
							ysb=ys0;
							izb=iz0;
							xpb=xp0;
							ypb=yp0;														
							zpb=zp0;
						}
						nVertex++;
					}
	
					if(clipZMin!=0)
						setHLigne(clipzmin2xs,clipzmin2ys,this.iZMin,clipzmin1xs,clipzmin1ys,this.iZMin);
					else 
						continue;
						
				}

				if(hLinesBufferMax<=hLinesBufferMin)
					continue;

				compiledFace.lastRenderOffset=-1;
				compiledFace.firstRenderOffset=-1;
				compiledFace.computeScreenSpace();
				compiledFace.zMin=zPMin;
				compiledFace.zMax=zPMax;

				if(pasteHLine(compiledFace)!=0)
					result=1;
					
					
				/*	
				
				if(result==1 && dPlan>0)
					Log.log("dPlan="+dPlan);
				
				*/
				if((this.zBufferMode & ZB_ALPHA) !=0 && compiledFace.firstRenderOffset!=-1)
				{
					
					this.drawer.setMesh3D(po.object);
					if((po.object.renderMode&DzzD.RM_LIGHT)!=0)
						this.prepareMesh3DLocalLight3DBuffer(po.object.getScene3D(),po.object);					
					this.drawer.drawFace3D(this.compiledMeshes[po.object.id],this.compiledMeshes[po.object.id].compiledFaces[po.id],true);
					
				}

	}
	return result;
}

	private final void setHLigne(double p1xd,double p1yd,double p1izd,double p2xd,double p2yd,double p2izd)
	{
		double p21yd;
		double coeffAX;
		double coeffBX;
		double coeffAZ;
		double coeffBZ;	
		
		boolean down=true;
		if(p1yd>p2yd)
		{
			down=false;
			double tp1xd=p1xd;
			double tp1yd=p1yd;
			double tp1izd=p1izd;
			
			p1xd=p2xd;
			p1yd=p2yd;
			p1izd=p2izd;
			p2xd=tp1xd;
			p2yd=tp1yd;
			p2izd=tp1izd;
		}
	
		if(p1yd>this.maxYValue)
			return;
		if(p2yd<this.minYValue)
			return;
		if(p2yd-p1yd==0.0)
			return;
	
		p21yd=1.0/(p2yd-p1yd);
		coeffAX=(p2xd-p1xd)*p21yd;
		coeffBX=p1xd-coeffAX*p1yd;
		coeffAZ=(p2izd-p1izd)*p21yd;
		coeffBZ=p1izd-coeffAZ*p1yd;
	
		//Clip en Y
		if(p1yd<this.minYValue)
		{
			p1xd=coeffAX*this.minYValue+coeffBX;
			p1izd=coeffAZ*this.minYValue+coeffBZ;
			p1yd=this.minYValue;
		}
						
		if(p2yd>this.maxYValue)
		{
			p2xd=coeffAX*this.maxYValue+coeffBX;
			p2izd=coeffAZ*this.maxYValue+coeffBZ;
			p2yd=this.maxYValue;	
		}

		int p1y=(int)p1yd;
		int p2y=(int)p2yd;
		if(p1y==p2y) return;

		if(p2y>hLinesBufferMax) hLinesBufferMax=p2y;	//Ajuste hLinesBufferMax	
		if(p1y<hLinesBufferMin) hLinesBufferMin=p1y;	//Ajuste hLinesBufferMin

		
		if(down) //Set min values if going down (on screen)
		{
			
			this.vLinesBufferMin[p1y]=p2y;
			this.vLinesBufferMinX[p1y]=p1xd;
			this.vLinesBufferMinIncX[p1y]=coeffAX;
			this.vLinesBufferMinIz[p1y]=p1izd;
			this.vLinesBufferMinIncIz[p1y]=coeffAZ;

		}
		else //Set max value if going up (on screen)
		{

			this.vLinesBufferMax[p1y]=p2y;
			this.vLinesBufferMaxX[p1y]=p1xd;
			this.vLinesBufferMaxIncX[p1y]=coeffAX;
			this.vLinesBufferMaxIz[p1y]=p1izd;
			this.vLinesBufferMaxIncIz[p1y]=coeffAZ;
		}
			
	}	
	
	private double getFaceIzAt(int meshId,int faceId,double x,double y)
	{
		if(meshId==-1)
			return this.iZMax;
			
		if(meshId==-2)
			return this.iZTest;				
			
		double xs=x-renderPixelWidthDiv2;
		double ys=y-renderPixelHeightDiv2;
		xs*=this.iZoomX;
		ys*=this.iZoomY;
	
		
		CompiledFace cFace=this.compiledMeshes[meshId].compiledFaces[faceId];
		double iza=cFace.iZA;
		double izb=cFace.iZB;
		double izc=cFace.iZC;
		double iz=xs*iza+ys*izb+izc;
		return iz;
	}
	
	private double getZmax(int meshId,int faceId)
	{
		if(meshId==-1)
			return 1.0/this.iZMax;
			
		if(meshId==-2)
			return 1.0/this.iZTest;				
			
		CompiledFace cFace=this.compiledMeshes[meshId].compiledFaces[faceId];
		return cFace.zMax;
	}
	
	private double getZmin(int meshId,int faceId)
	{
		if(meshId==-1)
			return 1.0/this.iZMin;
			
		if(meshId==-2)
			return 1.0/this.iZTest;				
			
		CompiledFace cFace=this.compiledMeshes[meshId].compiledFaces[faceId];
		return cFace.zMin;
	}	
	
	private double getFaceIzX(int meshId,int faceId,int meshId2,int faceId2,double y)
	{	
		double ys=y-renderPixelHeightDiv2;
		ys*=this.iZoomY;
			
		double iza=0;
		double izb=0;
		double izc=this.iZTest;
		if(meshId!=-2)
		{	
			CompiledFace cFace=this.compiledMeshes[meshId].compiledFaces[faceId];
			iza=cFace.iZA;
			izb=cFace.iZB;
			izc=cFace.iZC;	
		}
		double b1=izb*ys+izc;
		
		
		double iza2=0;
		double izb2=0;
		double izc2=this.iZMax;
		if(meshId2!=-1)
		{
			CompiledFace cFace2=this.compiledMeshes[meshId2].compiledFaces[faceId2];
			iza2=cFace2.iZA;
			izb2=cFace2.iZB;
			izc2=cFace2.iZC;		
		}
		double b2=izb2*ys+izc2;

		double x=(b2-b1)/(iza-iza2);
		
		return x/this.iZoomX+renderPixelWidthDiv2;
	}

	private final int pasteHLine(CompiledFace compiledFace)
	{
		int result=0;
		
		int mId=-2;					
		int fId=-2;
		double zMin=getZmin(mId,fId);
		double zMax=getZmax(mId,fId);
			  
		if(compiledFace!=null)
		{
			mId=cMesh3D.id;					
			fId=compiledFace.face.id;
			zMin=compiledFace.zMin;
			zMax=compiledFace.zMax;
		}

	
		double x1=0;	//Set x1 to line starting x offset
		double x2=0;	//Set x2 to line ending x offset
		int nSeg;		
		int sSeg;		
		int dSeg=0;
		int ndSeg=0;         
		double xa;		//Starting x offset for current line as double
		double xb;		//Ending x offset for current line as double
		int xai;		//Starting x offset for current line as int
		int xbi;		//Ending x offset for current line as int
        
        int ofsY=this.hLinesBufferMin*this.renderPixelWidth;
        
		int endMinVLineY=this.vLinesBufferMin[this.hLinesBufferMin];
		double pXMin0=this.vLinesBufferMinX[this.hLinesBufferMin];
		double pXMinIncX=this.vLinesBufferMinIncX[this.hLinesBufferMin];
		double pXMinIz0=this.vLinesBufferMinIz[this.hLinesBufferMin];
		double pXMinIncIz=this.vLinesBufferMinIncIz[this.hLinesBufferMin];
        
		int endMaxVLineY=this.vLinesBufferMax[this.hLinesBufferMin];
		double pXMax0=this.vLinesBufferMaxX[this.hLinesBufferMin];
		double pXMaxIncX=this.vLinesBufferMaxIncX[this.hLinesBufferMin];
		double pXMaxIz0=this.vLinesBufferMaxIz[this.hLinesBufferMin];
		double pXMaxIncIz=this.vLinesBufferMaxIncIz[this.hLinesBufferMin];
		
        double pXMin=pXMin0;
        double pXMax=pXMax0;
		double pXMinIz=pXMinIz0;
        double pXMaxIz=pXMaxIz0;        
  		
		//Loop on y range defined by hLinesBufferMin and hLinesBufferMax
        int dyMin=0;
        int dyMax=0;
        
        nextLine: 
		for(int y=this.hLinesBufferMin;y<this.hLinesBufferMax;y++,dyMin++,dyMax++)
		{
			if(y!=this.hLinesBufferMin)
			{
				ofsY+=this.renderPixelWidth;
								
				pXMin=pXMin0+pXMinIncX*dyMin;
				pXMax=pXMax0+pXMaxIncX*dyMax;
				pXMinIz=pXMinIz0+pXMinIncIz*dyMin;
				pXMaxIz=pXMaxIz0+pXMaxIncIz*dyMax;				
				
				if(y==endMinVLineY)
				{
					endMinVLineY=this.vLinesBufferMin[y];
					pXMin0=this.vLinesBufferMinX[y];
					pXMinIncX=this.vLinesBufferMinIncX[y];
					pXMinIz0=this.vLinesBufferMinIz[y];
					pXMinIncIz=this.vLinesBufferMinIncIz[y];
					dyMin=0;		
        			pXMin=pXMin0;
					pXMinIz=pXMinIz0;				
				}
				
				if(y==endMaxVLineY)
				{
					endMaxVLineY=this.vLinesBufferMax[y];
					pXMax0=this.vLinesBufferMaxX[y];
					pXMaxIncX=this.vLinesBufferMaxIncX[y];
					pXMaxIz0=this.vLinesBufferMaxIz[y];
					pXMaxIncIz=this.vLinesBufferMaxIncIz[y];		
					dyMax=0;
			        pXMax=pXMax0;
        			pXMaxIz=pXMaxIz0;        
					
				}				
				
			}

			xa=pXMin;
			xb=pXMax;
			
		
			/**
			 * Verify if current line fit into x clipping area and clip x range if needed
			 */
			 
			double ofsxa=0;
			double ofsxb=0;
			double xab=(xb-xa);
			 
			if(xa>=this.maxXValue)
			{
				if(pXMinIncX>=0) return 0;
				continue nextLine;
			}
			else
				if(xa<this.minXValue)
				{
					ofsxa=((this.minXValue-xa));
					xa=this.minXValue;
				}

			if(xb<=this.minXValue)
			{
				if(pXMaxIncX<=0) return 0;
				continue nextLine;
			}
			else		
				if(xb>this.maxXValue)
				{
					ofsxb=((xb-this.maxXValue));
					xb=this.maxXValue;
				}
				
			//TODO: produce some artefact, must be reviewed (point)
			
			xai=((int)(xa));
			xbi=((int)(xb));
			if(xbi<=xai) {continue nextLine;}
			

			/**
			 * Update current line iz min, iz max and inc iz
			 */
			 /*
			double piZXA;
			double piZXB;
			double piZMin;	
			double piZMax;	
			double piZInc;
			
			piZXA=pXMinIz;
			piZXB=pXMaxIz;
			
			piZInc=(piZXB-piZXA)/xab;
			
			piZXA+=piZInc*ofsxa;
			piZXB-=piZInc*ofsxb;
							
			if(piZInc>=0f)
			{
				piZMin=piZXA;
				piZMax=piZXB;
			}	
			else
			{
				piZMin=piZXB;
				piZMax=piZXA;
			}	*/
			
			
			
			int segC=0;					//0 - if no segment has been started, 1 - if new segment has been started

			/**
			 * Look for first segment
			 */
			int fSeg=0;
			int fSegEnd=this.zBuffer[ofsY]&0xFFFF;
			while(xai>=fSegEnd )
			{
				fSeg=fSegEnd;
				fSegEnd=this.zBuffer[ofsY+fSegEnd]&0xFFFF;
			}
			sSeg=fSeg;
			nextSeg:
			do
			{
				nSeg=sSeg;

				if(xbi<=nSeg || nSeg>=this.renderPixelWidth)
					break;	
			
				/** 
				 * Read next segment position
				 */				
				int ofsSSeg=ofsY+nSeg;
				sSeg=zBuffer[ofsSSeg]&0xFFFF;

				/** 
				 * Read segment informations
				 */	
				int SegMId=this.zBufferO[ofsSSeg];
				int SegFId=this.zBufferP[ofsSSeg];
				double zMinS=getZmin(SegMId,SegFId);
				double zMaxS=getZmax(SegMId,SegFId);

				
				/*					
				if(piZIncS>=0f)
				{
					piZMinS=piZX1S;
					piZMaxS=piZX2S;
				}
				else
				{
					piZMinS=piZX2S;
					piZMaxS=piZX1S;
				}
				*/

				//TODO: whole segments gesture must be improved still some artefact
				boolean devant=(zMax<=zMinS);
				boolean derriere=(zMin>zMaxS);
										
				/**
				 * If no new segment started than we look if we need to start one
				 */	
				if(segC==0)
				{
					
					if(derriere)
					{						
						if(xbi<=sSeg)
							continue nextLine;
						else
							continue nextSeg;
						
					}

					
					double maxEndX=Math.min(sSeg,xb);
					double minStartX=Math.max(nSeg,xa);
					
					{
						if(devant || this.getFaceIzAt(mId,fId,minStartX+0.5,y)>=this.getFaceIzAt(SegMId,SegFId,minStartX+0.5,y))
						{
							x1=minStartX;
							segC=1;	
							
						}
						else
						{
							if(this.getFaceIzAt(mId,fId,maxEndX,y)>this.getFaceIzAt(SegMId,SegFId,maxEndX,y))
							{
								double dxt=getFaceIzX(mId,fId,SegMId,SegFId,y);
								if(dxt>=minStartX && dxt<maxEndX)
								{
									x1=dxt;
									segC=1;	
								}
							}
							
						}
												
					}
					/**
					 * If not new segment started continue	
					 */
					if(segC==0)
						continue nextSeg;
					else
						if((this.zBufferMode & this.ZB_TEST)!=0)
							return 1;
												
					dSeg=((int)x1);
					ndSeg=nSeg;
					
														
				}
								
				double maxEndX=Math.min(sSeg,xb);
				double minStartX=Math.max(nSeg,x1);
				
				if(devant)
				{
					if(xbi<=sSeg)
					{
						x2=xb;
						segC=0;	
					}
					else
						continue nextSeg;
	
				}
				else					
				if(derriere || this.getFaceIzAt(mId,fId,minStartX+0.5,y)<this.getFaceIzAt(SegMId,SegFId,minStartX+0.5,y))
				{
					
					x2=minStartX;
					segC=0;	
					
				}
				else
				{
					if(this.getFaceIzAt(mId,fId,maxEndX,y)<this.getFaceIzAt(SegMId,SegFId,maxEndX,y))
					{
						double dxt=getFaceIzX(mId,fId,SegMId,SegFId,y);
						if(dxt>=minStartX)
						{
							if(dxt<=maxEndX)
							{
								x2=dxt;
								segC=0;	
							}
							else
							{
								x2=maxEndX;
								segC=0;																				
							}
						}
						else
						{
								x2=minStartX;
								segC=0;										
						}
					}
					
				}
				
				if(segC==1 && xbi<=sSeg)
				{
					x2=xb;
					segC=0;	
					//drawer.debug[drawer.nbDebug++]=((int)x2)+(this.renderPixelHeight-1-y)*this.renderPixelWidth;
					
				}					
				
				
				
				if(segC==0 && x1==x2)
				{
					continue nextLine;
				}			
				
				//If new segment not closed continue
				if(segC!=0)	continue;	
			
				//If new segment closed, write it
				int px1=(int)(x1*65536f)>>8&0xFF;
				int px2=(int)(x2*65536f)>>8&0xFF;
				
				//int iX2=(int)(x2*65536f);

				int eSeg=(int)x2;

				if(eSeg==dSeg)
				{
					segC=0;
					continue;
				}

															
				if((this.zBufferMode & this.ZB_WRITE)!=0 || ((this.zBufferMode & ZB_ALPHA)!=0) )
				{
					result=1;
					//Update current segment new ending offset if needed
					if(dSeg!=ndSeg)
					{
						int pX1S=zBuffer[ofsY+ndSeg]>>16&0xFF;
						zBuffer[ofsY+ndSeg]=dSeg|(pX1S<<16);
					}

					int ofsSeg=ofsY+nSeg;
					int objId=zBufferO[ofsSeg];
					int faceId=zBufferP[ofsSeg];
					int ofsX1=ofsY+dSeg;
					zBufferO[ofsX1]=(short)cMesh3D.id;					
					zBufferP[ofsX1]=compiledFace.face.id;					
					zBuffer[ofsX1]=eSeg|(px1<<16);				
					
					//Update current segment new starting offset if needed				
					if(eSeg!=sSeg)
					{
						int ofsX2=ofsY+eSeg;
						this.zBuffer[ofsX2]=sSeg|(px2<<16); 
						this.zBufferO[ofsX2]=(short)objId;
						this.zBufferP[ofsX2]=faceId;						
					}
				}
				
				if(((this.zBufferMode & ZB_ALPHA)!=0))
				{
					result=1;
					if(eSeg!=dSeg)
					{
						if(compiledFace.lastRenderImage!=this.numImage)
						{
							compiledFace.lastRenderImage=this.numImage;
							compiledFace.firstRenderOffset=dSeg|(y<<16&0xFFFF0000);
							compiledFace.lastRenderOffset=compiledFace.firstRenderOffset;
						}
						else
						{
							int ofsX=compiledFace.lastRenderOffset;
							int ofsYF=(ofsX>>16)&0xFFFF;
							ofsX&=0xFFFF;
							int ofsXY=ofsX+ofsYF*this.renderPixelWidth;
							//this.zBuffer[ofsXY]=dSeg|(y<<16&0xFFFF0000);
							this.zBufferF[ofsXY]=dSeg|(y<<16&0xFFFF0000);
							
							compiledFace.lastRenderOffset=dSeg|(y<<16&0xFFFF0000);
						}		
					}

				}
				sSeg=eSeg;								
				
			}
			while(true);	
			if(segC!=0) Log.log("erreur");
		}
		return result;
	}

	private final void prepareFaceBuffer(IScene3D scene)
	{
		CompiledMesh3D compiledMesh=null;
		CompiledFace compiledFace=null;
		
		this.firstRenderBackgroundOffset=-1;
		this.firstMeshToRender=null;
		
		this.nbRenderedFace=0;		
		this.nbRenderedMesh=0;
				
		int lastMeshId=-1;
		int lastFaceId=-1;
		
		for(int y=0;y<this.renderPixelHeight;y++)
		{
			int startX=0;
			int startY=y*this.renderPixelWidth;
			
			while (startX!=this.renderPixelWidth)
			{
				int startXY=startY+startX;
				int endX=zBuffer[startXY]&0xFFFF;
				int meshId=zBufferO[startXY];
				
				if(meshId!=-1)	//Mesh 
				{
					
					if(meshId!=lastMeshId)
					{
						//objRef=(Mesh3D)scene.getMesh3DById(obj);
						compiledMesh=this.compiledMeshes[meshId];
						lastMeshId=meshId;
						lastFaceId=-1;
					}
					
					if(compiledMesh.lastRenderImage!=this.numImage)
					{
						compiledMesh.lastRenderImage=this.numImage;
						//compiledMesh.lastVisibleImage=this.numImage;
						compiledMesh.nbRenderFace=0;				
						compiledMesh.firstFaceToRender=null;					
						compiledMesh.nextMeshToRender=this.firstMeshToRender;
						this.firstMeshToRender=compiledMesh;
						this.nbRenderedMesh++;
					}

					int faceId=zBufferP[startXY];
					
					if(faceId!=lastFaceId)
					{
						compiledFace=compiledMesh.compiledFaces[faceId];
						//face=mesh.faces3D[faceId];
						lastFaceId=faceId;
					}
					if(compiledFace.lastRenderImage!=this.numImage)
					{
						
						//Mesh3DOctree octree=compiledFace.face.objectOctree;
						/*
						while(octree!=null)// && octree.lastVisibleImage!=this.numImage)
						{
							//octree.lastVisibleImage=this.numImage;
							octree=octree.parent;							
						}
						*/
						
						compiledFace.lastRenderImage=this.numImage;
						compiledFace.nextFaceToRender=compiledMesh.firstFaceToRender;
						compiledMesh.firstFaceToRender=compiledFace;
						compiledMesh.nbRenderFace++;
						compiledFace.firstRenderOffset=startX|(y<<16&0xFFFF0000);
						compiledFace.lastRenderOffset=compiledFace.firstRenderOffset;
						this.nbRenderedFace++;
					}
					else
					{
						int ofsX=compiledFace.lastRenderOffset;
						int ofsY=(ofsX>>16)&0xFFFF;
						ofsX&=0xFFFF;
						int ofsXY=ofsX+ofsY*this.renderPixelWidth;
						this.zBufferF[ofsXY]=startX|(y<<16&0xFFFF0000);
						compiledFace.lastRenderOffset=startX|(y<<16&0xFFFF0000);
					}					
				}
				else //Background 
				{
					if(this.firstRenderBackgroundOffset==-1)
					{
						this.firstRenderBackgroundOffset=startX|(y<<16&0xFFFF0000);
						this.lastRenderBackgroundOffset=this.firstRenderBackgroundOffset;
					}
					else
					{
						int ofsX=this.lastRenderBackgroundOffset;
						int ofsY=(ofsX>>16)&0xFFFF;
						ofsX&=0xFFFF;
						int ofsXY=ofsX+ofsY*this.renderPixelWidth;
						this.zBufferF[ofsXY]=startX|(y<<16&0xFFFF0000);
						this.lastRenderBackgroundOffset=startX|(y<<16&0xFFFF0000);
					}
				}
				startX=endX;
			}
			
		}
		if(firstRenderBackgroundOffset!=-1)
		{
			int ofsX=this.lastRenderBackgroundOffset;
			int ofsY=(ofsX>>16)&0xFFFF;
			ofsX&=0xFFFF;
			int ofsXY=ofsX+ofsY*this.renderPixelWidth;
			this.zBufferF[ofsXY]=(this.renderPixelWidth-1)|((this.renderPixelHeight-1)<<16&0xFFFF0000);
			this.lastRenderBackgroundOffset=(this.renderPixelWidth-1)|((this.renderPixelHeight-1)<<16&0xFFFF0000);	
		}
	}
	
	private final CompiledMesh3D getFirstMeshToRender()
	{
		return this.firstMeshToRender;
	}
	
	private final CompiledMesh3D getNextMeshToRender(CompiledMesh3D mesh)
	{
		return mesh.nextMeshToRender;	
	}

	private final void drawMesh3D(IScene3D scene)
	{
		CompiledMesh3D compiledMesh=this.getFirstMeshToRender();
	
		this.zBufferMode=ZB_WRITE;
		while(compiledMesh!=null)
		{
			Mesh3D mesh=compiledMesh.mesh;
			if((mesh.renderMode&DzzD.RM_LIGHT)!=0)
				this.prepareMesh3DLocalLight3DBuffer(scene,mesh);
			
			this.drawer.setMesh3D(mesh);
			CompiledFace compiledFace=compiledMesh.firstFaceToRender;
			while(compiledFace!=null)
			{
				this.drawer.drawFace3D(compiledMesh,compiledFace,false);
				compiledFace=compiledFace.nextFaceToRender;
			}
			compiledMesh=this.getNextMeshToRender(compiledMesh);
		}

/*
		this.zBufferMode=ZB_ALPHA;//|ZB_WRITE;
		for(Face3D alphaFace=this.firstAlphaFace;alphaFace!=null;alphaFace=alphaFace.nextAlphaFace)
		{
			this.setCurrentMesh3D(alphaFace.object);
			this.setFaces3DToZBuffer(alphaFace.object.faces3D,alphaFace.id);
		}
		*/
	}	
	
	public final String getImplementationName()
	{
		return "SOFT";
	}
		
	/** Get object ID rendered at the specified location 
	 *  @param x 
	 *  @param y
	 *  @return object ID at x,y	 
 	 */			
	public final int getRenderedMesh3DIdAt(int x,int y)
	{
		if((this.antialias&2)!=0)	
			x*=2;
		if((this.antialias&4)!=0)				
			y*=2;	
		y=this.renderPixelHeight-y-1;
		int numMesh3D=-1;
		if(x<0) x=0;
		if(x>=this.renderPixelWidth)	
			x=this.renderPixelWidth-1;
		if(y<0) y=0;
		if(y>=this.renderPixelHeight)	
			y=this.renderPixelHeight-1;

		int startX=0;
		int startY=y*this.renderPixelWidth;
		int posXY=startY+x;
		while (startX!=renderPixelWidth)
		{
			int startXY=startY+startX;
			int endX=zBuffer[startXY]&=0xFFFF;
			int endXY=startY+endX;
			if(posXY>=startXY&&posXY<endXY)
			{
				numMesh3D=zBufferO[startXY];
				break;
			}
			startX=endX;		
		}
		return numMesh3D;
	}
	
	/** Get face ID rendered at the specified location 
	 *  @param x 
	 *  @param y
	 *  @return face ID at x,y	 
 	 */			
	public final int getRenderedFace3DIdAt(int x,int y)
	{
		if((this.antialias&2)!=0)	
			x*=2;
		if((this.antialias&4)!=0)				
			y*=2;	
		y=this.renderPixelHeight-y-1;
		int numFace3D=-1;
		if(x<0) x=0;
		if(x>=this.renderPixelWidth)	
			x=this.renderPixelWidth-1;
		if(y<0) y=0;
		if(y>=this.renderPixelHeight)	
			y=this.renderPixelHeight-1;

		int startX=0;
		int startY=y*this.renderPixelWidth;
		int posXY=startY+x;
		while (startX!=renderPixelWidth)
		{
			int startXY=startY+startX;
			int endX=zBuffer[startXY]&=0xFFFF;
			int endXY=startY+endX;
			if(posXY>=startXY&&posXY<endXY)
			{
				numFace3D=zBufferP[startXY];
				break;
			}
			startX=endX;		
		}
		return numFace3D;
	}	

	/** Get z value at the specified location 
	 *  @param x 
	 *  @param y
	 *  @return z	 
 	 */				
	public final double getZAt(int x,int y)
	{
		int mid=this.getRenderedMesh3DIdAt(x,y);
		
		if(mid==-1)
			return this.zMax;
			
		int fid=this.getRenderedFace3DIdAt(x,y);	
		
		CompiledFace cf=compiledMeshes[mid].compiledFaces[fid];
		
		double piZ=x*cf.iZA+y*cf.iZB+cf.iZC;
		return 1.0/piZ;

	}	

}