[Maya] Light Culling?

I’m getting the sinking feeling that I’m barking up the wrong tree here, but…

Problem: I’ve got a relatively complex scene in Maya 2011 that has quite a few lights (around 1400). Rendering it is taking upwards of 40 minutes a frame, which is crippling. Turning all the lights off caused the render time to drop to 40 seconds. Here’s the key bit though:
[ol]
[li]Turning some lights on that are on the far side of the scene - and aren’t visible at all - causes the render time to climb quickly again
[/li][li]But disconnecting them from the visible geometry in the lightlinker causes it to fall back down again.
[/li][/ol]

So… I’m thinking my render time will be acceptable if I can just get the lights set up in the lightlinker so they only apply to the geometry within their cone. The issue I’m running into is there doesn’t seem to be any way of finding out if geometry is within a light’s cone.

I’ve tried a few approaches to this, but I’m not having any luck.
[ul]
[li]I could turn the light locator cones into actual meshes, but I don’t see a good way of finding out which other meshes intersect.
[/li][li]I could cast a ray down the lights normal and see what it intersects, but that doesn’t tell me anything about the rest of the cone
[/li][li]I could turn on one light at a time, and then see what meshes are recieving illumination, but I have no idea where to start with that, if it’s even possible.
[/li][/ul]

Any help? Or am i completely offtrack here? Is there any feasible way to figure out what objects in a scene are being affected by a light? Or alternatively, am I missing some other way to bring render time down?

Thanks for any advice. :sigh:

Hmm, could maybe automate the task with scripts
this might work in pseudo code:

POINT LIGHT INTERSECTION
proc int PointLightIntersection(float lightRadius, vector lightPos, vector objectPos)
{
point light intersection is all about sphere collision with a point.
Since a spheres RADIUS is the same towards any direction within that sphere, so that means to find an intersection of a point if it’s inside a sphere or not, we simply check the distance of the point to the spheres center and check if it’s less or greater than the RADIUS to find if it’s inside or not

  Distance formula: sqrt( pow(A.x - B.x, 2) + pow(A.y - B.y, 2) + pow(A.z - B.z, 2) )

if(distance < lightRadius)
LINK LIGHT so we return true
else
we return false

}

SPOT LIGHT INTERSECTION
proc int SpotLightIntersection(float coneAngle, vector lightDir, vector lightPos, vector objectPos)
{
Now this is more complicated. Values we have are the coneAngle, the light direction, the light position and the object position.
We want to find if the objects point is within the cone or not.

First let’s learn some equations:
DIRECTION VECTOR from point A to point B is (B minus A)
This gives a vector that points with the direction it would take to go from A to B.

LENGHT of a vector is (sqrt(A.xA.x + A.yA.y + A.z*A.z)).
This is the length of the vector from it’s tail to it’s head in units. A single dimension vector such as 5 has the length of 5. But a multi dimensional vector such as (5, 5) does not have a length of 10. Think of the Right Triangle. To get the hypotenuse side of it ( LENGTH basically), you did SquareRoot(Adjacent * Adjacent + Opposite * Opposite). The same applies to get the length of a vector.

NORMALIZED vector is a vector divided by the LENGTH of it. Think of a single dimension vector which can be any integer, like 5, 7 etc. If you want to get that to length of one, you divide it by it’s length which is 5, 7 respectivly, which results 1. To get a vector length of one ( normalized form ) you divide it by its length.

Now that we have learned some stuff needed to accomplish this task, let’s get started.

So lightPos - objectPos gives the direction from the light towards the object
Then we take that direction and normalize it to get it to length of one by dividing it with its length.

vector SpotDir = lightPos - objectPos;
float SpotDirLength = //apply the length algorithm here on the above vector.
vector SpotDirNormalized = SpotDir / SpotDirLength

Now we got a direction that points from the light towards the object and it is in length of one.
We ALSO have the lights real direction that comes in as a parameter to this function.
All we have to do is determine if the ANGLE between these two directions are less than or greater than CONE ANGLE or NOT. Simple when you think about it aye? :stuck_out_tongue:

The way we do that is with DOT product that is usually used in computer graphics and shaders. The DOT product returns the COSINE between two vectors.
DotProduct equation: A.xB.x + A.yB.y + A.z*B.z. (Remember that if you want to get the cosine between two vectors, they both needs to be normalized, or else one will scale the other)

float SpotDirDotLightDir = DOTPRODUCT(SpotDirNormalized, LightDir); //Cosine between light direction and spot direction.

Now all that’s left is an if check

float ArcCosSDotL = arccos(SpotDirDotLightDir); //Get the angle from the cosine by using the INVERSE COSINE function
If (ArcCosSDotL < ConeAngle)
Return true meaning LIGHT LINK
else
Return false meaning don’t LINK
}

Now you can loop through each object in the scene like this:

Foreach light in the scene
Foreach object in the scene

If(light is spotLight)
Check intersection with obejct
else if(light is pointLight)
Check intersection with object

Do LIGHT LINKING through script with the returned boolean value

end of inner loop
end of outer loop

I hope I haven’t done something wrong in this post, and I hope this gives you an idea how to make this.
I’m pretty sure there are alot easier ways to do this but since I don’t know them I’ve taken the regular approach; “do it the hard way” ^^ There is always workarounds for problems!

PS: Also if you want you can do an early distance check in the SPOT LIGHT function to do an early abort, you might not want objects 10000 units to be affected by a spot light that has a range of 10 units. Just pass in another float and do the regular if (distance > range) return false.

Wow, thanks, that helps a lot.

My only concern is that - if I’m reading it right - this will only catch objects who’s pivot is in a light cone. So, for example, a large wall with several spotlights shining on it wouldn’t work right as only the middle spotlight would be covering the pivot point.

It seems like a fairly simple change to check each vertex instead of checking the overall object. That will be incredibly slow, but it also has problems since it will only catch objects who have a vertex in a light cone. Annoyingly, my scene has a lot of small lights and large, low-poly objects, some of which need to receive light in areas in between individual vertices.

I can’t shake the feeling that I’m doing something really wrong here to even be having this problem…

Gah. :x:

Hihi =]

Yeah that’s true it will only catch objects pivots. Good catch didn’t think about that ^^. You could maybe loop on every 10th or every 50th vertex maybe? And break out of the loop on the first intersection of vertex and light radius / cone, then just Light Link !

EDIT: you can also loop on the BoundingBox points! Every object got a bounding box if you check in attribute editor =D A min value and a max value. It’s axis alligned so you can build up 8 points with those min and max values!

Other than that I don’t know since I haven’t rendered much, guess it will happen when I take my Rendering course and Portfolio projects start getting closer later this year. Though it feels like maya should have native support of light culling or something for scenes like yours!

Anyhow good luck and I hope some senior will help ya out here who has far more experience with Rendering!

Peace!

I appreciate the help; even if it doesn’t solve my problem it’s got to get me closer to a solution than I was before I read your post. :slight_smile:

But yes, it seems like there’s got to be an easy, native solution to this…right? :frowning:

No worries =] I had a bad feeling that I had written something wrong on the previous post and I was right :stuck_out_tongue: I found two wrong lines and fixed them incase you will use this bruteforce method :stuck_out_tongue:

Also edited the previous post that you can loop on the AABB of the object (8 points, and break out of the loop on the first intersection with a break;)
PS: Maya doesn’t have built in variables afaik for the lights radius or range. So you’ll have to use some form of math to produce them together with the current Attenuation and its intensity to find out its radius that it covers!

Anyhow, getting late here!

Bounding boxes! I knew I was missing something…

Okay, that…should work fine. It’ll be WAY faster than checking vertices, and while it’ll give some false positives (a narrow light cone shining through the centre of a torus, for example), it won’t give any false negatives the way a vertex check would (a narrow light cone shining on a large quad, for example).

Awesome. :slight_smile: Time to start coding! (I’ll update this post with the code ASAP, in case anyone else ever needs to solve this problem.)

Duplicate post - see below

Alright, was a little harder than I expected. :slight_smile: I ended up with a LightCuller class. You instantiate it by passing it a list of lights, and then you call it’s start method with a distance (which is only used for pointlights).

It only supports spotlights and pointlights. For spotlights it:
[ul]
[li]Checks to see if the spotlight is in the mesh’s bounding box
[/li][li]Checks to see if the center of the mesh’s bounding box is in the spotlight’s cone
[/li][li]Checks to see if any vertex of the mesh’s bounding box is in the spotlight’s cone
[/li][li]Checks to see if the light’s frustum intersects with any part of the mesh’s bounding box
[/li][/ul]

For pointlights it checks the distance between the pointlight and the mesh’s bounding sphere, and compares it to a completely arbitrary distance you have to manually pass in. Hackish, but enough for my purposes.

It has some error checking. It also displays a progress bar, and can be cancelled by hitting escape, which is kind of good since running this on a big scene is an overnight sort of job. :slight_smile:

Hope someone finds it interesting (or at least entertaining; I’m pretty rusty with Python, bad at vector math, and new to Maya/pymel, so the best I can say about my code is that it mostly seems to work).

Note 1: If you run it with debug = True, it’ll spit out a bunch of debugging statements as it goes, but it will NOT actually do any lightlinking. If you run it with debug = False it won’t say anything, but it’ll wipe out all existing lightlinking for the lights you run it on.

Note 2: Works with Maya 2011. No promises about earlier or later versions.

Note 3: In the rather unlikely event that anyone cares, the script is released under under a [Creative Commons Attribution 3.0 License](Creative Commons Attribution 3.0 License).

Note 4: If anyone has any comments or suggestions, I’d love to hear them. As I said, knew to this area.

Part 1 - the LightCuller class:

################################################################
#       Link selected lights to objects in light frustum       #
#                                                              #
#            Copyright 2010 Thaumaturgy Studios Ltd            #
#                http://www.thaumaturgy.co.nz                  #
#                   info@thaumaturgy.co.nz                     #
################################################################

[b]from[/b] pymel[b].[/b]core [b]import[/b] [b]*[/b]
[b]import[/b] maya[b].[/b]cmds [b]as[/b] cmds
[b]import[/b] maya[b].[/b]mel [b]as[/b] mel
[b]import[/b] math
[b]import[/b] pymel[b].[/b]core[b].[/b]datatypes [b]as[/b] dt
[b]import[/b] logging
[b]from[/b] pymel[b].[/b]core[b].[/b]uitypes [b]import[/b] ProgressBar

debug [b]=[/b] [b]False[/b]
logger [b]=[/b] logging[b].[/b]getLogger[b]([/b]"lightCulling"[b])[/b]
[b]if[/b] debug[b]:[/b]
	logger[b].[/b]setLevel[b]([/b]logging[b].[/b]DEBUG[b])[/b]
[b]else[/b][b]:[/b]
	logger[b].[/b]setLevel[b]([/b]logging[b].[/b]INFO[b])[/b]

[b]class[/b] Plane[b]:[/b]
	[b]def[/b] __init__[b]([/b]self[b],[/b] point[b],[/b] normal[b],[/b] name[b]):[/b]
		self[b].[/b]normal [b]=[/b] normal	# This is the normal vector
		self[b].[/b]point [b]=[/b] point		# This is a point on the plane
		self[b].[/b]name [b]=[/b] name

[b]class[/b] Frustum[b]:[/b]
	[b]def[/b] __init__[b]([/b]self[b],[/b] lightPos[b],[/b] lightQ[b],[/b] lightCone[b]):[/b]
		theta [b]=[/b] math[b].[/b]radians[b]([/b]lightCone[b])[/b]
		self[b].[/b]planes [b]= [][/b] 
		vectors [b]=[/b] [b]list[/b][b]()[/b]

		vectors[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b][b]0[/b][b],[/b][b]0[/b][b],-[/b][b]1[/b][b]).[/b]rotateBy[b]([/b]dt[b].[/b]Quaternion[b]().[/b]setToYAxis[b]([/b]theta[b]) *[/b] dt[b].[/b]Quaternion[b]().[/b]setToXAxis[b]([/b]theta[b]) *[/b] lightQ[b]))[/b]
		vectors[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b][b]0[/b][b],[/b][b]0[/b][b],-[/b][b]1[/b][b]).[/b]rotateBy[b]([/b]dt[b].[/b]Quaternion[b]().[/b]setToYAxis[b](-[/b]theta[b]) *[/b] dt[b].[/b]Quaternion[b]().[/b]setToXAxis[b]([/b]theta[b]) *[/b] lightQ[b]))[/b]
		vectors[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b][b]0[/b][b],[/b][b]0[/b][b],-[/b][b]1[/b][b]).[/b]rotateBy[b]([/b]dt[b].[/b]Quaternion[b]().[/b]setToYAxis[b]([/b]theta[b]) *[/b] dt[b].[/b]Quaternion[b]().[/b]setToXAxis[b](-[/b]theta[b]) *[/b] lightQ[b]))[/b]
		vectors[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b][b]0[/b][b],[/b][b]0[/b][b],-[/b][b]1[/b][b]).[/b]rotateBy[b]([/b]dt[b].[/b]Quaternion[b]().[/b]setToYAxis[b](-[/b]theta[b]) *[/b] dt[b].[/b]Quaternion[b]().[/b]setToXAxis[b](-[/b]theta[b]) *[/b] lightQ[b]))[/b]
		
		self[b].[/b]planes[b].[/b]append[b]([/b]Plane[b]([/b]lightPos[b],[/b]vectors[b][[/b][b]1[/b][b]].[/b]cross[b]([/b]vectors[b][[/b][b]0[/b][b]]).[/b]normal[b](),[/b] "top"[b]))[/b]
		self[b].[/b]planes[b].[/b]append[b]([/b]Plane[b]([/b]lightPos[b],[/b]vectors[b][[/b][b]3[/b][b]].[/b]cross[b]([/b]vectors[b][[/b][b]1[/b][b]]).[/b]normal[b](),[/b] "right"[b]))[/b]
		self[b].[/b]planes[b].[/b]append[b]([/b]Plane[b]([/b]lightPos[b],[/b]vectors[b][[/b][b]2[/b][b]].[/b]cross[b]([/b]vectors[b][[/b][b]3[/b][b]]).[/b]normal[b](),[/b] "bot"[b]))[/b]
		self[b].[/b]planes[b].[/b]append[b]([/b]Plane[b]([/b]lightPos[b],[/b]vectors[b][[/b][b]0[/b][b]].[/b]cross[b]([/b]vectors[b][[/b][b]2[/b][b]]).[/b]normal[b](),[/b] "left"[b]))[/b]
			
	[b]def[/b] checkBox[b]([/b]self[b],[/b] boxCorners[b]):[/b]
		[b]for[/b] plane [b]in[/b] self[b].[/b]planes[b]:[/b]
			numBehind [b]=[/b] [b]0[/b]
			[b]for[/b] corner [b]in[/b] boxCorners[b]:[/b]
				[b]if[/b] [b]([/b]corner [b]-[/b] plane[b].[/b]point[b]).[/b]dot[b]([/b]plane[b].[/b]normal[b]) >[/b] [b]0[/b][b]:[/b]
					numBehind [b]+=[/b] [b]1[/b]
			logger[b].[/b]debug[b]([/b]"Num points behind plane " [b]+[/b] plane[b].[/b]name [b]+[/b] ": " [b]+[/b] [b]str[/b][b]([/b]numBehind[b]))[/b]
			[b]if[/b] numBehind [b]==[/b] [b]len[/b][b]([/b]boxCorners[b]):[/b]
				[b]return False[/b]
		[b]return True[/b]

[b]class[/b] LightCuller[b]:[/b]
	[b]def[/b] __init__[b]([/b]self[b],[/b] lightList[b]):[/b]
		self[b].[/b]llData [b]= [][/b]
		self[b].[/b]lightList [b]=[/b] lightList
		self[b].[/b]meshList [b]=[/b] [b]filter[/b][b]([/b]self[b].[/b]checkVis[b],[/b] ls[b]([/b][b]type[/b][b]=[/b]'mesh'[b]))[/b]
		[b]if[/b] [b]len[/b][b]([/b]self[b].[/b]meshList[b]) ==[/b] [b]0[/b][b]:[/b]
			logger[b].[/b]critical[b]([/b]"You have no unhidden meshes in the scene!"[b])[/b]
			sys[b].[/b]exit[b]()[/b]
		self[b].[/b]initProgress[b]()[/b]
	[b]def[/b] start[b]([/b]self[b],[/b]maxDist[b]=[/b][b]0[/b][b]):[/b]
		self[b].[/b]maxDistance [b]=[/b] maxDist
		[b]for[/b] light [b]in[/b] self[b].[/b]lightList[b]:[/b]
			self[b].[/b]process[b]([/b]light[b])[/b]
		[b]if[/b] debug[b]:[/b]	
			[b]for[/b] light[b],[/b] mesh [b]in[/b] [b]list[/b][b]([/b][b]set[/b][b]([/b]lightc[b].[/b]llData[b])):[/b]
				[b]print[/b] light[b].[/b]name[b](),[/b] '->'[b],[/b] mesh[b].[/b]name[b]()[/b]
		[b]else[/b][b]:[/b]
			lightlink[b]([/b]ual[b]=[/b][b]True[/b][b],[/b] b[b]=[/b][b]True[/b][b],[/b] o[b]=[/b]ls[b]([/b][b]type[/b][b]=[/b]'transform'[b]))[/b] # Breaks all light links
			[b]for[/b] light[b],[/b] mesh [b]in[/b] [b]list[/b][b]([/b][b]set[/b][b]([/b]lightc[b].[/b]llData[b])):[/b]
				lightlink[b]([/b]l[b]=[/b]light[b],[/b] o[b]=[/b]mesh[b],[/b] m[b]=[/b][b]True[/b][b])[/b]
	[b]def[/b] process[b]([/b]self[b],[/b] light[b]):[/b]
		lightPos [b]=[/b] light[b].[/b]getTranslation[b]([/b]space[b]=[/b]'world'[b])[/b]
		self[b].[/b]startProgress[b]([/b][b]len[/b][b]([/b]self[b].[/b]meshList[b]))[/b]

		[b]if[/b] light[b].[/b]getShape[b]().[/b]nodeType[b]() ==[/b] 'spotLight'[b]:[/b]
				lightCone [b]=[/b] self[b].[/b]getLightCone[b]([/b]light[b].[/b]getShape[b]())[/b]
				lightVec [b]=[/b] dt[b].[/b]Vector[b]([/b][b]0[/b][b],[/b][b]0[/b][b],[/b][b]1[/b][b]).[/b]rotateBy[b]([/b]light[b].[/b]getRotation[b]([/b]space[b]=[/b]'world'[b]))[/b]
				lightPos [b]=[/b] light[b].[/b]getTranslation[b]()[/b]
				lightQ [b]=[/b] dt[b].[/b]Quaternion[b]([/b]light[b].[/b]getTransformation[b]().[/b]getRotationQuaternion[b]())[/b]
				frustum [b]=[/b] Frustum[b]([/b]lightPos[b],[/b] lightQ[b],[/b] lightCone[b])[/b]

		[b]for[/b] mesh [b]in[/b] self[b].[/b]meshList[b]:[/b]
			logger[b].[/b]debug[b]([/b]"Processing " [b]+[/b] mesh[b].[/b]name[b]())[/b]
			box [b]=[/b] mesh[b].[/b]getParent[b]().[/b]getBoundingBox[b]([/b]space[b]=[/b]'world'[b])[/b]
			boxCorners [b]=[/b] self[b].[/b]findCorners[b]([/b]box[b])[/b]
			self[b].[/b]setProgress[b]([/b]'Processing ' [b]+[/b] light[b].[/b]name[b]() +[/b] ' - ' [b]+[/b] mesh[b].[/b]getParent[b]().[/b]name[b]())[/b]
			self[b].[/b]checkCancelled[b]()[/b]
		
			[b]if[/b] light[b].[/b]getShape[b]().[/b]nodeType[b]() ==[/b] 'spotLight'[b]:[/b]
				# Does the box contain the cone?
				[b]if[/b] box[b].[/b]contains[b]([/b]lightPos[b]):[/b]
					logger[b].[/b]debug[b]([/b]light[b].[/b]name[b]() +[/b] " is inside bounding box of " [b]+[/b] mesh[b].[/b]getParent[b]().[/b]name[b]())[/b]
					self[b].[/b]llData[b].[/b]append[b](([/b]light[b],[/b]mesh[b].[/b]getParent[b]()))[/b]
				# Does the cone contain the center of the box?
				[b]elif[/b] lightCone [b]>=[/b] self[b].[/b]calcMinAngle[b]([/b]lightVec[b],[/b] lightPos[b], [[/b]box[b].[/b]center[b]()]):[/b]
					logger[b].[/b]debug[b]([/b]light[b].[/b]name[b]() +[/b] "'s cone contains the center point of " [b]+[/b] mesh[b].[/b]getParent[b]().[/b]name[b]())[/b]
					self[b].[/b]llData[b].[/b]append[b](([/b]light[b],[/b]mesh[b].[/b]getParent[b]()))[/b]
				# Does the cone contain a vertex of the box?
				[b]elif[/b] lightCone [b]>=[/b] self[b].[/b]calcMinAngle[b]([/b]lightVec[b],[/b] lightPos[b],[/b] boxCorners[b]):[/b]
					logger[b].[/b]debug[b]([/b]light[b].[/b]name[b]() +[/b] "'s cone contains a vertex of " [b]+[/b] mesh[b].[/b]getParent[b]().[/b]name[b]())[/b]
					self[b].[/b]llData[b].[/b]append[b](([/b]light[b],[/b]mesh[b].[/b]getParent[b]()))[/b]
				# Is the frustrum surrounded by points?	
				[b]elif[/b] frustum[b].[/b]checkBox[b]([/b]boxCorners[b]):[/b]
					logger[b].[/b]debug[b]([/b]light[b].[/b]name[b]() +[/b] "'s frustum is inside " [b]+[/b] mesh[b].[/b]getParent[b]().[/b]name[b]())[/b]
					self[b].[/b]llData[b].[/b]append[b](([/b]light[b],[/b]mesh[b].[/b]getParent[b]()))[/b]
			[b]else[/b][b]:[/b]
				# Is distance outside our check distance?
				[b]if[/b] self[b].[/b]maxDistance [b]>= ([/b]lightPos [b]-[/b] box[b].[/b]center[b]()).[/b]length[b]() -[/b] self[b].[/b]calcBoundingRadius[b]([/b]boxCorners[b]):[/b]
					logger[b].[/b]debug[b]([/b]light[b].[/b]name[b]() +[/b] "is close enough to " [b]+[/b] mesh[b].[/b]getParent[b]().[/b]name[b]())[/b]
					self[b].[/b]llData[b].[/b]append[b](([/b]light[b],[/b]mesh[b].[/b]getParent[b]()))[/b]
			self[b].[/b]stepProgress[b]()[/b]
		self[b].[/b]close[b]()[/b] 
	[b]def[/b] findCorners[b]([/b]self[b],[/b]mesh[b]):[/b]
		bbox [b]= [][/b]
		bbox[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b]mesh[b].[/b][b]min[/b][b]().[/b]x[b],[/b] mesh[b].[/b][b]min[/b][b]().[/b]y[b],[/b] mesh[b].[/b][b]min[/b][b]().[/b]z[b]))[/b]
		bbox[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b]mesh[b].[/b][b]max[/b][b]().[/b]x[b],[/b] mesh[b].[/b][b]min[/b][b]().[/b]y[b],[/b] mesh[b].[/b][b]min[/b][b]().[/b]z[b]))[/b]
		bbox[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b]mesh[b].[/b][b]min[/b][b]().[/b]x[b],[/b] mesh[b].[/b][b]max[/b][b]().[/b]y[b],[/b] mesh[b].[/b][b]min[/b][b]().[/b]z[b]))[/b]
		bbox[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b]mesh[b].[/b][b]max[/b][b]().[/b]x[b],[/b] mesh[b].[/b][b]max[/b][b]().[/b]y[b],[/b] mesh[b].[/b][b]min[/b][b]().[/b]z[b]))[/b]
		bbox[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b]mesh[b].[/b][b]min[/b][b]().[/b]x[b],[/b] mesh[b].[/b][b]min[/b][b]().[/b]y[b],[/b] mesh[b].[/b][b]max[/b][b]().[/b]z[b]))[/b]
		bbox[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b]mesh[b].[/b][b]max[/b][b]().[/b]x[b],[/b] mesh[b].[/b][b]min[/b][b]().[/b]y[b],[/b] mesh[b].[/b][b]max[/b][b]().[/b]z[b]))[/b]
		bbox[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b]mesh[b].[/b][b]min[/b][b]().[/b]x[b],[/b] mesh[b].[/b][b]max[/b][b]().[/b]y[b],[/b] mesh[b].[/b][b]max[/b][b]().[/b]z[b]))[/b]
		bbox[b].[/b]append[b]([/b]dt[b].[/b]Vector[b]([/b]mesh[b].[/b][b]max[/b][b]().[/b]x[b],[/b] mesh[b].[/b][b]max[/b][b]().[/b]y[b],[/b] mesh[b].[/b][b]max[/b][b]().[/b]z[b]))[/b]
		[b]return[/b] bbox
	[b]def[/b] calcMinAngle[b]([/b]self[b],[/b] obj1Vec[b],[/b] obj1Pos[b],[/b] otherObjects[b]):[/b]
		smallestAngle [b]=[/b] [b]360[/b]
		[b]for[/b] obj2Pos [b]in[/b] otherObjects[b]:[/b]
			curAngle [b]=[/b] math[b].[/b]degrees[b]([/b]obj1Vec[b].[/b]angle[b](([/b]obj1Pos [b]-[/b] obj2Pos[b]).[/b]normal[b]()))[/b]
			[b]if[/b] curAngle [b]<[/b] smallestAngle[b]:[/b]
				smallestAngle [b]=[/b] curAngle
		[b]return[/b] smallestAngle
	[b]def[/b] calcBoundingRadius[b]([/b]self[b],[/b] bboxPoints[b]):[/b]
		maxDist [b]=[/b] [b]0[/b]
		[b]for[/b] point1 [b]in[/b] bboxPoints[b]:[/b]
			[b]for[/b] point2 [b]in[/b] bboxPoints[b]:[/b]
				[b]if[/b] [b]([/b]point1 [b]-[/b] point2[b]).[/b]length[b]() >[/b] maxDist[b]:[/b]
					maxDist [b]= ([/b]point1 [b]-[/b] point2[b]).[/b]length[b]()[/b]
		[b]return[/b] maxDist[b]/[/b][b]2[/b]
	[b]def[/b] getLightCone[b]([/b]self[b],[/b] light[b]):[/b]
		[b]if[/b] light[b].[/b]nodeType[b]() ==[/b] 'spotLight'[b]:[/b]
			[b]return[/b] light[b].[/b]coneAngle[b].[/b]get[b]()/[/b][b]2[/b] [b]+[/b] light[b].[/b]penumbraAngle[b].[/b]get[b]()/[/b][b]2[/b]
		[b]else[/b][b]:[/b]
			self[b].[/b]close[b]()[/b]
			sys[b].[/b]exit[b]()[/b]
	[b]def[/b] checkVis[b]([/b]self[b],[/b] obj[b]):[/b]
		[b]if[/b] obj[b].[/b]visibility[b].[/b]get[b]():[/b]
			[b]if[/b] obj[b].[/b]getParent[b]():[/b]
				[b]return[/b] self[b].[/b]checkVis[b]([/b]obj[b].[/b]getParent[b]())[/b]
			[b]else[/b][b]:[/b]
				[b]return[/b] obj[b].[/b]visibility[b].[/b]get[b]()[/b]	
	[b]def[/b] initProgress[b]([/b]self[b]):[/b]
		self[b].[/b]gMainProgressBar [b]=[/b] mel[b].[/b][b]eval[/b][b]([/b]'$tmp = $gMainProgressBar'[b])[/b]
	[b]def[/b] startProgress[b]([/b]self[b],[/b]length[b]):[/b]
		progressBar[b]([/b]self[b].[/b]gMainProgressBar[b],[/b] edit[b]=[/b][b]True[/b][b],[/b] beginProgress[b]=[/b][b]True[/b][b],[/b] isInterruptable[b]=[/b][b]True[/b][b],[/b] status[b]=[/b]'Starting script...'[b],[/b] maxValue[b]=[/b]length[b])[/b]
	[b]def[/b] setProgress[b]([/b]self[b],[/b]msg[b]):[/b]
		progressBar[b]([/b]self[b].[/b]gMainProgressBar[b],[/b] edit[b]=[/b][b]True[/b][b],[/b] status[b]=[/b]msg[b])[/b]
	[b]def[/b] stepProgress[b]([/b]self[b]):[/b]
		progressBar[b]([/b]self[b].[/b]gMainProgressBar[b],[/b] edit[b]=[/b][b]True[/b][b],[/b] step[b]=[/b][b]1[/b][b])[/b]
	[b]def[/b] checkCancelled[b]([/b]self[b]):[/b]
		[b]if[/b] progressBar[b]([/b]self[b].[/b]gMainProgressBar[b],[/b] query[b]=[/b][b]True[/b][b],[/b] isCancelled[b]=[/b][b]True[/b][b]):[/b]
			progressBar[b]([/b]self[b].[/b]gMainProgressBar[b],[/b] edit[b]=[/b][b]True[/b][b],[/b] ep[b]=[/b][b]True[/b][b])[/b]
			sys[b].[/b]exit[b]()[/b]
	[b]def[/b] close[b]([/b]self[b]):[/b]
		progressBar[b]([/b]self[b].[/b]gMainProgressBar[b],[/b] edit[b]=[/b][b]True[/b][b],[/b] ep[b]=[/b][b]True[/b][b])[/b]

And part 2 - example usage:

[b]if[/b] [b]len[/b][b]([/b]ls[b]([/b]sl[b]=[/b][b]True[/b][b])) ==[/b] [b]0[/b][b]:[/b]
	[b]print[/b] "Please select at least one light!"
	sys[b].[/b]exit[b]()[/b]
needDist [b]=[/b] [b]False[/b]
[b]for[/b] light [b]in[/b] ls[b]([/b]sl[b]=[/b][b]True[/b][b]):[/b]
	[b]if not[/b] light[b].[/b]getShape[b]().[/b]nodeType[b]()[/b] [b]in[/b] [b][[/b]'pointLight'[b],[/b] 'spotLight'[b]]:[/b]
		[b]print[/b] "Please only select pointLight or spotLights!  " [b]+[/b] light[b].[/b]name[b]() +[/b] " is not a supported light."
		sys[b].[/b]exit[b]()[/b]
	[b]if[/b] light[b].[/b]getShape[b]().[/b]nodeType[b]() ==[/b] 'pointLight'[b]:[/b]
		needDist [b]=[/b] [b]True[/b]
[b]if[/b] needDist[b]:[/b]
	with window[b]([/b]title[b]=[/b]'Pointlight maxDist'[b])[/b] [b]as[/b] win[b]:[/b] 
		with columnLayout[b]():[/b]
			text[b]([/b]label[b]=[/b]'Please enter a maximum distance for pointlight culling:'[b])[/b]
			maxDist [b]=[/b] floatField[b]([/b][b]min[/b][b]=[/b][b]0[/b][b],[/b] enterCommand[b]=[/b]'lightc = LightCuller(ls(sl=True)), lightc.start(maxDist.getValue()); deleteUI(win)'[b])[/b]
			button[b]([/b] label[b]=[/b]'Set max distance'[b],[/b] command[b]=[/b]'lightc = LightCuller(ls(sl=True)), lightc.start(maxDist.getValue()); deleteUI(win)'[b])[/b]
[b]else[/b][b]:[/b]
	lightc [b]=[/b] LightCuller[b]([/b]ls[b]([/b]sl[b]=[/b][b]True[/b][b]))[/b]
	lightc[b].[/b]start[b]([/b][b]0.0[/b][b])[/b]

Is it a bad sign now that when I close my eyes I can see vectors? :frowning: