"""This module defines various transition effects that can be used to
graphically transition between two scenes, such as by fading the screen to
a particular color."""
__all__ = ['Transitions']
from panda3d.core import *
from direct.showbase import ShowBaseGlobal
from direct.showbase.MessengerGlobal import messenger
from direct.gui.DirectGui import DirectFrame
from direct.gui import DirectGuiGlobals as DGG
from direct.interval.LerpInterval import LerpColorScaleInterval, LerpColorInterval, LerpScaleInterval, LerpPosInterval
from direct.interval.MetaInterval import Sequence, Parallel
from direct.interval.FunctionInterval import Func
[docs]class Transitions:
# These may be reassigned before the fade or iris transitions are
# actually invoked to change the models that will be used.
IrisModelName = "models/misc/iris"
FadeModelName = "models/misc/fade"
[docs] def __init__(self, loader,
model=None,
scale=3.0,
pos=Vec3(0, 0, 0)):
self.transitionIval = None
self.__transitionFuture = None
self.letterboxIval = None
self.__letterboxFuture = None
self.iris = None
self.fade = None
self.letterbox = None
self.fadeModel = model
self.imagePos = pos
if model:
self.alphaOff = Vec4(1, 1, 1, 0)
self.alphaOn = Vec4(1, 1, 1, 1)
model.setTransparency(1)
self.lerpFunc = LerpColorScaleInterval
else:
self.alphaOff = Vec4(0, 0, 0, 0)
self.alphaOn = Vec4(0, 0, 0, 1)
self.lerpFunc = LerpColorInterval
self.irisTaskName = "irisTask"
self.fadeTaskName = "fadeTask"
self.letterboxTaskName = "letterboxTask"
def __del__(self):
if self.fadeModel:
self.fadeModel.removeNode()
self.fadeModel = None
##################################################
# Fade
##################################################
# We can set a custom model for the fade before using it for the first time
[docs] def setFadeModel(self, model, scale=1.0):
self.fadeModel = model
# We have to change some default parameters for a custom fadeModel
self.alphaOn = Vec4(1, 1, 1, 1)
# Reload fade if its already been created
if self.fade:
self.fade.destroy()
self.fade = None
self.loadFade()
[docs] def loadFade(self):
if self.fade is None:
# We create a DirectFrame for the fade polygon, instead of
# simply loading the polygon model and using it directly,
# so that it will also obscure mouse events for objects
# positioned behind it.
self.fade = DirectFrame(
parent = ShowBaseGlobal.hidden,
guiId = 'fade',
relief = None,
image = self.fadeModel,
image_scale = (4, 2, 2),
state = DGG.NORMAL,
)
if not self.fadeModel:
# No fade model was given, so we make this the fade model.
self.fade["relief"] = DGG.FLAT
self.fade["frameSize"] = (-2, 2, -1, 1)
self.fade["frameColor"] = (0, 0, 0, 1)
self.fade.setTransparency(TransparencyAttrib.MAlpha)
self.fade.setBin('unsorted', 0)
self.fade.setColor(0,0,0,0)
self.fade.setScale(max(base.a2dRight, base.a2dTop))
[docs] def getFadeInIval(self, t=0.5, finishIval=None, blendType='noBlend'):
"""
Returns an interval without starting it. This is particularly useful in
cutscenes, so when the cutsceneIval is escaped out of we can finish the fade immediately
"""
#self.noTransitions() masad: this creates a one frame pop, is it necessary?
self.loadFade()
transitionIval = Sequence(Func(self.fade.reparentTo, ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX),
Func(self.fade.showThrough), # in case aspect2d is hidden for some reason
self.lerpFunc(self.fade, t,
self.alphaOff,
# self.alphaOn,
blendType=blendType
),
Func(self.fade.detachNode),
name = self.fadeTaskName,
)
if finishIval:
transitionIval.append(finishIval)
return transitionIval
[docs] def getFadeOutIval(self, t=0.5, finishIval=None, blendType='noBlend'):
"""
Create a sequence that lerps the color out, then
parents the fade to hidden
"""
self.noTransitions()
self.loadFade()
transitionIval = Sequence(Func(self.fade.reparentTo, ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX),
Func(self.fade.showThrough), # in case aspect2d is hidden for some reason
self.lerpFunc(self.fade, t,
self.alphaOn,
# self.alphaOff,
blendType=blendType
),
name = self.fadeTaskName,
)
if finishIval:
transitionIval.append(finishIval)
return transitionIval
[docs] def fadeIn(self, t=0.5, finishIval=None, blendType='noBlend'):
"""
Play a fade in transition over t seconds.
Places a polygon on the aspect2d plane then lerps the color
from black to transparent. When the color lerp is finished, it
parents the fade polygon to hidden.
"""
gsg = base.win.getGsg()
if gsg:
# If we're about to fade in from black, go ahead and
# preload all the textures etc.
base.graphicsEngine.renderFrame()
base.render.prepareScene(gsg)
base.render2d.prepareScene(gsg)
if t == 0:
# Fade in immediately with no lerp
#print "transitiosn: fadeIn 0.0"
self.noTransitions()
self.loadFade()
self.fade.detachNode()
fut = AsyncFuture()
fut.setResult(None)
return fut
else:
# Create a sequence that lerps the color out, then
# parents the fade to hidden
self.transitionIval = self.getFadeInIval(t, finishIval, blendType)
self.transitionIval.append(Func(self.__finishTransition))
self.__transitionFuture = AsyncFuture()
self.transitionIval.start()
return self.__transitionFuture
[docs] def fadeOut(self, t=0.5, finishIval=None, blendType='noBlend'):
"""
Play a fade out transition over t seconds.
Places a polygon on the aspect2d plane then lerps the color
from transparent to full black. When the color lerp is finished,
it leaves the fade polygon covering the aspect2d plane until you
fadeIn or call noFade.
lerp
"""
if t == 0:
# Fade out immediately with no lerp
self.noTransitions()
self.loadFade()
self.fade.reparentTo(ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX)
self.fade.setColor(self.alphaOn)
elif ConfigVariableBool('no-loading-screen', False):
if finishIval:
self.transitionIval = finishIval
self.transitionIval.start()
else:
# Create a sequence that lerps the color out, then
# parents the fade to hidden
self.transitionIval = self.getFadeOutIval(t, finishIval, blendType)
self.transitionIval.append(Func(self.__finishTransition))
self.__transitionFuture = AsyncFuture()
self.transitionIval.start()
return self.__transitionFuture
# Immediately done, so return a dummy future.
fut = AsyncFuture()
fut.setResult(None)
return fut
[docs] def fadeOutActive(self):
return self.fade and self.fade.getColor()[3] > 0
[docs] def fadeScreen(self, alpha=0.5):
"""
Put a semitransparent screen over the camera plane
to darken out the world. Useful for drawing attention to
a dialog box for instance
"""
#print "transitiosn: fadeScreen"
self.noTransitions()
self.loadFade()
self.fade.reparentTo(ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX)
self.fade.setColor(self.alphaOn[0],
self.alphaOn[1],
self.alphaOn[2],
alpha)
[docs] def fadeScreenColor(self, color):
"""
Put a semitransparent screen over the camera plane
to darken out the world. Useful for drawing attention to
a dialog box for instance
"""
#print "transitiosn: fadeScreenColor"
self.noTransitions()
self.loadFade()
self.fade.reparentTo(ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX)
self.fade.setColor(color)
[docs] def noFade(self):
"""
Removes any current fade tasks and parents the fade polygon away
"""
#print "transitiosn: noFade"
if self.transitionIval:
self.transitionIval.pause()
self.transitionIval = None
if self.__transitionFuture:
self.__transitionFuture.cancel()
self.__transitionFuture = None
if self.fade:
# Make sure to reset the color, since fadeOutActive() is looking at it
self.fade.setColor(self.alphaOff)
self.fade.detachNode()
[docs] def setFadeColor(self, r, g, b):
self.alphaOn.set(r, g, b, 1)
self.alphaOff.set(r, g, b, 0)
##################################################
# Iris
##################################################
[docs] def loadIris(self):
if self.iris is None:
self.iris = base.loader.loadModel(self.IrisModelName)
self.iris.setPos(0, 0, 0)
[docs] def irisIn(self, t=0.5, finishIval=None, blendType = 'noBlend'):
"""
Play an iris in transition over t seconds.
Places a polygon on the aspect2d plane then lerps the scale
of the iris polygon up so it looks like we iris in. When the
scale lerp is finished, it parents the iris polygon to hidden.
"""
self.noTransitions()
self.loadIris()
if t == 0:
self.iris.detachNode()
fut = AsyncFuture()
fut.setResult(None)
return fut
else:
self.iris.reparentTo(ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX)
scale = 0.18 * max(base.a2dRight, base.a2dTop)
self.transitionIval = Sequence(LerpScaleInterval(self.iris, t,
scale = scale,
startScale = 0.01,
blendType=blendType),
Func(self.iris.detachNode),
Func(self.__finishTransition),
name = self.irisTaskName,
)
self.__transitionFuture = AsyncFuture()
if finishIval:
self.transitionIval.append(finishIval)
self.transitionIval.start()
return self.__transitionFuture
[docs] def irisOut(self, t=0.5, finishIval=None, blendType='noBlend'):
"""
Play an iris out transition over t seconds.
Places a polygon on the aspect2d plane then lerps the scale
of the iris down so it looks like we iris out. When the scale
lerp is finished, it leaves the iris polygon covering the
aspect2d plane until you irisIn or call noIris.
"""
self.noTransitions()
self.loadIris()
self.loadFade() # we need this to cover up the hole.
if t == 0:
self.iris.detachNode()
self.fadeOut(0)
fut = AsyncFuture()
fut.setResult(None)
return fut
else:
self.iris.reparentTo(ShowBaseGlobal.aspect2d, DGG.FADE_SORT_INDEX)
scale = 0.18 * max(base.a2dRight, base.a2dTop)
self.transitionIval = Sequence(LerpScaleInterval(self.iris, t,
scale = 0.01,
startScale = scale,
blendType=blendType),
Func(self.iris.detachNode),
# Use the fade to cover up the hole that the iris would leave
Func(self.fadeOut, 0),
Func(self.__finishTransition),
name = self.irisTaskName,
)
self.__transitionFuture = AsyncFuture()
if finishIval:
self.transitionIval.append(finishIval)
self.transitionIval.start()
return self.__transitionFuture
[docs] def noIris(self):
"""
Removes any current iris tasks and parents the iris polygon away
"""
if self.transitionIval:
self.transitionIval.pause()
self.transitionIval = None
if self.iris is not None:
self.iris.detachNode()
# Actually we need to remove the fade too,
# because the iris effect uses it.
self.noFade()
[docs] def noTransitions(self):
"""
This call should immediately remove any and all transitions running
"""
self.noFade()
self.noIris()
# Letterbox is not really a transition, it is a screen overlay
# self.noLetterbox()
def __finishTransition(self):
if self.__transitionFuture:
self.__transitionFuture.setResult(None)
self.__transitionFuture = None
##################################################
# Letterbox
##################################################
[docs] def loadLetterbox(self):
if not self.letterbox:
# We create a DirectFrame for the fade polygon, instead of
# simply loading the polygon model and using it directly,
# so that it will also obscure mouse events for objects
# positioned behind it.
self.letterbox = NodePath("letterbox")
# Allow fade in and out of the bars
self.letterbox.setTransparency(1)
# Allow DirectLabels to be parented to the letterbox sensibly
self.letterbox.setBin('unsorted', 0)
# Allow a custom look to the letterbox graphic.
# TODO: This model isn't available everywhere. We should
# pass it in as a parameter.
button = base.loader.loadModel('models/gui/toplevel_gui',
okMissing = True)
barImage = None
if button:
barImage = button.find('**/generic_button')
self.letterboxTop = DirectFrame(
parent = self.letterbox,
guiId = 'letterboxTop',
relief = DGG.FLAT,
state = DGG.NORMAL,
frameColor = (0, 0, 0, 1),
borderWidth = (0, 0),
frameSize = (-1, 1, 0, 0.2),
pos = (0, 0, 1.0),
image = barImage,
image_scale = (2.25,1,.5),
image_pos = (0,0,.1),
image_color = (0.3,0.3,0.3,1),
sortOrder = 0,
)
self.letterboxBottom = DirectFrame(
parent = self.letterbox,
guiId = 'letterboxBottom',
relief = DGG.FLAT,
state = DGG.NORMAL,
frameColor = (0, 0, 0, 1),
borderWidth = (0, 0),
frameSize = (-1, 1, 0, 0.2),
pos = (0, 0, -1.2),
image = barImage,
image_scale = (2.25,1,.5),
image_pos = (0,0,.1),
image_color = (0.3,0.3,0.3,1),
sortOrder = 0,
)
# masad: always place these at the bottom of render
self.letterboxTop.setBin('sorted',0)
self.letterboxBottom.setBin('sorted',0)
self.letterbox.reparentTo(ShowBaseGlobal.render2d, -1)
self.letterboxOff(0)
[docs] def noLetterbox(self):
"""
Removes any current letterbox tasks and parents the letterbox polygon away
"""
if self.letterboxIval:
self.letterboxIval.pause()
self.letterboxIval = None
if self.__letterboxFuture:
self.__letterboxFuture.cancel()
self.__letterboxFuture = None
if self.letterbox:
self.letterbox.stash()
def __finishLetterbox(self):
if self.__letterboxFuture:
self.__letterboxFuture.setResult(None)
self.__letterboxFuture = None
[docs] def letterboxOn(self, t=0.25, finishIval=None, blendType='noBlend'):
"""
Move black bars in over t seconds.
"""
self.noLetterbox()
self.loadLetterbox()
self.letterbox.unstash()
if t == 0:
self.letterboxBottom.setPos(0, 0, -1)
self.letterboxTop.setPos(0, 0, 0.8)
fut = AsyncFuture()
fut.setResult(None)
return fut
else:
self.__letterboxFuture = AsyncFuture()
self.letterboxIval = Sequence(Parallel(
LerpPosInterval(self.letterboxBottom,
t,
pos = Vec3(0, 0, -1),
#startPos = Vec3(0, 0, -1.2),
blendType=blendType
),
LerpPosInterval(self.letterboxTop,
t,
pos = Vec3(0, 0, 0.8),
# startPos = Vec3(0, 0, 1),
blendType=blendType
),
),
Func(self.__finishLetterbox),
name = self.letterboxTaskName,
)
if finishIval:
self.letterboxIval.append(finishIval)
self.letterboxIval.start()
return self.__letterboxFuture
[docs] def letterboxOff(self, t=0.25, finishIval=None, blendType='noBlend'):
"""
Move black bars away over t seconds.
"""
self.noLetterbox()
self.loadLetterbox()
self.letterbox.unstash()
if t == 0:
self.letterbox.stash()
fut = AsyncFuture()
fut.setResult(None)
return fut
else:
self.__letterboxFuture = AsyncFuture()
self.letterboxIval = Sequence(Parallel(
LerpPosInterval(self.letterboxBottom,
t,
pos = Vec3(0, 0, -1.2),
# startPos = Vec3(0, 0, -1),
blendType=blendType
),
LerpPosInterval(self.letterboxTop,
t,
pos = Vec3(0, 0, 1),
# startPos = Vec3(0, 0, 0.8),
blendType=blendType
),
),
Func(self.letterbox.stash),
Func(self.__finishLetterbox),
Func(messenger.send, 'letterboxOff'),
name = self.letterboxTaskName,
)
if finishIval:
self.letterboxIval.append(finishIval)
self.letterboxIval.start()
return self.__letterboxFuture