Jumpy text

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()

nice effect, i like the ones coming from the left!
c64 also takes me to the best timeplace of myself :slight_smile:

technology has to be fun, if not it becomes a weapon!
c