Code archives/3D Graphics - Effects/"Twirly" special FX demo

This code has been declared by its author to be Public Domain code.

Download source code

"Twirly" special FX demo by big10p2004
This was a special FX entity system I was working on about a year ago before I gave up on it after realizing that overlaying lots of blended layers on top of each other wasn't such a great idea (speed wise).

Anyway, I guess 'Twirlies' could still be used for certain things, like making a pretty screensaver, or something. They're pretty much infinately configurable as each can have it's own user defined CLUT which can be cycled etc.
; 
; Twirly Special FX demo by big10p (A.K.A. Chris Chadwick) 2003
;


	Graphics3D 800,600,32
	SetBuffer BackBuffer()

	WireFrame 0
	AntiAlias 0

	SeedRnd MilliSecs()

	Type twirly_layerT
		Field ent			; Layer star mesh entity.
		Field rot#			; Amount to rotate layer each update.
		Field clut_i%		; Index of layer's CLUT colour.
	End Type
	
	Type twirlyT
		Field tx#,ty#		; Demo-specific 2D twirly movement amounts.
		
		Field piv			; Control pivot - all layers are attached to this.
		Field hidden%		; Bool flag to indicate if Twirly is hidden.
		Field layer_bank	; Bank holding Handle() IDs of all layers.
		Field num_layers%	; Total number of Twirly layers.
		Field clut%			; clut_banks() index of Twirly's CLUT.
		Field max_clut_i%	; Bank index of last colour (RGB set) in CLUT.
		Field clut_cycle%	; CLUT cycle control. - left, 0 no cycle, + right.
		Field cycle_count%	; Count down to next CLUT cycle.
	End Type

	; Create 2 starfield planes.
	Global tex1 = CreateTexture(128,128,4)
	make_tex(tex1)
	plane1 = CreatePlane()
	RotateEntity plane1,-90,0,0
	PositionEntity plane1,0,0,10
	EntityTexture plane1,tex1
	Global tex2 = CreateTexture(128,128,4)
	make_tex(tex2)
	plane2 = CreatePlane()
	RotateEntity plane2,-90,0,0
	PositionEntity plane2,0,0,11
	EntityTexture plane2,tex2
	
	Global frame_count%
	Global fps%
	Global slowest_fps%
	Global fps_timeout%
	Global frame_time%
	Global slowest_frame%
	Global frame_start%
	fps_timer = CreateTimer(60)
	slowmo% = False
	wiref% = False
		
	Global cam = CreateCamera()
	PositionEntity cam,0,0,-20
	CameraZoom cam,1.4
	
	light = CreateLight()	
	
	Const END_OF_CLUT% = -1
	Const END_OF_CLUT_LIST% = -2
	Const MAX_CLUTS% = 100
	Global num_cluts% = 0
	
	Dim clut_banks(MAX_CLUTS-1)
	open_twirly()
	copy_clut_grad(1,5)

	; Create some twirlies.
	For n = 1 To 3
		tw1.twirlyT = create_preset_twirly(n)
		PositionEntity tw1\piv,Rnd(-5,5),Rnd(-5,5),-10	
	Next
	
	vp1# = 1 : vp2# = 1
		
	
	; --- Main loop ---
	
	While Not KeyHit(1)

		frame_start = MilliSecs()

		If KeyHit(28) Then slowmo = Not slowmo
		If KeyHit(14) Then wiref = Not wiref : WireFrame wiref

		For this.twirlyT = Each twirlyT
			CameraProject cam,EntityX(this\piv),EntityY(this\piv),EntityZ(this\piv)
			If ProjectedX() > 800 Or ProjectedX() < 0 Then this\tx=-this\tx
			If ProjectedY() > 600 Or ProjectedY() < 0 Then this\ty=-this\ty
			TranslateEntity this\piv,this\tx,this\ty,0
		Next
		
		vp1 = vp1 - 0.003
		vp2 = vp2 - 0.001
		PositionTexture tex1,1,vp1
		PositionTexture tex2,1,vp2
		
		update_twirly()
		RenderWorld
		;show_info()
		;show_clut(10,80)

		frame_time = MilliSecs() - frame_start	
		
		WaitTimer(fps_timer)
		Flip(1)

		If slowmo Then Delay 200
	Wend

	close_twirly()
	ClearWorld	

	End


; The Twirly CLUTS, defined in RGB sets.
.twirly_clut_data

Data 255,0,0
Data 0,255,0
Data 0,0,255
Data END_OF_CLUT

Data 255,255,0
Data 0,255,0
Data 0,255,255
Data 0,0,255
Data 255,0,255
Data 255,0,0
Data END_OF_CLUT

Data END_OF_CLUT_LIST


;
; Draws random coloured starfield onto text.
;
Function make_tex(tex)

	SetBuffer TextureBuffer(tex)

	For y = 0 To 127
		For x = 0 To 127
			WritePixel x,y,$00000000
		Next
	Next
	Color 255,255,255

	For n = 1 To 200
		r = Rand(0,255) Shl 16
		g = Rand(0,255) Shl 8
		b = Rand(0,255)
		WritePixel Rand(0,127),Rand(0,127),$ff000000 Or (r Or g Or b)
	Next
	ScaleTexture tex,30,30
	SetBuffer BackBuffer()
	
End Function


;
; Prepares the Twirly system ready for use.
;
Function open_twirly()

	Local r%,g%,b%
	Local clut_size%[MAX_CLUTS-1]
	Local cs% = 0
		
	; Count CLUT entries and size.
	Restore twirly_clut_data
	Repeat
		Read r
		If r = END_OF_CLUT
			clut_size[num_cluts] = cs
			num_cluts = num_cluts + 1
			cs = 0
			
			Read r : If r = END_OF_CLUT_LIST Then Exit
		EndIf
		Read g
		Read b
		
		cs = cs + 1
	Forever
	
	; Create CLUT banks.
	Restore twirly_clut_data
	For i = 0 To num_cluts-1
		clut_banks(i) = CreateBank((clut_size[i]*3)*4)
	
		For n = 0 To BankSize(clut_banks(i))-1 Step 12
			Read r,g,b
			PokeInt clut_banks(i),n,r
			PokeInt clut_banks(i),n+4,g
			PokeInt clut_banks(i),n+8,b
		Next
		
		Read dummy	; Skip END_OF_CLUT terminator.
	Next
	
End Function


;
; Closes down the Twirly system, freeing all resources.
;
Function close_twirly()

	; Kill all Twirlies.
	For this.twirlyT = Each twirlyT
		free_twirly(this)
	Next

	; Free the CLUT banks.
	For i = 0 To num_cluts-1
		FreeBank clut_banks(i)
	Next
	
End Function


;
; Frees all resources used by a given twirly.
;
; Params:
; twirly - The twirly to be freed.
;
Function free_twirly(twirly.twirlyT)

	; Free all Twirly layer resources.
	For n = 0 To BankSize(twirly\layer_bank)-1 Step 4
		layer.twirly_layerT = Object.twirly_layerT(PeekInt(twirly\layer_bank,n))
		FreeEntity layer\ent
		Delete layer	
	Next
	
	; Free actual Twirly resources.
	FreeEntity twirly\piv
	FreeBank layer_bank
	Delete twirly
	
End Function


;
; Creates a single layer twirly. See create_star_mesh() for params.
;	
Function create_twirly.twirlyT(radius#,points%,indent#,ratio%=True)
	
	twirly.twirlyT = New twirlyT
	twirly\piv = CreatePivot()
	RotateEntity twirly\piv,0,180,0	; So we can point twirly at camera.
	twirly\num_layers = 1
	twirly\hidden = False

	twirly\tx = Rnd(.01,.03) : If Rand(0,1) Then twirly\tx = -twirly\tx
	twirly\ty = Rnd(.01,.03) : If Rand(0,1) Then twirly\ty = -twirly\ty
	
	layer.twirly_layerT = New twirly_layerT
	twirly\layer_bank = CreateBank(4)
	PokeInt twirly\layer_bank,0,Handle(layer)
	
	; Create initial twirly layer.
	layer\ent = create_star_mesh(radius,points,indent,ratio)
	EntityParent layer\ent,twirly\piv
	PositionEntity layer\ent,0,0,0,0
	EntityFX layer\ent,1+4+8

	Return twirly

End Function


;
; Adds a layer to the specified, existing twirly.
; See create_star_mesh() for other params.
;
Function add_twirly_layer(twirly.twirlyT,radius#,points%,indent#,ratio%=True)

	layer.twirly_layerT = New twirly_layerT
	bs = BankSize(twirly\layer_bank)
	ResizeBank twirly\layer_bank,bs+4
	PokeInt twirly\layer_bank,bs,Handle(layer)
	
	; Create twirly layer.
	layer\ent = create_star_mesh(radius,points,indent,ratio)
	PositionEntity layer\ent,0,0,Float(twirly\num_layers)*0.001
	EntityParent layer\ent,twirly\piv,1

	twirly\num_layers = twirly\num_layers + 1
	EntityFX layer\ent,1+4+8

End Function


;
; Sequentially 'paints' all layers of a twirly with the given CLUT.
;
; Params:
; twirly - Twirly to be coloured with the specified CLUT. 
; clut   - Number of CLUT to colour twirly with.
; cycle  - Specifies how to cycle the CLUT every time the twirly is updated:
;          0 (default) to not cycle the CLUT.
;          -1 to cycle the CLUT to the left.
;          1 to cycle the CLUT to the right.
; dir    - Direction to colour the twirly layers with:
;          0 (default) to colour layers from first-to-last.
;          1 to colour layers from last-to-first.
;
Function set_twirly_clut(twirly.twirlyT,clut%,cycle%=0,dir%=0)

	clut_i% = 0
	clut_size% = BankSize(clut_banks(clut))	
	cb = clut_banks(clut)
	
	If dir = 0
		For n = 0 To twirly\num_layers-1
			id = PeekInt(twirly\layer_bank,n*4)
			layer.twirly_layerT = Object.twirly_layerT(id)
			layer\clut_i = clut_i
			EntityColor layer\ent,PeekInt(cb,clut_i),PeekInt(cb,clut_i+4),PeekInt(cb,clut_i+8)
			clut_i = clut_i + 12
			If clut_i = clut_size Then clut_i = 0
		Next	
	Else
		For n = twirly\num_layers-1 To 0 Step -1
			id = PeekInt(twirly\layer_bank,n*4)
			layer.twirly_layerT = Object.twirly_layerT(id)
			layer\clut_i = clut_i
			EntityColor layer\ent,PeekInt(cb,clut_i),PeekInt(cb,clut_i+4),PeekInt(cb,clut_i+8)
			clut_i = clut_i + 12
			If clut_i = clut_size Then clut_i = 0
		Next	
	EndIf
	
	twirly\clut = clut
	twirly\clut_cycle = cycle
	twirly\cycle_count = Abs(cycle)
	twirly\max_clut_i = clut_size-12
	
End Function


;
; Sets all layers of a twirly to use a given blend mode.
;
Function set_twirly_blend(twirly.twirlyT,mode%)

	For n = 0 To BankSize(twirly\layer_bank)-1 Step 4
		layer.twirly_layerT = Object.twirly_layerT(PeekInt(twirly\layer_bank,n))
		EntityBlend layer\ent,mode
	Next
	
End Function


;
; Sets all layers of a twirly to use a given alpha level.
;
Function set_twirly_alpha(twirly.twirlyT,alpha#)

	For n = 0 To BankSize(twirly\layer_bank)-1 Step 4
		layer.twirly_layerT = Object.twirly_layerT(PeekInt(twirly\layer_bank,n))
		EntityAlpha layer\ent,alpha
	Next
	
End Function


;
; Creates a copy of an existing CLUT with gradients between each colour.
;
; Params:
; clut   - Number of CLUT to be copied.
; grads  - Number of gradients to insert between each colour.
; grad_r - Each colour from the copied CLUT is faded to this colour.
; grad_g   If not specified (default), colours fade to the next colour
; grad_b   from the CLUT being copied.
; 
; Returns:
; Index of new clut in clut_banks() array.
;
Function copy_clut_grad(clut%,grads%,grad_r#=-1,grad_g#=-1,grad_b#=-1)

	old_clut = clut_banks(clut)
	old_size = BankSize(clut_banks(clut))

	new_clut = CreateBank(old_size*(grads+1))
	clut_banks(num_cluts) = new_clut
	num_cluts = num_cluts + 1
	
	poke_i = 0
	
	If grad_r=-1 Or grads=0
		; Create gradients between adjacent colours from the copied CLUT.

		grad_div = grads + 1
		
		For i = 0 To old_size-1 Step 12
			r1# = Float(PeekInt(old_clut,i))
			g1# = Float(PeekInt(old_clut,i+4))
			b1# = Float(PeekInt(old_clut,i+8))
	
			; Get colour to fade to.
			If i = old_size-12
				; Wraparound to first colour.
				r2# = Float(PeekInt(old_clut,0))
				g2# = Float(PeekInt(old_clut,4))
				b2# = Float(PeekInt(old_clut,8))
			Else
				; Use next colour in CLUT.
				r2# = Float(PeekInt(old_clut,i+12))
				g2# = Float(PeekInt(old_clut,i+12+4))
				b2# = Float(PeekInt(old_clut,i+12+8))
			EndIf
					
			r_step# = (r2-r1)/grad_div
			g_step# = (g2-g1)/grad_div
			b_step# = (b2-b1)/grad_div
			
			; Add original colour and gradients to new CLUT.
			For n = 0 To grads
				PokeInt new_clut,poke_i,  Int(r1 + (n*r_step))
				PokeInt new_clut,poke_i+4,Int(g1 + (n*g_step))
				PokeInt new_clut,poke_i+8,Int(b1 + (n*b_step))
				poke_i = poke_i + 12
			Next
		Next
	Else
		; Create gradients from copied CLUT colour to grad_r,grad_g,grad_b.
		
		For i = 0 To old_size-1 Step 12
			r1# = Float(PeekInt(old_clut,i))
			g1# = Float(PeekInt(old_clut,i+4))
			b1# = Float(PeekInt(old_clut,i+8))
		
			r_step# = (grad_r-r1)/grads
			g_step# = (grad_g-g1)/grads
			b_step# = (grad_b-b1)/grads
			
			; Add original colour and gradients to new CLUT.
			For n = 0 To grads
				PokeInt new_clut,poke_i,  Int(r1 + (n*r_step))
				PokeInt new_clut,poke_i+4,Int(g1 + (n*g_step))
				PokeInt new_clut,poke_i+8,Int(b1 + (n*b_step))
				poke_i = poke_i + 12
			Next
		Next	
	EndIf
	
	Return num_cluts - 1
			
End Function


;
; Updates ALL non-hidden Twirlies.
;
Function update_twirly()

	For this.twirlyT = Each twirlyT
		If Not this\hidden	
			PointEntity this\piv,cam
			cb = clut_banks(this\clut)
	
			; Twirly's CLUT cycle frequency management.
			do_cycle = False
			If this\clut_cycle
				this\cycle_count = this\cycle_count - 1
				If this\cycle_count = 0
					this\cycle_count = Abs(this\clut_cycle)
					do_cycle = True
				EndIf
			EndIf
			
			If do_cycle
				; Update each layer attached to this twirly, cycling the CLUT.
				For n = 0 To BankSize(this\layer_bank)-1 Step 4
					layer.twirly_layerT = Object.twirly_layerT(PeekInt(this\layer_bank,n))
					TurnEntity layer\ent,0,0,layer\rot
			
					If this\clut_cycle < 0
						; Cycle CLUT to the left.
						clut_i = layer\clut_i + 12
						If clut_i > this\max_clut_i Then clut_i = 0
					Else
						; Cycle CLUT to the right.
						clut_i = layer\clut_i - 12
						If clut_i < 0 Then clut_i = this\max_clut_i
					EndIf
		
					layer\clut_i = clut_i
					EntityColor layer\ent,PeekInt(cb,clut_i),PeekInt(cb,clut_i+4),PeekInt(cb,clut_i+8)
				Next
			Else
				; Update each layer attached to this twirly.
				For n = 0 To BankSize(this\layer_bank)-1 Step 4
					layer.twirly_layerT = Object.twirly_layerT(PeekInt(this\layer_bank,n))
					TurnEntity layer\ent,0,0,layer\rot			
				Next
			EndIf

		End If
	Next
		
End Function


;
; Creates a planar, star-shaped mesh.
;
; Params:
; radius - Radius of star in world units.
; points - Number of points the star should have.
; indent - Sets the 'depth' of the indent between the points of the star.
; ratio  - True (default) to indicate that indent is a ratio of the radius
;          e.g. 1=no indent (circle), .5=indent midway between point and centre.
;          False to indicate that indent is an absolute size.
;
; Returns:
; The newly created star mesh.
;
Function create_star_mesh(radius#=1.0,points%=5,indent#=0.5,ratio%=True)

	If ratio Then indent = radius * indent	; Convert indent to ratio of radius.

	mesh = CreateMesh()
	surf = CreateSurface(mesh)
	
	verts% = points * 2
	vang# = 0
	vang_step# = 360.0/Float(verts)

	; Add verts for all star points & indents.
	For n = 0 To verts-1

		If (n And 1) Then size# = indent Else size# = radius

		AddVertex(surf,Cos(vang)*size,Sin(vang)*size,0)
		VertexNormal surf,n,0,0,-1	

		vang = vang + vang_step
	Next	

	; Add centre vert.
	AddVertex(surf,0,0,0)
	VertexNormal surf,verts,0,0,-1
	
	; Make triangles.
	For n = 0 To verts-2
		AddTriangle surf,n,verts,n+1
	Next
	AddTriangle surf,verts-1,verts,0

	Return mesh
	
End Function


;
; Creates a twirly_type preset twirly.
;
Function create_preset_twirly.twirlyT(twirly_type%)

	Select twirly_type
		Case 1
			dir#=.01
			For n = 1 To 30
				If n=1
					tw.twirlyT = create_twirly(n*.01,10,.2)
				Else
					add_twirly_layer(tw,n*.1,10,.1)
				EndIf
				set_layer_rotate(tw,n-1,dir*n)
			Next
			set_twirly_clut(tw,2,2,1)
			set_twirly_blend(tw,3)
			set_twirly_alpha(tw,.2)
			Return tw
		Case 2
			dir#=.01
			For n = 1 To 30
				If n=1
					tw.twirlyT = create_twirly(n*.01,5,.2)
				Else
					add_twirly_layer(tw,n*.1,10,.3)
				EndIf
				set_layer_rotate(tw,n-1,dir*n)
				dir = -dir
			Next
			set_twirly_clut(tw,2,2,1)
			set_twirly_blend(tw,3)
			set_twirly_alpha(tw,.2)
			Return tw
		Case 3
			dir#=.01
			For n = 1 To 30
				If n=1
					tw.twirlyT = create_twirly(n*.01,5,.2)
				Else
					add_twirly_layer(tw,n*.1,5,.3)
				EndIf
				set_layer_rotate(tw,n-1,dir*n)
			Next
			set_twirly_clut(tw,2,2,1)
			set_twirly_blend(tw,3)
			set_twirly_alpha(tw,.2)
			Return tw
	End Select		
	
End Function


;
; Hides a Twirly from view. Ignores if Twirly is already hidden.
;
; Params:
; twirly - The Twirly to hide from view.
;
Function hide_twirly(twirly.twirlyT)

	If twirly\hidden = False
		HideEntity twirly\piv
		twirly\hidden = True
	End If
	
End Function


;
; Makes a hidden Twirly visible. Ignores if Twirly is already visible.
;
; Params:
; twirly - The hidden Twirly to make visible.
;
Function show_twirly(twirly.twirlyT)

	If twirly\hidden = True
		ShowEntity twirly\piv
		twirly\hidden = False
	End If
	
End Function


;
; Sets a given twirly layer to an absolute rotation.
;
Function rotate_twirly_layer(twirly.twirlyT,layer%,rot#)

	star.twirly_layerT = Object.twirly_layerT(PeekInt(twirly\layer_bank,layer*4))
	RotateEntity star\ent,0,0,rot,1

End Function


;
; Sets how much a given twirly layer should rotate by every update.
;
Function set_layer_rotate(twirly.twirlyT,layer%,rot#)

	star.twirly_layerT = Object.twirly_layerT(PeekInt(twirly\layer_bank,layer*4))
	star\rot = rot

End Function


;
; Debug function to visually display CLUT(s).
;
; Params:
; cx   - Screen X coord to display CLUT at.
; cy   - Screen Y coord to display CLUT at.
; clut - Number of CLUT to display.
;        If not specified (default) then all CLUTS are displayed.
;
Function show_clut(cx%=0,cy%=0,clut%=-1)

	Local cxs = cx
	Local cw = 10
	Local ch = 10
	Local gap = 0
	
	If clut = -1
		first_clut = 0
		last_clut = num_cluts - 1
	Else
		first_clut = clut
		last_clut = clut
	EndIf
		
	For n = first_clut To last_clut
		clut = clut_banks(n)
		
		For j = 0 To BankSize(clut)-1 Step 12
			r = PeekInt(clut,j)
			g = PeekInt(clut,j+4)
			b = PeekInt(clut,j+8)
			Color r,g,b
			Rect cx,cy,cw,ch,1
			cx = cx + cw + gap
			If (cx+cw-1)>=GraphicsWidth() Then cx=cxs:cy=cy+ch+gap
		Next
	
		cx = cxs
		cy = cy + ch + 4
	Next

	Color 255,255,255
	
End Function


;
; Display debug info.
;
Function show_info()
	
	If fps_timeout
		frame_count = frame_count + 1

		If MilliSecs() > fps_timeout Then
			fps_timeout = MilliSecs() + 1000 
			fps = frame_count 
			frame_count = 0 
		
			If fps < slowest_fps Or slowest_fps = 0 Then slowest_fps = fps
		EndIf 
		
		If frame_time > slowest_frame Then slowest_frame = frame_time
		
		Color 0,255,0
		Text 10,10," Triangles: " + TrisRendered()
		Color 255,255,0
		Text 10,25," Millisecs: " + frame_time
		Text 10,40,"   Slowest: " + slowest_frame
		Color 0,255,255
		Text 10,55,"       FPS: " + fps
		Text 10,70,"     Worst: " + slowest_fps
		Color 255,255,255
	Else
		; First call initialization.
		fps_timeout = MilliSecs() + 1000 
	EndIf
	
End Function

Comments

Clyde2004
Wicked stuff there mate!


Rook Zimbabwe2006
This has to be the coolest thing!


big10p2006
Crikey, this is an old one. Glad you like it, though. :)


Yo! Wazzup?2007
Oooohhhhh... pretty lights...


Code Archives Forum