mirror of https://github.com/AxioDL/metaforce.git
initial actor cooking support
This commit is contained in:
parent
bcee8aa897
commit
fc64c4d4a4
|
@ -587,6 +587,7 @@ BlenderConnection::DataStream::Mesh::getContiguousSkinningVersion() const
|
||||||
Mesh newMesh = *this;
|
Mesh newMesh = *this;
|
||||||
newMesh.pos.clear();
|
newMesh.pos.clear();
|
||||||
newMesh.norm.clear();
|
newMesh.norm.clear();
|
||||||
|
newMesh.contiguousSkinVertCounts.clear();
|
||||||
newMesh.contiguousSkinVertCounts.reserve(skins.size());
|
newMesh.contiguousSkinVertCounts.reserve(skins.size());
|
||||||
for (size_t i=0 ; i<skins.size() ; ++i)
|
for (size_t i=0 ; i<skins.size() ; ++i)
|
||||||
{
|
{
|
||||||
|
@ -754,6 +755,174 @@ uint32_t BlenderConnection::DataStream::Mesh::SkinBanks::addSurface
|
||||||
return uint32_t(-1);
|
return uint32_t(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BlenderConnection::DataStream::Actor::Actor(BlenderConnection& conn)
|
||||||
|
{
|
||||||
|
uint32_t armCount;
|
||||||
|
conn._readBuf(&armCount, 4);
|
||||||
|
armatures.reserve(armCount);
|
||||||
|
for (uint32_t i=0 ; i<armCount ; ++i)
|
||||||
|
armatures.emplace_back(conn);
|
||||||
|
|
||||||
|
uint32_t subtypeCount;
|
||||||
|
conn._readBuf(&subtypeCount, 4);
|
||||||
|
subtypes.reserve(subtypeCount);
|
||||||
|
for (uint32_t i=0 ; i<subtypeCount ; ++i)
|
||||||
|
subtypes.emplace_back(conn);
|
||||||
|
|
||||||
|
uint32_t actionCount;
|
||||||
|
conn._readBuf(&actionCount, 4);
|
||||||
|
actions.reserve(actionCount);
|
||||||
|
for (uint32_t i=0 ; i<actionCount ; ++i)
|
||||||
|
actions.emplace_back(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlenderConnection::DataStream::Actor::Armature::Armature(BlenderConnection& conn)
|
||||||
|
{
|
||||||
|
uint32_t bufSz;
|
||||||
|
conn._readBuf(&bufSz, 4);
|
||||||
|
name.assign(bufSz, ' ');
|
||||||
|
conn._readBuf(&name[0], bufSz);
|
||||||
|
|
||||||
|
uint32_t boneCount;
|
||||||
|
conn._readBuf(&boneCount, 4);
|
||||||
|
bones.reserve(boneCount);
|
||||||
|
for (uint32_t i=0 ; i<boneCount ; ++i)
|
||||||
|
bones.emplace_back(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlenderConnection::DataStream::Actor::Armature::Bone::Bone(BlenderConnection& conn)
|
||||||
|
{
|
||||||
|
uint32_t bufSz;
|
||||||
|
conn._readBuf(&bufSz, 4);
|
||||||
|
name.assign(bufSz, ' ');
|
||||||
|
conn._readBuf(&name[0], bufSz);
|
||||||
|
|
||||||
|
origin.read(conn);
|
||||||
|
|
||||||
|
conn._readBuf(&parent, 4);
|
||||||
|
|
||||||
|
uint32_t childCount;
|
||||||
|
conn._readBuf(&childCount, 4);
|
||||||
|
children.reserve(childCount);
|
||||||
|
for (uint32_t i=0 ; i<childCount ; ++i)
|
||||||
|
{
|
||||||
|
children.emplace_back(0);
|
||||||
|
conn._readBuf(&children.back(), 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BlenderConnection::DataStream::Actor::Subtype::Subtype(BlenderConnection& conn)
|
||||||
|
{
|
||||||
|
uint32_t bufSz;
|
||||||
|
conn._readBuf(&bufSz, 4);
|
||||||
|
name.assign(bufSz, ' ');
|
||||||
|
conn._readBuf(&name[0], bufSz);
|
||||||
|
|
||||||
|
std::string meshPath;
|
||||||
|
conn._readBuf(&bufSz, 4);
|
||||||
|
if (bufSz)
|
||||||
|
{
|
||||||
|
meshPath.assign(bufSz, ' ');
|
||||||
|
conn._readBuf(&meshPath[0], bufSz);
|
||||||
|
SystemStringView meshPathAbs(meshPath);
|
||||||
|
|
||||||
|
SystemString meshPathRel =
|
||||||
|
conn.m_loadedBlend.getProject().getProjectRootPath().getProjectRelativeFromAbsolute(meshPathAbs);
|
||||||
|
mesh.assign(conn.m_loadedBlend.getProject().getProjectWorkingPath(), meshPathRel);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn._readBuf(&armature, 4);
|
||||||
|
|
||||||
|
uint32_t overlayCount;
|
||||||
|
conn._readBuf(&overlayCount, 4);
|
||||||
|
overlayMeshes.reserve(overlayCount);
|
||||||
|
for (uint32_t i=0 ; i<overlayCount ; ++i)
|
||||||
|
{
|
||||||
|
std::string overlayName;
|
||||||
|
conn._readBuf(&bufSz, 4);
|
||||||
|
overlayName.assign(bufSz, ' ');
|
||||||
|
conn._readBuf(&overlayName[0], bufSz);
|
||||||
|
|
||||||
|
std::string meshPath;
|
||||||
|
conn._readBuf(&bufSz, 4);
|
||||||
|
if (bufSz)
|
||||||
|
{
|
||||||
|
meshPath.assign(bufSz, ' ');
|
||||||
|
conn._readBuf(&meshPath[0], bufSz);
|
||||||
|
SystemStringView meshPathAbs(meshPath);
|
||||||
|
|
||||||
|
SystemString meshPathRel =
|
||||||
|
conn.m_loadedBlend.getProject().getProjectRootPath().getProjectRelativeFromAbsolute(meshPathAbs);
|
||||||
|
overlayMeshes.emplace_back(std::move(overlayName),
|
||||||
|
ProjectPath(conn.m_loadedBlend.getProject().getProjectWorkingPath(), meshPathRel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BlenderConnection::DataStream::Actor::Action::Action(BlenderConnection& conn)
|
||||||
|
{
|
||||||
|
uint32_t bufSz;
|
||||||
|
conn._readBuf(&bufSz, 4);
|
||||||
|
name.assign(bufSz, ' ');
|
||||||
|
conn._readBuf(&name[0], bufSz);
|
||||||
|
|
||||||
|
conn._readBuf(&interval, 4);
|
||||||
|
conn._readBuf(&additive, 1);
|
||||||
|
|
||||||
|
uint32_t frameCount;
|
||||||
|
conn._readBuf(&frameCount, 4);
|
||||||
|
frames.reserve(frameCount);
|
||||||
|
for (uint32_t i=0 ; i<frameCount ; ++i)
|
||||||
|
{
|
||||||
|
frames.emplace_back();
|
||||||
|
conn._readBuf(&frames.back(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t chanCount;
|
||||||
|
conn._readBuf(&chanCount, 4);
|
||||||
|
channels.reserve(chanCount);
|
||||||
|
for (uint32_t i=0 ; i<chanCount ; ++i)
|
||||||
|
channels.emplace_back(conn);
|
||||||
|
|
||||||
|
uint32_t aabbCount;
|
||||||
|
conn._readBuf(&aabbCount, 4);
|
||||||
|
subtypeAABBs.reserve(aabbCount);
|
||||||
|
for (uint32_t i=0 ; i<aabbCount ; ++i)
|
||||||
|
{
|
||||||
|
subtypeAABBs.emplace_back();
|
||||||
|
subtypeAABBs.back().first.read(conn);
|
||||||
|
subtypeAABBs.back().second.read(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BlenderConnection::DataStream::Actor::Action::Channel::Channel(BlenderConnection& conn)
|
||||||
|
{
|
||||||
|
uint32_t bufSz;
|
||||||
|
conn._readBuf(&bufSz, 4);
|
||||||
|
boneName.assign(bufSz, ' ');
|
||||||
|
conn._readBuf(&boneName[0], bufSz);
|
||||||
|
|
||||||
|
conn._readBuf(&attrMask, 4);
|
||||||
|
|
||||||
|
uint32_t keyCount;
|
||||||
|
conn._readBuf(&keyCount, 4);
|
||||||
|
keys.reserve(keyCount);
|
||||||
|
for (uint32_t i=0 ; i<keyCount ; ++i)
|
||||||
|
keys.emplace_back(conn, attrMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlenderConnection::DataStream::Actor::Action::Channel::Key::Key(BlenderConnection& conn, uint32_t attrMask)
|
||||||
|
{
|
||||||
|
if (attrMask & 1)
|
||||||
|
rotation.read(conn);
|
||||||
|
|
||||||
|
if (attrMask & 2)
|
||||||
|
position.read(conn);
|
||||||
|
|
||||||
|
if (attrMask & 4)
|
||||||
|
scale.read(conn);
|
||||||
|
}
|
||||||
|
|
||||||
void BlenderConnection::quitBlender()
|
void BlenderConnection::quitBlender()
|
||||||
{
|
{
|
||||||
_writeLine("QUIT");
|
_writeLine("QUIT");
|
||||||
|
|
|
@ -66,6 +66,13 @@ public:
|
||||||
bool saveBlend();
|
bool saveBlend();
|
||||||
void deleteBlend();
|
void deleteBlend();
|
||||||
|
|
||||||
|
enum ANIMCurveType
|
||||||
|
{
|
||||||
|
CurveRotate,
|
||||||
|
CurveTranslate,
|
||||||
|
CurveScale
|
||||||
|
};
|
||||||
|
|
||||||
class PyOutStream : public std::ostream
|
class PyOutStream : public std::ostream
|
||||||
{
|
{
|
||||||
friend class BlenderConnection;
|
friend class BlenderConnection;
|
||||||
|
@ -165,12 +172,7 @@ public:
|
||||||
unsigned m_totalCount = 0;
|
unsigned m_totalCount = 0;
|
||||||
bool m_inCurve = false;
|
bool m_inCurve = false;
|
||||||
public:
|
public:
|
||||||
enum CurveType
|
using CurveType = ANIMCurveType;
|
||||||
{
|
|
||||||
CurveRotate,
|
|
||||||
CurveTranslate,
|
|
||||||
CurveScale
|
|
||||||
};
|
|
||||||
ANIMOutStream(BlenderConnection* parent)
|
ANIMOutStream(BlenderConnection* parent)
|
||||||
: m_parent(parent)
|
: m_parent(parent)
|
||||||
{
|
{
|
||||||
|
@ -283,28 +285,43 @@ public:
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Intermediate mesh representation prepared by blender from a single mesh object */
|
/* Vector types with integrated stream reading constructor */
|
||||||
|
struct Vector2f
|
||||||
|
{
|
||||||
|
atVec2f val;
|
||||||
|
Vector2f() = default;
|
||||||
|
void read(BlenderConnection& conn) {conn._readBuf(&val, 8);}
|
||||||
|
Vector2f(BlenderConnection& conn) {read(conn);}
|
||||||
|
operator const atVec2f&() const {return val;}
|
||||||
|
};
|
||||||
|
struct Vector3f
|
||||||
|
{
|
||||||
|
atVec3f val;
|
||||||
|
Vector3f() = default;
|
||||||
|
void read(BlenderConnection& conn) {conn._readBuf(&val, 12);}
|
||||||
|
Vector3f(BlenderConnection& conn) {read(conn);}
|
||||||
|
operator const atVec3f&() const {return val;}
|
||||||
|
};
|
||||||
|
struct Vector4f
|
||||||
|
{
|
||||||
|
atVec4f val;
|
||||||
|
Vector4f() = default;
|
||||||
|
void read(BlenderConnection& conn) {conn._readBuf(&val, 16);}
|
||||||
|
Vector4f(BlenderConnection& conn) {read(conn);}
|
||||||
|
operator const atVec4f&() const {return val;}
|
||||||
|
};
|
||||||
|
struct Index
|
||||||
|
{
|
||||||
|
uint32_t val;
|
||||||
|
Index() = default;
|
||||||
|
void read(BlenderConnection& conn) {conn._readBuf(&val, 4);}
|
||||||
|
Index(BlenderConnection& conn) {read(conn);}
|
||||||
|
operator const uint32_t&() const {return val;}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Intermediate mesh representation prepared by blender from a single mesh object */
|
||||||
struct Mesh
|
struct Mesh
|
||||||
{
|
{
|
||||||
struct Vector2f
|
|
||||||
{
|
|
||||||
atVec2f val;
|
|
||||||
Vector2f(BlenderConnection& conn) {conn._readBuf(&val, 8);}
|
|
||||||
operator const atVec2f&() const {return val;}
|
|
||||||
};
|
|
||||||
struct Vector3f
|
|
||||||
{
|
|
||||||
atVec3f val;
|
|
||||||
Vector3f(BlenderConnection& conn) {conn._readBuf(&val, 12);}
|
|
||||||
operator const atVec3f&() const {return val;}
|
|
||||||
};
|
|
||||||
struct Index
|
|
||||||
{
|
|
||||||
uint32_t val;
|
|
||||||
Index(BlenderConnection& conn) {conn._readBuf(&val, 4);}
|
|
||||||
operator const uint32_t&() const {return val;}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum OutputMode
|
enum OutputMode
|
||||||
{
|
{
|
||||||
OutputTriangles,
|
OutputTriangles,
|
||||||
|
@ -401,10 +418,14 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Compile mesh by context */
|
/** Compile mesh by context (MESH blends only) */
|
||||||
Mesh compileMesh(Mesh::OutputMode outMode, int skinSlotCount=10,
|
Mesh compileMesh(Mesh::OutputMode outMode, int skinSlotCount=10,
|
||||||
Mesh::SurfProgFunc surfProg=[](int){})
|
Mesh::SurfProgFunc surfProg=[](int){})
|
||||||
{
|
{
|
||||||
|
if (m_parent->m_loadedType != TypeMesh)
|
||||||
|
BlenderLog.report(LogVisor::FatalError, _S("%s is not a MESH blend"),
|
||||||
|
m_parent->m_loadedBlend.getAbsolutePath().c_str());
|
||||||
|
|
||||||
char req[128];
|
char req[128];
|
||||||
snprintf(req, 128, "MESHCOMPILE %s %d",
|
snprintf(req, 128, "MESHCOMPILE %s %d",
|
||||||
MeshOutputModeString(outMode), skinSlotCount);
|
MeshOutputModeString(outMode), skinSlotCount);
|
||||||
|
@ -418,10 +439,14 @@ public:
|
||||||
return Mesh(*m_parent, outMode, skinSlotCount, surfProg);
|
return Mesh(*m_parent, outMode, skinSlotCount, surfProg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compile mesh by name */
|
/** Compile mesh by name (AREA blends only) */
|
||||||
Mesh compileMesh(const std::string& name, Mesh::OutputMode outMode, int skinSlotCount=10,
|
Mesh compileMesh(const std::string& name, Mesh::OutputMode outMode, int skinSlotCount=10,
|
||||||
Mesh::SurfProgFunc surfProg=[](int){})
|
Mesh::SurfProgFunc surfProg=[](int){})
|
||||||
{
|
{
|
||||||
|
if (m_parent->m_loadedType != TypeArea)
|
||||||
|
BlenderLog.report(LogVisor::FatalError, _S("%s is not an AREA blend"),
|
||||||
|
m_parent->m_loadedBlend.getAbsolutePath().c_str());
|
||||||
|
|
||||||
char req[128];
|
char req[128];
|
||||||
snprintf(req, 128, "MESHCOMPILENAME %s %s %d", name.c_str(),
|
snprintf(req, 128, "MESHCOMPILENAME %s %s %d", name.c_str(),
|
||||||
MeshOutputModeString(outMode), skinSlotCount);
|
MeshOutputModeString(outMode), skinSlotCount);
|
||||||
|
@ -435,10 +460,14 @@ public:
|
||||||
return Mesh(*m_parent, outMode, skinSlotCount, surfProg);
|
return Mesh(*m_parent, outMode, skinSlotCount, surfProg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compile all meshes into one */
|
/** Compile all meshes into one (AREA blends only) */
|
||||||
Mesh compileAllMeshes(Mesh::OutputMode outMode, int skinSlotCount=10, float maxOctantLength=5.0,
|
Mesh compileAllMeshes(Mesh::OutputMode outMode, int skinSlotCount=10, float maxOctantLength=5.0,
|
||||||
Mesh::SurfProgFunc surfProg=[](int){})
|
Mesh::SurfProgFunc surfProg=[](int){})
|
||||||
{
|
{
|
||||||
|
if (m_parent->m_loadedType != TypeArea)
|
||||||
|
BlenderLog.report(LogVisor::FatalError, _S("%s is not an AREA blend"),
|
||||||
|
m_parent->m_loadedBlend.getAbsolutePath().c_str());
|
||||||
|
|
||||||
char req[128];
|
char req[128];
|
||||||
snprintf(req, 128, "MESHCOMPILEALL %s %d %f",
|
snprintf(req, 128, "MESHCOMPILEALL %s %d %f",
|
||||||
MeshOutputModeString(outMode),
|
MeshOutputModeString(outMode),
|
||||||
|
@ -452,6 +481,87 @@ public:
|
||||||
|
|
||||||
return Mesh(*m_parent, outMode, skinSlotCount, surfProg);
|
return Mesh(*m_parent, outMode, skinSlotCount, surfProg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Intermediate actor representation prepared by blender from a single HECL actor blend */
|
||||||
|
struct Actor
|
||||||
|
{
|
||||||
|
struct Armature
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
struct Bone
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
Vector3f origin;
|
||||||
|
int32_t parent = -1;
|
||||||
|
std::vector<int32_t> children;
|
||||||
|
Bone(BlenderConnection& conn);
|
||||||
|
};
|
||||||
|
std::vector<Bone> bones;
|
||||||
|
Bone* lookupBone(const char* name)
|
||||||
|
{
|
||||||
|
for (Bone& b : bones)
|
||||||
|
if (!b.name.compare(name))
|
||||||
|
return &b;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Armature(BlenderConnection& conn);
|
||||||
|
};
|
||||||
|
std::vector<Armature> armatures;
|
||||||
|
|
||||||
|
struct Subtype
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
ProjectPath mesh;
|
||||||
|
int32_t armature = -1;
|
||||||
|
std::vector<std::pair<std::string, ProjectPath>> overlayMeshes;
|
||||||
|
Subtype(BlenderConnection& conn);
|
||||||
|
};
|
||||||
|
std::vector<Subtype> subtypes;
|
||||||
|
|
||||||
|
struct Action
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
float interval;
|
||||||
|
bool additive;
|
||||||
|
std::vector<int32_t> frames;
|
||||||
|
struct Channel
|
||||||
|
{
|
||||||
|
std::string boneName;
|
||||||
|
uint32_t attrMask;
|
||||||
|
struct Key
|
||||||
|
{
|
||||||
|
Vector4f rotation;
|
||||||
|
Vector3f position;
|
||||||
|
Vector3f scale;
|
||||||
|
Key(BlenderConnection& conn, uint32_t attrMask);
|
||||||
|
};
|
||||||
|
std::vector<Key> keys;
|
||||||
|
Channel(BlenderConnection& conn);
|
||||||
|
};
|
||||||
|
std::vector<Channel> channels;
|
||||||
|
std::vector<std::pair<Vector3f, Vector3f>> subtypeAABBs;
|
||||||
|
Action(BlenderConnection& conn);
|
||||||
|
};
|
||||||
|
std::vector<Action> actions;
|
||||||
|
|
||||||
|
Actor(BlenderConnection& conn);
|
||||||
|
};
|
||||||
|
|
||||||
|
Actor compileActor()
|
||||||
|
{
|
||||||
|
if (m_parent->m_loadedType != TypeActor)
|
||||||
|
BlenderLog.report(LogVisor::FatalError, _S("%s is not an ACTOR blend"),
|
||||||
|
m_parent->m_loadedBlend.getAbsolutePath().c_str());
|
||||||
|
|
||||||
|
m_parent->_writeLine("ACTORCOMPILE");
|
||||||
|
|
||||||
|
char readBuf[256];
|
||||||
|
m_parent->_readLine(readBuf, 256);
|
||||||
|
if (strcmp(readBuf, "OK"))
|
||||||
|
BlenderLog.report(LogVisor::FatalError, "unable to compile actor: %s", readBuf);
|
||||||
|
|
||||||
|
return Actor(*m_parent);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
DataStream beginData()
|
DataStream beginData()
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,10 +5,8 @@ list(APPEND PY_SOURCES
|
||||||
hecl/hmdl/__init__.py
|
hecl/hmdl/__init__.py
|
||||||
hecl/hmdl/HMDLMesh.py
|
hecl/hmdl/HMDLMesh.py
|
||||||
hecl/hmdl/HMDLShader.py
|
hecl/hmdl/HMDLShader.py
|
||||||
hecl/hmdl/HMDLSkin.py
|
|
||||||
hecl/sact/__init__.py
|
hecl/sact/__init__.py
|
||||||
hecl/sact/SACTAction.py
|
hecl/sact/SACTAction.py
|
||||||
hecl/sact/SACTEvent.py
|
|
||||||
hecl/sact/SACTSubtype.py
|
hecl/sact/SACTSubtype.py
|
||||||
hecl/srea/__init__.py)
|
hecl/srea/__init__.py)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
'''Root HECL addon package for Blender'''
|
|
||||||
|
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "HECL",
|
"name": "HECL",
|
||||||
"author": "Jack Andersen <jackoalan@gmail.com>",
|
"author": "Jack Andersen <jackoalan@gmail.com>",
|
||||||
"version": (1, 0),
|
"version": (1, 0),
|
||||||
"blender": (2, 75),
|
"blender": (2, 76),
|
||||||
"tracker_url": "https://github.com/RetroView/hecl/issues/new",
|
"tracker_url": "https://github.com/AxioDL/hecl/issues/new",
|
||||||
"location": "Properties > Scene > HECL",
|
"location": "Properties > Scene > HECL",
|
||||||
"description": "Enables blender to gather meshes, materials, and textures for hecl",
|
"description": "Enables blender to gather meshes, materials, and textures for hecl",
|
||||||
"category": "System"}
|
"category": "System"}
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
'''
|
|
||||||
HMDL Export Blender Addon
|
|
||||||
By Jack Andersen <jackoalan@gmail.com>
|
|
||||||
'''
|
|
||||||
|
|
||||||
import bpy, bmesh, operator, struct
|
import bpy, bmesh, operator, struct
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,3 @@
|
||||||
'''
|
|
||||||
HMDL Export Blender Addon
|
|
||||||
By Jack Andersen <jackoalan@gmail.com>
|
|
||||||
|
|
||||||
Traces the 'Blender Internal' shader node structure to generate a
|
|
||||||
HECL combiner string
|
|
||||||
'''
|
|
||||||
|
|
||||||
import bpy, bpy.path, os.path
|
import bpy, bpy.path, os.path
|
||||||
|
|
||||||
def get_texmap_idx(tex_list, name):
|
def get_texmap_idx(tex_list, name):
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
'''
|
|
||||||
HMDL Export Blender Addon
|
|
||||||
By Jack Andersen <jackoalan@gmail.com>
|
|
||||||
|
|
||||||
This file defines the `hmdl_skin` class to iteratively construct
|
|
||||||
a Skinning Info Section for HMDL files. Used by draw-format
|
|
||||||
generators to select an optimal skin entry for a draw primitive,
|
|
||||||
or have a new one established.
|
|
||||||
'''
|
|
||||||
|
|
||||||
import struct
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
class hmdl_skin:
|
|
||||||
|
|
||||||
# Set up with HMDL containing ARMATURE `object`
|
|
||||||
def __init__(self, max_bone_count, vertex_groups):
|
|
||||||
self.max_bone_count = max_bone_count
|
|
||||||
self.mesh_vertex_groups = vertex_groups
|
|
||||||
|
|
||||||
self.bone_arrays = []
|
|
||||||
|
|
||||||
|
|
||||||
# Augment bone array with loop vert and return weight array
|
|
||||||
# Returns 'None' if bone overflow
|
|
||||||
def augment_bone_array_with_lv(self, mesh_data, bone_array, loop_vert):
|
|
||||||
vertex = mesh_data.vertices[loop_vert[0].loop.vertex_index]
|
|
||||||
|
|
||||||
# Loop-vert weight array
|
|
||||||
weight_array = []
|
|
||||||
for i in range(len(bone_array)):
|
|
||||||
weight_array.append(0.0)
|
|
||||||
|
|
||||||
# Tentative bone additions
|
|
||||||
new_bones = []
|
|
||||||
|
|
||||||
|
|
||||||
# Determine which bones (vertex groups) belong to loop_vert
|
|
||||||
for group_elem in vertex.groups:
|
|
||||||
vertex_group = self.mesh_vertex_groups[group_elem.group]
|
|
||||||
|
|
||||||
if vertex_group.name not in bone_array:
|
|
||||||
|
|
||||||
# Detect bone overflow
|
|
||||||
if len(bone_array) + len(new_bones) >= self.max_bone_count:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Add to array otherwise
|
|
||||||
new_bones.append(vertex_group.name)
|
|
||||||
|
|
||||||
# Record bone weight
|
|
||||||
weight_array.append(group_elem.weight)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
# Record bone weight
|
|
||||||
weight_array[bone_array.index(vertex_group.name)] = group_elem.weight
|
|
||||||
|
|
||||||
|
|
||||||
# If we get here, no overflows; augment bone array and return weight array
|
|
||||||
bone_array.extend(new_bones)
|
|
||||||
return weight_array
|
|
||||||
|
|
||||||
|
|
||||||
# Augment triangle-strip bone array to rigging info
|
|
||||||
def augment_skin(self, bone_array):
|
|
||||||
if bone_array not in self.bone_arrays:
|
|
||||||
self.bone_arrays.append(bone_array)
|
|
||||||
return (len(self.bone_arrays)-1)
|
|
||||||
return self.bone_arrays.index(bone_array)
|
|
||||||
|
|
||||||
|
|
||||||
# Generate Rigging Info structure (call after all index-buffers generated)
|
|
||||||
def generate_rigging_info(self, bone_dict, endian_char):
|
|
||||||
|
|
||||||
skin_entries = []
|
|
||||||
for bone_array in self.bone_arrays:
|
|
||||||
skin_bytes = bytearray()
|
|
||||||
skin_bytes += struct.pack(endian_char + 'I', len(bone_array))
|
|
||||||
for bone in bone_array:
|
|
||||||
skin_bytes += struct.pack(endian_char + 'I', bone_dict[bone])
|
|
||||||
skin_entries.append(skin_bytes)
|
|
||||||
|
|
||||||
# Generate skinning data
|
|
||||||
info_bytes = bytearray()
|
|
||||||
info_bytes += struct.pack(endian_char + 'I', len(skin_entries))
|
|
||||||
|
|
||||||
cur_offset = len(skin_entries) * 4 + 4
|
|
||||||
for entry in skin_entries:
|
|
||||||
info_bytes += struct.pack(endian_char + 'I', cur_offset)
|
|
||||||
cur_offset += len(entry)
|
|
||||||
|
|
||||||
for entry in skin_entries:
|
|
||||||
info_bytes += entry
|
|
||||||
|
|
||||||
return info_bytes
|
|
||||||
|
|
|
@ -1,69 +1,6 @@
|
||||||
'''
|
|
||||||
HMDL Export Blender Addon
|
|
||||||
By Jack Andersen <jackoalan@gmail.com>
|
|
||||||
|
|
||||||
This Python module provides a generator implementation for
|
|
||||||
the 'HMDL' mesh format designed for use with HECL.
|
|
||||||
|
|
||||||
The format features three main sections:
|
|
||||||
* Shader table
|
|
||||||
* Skin-binding table
|
|
||||||
* Mesh table (VBOs [array,element], VAO attribs, drawing index)
|
|
||||||
|
|
||||||
The Shader table provides index-referenced binding points
|
|
||||||
for mesh-portions to use for rendering.
|
|
||||||
|
|
||||||
The Skin-binding table provides the runtime with identifiers
|
|
||||||
to use in ensuring the correct bone-transformations are bound
|
|
||||||
to the shader when rendering a specific primitive.
|
|
||||||
|
|
||||||
The Mesh table contains Vertex and Element buffers with interleaved
|
|
||||||
Positions, Normals, UV coordinates, and Weight Vectors
|
|
||||||
'''
|
|
||||||
|
|
||||||
import struct, bpy, bmesh
|
import struct, bpy, bmesh
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from . import HMDLShader, HMDLSkin, HMDLMesh
|
from . import HMDLShader, HMDLMesh
|
||||||
|
|
||||||
# Generate Skeleton Info structure (free-form tree structure)
|
|
||||||
def generate_skeleton_info(armature, endian_char='<'):
|
|
||||||
|
|
||||||
bones = []
|
|
||||||
for bone in armature.data.bones:
|
|
||||||
bone_bytes = bytearray()
|
|
||||||
|
|
||||||
# Write bone hash
|
|
||||||
bone_bytes += struct.pack(endian_char + 'I', hmdl_anim.hashbone(bone.name))
|
|
||||||
|
|
||||||
for comp in bone.head_local:
|
|
||||||
bone_bytes += struct.pack(endian_char + 'f', comp)
|
|
||||||
|
|
||||||
parent_idx = -1
|
|
||||||
if bone.parent:
|
|
||||||
parent_idx = armature.data.bones.find(bone.parent.name)
|
|
||||||
bone_bytes += struct.pack(endian_char + 'i', parent_idx)
|
|
||||||
|
|
||||||
bone_bytes += struct.pack(endian_char + 'I', len(bone.children))
|
|
||||||
|
|
||||||
for child in bone.children:
|
|
||||||
child_idx = armature.data.bones.find(child.name)
|
|
||||||
bone_bytes += struct.pack(endian_char + 'I', child_idx)
|
|
||||||
|
|
||||||
bones.append(bone_bytes)
|
|
||||||
|
|
||||||
# Generate bone tree data
|
|
||||||
info_bytes = bytearray()
|
|
||||||
info_bytes += struct.pack(endian_char + 'I', len(bones))
|
|
||||||
|
|
||||||
cur_offset = len(bones) * 4 + 4
|
|
||||||
for bone in bones:
|
|
||||||
info_bytes += struct.pack(endian_char + 'I', cur_offset)
|
|
||||||
cur_offset += len(bone)
|
|
||||||
|
|
||||||
for bone in bones:
|
|
||||||
info_bytes += bone
|
|
||||||
|
|
||||||
return info_bytes
|
|
||||||
|
|
||||||
def write_out_material(writebuf, mat, mesh_obj):
|
def write_out_material(writebuf, mat, mesh_obj):
|
||||||
hecl_str, texs = HMDLShader.shader(mat, mesh_obj)
|
hecl_str, texs = HMDLShader.shader(mat, mesh_obj)
|
||||||
|
|
|
@ -1,442 +0,0 @@
|
||||||
import bpy
|
|
||||||
|
|
||||||
# Loop event class
|
|
||||||
class hecl_actor_event_loop(bpy.types.PropertyGroup):
|
|
||||||
bool = bpy.props.BoolProperty(name="Loop Bool")
|
|
||||||
|
|
||||||
# UEVT event class
|
|
||||||
class hecl_actor_event_uevt(bpy.types.PropertyGroup):
|
|
||||||
type = bpy.props.IntProperty(name="UEVT Type")
|
|
||||||
bone_name = bpy.props.StringProperty(name="Bone Name")
|
|
||||||
|
|
||||||
# Effect event class
|
|
||||||
class hecl_actor_event_effect(bpy.types.PropertyGroup):
|
|
||||||
frame_count = bpy.props.IntProperty(name="Frame Count", min=0)
|
|
||||||
uid = bpy.props.StringProperty(name="Effect UID")
|
|
||||||
bone_name = bpy.props.StringProperty(name="Bone Name")
|
|
||||||
scale = bpy.props.FloatProperty(name="Scale", description="Proportional spacial scale")
|
|
||||||
transform_mode = bpy.props.EnumProperty(name="Transform Mode", description="How the bone will transform the effect",
|
|
||||||
items=[('STATIONARY', "Stationary", "Effect emitter will be transformed in bone-space, then retained"),
|
|
||||||
('WORLD', "World", "Effect emitter will be transformed in bone-space"),
|
|
||||||
('LOCAL', "Local", "Entire effect will be transformed in bone-space")])
|
|
||||||
|
|
||||||
# Sound event class
|
|
||||||
class hecl_actor_event_sound(bpy.types.PropertyGroup):
|
|
||||||
sound_id = bpy.props.StringProperty(name="Sound ID")
|
|
||||||
ref_amp = bpy.props.FloatProperty(name="Reference Amplitude")
|
|
||||||
ref_dist = bpy.props.FloatProperty(name="Reference Distance")
|
|
||||||
|
|
||||||
# Name update
|
|
||||||
def update_name(self, context):
|
|
||||||
if bpy.context.scene.hecl_type == 'ACTOR':
|
|
||||||
clear_event_markers(bpy.context.scene.hecl_sact_data, context)
|
|
||||||
update_action_events(None)
|
|
||||||
active_event_update(self, context)
|
|
||||||
|
|
||||||
|
|
||||||
# Actor event class
|
|
||||||
class hecl_actor_event(bpy.types.PropertyGroup):
|
|
||||||
name = bpy.props.StringProperty(name="Event Name",
|
|
||||||
update=update_name)
|
|
||||||
type = bpy.props.EnumProperty(name="Event Type",
|
|
||||||
items=[('LOOP', "Loop", "Loop Event"),
|
|
||||||
('UEVT', "UEVT", "UEVT Event"),
|
|
||||||
('EFFECT', "Effect", "Effect Event"),
|
|
||||||
('SOUND', "Sound", "Sound Event")],
|
|
||||||
default='LOOP')
|
|
||||||
|
|
||||||
index = bpy.props.IntProperty(name="Event Index")
|
|
||||||
time = bpy.props.FloatProperty(name="Event Time")
|
|
||||||
props = bpy.props.StringProperty(name="Event props")
|
|
||||||
|
|
||||||
loop_data = bpy.props.PointerProperty(name="Loop event data",
|
|
||||||
type=hecl_actor_event_loop)
|
|
||||||
uevt_data = bpy.props.PointerProperty(name="UEVT event data",
|
|
||||||
type=hecl_actor_event_uevt)
|
|
||||||
effect_data = bpy.props.PointerProperty(name="Effect event data",
|
|
||||||
type=hecl_actor_event_effect)
|
|
||||||
sound_data = bpy.props.PointerProperty(name="Sound event data",
|
|
||||||
type=hecl_actor_event_sound)
|
|
||||||
|
|
||||||
|
|
||||||
# Panel draw
|
|
||||||
def draw(layout, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
|
|
||||||
armature = None
|
|
||||||
if actor_data.active_subtype >= 0:
|
|
||||||
if actor_data.active_subtype in range(len(actor_data.subtypes)):
|
|
||||||
subtype = actor_data.subtypes[actor_data.active_subtype]
|
|
||||||
if subtype and subtype.linked_armature in bpy.data.objects:
|
|
||||||
armature = bpy.data.objects[subtype.linked_armature]
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.alignment = 'LEFT'
|
|
||||||
row.prop(actor_data, 'show_events', text="Events", icon='PREVIEW_RANGE', emboss=False)
|
|
||||||
if actor_data.show_events:
|
|
||||||
|
|
||||||
# Get action
|
|
||||||
action_data = None
|
|
||||||
subaction_data = None
|
|
||||||
if actor_data.active_action in range(len(actor_data.actions)):
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
if action_data.type == 'SINGLE':
|
|
||||||
subaction_data = action_data.subactions[0]
|
|
||||||
elif action_data.type == 'SEQUENCE' or action_data.type == 'RANDOM':
|
|
||||||
if action_data.active_subaction in range(len(action_data.subactions)):
|
|
||||||
subaction_data = action_data.subactions[action_data.active_subaction]
|
|
||||||
|
|
||||||
# Validate
|
|
||||||
if subaction_data is None:
|
|
||||||
layout.label("(Sub)action not selected in 'Actions'", icon='ERROR')
|
|
||||||
else:
|
|
||||||
|
|
||||||
if subaction_data.name == '':
|
|
||||||
layout.label("Action not set", icon='ERROR')
|
|
||||||
elif subaction_data.name not in bpy.data.actions:
|
|
||||||
layout.label("Action '"+subaction_data.name+"' not found", icon='ERROR')
|
|
||||||
else:
|
|
||||||
action = bpy.data.actions[subaction_data.name]
|
|
||||||
|
|
||||||
# Event list
|
|
||||||
row = layout.row()
|
|
||||||
row.template_list("UI_UL_list", "SCENE_UL_hecl_actor_subaction_events",
|
|
||||||
action, 'hecl_events', action, 'hecl_active_event')
|
|
||||||
col = row.column(align=True)
|
|
||||||
col.operator("scene.hecl_actor_subaction_event_add", icon="ZOOMIN", text="")
|
|
||||||
col.operator("scene.hecl_actor_subaction_event_remove", icon="ZOOMOUT", text="")
|
|
||||||
col.separator()
|
|
||||||
col.operator("scene.hecl_actor_subaction_event_move_up", icon="TRIA_UP", text="")
|
|
||||||
col.operator("scene.hecl_actor_subaction_event_move_down", icon="TRIA_DOWN", text="")
|
|
||||||
|
|
||||||
|
|
||||||
if len(action.hecl_events) and action.hecl_active_event >= 0:
|
|
||||||
event = action.hecl_events[action.hecl_active_event]
|
|
||||||
|
|
||||||
layout.prop(event, 'name', text="Name")
|
|
||||||
layout.prop(event, 'index', text="Index")
|
|
||||||
layout.prop(event, 'props', text="Props")
|
|
||||||
layout.label('Marker Time: ' + '{:g}'.format(event.time), icon='MARKER_HLT')
|
|
||||||
|
|
||||||
layout.label("Event Type:")
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop_enum(event, 'type', 'LOOP')
|
|
||||||
row.prop_enum(event, 'type', 'UEVT')
|
|
||||||
row.prop_enum(event, 'type', 'EFFECT')
|
|
||||||
row.prop_enum(event, 'type', 'SOUND')
|
|
||||||
|
|
||||||
if event.type == 'LOOP':
|
|
||||||
loop_data = event.loop_data
|
|
||||||
layout.prop(loop_data, 'bool')
|
|
||||||
|
|
||||||
elif event.type == 'UEVT':
|
|
||||||
uevt_data = event.uevt_data
|
|
||||||
layout.prop(uevt_data, 'type')
|
|
||||||
layout.prop(uevt_data, 'bone_name')
|
|
||||||
|
|
||||||
elif event.type == 'EFFECT':
|
|
||||||
effect_data = event.effect_data
|
|
||||||
layout.prop(effect_data, 'frame_count')
|
|
||||||
layout.prop(effect_data, 'uid')
|
|
||||||
if armature:
|
|
||||||
layout.prop_search(effect_data, 'bone_name', armature.data, 'bones')
|
|
||||||
else:
|
|
||||||
layout.prop(effect_data, 'bone_name')
|
|
||||||
layout.prop(effect_data, 'scale')
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop_enum(effect_data, 'transform_mode', 'STATIONARY')
|
|
||||||
row.prop_enum(effect_data, 'transform_mode', 'WORLD')
|
|
||||||
row.prop_enum(effect_data, 'transform_mode', 'LOCAL')
|
|
||||||
|
|
||||||
elif event.type == 'SOUND':
|
|
||||||
sound_data = event.sound_data
|
|
||||||
layout.prop(sound_data, 'sound_id')
|
|
||||||
layout.prop(sound_data, 'ref_amp')
|
|
||||||
layout.prop(sound_data, 'ref_dist')
|
|
||||||
|
|
||||||
# Clear event markers not in active event
|
|
||||||
def clear_event_markers(actor_data, context):
|
|
||||||
for marker in context.scene.timeline_markers:
|
|
||||||
if marker.name.startswith('hecl_'):
|
|
||||||
context.scene.timeline_markers.remove(marker)
|
|
||||||
|
|
||||||
# Event marker update
|
|
||||||
@bpy.app.handlers.persistent
|
|
||||||
def update_action_events(dummy):
|
|
||||||
context = bpy.context
|
|
||||||
if context.scene.hecl_type == 'ACTOR':
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
|
|
||||||
if actor_data.active_action in range(len(actor_data.actions)):
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
if action_data.name in bpy.data.actions:
|
|
||||||
action_obj =\
|
|
||||||
bpy.data.actions[action_data.name]
|
|
||||||
for i in range(len(action_obj.hecl_events)):
|
|
||||||
event = action_obj.hecl_events[i]
|
|
||||||
marker_name = 'hecl_' + str(action_obj.hecl_index) + '_' + str(i) + '_' + event.name
|
|
||||||
if marker_name in context.scene.timeline_markers:
|
|
||||||
marker = context.scene.timeline_markers[marker_name]
|
|
||||||
event_time = marker.frame / action_obj.hecl_fps
|
|
||||||
if event_time != event.time:
|
|
||||||
event.time = event_time
|
|
||||||
else:
|
|
||||||
marker = context.scene.timeline_markers.new(marker_name)
|
|
||||||
marker.frame = event.time * action_obj.hecl_fps
|
|
||||||
marker.select = False
|
|
||||||
|
|
||||||
if i != action_obj.hecl_active_event and marker.select:
|
|
||||||
action_obj.hecl_active_event = i
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Event 'add' operator
|
|
||||||
class hecl_actor_subaction_event_add(bpy.types.Operator):
|
|
||||||
bl_idname = "scene.hecl_actor_subaction_event_add"
|
|
||||||
bl_label = "New HECL Actor Event"
|
|
||||||
bl_description = "Add New HECL Actor Event to active Sub-action"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
check = (context.scene is not None and
|
|
||||||
not context.scene.library and
|
|
||||||
context.scene.hecl_type == 'ACTOR' and
|
|
||||||
len(actor_data.actions) and
|
|
||||||
actor_data.active_action >= 0 and
|
|
||||||
len(actor_data.actions[actor_data.active_action].subactions) and
|
|
||||||
actor_data.actions[actor_data.active_action].active_subaction >= 0)
|
|
||||||
if not check:
|
|
||||||
return False
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
subaction_data = action_data.subactions[action_data.active_subaction]
|
|
||||||
return subaction_data.name in bpy.data.actions
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
subaction_data = action_data.subactions[action_data.active_subaction]
|
|
||||||
blend_action = bpy.data.actions[subaction_data.name]
|
|
||||||
event_name = 'SubactionEvent'
|
|
||||||
if event_name in blend_action.hecl_events:
|
|
||||||
event_name = 'SubactionEvent.001'
|
|
||||||
event_idx = 1
|
|
||||||
while event_name in blend_action.hecl_events:
|
|
||||||
event_idx += 1
|
|
||||||
event_name = 'SubactionEvent.{:0>3}'.format(event_idx)
|
|
||||||
event = blend_action.hecl_events.add()
|
|
||||||
event.name = event_name
|
|
||||||
action_obj =\
|
|
||||||
bpy.data.actions[subaction_data.name]
|
|
||||||
event.time = (context.scene.frame_current / (context.scene.render.frame_map_new / context.scene.render.frame_map_old)) / action_obj.hecl_fps
|
|
||||||
blend_action.hecl_active_event = len(blend_action.hecl_events)-1
|
|
||||||
|
|
||||||
if not bpy.app.background:
|
|
||||||
update_action_events(None)
|
|
||||||
active_event_update(self, context)
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
# Event 'remove' operator
|
|
||||||
class hecl_actor_subaction_event_remove(bpy.types.Operator):
|
|
||||||
bl_idname = "scene.hecl_actor_subaction_event_remove"
|
|
||||||
bl_label = "Remove HECL Actor Event"
|
|
||||||
bl_description = "Remove HECL Actor Event from active Sub-action"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
check = (context.scene is not None and
|
|
||||||
not context.scene.library and
|
|
||||||
context.scene.hecl_type == 'ACTOR' and
|
|
||||||
len(actor_data.actions) and
|
|
||||||
actor_data.active_action >= 0 and
|
|
||||||
len(actor_data.actions[actor_data.active_action].subactions) and
|
|
||||||
actor_data.actions[actor_data.active_action].active_subaction >= 0)
|
|
||||||
if not check:
|
|
||||||
return False
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
subaction_data = action_data.subactions[action_data.active_subaction]
|
|
||||||
if subaction_data.name not in bpy.data.actions:
|
|
||||||
return False
|
|
||||||
blend_action = bpy.data.actions[subaction_data.name]
|
|
||||||
return blend_action.hecl_active_event in range(len(blend_action.hecl_events))
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
subaction_data = action_data.subactions[action_data.active_subaction]
|
|
||||||
blend_action = bpy.data.actions[subaction_data.name]
|
|
||||||
event_name = blend_action.hecl_events[blend_action.hecl_active_event].name
|
|
||||||
blend_action.hecl_events.remove(blend_action.hecl_active_event)
|
|
||||||
|
|
||||||
marker_name = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name
|
|
||||||
if marker_name in context.scene.timeline_markers:
|
|
||||||
context.scene.timeline_markers.remove(context.scene.timeline_markers[marker_name])
|
|
||||||
|
|
||||||
blend_action.hecl_active_event -= 1
|
|
||||||
if blend_action.hecl_active_event == -1:
|
|
||||||
blend_action.hecl_active_event = 0
|
|
||||||
|
|
||||||
clear_event_markers(actor_data, context)
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
# Event 'move down' operator
|
|
||||||
class hecl_actor_subaction_event_move_down(bpy.types.Operator):
|
|
||||||
bl_idname = "scene.hecl_actor_subaction_event_move_down"
|
|
||||||
bl_label = "Move HECL Actor Event Down in Stack"
|
|
||||||
bl_description = "Move HECL Actor Event down in stack from active Sub-action"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
check = (context.scene is not None and
|
|
||||||
not context.scene.library and
|
|
||||||
context.scene.hecl_type == 'ACTOR' and
|
|
||||||
len(actor_data.actions) and
|
|
||||||
actor_data.active_action >= 0 and
|
|
||||||
len(actor_data.actions[actor_data.active_action].subactions) and
|
|
||||||
actor_data.actions[actor_data.active_action].active_subaction >= 0)
|
|
||||||
if not check:
|
|
||||||
return False
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
subaction_data = action_data.subactions[action_data.active_subaction]
|
|
||||||
if subaction_data.name not in bpy.data.actions:
|
|
||||||
return False
|
|
||||||
blend_action = bpy.data.actions[subaction_data.name]
|
|
||||||
return (blend_action.hecl_active_event in range(len(blend_action.hecl_events)) and
|
|
||||||
blend_action.hecl_active_event < len(blend_action.hecl_events) - 1)
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
subaction_data = action_data.subactions[action_data.active_subaction]
|
|
||||||
blend_action = bpy.data.actions[subaction_data.name]
|
|
||||||
event_name_a = blend_action.hecl_events[blend_action.hecl_active_event].name
|
|
||||||
event_name_b = blend_action.hecl_events[blend_action.hecl_active_event + 1].name
|
|
||||||
blend_action.hecl_events.move(blend_action.hecl_active_event, blend_action.hecl_active_event + 1)
|
|
||||||
|
|
||||||
marker_name_a = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name_a
|
|
||||||
marker_a = None
|
|
||||||
if marker_name_a in context.scene.timeline_markers:
|
|
||||||
marker_a = context.scene.timeline_markers[marker_name_a]
|
|
||||||
|
|
||||||
marker_name_b = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event + 1) + '_' + event_name_b
|
|
||||||
marker_b = None
|
|
||||||
if marker_name_b in context.scene.timeline_markers:
|
|
||||||
marker_b = context.scene.timeline_markers[marker_name_b]
|
|
||||||
|
|
||||||
if marker_a and marker_b:
|
|
||||||
marker_a.name =\
|
|
||||||
'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event + 1) + '_' + event_name_a
|
|
||||||
marker_b.name =\
|
|
||||||
'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name_b
|
|
||||||
|
|
||||||
blend_action.hecl_active_event += 1
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
# Event 'move up' operator
|
|
||||||
class hecl_actor_subaction_event_move_up(bpy.types.Operator):
|
|
||||||
bl_idname = "scene.hecl_actor_subaction_event_move_up"
|
|
||||||
bl_label = "Move HECL Actor Event Up in Stack"
|
|
||||||
bl_description = "Move HECL Actor Event up in stack from active Sub-action"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
check = (context.scene is not None and
|
|
||||||
not context.scene.library and
|
|
||||||
context.scene.hecl_type == 'ACTOR' and
|
|
||||||
len(actor_data.actions) and
|
|
||||||
actor_data.active_action >= 0 and
|
|
||||||
len(actor_data.actions[actor_data.active_action].subactions) and
|
|
||||||
actor_data.actions[actor_data.active_action].active_subaction >= 0)
|
|
||||||
if not check:
|
|
||||||
return False
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
subaction_data = action_data.subactions[action_data.active_subaction]
|
|
||||||
if subaction_data.name not in bpy.data.actions:
|
|
||||||
return False
|
|
||||||
blend_action = bpy.data.actions[subaction_data.name]
|
|
||||||
return (blend_action.hecl_active_event in range(len(blend_action.hecl_events)) and
|
|
||||||
blend_action.hecl_active_event > 0)
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
subaction_data = action_data.subactions[action_data.active_subaction]
|
|
||||||
blend_action = bpy.data.actions[subaction_data.name]
|
|
||||||
event_name_a = blend_action.hecl_events[blend_action.hecl_active_event].name
|
|
||||||
event_name_b = blend_action.hecl_events[blend_action.hecl_active_event - 1].name
|
|
||||||
blend_action.hecl_events.move(blend_action.hecl_active_event, blend_action.hecl_active_event - 1)
|
|
||||||
|
|
||||||
marker_name_a = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name_a
|
|
||||||
marker_a = None
|
|
||||||
if marker_name_a in context.scene.timeline_markers:
|
|
||||||
marker_a = context.scene.timeline_markers[marker_name_a]
|
|
||||||
|
|
||||||
marker_name_b = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event - 1) + '_' + event_name_b
|
|
||||||
marker_b = None
|
|
||||||
if marker_name_b in context.scene.timeline_markers:
|
|
||||||
marker_b = context.scene.timeline_markers[marker_name_b]
|
|
||||||
|
|
||||||
if marker_a and marker_b:
|
|
||||||
marker_a.name =\
|
|
||||||
'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event - 1) + '_' + event_name_a
|
|
||||||
marker_b.name =\
|
|
||||||
'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name_b
|
|
||||||
|
|
||||||
blend_action.hecl_active_event -= 1
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
def active_event_update(self, context):
|
|
||||||
actor_data = context.scene.hecl_sact_data
|
|
||||||
if actor_data.active_action in range(len(actor_data.actions)):
|
|
||||||
action_data = actor_data.actions[actor_data.active_action]
|
|
||||||
for marker in context.scene.timeline_markers:
|
|
||||||
if marker.name.startswith('hecl_'):
|
|
||||||
blend_action = bpy.data.actions[action_data.name]
|
|
||||||
event_name = blend_action.hecl_events[blend_action.hecl_active_event].name
|
|
||||||
if marker.name == 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name:
|
|
||||||
marker.select = True
|
|
||||||
else:
|
|
||||||
marker.select = False
|
|
||||||
|
|
||||||
# Registration
|
|
||||||
def register():
|
|
||||||
bpy.utils.register_class(hecl_actor_event_loop)
|
|
||||||
bpy.utils.register_class(hecl_actor_event_uevt)
|
|
||||||
bpy.utils.register_class(hecl_actor_event_effect)
|
|
||||||
bpy.utils.register_class(hecl_actor_event_sound)
|
|
||||||
bpy.utils.register_class(hecl_actor_event)
|
|
||||||
bpy.utils.register_class(hecl_actor_subaction_event_add)
|
|
||||||
bpy.utils.register_class(hecl_actor_subaction_event_remove)
|
|
||||||
bpy.utils.register_class(hecl_actor_subaction_event_move_down)
|
|
||||||
bpy.utils.register_class(hecl_actor_subaction_event_move_up)
|
|
||||||
bpy.types.Action.hecl_events = bpy.props.CollectionProperty(name="HECL action event",
|
|
||||||
type=hecl_actor_event)
|
|
||||||
bpy.types.Action.hecl_active_event = bpy.props.IntProperty(name="HECL active action event",
|
|
||||||
default=0,
|
|
||||||
update=active_event_update)
|
|
||||||
if not bpy.app.background and update_action_events not in bpy.app.handlers.scene_update_post:
|
|
||||||
bpy.app.handlers.scene_update_post.append(update_action_events)
|
|
||||||
|
|
||||||
def unregister():
|
|
||||||
if update_action_events in bpy.app.handlers.scene_update_post:
|
|
||||||
bpy.app.handlers.scene_update_post.remove(update_action_events)
|
|
||||||
bpy.utils.unregister_class(hecl_actor_event)
|
|
||||||
bpy.utils.unregister_class(hecl_actor_event_loop)
|
|
||||||
bpy.utils.unregister_class(hecl_actor_event_uevt)
|
|
||||||
bpy.utils.unregister_class(hecl_actor_event_effect)
|
|
||||||
bpy.utils.unregister_class(hecl_actor_event_sound)
|
|
||||||
bpy.utils.unregister_class(hecl_actor_subaction_event_add)
|
|
||||||
bpy.utils.unregister_class(hecl_actor_subaction_event_remove)
|
|
||||||
bpy.utils.unregister_class(hecl_actor_subaction_event_move_down)
|
|
||||||
bpy.utils.unregister_class(hecl_actor_subaction_event_move_up)
|
|
|
@ -236,15 +236,15 @@ class SACTSubtypeOverlay_add(bpy.types.Operator):
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
actor_data = context.scene.hecl_sact_data
|
actor_data = context.scene.hecl_sact_data
|
||||||
subtype = actor_data.subtypes[actor_data.active_subtype]
|
subtype = actor_data.subtypes[actor_data.active_subtype]
|
||||||
overlay_name = 'ActorMesh'
|
overlay_name = 'ActorOverlay'
|
||||||
if overlay_name in subtype.overlays:
|
if overlay_name in subtype.overlays:
|
||||||
overlay_name = 'ActorMesh.001'
|
overlay_name = 'ActorOverlay.001'
|
||||||
overlay_idx = 1
|
overlay_idx = 1
|
||||||
while overlay_name in subtype.overlays:
|
while overlay_name in subtype.overlays:
|
||||||
overlay_idx += 1
|
overlay_idx += 1
|
||||||
overlay_name = 'ActorMesh.{:0>3}'.format(overlay_idx)
|
overlay_name = 'ActorOverlay.{:0>3}'.format(overlay_idx)
|
||||||
overlay = subtype.overlays.add()
|
overlay = subtype.overlays.add()
|
||||||
mesh.name = overlay_name
|
overlay.name = overlay_name
|
||||||
subtype.active_overlay = len(subtype.overlays)-1
|
subtype.active_overlay = len(subtype.overlays)-1
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
|
@ -2,11 +2,12 @@ from . import SACTSubtype, SACTAction, ANIM
|
||||||
from .. import hmdl
|
from .. import hmdl
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import bpy.path
|
||||||
import re
|
import re
|
||||||
import os.path
|
import os.path
|
||||||
import posixpath
|
import posixpath
|
||||||
import struct
|
import struct
|
||||||
from mathutils import Vector
|
from mathutils import Vector, Quaternion, Euler
|
||||||
|
|
||||||
# Actor data class
|
# Actor data class
|
||||||
class SACTData(bpy.types.PropertyGroup):
|
class SACTData(bpy.types.PropertyGroup):
|
||||||
|
@ -25,217 +26,289 @@ class SACTData(bpy.types.PropertyGroup):
|
||||||
show_actions =\
|
show_actions =\
|
||||||
bpy.props.BoolProperty()
|
bpy.props.BoolProperty()
|
||||||
|
|
||||||
#show_events =\
|
# Regex RNA path matchers
|
||||||
#bpy.props.BoolProperty()
|
scale_matcher = re.compile(r'pose.bones\["(\S+)"\].scale')
|
||||||
|
rotation_matcher = re.compile(r'pose.bones\["(\S+)"\].rotation')
|
||||||
|
location_matcher = re.compile(r'pose.bones\["(\S+)"\].location')
|
||||||
|
|
||||||
# A Routine to resolve HECL DAG-relative paths while ensuring database path-constraints
|
def write_action_channels(writebuf, action):
|
||||||
def resolve_local_path(blend_path, rel_path):
|
# Set of frame indices
|
||||||
if not rel_path.startswith('//'):
|
frame_set = set()
|
||||||
raise RuntimeError("Files must have relative paths")
|
|
||||||
blend_dir = os.path.split(blend_path)[0]
|
# Set of unique bone names
|
||||||
image_comps = re.split('/|\\\\', rel_path[2:])
|
bone_set = []
|
||||||
start_idx = 0
|
|
||||||
for comp in image_comps:
|
# Scan through all fcurves to build animated bone set
|
||||||
if comp == '..':
|
for fcurve in action.fcurves:
|
||||||
start_idx += 1
|
data_path = fcurve.data_path
|
||||||
if not blend_dir:
|
scale_match = scale_matcher.match(data_path)
|
||||||
raise RuntimeError("Relative file path has exceeded DAG root")
|
rotation_match = rotation_matcher.match(data_path)
|
||||||
blend_dir = os.path.split(blend_dir)[0]
|
location_match = location_matcher.match(data_path)
|
||||||
|
|
||||||
|
if scale_match:
|
||||||
|
if scale_match.group(1) not in bone_set:
|
||||||
|
bone_set.append(scale_match.group(1))
|
||||||
|
elif rotation_match:
|
||||||
|
if rotation_match.group(1) not in bone_set:
|
||||||
|
bone_set.append(rotation_match.group(1))
|
||||||
|
elif location_match:
|
||||||
|
if location_match.group(1) not in bone_set:
|
||||||
|
bone_set.append(location_match.group(1))
|
||||||
else:
|
else:
|
||||||
break
|
continue
|
||||||
retval = blend_dir
|
|
||||||
for i in range(len(image_comps)-start_idx):
|
# Count unified keyframes for interleaving channel data
|
||||||
if retval:
|
for key in fcurve.keyframe_points:
|
||||||
retval += '/'
|
frame_set.add(int(key.co[0]))
|
||||||
retval += image_comps[start_idx+i]
|
|
||||||
return posixpath.relpath(retval)
|
# Build bone table
|
||||||
|
bone_list = []
|
||||||
|
for bone in bone_set:
|
||||||
|
fc_dict = dict()
|
||||||
|
rotation_mode = None
|
||||||
|
property_bits = 0
|
||||||
|
for fcurve in action.fcurves:
|
||||||
|
if fcurve.data_path == 'pose.bones["'+bone+'"].scale':
|
||||||
|
if 'scale' not in fc_dict:
|
||||||
|
fc_dict['scale'] = [None, None, None]
|
||||||
|
property_bits |= 4
|
||||||
|
fc_dict['scale'][fcurve.array_index] = fcurve
|
||||||
|
elif fcurve.data_path == 'pose.bones["'+bone+'"].rotation_euler':
|
||||||
|
if 'rotation_euler' not in fc_dict:
|
||||||
|
fc_dict['rotation_euler'] = [None, None, None]
|
||||||
|
rotation_mode = 'rotation_euler'
|
||||||
|
property_bits |= 1
|
||||||
|
fc_dict['rotation_euler'][fcurve.array_index] = fcurve
|
||||||
|
elif fcurve.data_path == 'pose.bones["'+bone+'"].rotation_quaternion':
|
||||||
|
if 'rotation_quaternion' not in fc_dict:
|
||||||
|
fc_dict['rotation_quaternion'] = [None, None, None, None]
|
||||||
|
rotation_mode = 'rotation_quaternion'
|
||||||
|
property_bits |= 1
|
||||||
|
fc_dict['rotation_quaternion'][fcurve.array_index] = fcurve
|
||||||
|
elif fcurve.data_path == 'pose.bones["'+bone+'"].rotation_axis_angle':
|
||||||
|
if 'rotation_axis_angle' not in fc_dict:
|
||||||
|
fc_dict['rotation_axis_angle'] = [None, None, None, None]
|
||||||
|
rotation_mode = 'rotation_axis_angle'
|
||||||
|
property_bits |= 1
|
||||||
|
fc_dict['rotation_axis_angle'][fcurve.array_index] = fcurve
|
||||||
|
elif fcurve.data_path == 'pose.bones["'+bone+'"].location':
|
||||||
|
if 'location' not in fc_dict:
|
||||||
|
fc_dict['location'] = [None, None, None]
|
||||||
|
property_bits |= 2
|
||||||
|
fc_dict['location'][fcurve.array_index] = fcurve
|
||||||
|
bone_list.append((bone, rotation_mode, fc_dict, property_bits))
|
||||||
|
|
||||||
|
# Write out frame indices
|
||||||
|
sorted_frames = sorted(frame_set)
|
||||||
|
writebuf(struct.pack('I', len(sorted_frames)))
|
||||||
|
for frame in sorted_frames:
|
||||||
|
writebuf(struct.pack('i', frame))
|
||||||
|
|
||||||
|
# Interleave / interpolate keyframe data
|
||||||
|
writebuf(struct.pack('I', len(bone_list)))
|
||||||
|
for bone in bone_list:
|
||||||
|
|
||||||
|
bone_name = bone[0]
|
||||||
|
rotation_mode = bone[1]
|
||||||
|
fc_dict = bone[2]
|
||||||
|
property_bits = bone[3]
|
||||||
|
|
||||||
|
writebuf(struct.pack('I', len(bone_name)))
|
||||||
|
writebuf(bone_name.encode())
|
||||||
|
|
||||||
|
writebuf(struct.pack('I', property_bits))
|
||||||
|
|
||||||
|
writebuf(struct.pack('I', len(sorted_frames)))
|
||||||
|
for frame in sorted_frames:
|
||||||
|
|
||||||
|
# Rotation curves
|
||||||
|
if rotation_mode == 'rotation_quaternion':
|
||||||
|
writevec = [0.0]*4
|
||||||
|
for comp in range(4):
|
||||||
|
if fc_dict['rotation_quaternion'][comp]:
|
||||||
|
writevec[comp] = fc_dict['rotation_quaternion'][comp].evaluate(frame)
|
||||||
|
writebuf(struct.pack('ffff', writevec[0], writevec[1], writevec[2], writevec[3]))
|
||||||
|
|
||||||
|
elif rotation_mode == 'rotation_euler':
|
||||||
|
euler = [0.0, 0.0, 0.0]
|
||||||
|
for comp in range(3):
|
||||||
|
if fc_dict['rotation_euler'][comp]:
|
||||||
|
euler[comp] = fc_dict['rotation_euler'][comp].evaluate(frame)
|
||||||
|
euler_o = Euler(euler, 'XYZ')
|
||||||
|
quat = euler_o.to_quaternion()
|
||||||
|
writebuf(struct.pack('ffff', quat[0], quat[1], quat[2], quat[3]))
|
||||||
|
|
||||||
|
elif rotation_mode == 'rotation_axis_angle':
|
||||||
|
axis_angle = [0.0, 0.0, 0.0, 0.0]
|
||||||
|
for comp in range(4):
|
||||||
|
if fc_dict['rotation_axis_angle'][comp]:
|
||||||
|
axis_angle[comp] = fc_dict['rotation_axis_angle'][comp].evaluate(frame)
|
||||||
|
quat = Quaternion(axis_angle[1:4], axis_angle[0])
|
||||||
|
writebuf(struct.pack('ffff', quat[0], quat[1], quat[2], quat[3]))
|
||||||
|
|
||||||
|
# Location curves
|
||||||
|
if 'location' in fc_dict:
|
||||||
|
writevec = [0.0]*3
|
||||||
|
for comp in range(3):
|
||||||
|
if fc_dict['location'][comp]:
|
||||||
|
writevec[comp] = fc_dict['location'][comp].evaluate(frame)
|
||||||
|
writebuf(struct.pack('fff', writevec[0], writevec[1], writevec[2]))
|
||||||
|
|
||||||
|
# Scale curves
|
||||||
|
if 'scale' in fc_dict:
|
||||||
|
writevec = [1.0]*3
|
||||||
|
for comp in range(3):
|
||||||
|
if fc_dict['scale'][comp]:
|
||||||
|
writevec[comp] = fc_dict['scale'][comp].evaluate(frame)
|
||||||
|
writebuf(struct.pack('fff', writevec[0], writevec[1], writevec[2]))
|
||||||
|
|
||||||
|
|
||||||
# RARM Generator
|
def write_action_aabb(writebuf, arm_obj, mesh_obj):
|
||||||
def package_rarm(arm_obj, res_db, heclpak, arg_path, arg_package):
|
scene = bpy.context.scene
|
||||||
|
|
||||||
rarm_db_id, rarm_hash = res_db.register_resource(arg_path, arm_obj.name, arg_package)
|
# Frame 1
|
||||||
if not rarm_hash:
|
scene.frame_set(1)
|
||||||
|
|
||||||
skeleton_data = hecl_rmdl.generate_skeleton_info(arm_obj)
|
# Transform against root
|
||||||
rarm_hash = heclpak.add_object(skeleton_data, b'RARM')
|
root_bone = arm_obj.pose.bones['root']
|
||||||
res_db.update_resource_stats(rarm_db_id, rarm_hash)
|
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])
|
||||||
|
|
||||||
return rarm_db_id, rarm_hash
|
# Accumulate AABB for each frame
|
||||||
|
for frame_idx in range(2, scene.frame_end + 1):
|
||||||
|
scene.frame_set(frame_idx)
|
||||||
|
|
||||||
# RANI Generator
|
root_bone.location = (0.0,0.0,0.0)
|
||||||
def package_rani(action_obj, res_db, heclpak, arg_path, arg_package):
|
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])
|
||||||
|
|
||||||
rani_db_id, rani_hash = res_db.register_resource(arg_path, action_obj.name, arg_package)
|
for comp in range(3):
|
||||||
if not rani_hash:
|
if test_aabb_min[comp] < root_aabb_min[comp]:
|
||||||
|
root_aabb_min[comp] = test_aabb_min[comp]
|
||||||
res_db.clear_dependencies(rani_db_id)
|
for comp in range(3):
|
||||||
rani_hash = heclpak.add_object(hecl_rmdl.rmdl_anim.generate_animation_info(action_obj, res_db, rani_db_id, arg_package), b'RANI')
|
if test_aabb_max[comp] > root_aabb_max[comp]:
|
||||||
res_db.update_resource_stats(rani_db_id, rani_hash)
|
root_aabb_max[comp] = test_aabb_max[comp]
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
writebuf(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]))
|
||||||
|
|
||||||
# Cook
|
# Cook
|
||||||
def cook(writebuffunc, platform, endianchar):
|
def cook(writebuf):
|
||||||
print('COOKING SACT')
|
sact_data = bpy.context.scene.hecl_sact_data
|
||||||
|
|
||||||
|
# Output armatures
|
||||||
|
writebuf(struct.pack('I', len(bpy.data.armatures)))
|
||||||
|
for arm in bpy.data.armatures:
|
||||||
|
writebuf(struct.pack('I', len(arm.name)))
|
||||||
|
writebuf(arm.name.encode())
|
||||||
|
|
||||||
|
writebuf(struct.pack('I', len(arm.bones)))
|
||||||
|
for bone in arm.bones:
|
||||||
|
writebuf(struct.pack('I', len(bone.name)))
|
||||||
|
writebuf(bone.name.encode())
|
||||||
|
|
||||||
|
writebuf(struct.pack('fff', bone.head_local[0], bone.head_local[1], bone.head_local[2]))
|
||||||
|
|
||||||
|
if bone.parent:
|
||||||
|
writebuf(struct.pack('i', arm.bones.find(bone.parent.name)))
|
||||||
|
else:
|
||||||
|
writebuf(struct.pack('i', -1))
|
||||||
|
|
||||||
|
writebuf(struct.pack('I', len(bone.children)))
|
||||||
|
for child in bone.children:
|
||||||
|
writebuf(struct.pack('i', arm.bones.find(child.name)))
|
||||||
|
|
||||||
|
# Output subtypes
|
||||||
|
writebuf(struct.pack('I', len(sact_data.subtypes)))
|
||||||
|
for subtype in sact_data.subtypes:
|
||||||
|
writebuf(struct.pack('I', len(subtype.name)))
|
||||||
|
writebuf(subtype.name.encode())
|
||||||
|
|
||||||
|
mesh = None
|
||||||
|
if subtype.linked_mesh in bpy.data.objects:
|
||||||
|
mesh = bpy.data.objects[subtype.linked_mesh]
|
||||||
|
|
||||||
|
if mesh and mesh.library:
|
||||||
|
mesh_path = bpy.path.abspath(mesh.library.filepath)
|
||||||
|
writebuf(struct.pack('I', len(mesh_path)))
|
||||||
|
writebuf(mesh_path.encode())
|
||||||
|
else:
|
||||||
|
writebuf(struct.pack('I', 0))
|
||||||
|
|
||||||
|
arm = None
|
||||||
|
if subtype.linked_armature in bpy.data.objects:
|
||||||
|
arm = bpy.data.objects[subtype.linked_armature]
|
||||||
|
|
||||||
|
arm_idx = -1
|
||||||
|
if arm:
|
||||||
|
arm_idx = bpy.data.armatures.find(arm.name)
|
||||||
|
writebuf(struct.pack('i', arm_idx))
|
||||||
|
|
||||||
|
writebuf(struct.pack('I', len(subtype.overlays)))
|
||||||
|
for overlay in subtype.overlays:
|
||||||
|
writebuf(struct.pack('I', len(overlay.name)))
|
||||||
|
writebuf(overlay.name.encode())
|
||||||
|
|
||||||
|
mesh = None
|
||||||
|
if overlay.linked_mesh in bpy.data.objects:
|
||||||
|
mesh = bpy.data.objects[overlay.linked_mesh]
|
||||||
|
|
||||||
|
if mesh and mesh.library:
|
||||||
|
mesh_path = bpy.path.abspath(mesh.library.filepath)
|
||||||
|
writebuf(struct.pack('I', len(mesh_path)))
|
||||||
|
writebuf(mesh_path.encode())
|
||||||
|
else:
|
||||||
|
writebuf(struct.pack('I', 0))
|
||||||
|
|
||||||
|
|
||||||
|
# Output actions
|
||||||
|
writebuf(struct.pack('I', len(sact_data.actions)))
|
||||||
|
for action_idx in range(len(sact_data.actions)):
|
||||||
|
sact_data.active_action = action_idx
|
||||||
|
action = sact_data.actions[action_idx]
|
||||||
|
writebuf(struct.pack('I', len(action.name)))
|
||||||
|
writebuf(action.name.encode())
|
||||||
|
|
||||||
|
bact = None
|
||||||
|
if action.name in bpy.data.actions:
|
||||||
|
bact = bpy.data.actions[action.name]
|
||||||
|
if not bact:
|
||||||
|
raise RuntimeError('action %s not found' % action.name)
|
||||||
|
|
||||||
|
writebuf(struct.pack('f', 1.0 / bact.hecl_fps))
|
||||||
|
writebuf(struct.pack('b', int(bact.hecl_additive)))
|
||||||
|
|
||||||
|
write_action_channels(writebuf, bact)
|
||||||
|
writebuf(struct.pack('I', len(sact_data.subtypes)))
|
||||||
|
for subtype_idx in range(len(sact_data.subtypes)):
|
||||||
|
subtype = sact_data.subtypes[subtype_idx]
|
||||||
|
sact_data.active_subtype = subtype_idx
|
||||||
|
bpy.ops.scene.sactaction_load()
|
||||||
|
if subtype.linked_armature not in bpy.data.objects:
|
||||||
|
raise RuntimeError('armature %s not found' % subtype.linked_armature)
|
||||||
|
arm = bpy.data.objects[subtype.linked_armature]
|
||||||
|
if subtype.linked_mesh not in bpy.data.objects:
|
||||||
|
raise RuntimeError('mesh %s not found' % subtype.linked_mesh)
|
||||||
|
mesh = bpy.data.objects[subtype.linked_mesh]
|
||||||
|
write_action_aabb(writebuf, arm, mesh)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Panel draw
|
# Panel draw
|
||||||
def draw(layout, context):
|
def draw(layout, context):
|
||||||
SACTSubtype.draw(layout.box(), context)
|
SACTSubtype.draw(layout.box(), context)
|
||||||
SACTAction.draw(layout.box(), context)
|
SACTAction.draw(layout.box(), context)
|
||||||
#SACTEvent.draw(layout.box(), context)
|
|
||||||
|
|
||||||
|
|
||||||
# Time-remap option update
|
# Time-remap option update
|
||||||
|
@ -247,7 +320,6 @@ def time_remap_update(self, context):
|
||||||
# Registration
|
# Registration
|
||||||
def register():
|
def register():
|
||||||
SACTSubtype.register()
|
SACTSubtype.register()
|
||||||
#SACTEvent.register()
|
|
||||||
SACTAction.register()
|
SACTAction.register()
|
||||||
bpy.utils.register_class(SACTData)
|
bpy.utils.register_class(SACTData)
|
||||||
bpy.types.Scene.hecl_sact_data = bpy.props.PointerProperty(type=SACTData)
|
bpy.types.Scene.hecl_sact_data = bpy.props.PointerProperty(type=SACTData)
|
||||||
|
@ -261,4 +333,3 @@ def unregister():
|
||||||
bpy.utils.unregister_class(SACTData)
|
bpy.utils.unregister_class(SACTData)
|
||||||
SACTSubtype.unregister()
|
SACTSubtype.unregister()
|
||||||
SACTAction.unregister()
|
SACTAction.unregister()
|
||||||
#SACTEvent.unregister()
|
|
||||||
|
|
|
@ -184,6 +184,10 @@ def dataout_loop():
|
||||||
bpy.data.objects.remove(join_obj)
|
bpy.data.objects.remove(join_obj)
|
||||||
bpy.data.meshes.remove(join_mesh)
|
bpy.data.meshes.remove(join_mesh)
|
||||||
|
|
||||||
|
elif cmdargs[0] == 'ACTORCOMPILE':
|
||||||
|
writepipeline(b'OK')
|
||||||
|
hecl.sact.cook(writepipebuf)
|
||||||
|
|
||||||
|
|
||||||
# Command loop
|
# Command loop
|
||||||
while True:
|
while True:
|
||||||
|
|
Loading…
Reference in New Issue