// Copyright (C) 2002-2009 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "IrrCompileConfig.h"
#include "CSoftwareDriver.h"

#ifdef _IRR_COMPILE_WITH_SOFTWARE_

#include "CSoftwareTexture.h"
#include "os.h"
#include "S3DVertex.h"

namespace irr
{
namespace video
{


//! constructor
CSoftwareDriver::CSoftwareDriver(const core::dimension2d<u32>& windowSize, bool fullscreen, io::IFileSystem* io, video::IImagePresenter* presenter)
: CNullDriver(io, windowSize), BackBuffer(0), Presenter(presenter), WindowId(0),
	SceneSourceRect(0), RenderTargetTexture(0), RenderTargetSurface(0),
	CurrentTriangleRenderer(0), ZBuffer(0), Texture(0)
{
	#ifdef _DEBUG
	setDebugName("CSoftwareDriver");
	#endif

	// create backbuffer

	BackBuffer = new CImage(ECF_A1R5G5B5, windowSize);
	if (BackBuffer)
	{
		BackBuffer->fill(SColor(0));

		// create z buffer
		ZBuffer = video::createZBuffer(BackBuffer->getDimension());
	}

	DriverAttributes->setAttribute("MaxTextures", 1);
	DriverAttributes->setAttribute("MaxIndices", 1<<16);
	DriverAttributes->setAttribute("MaxTextureSize", 1024);
	DriverAttributes->setAttribute("Version", 1);

	// create triangle renderers

	TriangleRenderers[ETR_FLAT] = createTriangleRendererFlat(ZBuffer);
	TriangleRenderers[ETR_FLAT_WIRE] = createTriangleRendererFlatWire(ZBuffer);
	TriangleRenderers[ETR_GOURAUD] = createTriangleRendererGouraud(ZBuffer);
	TriangleRenderers[ETR_GOURAUD_WIRE] = createTriangleRendererGouraudWire(ZBuffer);
	TriangleRenderers[ETR_TEXTURE_FLAT] = createTriangleRendererTextureFlat(ZBuffer);
	TriangleRenderers[ETR_TEXTURE_FLAT_WIRE] = createTriangleRendererTextureFlatWire(ZBuffer);
	TriangleRenderers[ETR_TEXTURE_GOURAUD] = createTriangleRendererTextureGouraud(ZBuffer);
	TriangleRenderers[ETR_TEXTURE_GOURAUD_WIRE] = createTriangleRendererTextureGouraudWire(ZBuffer);
	TriangleRenderers[ETR_TEXTURE_GOURAUD_NOZ] = createTriangleRendererTextureGouraudNoZ();
	TriangleRenderers[ETR_TEXTURE_GOURAUD_ADD] = createTriangleRendererTextureGouraudAdd(ZBuffer);

	// select render target

	setRenderTarget(BackBuffer);

	// select the right renderer

	selectRightTriangleRenderer();
}



//! destructor
CSoftwareDriver::~CSoftwareDriver()
{
	// delete Backbuffer
	if (BackBuffer)
		BackBuffer->drop();

	// delete triangle renderers

	for (s32 i=0; i<ETR_COUNT; ++i)
		if (TriangleRenderers[i])
			TriangleRenderers[i]->drop();

	// delete zbuffer

	if (ZBuffer)
		ZBuffer->drop();

	// delete current texture

	if (Texture)
		Texture->drop();

	if (RenderTargetTexture)
		RenderTargetTexture->drop();

	if (RenderTargetSurface)
		RenderTargetSurface->drop();
}



//! switches to a triangle renderer
void CSoftwareDriver::switchToTriangleRenderer(ETriangleRenderer renderer)
{
	video::IImage* s = 0;
	if (Texture)
		s = ((CSoftwareTexture*)Texture)->getTexture();

	CurrentTriangleRenderer = TriangleRenderers[renderer];
	CurrentTriangleRenderer->setBackfaceCulling(Material.BackfaceCulling == true);
	CurrentTriangleRenderer->setTexture(s);
	CurrentTriangleRenderer->setRenderTarget(RenderTargetSurface, ViewPort);
}


//! void selects the right triangle renderer based on the render states.
void CSoftwareDriver::selectRightTriangleRenderer()
{

	ETriangleRenderer renderer = ETR_FLAT;

	if (Texture)
	{
		if (!Material.GouraudShading)
			renderer = (!Material.Wireframe) ? ETR_TEXTURE_FLAT : ETR_TEXTURE_FLAT_WIRE;
		else
		{
			if (Material.Wireframe)
				renderer = ETR_TEXTURE_GOURAUD_WIRE;
			else
			{
				if (Material.MaterialType == EMT_TRANSPARENT_ADD_COLOR ||
						Material.MaterialType == EMT_TRANSPARENT_ALPHA_CHANNEL ||
						Material.MaterialType == EMT_TRANSPARENT_VERTEX_ALPHA)
				{
					// simply draw all transparent stuff with the same renderer. at
					// least it is transparent then.
					renderer = ETR_TEXTURE_GOURAUD_ADD;
				}
				else
				if ((Material.ZBuffer==ECFN_NEVER) && !Material.ZWriteEnable)
					renderer = ETR_TEXTURE_GOURAUD_NOZ;
				else
				{
					renderer = ETR_TEXTURE_GOURAUD;
				}
			}
		}
	}
	else
	{
		if (!Material.GouraudShading)
			renderer = (!Material.Wireframe) ? ETR_FLAT : ETR_FLAT_WIRE;
		else
			renderer = (!Material.Wireframe) ? ETR_GOURAUD : ETR_GOURAUD_WIRE;
	}

	switchToTriangleRenderer(renderer);
}


//! queries the features of the driver, returns true if feature is available
bool CSoftwareDriver::queryFeature(E_VIDEO_DRIVER_FEATURE feature) const
{
	switch (feature)
	{
	case EVDF_RENDER_TO_TARGET:
	case EVDF_TEXTURE_NSQUARE:
		return FeatureEnabled[feature];
	default:
		return false;
	};
}


//! sets transformation
void CSoftwareDriver::setTransform(E_TRANSFORMATION_STATE state, const core::matrix4& mat)
{
	TransformationMatrix[state] = mat;
}


//! sets the current Texture
bool CSoftwareDriver::setActiveTexture(u32 stage, video::ITexture* texture)
{
	if (texture && texture->getDriverType() != EDT_SOFTWARE)
	{
		os::Printer::log("Fatal Error: Tried to set a texture not owned by this driver.", ELL_ERROR);
		return false;
	}

	if (Texture)
		Texture->drop();

	Texture = texture;

	if (Texture)
		Texture->grab();

	selectRightTriangleRenderer();
	return true;
}


//! sets a material
void CSoftwareDriver::setMaterial(const SMaterial& material)
{
	Material = material;
	OverrideMaterial.apply(Material);

	for (u32 i = 0; i < 1; ++i)
	{
		setActiveTexture(i, Material.getTexture(i));
		setTransform ((E_TRANSFORMATION_STATE) ( ETS_TEXTURE_0 + i ),
				material.getTextureMatrix(i));
	}
}


//! clears the zbuffer
bool CSoftwareDriver::beginScene(bool backBuffer, bool zBuffer, SColor color,
		const SExposedVideoData& videoData, core::rect<s32>* sourceRect)
{
	CNullDriver::beginScene(backBuffer, zBuffer, color, videoData, sourceRect);
	WindowId=videoData.D3D9.HWnd;
	SceneSourceRect = sourceRect;

	if (backBuffer && BackBuffer)
		BackBuffer->fill(color);

	if (ZBuffer && zBuffer)
		ZBuffer->clear();

	return true;
}


//! presents the rendered scene on the screen, returns false if failed
bool CSoftwareDriver::endScene()
{
	CNullDriver::endScene();

	return Presenter->present(BackBuffer, WindowId, SceneSourceRect);
}


//! returns a device dependent texture from a software surface (IImage)
//! THIS METHOD HAS TO BE OVERRIDDEN BY DERIVED DRIVERS WITH OWN TEXTURES
ITexture* CSoftwareDriver::createDeviceDependentTexture(IImage* surface, const io::path& name, void* mipmapData)
{
	return new CSoftwareTexture(surface, name, false, mipmapData);
}


//! sets a render target
bool CSoftwareDriver::setRenderTarget(video::ITexture* texture, bool clearBackBuffer,
								bool clearZBuffer, SColor color)
{
	if (texture && texture->getDriverType() != EDT_SOFTWARE)
	{
		os::Printer::log("Fatal Error: Tried to set a texture not owned by this driver.", ELL_ERROR);
		return false;
	}

	if (RenderTargetTexture)
		RenderTargetTexture->drop();

	RenderTargetTexture = texture;

	if (RenderTargetTexture)
	{
		RenderTargetTexture->grab();
		setRenderTarget(((CSoftwareTexture*)RenderTargetTexture)->getTexture());
	}
	else
	{
		setRenderTarget(BackBuffer);
	}

	if (RenderTargetSurface && (clearBackBuffer || clearZBuffer))
	{
		if (clearZBuffer)
			ZBuffer->clear();

		if (clearBackBuffer)
			((video::CImage*)RenderTargetSurface)->fill( color );
	}

	return true;
}


//! sets a render target
void CSoftwareDriver::setRenderTarget(video::CImage* image)
{
	if (RenderTargetSurface)
		RenderTargetSurface->drop();

	RenderTargetSurface = image;
	RenderTargetSize.Width = 0;
	RenderTargetSize.Height = 0;
	Render2DTranslation.X = 0;
	Render2DTranslation.Y = 0;

	if (RenderTargetSurface)
	{
		RenderTargetSurface->grab();
		RenderTargetSize = RenderTargetSurface->getDimension();
	}

	setViewPort(core::rect<s32>(0,0,RenderTargetSize.Width,RenderTargetSize.Height));

	if (ZBuffer)
		ZBuffer->setSize(RenderTargetSize);
}


//! sets a viewport
void CSoftwareDriver::setViewPort(const core::rect<s32>& area)
{
	ViewPort = area;

	//TODO: the clipping is not correct, because the projection is affected.
	// to correct this, ViewPortSize and Render2DTranslation will have to be corrected.
	core::rect<s32> rendert(0,0,RenderTargetSize.Width,RenderTargetSize.Height);
	ViewPort.clipAgainst(rendert);

	ViewPortSize = core::dimension2du(ViewPort.getSize());
	Render2DTranslation.X = (ViewPortSize.Width / 2) + ViewPort.UpperLeftCorner.X;
	Render2DTranslation.Y = ViewPort.UpperLeftCorner.Y + ViewPortSize.Height - (ViewPortSize.Height / 2);// + ViewPort.UpperLeftCorner.Y;

	if (CurrentTriangleRenderer)
		CurrentTriangleRenderer->setRenderTarget(RenderTargetSurface, ViewPort);
}


void CSoftwareDriver::drawVertexPrimitiveList(const void* vertices, u32 vertexCount,
				const void* indexList, u32 primitiveCount,
				E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType)

{
	switch (iType)
	{
		case (EIT_16BIT):
		{
			drawVertexPrimitiveList16(vertices, vertexCount, (const u16*)indexList, primitiveCount, vType, pType);
			break;
		}
		case (EIT_32BIT):
		{
			os::Printer::log("Software driver can not render 32bit buffers", ELL_ERROR);
			break;
		}
	}
}


//! draws a vertex primitive list
void CSoftwareDriver::drawVertexPrimitiveList16(const void* vertices, u32 vertexCount, const u16* indexList, u32 primitiveCount, E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType)
{
	const u16* indexPointer=0;
	core::array<u16> newBuffer;
	switch (pType)
	{
		case scene::EPT_LINE_STRIP:
			{
				switch (vType)
				{
					case EVT_STANDARD:
						{
							for (u32 i=0; i < primitiveCount-1; ++i)
								draw3DLine(((S3DVertex*)vertices)[indexList[i]].Pos,
									((S3DVertex*)vertices)[indexList[i+1]].Pos,
									((S3DVertex*)vertices)[indexList[i]].Color);
						}
						break;
					case EVT_2TCOORDS:
						{
							for (u32 i=0; i < primitiveCount-1; ++i)
								draw3DLine(((S3DVertex2TCoords*)vertices)[indexList[i]].Pos,
									((S3DVertex2TCoords*)vertices)[indexList[i+1]].Pos,
									((S3DVertex2TCoords*)vertices)[indexList[i]].Color);
						}
						break;
					case EVT_TANGENTS:
						{
							for (u32 i=0; i < primitiveCount-1; ++i)
								draw3DLine(((S3DVertexTangents*)vertices)[indexList[i]].Pos,
									((S3DVertexTangents*)vertices)[indexList[i+1]].Pos,
									((S3DVertexTangents*)vertices)[indexList[i]].Color);
						}
						break;
				}
			}
			return;
		case scene::EPT_LINE_LOOP:
			drawVertexPrimitiveList16(vertices, vertexCount, indexList, primitiveCount-1, vType, scene::EPT_LINE_STRIP);
			switch (vType)
			{
				case EVT_STANDARD:
					draw3DLine(((S3DVertex*)vertices)[indexList[primitiveCount-1]].Pos,
						((S3DVertex*)vertices)[indexList[0]].Pos,
						((S3DVertex*)vertices)[indexList[primitiveCount-1]].Color);
					break;
				case EVT_2TCOORDS:
					draw3DLine(((S3DVertex2TCoords*)vertices)[indexList[primitiveCount-1]].Pos,
						((S3DVertex2TCoords*)vertices)[indexList[0]].Pos,
						((S3DVertex2TCoords*)vertices)[indexList[primitiveCount-1]].Color);
					break;
				case EVT_TANGENTS:
					draw3DLine(((S3DVertexTangents*)vertices)[indexList[primitiveCount-1]].Pos,
						((S3DVertexTangents*)vertices)[indexList[0]].Pos,
						((S3DVertexTangents*)vertices)[indexList[primitiveCount-1]].Color);
					break;
			}
			return;
		case scene::EPT_LINES:
			{
				switch (vType)
				{
					case EVT_STANDARD:
						{
							for (u32 i=0; i < 2*primitiveCount; i+=2)
								draw3DLine(((S3DVertex*)vertices)[indexList[i]].Pos,
									((S3DVertex*)vertices)[indexList[i+1]].Pos,
									((S3DVertex*)vertices)[indexList[i]].Color);
						}
						break;
					case EVT_2TCOORDS:
						{
							for (u32 i=0; i < 2*primitiveCount; i+=2)
								draw3DLine(((S3DVertex2TCoords*)vertices)[indexList[i]].Pos,
									((S3DVertex2TCoords*)vertices)[indexList[i+1]].Pos,
									((S3DVertex2TCoords*)vertices)[indexList[i]].Color);
						}
						break;
					case EVT_TANGENTS:
						{
							for (u32 i=0; i < 2*primitiveCount; i+=2)
								draw3DLine(((S3DVertexTangents*)vertices)[indexList[i]].Pos,
									((S3DVertexTangents*)vertices)[indexList[i+1]].Pos,
									((S3DVertexTangents*)vertices)[indexList[i]].Color);
						}
						break;
				}
			}
			return;
		case scene::EPT_TRIANGLE_FAN:
			{
				// TODO: don't convert fan to list
				newBuffer.reallocate(primitiveCount*3);
				for( u32 t=0; t<primitiveCount; ++t )
				{
					newBuffer.push_back(indexList[0]);
					newBuffer.push_back(indexList[t+1]);
					newBuffer.push_back(indexList[t+2]);
				}

				indexPointer = newBuffer.pointer();
			}
			break;
		case scene::EPT_TRIANGLES:
			indexPointer=indexList;
			break;
		default:
			return;
	}
	switch (vType)
	{
		case EVT_STANDARD:
			drawClippedIndexedTriangleListT((S3DVertex*)vertices, vertexCount, indexPointer, primitiveCount);
			break;
		case EVT_2TCOORDS:
			drawClippedIndexedTriangleListT((S3DVertex2TCoords*)vertices, vertexCount, indexPointer, primitiveCount);
			break;
		case EVT_TANGENTS:
			drawClippedIndexedTriangleListT((S3DVertexTangents*)vertices, vertexCount, indexPointer, primitiveCount);
			break;
	}
}


template<class VERTEXTYPE>
void CSoftwareDriver::drawClippedIndexedTriangleListT(const VERTEXTYPE* vertices,
	s32 vertexCount, const u16* indexList, s32 triangleCount)
{
	if (!RenderTargetSurface || !ZBuffer || !triangleCount)
		return;

	if (!checkPrimitiveCount(triangleCount))
		return;

	// arrays for storing clipped vertices
	core::array<VERTEXTYPE> clippedVertices;
	core::array<u16> clippedIndices;

	// calculate inverse world transformation
	core::matrix4 worldinv(TransformationMatrix[ETS_WORLD]);
	worldinv.makeInverse();

	// calculate view frustum planes
	scene::SViewFrustum frustum(TransformationMatrix[ETS_PROJECTION] * TransformationMatrix[ETS_VIEW]);

	// copy and transform clipping planes ignoring far plane
	core::plane3df planes[5]; // ordered by near, left, right, bottom, top
	for (int p=0; p<5; ++p)
		worldinv.transformPlane(frustum.planes[p+1], planes[p]);

	core::EIntersectionRelation3D inout[3]; // is point in front or back of plane?

	// temporary buffer for vertices to be clipped by all planes
	core::array<VERTEXTYPE> tClpBuf;
	int t;

	int i;
	for (i=0; i<triangleCount; ++i) // for all input triangles
	{
		// add next triangle to tempClipBuffer
		for (t=0; t<3; ++t)
			tClpBuf.push_back(vertices[indexList[(i*3)+t]]);

		for (int p=0; p<5; ++p) // for all clip planes
		for (int v=0; v<(int)tClpBuf.size(); v+=3) // for all vertices in temp clip buffer
		{
			int inside = 0;
			int outside = 0;

			// test intersection relation of the current vertices
			for (t=0; t<3; ++t)
			{
				inout[t] = planes[p].classifyPointRelation(tClpBuf[v+t].Pos);
				if (inout[t] != core::ISREL3D_FRONT)
					++inside;
				else
				if (inout[t] == core::ISREL3D_FRONT)
					++outside;
			}

			if (!outside)
			{
				// add all vertices to new buffer, this triangle needs no clipping.
				// so simply don't change this part of the temporary triangle buffer
				continue;
			}

			if (!inside)
			{
				// all vertices are outside, don't add this triangle, so erase this
				// triangle from the tClpBuf
				tClpBuf.erase(v,3);
				v -= 3;
				continue;
			}

			// this vertex has to be clipped by this clipping plane.

			// The following lines represent my try to implement some real clipping.
			// There is a bug somewhere, and after some time I've given up.
			// So now it is commented out, resulting that triangles which would need clipping
			// are simply taken out (in the next two lines).
#ifndef __SOFTWARE_CLIPPING_PROBLEM__
			tClpBuf.erase(v,3);
			v -= 3;
#endif

			/*
			// my idea is the following:
			// current vertex to next vertex relation:
			// out - out : add nothing
			// out -  in : add middle point
			// in -  out : add first and middle point
			// in -   in : add both


			// now based on the number of intersections, create new vertices
			// into tClpBuf (at the front for not letting them be clipped again)

			int added = 0;
			int prev = v+2;
			for (int index=v; index<v+3; ++index)
			{
				if (inout[prev] == core::ISREL3D_BACK)
				{
					if (inout[index] != core::ISREL3D_BACK)
					{
						VERTEXTYPE& vt1 = tClpBuf[prev];
						VERTEXTYPE& vt2 = tClpBuf[index];

						f32 fact = planes[p].getKnownIntersectionWithLine(vt1.Pos, vt2.Pos);
						VERTEXTYPE nvt;
						nvt.Pos = vt1.Pos.getInterpolated(vt2.Pos, fact);
						nvt.Color = vt1.Color.getInterpolated(vt2.Color, fact);
						nvt.TCoords = vt1.TCoords.getInterpolated(vt2.TCoords, fact);

						tClpBuf.push_front(nvt); ++index; ++prev; ++v;
						++added;
					}
				}
				else
				{
					if (inout[index] != core::ISREL3D_BACK)
					{
						VERTEXTYPE vt1 = tClpBuf[index];
						VERTEXTYPE vt2 = tClpBuf[prev];
						tClpBuf.push_front(vt1); ++index; ++prev; ++v;
						tClpBuf.push_front(vt2); ++index; ++prev; ++v;
						added+= 2;
					}
					else
					{
						// same as above, but other way round.
						VERTEXTYPE vt1 = tClpBuf[index];
						VERTEXTYPE vt2 = tClpBuf[prev];

						f32 fact = planes[p].getKnownIntersectionWithLine(vt1.Pos, vt2.Pos);
						VERTEXTYPE nvt;
						nvt.Pos = vt1.Pos.getInterpolated(vt2.Pos, fact);
						nvt.Color = vt1.Color.getInterpolated(vt2.Color, fact);
						nvt.TCoords = vt1.TCoords.getInterpolated(vt2.TCoords, fact);

						tClpBuf.push_front(vt2); ++index; ++prev; ++v;
						tClpBuf.push_front(nvt); ++index; ++prev; ++v;
						added += 2;
					}
				}

				prev = index;
			}

			// erase original vertices
			tClpBuf.erase(v,3);
			v -= 3;
			*/


		} // end for all clip planes

		// now add all remaining triangles in tempClipBuffer to clippedIndices
		// and clippedVertices array.
		if (clippedIndices.size() + tClpBuf.size() < 65535)
		for (t=0; t<(int)tClpBuf.size(); ++t)
		{
			clippedIndices.push_back(clippedVertices.size());
			clippedVertices.push_back(tClpBuf[t]);
		}
		tClpBuf.clear();

	} // end for all input triangles


	// draw newly created triangles.

	// -----------------------------------------------------------
	// here all triangles are being drawn. I put this in a separate
	// method, but the visual studio 6 compiler has great problems
	// with templates and didn't accept two template methods in this
	// class.

	// draw triangles

	CNullDriver::drawVertexPrimitiveList(clippedVertices.pointer(), clippedVertices.size(),
		clippedIndices.pointer(), clippedIndices.size()/3, EVT_STANDARD, scene::EPT_TRIANGLES, EIT_16BIT);

	if (TransformedPoints.size() < clippedVertices.size())
		TransformedPoints.set_used(clippedVertices.size());

	if (TransformedPoints.empty())
		return;

	const VERTEXTYPE* currentVertex = clippedVertices.pointer();
	S2DVertex* tp = &TransformedPoints[0];

	core::dimension2d<u32> textureSize(0,0);
	f32 zDiv;

	if (Texture)
		textureSize = ((CSoftwareTexture*)Texture)->getTexture()->getDimension();

	f32 transformedPos[4]; // transform all points in the list

	core::matrix4 matrix(TransformationMatrix[ETS_PROJECTION]);
	matrix *= TransformationMatrix[ETS_VIEW];
	matrix *= TransformationMatrix[ETS_WORLD];

	s32 ViewTransformWidth = (ViewPortSize.Width>>1);
	s32 ViewTransformHeight = (ViewPortSize.Height>>1);

	for (i=0; i<(int)clippedVertices.size(); ++i)
	{
		transformedPos[0] = currentVertex->Pos.X;
		transformedPos[1] = currentVertex->Pos.Y;
		transformedPos[2] = currentVertex->Pos.Z;
		transformedPos[3] = 1.0f;

		matrix.multiplyWith1x4Matrix(transformedPos);
		zDiv = transformedPos[3] == 0.0f ? 1.0f : (1.0f / transformedPos[3]);

		tp->Pos.X = (s32)(ViewTransformWidth * (transformedPos[0] * zDiv) + (Render2DTranslation.X));
		tp->Pos.Y = (Render2DTranslation.Y - (s32)(ViewTransformHeight * (transformedPos[1] * zDiv)));
		tp->Color = currentVertex->Color.toA1R5G5B5();
		tp->ZValue = (TZBufferType)(32767.0f * zDiv);

		tp->TCoords.X = (s32)(currentVertex->TCoords.X * textureSize.Width);
		tp->TCoords.X <<= 8;
		tp->TCoords.Y = (s32)(currentVertex->TCoords.Y * textureSize.Height);
		tp->TCoords.Y <<= 8;

		++currentVertex;
		++tp;
	}

	// draw all transformed points from the index list
	CurrentTriangleRenderer->drawIndexedTriangleList(&TransformedPoints[0],
		clippedVertices.size(), clippedIndices.pointer(), clippedIndices.size()/3);
}


//! Draws a 3d line.
void CSoftwareDriver::draw3DLine(const core::vector3df& start,
				const core::vector3df& end, SColor color)
{
	core::vector3df vect = start.crossProduct(end);
	vect.normalize();
	vect *= Material.Thickness*0.3f;

	S3DVertex vtx[4];

	vtx[0].Color = color;
	vtx[1].Color = color;
	vtx[2].Color = color;
	vtx[3].Color = color;

	vtx[0].Pos = start;
	vtx[1].Pos = end;

	vtx[2].Pos = start + vect;
	vtx[3].Pos = end + vect;

	u16 idx[12] = {0,1,2, 0,2,1, 0,1,3, 0,3,1};

	drawIndexedTriangleList(vtx, 4, idx, 4);
}


//! clips a triangle against the viewing frustum
void CSoftwareDriver::clipTriangle(f32* transformedPos)
{
}


//! Only used by the internal engine. Used to notify the driver that
//! the window was resized.
void CSoftwareDriver::OnResize(const core::dimension2d<u32>& size)
{
	// make sure width and height are multiples of 2
	core::dimension2d<u32> realSize(size);

	if (realSize.Width % 2)
		realSize.Width += 1;

	if (realSize.Height % 2)
		realSize.Height += 1;

	if (ScreenSize != realSize)
	{
		if (ViewPort.getWidth() == (s32)ScreenSize.Width &&
			ViewPort.getHeight() == (s32)ScreenSize.Height)
		{
			ViewPort = core::rect<s32>(core::position2d<s32>(0,0),
										core::dimension2di(realSize));
		}

		ScreenSize = realSize;

		bool resetRT = (RenderTargetSurface == BackBuffer);

		if (BackBuffer)
			BackBuffer->drop();
		BackBuffer = new CImage(ECF_A1R5G5B5, realSize);

		if (resetRT)
			setRenderTarget(BackBuffer);
	}
}

//! returns the current render target size
const core::dimension2d<u32>& CSoftwareDriver::getCurrentRenderTargetSize() const
{
	return RenderTargetSize;
}


//! draws an 2d image, using a color (if color is other then Color(255,255,255,255)) and the alpha channel of the texture if wanted.
void CSoftwareDriver::draw2DImage(const video::ITexture* texture, const core::position2d<s32>& destPos,
					const core::rect<s32>& sourceRect,
					const core::rect<s32>* clipRect, SColor color,
					bool useAlphaChannelOfTexture)
{
	if (texture)
	{
		if (texture->getDriverType() != EDT_SOFTWARE)
		{
			os::Printer::log("Fatal Error: Tried to copy from a surface not owned by this driver.", ELL_ERROR);
			return;
		}

		if (useAlphaChannelOfTexture)
			((CSoftwareTexture*)texture)->getImage()->copyToWithAlpha(
				RenderTargetSurface, destPos, sourceRect, color, clipRect);
		else
			((CSoftwareTexture*)texture)->getImage()->copyTo(
				RenderTargetSurface, destPos, sourceRect, clipRect);
	}
}



//! Draws a 2d line.
void CSoftwareDriver::draw2DLine(const core::position2d<s32>& start,
				const core::position2d<s32>& end,
				SColor color)
{
	RenderTargetSurface->drawLine(start, end, color );
}


//! Draws a pixel
void CSoftwareDriver::drawPixel(u32 x, u32 y, const SColor & color)
{
	BackBuffer->setPixel(x, y, color, true);
}


//! draw a 2d rectangle
void CSoftwareDriver::draw2DRectangle(SColor color, const core::rect<s32>& pos,
					const core::rect<s32>* clip)
{
	if (clip)
	{
		core::rect<s32> p(pos);

		p.clipAgainst(*clip);

		if(!p.isValid())
			return;

		RenderTargetSurface->drawRectangle(p, color);
	}
	else
	{
		if(!pos.isValid())
			return;

		RenderTargetSurface->drawRectangle(pos, color);
	}
}


//!Draws an 2d rectangle with a gradient.
void CSoftwareDriver::draw2DRectangle(const core::rect<s32>& pos,
	SColor colorLeftUp, SColor colorRightUp, SColor colorLeftDown, SColor colorRightDown,
	const core::rect<s32>* clip)
{
	// TODO: implement
	draw2DRectangle(colorLeftUp, pos, clip);
}


//! \return Returns the name of the video driver. Example: In case of the Direct3D8
//! driver, it would return "Direct3D8.1".
const wchar_t* CSoftwareDriver::getName() const
{
	return L"Irrlicht Software Driver 1.0";
}


//! Returns type of video driver
E_DRIVER_TYPE CSoftwareDriver::getDriverType() const
{
	return EDT_SOFTWARE;
}


//! returns color format
ECOLOR_FORMAT CSoftwareDriver::getColorFormat() const
{
	if (BackBuffer)
		return BackBuffer->getColorFormat();
	else
		return CNullDriver::getColorFormat();
}


//! Returns the transformation set by setTransform
const core::matrix4& CSoftwareDriver::getTransform(E_TRANSFORMATION_STATE state) const
{
	return TransformationMatrix[state];
}


//! Creates a render target texture.
ITexture* CSoftwareDriver::addRenderTargetTexture(const core::dimension2d<u32>& size,
												  const io::path& name,
												  const ECOLOR_FORMAT format)
{
	CImage* img = new CImage(video::ECF_A1R5G5B5, size);
	ITexture* tex = new CSoftwareTexture(img, name, true);
	img->drop();
	addTexture(tex);
	tex->drop();
	return tex;
}


//! Clears the ZBuffer.
void CSoftwareDriver::clearZBuffer()
{
	if (ZBuffer)
		ZBuffer->clear();
}


//! Returns an image created from the last rendered frame.
IImage* CSoftwareDriver::createScreenShot()
{
	if (BackBuffer)
	{
		CImage* tmp = new CImage(BackBuffer->getColorFormat(), BackBuffer->getDimension());
		BackBuffer->copyTo(tmp);
		return tmp;
	}
	else
		return 0;
}


//! Returns the maximum amount of primitives (mostly vertices) which
//! the device is able to render with one drawIndexedTriangleList
//! call.
u32 CSoftwareDriver::getMaximalPrimitiveCount() const
{
	return 0x00800000;
}

} // end namespace video
} // end namespace irr

#endif // _IRR_COMPILE_WITH_SOFTWARE_

namespace irr
{
namespace video
{


//! creates a video driver
IVideoDriver* createSoftwareDriver(const core::dimension2d<u32>& windowSize, bool fullscreen, io::IFileSystem* io, video::IImagePresenter* presenter)
{
	#ifdef _IRR_COMPILE_WITH_SOFTWARE_
	return new CSoftwareDriver(windowSize, fullscreen, io, presenter);
	#else
	return 0;
	#endif
}


} // end namespace video
} // end namespace irr

