Exemple #1
0
class Light(ShaderStructElement):

    """ Abstract light class. All light types are subclasses of this class. This 
    class handles all generic properties of a light, aswell as storing and managing
    the shadow sources and light updates. """

    def __init__(self):
        """ Constructs a new Light, subclasses have to call this """
        DebugObject.__init__(self, "AbstractLight")
        ShaderStructElement.__init__(self)
        self.debugNode = NodePath("LightDebug")
        self.visualizationNumSteps = 32
        self.dataNeedsUpdate = False
        self.castShadows = False
        self.debugEnabled = False
        self.bounds = OmniBoundingVolume()
        self.shadowSources = []
        self.lightType = self.getLightType()
        self.position = Vec3(0)
        self.color = Vec3(1)
        self.posterIndex = -1
        self.radius = 10.0
        self.typeName = ""
        self.sourceIndexes = PTAInt.emptyArray(6)
        self.attached = False
        self.shadowResolution = 512
        self.index = -1
        self.iesProfile = -1
        self.iesProfileName = None
        self.mvp = Mat4()

        # A light can have up to 6 sources
        for i in range(6):
            self.sourceIndexes[i] = -1

    @classmethod
    def getExposedAttributes(self):
        """ Returns the exposed attributes, required for the
        ShaderStructArray """
        return {
            "position": "vec3",
            "color": "vec3",
            "posterIndex": "int",
            "iesProfile": "int",
            "lightType": "int",
            "radius": "float",
            "sourceIndexes": "array<int>(6)",
            "mvp": "mat4"
        }

    def setIESProfile(self, profileName):
        """ Sets the specified ies profile for that light """
        self.iesProfileName = profileName
        self.iesProfile = -1

    def getIESProfileName(self):
        """ Returns the ies profile of this light """
        return self.iesProfileName

    def setIESProfileIndex(self, index):
        """ Sets the ies profile index """
        self.iesProfile = index
        self.queueUpdate()

    def getIESProfileIndex(self):
        """ Returns the ies profile index of this light """
        return self.iesProfile

    def getIndex(self):
        """ Returns the light index, this is only set if the light is already attached """
        return self.index

    def setIndex(self, index):
        """ Sets the light index, gets called by the LightManager """
        self.index = index

    def getTypeName(self):
        """ Returns the internal id of the light-type, e.g. "PointLight" """
        return self.typeName

    def setShadowMapResolution(self, resolution):
        """ Attempts to set the resolution of the shadow soures. You
        cannot call this after the light got attached to the LightManager,
        as the shadow sources might already have a position in the
        shadow atlas """
        if self.attached:
            raise Exception(
                "You cannot change the resolution after the light got attached")
        self.shadowResolution = resolution


    def setRadius(self, radius):
        """ Sets the radius of the light. Only affects PointLights """
        self.radius = max(0.01, radius)
        self.queueUpdate()

    def getShadowSources(self):
        """ Returns a list of assigned shadow sources for this light """
        return self.shadowSources

    def hasShadows(self):
        """ Returns Whether the light casts shadows """
        return self.castShadows

    def setCastsShadows(self, shadows=True):
        """ Sets Whether the light casts shadows or not """
        self.castShadows = shadows

        if self.castShadows:
            self._initShadowSources()
            self.shadowNeedsUpdate = True
        else:
            for source in self.shadowSources:
                del source
            self.shadowSources = []
            self.shadowNeedsUpdate = False

    def setPos(self, *args):
        """ Sets the position of the light. """

        # Assume vec3 when only 1 argument is passed
        if len(args) == 1:
            pos = args[0]
        else:
            pos = Vec3(*args)

        # If the position is very similar, don't update
        if (pos - self.position).length() > 0.001:
            self.position = pos
            self.queueUpdate()
            self.queueShadowUpdate()

    def setZ(self, z):
        if abs(z - self.position.z) > 0.001:
            self.position.z = z
            self.queueUpdate()
            self.queueShadowUpdate()

    def getBounds(self):
        """ Returns the bounds of this light for culling """
        return self.bounds

    def setColor(self, *args):
        """ Sets the color of the light """

        if len(args) == 0:
            col = args[0]
        else:
            col = Vec3(*args)

        self.color = col
        self.queueUpdate()

    def needsUpdate(self):
        """ Returns Whether the light data is up-to-date or needs an update """
        return self.dataNeedsUpdate

    def needsShadowUpdate(self):
        """ Returns Whether the light shadow map is up-to-date or needs an update """

        # no update needed if we have no shadows
        if not self.castShadows:
            return False

        for source in self.shadowSources:
            if not source.isValid():
                return True

        return False

    def queueUpdate(self):
        """ Queues a light update, that means in the next frame the light data will update """
        self.dataNeedsUpdate = True

    def queueShadowUpdate(self):
        """ Queues a shadow update, means invalidating all shadow sources and
        adding them to the shadow-map update queue, beeing processed as fast as
        possible """
        self.shadowNeedsUpdate = True

        for source in self.shadowSources:
            source.invalidate()

    def performUpdate(self):
        """ Recomputes the light data """
        self.dataNeedsUpdate = False
        self._computeAdditionalData()
        self._computeLightBounds()

        if self.debugEnabled:
            self._updateDebugNode()

        self.onPropertyChanged()

    def cleanup(self):
        """ Cleans up the light before it gets removed """
        self.debugNode.removeNode()

    def performShadowUpdate(self):
        """ Computes which shadow sources need an update and returns these """
        self._updateShadowSources()
        queued = []
        for source in self.shadowSources:
            if not source.isValid():
                queued.append(source)
        self.shadowNeedsUpdate = not len(queued) < 1
        return queued

    def attachDebugNode(self):
        """ Attachs a debug node to parent which shows the bounds of the light.
        VERY SLOW. USE ONLY FOR DEBUGGING """
        self.debugNode.reparentTo(render.find("RPLightDebugNodes"))
        self.debugEnabled = True
        self._updateDebugNode()

    def detachDebugNode(self):
        """ Detachs the debug node """
        self.debugEnabled = False
        self.debugNode.detach()

    def setSourceIndex(self, sourceId, index):
        """ Sets the global shadow source index for the given source """
        self.sourceIndexes[sourceId] = index

    def _addShadowSource(self, source):
        """ Adds a shadow source to the list of updating shadow sources """
        if source in self.shadowSources:
            self.warning("Shadow source already exists!")
            return

        self.shadowSources.append(source)
        self.queueShadowUpdate()

    def _computeLightBounds(self):
        """ Child classes should compute the light bounds here """
        raise NotImplementedError()

    def _updateDebugNode(self):
        """ Child classes should update the debug node here """
        raise NotImplementedError()

    def _computeAdditionalData(self):
        """ If child classes need to compute anything fancy, do it here """
        pass

    def getLightType(self):
        """ Child classes have to implement this and
        return the correct type """
        return LightType.NoType

    def _initShadowSources(self):
        """ Child classes should spawn their shadow sources here """
        raise NotImplementedError()

    def _updateShadowSources(self):
        """ Child classes should reposition their shadow sources here """
        raise NotImplementedError()

    def _createDebugLine(self, points, connectToEnd=False):
        """ Helper for visualizing the light bounds.
        Draws a line trough all points. When connectToEnd is true,
        the last point will get connected to the first point. """
        segs = LineSegs()
        segs.setThickness(1.0)
        segs.setColor(Vec4(1, 1, 0, 1))

        segs.moveTo(points[0])

        for point in points[1:]:
            segs.drawTo(point)

        if connectToEnd:
            segs.drawTo(points[0])

        return NodePath(segs.create())

    def __repr__(self):
        """ Generates a string representation of this instance """
        return "AbstractLight[]"