Type TAnimation

	' MiniB3D functions
	
	' Internal - not recommended for general use

	Function AnimateMesh(ent1:TEntity,framef#,start_frame,end_frame)
		
		If TMesh(ent1)<>Null
			
			If TMesh(ent1).anim=False Then Return ' mesh contains no anim data
	
			TMesh(ent1).anim_render=True
	
			Local vm[TMesh(ent1).GetSurface(1).CountVertices()] ' vertex moved
	
			' cap framef values
			If framef>end_frame Then framef=end_frame
			If framef<start_frame Then framef=start_frame
			
			Local frame=framef ' float to int
	
			For Local bent:TBone=EachIn TMesh(ent1).bone_list
					
				Local i=0
				Local ii=0
				Local fd1#=0 ' anim time since last key
				Local fd2#=0 ' anim time until next key
				Local found=False
				Local no_keys=False
				Local w1#
				Local x1#
				Local y1#
				Local z1#
				Local w2#
				Local x2#
				Local y2#
				Local z2#
				
				Local flag=0
				
				' position
						
				' backwards
				i=frame+1
				Repeat
					i=i-1
					flag=TBone(bent).keys.flags[i]&1
					If flag
						x1#=TBone(bent).keys.px[i]
						y1#=TBone(bent).keys.py[i]
						z1#=TBone(bent).keys.pz[i]
						fd1=framef-i
						found=True
					EndIf
					If i<=start_frame Then i=end_frame+1;ii=ii+1
				Until found=True Or ii>=2
				If found=False Then no_keys=True
				found=False
				ii=0
				
				' forwards
				i=frame
				Repeat
					i=i+1
					If i>end_frame Then i=start_frame;ii=ii+1
					flag=TBone(bent).keys.flags[i]&1
					If flag
						x2#=TBone(bent).keys.px[i]
						y2#=TBone(bent).keys.py[i]
						z2#=TBone(bent).keys.pz[i]
						fd2=i-framef
						found=True
					EndIf
				Until found=True Or ii>=2
				If found=False Then no_keys=True
				found=False
				ii=0
		
				Local px3#=0
				Local py3#=0
				Local pz3#=0
				If no_keys=True ' no keyframes
					px3#=TBone(bent).n_px#
					py3#=TBone(bent).n_py#
					pz3#=-TBone(bent).n_pz#
				Else
					If fd1+fd2=0.0 ' one keyframe
						' if only one keyframe, fd1+fd2 will equal 0 resulting in division error and garbage positional values (which can affect children)
						' so we check for this, and if true then positional values equals x1,y1,z1 (same as x2,y2,z2)
						px3#=x1#
						py3#=y1#
						pz3#=z1#
					Else ' more than one keyframe
						px3#=(((x2#-x1#)/(fd1+fd2))*fd1)+x1#
						py3#=(((y2#-y1#)/(fd1+fd2))*fd1)+y1#
						pz3#=(((z2#-z1#)/(fd1+fd2))*fd1)+z1#
					EndIf
				EndIf
				no_keys=False
				
				' store current keyframe for use with transtions
				TBone(bent).kx=px3
				TBone(bent).ky=py3
				TBone(bent).kz=pz3
			
				' rotation
	
				i=frame+1
				Repeat
					i=i-1
					flag=TBone(bent).keys.flags[i]&4
					If flag
						w1#=TBone(bent).keys.qw[i]
						x1#=TBone(bent).keys.qx[i]
						y1#=TBone(bent).keys.qy[i]
						z1#=TBone(bent).keys.qz[i]
						fd1=framef-i
						found=True
					EndIf
					If i<=start_frame Then i=end_frame+1;ii=ii+1
				Until found=True Or ii>=2
				If found=False Then no_keys=True
				found=False
				ii=0
				
				' forwards
				i=frame
				Repeat
					i=i+1
					If i>end_frame Then i=start_frame;ii=ii+1
					flag=TBone(bent).keys.flags[i]&4
					If flag
						w2#=TBone(bent).keys.qw[i]
						x2#=TBone(bent).keys.qx[i]
						y2#=TBone(bent).keys.qy[i]
						z2#=TBone(bent).keys.qz[i]
						fd2=i-framef
						found=True
					EndIf
				Until found=True Or ii>=2
				If found=False Then no_keys=True
				found=False
				ii=0
	
				' interpolate keys
	
				Local w3#=0
				Local x3#=0
				Local y3#=0
				Local z3#=0
				If no_keys=True ' no keyframes
					w3#=TBone(bent).n_qw#
					x3#=TBone(bent).n_qx#
					y3#=TBone(bent).n_qy#
					z3#=TBone(bent).n_qz#
				Else
					If fd1+fd2=0.0 ' one keyframe
						' if only one keyframe, fd1+fd2 will equal 0 resulting in division error and garbage rotational values (which can affect children)
						' so we check for this, and if true then rotational values equals w1,x1,y1,z1 (same as w2,x2,y2,z2)
						w3#=w1#
						x3#=x1#
						y3#=y1#
						z3#=z1#
					Else ' more than one keyframe
						Local t#=(1.0/(fd1+fd2))*fd1
						TQuaternion.Slerp(x1,y1,z1,w1,x2,y2,z2,w2,x3,y3,z3,w3,t#) ' interpolate between prev and next rotations
					EndIf
				EndIf
				no_keys=False
				
				' store current keyframe for use with transtions
				TBone(bent).kqw=w3
				TBone(bent).kqx=x3
				TBone(bent).kqy=y3
				TBone(bent).kqz=z3
	
				'Local pitch#
				'Local yaw#
				'Local roll#
				'QuatToEuler(-w3#,x3#,y3#,-z3#,pitch#,yaw#,roll#)
		
				'TBone(bent).rx#=pitch#
				'TBone(bent).ry#=yaw#
				'TBone(bent).rz#=roll#
		
				TQuaternion.QuatToMat(w3#,x3#,y3#,z3#,TBone(bent).mat)
	
				TBone(bent).mat.grid[3,0]=px3#
				TBone(bent).mat.grid[3,1]=py3#
				TBone(bent).mat.grid[3,2]=pz3#
		
				' copy parent's matrix and multiply by self's matrix
				If TBone(bent).parent<>Null And bent.parent<>ent1
					Local new_mat:TMatrix=TBone(bent).parent.mat.Copy()
					new_mat.Multiply(TBone(bent).mat)
					TBone(bent).mat.Overwrite(new_mat)
				EndIf
					
				' *** vertex deform ***
				VertexDeform(TMesh(ent1),TBone(bent),vm)
											
			Next
		
		EndIf
			
	End Function
	
	' AnimateMesh2, used to animate transitions between animations, very similar to AnimateMesh except it
	' interpolates between current animation pose (via saved keyframe) and first keyframe of new animation.
	' framef# interpolates between 0 and 1
	
	Function AnimateMesh2(ent1:TEntity,framef#,start_frame,end_frame)
		
		If TMesh(ent1)<>Null
	
			If TMesh(ent1).anim=False Then Return ' mesh contains no anim data
			
			TMesh(ent1).anim_render=True
	
			Local vm[TMesh(ent1).GetSurface(1).CountVertices()] ' vertex moved
			
			'Local frame=framef ' float to int
	
			For Local bent:TBone=EachIn TMesh(ent1).bone_list
					
				Local i=0
				Local ii=0
				Local fd1#=framef# ' fd1 always between 0 and 1 for this function
				Local fd2#=1.0-fd1# ' fd1+fd2 always equals 0 for this function
				Local found=False
				Local no_keys=False
				Local w1#
				
				' get current keyframe
				Local x1#=TBone(bent).kx#
				Local y1#=TBone(bent).ky#
				Local z1#=TBone(bent).kz#
				
				Local w2#
				Local x2#
				Local y2#
				Local z2#
				
				Local flag=0
				
				' position
	
				' forwards
				'i=frame
				i=start_frame-1
				Repeat
					i=i+1
					If i>end_frame Then i=start_frame;ii=ii+1
					flag=TBone(bent).keys.flags[i]&1
					If flag
						x2#=TBone(bent).keys.px[i]
						y2#=TBone(bent).keys.py[i]
						z2#=TBone(bent).keys.pz[i]
						'fd2=i-framef
						found=True
					EndIf
				Until found=True Or ii>=2
				If found=False Then no_keys=True
				found=False
				ii=0
		
				Local px3#=0
				Local py3#=0
				Local pz3#=0
				If no_keys=True ' no keyframes
					px3#=TBone(bent).n_px#
					py3#=TBone(bent).n_py#
					pz3#=-TBone(bent).n_pz#
				Else
					If fd1+fd2=0.0 ' one keyframe
						' if only one keyframe, fd1+fd2 will equal 0 resulting in division error and garbage positional values (which can affect children)
						' so we check for this, and if true then positional values equals x1,y1,z1 (same as x2,y2,z2)
						px3#=x1#
						py3#=y1#
						pz3#=z1#
					Else ' more than one keyframe
						px3#=(((x2#-x1#)/(fd1+fd2))*fd1)+x1#
						py3#=(((y2#-y1#)/(fd1+fd2))*fd1)+y1#
						pz3#=(((z2#-z1#)/(fd1+fd2))*fd1)+z1#
					EndIf
				EndIf
				no_keys=False
			
				' get current keyframe
				w1#=TBone(bent).kqw#
				x1#=TBone(bent).kqx#
				y1#=TBone(bent).kqy#
				z1#=TBone(bent).kqz#
					
				' rotation
	
				' forwards
				'i=frame
				i=start_frame-1
				Repeat
					i=i+1
					If i>end_frame Then i=start_frame;ii=ii+1
					flag=TBone(bent).keys.flags[i]&4
					If flag
						w2#=TBone(bent).keys.qw[i]
						x2#=TBone(bent).keys.qx[i]
						y2#=TBone(bent).keys.qy[i]
						z2#=TBone(bent).keys.qz[i]
						'fd2=i-framef
						found=True
					EndIf
				Until found=True Or ii>=2
				If found=False Then no_keys=True
				found=False
				ii=0
	
				' interpolate keys
	
				Local w3#=0
				Local x3#=0
				Local y3#=0
				Local z3#=0
				If no_keys=True ' no keyframes
					w3#=TBone(bent).n_qw#
					x3#=TBone(bent).n_qx#
					y3#=TBone(bent).n_qy#
					z3#=TBone(bent).n_qz#
				Else
					If fd1+fd2=0.0 ' one keyframe
						' if only one keyframe, fd1+fd2 will equal 0 resulting in division error and garbage rotational values (which can affect children)
						' so we check for this, and if true then rotational values equals w1,x1,y1,z1 (same as w2,x2,y2,z2)
						w3#=w1#
						x3#=x1#
						y3#=y1#
						z3#=z1#
					Else ' more than one keyframe
						Local t#=(1.0/(fd1+fd2))*fd1
						TQuaternion.Slerp(x1,y1,z1,w1,x2,y2,z2,w2,x3,y3,z3,w3,t#) ' interpolate between prev and next rotations
					EndIf
				EndIf
				no_keys=False
	
				'Local pitch#
				'Local yaw#
				'Local roll#
				'QuatToEuler(-w3#,x3#,y3#,-z3#,pitch#,yaw#,roll#)
		
				'TBone(bent).rx#=pitch#
				'TBone(bent).ry#=yaw#
				'TBone(bent).rz#=roll#
				
				TQuaternion.QuatToMat(w3#,x3#,y3#,z3#,TBone(bent).mat)
	
				TBone(bent).mat.grid[3,0]=px3#
				TBone(bent).mat.grid[3,1]=py3#
				TBone(bent).mat.grid[3,2]=pz3#
		
				' copy parent's matrix and multiply by self's matrix
				If TBone(bent).parent<>Null And bent.parent<>ent1
					Local new_mat:TMatrix=TBone(bent).parent.mat.Copy()
					new_mat.Multiply(TBone(bent).mat)
					TBone(bent).mat.Overwrite(new_mat)
				EndIf
						
				' *** vertex deform ***
				VertexDeform(TMesh(ent1),TBone(bent),vm)
			
			Next
		
		EndIf
			
	End Function
	
	Function VertexDeform(ent1:TMesh,bent:TBone,vm[])
		
		' get first surface if only one surface in mesh, otherwise set multi_surfs=True and we'll find correct surf later
		Local multi_surfs=False
		Local surf:TSurface
		Local anim_surf:TSurface
		If TMesh(ent1).no_surfs>1
			multi_surfs=True
		Else
			surf:TSurface=TSurface(TMesh(ent1).surf_list.First())
			anim_surf:TSurface=TSurface(TMesh(ent1).anim_surf_list.First())
		EndIf
																																																																																						
		' get transform mat by multiplying current pose matrix with reference pose inverse matrix
		Local tform_mat:TMatrix=TBone(bent).mat.Copy()
		tform_mat.Multiply(TBone(bent).inv_mat)
	
		' cycle through all vertices attached to bone
		For Local ix=0 To TBone(bent).no_verts-1
	
			Local vid=TBone(bent).verts_id[ix]	' get vertex id
			Local weight#=TBone(bent).verts_w#[ix] ' get vertex weight
			
			If weight#<>0.0
			
				' if more than one surf in mesh, check vertex id and find which surf it belongs to
				If multi_surfs=True
				
					Local alink:TLink=TMesh(ent1).anim_surf_list.FirstLink() ' used to iterate through anim_surf_list
				
					For surf:TSurface=EachIn TMesh(ent1).surf_list
				
						anim_surf=TSurface(alink.Value:Object()) ' get anim_surf
	
						If vid>=surf.vmin And vid<=surf.vmax Then Exit
	
						alink=alink.NextLink() ' iterate through anim_surf_list in sync with surf_list
					
					Next
					
				EndIf
				
				' get original vertex position
				Local ovx#=surf.VertexX#(vid)
				Local ovy#=surf.VertexY#(vid)
				Local ovz#=surf.VertexZ#(vid)
				
				' transform vertex position with transform mat
				Local new_mat:TMatrix=tform_mat.Copy()
				new_mat.Translate(ovx,ovy,ovz)
	
				Local vx#=0
				Local vy#=0
				Local vz#=0
				Local vertex_moved=vm[vid] ' a simple true/false check to see whether vertex has been moved already
				If vertex_moved=False ' if vertex not moved yet, we don't add current vertex position
					vx#=0
					vy#=0
					vz#=0
				Else ' if vertex moved, we add current vertex position
					vx#=anim_surf.VertexX#(vid)
					vy#=anim_surf.VertexY#(vid)
					vz#=anim_surf.VertexZ#(vid)
				EndIf
				
				' get new vertex position
				Local x#=vx#+(new_mat.grid[3,0])*weight#
				Local y#=vy#+(new_mat.grid[3,1])*weight#	
				Local z#=vz#+(new_mat.grid[3,2])*weight#
					
				' update vertex position
				anim_surf.VertexCoords(vid,x#,y#,z#)
					
				' vertex has been moved
				vm[vid]=True
	
			EndIf
			
		Next
	
	End Function
	
	' simpler, faster version of VertexDeform suitable for use with non-weighted anims. unused. 
	Function VertexDeform1(ent1:TMesh,bent:TBone,vm[])
		
		' get first surface if only one surface in mesh, otherwise set multi_surfs=True and we'll find correct surf later
		Local multi_surfs=False
		Local surf:TSurface
		Local anim_surf:TSurface
		If TMesh(ent1).no_surfs>1
			multi_surfs=True
		Else
			surf:TSurface=TSurface(TMesh(ent1).surf_list.First())
			anim_surf:TSurface=TSurface(TMesh(ent1).anim_surf_list.First())
		EndIf
																																																																																						
		' get transform mat by multiplying current pose matrix with reference pose inverse matrix
		Local tform_mat:TMatrix=TBone(bent).mat.Copy()
		tform_mat.Multiply(TBone(bent).inv_mat)
	
		' cycle through all vertices attached to bone
		For Local ix=0 To TBone(bent).no_verts-1
	
			Local vid=TBone(bent).verts_id[ix]*3	' get vertex id
	
			' get original vertex position
			Local x#=surf.vert_coords[vid]'surf.VertexX#(vid)
			Local y#=surf.vert_coords[vid+1]'surf.VertexY#(vid)
			Local z#=-surf.vert_coords[vid+2]'surf.VertexZ#(vid)
			
			anim_surf.vert_coords#[vid] = tform_mat.grid#[0,0]*x# + tform_mat.grid#[1,0]*y# + tform_mat.grid#[2,0]*z# + tform_mat.grid#[3,0]
			anim_surf.vert_coords#[vid+1] = tform_mat.grid#[0,1]*x# + tform_mat.grid#[1,1]*y# + tform_mat.grid#[2,1]*z# + tform_mat.grid#[3,1]
			anim_surf.vert_coords#[vid+2] = -(tform_mat.grid#[0,2]*x# + tform_mat.grid#[1,2]*y# + tform_mat.grid#[2,2]*z# + tform_mat.grid#[3,2])
			
			' transform vertex position with transform mat
			'Local new_mat:TMatrix=tform_mat.Copy()
			'new_mat.Translate(ovx,ovy,ovz)
			
			' get new vertex position
			'Local x#=new_mat.grid[3,0]
			'Local y#=new_mat.grid[3,1]	
			'Local z#=new_mat.grid[3,2]
			
					
			'vert_coords#[vid]=x#
			'vert_coords#[vid+1]=y#
			'vert_coords#[vid+2]=z#*-1 ' ***ogl***
				
			' update vertex position
			'anim_surf.VertexCoords(vid,x#,y#,z#)
		
		Next
	
	End Function
	
	' this function will ensure that weights are set correctly - it will choose the 4 strongest weights for each vertex 
	' if more than 4 weights exist, and will normalise weights if their sum doesn't equal 1.0
	 
	' this is quite an expensive function to call, and not needed if the b3d file has been exported correctly (which most are).
	' therefore, it's not currently used by minib3d, but if you find your vertex weighting isn't working as expected,
	' try calling this after loading your mesh.
	Function UpdateWeights(mesh:TMesh)
	
		' get bone weights from indivual bone arrays, store in big weight arrays covering all vertices
		' only store four strongest weights for each vertex
	
		Local no_verts=mesh.GetSurface(1).no_verts
	
		Local verts_w1#[no_verts]
		Local verts_w2#[no_verts]
		Local verts_w3#[no_verts]
		Local verts_w4#[no_verts]
		Local verts_b1:TBone[no_verts]
		Local verts_b2:TBone[no_verts]
		Local verts_b3:TBone[no_verts]
		Local verts_b4:TBone[no_verts]
	
		For Local bone:TBone=EachIn mesh.bone_list
		
			For Local i=0 To bone.no_verts-1
		
				Local vid#=bone.verts_id[i]
				Local weight#=bone.verts_w#[i]
				
				Local w1#=verts_w1#[vid]
				Local w2#=verts_w2#[vid]
				Local w3#=verts_w3#[vid]
				Local w4#=verts_w4#[vid]
			
				Local weight_index=0
				
				If weight>0.0
				
					If w1#=0.0
						w1#=weight#
						weight_index=1
					Else
						If w2#=0.0
							w2#=weight#
							weight_index=2
						Else
							If w3#=0.0
								w3#=weight#
								weight_index=3
							Else
								If w4#=0.0
									w4#=weight#
									weight_index=4
								Else
									Local lowest_weight#=weight#
									Local lowest_weight_index=0
									If w1#<lowest_weight# Then lowest_weight#=w1#;lowest_weight_index=1
									If w2#<lowest_weight# Then lowest_weight#=w2#;lowest_weight_index=2
									If w3#<lowest_weight# Then lowest_weight#=w3#;lowest_weight_index=3
									If w4#<lowest_weight# Then lowest_weight#=w4#;lowest_weight_index=4
									If lowest_weight_index<>0
										If lowest_weight_index=1 Then w1#=weight#;weight_index=1
										If lowest_weight_index=2 Then w2#=weight#;weight_index=2
										If lowest_weight_index=3 Then w3#=weight#;weight_index=3
										If lowest_weight_index=4 Then w4#=weight#;weight_index=4
									EndIf
								EndIf
							EndIf
						EndIf
					EndIf
					
					If weight_index<>0
	
						If weight_index=1 Then verts_w1#[vid]=w1#;verts_b1[vid]=bone
						If weight_index=2 Then verts_w2#[vid]=w2#;verts_b2[vid]=bone
						If weight_index=3 Then verts_w3#[vid]=w3#;verts_b3[vid]=bone
						If weight_index=4 Then verts_w4#[vid]=w4#;verts_b4[vid]=bone
				
					EndIf
				
				EndIf
			
			Next
		
		Next
		
		' normalise weights
		
		For Local i=0 To no_verts-1
	
			Local w1#=verts_w1#[i]
			Local w2#=verts_w2#[i]
			Local w3#=verts_w3#[i]
			Local w4#=verts_w4#[i]
						
			Local wt#=w1#+w2#+w3#+w4#
				
			' order weights so strongest first - unnecessary, but available for debug purposes
				
			Repeat
				
				Local ww1#=w1#
				Local ww2#=w2#
				Local ww3#=w3#
				Local ww4#=w4#
				
				If w1#<w2#
					w1#=ww2#
					w2#=ww1#
				EndIf
				
				ww2#=w2#
				
				If w2#<w3#
					w2#=ww3#
					w3#=ww2#
				EndIf
				
				ww3#=w3#
				
				If w3#<w4#
					w3#=ww4#
					w4#=ww3#
				EndIf
							
			Until w1#>=w2# And w2#>=w3# And w3#>=w4#		
				
			' normalise weights if sum of them <> 1.0
																												
			If wt#<0.99 Or wt#>1.01
	
				Local wm#
				If wt#<>0.0
					wm#=1.0/wt#
				Else
					wm#=1.0
				EndIf
				w1#=w1#*wm#
				w2#=w2#*wm#
				w3#=w3#*wm#
				w4#=w4#*wm#
	
				verts_w1#[i]=w1#
				verts_w2#[i]=w2#
				verts_w3#[i]=w3#
				verts_w4#[i]=w4#
				
				wt#=w1#+w2#+w3#+w4#
	
			EndIf
	
		Next
		
		' reassign weights to bone arrays
		
		For Local vid=0 To no_verts-1
		
			Local bone0:TBone=Null
			Local no=0
		
			If verts_b1[vid]<>Null Then bone0=verts_b1[vid];no=1
			If verts_b2[vid]<>Null Then bone0=verts_b2[vid];no=2
			If verts_b3[vid]<>Null Then bone0=verts_b3[vid];no=3
			If verts_b4[vid]<>Null Then bone0=verts_b4[vid];no=4
		
			If bone0<>Null
		
				For Local bone:TBone=EachIn mesh.bone_list
				
					If bone=bone0
					
						For Local i=0 To bone.no_verts-1
						
							If bone.verts_id[i]=vid
							
								If no=1 bone.verts_w#[i]=verts_w1#[vid]
								If no=2 bone.verts_w#[i]=verts_w2#[vid]
								If no=3 bone.verts_w#[i]=verts_w3#[vid]
								If no=4 bone.verts_w#[i]=verts_w4#[vid]
								Exit
							
							EndIf
						
						Next
						
						Exit
					
					EndIf
				
				Next
		
			EndIf
		
		Next
		
	End Function

End Type

Type TAnimationKeys

	Field frames
	Field flags[1]
	Field px#[1]
	Field py#[1]
	Field pz#[1]
	Field sx#[1]
	Field sy#[1]
	Field sz#[1]
	Field qw#[1]
	Field qx#[1]
	Field qy#[1]
	Field qz#[1]
	
	Method Copy:TAnimationKeys()
	
		Local keys:TAnimationKeys=New TAnimationKeys
	
		keys.frames=frames
		keys.flags=flags[..]
		keys.px#=px#[..]
		keys.py#=py#[..]
		keys.pz#=pz#[..]
		keys.sx#=sx#[..]
		keys.sy#=sy#[..]
		keys.sz#=sz#[..]
		keys.qw#=qw#[..]
		keys.qx#=qx#[..]
		keys.qy#=qy#[..]
		keys.qz#=qz#[..]

		Return keys
	
	End Method
	
End Type
