mirror of https://github.com/AxioDL/metaforce.git
254 lines
10 KiB
Python
254 lines
10 KiB
Python
|
from . import SACTSubtype, SACTAction, SACTEvent, ANIM
|
||
|
from .. import hmdl
|
||
|
|
||
|
import bpy
|
||
|
import re
|
||
|
import os.path
|
||
|
import posixpath
|
||
|
import struct
|
||
|
from mathutils import Vector
|
||
|
|
||
|
# Actor data class
|
||
|
class SACTData(bpy.types.PropertyGroup):
|
||
|
|
||
|
subtypes =\
|
||
|
bpy.props.CollectionProperty(type=SACTSubtype.SACTSubtype, name="Actor Subtype List")
|
||
|
active_subtype =\
|
||
|
bpy.props.IntProperty(name="Active Actor Subtype", default=0, update=SACTSubtype.active_subtype_update)
|
||
|
show_subtypes =\
|
||
|
bpy.props.BoolProperty()
|
||
|
|
||
|
actions =\
|
||
|
bpy.props.CollectionProperty(type=SACTAction.SACTAction, name="Actor Action List")
|
||
|
active_action =\
|
||
|
bpy.props.IntProperty(name="Active Actor Action", default=0, update=SACTAction.active_action_update)
|
||
|
show_actions =\
|
||
|
bpy.props.BoolProperty()
|
||
|
|
||
|
show_events =\
|
||
|
bpy.props.BoolProperty()
|
||
|
|
||
|
# A Routine to resolve HECL DAG-relative paths while ensuring database path-constraints
|
||
|
def resolve_local_path(blend_path, rel_path):
|
||
|
if not rel_path.startswith('//'):
|
||
|
raise RuntimeError("Files must have relative paths")
|
||
|
blend_dir = os.path.split(blend_path)[0]
|
||
|
image_comps = re.split('/|\\\\', rel_path[2:])
|
||
|
start_idx = 0
|
||
|
for comp in image_comps:
|
||
|
if comp == '..':
|
||
|
start_idx += 1
|
||
|
if not blend_dir:
|
||
|
raise RuntimeError("Relative file path has exceeded DAG root")
|
||
|
blend_dir = os.path.split(blend_dir)[0]
|
||
|
else:
|
||
|
break
|
||
|
retval = blend_dir
|
||
|
for i in range(len(image_comps)-start_idx):
|
||
|
if retval:
|
||
|
retval += '/'
|
||
|
retval += image_comps[start_idx+i]
|
||
|
return posixpath.relpath(retval)
|
||
|
|
||
|
|
||
|
# RARM Generator
|
||
|
def package_rarm(arm_obj, res_db, heclpak, arg_path, arg_package):
|
||
|
|
||
|
rarm_db_id, rarm_hash = res_db.register_resource(arg_path, arm_obj.name, arg_package)
|
||
|
if not rarm_hash:
|
||
|
|
||
|
skeleton_data = hecl_rmdl.generate_skeleton_info(arm_obj)
|
||
|
rarm_hash = heclpak.add_object(skeleton_data, b'RARM')
|
||
|
res_db.update_resource_stats(rarm_db_id, rarm_hash)
|
||
|
|
||
|
return rarm_db_id, rarm_hash
|
||
|
|
||
|
# RANI Generator
|
||
|
def package_rani(action_obj, res_db, heclpak, arg_path, arg_package):
|
||
|
|
||
|
rani_db_id, rani_hash = res_db.register_resource(arg_path, action_obj.name, arg_package)
|
||
|
if not rani_hash:
|
||
|
|
||
|
res_db.clear_dependencies(rani_db_id)
|
||
|
rani_hash = heclpak.add_object(hecl_rmdl.rmdl_anim.generate_animation_info(action_obj, res_db, rani_db_id, arg_package), b'RANI')
|
||
|
res_db.update_resource_stats(rani_db_id, rani_hash)
|
||
|
|
||
|
return rani_db_id, rani_hash
|
||
|
|
||
|
|
||
|
# Actor Ticket Generator
|
||
|
def package_actor(scene, res_db, heclpak, arg_path, arg_package, arg_res_name):
|
||
|
actor_data = scene.hecl_sact_data
|
||
|
|
||
|
act_db_id, act_hash = res_db.register_resource(arg_path, None, arg_package)
|
||
|
res_db.clear_dependencies(act_db_id)
|
||
|
|
||
|
with open(os.path.splitext(bpy.data.filepath)[0] + '.heclticket', 'wb') as ticket:
|
||
|
|
||
|
# Subtypes
|
||
|
ticket.write(struct.pack('I', len(actor_data.subtypes)))
|
||
|
for subtype_idx in range(len(actor_data.subtypes)):
|
||
|
subtype = actor_data.subtypes[subtype_idx]
|
||
|
scene.hecl_sact_data.active_subtype = subtype_idx
|
||
|
|
||
|
# Subtype name
|
||
|
ticket.write(subtype.name.encode() + b'\0')
|
||
|
|
||
|
# Mesh
|
||
|
if subtype.linked_mesh in bpy.data.objects:
|
||
|
mesh_obj = bpy.data.objects[subtype.linked_mesh]
|
||
|
if mesh_obj.library:
|
||
|
path = resolve_local_path(arg_path.split(';')[-1], mesh_obj.library.filepath)
|
||
|
mesh_db_id, mesh_hash = res_db.search_for_resource(path, arg_package)
|
||
|
if not mesh_hash:
|
||
|
raise RuntimeError("Error - unable to load mesh library '{0}'".format(path))
|
||
|
res_db.register_dependency(act_db_id, mesh_db_id)
|
||
|
ticket.write(mesh_hash)
|
||
|
else:
|
||
|
mesh_db_id, mesh_hash, _final_data = hecl_rmdl.to_rmdl(mesh_obj, mesh_obj.name, res_db, heclpak, arg_path, arg_package)
|
||
|
res_db.register_dependency(act_db_id, mesh_db_id)
|
||
|
ticket.write(mesh_hash)
|
||
|
else:
|
||
|
raise RuntimeError("Error - unable to load mesh '{0}'".format(mesh))
|
||
|
|
||
|
# Armature
|
||
|
if subtype.linked_armature in bpy.data.objects:
|
||
|
arm_obj = bpy.data.objects[subtype.linked_armature]
|
||
|
rarm_db_id, rarm_hash = package_rarm(arm_obj, res_db, heclpak, arg_path, arg_package)
|
||
|
res_db.register_dependency(act_db_id, rarm_db_id)
|
||
|
ticket.write(rarm_hash)
|
||
|
else:
|
||
|
raise RuntimeError("Error - unable to load armature '{0}'".format(subtype.linked_armature))
|
||
|
|
||
|
# Action AABBs
|
||
|
print('\nComputing Action AABBs for', subtype.name)
|
||
|
scene.hecl_auto_remap = False
|
||
|
ticket.write(struct.pack('I', len(actor_data.actions)))
|
||
|
for action_idx in range(len(actor_data.actions)):
|
||
|
action = actor_data.actions[action_idx]
|
||
|
print(action.name)
|
||
|
scene.hecl_sact_data.active_action = action_idx
|
||
|
bpy.ops.scene.SACTAction_load()
|
||
|
|
||
|
# Action name
|
||
|
ticket.write(action.name.encode() + b'\0')
|
||
|
|
||
|
# Frame 1
|
||
|
scene.frame_set(1)
|
||
|
|
||
|
# Transform against root
|
||
|
root_bone = arm_obj.pose.bones['root']
|
||
|
root_bone.location = (0.0,0.0,0.0)
|
||
|
if root_bone.rotation_mode == 'QUATERNION':
|
||
|
root_bone.rotation_quaternion = (1.0,0.0,0.0,0.0)
|
||
|
else:
|
||
|
root_bone.rotation_euler = (0.0,0.0,0.0)
|
||
|
root_aabb_min = Vector(mesh_obj.bound_box[0])
|
||
|
root_aabb_max = Vector(mesh_obj.bound_box[6])
|
||
|
|
||
|
# Accumulate AABB for each frame
|
||
|
for frame_idx in range(2, scene.frame_end + 1):
|
||
|
scene.frame_set(frame_idx)
|
||
|
|
||
|
root_bone.location = (0.0,0.0,0.0)
|
||
|
scene.update()
|
||
|
if root_bone.rotation_mode == 'QUATERNION':
|
||
|
root_bone.rotation_quaternion = (1.0,0.0,0.0,0.0)
|
||
|
else:
|
||
|
root_bone.rotation_euler = (0.0,0.0,0.0)
|
||
|
test_aabb_min = Vector(mesh_obj.bound_box[0])
|
||
|
test_aabb_max = Vector(mesh_obj.bound_box[6])
|
||
|
|
||
|
for comp in range(3):
|
||
|
if test_aabb_min[comp] < root_aabb_min[comp]:
|
||
|
root_aabb_min[comp] = test_aabb_min[comp]
|
||
|
for comp in range(3):
|
||
|
if test_aabb_max[comp] > root_aabb_max[comp]:
|
||
|
root_aabb_max[comp] = test_aabb_max[comp]
|
||
|
|
||
|
ticket.write(struct.pack('ffffff',
|
||
|
root_aabb_min[0], root_aabb_min[1], root_aabb_min[2],
|
||
|
root_aabb_max[0], root_aabb_max[1], root_aabb_max[2]))
|
||
|
|
||
|
|
||
|
# Actions
|
||
|
anim_hashes = dict()
|
||
|
ticket.write(struct.pack('I', len(actor_data.actions)))
|
||
|
for action in actor_data.actions:
|
||
|
|
||
|
# Action name
|
||
|
ticket.write(action.name.encode() + b'\0')
|
||
|
|
||
|
if action.type == 'SINGLE':
|
||
|
ticket.write(struct.pack('I', 0))
|
||
|
action_name = action.subactions[0].name
|
||
|
if action_name not in bpy.data.actions:
|
||
|
raise RuntimeError("Error - unable to load action '{0}'".format(action_name))
|
||
|
if action_name in anim_hashes:
|
||
|
rani_hash = anim_hashes[action_name]
|
||
|
else:
|
||
|
action_obj = bpy.data.actions[action_name]
|
||
|
rani_db_id, rani_hash = package_rani(action_obj, res_db, heclpak, arg_path, arg_package)
|
||
|
res_db.register_dependency(act_db_id, rani_db_id)
|
||
|
anim_hashes[action_name] = rani_hash
|
||
|
ticket.write(rani_hash)
|
||
|
|
||
|
elif action.type == 'SEQUENCE':
|
||
|
ticket.write(struct.pack('II', 1, len(action.subactions)))
|
||
|
for subaction in action.subactions:
|
||
|
action_name = subaction.name
|
||
|
if action_name not in bpy.data.actions:
|
||
|
raise RuntimeError("Error - unable to load action '{0}'".format(action_name))
|
||
|
if action_name in anim_hashes:
|
||
|
rani_hash = anim_hashes[action_name]
|
||
|
else:
|
||
|
action_obj = bpy.data.actions[action_name]
|
||
|
rani_db_id, rani_hash = package_rani(action_obj,
|
||
|
res_db, heclpak, arg_path, arg_package)
|
||
|
res_db.register_dependency(act_db_id, rani_db_id)
|
||
|
anim_hashes[action_name] = rani_hash
|
||
|
ticket.write(rani_hash)
|
||
|
|
||
|
elif action.type == 'RANDOM':
|
||
|
ticket.write(struct.pack('III', 2, len(action.subactions), action.random_val))
|
||
|
for subaction in action.subactions:
|
||
|
action_name = subaction.name
|
||
|
if action_name not in bpy.data.actions:
|
||
|
raise RuntimeError("Error - unable to load action '{0}'".format(action_name))
|
||
|
if action_name in anim_hashes:
|
||
|
rani_hash = anim_hashes[action_name]
|
||
|
else:
|
||
|
action_obj = bpy.data.actions[action_name]
|
||
|
rani_db_id, rani_hash = package_rani(action_obj,
|
||
|
res_db, heclpak, arg_path, arg_package)
|
||
|
res_db.register_dependency(act_db_id, rani_db_id)
|
||
|
anim_hashes[action_name] = rani_hash
|
||
|
ticket.write(rani_hash)
|
||
|
|
||
|
|
||
|
# Cook
|
||
|
def cook(writebuffunc, platform, endianchar):
|
||
|
print('COOKING SACT')
|
||
|
|
||
|
|
||
|
# Panel draw
|
||
|
def draw(layout, context):
|
||
|
SACTSubtype.draw(layout.box(), context)
|
||
|
SACTAction.draw(layout.box(), context)
|
||
|
SACTEvent.draw(layout.box(), context)
|
||
|
|
||
|
|
||
|
# Registration
|
||
|
def register():
|
||
|
SACTSubtype.register()
|
||
|
SACTEvent.register()
|
||
|
SACTAction.register()
|
||
|
bpy.utils.register_class(SACTData)
|
||
|
bpy.types.Scene.hecl_sact_data = bpy.props.PointerProperty(type=SACTData)
|
||
|
|
||
|
def unregister():
|
||
|
bpy.utils.unregister_class(SACTData)
|
||
|
SACTSubtype.unregister()
|
||
|
SACTAction.unregister()
|
||
|
SACTEvent.unregister()
|