Given a randomized euler rotation of (x1,y1,z1) of a sun lamp object, how do we compute whether the final light ray will be pointing 'down' in negative z worldspace or up?

Essentially, I am looking for a way to test if the sun will be visible, or below the horizon.

  • $\begingroup$ Is the sun lamp rotating around the scene? What does visible mean to you? That a ray from the camera will be able to reach the sun lamp? If so, you're concerned about the position of the lamp, and not its orientation. Does visible mean that an object will be under the influence of the sun lamp? $\endgroup$ – Dazotaro May 3 '19 at 22:13
  • $\begingroup$ @Dazotaro for a sun only the direction matters, not so much the location. If the direction has global z up it can be considered below the horizon, or on the other side of the earth (or night time for that scene) $\endgroup$ – batFINGER May 3 '19 at 22:24
  • $\begingroup$ @batFINGER, it makes sense. I was wondering whether there might be a mesh object attached to the lamp to "render' the sun if in the camera's FOV. $\endgroup$ – Dazotaro May 3 '19 at 22:45

If you are just wanting a calculation based on given Euler angles, (since location makes no difference to a Sun lamp, which shines down its negative Z all over the world from an infinite distance), then all we are interested in the world-Z component of the direction of the lamp's own Z axis. If that is positive, the sun is shining down.

So you could do this sort of thing:

from mathutils import Matrix, Euler
from math import radians

def shines_down(XYZ_angles):     
    angles = (radians(x) for x in XYZ_angles)
    eul = Euler(angles, 'XYZ')
    mat = eul.to_matrix()
    return mat[2][2] > 0

print ( shines_down((78,38,-179)) )

However, if you have the object already created, then you could just read the value out of its transformation matrix:

import bpy

ob = bpy.data.objects['Sun']
print (ob.matrix_world[2][2] > 0)

#True if down
  • 1
    $\begingroup$ A far simpler test. I'm assuming we have now voted for each other lol. $\endgroup$ – batFINGER May 4 '19 at 9:54

A sun lamp shines in direction of local -z axis. Calculate as a global sun direction vector by multiplying -z axis (0, 0, -1) by its matrix world and subtracting the global coord of the origin (which is also the translation of the world matrix).

If the dot product of global -z axis and global sun direction vector is > 0 then the sun has some component of global down, and can be considered above horizon.

import bpy
from mathutils import Vector

local_lamp_dir = Vector((0, 0, -1))
lamp = bpy.context.object
mw = lamp.matrix_world
loc = mw.translation
global_lamp_dir = (mw @ local_lamp_dir - loc).normalized()

is_above_horizon = global_lamp_dir.dot(local_lamp_dir) > 0


As a simple test recommend using the simple matrix element test proposed by @Robin Betts. Remember that using the matrix world will give visual result should constraints be involved.

Using a vector to produce random, but down facing sun object world matrix.

Instead of randomly generating Euler rotations and testing if facing down, could randomly produce a vector where the z component is less than zero and build a world matrix from the track to quaternion of the vector.

Test script. Pass an object and optionally a location. Sets its matrix world to be down facing (-Z). The vector v will have random x and y values from range [-1, 1] and z in [-1, 0].

import bpy

from mathutils import Vector
from random import uniform

def random_down(ob, loc=(0, 0, 0)):

    v = Vector(uniform(-1, 1) for i in "xyz")
    v.z = -abs(v.z) # ensure negativity
    M = v.to_track_quat('-Z', 'Y').to_matrix().to_4x4()
    M.translation = loc
    ob.matrix_world = M

# test call
ob = bpy.context.object

Driving the sun strength

The dot product result is a handy factor to drive sun strength. It will be maximum at 12 o'clock high, be zero at the horizon and negative below.

enter image description here max(0, global_lamp_dir.dot(local_lamp_dir)) being used as a driver on the strength

  • $\begingroup$ Honored by the ref.. now you've got me trying to get my head round basis,local,parent_inverse : | $\endgroup$ – Robin Betts May 4 '19 at 15:21
    if -1.5708 < bpy.context.object.rotation_euler[0] < 1.5708:

1.5708 is Pi/2

  • $\begingroup$ I don't believe this would work at all times since rotation in the other axis could affect the quadrant that the x-euler is pointing. $\endgroup$ – Mike Pan May 5 '19 at 7:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.