225 lines
8.6 KiB
Python
Executable File
225 lines
8.6 KiB
Python
Executable File
"""
|
|
Description:
|
|
Integrates IVR Tracks into Motion Builder
|
|
|
|
Authors:
|
|
Kat Bodey <kat.bodey@rockstarnorth.com>
|
|
David Vega <david.vega@rockstargames.com>
|
|
"""
|
|
|
|
import os
|
|
import struct
|
|
|
|
import pyfbsdk as mobu
|
|
|
|
from RS import Globals
|
|
from RS.Utils import Math
|
|
from RS.Utils import Creation
|
|
|
|
|
|
class IVRFile(object):
|
|
""" Generates an IVR Track in the scene based on the contents of an ivr file generated from RAGE """
|
|
def __init__(self, filepath=""):
|
|
"""
|
|
Constructor
|
|
|
|
Arguments:
|
|
filepath (string): path to ivr file
|
|
"""
|
|
self.FilePath = filepath
|
|
self.TimeInRecordingArray = []
|
|
self.SpeedArray = []
|
|
self.MatrixAArray = []
|
|
self.MatrixBArray = []
|
|
self.SteerAngleArray = []
|
|
self.GasArray = []
|
|
self.BrakeArray = []
|
|
self.HandBrakeArray = []
|
|
self.CoordsArray = []
|
|
self.ValidData = False
|
|
|
|
if os.path.exists(filepath):
|
|
self.ValidData = self.ParseIVRFile()
|
|
|
|
def ParseIVRFile(self):
|
|
""" Read the contents of the IVR File """
|
|
|
|
lIvrFile = open(self.FilePath, 'rb')
|
|
lIvrData = lIvrFile.read()
|
|
lFrames = len(lIvrData) / 32
|
|
|
|
for frame in xrange(lFrames):
|
|
# Convert the binary data to usable strings
|
|
bits = frame * 32
|
|
|
|
self.TimeInRecordingArray.append(struct.unpack('L', lIvrData[0 + bits:4 + bits])[0])
|
|
self.SpeedArray.append(struct.unpack('hhh', lIvrData[4 + bits:10 + bits]))
|
|
self.MatrixAArray.append(struct.unpack('bbb', lIvrData[10 + bits:13 + bits]))
|
|
self.MatrixBArray.append(struct.unpack('bbb', lIvrData[13 + bits:16 + bits]))
|
|
self.SteerAngleArray.append(struct.unpack('b', lIvrData[16 + bits])[0])
|
|
self.GasArray.append(struct.unpack('b', lIvrData[17 + bits])[0])
|
|
self.BrakeArray.append(struct.unpack('b', lIvrData[18 + bits])[0])
|
|
self.HandBrakeArray.append(struct.unpack('b', lIvrData[19 + bits])[0])
|
|
self.CoordsArray.append(struct.unpack('fff', lIvrData[20 + bits:32 + bits]))
|
|
|
|
return True
|
|
|
|
def GenerateComponents(self):
|
|
""" Create the mobu.FBComponents that make up the IVR Track and set them up properly"""
|
|
mobu.FBSystem().CurrentTake.SetCurrentLayer(0)
|
|
|
|
lNullA = mobu.FBModelNull("Matrix_A")
|
|
lNullB = mobu.FBModelNull("Translate_Null")
|
|
rootNull = mobu.FBModelNull("IVR_Null")
|
|
|
|
for vectorA, vectorB, position, time in \
|
|
zip(self.MatrixAArray, self.MatrixBArray, self.CoordsArray, self.TimeInRecordingArray):
|
|
|
|
# Get vectors that represent the rotation
|
|
# We add 0 to the vectors because MB uses 4x4 matricies and the vectors that the
|
|
# GetOrthonormalMatrixVectors method return only contain x,y, and z
|
|
|
|
rotationVectors = [vector + [0] for vector in
|
|
Math.GetOrthonormalMatrixVectors(vectorA, vectorB)]
|
|
|
|
transformVector = list(position) + [1]
|
|
|
|
lMatrix = mobu.FBMatrix()
|
|
lMatrix.Set(rotationVectors + [transformVector])
|
|
lNullA.SetMatrix(lMatrix)
|
|
|
|
time = mobu.FBTime(0, 0, 0, int(time / 33.0))
|
|
for translationRotation in ["Translation", "Rotation"]:
|
|
translationRotationNode = getattr(lNullA, translationRotation)
|
|
translationRotationNode.SetAnimated(True)
|
|
animationNode = translationRotationNode.GetAnimationNode()
|
|
|
|
for node, data in zip(animationNode.Nodes, translationRotationNode.Data):
|
|
node.FCurve.KeyAdd(time, data * (100 ** (translationRotation == "Translation")))
|
|
|
|
mobu.FBSystem().Scene.Evaluate()
|
|
|
|
path = self.MakeSpline()
|
|
path.Parent = lNullB
|
|
|
|
self.AnimateVehicleAttributes().Parent = lNullB
|
|
lNullB.Parent = rootNull
|
|
lNullA.Parent = lNullB
|
|
lNullB.Rotation.Data = mobu.FBVector3d(-90, 0, 0)
|
|
self.SetupMatrixNull(lNullA)
|
|
return lNullB
|
|
|
|
def MakeSpline(self):
|
|
""" Generate the 3D Curve Path """
|
|
|
|
PathName = os.path.basename(self.FilePath)
|
|
return Creation.CreatePath(PathName,
|
|
*[[coordinate[0] * 100, coordinate[1] * 100, coordinate[2] * 100, 0]
|
|
for coordinate in self.CoordsArray])
|
|
|
|
def AnimateVehicleAttributes(self):
|
|
""" Creates Vehicle Animation Properties """
|
|
|
|
null = mobu.FBCreateObject( "Browsing/Templates/Elements", "Null", "CarProperties" )
|
|
|
|
for propertyName in ['Speed', 'SteerAngle', 'Gas', 'Brake', 'HandBrake']:
|
|
|
|
# Create Property and set a Key on it
|
|
|
|
isSpeed = propertyName == 'Speed'
|
|
newProperty = null.PropertyCreate(propertyName,
|
|
[mobu.FBPropertyType.kFBPT_double, mobu.FBPropertyType.kFBPT_Vector3D]
|
|
[isSpeed],
|
|
['Double', 'Vector3D'][isSpeed],
|
|
True, True, None)
|
|
|
|
newProperty.SetAnimated(True)
|
|
newProperty.Key()
|
|
|
|
# In the older method, this loop would iterate over a matrix but not use the values to add keys
|
|
# Instead we just use the hard value of 16 as all Motion Builder Matricies are 4 x 4 Matricies
|
|
|
|
for index in xrange(16):
|
|
time = mobu.FBTime(0, 0, 0, index)
|
|
animationNode = newProperty.GetAnimationNode()
|
|
|
|
# The Speed property takes a Vector3D as a value , So we iterate over the xyz values to set them.
|
|
# We also switch the y & z values around so the data is represented correctly in Motion Builder
|
|
|
|
if isSpeed:
|
|
for nullIndex, speedIndex in enumerate([0, 2, 1]):
|
|
animationNode.Nodes[nullIndex].FCurve.KeyAdd(time, self.SpeedArray[nullIndex][speedIndex])
|
|
continue
|
|
|
|
animationNode.FCurve.KeyAdd(time, getattr(self, "{}Array".format(propertyName))[index])
|
|
|
|
return null
|
|
|
|
@staticmethod
|
|
def SetupMatrixNull(null):
|
|
"""
|
|
Setup the Matrix Null so it follows the 3D Curve Path
|
|
|
|
Arguments:
|
|
null (pyfbsdk.FBModelNull): null that travels along the 3D Curve path
|
|
|
|
Return:
|
|
null
|
|
"""
|
|
translation = null.Translation.GetAnimationNode()
|
|
rotation = null.Rotation.GetAnimationNode()
|
|
currentTake = mobu.FBSystem().CurrentTake
|
|
|
|
# push farplane back
|
|
for iCamera in Globals.gCameras:
|
|
if iCamera.Name == "Producer Perspective":
|
|
iCamera.PropertyList.Find("Far Plane").Data = 400000
|
|
break
|
|
|
|
# set end range
|
|
lEndRange = translation.Nodes[0].FCurve.Keys[-1].Time.GetFrame()
|
|
|
|
mobu.FBPlayerControl().LoopStop = mobu.FBTime(0, 0, 0, lEndRange)
|
|
|
|
# apply gimblekill & resample filters
|
|
for each in ['Gimbal Killer', 'Resample']:
|
|
lGimbleFilter = mobu.FBFilterManager().CreateFilter('Gimbal Killer')
|
|
|
|
if each == 'Resample':
|
|
lGimbleFilter.Apply(translation, False)
|
|
|
|
lGimbleFilter.Apply(rotation, False)
|
|
lGimbleFilter.FBDelete()
|
|
|
|
# re-orientate matrix null
|
|
lCurrentLayer = currentTake.GetCurrentLayer()
|
|
lLayer1 = lCurrentLayer + 1
|
|
currentTake.SetCurrentLayer(lLayer1)
|
|
|
|
[rotation.Nodes[1].FCurve.KeyAdd(mobu.FBTime(0, 0, 0), value) for value in [0.0, 180.0]]
|
|
|
|
rotation.Nodes[1].Selected = True
|
|
|
|
currentTake.PlotTakeOnSelectedProperties(mobu.FBTime(0, 0, 0, 1))
|
|
|
|
mobu.FBSystem().Scene.Evaluate()
|
|
|
|
return null
|
|
|
|
@staticmethod
|
|
def SceneAdjustment(null, pX, pY, pZ, propertyType="Translation"):
|
|
"""
|
|
Move scene to user coordinates or orientation
|
|
|
|
Arguments:
|
|
null (pyfbsdk.FBModelNull): null that travels along the 3D Curve path
|
|
pX (int/float): translate or rotate x value
|
|
pY (int/float): translate or rotate y value
|
|
pZ (int/float): translate or rotate z value
|
|
type (string): whether the values should be applied as translation or rotation
|
|
"""
|
|
if null:
|
|
setattr(null, propertyType, mobu.FBVector3d(pX, pY, pZ))
|
|
|
|
else:
|
|
mobu.FBMessageBox("Warning", "No Null Selected", "OK") |