mirror of
https://github.com/aap/librw.git
synced 2025-01-22 00:32:19 +00:00
got animation, hanim and skinning basically working
This commit is contained in:
parent
b1e55a7784
commit
5dff69431e
1
rw.h
1
rw.h
@ -6,6 +6,7 @@
|
||||
#include "src/rwplg.h"
|
||||
#include "src/rwpipeline.h"
|
||||
#include "src/rwobjects.h"
|
||||
#include "src/rwanim.h"
|
||||
#include "src/rwengine.h"
|
||||
#include "src/rwplugins.h"
|
||||
#include "src/ps2/rwps2.h"
|
||||
|
438
src/anim.cpp
438
src/anim.cpp
@ -8,18 +8,23 @@
|
||||
#include "rwplg.h"
|
||||
#include "rwpipeline.h"
|
||||
#include "rwobjects.h"
|
||||
#include "rwanim.h"
|
||||
#include "rwplugins.h"
|
||||
|
||||
#define PLUGIN_ID 2 // ?
|
||||
#define PLUGIN_ID 0
|
||||
|
||||
namespace rw {
|
||||
|
||||
//
|
||||
// AnimInterpolatorInfo
|
||||
//
|
||||
|
||||
#define MAXINTERPINFO 10
|
||||
|
||||
static AnimInterpolatorInfo *interpInfoList[MAXINTERPINFO];
|
||||
|
||||
void
|
||||
registerAnimInterpolatorInfo(AnimInterpolatorInfo *interpInfo)
|
||||
AnimInterpolatorInfo::registerInterp(AnimInterpolatorInfo *interpInfo)
|
||||
{
|
||||
for(int32 i = 0; i < MAXINTERPINFO; i++)
|
||||
if(interpInfoList[i] == nil){
|
||||
@ -30,7 +35,7 @@ registerAnimInterpolatorInfo(AnimInterpolatorInfo *interpInfo)
|
||||
}
|
||||
|
||||
AnimInterpolatorInfo*
|
||||
findAnimInterpolatorInfo(int32 id)
|
||||
AnimInterpolatorInfo::find(int32 id)
|
||||
{
|
||||
for(int32 i = 0; i < MAXINTERPINFO; i++){
|
||||
if(interpInfoList[i] && interpInfoList[i]->id == id)
|
||||
@ -39,17 +44,30 @@ findAnimInterpolatorInfo(int32 id)
|
||||
return nil;
|
||||
}
|
||||
|
||||
//
|
||||
// Animation
|
||||
//
|
||||
|
||||
Animation*
|
||||
Animation::create(AnimInterpolatorInfo *interpInfo, int32 numFrames, int32 flags, float duration)
|
||||
Animation::create(AnimInterpolatorInfo *interpInfo, int32 numFrames,
|
||||
int32 flags, float duration)
|
||||
{
|
||||
Animation *anim = (Animation*)malloc(sizeof(*anim));
|
||||
int32 sz = sizeof(Animation) +
|
||||
numFrames*interpInfo->animKeyFrameSize +
|
||||
interpInfo->customDataSize;
|
||||
uint8 *data = (uint8*)malloc(sz);
|
||||
if(data == nil){
|
||||
RWERROR((ERR_ALLOC, sz));
|
||||
return nil;
|
||||
}
|
||||
Animation *anim = (Animation*)data;
|
||||
data += sizeof(Animation);
|
||||
anim->interpInfo = interpInfo;
|
||||
anim->numFrames = numFrames;
|
||||
anim->flags = flags;
|
||||
anim->duration = duration;
|
||||
uint8 *data = new uint8[anim->numFrames*interpInfo->keyFrameSize + interpInfo->customDataSize];
|
||||
anim->keyframes = data;
|
||||
data += anim->numFrames*interpInfo->keyFrameSize;
|
||||
data += anim->numFrames*interpInfo->animKeyFrameSize;
|
||||
anim->customData = data;
|
||||
return anim;
|
||||
}
|
||||
@ -57,11 +75,20 @@ Animation::create(AnimInterpolatorInfo *interpInfo, int32 numFrames, int32 flags
|
||||
void
|
||||
Animation::destroy(void)
|
||||
{
|
||||
uint8 *c = (uint8*)this->keyframes;
|
||||
delete[] c;
|
||||
free(this);
|
||||
}
|
||||
|
||||
int32
|
||||
Animation::getNumNodes(void)
|
||||
{
|
||||
int32 sz = this->interpInfo->animKeyFrameSize;
|
||||
KeyFrameHeader *first = (KeyFrameHeader*)this->keyframes;
|
||||
int32 n = 0;
|
||||
for(KeyFrameHeader *f = first; f->prev != first; f = f->next(sz))
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
Animation*
|
||||
Animation::streamRead(Stream *stream)
|
||||
{
|
||||
@ -69,7 +96,7 @@ Animation::streamRead(Stream *stream)
|
||||
if(stream->readI32() != 0x100)
|
||||
return nil;
|
||||
int32 typeID = stream->readI32();
|
||||
AnimInterpolatorInfo *interpInfo = findAnimInterpolatorInfo(typeID);
|
||||
AnimInterpolatorInfo *interpInfo = AnimInterpolatorInfo::find(typeID);
|
||||
int32 numFrames = stream->readI32();
|
||||
int32 flags = stream->readI32();
|
||||
float duration = stream->readF32();
|
||||
@ -82,15 +109,15 @@ Animation*
|
||||
Animation::streamReadLegacy(Stream *stream)
|
||||
{
|
||||
Animation *anim;
|
||||
AnimInterpolatorInfo *interpInfo = findAnimInterpolatorInfo(1);
|
||||
AnimInterpolatorInfo *interpInfo = AnimInterpolatorInfo::find(1);
|
||||
int32 numFrames = stream->readI32();
|
||||
int32 flags = stream->readI32();
|
||||
float duration = stream->readF32();
|
||||
anim = Animation::create(interpInfo, numFrames, flags, duration);
|
||||
HAnimKeyFrame *frames = (HAnimKeyFrame*)anim->keyframes;
|
||||
for(int32 i = 0; i < anim->numFrames; i++){
|
||||
stream->read(frames[i].q, 4*4);
|
||||
stream->read(frames[i].t, 3*4);
|
||||
stream->read(&frames[i].q, 4*4);
|
||||
stream->read(&frames[i].t, 3*4);
|
||||
frames[i].time = stream->readF32();
|
||||
int32 prev = stream->readI32();
|
||||
frames[i].prev = &frames[prev];
|
||||
@ -120,8 +147,8 @@ Animation::streamWriteLegacy(Stream *stream)
|
||||
assert(interpInfo->id == 1);
|
||||
HAnimKeyFrame *frames = (HAnimKeyFrame*)this->keyframes;
|
||||
for(int32 i = 0; i < this->numFrames; i++){
|
||||
stream->write(frames[i].q, 4*4);
|
||||
stream->write(frames[i].t, 3*4);
|
||||
stream->write(&frames[i].q, 4*4);
|
||||
stream->write(&frames[i].t, 3*4);
|
||||
stream->writeF32(frames[i].time);
|
||||
stream->writeI32(frames[i].prev - frames);
|
||||
}
|
||||
@ -136,321 +163,114 @@ Animation::streamGetSize(void)
|
||||
return size;
|
||||
}
|
||||
|
||||
AnimInterpolator::AnimInterpolator(Animation *anim)
|
||||
{
|
||||
this->anim = anim;
|
||||
}
|
||||
|
||||
//
|
||||
// UVAnim
|
||||
// AnimInterpolator
|
||||
//
|
||||
|
||||
void
|
||||
UVAnimCustomData::destroy(Animation *anim)
|
||||
AnimInterpolator*
|
||||
AnimInterpolator::create(int32 numNodes, int32 maxFrameSize)
|
||||
{
|
||||
this->refCount--;
|
||||
if(this->refCount <= 0)
|
||||
anim->destroy();
|
||||
}
|
||||
AnimInterpolator *interp;
|
||||
int32 sz;
|
||||
int32 realsz = maxFrameSize;
|
||||
|
||||
UVAnimDictionary *currentUVAnimDictionary;
|
||||
|
||||
UVAnimDictionary*
|
||||
UVAnimDictionary::create(void)
|
||||
{
|
||||
UVAnimDictionary *dict = (UVAnimDictionary*)malloc(sizeof(UVAnimDictionary));
|
||||
if(dict == nil){
|
||||
RWERROR((ERR_ALLOC, sizeof(UVAnimDictionary)));
|
||||
// Add some space for pointers and padding, hopefully this will
|
||||
// enough. Don't change maxFrameSize not to mess up streaming.
|
||||
if(sizeof(void*) > 4)
|
||||
realsz += 16;
|
||||
sz = sizeof(AnimInterpolator) + numNodes*realsz;
|
||||
interp = (AnimInterpolator*)malloc(sz);
|
||||
if(interp == nil){
|
||||
RWERROR((ERR_ALLOC, sz));
|
||||
return nil;
|
||||
}
|
||||
dict->animations.init();
|
||||
return dict;
|
||||
interp->currentAnim = nil;
|
||||
interp->currentTime = 0.0f;
|
||||
interp->nextFrame = nil;
|
||||
interp->maxInterpKeyFrameSize = maxFrameSize;
|
||||
interp->currentInterpKeyFrameSize = maxFrameSize;
|
||||
interp->currentAnimKeyFrameSize = -1;
|
||||
interp->numNodes = numNodes;;
|
||||
|
||||
return interp;
|
||||
}
|
||||
|
||||
void
|
||||
UVAnimDictionary::destroy(void)
|
||||
AnimInterpolator::destroy(void)
|
||||
{
|
||||
FORLIST(lnk, this->animations){
|
||||
UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk);
|
||||
UVAnimCustomData *cust = (UVAnimCustomData*)de->anim->customData;
|
||||
cust->destroy(de->anim);
|
||||
delete de;
|
||||
}
|
||||
free(this);
|
||||
}
|
||||
|
||||
void
|
||||
UVAnimDictionary::add(Animation *anim)
|
||||
bool32
|
||||
AnimInterpolator::setCurrentAnim(Animation *anim)
|
||||
{
|
||||
UVAnimDictEntry *de = new UVAnimDictEntry;
|
||||
de->anim = anim;
|
||||
this->animations.append(&de->inDict);
|
||||
}
|
||||
|
||||
UVAnimDictionary*
|
||||
UVAnimDictionary::streamRead(Stream *stream)
|
||||
{
|
||||
if(!findChunk(stream, ID_STRUCT, nil, nil)){
|
||||
RWERROR((ERR_CHUNK, "STRUCT"));
|
||||
return nil;
|
||||
int32 i;
|
||||
AnimInterpolatorInfo *interpInfo = anim->interpInfo;
|
||||
this->currentAnim = anim;
|
||||
this->currentTime = 0.0f;
|
||||
int32 maxkf = this->maxInterpKeyFrameSize;
|
||||
if(sizeof(void*) > 4) // see above in create()
|
||||
maxkf += 16;
|
||||
if(interpInfo->interpKeyFrameSize > maxkf){
|
||||
RWERROR((ERR_GENERAL, "interpolation frame too big"));
|
||||
return 0;
|
||||
}
|
||||
UVAnimDictionary *dict = UVAnimDictionary::create();
|
||||
if(dict == nil)
|
||||
return nil;
|
||||
int32 numAnims = stream->readI32();
|
||||
Animation *anim;
|
||||
for(int32 i = 0; i < numAnims; i++){
|
||||
if(!findChunk(stream, ID_ANIMANIMATION, nil, nil)){
|
||||
RWERROR((ERR_CHUNK, "ANIMANIMATION"));
|
||||
goto fail;
|
||||
}
|
||||
anim = Animation::streamRead(stream);
|
||||
if(anim == nil)
|
||||
goto fail;
|
||||
dict->add(anim);
|
||||
this->currentInterpKeyFrameSize = interpInfo->interpKeyFrameSize;
|
||||
this->currentAnimKeyFrameSize = interpInfo->animKeyFrameSize;
|
||||
this->applyCB = interpInfo->applyCB;
|
||||
this->blendCB = interpInfo->blendCB;
|
||||
this->interpCB = interpInfo->interpCB;
|
||||
this->addCB = interpInfo->addCB;
|
||||
for(i = 0; i < numNodes; i++){
|
||||
InterpFrameHeader *intf;
|
||||
KeyFrameHeader *kf1, *kf2;
|
||||
intf = this->getInterpFrame(i);
|
||||
kf1 = this->getAnimFrame(i);
|
||||
kf2 = this->getAnimFrame(i+numNodes);
|
||||
intf->keyFrame1 = kf1;
|
||||
intf->keyFrame2 = kf2;
|
||||
this->interpCB(intf, kf1, kf2, 0.0f, anim->customData);
|
||||
}
|
||||
return dict;
|
||||
fail:
|
||||
dict->destroy();
|
||||
return nil;
|
||||
this->nextFrame = this->getAnimFrame(numNodes*2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool
|
||||
UVAnimDictionary::streamWrite(Stream *stream)
|
||||
{
|
||||
uint32 size = this->streamGetSize();
|
||||
writeChunkHeader(stream, ID_UVANIMDICT, size);
|
||||
writeChunkHeader(stream, ID_STRUCT, 4);
|
||||
int32 numAnims = this->count();
|
||||
stream->writeI32(numAnims);
|
||||
FORLIST(lnk, this->animations){
|
||||
UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk);
|
||||
de->anim->streamWrite(stream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32
|
||||
UVAnimDictionary::streamGetSize(void)
|
||||
{
|
||||
uint32 size = 12 + 4;
|
||||
FORLIST(lnk, this->animations){
|
||||
UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk);
|
||||
size += 12 + de->anim->streamGetSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
Animation*
|
||||
UVAnimDictionary::find(const char *name)
|
||||
{
|
||||
FORLIST(lnk, this->animations){
|
||||
Animation *anim = UVAnimDictEntry::fromDict(lnk)->anim;
|
||||
UVAnimCustomData *custom = (UVAnimCustomData*)anim->customData;
|
||||
if(strncmp_ci(custom->name, name, 32) == 0)
|
||||
return anim;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
static void
|
||||
uvAnimStreamRead(Stream *stream, Animation *anim)
|
||||
{
|
||||
UVAnimCustomData *custom = (UVAnimCustomData*)anim->customData;
|
||||
UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes;
|
||||
stream->readI32();
|
||||
stream->read(custom->name, 32);
|
||||
stream->read(custom->nodeToUVChannel, 8*4);
|
||||
custom->refCount = 1;
|
||||
|
||||
for(int32 i = 0; i < anim->numFrames; i++){
|
||||
frames[i].time = stream->readF32();
|
||||
stream->read(frames[i].uv, 6*4);
|
||||
int32 prev = stream->readI32();
|
||||
frames[i].prev = &frames[prev];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
uvAnimStreamWrite(Stream *stream, Animation *anim)
|
||||
{
|
||||
UVAnimCustomData *custom = (UVAnimCustomData*)anim->customData;
|
||||
UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes;
|
||||
stream->writeI32(0);
|
||||
stream->write(custom->name, 32);
|
||||
stream->write(custom->nodeToUVChannel, 8*4);
|
||||
|
||||
for(int32 i = 0; i < anim->numFrames; i++){
|
||||
stream->writeF32(frames[i].time);
|
||||
stream->write(frames[i].uv, 6*4);
|
||||
stream->writeI32(frames[i].prev - frames);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32
|
||||
uvAnimStreamGetSize(Animation *anim)
|
||||
{
|
||||
return 4 + 32 + 8*4 + anim->numFrames*(4 + 6*4 + 4);
|
||||
}
|
||||
|
||||
static void
|
||||
registerUVAnimInterpolator(void)
|
||||
{
|
||||
// Linear
|
||||
AnimInterpolatorInfo *info = new AnimInterpolatorInfo;
|
||||
info->id = 0x1C0;
|
||||
info->keyFrameSize = sizeof(UVAnimKeyFrame);
|
||||
info->customDataSize = sizeof(UVAnimCustomData);
|
||||
info->streamRead = uvAnimStreamRead;
|
||||
info->streamWrite = uvAnimStreamWrite;
|
||||
info->streamGetSize = uvAnimStreamGetSize;
|
||||
registerAnimInterpolatorInfo(info);
|
||||
|
||||
// Param
|
||||
info = new AnimInterpolatorInfo;
|
||||
info->id = 0x1C1;
|
||||
info->keyFrameSize = sizeof(UVAnimKeyFrame);
|
||||
info->customDataSize = sizeof(UVAnimCustomData);
|
||||
info->streamRead = uvAnimStreamRead;
|
||||
info->streamWrite = uvAnimStreamWrite;
|
||||
info->streamGetSize = uvAnimStreamGetSize;
|
||||
registerAnimInterpolatorInfo(info);
|
||||
}
|
||||
|
||||
int32 uvAnimOffset;
|
||||
|
||||
static void*
|
||||
createUVAnim(void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim;
|
||||
uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
memset(uvanim, 0, sizeof(*uvanim));
|
||||
return object;
|
||||
}
|
||||
|
||||
static void*
|
||||
destroyUVAnim(void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim;
|
||||
uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
AnimInterpolator *ip = uvanim->interp[i];
|
||||
if(ip){
|
||||
UVAnimCustomData *custom = (UVAnimCustomData*)ip->anim->customData;
|
||||
custom->destroy(ip->anim);
|
||||
delete ip;
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
static void*
|
||||
copyUVAnim(void *dst, void *src, int32 offset, int32)
|
||||
{
|
||||
UVAnim *srcuvanim, *dstuvanim;
|
||||
dstuvanim = PLUGINOFFSET(UVAnim, dst, offset);
|
||||
srcuvanim = PLUGINOFFSET(UVAnim, src, offset);
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
AnimInterpolator *srcip = srcuvanim->interp[i];
|
||||
AnimInterpolator *dstip;
|
||||
if(srcip){
|
||||
UVAnimCustomData *custom = (UVAnimCustomData*)srcip->anim->customData;
|
||||
dstip = new AnimInterpolator(srcip->anim);
|
||||
custom->refCount++;
|
||||
dstuvanim->interp[i] = dstip;
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
Animation*
|
||||
makeDummyAnimation(const char *name)
|
||||
{
|
||||
AnimInterpolatorInfo *interpInfo = findAnimInterpolatorInfo(0x1C0);
|
||||
Animation *anim = Animation::create(interpInfo, 2, 0, 1.0f);
|
||||
UVAnimCustomData *custom = (UVAnimCustomData*)anim->customData;
|
||||
strncpy(custom->name, name, 32);
|
||||
memset(custom->nodeToUVChannel, 0, sizeof(custom->nodeToUVChannel));
|
||||
custom->refCount = 1;
|
||||
// TODO: init the frames
|
||||
// UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes;
|
||||
return anim;
|
||||
}
|
||||
|
||||
static Stream*
|
||||
readUVAnim(Stream *stream, int32, void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
if(!findChunk(stream, ID_STRUCT, nil, nil)){
|
||||
RWERROR((ERR_CHUNK, "STRUCT"));
|
||||
return nil;
|
||||
}
|
||||
char name[32];
|
||||
uint32 mask = stream->readI32();
|
||||
uint32 bit = 1;
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
if(mask & bit){
|
||||
stream->read(name, 32);
|
||||
Animation *anim = nil;
|
||||
if(currentUVAnimDictionary)
|
||||
anim = currentUVAnimDictionary->find(name);
|
||||
if(anim == nil){
|
||||
anim = makeDummyAnimation(name);
|
||||
if(currentUVAnimDictionary)
|
||||
currentUVAnimDictionary->add(anim);
|
||||
}
|
||||
UVAnimCustomData *custom = (UVAnimCustomData*)anim->customData;
|
||||
AnimInterpolator *interp = new AnimInterpolator(anim);
|
||||
custom->refCount++;
|
||||
uvanim->interp[i] = interp;
|
||||
}
|
||||
bit <<= 1;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
static Stream*
|
||||
writeUVAnim(Stream *stream, int32 size, void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
writeChunkHeader(stream, ID_STRUCT, size-12);
|
||||
uint32 mask = 0;
|
||||
uint32 bit = 1;
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
if(uvanim->interp[i])
|
||||
mask |= bit;
|
||||
bit <<= 1;
|
||||
}
|
||||
stream->writeI32(mask);
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
if(uvanim->interp[i]){
|
||||
UVAnimCustomData *custom =
|
||||
(UVAnimCustomData*)uvanim->interp[i]->anim->customData;
|
||||
stream->write(custom->name, 32);
|
||||
}
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
static int32
|
||||
getSizeUVAnim(void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
int32 size = 0;
|
||||
for(int32 i = 0; i < 8; i++)
|
||||
if(uvanim->interp[i])
|
||||
size += 32;
|
||||
return size ? size + 12 + 4 : 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
registerUVAnimPlugin(void)
|
||||
AnimInterpolator::addTime(float32 t)
|
||||
{
|
||||
registerUVAnimInterpolator();
|
||||
uvAnimOffset = Material::registerPlugin(sizeof(UVAnim), ID_UVANIMATION,
|
||||
createUVAnim, destroyUVAnim, copyUVAnim);
|
||||
Material::registerPluginStream(ID_UVANIMATION, readUVAnim, writeUVAnim, getSizeUVAnim);
|
||||
int32 i;
|
||||
if(t <= 0.0f)
|
||||
return;
|
||||
this->currentTime += t;
|
||||
// reset animation
|
||||
if(this->currentTime > this->currentAnim->duration){
|
||||
this->setCurrentAnim(this->currentAnim);
|
||||
return;
|
||||
}
|
||||
KeyFrameHeader *last = this->getAnimFrame(this->currentAnim->numFrames);
|
||||
KeyFrameHeader *next = (KeyFrameHeader*)this->nextFrame;
|
||||
InterpFrameHeader *ifrm;
|
||||
while(next < last && next->prev->time <= this->currentTime){
|
||||
// find next interpolation frame to expire
|
||||
for(i = 0; i < this->numNodes; i++){
|
||||
ifrm = this->getInterpFrame(i);
|
||||
if(ifrm->keyFrame2 == next->prev)
|
||||
break;
|
||||
}
|
||||
// advance interpolation frame
|
||||
ifrm->keyFrame1 = ifrm->keyFrame2;
|
||||
ifrm->keyFrame2 = next;
|
||||
// ... and next frame
|
||||
next = (KeyFrameHeader*)((uint8*)this->nextFrame +
|
||||
currentAnimKeyFrameSize);
|
||||
this->nextFrame = next;
|
||||
}
|
||||
for(i = 0; i < this->numNodes; i++){
|
||||
ifrm = this->getInterpFrame(i);
|
||||
this->interpCB(ifrm, ifrm->keyFrame1, ifrm->keyFrame2,
|
||||
this->currentTime,
|
||||
this->currentAnim->customData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
70
src/base.cpp
70
src/base.cpp
@ -10,7 +10,6 @@
|
||||
#include "rwplg.h"
|
||||
#include "rwpipeline.h"
|
||||
#include "rwobjects.h"
|
||||
#include "rwplugins.h"
|
||||
#include "rwengine.h"
|
||||
|
||||
namespace rw {
|
||||
@ -74,6 +73,41 @@ mult(const Quat &q, const Quat &p)
|
||||
q.w*p.z + q.z*p.w + q.x*p.y - q.y*p.x);
|
||||
}
|
||||
|
||||
Quat
|
||||
lerp(const Quat &q, const Quat &p, float32 r)
|
||||
{
|
||||
float32 c;
|
||||
Quat q1 = q;
|
||||
c = dot(q1, p);
|
||||
if(c < 0.0f){
|
||||
c = -c;
|
||||
q1 = negate(q1);
|
||||
}
|
||||
return Quat(q1.w + r*(p.w - q1.w),
|
||||
q1.x + r*(p.x - q1.x),
|
||||
q1.y + r*(p.y - q1.y),
|
||||
q1.z + r*(p.z - q1.z));
|
||||
};
|
||||
|
||||
Quat
|
||||
slerp(const Quat &q, const Quat &p, float32 a)
|
||||
{
|
||||
float32 c;
|
||||
Quat q1 = q;
|
||||
c = dot(q1, p);
|
||||
if(c < 0.0f){
|
||||
c = -c;
|
||||
q1 = negate(q1);
|
||||
}
|
||||
float32 phi = acos(c);
|
||||
if(phi > 0.00001f){
|
||||
float32 s = sin(phi);
|
||||
return add(scale(q1, sin((1.0f-a)*phi)/s),
|
||||
scale(p, sin(a*phi)/s));
|
||||
}
|
||||
return q1;
|
||||
}
|
||||
|
||||
V3d
|
||||
cross(const V3d &a, const V3d &b)
|
||||
{
|
||||
@ -82,19 +116,35 @@ cross(const V3d &a, const V3d &b)
|
||||
a.x*b.y - a.y*b.x);
|
||||
}
|
||||
|
||||
/* q must be normalized */
|
||||
Matrix
|
||||
Matrix::makeRotation(const Quat &q)
|
||||
{
|
||||
Matrix res;
|
||||
res.right.x = q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z;
|
||||
res.right.y = 2*q.w*q.z + 2*q.x*q.y;
|
||||
res.right.z = 2*q.x*q.z - 2*q.w*q.y;
|
||||
res.up.x = 2*q.x*q.y - 2*q.w*q.z;
|
||||
res.up.y = q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z;
|
||||
res.up.z = 2*q.w*q.x + 2*q.y*q.z;
|
||||
res.at.x = 2*q.w*q.y + 2*q.x*q.z;
|
||||
res.at.y = 2*q.y*q.z - 2*q.w*q.x;
|
||||
res.at.z = q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z;
|
||||
float xx = q.x*q.x;
|
||||
float yy = q.y*q.y;
|
||||
float zz = q.z*q.z;
|
||||
float yz = q.y*q.z;
|
||||
float zx = q.z*q.x;
|
||||
float xy = q.x*q.y;
|
||||
float wx = q.w*q.x;
|
||||
float wy = q.w*q.y;
|
||||
float wz = q.w*q.z;
|
||||
|
||||
res.right.x = 1.0f - 2.0f*(yy + zz);
|
||||
res.right.y = 2.0f*(xy + wz);
|
||||
res.right.z = 2.0f*(zx - wy);
|
||||
|
||||
res.up.x = 2.0f*(xy - wz);
|
||||
res.up.y = 1.0f - 2.0f*(xx + zz);
|
||||
res.up.z = 2.0f*(yz + wx);
|
||||
|
||||
res.at.x = 2.0f*(zx + wy);
|
||||
res.at.y = 2.0f*(yz - wx);
|
||||
res.at.z = 1.0f - 2.0f*(xx + yy);
|
||||
|
||||
res.pos.x = res.pos.y = res.pos.z = 0.0f;
|
||||
|
||||
res.rightw = res.upw = res.atw = 0.0f;
|
||||
res.posw = 1.0f;
|
||||
return res;
|
||||
|
@ -1,5 +1,5 @@
|
||||
ECODE(ERR_GENERAL,
|
||||
"General Error")
|
||||
"Error: %s")
|
||||
ECODE(ERR_ALLOC,
|
||||
"Couldn't allocate 0x%X bytes")
|
||||
ECODE(ERR_FILE,
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../rwplg.h"
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwanim.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#include "rwd3d.h"
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../rwplg.h"
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwanim.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#include "rwd3d.h"
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#include "rwxbox.h"
|
||||
|
||||
#include "rwxboximpl.h"
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../rwplg.h"
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwanim.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#include "rwxbox.h"
|
||||
@ -115,7 +116,7 @@ skinInstanceCB(Geometry *geo, InstanceDataHeader *header)
|
||||
{
|
||||
defaultInstanceCB(geo, header);
|
||||
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
Skin *skin = Skin::get(geo);
|
||||
if(skin == nil)
|
||||
return;
|
||||
NativeSkin *natskin = new NativeSkin;
|
||||
@ -169,7 +170,7 @@ skinUninstanceCB(Geometry *geo, InstanceDataHeader *header)
|
||||
{
|
||||
defaultUninstanceCB(geo, header);
|
||||
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
Skin *skin = Skin::get(geo);
|
||||
if(skin == nil)
|
||||
return;
|
||||
NativeSkin *natskin = (NativeSkin*)skin->platformData;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "rwplg.h"
|
||||
#include "rwpipeline.h"
|
||||
#include "rwobjects.h"
|
||||
#include "rwanim.h"
|
||||
#include "rwplugins.h"
|
||||
#include "ps2/rwps2.h"
|
||||
#include "ps2/rwps2plg.h"
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#ifdef RW_OPENGL
|
||||
#include <GL/glew.h>
|
||||
#endif
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#ifdef RW_OPENGL
|
||||
#include <GL/glew.h>
|
||||
#include "rwgl3.h"
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#ifdef RW_OPENGL
|
||||
#include <GL/glew.h>
|
||||
#endif
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../rwplg.h"
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwanim.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#ifdef RW_OPENGL
|
||||
@ -229,10 +230,13 @@ makeMatFXPipeline(void)
|
||||
|
||||
// Skin
|
||||
|
||||
Shader *skinShader;
|
||||
|
||||
static void*
|
||||
skinOpen(void *o, int32, int32)
|
||||
{
|
||||
skinGlobals.pipelines[PLATFORM_GL3] = makeSkinPipeline();
|
||||
skinShader = Shader::fromFiles("skin.vert", "simple.frag");
|
||||
return o;
|
||||
}
|
||||
|
||||
@ -247,15 +251,250 @@ initSkin(void)
|
||||
{
|
||||
Driver::registerPlugin(PLATFORM_GL3, 0, ID_SKIN,
|
||||
skinOpen, skinClose);
|
||||
registerUniform("u_boneMatrices");
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
ATTRIB_WEIGHTS = ATTRIB_TEXCOORDS7+1,
|
||||
ATTRIB_INDICES
|
||||
};
|
||||
|
||||
void
|
||||
skinInstanceCB(Geometry *geo, InstanceDataHeader *header)
|
||||
{
|
||||
AttribDesc attribs[14], *a;
|
||||
uint32 stride;
|
||||
|
||||
//
|
||||
// Create attribute descriptions
|
||||
//
|
||||
a = attribs;
|
||||
stride = 0;
|
||||
|
||||
// Positions
|
||||
a->index = ATTRIB_POS;
|
||||
a->size = 3;
|
||||
a->type = GL_FLOAT;
|
||||
a->normalized = GL_FALSE;
|
||||
a->offset = stride;
|
||||
stride += 12;
|
||||
a++;
|
||||
|
||||
// Normals
|
||||
// TODO: compress
|
||||
bool hasNormals = !!(geo->geoflags & Geometry::NORMALS);
|
||||
if(hasNormals){
|
||||
a->index = ATTRIB_NORMAL;
|
||||
a->size = 3;
|
||||
a->type = GL_FLOAT;
|
||||
a->normalized = GL_FALSE;
|
||||
a->offset = stride;
|
||||
stride += 12;
|
||||
a++;
|
||||
}
|
||||
|
||||
// Prelighting
|
||||
bool isPrelit = !!(geo->geoflags & Geometry::PRELIT);
|
||||
if(isPrelit){
|
||||
a->index = ATTRIB_COLOR;
|
||||
a->size = 4;
|
||||
a->type = GL_UNSIGNED_BYTE;
|
||||
a->normalized = GL_TRUE;
|
||||
a->offset = stride;
|
||||
stride += 4;
|
||||
a++;
|
||||
}
|
||||
|
||||
// Texture coordinates
|
||||
for(int32 n = 0; n < geo->numTexCoordSets; n++){
|
||||
a->index = ATTRIB_TEXCOORDS0+n;
|
||||
a->size = 2;
|
||||
a->type = GL_FLOAT;
|
||||
a->normalized = GL_FALSE;
|
||||
a->offset = stride;
|
||||
stride += 8;
|
||||
a++;
|
||||
}
|
||||
|
||||
// Weights
|
||||
a->index = ATTRIB_WEIGHTS;
|
||||
a->size = 4;
|
||||
a->type = GL_FLOAT;
|
||||
a->normalized = GL_FALSE;
|
||||
a->offset = stride;
|
||||
stride += 16;
|
||||
a++;
|
||||
|
||||
// Indices
|
||||
a->index = ATTRIB_INDICES;
|
||||
a->size = 4;
|
||||
a->type = GL_UNSIGNED_BYTE;
|
||||
a->normalized = GL_FALSE;
|
||||
a->offset = stride;
|
||||
stride += 4;
|
||||
a++;
|
||||
|
||||
header->numAttribs = a - attribs;
|
||||
for(a = attribs; a != &attribs[header->numAttribs]; a++)
|
||||
a->stride = stride;
|
||||
header->attribDesc = new AttribDesc[header->numAttribs];
|
||||
memcpy(header->attribDesc, attribs,
|
||||
header->numAttribs*sizeof(AttribDesc));
|
||||
|
||||
//
|
||||
// Allocate and fill vertex buffer
|
||||
//
|
||||
Skin *skin = Skin::get(geo);
|
||||
uint8 *verts = new uint8[header->totalNumVertex*stride];
|
||||
header->vertexBuffer = verts;
|
||||
|
||||
// Positions
|
||||
for(a = attribs; a->index != ATTRIB_POS; a++)
|
||||
;
|
||||
instV3d(VERT_FLOAT3, verts + a->offset,
|
||||
geo->morphTargets[0].vertices,
|
||||
header->totalNumVertex, a->stride);
|
||||
|
||||
// Normals
|
||||
if(hasNormals){
|
||||
for(a = attribs; a->index != ATTRIB_NORMAL; a++)
|
||||
;
|
||||
instV3d(VERT_FLOAT3, verts + a->offset,
|
||||
geo->morphTargets[0].normals,
|
||||
header->totalNumVertex, a->stride);
|
||||
}
|
||||
|
||||
// Prelighting
|
||||
if(isPrelit){
|
||||
for(a = attribs; a->index != ATTRIB_COLOR; a++)
|
||||
;
|
||||
instColor(VERT_RGBA, verts + a->offset,
|
||||
geo->colors,
|
||||
header->totalNumVertex, a->stride);
|
||||
}
|
||||
|
||||
// Texture coordinates
|
||||
for(int32 n = 0; n < geo->numTexCoordSets; n++){
|
||||
for(a = attribs; a->index != ATTRIB_TEXCOORDS0+n; a++)
|
||||
;
|
||||
instV2d(VERT_FLOAT2, verts + a->offset,
|
||||
geo->texCoords[n],
|
||||
header->totalNumVertex, a->stride);
|
||||
}
|
||||
|
||||
// Weights
|
||||
for(a = attribs; a->index != ATTRIB_WEIGHTS; a++)
|
||||
;
|
||||
instV4d(VERT_FLOAT4, verts + a->offset,
|
||||
skin->weights,
|
||||
header->totalNumVertex, a->stride);
|
||||
|
||||
// Indices
|
||||
for(a = attribs; a->index != ATTRIB_INDICES; a++)
|
||||
;
|
||||
// not really colors of course but what the heck
|
||||
instColor(VERT_RGBA, verts + a->offset,
|
||||
skin->indices,
|
||||
header->totalNumVertex, a->stride);
|
||||
|
||||
glGenBuffers(1, &header->vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, header->vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, header->totalNumVertex*stride,
|
||||
header->vertexBuffer, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
void
|
||||
skinUninstanceCB(Geometry *geo, InstanceDataHeader *header)
|
||||
{
|
||||
assert(0 && "can't uninstance");
|
||||
}
|
||||
|
||||
#define U(s) currentShader->uniformLocations[findUniform(s)]
|
||||
|
||||
static float skinMatrices[64*16];
|
||||
|
||||
void
|
||||
updateSkinMatrices(Atomic *a)
|
||||
{
|
||||
Skin *skin = Skin::get(a->geometry);
|
||||
HAnimHierarchy *hier = Skin::getHierarchy(a);
|
||||
Matrix *invMats = (Matrix*)skin->inverseMatrices;
|
||||
|
||||
float *m;
|
||||
m = (float*)skinMatrices;
|
||||
for(int i = 0; i < hier->numNodes; i++){
|
||||
invMats[i].rightw = 0.0f;
|
||||
invMats[i].upw = 0.0f;
|
||||
invMats[i].atw = 0.0f;
|
||||
invMats[i].posw = 1.0f;
|
||||
Matrix::mult((Matrix*)m, &hier->matrices[i], &invMats[i]);
|
||||
m[3] = 0.0f;
|
||||
m[7] = 0.0f;
|
||||
m[11] = 0.0f;
|
||||
m[15] = 1.0f;
|
||||
m += 16;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
skinRenderCB(Atomic *atomic, InstanceDataHeader *header)
|
||||
{
|
||||
Material *m;
|
||||
RGBAf col;
|
||||
GLfloat surfProps[4];
|
||||
int id;
|
||||
|
||||
setWorldMatrix(atomic->getFrame()->getLTM());
|
||||
lightingCB();
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, header->vbo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo);
|
||||
setAttribPointers(header);
|
||||
|
||||
InstanceData *inst = header->inst;
|
||||
int32 n = header->numMeshes;
|
||||
|
||||
rw::setRenderState(ALPHATESTFUNC, 1);
|
||||
rw::setRenderState(ALPHATESTREF, 50);
|
||||
|
||||
skinShader->use();
|
||||
|
||||
updateSkinMatrices(atomic);
|
||||
glUniformMatrix4fv(U("u_boneMatrices"), 64, GL_FALSE,
|
||||
(GLfloat*)skinMatrices);
|
||||
|
||||
while(n--){
|
||||
m = inst->material;
|
||||
|
||||
convColor(&col, &m->color);
|
||||
glUniform4fv(U("u_matColor"), 1, (GLfloat*)&col);
|
||||
|
||||
surfProps[0] = m->surfaceProps.ambient;
|
||||
surfProps[1] = m->surfaceProps.specular;
|
||||
surfProps[2] = m->surfaceProps.diffuse;
|
||||
surfProps[3] = 0.0f;
|
||||
glUniform4fv(U("u_surfaceProps"), 1, surfProps);
|
||||
|
||||
setTexture(0, m->texture);
|
||||
|
||||
rw::setRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF);
|
||||
|
||||
flushCache();
|
||||
glDrawElements(header->primType, inst->numIndex,
|
||||
GL_UNSIGNED_SHORT, (void*)(uintptr)inst->offset);
|
||||
inst++;
|
||||
}
|
||||
}
|
||||
|
||||
ObjPipeline*
|
||||
makeSkinPipeline(void)
|
||||
{
|
||||
ObjPipeline *pipe = new ObjPipeline(PLATFORM_GL3);
|
||||
pipe->instanceCB = defaultInstanceCB;
|
||||
pipe->uninstanceCB = defaultUninstanceCB;
|
||||
pipe->renderCB = defaultRenderCB;
|
||||
pipe->instanceCB = skinInstanceCB;
|
||||
pipe->uninstanceCB = skinUninstanceCB;
|
||||
pipe->renderCB = skinRenderCB;
|
||||
pipe->pluginID = ID_SKIN;
|
||||
pipe->pluginData = 1;
|
||||
return pipe;
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#ifdef RW_OPENGL
|
||||
#include <GL/glew.h>
|
||||
#endif
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#ifdef RW_OPENGL
|
||||
#include <GL/glew.h>
|
||||
#include "rwgl3.h"
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#ifdef RW_OPENGL
|
||||
#include <GL/glew.h>
|
||||
#include "rwgl3.h"
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../rwplg.h"
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwanim.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#include "rwwdgl.h"
|
||||
@ -603,7 +604,7 @@ skinInstanceCB(Geometry *g, int32 i, uint32 offset)
|
||||
header->dataSize = offset*g->numVertices;
|
||||
header->data = new uint8[header->dataSize];
|
||||
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, g, skinGlobals.offset);
|
||||
Skin *skin = Skin::get(g);
|
||||
if(skin == nil)
|
||||
return 8;
|
||||
|
||||
@ -631,7 +632,7 @@ skinUninstanceCB(Geometry *geo)
|
||||
{
|
||||
InstanceDataHeader *header = (InstanceDataHeader*)geo->instData;
|
||||
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
Skin *skin = Skin::get(geo);
|
||||
if(skin == nil)
|
||||
return;
|
||||
|
||||
|
107
src/hanim.cpp
107
src/hanim.cpp
@ -8,6 +8,7 @@
|
||||
#include "rwplg.h"
|
||||
#include "rwpipeline.h"
|
||||
#include "rwobjects.h"
|
||||
#include "rwanim.h"
|
||||
#include "rwplugins.h"
|
||||
#include "ps2/rwps2.h"
|
||||
#include "ps2/rwps2plg.h"
|
||||
@ -25,22 +26,24 @@ int32 hAnimOffset;
|
||||
bool32 hAnimDoStream = 1;
|
||||
|
||||
HAnimHierarchy*
|
||||
HAnimHierarchy::create(int32 numNodes, int32 *nodeFlags, int32 *nodeIDs, int32 flags, int32 maxKeySize)
|
||||
HAnimHierarchy::create(int32 numNodes, int32 *nodeFlags, int32 *nodeIDs,
|
||||
int32 flags, int32 maxKeySize)
|
||||
{
|
||||
HAnimHierarchy *hier = (HAnimHierarchy*)malloc(sizeof(*hier));
|
||||
hier->currentAnim = AnimInterpolator::create(numNodes, maxKeySize);
|
||||
|
||||
hier->numNodes = numNodes;
|
||||
hier->flags = flags;
|
||||
hier->maxInterpKeyFrameSize = maxKeySize;
|
||||
hier->parentFrame = nil;
|
||||
hier->parentHierarchy = hier;
|
||||
if(hier->flags & 2)
|
||||
hier->matrices = hier->matricesUnaligned = nil;
|
||||
else{
|
||||
if(hier->flags & NOMATRICES){
|
||||
hier->matrices = nil;
|
||||
hier->matricesUnaligned = nil;
|
||||
}else{
|
||||
hier->matricesUnaligned =
|
||||
(float*) new uint8[hier->numNodes*64 + 15];
|
||||
(void*) new uint8[hier->numNodes*64 + 15];
|
||||
hier->matrices =
|
||||
(float*)((uintptr)hier->matricesUnaligned & ~0xF);
|
||||
(Matrix*)((uintptr)hier->matricesUnaligned & ~0xF);
|
||||
}
|
||||
hier->nodeInfo = new HAnimNodeInfo[hier->numNodes];
|
||||
for(int32 i = 0; i < hier->numNodes; i++){
|
||||
@ -112,6 +115,43 @@ HAnimHierarchy::find(Frame *f)
|
||||
return HAnimHierarchy::find(f->child);
|
||||
}
|
||||
|
||||
void
|
||||
HAnimHierarchy::updateMatrices(void)
|
||||
{
|
||||
// TODO: handle more (all!) cases
|
||||
|
||||
Matrix rootMat, animMat;
|
||||
Matrix *curMat, *parentMat;
|
||||
Matrix **sp, *stack[64];
|
||||
Frame *frm, *parfrm;
|
||||
int32 i;
|
||||
AnimInterpolator *anim = this->currentAnim;
|
||||
|
||||
sp = stack;
|
||||
curMat = this->matrices;
|
||||
|
||||
frm = this->parentFrame;
|
||||
if(frm && (parfrm = frm->getParent()))
|
||||
rootMat = *parfrm->getLTM();
|
||||
else
|
||||
rootMat.setIdentity();
|
||||
parentMat = &rootMat;
|
||||
HAnimNodeInfo *node = this->nodeInfo;
|
||||
for(i = 0; i < this->numNodes; i++){
|
||||
anim->applyCB(&animMat, anim->getInterpFrame(i));
|
||||
Matrix::mult(curMat, parentMat, &animMat);
|
||||
|
||||
if(node->flags & PUSH)
|
||||
*sp++ = parentMat;
|
||||
parentMat = curMat;
|
||||
if(node->flags & POP)
|
||||
parentMat = *--sp;
|
||||
|
||||
node++;
|
||||
curMat++;
|
||||
}
|
||||
}
|
||||
|
||||
HAnimData*
|
||||
HAnimData::get(Frame *f)
|
||||
{
|
||||
@ -161,6 +201,8 @@ readHAnim(Stream *stream, int32, void *object, int32 offset, int32)
|
||||
if(numNodes != 0){
|
||||
int32 flags = stream->readI32();
|
||||
int32 maxKeySize = stream->readI32();
|
||||
// Sizes are fucked for 64 bit pointers but
|
||||
// AnimInterpolator::create() will take care of that
|
||||
int32 *nodeFlags = new int32[numNodes];
|
||||
int32 *nodeIDs = new int32[numNodes];
|
||||
for(int32 i = 0; i < numNodes; i++){
|
||||
@ -190,7 +232,7 @@ writeHAnim(Stream *stream, int32, void *object, int32 offset, int32)
|
||||
HAnimHierarchy *hier = hanim->hierarchy;
|
||||
stream->writeI32(hier->numNodes);
|
||||
stream->writeI32(hier->flags);
|
||||
stream->writeI32(hier->maxInterpKeyFrameSize);
|
||||
stream->writeI32(hier->currentAnim->maxInterpKeyFrameSize);
|
||||
for(int32 i = 0; i < hier->numNodes; i++){
|
||||
stream->writeI32(hier->nodeInfo[i].id);
|
||||
stream->writeI32(hier->nodeInfo[i].index);
|
||||
@ -217,9 +259,9 @@ hAnimFrameRead(Stream *stream, Animation *anim)
|
||||
HAnimKeyFrame *frames = (HAnimKeyFrame*)anim->keyframes;
|
||||
for(int32 i = 0; i < anim->numFrames; i++){
|
||||
frames[i].time = stream->readF32();
|
||||
stream->read(frames[i].q, 4*4);
|
||||
stream->read(frames[i].t, 3*4);
|
||||
int32 prev = stream->readI32();
|
||||
stream->read(&frames[i].q, 4*4);
|
||||
stream->read(&frames[i].t, 3*4);
|
||||
int32 prev = stream->readI32()/0x24;
|
||||
frames[i].prev = &frames[prev];
|
||||
}
|
||||
}
|
||||
@ -230,9 +272,9 @@ hAnimFrameWrite(Stream *stream, Animation *anim)
|
||||
HAnimKeyFrame *frames = (HAnimKeyFrame*)anim->keyframes;
|
||||
for(int32 i = 0; i < anim->numFrames; i++){
|
||||
stream->writeF32(frames[i].time);
|
||||
stream->write(frames[i].q, 4*4);
|
||||
stream->write(frames[i].t, 3*4);
|
||||
stream->writeI32(frames[i].prev - frames);
|
||||
stream->write(&frames[i].q, 4*4);
|
||||
stream->write(&frames[i].t, 3*4);
|
||||
stream->writeI32((frames[i].prev - frames)*0x24);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,6 +284,31 @@ hAnimFrameGetSize(Animation *anim)
|
||||
return anim->numFrames*(4 + 4*4 + 3*4 + 4);
|
||||
}
|
||||
|
||||
//void hanimBlendCB(void *out, void *in1, void *in2, float32 a);
|
||||
//void hanimAddCB(void *out, void *in1, void *in2);
|
||||
//void hanimMulRecipCB(void *frame, void *start);
|
||||
|
||||
static void
|
||||
hanimApplyCB(void *result, void *frame)
|
||||
{
|
||||
Matrix *m = (Matrix*)result;
|
||||
HAnimInterpFrame *f = (HAnimInterpFrame*)frame;
|
||||
*m = Matrix::makeRotation(f->q);
|
||||
m->pos = f->t;
|
||||
}
|
||||
|
||||
static void
|
||||
hanimInterpCB(void *vout, void *vin1, void *vin2, float32 t, void*)
|
||||
{
|
||||
HAnimInterpFrame *out = (HAnimInterpFrame*)vout;
|
||||
HAnimKeyFrame *in1 = (HAnimKeyFrame*)vin1;
|
||||
HAnimKeyFrame *in2 = (HAnimKeyFrame*)vin2;
|
||||
assert(t >= in1->time && t <= in2->time);
|
||||
float32 a = (t - in1->time)/(in2->time - in1->time);
|
||||
out->t = lerp(in1->t, in2->t, a);
|
||||
out->q = slerp(in1->q, in2->q, a);
|
||||
}
|
||||
|
||||
void
|
||||
registerHAnimPlugin(void)
|
||||
{
|
||||
@ -255,12 +322,18 @@ registerHAnimPlugin(void)
|
||||
|
||||
AnimInterpolatorInfo *info = new AnimInterpolatorInfo;
|
||||
info->id = 1;
|
||||
info->keyFrameSize = sizeof(HAnimKeyFrame);
|
||||
info->customDataSize = sizeof(HAnimKeyFrame);
|
||||
info->interpKeyFrameSize = sizeof(HAnimInterpFrame);
|
||||
info->animKeyFrameSize = sizeof(HAnimKeyFrame);
|
||||
info->customDataSize = 0;
|
||||
info->applyCB = hanimApplyCB;
|
||||
info->blendCB = nil;
|
||||
info->interpCB = hanimInterpCB;
|
||||
info->addCB = nil;
|
||||
info->mulRecipCB = nil;
|
||||
info->streamRead = hAnimFrameRead;
|
||||
info->streamWrite = hAnimFrameWrite;
|
||||
info->streamGetSize = hAnimFrameGetSize;
|
||||
registerAnimInterpolatorInfo(info);
|
||||
AnimInterpolatorInfo::registerInterp(info);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "rwpipeline.h"
|
||||
#include "rwobjects.h"
|
||||
#include "rwengine.h"
|
||||
#include "rwanim.h"
|
||||
#include "rwplugins.h"
|
||||
#include "ps2/rwps2.h"
|
||||
#include "ps2/rwps2plg.h"
|
||||
|
@ -51,6 +51,19 @@ findMinVertAndNumVertices(uint16 *indices, uint32 numIndices, uint32 *minVert, i
|
||||
*numVertices = max - min + 1;
|
||||
}
|
||||
|
||||
void
|
||||
instV4d(int type, uint8 *dst, float *src, uint32 numVertices, uint32 stride)
|
||||
{
|
||||
if(type == VERT_FLOAT4)
|
||||
for(uint32 i = 0; i < numVertices; i++){
|
||||
memcpy(dst, src, 16);
|
||||
dst += stride;
|
||||
src += 4;
|
||||
}
|
||||
else
|
||||
assert(0 && "unsupported instV3d type");
|
||||
}
|
||||
|
||||
void
|
||||
instV3d(int type, uint8 *dst, float *src, uint32 numVertices, uint32 stride)
|
||||
{
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "../rwplg.h"
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwanim.h"
|
||||
#include "../rwplugins.h"
|
||||
#include "rwps2.h"
|
||||
#include "rwps2plg.h"
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwanim.h"
|
||||
#include "../rwplugins.h"
|
||||
#include "rwps2.h"
|
||||
#include "rwps2plg.h"
|
||||
@ -947,8 +948,8 @@ genericUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh
|
||||
uint32 *weights = nil;
|
||||
int8 *adc = nil;
|
||||
Skin *skin = nil;
|
||||
if(skinGlobals.offset)
|
||||
skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
if(skinGlobals.geoOffset)
|
||||
skin = Skin::get(geo);
|
||||
|
||||
PipeAttribute *a;
|
||||
for(int32 i = 0; i < nelem(pipe->attribs); i++)
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../rwplg.h"
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwanim.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#include "rwps2.h"
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "../rwplg.h"
|
||||
#include "../rwpipeline.h"
|
||||
#include "../rwobjects.h"
|
||||
#include "../rwanim.h"
|
||||
#include "../rwengine.h"
|
||||
#include "../rwplugins.h"
|
||||
#include "rwps2.h"
|
||||
@ -183,7 +184,7 @@ instanceSkinData(Geometry*, Mesh *m, Skin *skin, uint32 *data)
|
||||
void
|
||||
skinInstanceCB(MatPipeline *, Geometry *g, Mesh *m, uint8 **data)
|
||||
{
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, g, skinGlobals.offset);
|
||||
Skin *skin = Skin::get(g);
|
||||
if(skin == nil)
|
||||
return;
|
||||
instanceSkinData(g, m, skin, (uint32*)data[4]);
|
||||
@ -193,7 +194,7 @@ skinInstanceCB(MatPipeline *, Geometry *g, Mesh *m, uint8 **data)
|
||||
int32
|
||||
findVertexSkin(Geometry *g, uint32 flags[], uint32 mask, Vertex *v)
|
||||
{
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, g, skinGlobals.offset);
|
||||
Skin *skin = Skin::get(g);
|
||||
float32 *wghts = nil;
|
||||
uint8 *inds = nil;
|
||||
if(skin){
|
||||
@ -245,7 +246,7 @@ findVertexSkin(Geometry *g, uint32 flags[], uint32 mask, Vertex *v)
|
||||
void
|
||||
insertVertexSkin(Geometry *geo, int32 i, uint32 mask, Vertex *v)
|
||||
{
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
Skin *skin = Skin::get(geo);
|
||||
insertVertex(geo, i, mask, v);
|
||||
if(mask & 0x10000){
|
||||
memcpy(&skin->weights[i*4], v->w, 16);
|
||||
@ -308,7 +309,7 @@ skinUninstanceCB(MatPipeline*, Geometry *geo, uint32 flags[], Mesh *mesh, uint8
|
||||
void
|
||||
skinPreCB(MatPipeline*, Geometry *geo)
|
||||
{
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
Skin *skin = Skin::get(geo);
|
||||
if(skin == nil)
|
||||
return;
|
||||
uint8 *data = skin->data;
|
||||
@ -322,7 +323,7 @@ skinPreCB(MatPipeline*, Geometry *geo)
|
||||
void
|
||||
skinPostCB(MatPipeline*, Geometry *geo)
|
||||
{
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
Skin *skin = Skin::get(geo);
|
||||
if(skin){
|
||||
skin->findNumWeights(geo->numVertices);
|
||||
skin->findUsedBones(geo->numVertices);
|
||||
|
174
src/rwanim.h
Normal file
174
src/rwanim.h
Normal file
@ -0,0 +1,174 @@
|
||||
#include <stddef.h>
|
||||
|
||||
namespace rw {
|
||||
|
||||
struct Animation;
|
||||
|
||||
// These sizes of these are sadly not platform independent
|
||||
// because pointer sizes can vary.
|
||||
|
||||
struct KeyFrameHeader
|
||||
{
|
||||
KeyFrameHeader *prev;
|
||||
float32 time;
|
||||
|
||||
KeyFrameHeader *next(int32 sz){
|
||||
return (KeyFrameHeader*)((uint8*)this + sz); }
|
||||
};
|
||||
|
||||
struct InterpFrameHeader
|
||||
{
|
||||
KeyFrameHeader *keyFrame1;
|
||||
KeyFrameHeader *keyFrame2;
|
||||
};
|
||||
|
||||
struct AnimInterpolatorInfo
|
||||
{
|
||||
typedef void (*ApplyCB)(void *result, void *frame);
|
||||
typedef void (*BlendCB)(void *out, void *in1, void *in2, float32 a);
|
||||
typedef void (*InterpCB)(void *out, void *in1, void *in2, float32 t,
|
||||
void *custom);
|
||||
typedef void (*AddCB)(void *out, void *in1, void *in2);
|
||||
typedef void (*MulRecipCB)(void *frame, void *start);
|
||||
|
||||
int32 id;
|
||||
int32 interpKeyFrameSize;
|
||||
int32 animKeyFrameSize;
|
||||
int32 customDataSize;
|
||||
|
||||
ApplyCB applyCB;
|
||||
BlendCB blendCB;
|
||||
InterpCB interpCB;
|
||||
AddCB addCB;
|
||||
MulRecipCB mulRecipCB;
|
||||
void (*streamRead)(Stream *stream, Animation *anim);
|
||||
void (*streamWrite)(Stream *stream, Animation *anim);
|
||||
uint32 (*streamGetSize)(Animation *anim);
|
||||
|
||||
static void registerInterp(AnimInterpolatorInfo *interpInfo);
|
||||
static AnimInterpolatorInfo *find(int32 id);
|
||||
};
|
||||
|
||||
struct Animation
|
||||
{
|
||||
AnimInterpolatorInfo *interpInfo;
|
||||
int32 numFrames;
|
||||
int32 flags;
|
||||
float32 duration;
|
||||
void *keyframes;
|
||||
void *customData;
|
||||
|
||||
static Animation *create(AnimInterpolatorInfo*, int32 numFrames,
|
||||
int32 flags, float duration);
|
||||
void destroy(void);
|
||||
int32 getNumNodes(void);
|
||||
static Animation *streamRead(Stream *stream);
|
||||
static Animation *streamReadLegacy(Stream *stream);
|
||||
bool streamWrite(Stream *stream);
|
||||
bool streamWriteLegacy(Stream *stream);
|
||||
uint32 streamGetSize(void);
|
||||
};
|
||||
|
||||
struct AnimInterpolator
|
||||
{
|
||||
Animation *currentAnim;
|
||||
float32 currentTime;
|
||||
void *nextFrame;
|
||||
int32 maxInterpKeyFrameSize;
|
||||
int32 currentInterpKeyFrameSize;
|
||||
int32 currentAnimKeyFrameSize;
|
||||
int32 numNodes;
|
||||
// TODO some callbacks, parent/sub
|
||||
// cached from the InterpolatorInfo
|
||||
AnimInterpolatorInfo::ApplyCB applyCB;
|
||||
AnimInterpolatorInfo::BlendCB blendCB;
|
||||
AnimInterpolatorInfo::InterpCB interpCB;
|
||||
AnimInterpolatorInfo::AddCB addCB;
|
||||
// after this interpolated frames
|
||||
|
||||
static AnimInterpolator *create(int32 numNodes, int32 maxKeyFrameSize);
|
||||
void destroy(void);
|
||||
bool32 setCurrentAnim(Animation *anim);
|
||||
void addTime(float32 t);
|
||||
void *getFrames(void){ return this+1;}
|
||||
InterpFrameHeader *getInterpFrame(int32 n){
|
||||
return (InterpFrameHeader*)((uint8*)getFrames() +
|
||||
n*currentInterpKeyFrameSize);
|
||||
}
|
||||
KeyFrameHeader *getAnimFrame(int32 n){
|
||||
return (KeyFrameHeader*)((uint8*)currentAnim->keyframes +
|
||||
n*currentAnimKeyFrameSize);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// UV anim
|
||||
//
|
||||
|
||||
struct UVAnimKeyFrame
|
||||
{
|
||||
UVAnimKeyFrame *prev;
|
||||
float32 time;
|
||||
float32 uv[6];
|
||||
};
|
||||
|
||||
struct UVAnimInterpFrame
|
||||
{
|
||||
UVAnimKeyFrame *keyFrame1;
|
||||
UVAnimKeyFrame *keyFrame2;
|
||||
float32 uv[6];
|
||||
};
|
||||
|
||||
struct UVAnimDictionary;
|
||||
|
||||
// RW does it differently...maybe we should implement RtDict
|
||||
// and make it more general?
|
||||
|
||||
struct UVAnimCustomData
|
||||
{
|
||||
char name[32];
|
||||
int32 nodeToUVChannel[8];
|
||||
int32 refCount;
|
||||
|
||||
void destroy(Animation *anim);
|
||||
static UVAnimCustomData *get(Animation *anim){
|
||||
return (UVAnimCustomData*)anim->customData; }
|
||||
};
|
||||
|
||||
// This should be more general probably
|
||||
struct UVAnimDictEntry
|
||||
{
|
||||
Animation *anim;
|
||||
LLLink inDict;
|
||||
static UVAnimDictEntry *fromDict(LLLink *lnk){
|
||||
return LLLinkGetData(lnk, UVAnimDictEntry, inDict); }
|
||||
};
|
||||
|
||||
// This too
|
||||
struct UVAnimDictionary
|
||||
{
|
||||
LinkList animations;
|
||||
|
||||
static UVAnimDictionary *create(void);
|
||||
void destroy(void);
|
||||
int32 count(void) { return this->animations.count(); }
|
||||
void add(Animation *anim);
|
||||
Animation *find(const char *name);
|
||||
|
||||
static UVAnimDictionary *streamRead(Stream *stream);
|
||||
bool streamWrite(Stream *stream);
|
||||
uint32 streamGetSize(void);
|
||||
};
|
||||
|
||||
extern UVAnimDictionary *currentUVAnimDictionary;
|
||||
|
||||
struct UVAnim
|
||||
{
|
||||
AnimInterpolator *interp[8];
|
||||
};
|
||||
|
||||
extern int32 uvAnimOffset;
|
||||
|
||||
void registerUVAnimPlugin(void);
|
||||
|
||||
}
|
21
src/rwbase.h
21
src/rwbase.h
@ -112,14 +112,20 @@ inline V3d normalize(const V3d &v) { return scale(v, 1.0f/length(v)); }
|
||||
inline V3d setlength(const V3d &v, float32 l) { return scale(v, l/length(v)); }
|
||||
V3d cross(const V3d &a, const V3d &b);
|
||||
inline float32 dot(const V3d &a, const V3d &b) { return a.x*b.x + a.y*b.y + a.z*b.z; }
|
||||
inline V3d lerp(const V3d &a, const V3d &b, float32 r){
|
||||
return V3d(a.x + r*(b.x - a.x),
|
||||
a.y + r*(b.y - a.y),
|
||||
a.z + r*(b.z - a.z));
|
||||
};
|
||||
|
||||
struct Quat
|
||||
{
|
||||
float32 w, x, y, z;
|
||||
Quat(void) : w(0.0f), x(0.0f), y(0.0f), z(0.0f) {}
|
||||
Quat(float32 w, float32 x, float32 y, float32 z) : w(w), x(x), y(y), z(z) {}
|
||||
Quat(float32 w, V3d vec) : w(w), x(vec.x), y(vec.y), z(vec.z) {}
|
||||
Quat(V3d vec) : w(0.0f), x(vec.x), y(vec.y), z(vec.z) {}
|
||||
// order is important for streaming
|
||||
float32 x, y, z, w;
|
||||
Quat(void) : x(0.0f), y(0.0f), z(0.0f), w(0.0f) {}
|
||||
Quat(float32 w, float32 x, float32 y, float32 z) : x(x), y(y), z(z), w(w) {}
|
||||
Quat(float32 w, V3d vec) : x(vec.x), y(vec.y), z(vec.z), w(w) {}
|
||||
Quat(V3d vec) : x(vec.x), y(vec.y), z(vec.z), w(0.0f) {}
|
||||
static Quat rotation(float32 angle, const V3d &axis){
|
||||
return Quat(cos(angle/2.0f), scale(axis, sin(angle/2.0f))); }
|
||||
void set(float32 w, float32 x, float32 y, float32 z){
|
||||
@ -129,12 +135,17 @@ struct Quat
|
||||
|
||||
inline Quat add(const Quat &q, const Quat &p) { return Quat(q.w+p.w, q.x+p.x, q.y+p.y, q.z+p.z); }
|
||||
inline Quat sub(const Quat &q, const Quat &p) { return Quat(q.w-p.w, q.x-p.x, q.y-p.y, q.z-p.z); }
|
||||
inline Quat negate(const Quat &q) { return Quat(-q.w, -q.x, -q.y, -q.z); }
|
||||
inline float32 dot(const Quat &q, const Quat &p) { return q.w*p.w + q.x*p.x + q.y*p.y + q.z*p.z; }
|
||||
inline Quat scale(const Quat &q, float32 r) { return Quat(q.w*r, q.x*r, q.y*r, q.z*r); }
|
||||
inline float32 length(const Quat &q) { return sqrt(q.w*q.w + q.x*q.x + q.y*q.y + q.z*q.z); }
|
||||
inline Quat normalize(const Quat &q) { return scale(q, 1.0f/length(q)); }
|
||||
inline Quat conj(const Quat &q) { return Quat(q.w, -q.x, -q.y, -q.z); }
|
||||
Quat mult(const Quat &q, const Quat &p);
|
||||
inline V3d rotate(const V3d &v, const Quat &q) { return mult(mult(q, Quat(v)), conj(q)).vec(); }
|
||||
Quat lerp(const Quat &q, const Quat &p, float32 r);
|
||||
Quat slerp(const Quat &q, const Quat &p, float32 a);
|
||||
|
||||
|
||||
struct Matrix
|
||||
{
|
||||
|
@ -641,102 +641,4 @@ struct TexDictionary : PluginBase<TexDictionary>
|
||||
|
||||
extern TexDictionary *currentTexDictionary;
|
||||
|
||||
struct Animation;
|
||||
|
||||
struct AnimInterpolatorInfo
|
||||
{
|
||||
int32 id;
|
||||
int32 keyFrameSize;
|
||||
int32 customDataSize;
|
||||
void (*streamRead)(Stream *stream, Animation *anim);
|
||||
void (*streamWrite)(Stream *stream, Animation *anim);
|
||||
uint32 (*streamGetSize)(Animation *anim);
|
||||
};
|
||||
|
||||
void registerAnimInterpolatorInfo(AnimInterpolatorInfo *interpInfo);
|
||||
AnimInterpolatorInfo *findAnimInterpolatorInfo(int32 id);
|
||||
|
||||
struct Animation
|
||||
{
|
||||
AnimInterpolatorInfo *interpInfo;
|
||||
int32 numFrames;
|
||||
int32 flags;
|
||||
float duration;
|
||||
void *keyframes;
|
||||
void *customData;
|
||||
|
||||
static Animation *create(AnimInterpolatorInfo*, int32 numFrames, int32 flags, float duration);
|
||||
void destroy(void);
|
||||
static Animation *streamRead(Stream *stream);
|
||||
static Animation *streamReadLegacy(Stream *stream);
|
||||
bool streamWrite(Stream *stream);
|
||||
bool streamWriteLegacy(Stream *stream);
|
||||
uint32 streamGetSize(void);
|
||||
};
|
||||
|
||||
struct AnimInterpolator
|
||||
{
|
||||
// only a stub right now
|
||||
Animation *anim;
|
||||
|
||||
AnimInterpolator(Animation *anim);
|
||||
};
|
||||
|
||||
struct UVAnimKeyFrame
|
||||
{
|
||||
UVAnimKeyFrame *prev;
|
||||
float time;
|
||||
float uv[6];
|
||||
};
|
||||
|
||||
struct UVAnimDictionary;
|
||||
|
||||
// RW does it differently...maybe we should implement RtDict
|
||||
// and make it more general?
|
||||
|
||||
struct UVAnimCustomData
|
||||
{
|
||||
char name[32];
|
||||
int32 nodeToUVChannel[8];
|
||||
int32 refCount;
|
||||
|
||||
void destroy(Animation *anim);
|
||||
};
|
||||
|
||||
// This should be more general probably
|
||||
struct UVAnimDictEntry
|
||||
{
|
||||
Animation *anim;
|
||||
LLLink inDict;
|
||||
static UVAnimDictEntry *fromDict(LLLink *lnk){
|
||||
return LLLinkGetData(lnk, UVAnimDictEntry, inDict); }
|
||||
};
|
||||
|
||||
// This too
|
||||
struct UVAnimDictionary
|
||||
{
|
||||
LinkList animations;
|
||||
|
||||
static UVAnimDictionary *create(void);
|
||||
void destroy(void);
|
||||
int32 count(void) { return this->animations.count(); }
|
||||
void add(Animation *anim);
|
||||
Animation *find(const char *name);
|
||||
|
||||
static UVAnimDictionary *streamRead(Stream *stream);
|
||||
bool streamWrite(Stream *stream);
|
||||
uint32 streamGetSize(void);
|
||||
};
|
||||
|
||||
extern UVAnimDictionary *currentUVAnimDictionary;
|
||||
|
||||
struct UVAnim
|
||||
{
|
||||
AnimInterpolator *interp[8];
|
||||
};
|
||||
|
||||
extern int32 uvAnimOffset;
|
||||
|
||||
void registerUVAnimPlugin(void);
|
||||
|
||||
}
|
||||
|
@ -41,11 +41,13 @@ enum {
|
||||
VERT_NORMSHORT3,
|
||||
VERT_FLOAT2,
|
||||
VERT_FLOAT3,
|
||||
VERT_FLOAT4,
|
||||
VERT_ARGB,
|
||||
VERT_RGBA,
|
||||
VERT_COMPNORM
|
||||
};
|
||||
|
||||
void instV4d(int type, uint8 *dst, float *src, uint32 numVertices, uint32 stride);
|
||||
void instV3d(int type, uint8 *dst, float *src, uint32 numVertices, uint32 stride);
|
||||
void uninstV3d(int type, float *dst, uint8 *src, uint32 numVertices, uint32 stride);
|
||||
void instV2d(int type, uint8 *dst, float *src, uint32 numVertices, uint32 stride);
|
||||
|
@ -7,9 +7,17 @@ namespace rw {
|
||||
struct HAnimKeyFrame
|
||||
{
|
||||
HAnimKeyFrame *prev;
|
||||
float time;
|
||||
float q[4];
|
||||
float t[3];
|
||||
float32 time;
|
||||
Quat q;
|
||||
V3d t;
|
||||
};
|
||||
|
||||
struct HAnimInterpFrame
|
||||
{
|
||||
HAnimKeyFrame *keyFrame1;
|
||||
HAnimKeyFrame *keyFrame2;
|
||||
Quat q;
|
||||
V3d t;
|
||||
};
|
||||
|
||||
struct HAnimNodeInfo
|
||||
@ -24,24 +32,34 @@ struct HAnimHierarchy
|
||||
{
|
||||
int32 flags;
|
||||
int32 numNodes;
|
||||
float *matrices;
|
||||
float *matricesUnaligned;
|
||||
Matrix *matrices;
|
||||
void *matricesUnaligned;
|
||||
HAnimNodeInfo *nodeInfo;
|
||||
Frame *parentFrame;
|
||||
HAnimHierarchy *parentHierarchy; // mostly unused
|
||||
AnimInterpolator *currentAnim;
|
||||
|
||||
// temporary
|
||||
int32 maxInterpKeyFrameSize;
|
||||
|
||||
static HAnimHierarchy *create(int32 numNodes, int32 *nodeFlags, int32 *nodeIDs, int32 flags, int32 maxKeySize);
|
||||
static HAnimHierarchy *create(int32 numNodes, int32 *nodeFlags,
|
||||
int32 *nodeIDs, int32 flags, int32 maxKeySize);
|
||||
void destroy(void);
|
||||
void attachByIndex(int32 id);
|
||||
void attach(void);
|
||||
int32 getIndex(int32 id);
|
||||
void updateMatrices(void);
|
||||
|
||||
static HAnimHierarchy *get(Frame *f);
|
||||
static HAnimHierarchy *get(Clump *c){
|
||||
return find(c->getFrame()); }
|
||||
static HAnimHierarchy *find(Frame *f);
|
||||
|
||||
enum Flags {
|
||||
SUBHIERARCHY = 0x1,
|
||||
NOMATRICES = 0x2,
|
||||
|
||||
UPDATEMODELLINGMATRICES = 0x1000,
|
||||
UPDATELTMS = 0x2000,
|
||||
LOCALSPACEMATRICES = 0x4000
|
||||
};
|
||||
enum NodeFlag {
|
||||
POP = 1,
|
||||
PUSH
|
||||
@ -138,6 +156,14 @@ void registerMatFXPlugin(void);
|
||||
* Skin
|
||||
*/
|
||||
|
||||
struct SkinGlobals
|
||||
{
|
||||
int32 geoOffset;
|
||||
int32 atomicOffset;
|
||||
ObjPipeline *pipelines[NUM_PLATFORMS];
|
||||
};
|
||||
extern SkinGlobals skinGlobals;
|
||||
|
||||
struct Skin
|
||||
{
|
||||
int32 numBones;
|
||||
@ -173,15 +199,21 @@ struct Skin
|
||||
void init(int32 numBones, int32 numUsedBones, int32 numVertices);
|
||||
void findNumWeights(int32 numVertices);
|
||||
void findUsedBones(int32 numVertices);
|
||||
|
||||
static void setPipeline(Atomic *a, int32 type);
|
||||
static Skin *get(Geometry *geo){
|
||||
return *PLUGINOFFSET(Skin*, geo, skinGlobals.geoOffset);
|
||||
}
|
||||
static void setHierarchy(Atomic *atomic, HAnimHierarchy *hier){
|
||||
*PLUGINOFFSET(HAnimHierarchy*, atomic,
|
||||
skinGlobals.atomicOffset) = hier;
|
||||
}
|
||||
static HAnimHierarchy *getHierarchy(Atomic *atomic){
|
||||
return *PLUGINOFFSET(HAnimHierarchy*, atomic,
|
||||
skinGlobals.atomicOffset);
|
||||
}
|
||||
};
|
||||
|
||||
struct SkinGlobals
|
||||
{
|
||||
int32 offset;
|
||||
ObjPipeline *pipelines[NUM_PLATFORMS];
|
||||
};
|
||||
extern SkinGlobals skinGlobals;
|
||||
Stream *readSkinSplitData(Stream *stream, Skin *skin);
|
||||
Stream *writeSkinSplitData(Stream *stream, Skin *skin);
|
||||
int32 skinSplitDataSize(Skin *skin);
|
||||
|
35
src/skin.cpp
35
src/skin.cpp
@ -8,6 +8,7 @@
|
||||
#include "rwplg.h"
|
||||
#include "rwpipeline.h"
|
||||
#include "rwobjects.h"
|
||||
#include "rwanim.h"
|
||||
#include "rwengine.h"
|
||||
#include "rwplugins.h"
|
||||
#include "ps2/rwps2.h"
|
||||
@ -23,7 +24,7 @@
|
||||
|
||||
namespace rw {
|
||||
|
||||
SkinGlobals skinGlobals = { 0, { nil } };
|
||||
SkinGlobals skinGlobals = { 0, 0, { nil } };
|
||||
|
||||
static void*
|
||||
createSkin(void *object, int32 offset, int32)
|
||||
@ -257,6 +258,26 @@ skinRights(void *object, int32, int32, uint32)
|
||||
Skin::setPipeline((Atomic*)object, 1);
|
||||
}
|
||||
|
||||
static void*
|
||||
createSkinAtm(void *object, int32 offset, int32)
|
||||
{
|
||||
*PLUGINOFFSET(void*, object, offset) = nil;
|
||||
return object;
|
||||
}
|
||||
|
||||
static void*
|
||||
destroySkinAtm(void *object, int32 offset, int32)
|
||||
{
|
||||
return object;
|
||||
}
|
||||
|
||||
static void*
|
||||
copySkinAtm(void *dst, void *src, int32 offset, int32)
|
||||
{
|
||||
*PLUGINOFFSET(void*, dst, offset) = *PLUGINOFFSET(void*, src, offset);
|
||||
return dst;
|
||||
}
|
||||
|
||||
void
|
||||
registerSkinPlugin(void)
|
||||
{
|
||||
@ -274,13 +295,15 @@ registerSkinPlugin(void)
|
||||
wdgl::initSkin();
|
||||
gl3::initSkin();
|
||||
|
||||
skinGlobals.offset = Geometry::registerPlugin(sizeof(Skin*), ID_SKIN,
|
||||
createSkin,
|
||||
destroySkin,
|
||||
copySkin);
|
||||
int32 o;
|
||||
o = Geometry::registerPlugin(sizeof(Skin*), ID_SKIN,
|
||||
createSkin, destroySkin, copySkin);
|
||||
Geometry::registerPluginStream(ID_SKIN,
|
||||
readSkin, writeSkin, getSizeSkin);
|
||||
Atomic::registerPlugin(0, ID_SKIN, nil, nil, nil);
|
||||
skinGlobals.geoOffset = o;
|
||||
o = Atomic::registerPlugin(sizeof(HAnimHierarchy*),ID_SKIN,
|
||||
createSkinAtm, destroySkinAtm, copySkinAtm);
|
||||
skinGlobals.atomicOffset = o;
|
||||
Atomic::setStreamRightsCallback(ID_SKIN, skinRights);
|
||||
}
|
||||
|
||||
|
350
src/uvanim.cpp
Normal file
350
src/uvanim.cpp
Normal file
@ -0,0 +1,350 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
|
||||
#include "rwbase.h"
|
||||
#include "rwerror.h"
|
||||
#include "rwplg.h"
|
||||
#include "rwpipeline.h"
|
||||
#include "rwobjects.h"
|
||||
#include "rwanim.h"
|
||||
#include "rwplugins.h"
|
||||
|
||||
#define PLUGIN_ID ID_UVANIMATION
|
||||
|
||||
namespace rw {
|
||||
|
||||
//
|
||||
// UVAnim
|
||||
//
|
||||
|
||||
void
|
||||
UVAnimCustomData::destroy(Animation *anim)
|
||||
{
|
||||
this->refCount--;
|
||||
if(this->refCount <= 0)
|
||||
anim->destroy();
|
||||
}
|
||||
|
||||
UVAnimDictionary *currentUVAnimDictionary;
|
||||
|
||||
UVAnimDictionary*
|
||||
UVAnimDictionary::create(void)
|
||||
{
|
||||
UVAnimDictionary *dict = (UVAnimDictionary*)malloc(sizeof(UVAnimDictionary));
|
||||
if(dict == nil){
|
||||
RWERROR((ERR_ALLOC, sizeof(UVAnimDictionary)));
|
||||
return nil;
|
||||
}
|
||||
dict->animations.init();
|
||||
return dict;
|
||||
}
|
||||
|
||||
void
|
||||
UVAnimDictionary::destroy(void)
|
||||
{
|
||||
FORLIST(lnk, this->animations){
|
||||
UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk);
|
||||
UVAnimCustomData *cust = UVAnimCustomData::get(de->anim);
|
||||
cust->destroy(de->anim);
|
||||
delete de;
|
||||
}
|
||||
free(this);
|
||||
}
|
||||
|
||||
void
|
||||
UVAnimDictionary::add(Animation *anim)
|
||||
{
|
||||
UVAnimDictEntry *de = new UVAnimDictEntry;
|
||||
de->anim = anim;
|
||||
this->animations.append(&de->inDict);
|
||||
}
|
||||
|
||||
UVAnimDictionary*
|
||||
UVAnimDictionary::streamRead(Stream *stream)
|
||||
{
|
||||
if(!findChunk(stream, ID_STRUCT, nil, nil)){
|
||||
RWERROR((ERR_CHUNK, "STRUCT"));
|
||||
return nil;
|
||||
}
|
||||
UVAnimDictionary *dict = UVAnimDictionary::create();
|
||||
if(dict == nil)
|
||||
return nil;
|
||||
int32 numAnims = stream->readI32();
|
||||
Animation *anim;
|
||||
for(int32 i = 0; i < numAnims; i++){
|
||||
if(!findChunk(stream, ID_ANIMANIMATION, nil, nil)){
|
||||
RWERROR((ERR_CHUNK, "ANIMANIMATION"));
|
||||
goto fail;
|
||||
}
|
||||
anim = Animation::streamRead(stream);
|
||||
if(anim == nil)
|
||||
goto fail;
|
||||
dict->add(anim);
|
||||
}
|
||||
return dict;
|
||||
fail:
|
||||
dict->destroy();
|
||||
return nil;
|
||||
}
|
||||
|
||||
bool
|
||||
UVAnimDictionary::streamWrite(Stream *stream)
|
||||
{
|
||||
uint32 size = this->streamGetSize();
|
||||
writeChunkHeader(stream, ID_UVANIMDICT, size);
|
||||
writeChunkHeader(stream, ID_STRUCT, 4);
|
||||
int32 numAnims = this->count();
|
||||
stream->writeI32(numAnims);
|
||||
FORLIST(lnk, this->animations){
|
||||
UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk);
|
||||
de->anim->streamWrite(stream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32
|
||||
UVAnimDictionary::streamGetSize(void)
|
||||
{
|
||||
uint32 size = 12 + 4;
|
||||
FORLIST(lnk, this->animations){
|
||||
UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk);
|
||||
size += 12 + de->anim->streamGetSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
Animation*
|
||||
UVAnimDictionary::find(const char *name)
|
||||
{
|
||||
FORLIST(lnk, this->animations){
|
||||
Animation *anim = UVAnimDictEntry::fromDict(lnk)->anim;
|
||||
UVAnimCustomData *custom = UVAnimCustomData::get(anim);
|
||||
if(strncmp_ci(custom->name, name, 32) == 0)
|
||||
return anim;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
static void
|
||||
uvAnimStreamRead(Stream *stream, Animation *anim)
|
||||
{
|
||||
UVAnimCustomData *custom = UVAnimCustomData::get(anim);
|
||||
UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes;
|
||||
stream->readI32();
|
||||
stream->read(custom->name, 32);
|
||||
stream->read(custom->nodeToUVChannel, 8*4);
|
||||
custom->refCount = 1;
|
||||
|
||||
for(int32 i = 0; i < anim->numFrames; i++){
|
||||
frames[i].time = stream->readF32();
|
||||
stream->read(frames[i].uv, 6*4);
|
||||
int32 prev = stream->readI32();
|
||||
frames[i].prev = &frames[prev];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
uvAnimStreamWrite(Stream *stream, Animation *anim)
|
||||
{
|
||||
UVAnimCustomData *custom = UVAnimCustomData::get(anim);
|
||||
UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes;
|
||||
stream->writeI32(0);
|
||||
stream->write(custom->name, 32);
|
||||
stream->write(custom->nodeToUVChannel, 8*4);
|
||||
|
||||
for(int32 i = 0; i < anim->numFrames; i++){
|
||||
stream->writeF32(frames[i].time);
|
||||
stream->write(frames[i].uv, 6*4);
|
||||
stream->writeI32(frames[i].prev - frames);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32
|
||||
uvAnimStreamGetSize(Animation *anim)
|
||||
{
|
||||
return 4 + 32 + 8*4 + anim->numFrames*(4 + 6*4 + 4);
|
||||
}
|
||||
|
||||
static void
|
||||
registerUVAnimInterpolator(void)
|
||||
{
|
||||
// Linear
|
||||
AnimInterpolatorInfo *info = new AnimInterpolatorInfo;
|
||||
info->id = 0x1C0;
|
||||
info->interpKeyFrameSize = sizeof(UVAnimInterpFrame);
|
||||
info->animKeyFrameSize = sizeof(UVAnimKeyFrame);
|
||||
info->customDataSize = sizeof(UVAnimCustomData);
|
||||
info->applyCB = nil;
|
||||
info->blendCB = nil;
|
||||
info->interpCB = nil;
|
||||
info->addCB = nil;
|
||||
info->mulRecipCB = nil;
|
||||
info->streamRead = uvAnimStreamRead;
|
||||
info->streamWrite = uvAnimStreamWrite;
|
||||
info->streamGetSize = uvAnimStreamGetSize;
|
||||
AnimInterpolatorInfo::registerInterp(info);
|
||||
|
||||
// Param
|
||||
info = new AnimInterpolatorInfo;
|
||||
info->id = 0x1C1;
|
||||
info->interpKeyFrameSize = sizeof(UVAnimInterpFrame);
|
||||
info->animKeyFrameSize = sizeof(UVAnimKeyFrame);
|
||||
info->customDataSize = sizeof(UVAnimCustomData);
|
||||
info->applyCB = nil;
|
||||
info->blendCB = nil;
|
||||
info->interpCB = nil;
|
||||
info->addCB = nil;
|
||||
info->mulRecipCB = nil;
|
||||
info->streamRead = uvAnimStreamRead;
|
||||
info->streamWrite = uvAnimStreamWrite;
|
||||
info->streamGetSize = uvAnimStreamGetSize;
|
||||
AnimInterpolatorInfo::registerInterp(info);
|
||||
}
|
||||
|
||||
int32 uvAnimOffset;
|
||||
|
||||
static void*
|
||||
createUVAnim(void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim;
|
||||
uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
memset(uvanim, 0, sizeof(*uvanim));
|
||||
return object;
|
||||
}
|
||||
|
||||
static void*
|
||||
destroyUVAnim(void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim;
|
||||
uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
AnimInterpolator *ip = uvanim->interp[i];
|
||||
if(ip){
|
||||
UVAnimCustomData *custom =
|
||||
UVAnimCustomData::get(ip->currentAnim);
|
||||
custom->destroy(ip->currentAnim);
|
||||
delete ip;
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
static void*
|
||||
copyUVAnim(void *dst, void *src, int32 offset, int32)
|
||||
{
|
||||
UVAnim *srcuvanim, *dstuvanim;
|
||||
dstuvanim = PLUGINOFFSET(UVAnim, dst, offset);
|
||||
srcuvanim = PLUGINOFFSET(UVAnim, src, offset);
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
AnimInterpolator *srcip = srcuvanim->interp[i];
|
||||
AnimInterpolator *dstip;
|
||||
if(srcip){
|
||||
Animation *anim = srcip->currentAnim;
|
||||
UVAnimCustomData *custom = UVAnimCustomData::get(anim);
|
||||
dstip = AnimInterpolator::create(anim->getNumNodes(),
|
||||
anim->interpInfo->interpKeyFrameSize);
|
||||
dstip->setCurrentAnim(anim);
|
||||
custom->refCount++;
|
||||
dstuvanim->interp[i] = dstip;
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
Animation*
|
||||
makeDummyAnimation(const char *name)
|
||||
{
|
||||
AnimInterpolatorInfo *interpInfo = AnimInterpolatorInfo::find(0x1C0);
|
||||
Animation *anim = Animation::create(interpInfo, 2, 0, 1.0f);
|
||||
UVAnimCustomData *custom = UVAnimCustomData::get(anim);
|
||||
strncpy(custom->name, name, 32);
|
||||
memset(custom->nodeToUVChannel, 0, sizeof(custom->nodeToUVChannel));
|
||||
custom->refCount = 1;
|
||||
// TODO: init the frames
|
||||
// UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes;
|
||||
return anim;
|
||||
}
|
||||
|
||||
static Stream*
|
||||
readUVAnim(Stream *stream, int32, void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
if(!findChunk(stream, ID_STRUCT, nil, nil)){
|
||||
RWERROR((ERR_CHUNK, "STRUCT"));
|
||||
return nil;
|
||||
}
|
||||
char name[32];
|
||||
uint32 mask = stream->readI32();
|
||||
uint32 bit = 1;
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
if(mask & bit){
|
||||
stream->read(name, 32);
|
||||
Animation *anim = nil;
|
||||
if(currentUVAnimDictionary)
|
||||
anim = currentUVAnimDictionary->find(name);
|
||||
if(anim == nil){
|
||||
anim = makeDummyAnimation(name);
|
||||
if(currentUVAnimDictionary)
|
||||
currentUVAnimDictionary->add(anim);
|
||||
}
|
||||
UVAnimCustomData *custom = UVAnimCustomData::get(anim);
|
||||
AnimInterpolator *interp;
|
||||
interp = AnimInterpolator::create(anim->getNumNodes(),
|
||||
anim->interpInfo->interpKeyFrameSize);
|
||||
interp->setCurrentAnim(anim);
|
||||
custom->refCount++;
|
||||
uvanim->interp[i] = interp;
|
||||
}
|
||||
bit <<= 1;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
static Stream*
|
||||
writeUVAnim(Stream *stream, int32 size, void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
writeChunkHeader(stream, ID_STRUCT, size-12);
|
||||
uint32 mask = 0;
|
||||
uint32 bit = 1;
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
if(uvanim->interp[i])
|
||||
mask |= bit;
|
||||
bit <<= 1;
|
||||
}
|
||||
stream->writeI32(mask);
|
||||
for(int32 i = 0; i < 8; i++){
|
||||
if(uvanim->interp[i]){
|
||||
UVAnimCustomData *custom =
|
||||
UVAnimCustomData::get(uvanim->interp[i]->currentAnim);
|
||||
stream->write(custom->name, 32);
|
||||
}
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
static int32
|
||||
getSizeUVAnim(void *object, int32 offset, int32)
|
||||
{
|
||||
UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset);
|
||||
int32 size = 0;
|
||||
for(int32 i = 0; i < 8; i++)
|
||||
if(uvanim->interp[i])
|
||||
size += 32;
|
||||
return size ? size + 12 + 4 : 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
registerUVAnimPlugin(void)
|
||||
{
|
||||
registerUVAnimInterpolator();
|
||||
uvAnimOffset = Material::registerPlugin(sizeof(UVAnim), ID_UVANIMATION,
|
||||
createUVAnim, destroyUVAnim, copyUVAnim);
|
||||
Material::registerPluginStream(ID_UVANIMATION,
|
||||
readUVAnim, writeUVAnim, getSizeUVAnim);
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user