/////////////////////////////////////////////////////////////
// CINEMA SDK : SHADER																		 //
/////////////////////////////////////////////////////////////
// VERSION    : CINEMA 4D																	 //
/////////////////////////////////////////////////////////////
// (c) 1989-2002 MAXON Computer GmbH, all rights reserved	 //
/////////////////////////////////////////////////////////////

// volumetric shader example that accesses particles
// absolutely not optimized for speed

#include "c4d.h"
#include "c4d_symbols.h"

struct PVRender
{
	PVRender(void);
	~PVRender(void);

	Vector	*mp;
	LONG		count;
};

class ParticleVolume : public MaterialData
{
	private:
		PVRender *render;
	public:
		virtual	LONG GetRenderInfo		(PluginMaterial *mat);

		virtual	LONG InitRender				(PluginMaterial *mat, InitRenderStruct *irs);
		virtual	void FreeRender				(PluginMaterial *mat);

		virtual	void CalcSurface			(PluginMaterial *mat, VolumeData *vd);
		virtual	void CalcTransparency	(PluginMaterial *mat, VolumeData *vd);
		virtual	void CalcVolumetric		(PluginMaterial *mat, VolumeData *vd);

		static NodeData *Alloc(void) { return gNew ParticleVolume; }
};

LONG ParticleVolume::GetRenderInfo(PluginMaterial *mat)
{
	return SHADER_TRANSPARENCY|SHADER_VOLUMETRIC;
}

#define MAX_PARTICLES 10000

PVRender::PVRender(void)
{
	mp=NULL;
	count=0;
}

PVRender::~PVRender(void)
{
	GeFree(mp);
}

static Bool FillPV(PVRender *pv, BaseObject *op)
{
	while (op)
	{
		if (op->GetType()==Oparticle && !op->GetDown() && op->GetDeformMode()) // particle system without geometry
		{
			ParticleObject *po=(ParticleObject*)op;
			ParticleTag    *pt=(ParticleTag*)op->GetTag(Tparticle);
			if (pt)
			{
				Vector	 *mp  = NULL;
				Particle *pp  = NULL;
				LONG		 pcnt = po->GetParticleCount(),rcnt=0,i;

				for (i=0; i<pcnt; i++)
				{
					pp=po->GetParticle(pt,i);
					if (pp->bits&(PARTICLE_VISIBLE|PARTICLE_ALIVE)) rcnt++;
				}

				if (pv->count+rcnt>0)
				{
					mp = (Vector*)GeAlloc(sizeof(Vector)*(pv->count+rcnt));
					if (!mp) return FALSE;

					CopyMem(pv->mp,mp,sizeof(Vector)*pv->count);
					GeFree(pv->mp);
					pv->mp=mp;

					for (i=0; i<pcnt; i++)
					{
						pp=po->GetParticle(pt,i);
						if ((pp->bits&(PARTICLE_VISIBLE|PARTICLE_ALIVE))==(PARTICLE_VISIBLE|PARTICLE_ALIVE)) 
							mp[pv->count++]=pp->off;
					}
				}
			}
		}

		if (!FillPV(pv,op->GetDown())) return FALSE;
		op=op->GetNext();
	}
	return TRUE;
}

LONG ParticleVolume::InitRender(PluginMaterial *mat, InitRenderStruct *is)
{
	render = gNew PVRender;
	if (!render) return LOAD_NOMEM;

	if (is->doc && !FillPV(render,is->doc->GetFirstObject()))
	{
		gDelete(render);
		return LOAD_NOMEM;
	}

	return LOAD_OK;
}

void ParticleVolume::FreeRender(PluginMaterial *mat)
{
	gDelete(render);
}

Bool SphereIntersection(const Vector &mp, Real rad, Ray *ray, Real maxdist, Real *length)
{
	Vector	z;
	Real		kr,ee,det,s,s1;

	kr  = rad*rad;
	z   = SV(ray->p) - mp;
	ee  = SV(ray->v)*z;
	det = ee * ee - z*z + kr;
	
	if (det<0.0) return FALSE;

	det = Sqrt(det);
	s   = det - ee;

	if (s<=0.0) return FALSE;

	s1 = -det - ee;

	if (s1>=maxdist) return FALSE;

	if (s>=maxdist) s=maxdist;	
	if (s1<0.0) s1 = 0.0;

	*length = s-s1;
	
	return TRUE;
}

void ParticleVolume::CalcSurface(PluginMaterial *mat, VolumeData *vd)
{
	vd->trans = 1.0;
}

static Real GetCoverage(PVRender *pv, VolumeData *sd)
{
	#define PT_RADIUS 50.0

	LONG		 i;
	Real		 len,maxlen=0.0;

	for (i=0; i<pv->count; i++)			
	{
		if (!SphereIntersection(pv->mp[i],PT_RADIUS,sd->ray,sd->dist,&len)) continue;

		len =len/(2.0*PT_RADIUS);
		len*=len;
		len*=len;

		maxlen=len+maxlen-len*maxlen;
	}

	if (maxlen>1.0) maxlen=1.0;
	return maxlen;
}

void ParticleVolume::CalcVolumetric(PluginMaterial *mat, VolumeData *vd)
{
	Real cov=GetCoverage(render,vd);
	vd->col=Vector(0.0,0.0,cov);
	vd->trans=1.0-cov;
}

void ParticleVolume::CalcTransparency(PluginMaterial *mat, VolumeData *vd)
{
	Real cov=GetCoverage(render,vd);
	vd->trans=1.0-cov;
}

// be sure to use a unique ID obtained from www.plugincafe.com
#define ID_PARTICLEVOLUME	1001163

Bool RegisterParticleVolume(void)
{
	// decide by name if the plugin shall be registered - just for user convenience
	String name=GeLoadString(IDS_PARTICLEVOLUME); if (!name.Content()) return TRUE;
	return RegisterMaterialPlugin(ID_PARTICLEVOLUME,name,0,ParticleVolume::Alloc,"",0);
}
