This was inspired by C64 DYCP effect, and maybe someone may find it useful. I am a programmer for a long time, but never before used python nor any 3D engine (that is, until a two days ago, when I downloaded Panda3D).
By the way, my (over-realistic at the moment) plan is to make a presentation tool similar to anark studio (3D real-time animation and presentations, available for win only), and I am currently examining possible tools to use (I’m also considering Blender’s game engine and Processing, but Panda3D looks most bromising so far). I made presentation for my master thesis using anark (I was sick of PowerPoint-like presentations), and I want to do similar thing for my PhD, but on Linux. I found nothing even close to anark (and it’s ease of use) on linux, so I decided (as a long-term goal) to make something like that (initialy, it would probably be just enough to finish presentation, probably even without any GUI).
Anyway, if you find something wrong in the code, please tell (I’m still learning). Example uses arial font, available as a package for Ubuntu 8.04 (msttcorefonts), and probably for other distributions. Of course, you can use any font you like (I started this on a win machine, but I was to lazy to change the font).
# -*- coding: UTF-8 -*-
#uncomment the following three lines for fullscreen (change resolution if needed)
#from pandac.PandaModules import loadPrcFileData
#loadPrcFileData("", "fullscreen 1")
#loadPrcFileData("", "win-size 1280 1024")
import direct.directbase.DirectStart
from direct.showbase.DirectObject import *
from pandac.PandaModules import *
from direct.task import Task
from direct.interval.IntervalGlobal import *
import math
import random
import sys
class JumpyText:
"A jumpy text effect class"
#Parameters are string (txt), coordinates (x,y,z), rotation (h,p,r), optional font (font)
#initialHidden - wether the text will be displayed before animation starts
#initialJump - wether the text will jump upwards at the start of animation
#attachTo - where to attach dummy node
#scale - should be set if 2d render is chosen
def __init__(self,txt,x,y,z,h,p,r, font=None, initialAmplitude=10, initialBounces=5, initialTime=3, initialHidden=1, initialJump=0, attachTo=render, scale = 1):
#dummy node path to store all letters in
self.nodePath = attachTo.attachNewNode("Dummy")
self.nodePath.setPos(x,y,z);
self.nodePath.setHpr(h,p,r);
#setup a standard TextNode containing the whole string
self.text = txt
self.textNode = TextNode('')
#apply font, if exists
if font is not None: self.textNode.setFont(font)
self.textNode.setText(txt)
self.textNodePath = self.nodePath.attachNewNode(self.textNode)
if initialHidden: self.textNodePath.hide()
self.textNodePath.setTwoSided(True)
self.textNodePath.setScale(scale)
#starting X position relative to dummy nodePath
startX = 0
#list for holding (letter node path, lerp intr) pairs
self.letters = [[0,0] for i in range(len(txt))]
#sequence for effect playing - second argument is parallel holding initial jump animation, third is for individual letters animation
self.jumpMove = Sequence(Func(self.oscilateStart),Parallel(),Parallel(),Func(self.oscilateEnd),Wait(1))
animIndex = 2
if not initialJump:
del self.jumpMove[1]
animIndex = 1
#generate individual animations
i=0
for ii in range(len(txt)):
#setup a standard TextNode containing only one letter
letter = TextNode(txt[ii])
letter.setText(txt[ii])
if font is not None: letter.setFont(font)
#skip blanks, but calculate their width
if not txt[ii] in (' ', '\t'):
#number of bounces to perform
bounces = initialBounces+random.randint(-int((initialBounces+1)*0.3),int((initialBounces+1)*0.3))
#bounce amplitude
amplitude = initialAmplitude+random.uniform(-initialAmplitude*0.3,initialAmplitude*0.3)
#total animation time
time = initialTime+random.uniform(-initialTime*0.5,0)
#create a node path for each letter
self.letters[i][0] = self.nodePath.attachNewNode(letter)
#set letter node path attributes
self.letters[i][0].setPos(startX,0,0)
self.letters[i][0].setTwoSided(True)
self.letters[i][0].setScale(scale)
self.letters[i][0].hide()
#create lerp intr using custom lerp function
self.letters[i][1] = LerpFunc(self.oscilateLetter, duration = time, extraArgs=[self.letters[i][0], amplitude, bounces])
self.jumpMove[1].append(LerpFunc(self.letters[i][0].setZ, duration = 0.2, fromData=0, toData=amplitude, blendType = 'easeOut'))
#add lerp intr to parallel from jumpMove
self.jumpMove[animIndex].append(self.letters[i][1])
#because we are skipping blanks, we must take care of current position in list
i = i+1
#if the letter is a blank, delete last element from list (I guess it's faster to delete a few elements, than to append list for every letter)
else: self.letters.pop()
#calculate X position for the next letter
startX=startX+letter.getWidth()*scale
#function to be executed at animation start
def oscilateStart(self):
#hide the whole string
self.textNodePath.hide()
#display individual letters
for i in range(len(self.letters)):
self.letters[i][0].show()
self.letters[i][0].setZ(0)
#lerp function for calculating Z position of individual letter
def oscilateLetter(self, value, letter, amplitude, bounces):
import math
#math.cos(...) part gives up-down motion, while amplitude*(1-value) part gives amplitude decreasing with time
letter.setZ(math.fabs(math.cos(value*bounces*math.pi)*amplitude*(1-value)))
#function to be executed at animation end
def oscilateEnd(self):
#hide individual letters
for i in range(len(self.letters)): self.letters[i][0].hide()
#show the whole string
self.textNodePath.show()
class World(DirectObject):
def __init__(self):
base.disableMouse()
base.camera.setPos(0,-60,0)
#define jumpy texts
self.jump1 = JumpyText("Jumpy text",10,1,15,0,0,180,font=fnt, initialJump=1)
self.jump2 = JumpyText("JMP jmp",-8,-40,-3,0,-80,0,font=fnt,initialAmplitude=20)
self.jump3 = JumpyText("Every day in every way I'm getting better and better.",-10,0,5,0,0,0,font=fnt,initialTime=5, initialHidden=0, initialJump=1)
#fourth text is attached to aspect2d (default is render)
self.jump4 = JumpyText("Commodore 64 - the best 8-bit home computer of all time - still alive",-1.25,0,-0.95,0,0,0, initialHidden=0, initialJump=1, attachTo=aspect2d, initialAmplitude = 0.3, scale = 0.1)
self.jump4.nodePath.setColor(0,0,0,1)
#additional animation for third text
#change text color
self.jump3.nodePath.setColor(0,0,0,1)
#initial scroll
self.intr0 = LerpPosInterval(self.jump3.nodePath, 2, Point3(-72,0,0), blendType='easeOut', startPos=Point3(-20,0,0))
#scroll left->right
self.intr1 = LerpPosInterval(self.jump3.nodePath, 3, Point3(-20,0,0), blendType='easeInOut', startPos=Point3(-72,0,0))
#scroll right->left
self.intr2 = LerpPosInterval(self.jump3.nodePath, 3, Point3(-72,0,0), blendType='easeInOut')
#scroll sequence
self.seq1 = Sequence(self.intr1,self.intr2)
#color animation - white flash
self.cintr1 = LerpColorInterval(self.jump3.nodePath, 0.1, VBase4(1,1,1,1))
#color animation - black color
self.cintr2 = LerpColorInterval(self.jump3.nodePath, 0.1, VBase4(0,0,0,1))
#color sequence
self.cseq1 = Sequence(Wait(2.0),self.cintr1,self.cintr2)
#start initial scroll animation
self.intr0.start()
#after two seconds, start the rest of animations
taskMgr.doMethodLater(2, self.anim, 'anim')
#escape key exits
self.accept('escape', sys.exit)
#task for starting animations (it's called only once - doMethodLater)
def anim(self,task):
#start scroll and color sequences for third text
self.seq1.loop()
self.cseq1.loop()
#start jumping animations in loop mode (start can be used for executing them only once)
self.jump1.jumpMove.loop()
self.jump2.jumpMove.loop()
self.jump3.jumpMove.loop()
self.jump4.jumpMove.loop()
#load font to be used
#windows path
#fnt = loader.loadFont('/c/WINDOWS/Fonts/arial.ttf')
#linux (Ubuntu 8.04) path
fnt = loader.loadFont('/usr/share/fonts/truetype/msttcorefonts/arial.ttf')
fnt.setPointSize(40)
fnt.setSpaceAdvance(1)
w = World()
run()