1 /*
2 -----------------------------------------------------------------------------
3 This source file is part of OGRE
4 (Object-oriented Graphics Rendering Engine)
5 For the latest info, see http://www.ogre3d.org/
6 
7 Copyright (c) 2000-2013 Torus Knot Software Ltd
8 Also see acknowledgements in Readme.html
9 
10 You may use this sample code for anything you like, it is not covered by the
11 same license as the rest of the engine.
12 -----------------------------------------------------------------------------
13 */
14 
15 #include "DLight.h"
16 
17 #include "OgreHardwareBufferManager.h"
18 #include "OgreCamera.h"
19 #include "OgreSceneNode.h"
20 #include "OgreLight.h"
21 #include "GeomUtils.h"
22 #include "LightMaterialGenerator.h"
23 #include "OgreTechnique.h"
24 #include "OgreSceneManager.h"
25 
26 #define ENABLE_BIT(mask, flag) (mask) |= (flag)
27 #define DISABLE_BIT(mask, flag) (mask) &= ~(flag)
28 
29 using namespace Ogre;
30 //-----------------------------------------------------------------------
DLight(MaterialGenerator * sys,Ogre::Light * parentLight)31 DLight::DLight(MaterialGenerator *sys, Ogre::Light* parentLight):
32     mParentLight(parentLight), bIgnoreWorld(false), mGenerator(sys), mPermutation(0)
33 {
34 	// Set up geometry
35 	// Allocate render operation
36 	mRenderOp.operationType = RenderOperation::OT_TRIANGLE_LIST;
37 	mRenderOp.indexData = 0;
38 	mRenderOp.vertexData = 0;
39 	mRenderOp.useIndexes = true;
40 
41 	updateFromParent();
42 }
43 //-----------------------------------------------------------------------
~DLight()44 DLight::~DLight()
45 {
46 	// need to release IndexData and vertexData created for renderable
47     delete mRenderOp.indexData;
48     delete mRenderOp.vertexData;
49 }
50 //-----------------------------------------------------------------------
setAttenuation(float c,float b,float a)51 void DLight::setAttenuation(float c, float b, float a)
52 {
53 	// Set Attenuation parameter to shader
54 	//setCustomParameter(3, Vector4(c, b, a, 0));
55 	float outerRadius = mParentLight->getAttenuationRange();
56 	/// There is attenuation? Set material accordingly
57 	if(c != 1.0f || b != 0.0f || a != 0.0f)
58 	{
59 		ENABLE_BIT(mPermutation, LightMaterialGenerator::MI_ATTENUATED);
60 		if (mParentLight->getType() == Light::LT_POINT)
61 		{
62 			//// Calculate radius from Attenuation
63 			int threshold_level = 10;// difference of 10-15 levels deemed unnoticeable
64 			float threshold = 1.0f/((float)threshold_level/256.0f);
65 
66 			//// Use quadratic formula to determine outer radius
67 			c = c-threshold;
68 			float d=sqrt(b*b-4*a*c);
69 			outerRadius = (-2*c)/(b+d);
70 			outerRadius *= 1.2;
71 		}
72 	}
73 	else
74 	{
75 		DISABLE_BIT(mPermutation,LightMaterialGenerator::MI_ATTENUATED);
76 	}
77 
78 	rebuildGeometry(outerRadius);
79 }
80 //-----------------------------------------------------------------------
setSpecularColour(const ColourValue & col)81 void DLight::setSpecularColour(const ColourValue &col)
82 {
83 	//setCustomParameter(2, Vector4(col.r, col.g, col.b, col.a));
84 	/// There is a specular component? Set material accordingly
85 
86 	if(col.r != 0.0f || col.g != 0.0f || col.b != 0.0f)
87 		ENABLE_BIT(mPermutation,LightMaterialGenerator::MI_SPECULAR);
88 	else
89 		DISABLE_BIT(mPermutation,LightMaterialGenerator::MI_SPECULAR);
90 
91 }
92 //-----------------------------------------------------------------------
rebuildGeometry(float radius)93 void DLight::rebuildGeometry(float radius)
94 {
95 	//Disable all 3 bits
96 	DISABLE_BIT(mPermutation, LightMaterialGenerator::MI_POINT);
97 	DISABLE_BIT(mPermutation, LightMaterialGenerator::MI_SPOTLIGHT);
98 	DISABLE_BIT(mPermutation, LightMaterialGenerator::MI_DIRECTIONAL);
99 
100 	switch (mParentLight->getType())
101 	{
102 	case Light::LT_DIRECTIONAL:
103 		createRectangle2D();
104         ENABLE_BIT(mPermutation,LightMaterialGenerator::MI_DIRECTIONAL);
105 		break;
106 	case Light::LT_POINT:
107 		/// XXX some more intelligent expression for rings and segments
108 		createSphere(radius, 10, 10);
109 		ENABLE_BIT(mPermutation,LightMaterialGenerator::MI_POINT);
110 		break;
111 	case Light::LT_SPOTLIGHT:
112 		Real height = mParentLight->getAttenuationRange();
113 		Radian coneRadiusAngle = mParentLight->getSpotlightOuterAngle() / 2;
114         Real rad = Math::Tan(coneRadiusAngle) * height;
115 		createCone(rad, height, 20);
116 		ENABLE_BIT(mPermutation,LightMaterialGenerator::MI_SPOTLIGHT);
117 		break;
118 	}
119 }
120 //-----------------------------------------------------------------------
createRectangle2D()121 void DLight::createRectangle2D()
122 {
123 	/// XXX this RenderOp should really be re-used between DLight objects,
124 	/// not generated every time
125 	delete mRenderOp.vertexData;
126 	delete mRenderOp.indexData;
127 
128 	mRenderOp.vertexData = new VertexData();
129     mRenderOp.indexData = 0;
130 
131 	GeomUtils::createQuad(mRenderOp.vertexData);
132 
133     mRenderOp.operationType = RenderOperation::OT_TRIANGLE_STRIP;
134     mRenderOp.useIndexes = false;
135 
136 	// Set bounding
137     setBoundingBox(AxisAlignedBox(-10000,-10000,-10000,10000,10000,10000));
138 	mRadius = 15000;
139 	bIgnoreWorld = true;
140 }
141 //-----------------------------------------------------------------------
createSphere(float radius,int nRings,int nSegments)142 void DLight::createSphere(float radius, int nRings, int nSegments)
143 {
144 	delete mRenderOp.vertexData;
145 	delete mRenderOp.indexData;
146 	mRenderOp.operationType = RenderOperation::OT_TRIANGLE_LIST;
147 	mRenderOp.indexData = new IndexData();
148 	mRenderOp.vertexData = new VertexData();
149 	mRenderOp.useIndexes = true;
150 
151 	GeomUtils::createSphere(mRenderOp.vertexData, mRenderOp.indexData
152 		, radius
153 		, nRings, nSegments
154 		, false // no normals
155 		, false // no texture coordinates
156 		);
157 
158 	// Set bounding box and sphere
159 	setBoundingBox( AxisAlignedBox( Vector3(-radius, -radius, -radius), Vector3(radius, radius, radius) ) );
160 	mRadius = radius;
161 	bIgnoreWorld = false;
162 }
163 //-----------------------------------------------------------------------
createCone(float radius,float height,int nVerticesInBase)164 void DLight::createCone(float radius, float height, int nVerticesInBase)
165 {
166 	delete mRenderOp.vertexData;
167 	delete mRenderOp.indexData;
168 	mRenderOp.operationType = RenderOperation::OT_TRIANGLE_LIST;
169 	mRenderOp.indexData = new IndexData();
170 	mRenderOp.vertexData = new VertexData();
171 	mRenderOp.useIndexes = true;
172 
173 	GeomUtils::createCone(mRenderOp.vertexData, mRenderOp.indexData
174 		, radius
175 		, height, nVerticesInBase);
176 
177 	// Set bounding box and sphere
178 	setBoundingBox( AxisAlignedBox(
179 			Vector3(-radius, 0, -radius),
180 			Vector3(radius, height, radius) ) );
181 
182 	mRadius = radius;
183 	bIgnoreWorld = false;
184 }
185 //-----------------------------------------------------------------------
getBoundingRadius(void) const186 Real DLight::getBoundingRadius(void) const
187 {
188 	return mRadius;
189 }
190 //-----------------------------------------------------------------------
getSquaredViewDepth(const Camera * cam) const191 Real DLight::getSquaredViewDepth(const Camera* cam) const
192 {
193 	if(bIgnoreWorld)
194 	{
195 		return 0.0f;
196 	}
197 	else
198 	{
199 		Vector3 dist = cam->getDerivedPosition() - getParentSceneNode()->_getDerivedPosition();
200 		return dist.squaredLength();
201 	}
202 }
203 //-----------------------------------------------------------------------
getMaterial(void) const204 const MaterialPtr& DLight::getMaterial(void) const
205 {
206 	return mGenerator->getMaterial(mPermutation);
207 }
208 //-----------------------------------------------------------------------
getWorldTransforms(Matrix4 * xform) const209 void DLight::getWorldTransforms(Matrix4* xform) const
210 {
211 	if (mParentLight->getType() == Light::LT_SPOTLIGHT)
212 	{
213 		Quaternion quat = Vector3::UNIT_Y.getRotationTo(mParentLight->getDerivedDirection());
214 		xform->makeTransform(mParentLight->getDerivedPosition(),
215 			Vector3::UNIT_SCALE, quat);
216 	}
217 	else
218 	{
219 		xform->makeTransform(mParentLight->getDerivedPosition(),
220 			Vector3::UNIT_SCALE, Quaternion::IDENTITY);
221 	}
222 
223 }
224 //-----------------------------------------------------------------------
updateFromParent()225 void DLight::updateFromParent()
226 {
227 	//TODO : Don't do this unless something changed
228 	setAttenuation(mParentLight->getAttenuationConstant(),
229 		mParentLight->getAttenuationLinear(), mParentLight->getAttenuationQuadric());
230 	setSpecularColour(mParentLight->getSpecularColour());
231 
232 	if (getCastChadows())
233 	{
234 		ENABLE_BIT(mPermutation,LightMaterialGenerator::MI_SHADOW_CASTER);
235 	}
236 	else
237 	{
238 		DISABLE_BIT(mPermutation, LightMaterialGenerator::MI_SHADOW_CASTER);
239 	}
240 }
241 //-----------------------------------------------------------------------
isCameraInsideLight(Ogre::Camera * camera)242 bool DLight::isCameraInsideLight(Ogre::Camera* camera)
243 {
244 	switch (mParentLight->getType())
245 	{
246 	case Ogre::Light::LT_DIRECTIONAL:
247 		return false;
248 	case Ogre::Light::LT_POINT:
249 		{
250 		Ogre::Real distanceFromLight = camera->getDerivedPosition()
251 			.distance(mParentLight->getDerivedPosition());
252 		//Small epsilon fix to account for the fact that we aren't a true sphere.
253 		return distanceFromLight <= mRadius + camera->getNearClipDistance() + 0.1;
254 		}
255 	case Ogre::Light::LT_SPOTLIGHT:
256 		{
257 		Ogre::Vector3 lightPos = mParentLight->getDerivedPosition();
258 		Ogre::Vector3 lightDir = mParentLight->getDerivedDirection();
259 		Ogre::Radian attAngle = mParentLight->getSpotlightOuterAngle();
260 
261 		//Extend the analytic cone's radius by the near clip range by moving its tip accordingly.
262 		//Some trigonometry needed here.
263 		Ogre::Vector3 clipRangeFix = -lightDir * (camera->getNearClipDistance() / Ogre::Math::Tan(attAngle/2));
264 		lightPos = lightPos + clipRangeFix;
265 
266 		Ogre::Vector3 lightToCamDir = camera->getDerivedPosition() - lightPos;
267 		Ogre::Real distanceFromLight = lightToCamDir.normalise();
268 
269 		Ogre::Real cosAngle = lightToCamDir.dotProduct(lightDir);
270 		Ogre::Radian angle = Ogre::Math::ACos(cosAngle);
271 		//Check whether we will see the cone from our current POV.
272         return (distanceFromLight <= (mParentLight->getAttenuationRange() / cosAngle + clipRangeFix.length()))
273             && (angle <= attAngle);
274 		}
275 	default:
276 		//Please the compiler
277 		return false;
278 	}
279 }
280 //-----------------------------------------------------------------------
getCastChadows() const281 bool DLight::getCastChadows() const
282 {
283 	return
284 		mParentLight->_getManager()->isShadowTechniqueInUse() &&
285 		mParentLight->getCastShadows() &&
286 		(mParentLight->getType() == Light::LT_DIRECTIONAL || mParentLight->getType() == Light::LT_SPOTLIGHT);
287 }
288 //-----------------------------------------------------------------------
updateFromCamera(Ogre::Camera * camera)289 void DLight::updateFromCamera(Ogre::Camera* camera)
290 {
291 	//Set shader params
292 	const Ogre::MaterialPtr& mat = getMaterial();
293 	if (!mat->isLoaded())
294 	{
295 		mat->load();
296 	}
297 	Ogre::Technique* tech = mat->getBestTechnique();
298 	Ogre::Vector3 farCorner = camera->getViewMatrix(true) * camera->getWorldSpaceCorners()[4];
299 
300 	for (unsigned short i=0; i<tech->getNumPasses(); i++)
301 	{
302 		Ogre::Pass* pass = tech->getPass(i);
303 		// get the vertex shader parameters
304 		Ogre::GpuProgramParametersSharedPtr params = pass->getVertexProgramParameters();
305 		// set the camera's far-top-right corner
306 		if (params->_findNamedConstantDefinition("farCorner"))
307 			params->setNamedConstant("farCorner", farCorner);
308 
309 		params = pass->getFragmentProgramParameters();
310 		if (params->_findNamedConstantDefinition("farCorner"))
311 			params->setNamedConstant("farCorner", farCorner);
312 
313 		//If inside light geometry, render back faces with CMPF_GREATER, otherwise normally
314 		if (mParentLight->getType() == Ogre::Light::LT_DIRECTIONAL)
315 		{
316 			pass->setCullingMode(Ogre::CULL_CLOCKWISE);
317 			pass->setDepthCheckEnabled(false);
318 		}
319 		else
320 		{
321 			pass->setDepthCheckEnabled(true);
322 			if (isCameraInsideLight(camera))
323 			{
324 				pass->setCullingMode(Ogre::CULL_ANTICLOCKWISE);
325 				pass->setDepthFunction(Ogre::CMPF_GREATER_EQUAL);
326 			}
327 			else
328 			{
329 				pass->setCullingMode(Ogre::CULL_CLOCKWISE);
330 				pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL);
331 			}
332 		}
333 
334 		Camera shadowCam("ShadowCameraSetupCam", 0);
335 		shadowCam._notifyViewport(camera->getViewport());
336 		SceneManager* sm = mParentLight->_getManager();
337 		sm->getShadowCameraSetup()->getShadowCamera(sm,
338 			camera, camera->getViewport(), mParentLight, &shadowCam, 0);
339 
340 		//Get the shadow camera position
341 		if (params->_findNamedConstantDefinition("shadowCamPos"))
342 		{
343 			params->setNamedConstant("shadowCamPos", shadowCam.getPosition());
344 		}
345 		if (params->_findNamedConstantDefinition("shadowFarClip"))
346 		{
347 			params->setNamedConstant("shadowFarClip", shadowCam.getFarClipDistance());
348 		}
349 
350 	}
351 }
352