summaryrefslogtreecommitdiff
path: root/src/clump.cpp
diff options
context:
space:
mode:
authorclaude-bot <[email protected]>2026-07-13 12:40:03 +0000
committerclaude-bot <[email protected]>2026-07-13 12:40:03 +0000
commit847962910f0bff071f3bf07c9abb87764fb6cac3 (patch)
treeddcd429e134c7fd5f72ddc97ced175de8d66fcd0 /src/clump.cpp
downloadlibrw-master.tar.gz
librw-master.zip
Import aap/librw @ masterHEADmaster
Snapshot for re3/reVC vendoring, per @lzcnt. Source: https://github.com/aap/librw (master).
Diffstat (limited to 'src/clump.cpp')
-rw-r--r--src/clump.cpp669
1 files changed, 669 insertions, 0 deletions
diff --git a/src/clump.cpp b/src/clump.cpp
new file mode 100644
index 0000000..f228298
--- /dev/null
+++ b/src/clump.cpp
@@ -0,0 +1,669 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "rwbase.h"
+#include "rwerror.h"
+#include "rwplg.h"
+#include "rwpipeline.h"
+#include "rwobjects.h"
+#include "rwengine.h"
+
+#define PLUGIN_ID 2
+
+namespace rw {
+
+int32 Clump::numAllocated;
+int32 Atomic::numAllocated;
+
+PluginList Clump::s_plglist(sizeof(Clump));
+PluginList Atomic::s_plglist(sizeof(Atomic));
+
+//
+// Clump
+//
+
+Clump*
+Clump::create(void)
+{
+ Clump *clump = (Clump*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_CLUMP);
+ if(clump == nil){
+ RWERROR((ERR_ALLOC, s_plglist.size));
+ return nil;
+ }
+ numAllocated++;
+ clump->object.init(Clump::ID, 0);
+ clump->atomics.init();
+ clump->lights.init();
+ clump->cameras.init();
+
+ // World extension
+ clump->world = nil;
+ clump->inWorld.init();
+
+ s_plglist.construct(clump);
+ return clump;
+}
+
+Clump*
+Clump::clone(void)
+{
+ Clump *clump = Clump::create();
+ Frame *root = this->getFrame()->cloneAndLink();
+ clump->setFrame(root);
+ FORLIST(lnk, this->atomics){
+ Atomic *a = Atomic::fromClump(lnk);
+ Atomic *atomic = a->clone();
+ atomic->setFrame(a->getFrame()->root);
+ clump->addAtomic(atomic);
+ }
+ this->getFrame()->purgeClone();
+
+ // World extension
+ if(this->world)
+ this->world->addClump(clump);
+
+ s_plglist.copy(clump, this);
+ return clump;
+}
+
+void
+Clump::destroy(void)
+{
+ Frame *f;
+ s_plglist.destruct(this);
+ FORLIST(lnk, this->atomics){
+ Atomic *a = Atomic::fromClump(lnk);
+ this->removeAtomic(a);
+ a->destroy();
+ }
+ FORLIST(lnk, this->lights){
+ Light *l = Light::fromClump(lnk);
+ this->removeLight(l);
+ l->destroy();
+ }
+ FORLIST(lnk, this->cameras){
+ Camera *c = Camera::fromClump(lnk);
+ this->removeCamera(c);
+ c->destroy();
+ }
+ if(f = this->getFrame(), f)
+ f->destroyHierarchy();
+ assert(this->world == nil);
+ rwFree(this);
+ numAllocated--;
+}
+
+void
+Clump::addAtomic(Atomic *a)
+{
+ assert(a->clump == nil);
+ a->clump = this;
+ this->atomics.append(&a->inClump);
+}
+
+void
+Clump::removeAtomic(Atomic *a)
+{
+ assert(a->clump == this);
+ a->inClump.remove();
+ a->clump = nil;
+}
+
+void
+Clump::addLight(Light *l)
+{
+ assert(l->clump == nil);
+ l->clump = this;
+ this->lights.append(&l->inClump);
+}
+
+void
+Clump::removeLight(Light *l)
+{
+ assert(l->clump == this);
+ l->inClump.remove();
+ l->clump = nil;
+}
+
+void
+Clump::addCamera(Camera *c)
+{
+ assert(c->clump == nil);
+ c->clump = this;
+ this->cameras.append(&c->inClump);
+}
+
+void
+Clump::removeCamera(Camera *c)
+{
+ assert(c->clump == this);
+ c->inClump.remove();
+ c->clump = nil;
+}
+
+
+Clump*
+Clump::streamRead(Stream *stream)
+{
+ uint32 length, version;
+ int32 buf[3];
+ Clump *clump;
+ int32 numGeometries;
+ Geometry **geometryList;
+
+ if(!findChunk(stream, ID_STRUCT, &length, &version)){
+ RWERROR((ERR_CHUNK, "STRUCT"));
+ return nil;
+ }
+ stream->read32(buf, length);
+ int32 numAtomics = buf[0];
+ int32 numLights = 0;
+ int32 numCameras = 0;
+ if(version > 0x33000){
+ numLights = buf[1];
+ numCameras = buf[2];
+ }
+ clump = Clump::create();
+ if(clump == nil)
+ return nil;
+
+ // Frame list
+ FrameList_ frmlst;
+ frmlst.frames = nil;
+ if(!findChunk(stream, ID_FRAMELIST, nil, nil)){
+ RWERROR((ERR_CHUNK, "FRAMELIST"));
+ goto fail;
+ }
+ if(frmlst.streamRead(stream) == nil)
+ goto fail;
+ clump->setFrame(frmlst.frames[0]);
+
+ // Geometry list
+ numGeometries = 0;
+ geometryList = nil;
+ if(version >= 0x30400){
+ if(!findChunk(stream, ID_GEOMETRYLIST, nil, nil)){
+ RWERROR((ERR_CHUNK, "GEOMETRYLIST"));
+ goto fail;
+ }
+ if(!findChunk(stream, ID_STRUCT, nil, nil)){
+ RWERROR((ERR_CHUNK, "STRUCT"));
+ goto fail;
+ }
+ numGeometries = stream->readI32();
+ if(numGeometries){
+ size_t sz = numGeometries*sizeof(Geometry*);
+ geometryList = (Geometry**)rwMalloc(sz, MEMDUR_FUNCTION | ID_CLUMP);
+ if(geometryList == nil){
+ RWERROR((ERR_ALLOC, sz));
+ goto fail;
+ }
+ memset(geometryList, 0, sz);
+ }
+ for(int32 i = 0; i < numGeometries; i++){
+ if(!findChunk(stream, ID_GEOMETRY, nil, nil)){
+ RWERROR((ERR_CHUNK, "GEOMETRY"));
+ goto failgeo;
+ }
+ geometryList[i] = Geometry::streamRead(stream);
+ if(geometryList[i] == nil)
+ goto failgeo;
+ }
+ }
+
+ // Atomics
+ Atomic *a;
+ for(int32 i = 0; i < numAtomics; i++){
+ if(!findChunk(stream, ID_ATOMIC, nil, nil)){
+ RWERROR((ERR_CHUNK, "ATOMIC"));
+ goto failgeo;
+ }
+ a = Atomic::streamReadClump(stream, &frmlst, geometryList);
+ if(a == nil)
+ goto failgeo;
+ clump->addAtomic(a);
+ }
+
+ // Lights
+ int32 frm;
+ Light *l;
+ for(int32 i = 0; i < numLights; i++){
+ if(!findChunk(stream, ID_STRUCT, nil, nil)){
+ RWERROR((ERR_CHUNK, "STRUCT"));
+ goto failgeo;
+ }
+ frm = stream->readI32();
+ if(!findChunk(stream, ID_LIGHT, nil, nil)){
+ RWERROR((ERR_CHUNK, "LIGHT"));
+ goto failgeo;
+ }
+ l = Light::streamRead(stream);
+ if(l == nil)
+ goto failgeo;
+ l->setFrame(frmlst.frames[frm]);
+ clump->addLight(l);
+ }
+
+ // Cameras
+ Camera *cam;
+ for(int32 i = 0; i < numCameras; i++){
+ if(!findChunk(stream, ID_STRUCT, nil, nil)){
+ RWERROR((ERR_CHUNK, "STRUCT"));
+ goto failgeo;
+ }
+ frm = stream->readI32();
+ if(!findChunk(stream, ID_CAMERA, nil, nil)){
+ RWERROR((ERR_CHUNK, "CAMERA"));
+ goto failgeo;
+ }
+ cam = Camera::streamRead(stream);
+ if(cam == nil)
+ goto failgeo;
+ cam->setFrame(frmlst.frames[frm]);
+ clump->addCamera(cam);
+ }
+
+ for(int32 i = 0; i < numGeometries; i++)
+ if(geometryList[i])
+ geometryList[i]->destroy();
+ rwFree(geometryList);
+ rwFree(frmlst.frames);
+ if(s_plglist.streamRead(stream, clump))
+ return clump;
+
+failgeo:
+ for(int32 i = 0; i < numGeometries; i++)
+ if(geometryList[i])
+ geometryList[i]->destroy();
+ rwFree(geometryList);
+fail:
+ rwFree(frmlst.frames);
+ clump->destroy();
+ return nil;
+}
+
+bool
+Clump::streamWrite(Stream *stream)
+{
+ int size = this->streamGetSize();
+ writeChunkHeader(stream, ID_CLUMP, size);
+ int32 numAtomics = this->countAtomics();
+ int32 numLights = this->countLights();
+ int32 numCameras = this->countCameras();
+ int32 buf[3] = { numAtomics, numLights, numCameras };
+ size = version > 0x33000 ? 12 : 4;
+ writeChunkHeader(stream, ID_STRUCT, size);
+ stream->write32(buf, size);
+
+ FrameList_ frmlst;
+ frmlst.numFrames = this->getFrame()->count();
+ frmlst.frames = (Frame**)rwMalloc(frmlst.numFrames*sizeof(Frame*), MEMDUR_FUNCTION | ID_CLUMP);
+ makeFrameList(this->getFrame(), frmlst.frames);
+ frmlst.streamWrite(stream);
+
+ if(rw::version >= 0x30400){
+ size = 12+4;
+ FORLIST(lnk, this->atomics)
+ size += 12 + Atomic::fromClump(lnk)->geometry->streamGetSize();
+ writeChunkHeader(stream, ID_GEOMETRYLIST, size);
+ writeChunkHeader(stream, ID_STRUCT, 4);
+ stream->writeI32(numAtomics); // same as numGeometries
+ FORLIST(lnk, this->atomics)
+ Atomic::fromClump(lnk)->geometry->streamWrite(stream);
+ }
+
+ FORLIST(lnk, this->atomics)
+ Atomic::fromClump(lnk)->streamWriteClump(stream, &frmlst);
+
+ FORLIST(lnk, this->lights){
+ Light *l = Light::fromClump(lnk);
+ int frm = findPointer(l->getFrame(), (void**)frmlst.frames, frmlst.numFrames);
+ if(frm < 0)
+ return false;
+ writeChunkHeader(stream, ID_STRUCT, 4);
+ stream->writeI32(frm);
+ l->streamWrite(stream);
+ }
+
+ FORLIST(lnk, this->cameras){
+ Camera *c = Camera::fromClump(lnk);
+ int frm = findPointer(c->getFrame(), (void**)frmlst.frames, frmlst.numFrames);
+ if(frm < 0)
+ return false;
+ writeChunkHeader(stream, ID_STRUCT, 4);
+ stream->writeI32(frm);
+ c->streamWrite(stream);
+ }
+
+ rwFree(frmlst.frames);
+
+ s_plglist.streamWrite(stream, this);
+ return true;
+}
+
+uint32
+Clump::streamGetSize(void)
+{
+ uint32 size = 0;
+ size += 12; // Struct
+ size += 4; // numAtomics
+ if(version > 0x33000)
+ size += 8; // numLights, numCameras
+
+ // Frame list
+ size += FrameList_::streamGetSize(this->getFrame());
+
+ if(rw::version >= 0x30400){
+ // Geometry list
+ size += 12 + 12 + 4;
+ FORLIST(lnk, this->atomics)
+ size += 12 + Atomic::fromClump(lnk)->geometry->streamGetSize();
+ }
+
+ // Atomics
+ FORLIST(lnk, this->atomics)
+ size += 12 + Atomic::fromClump(lnk)->streamGetSize();
+
+ // Lights
+ FORLIST(lnk, this->lights)
+ size += 16 + 12 + Light::fromClump(lnk)->streamGetSize();
+
+ // Cameras
+ FORLIST(lnk, this->cameras)
+ size += 16 + 12 + Camera::fromClump(lnk)->streamGetSize();
+
+ size += 12 + s_plglist.streamGetSize(this);
+ return size;
+}
+
+void
+Clump::render(void)
+{
+ Atomic *a;
+ FORLIST(lnk, this->atomics){
+ a = Atomic::fromClump(lnk);
+ if(a->object.object.flags & Atomic::RENDER)
+ a->render();
+ }
+}
+
+//
+// Atomic
+//
+
+static void
+atomicSync(ObjectWithFrame *obj)
+{
+ // TODO: interpolate
+ obj->object.privateFlags |= Atomic::WORLDBOUNDDIRTY;
+}
+
+
+static void
+worldAtomicSync(ObjectWithFrame *obj)
+{
+ Atomic *atomic = (Atomic*)obj;
+ atomic->originalSync(obj);
+}
+
+Atomic*
+Atomic::create(void)
+{
+ Atomic *atomic = (Atomic*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_ATOMIC);
+ if(atomic == nil){
+ RWERROR((ERR_ALLOC, s_plglist.size));
+ return nil;
+ }
+ numAllocated++;
+ atomic->object.object.init(Atomic::ID, 0);
+ atomic->object.syncCB = atomicSync;
+ atomic->geometry = nil;
+ atomic->boundingSphere.center.set(0.0f, 0.0f, 0.0f);
+ atomic->boundingSphere.radius = 0.0f;
+ atomic->worldBoundingSphere.center.set(0.0f, 0.0f, 0.0f);
+ atomic->worldBoundingSphere.radius = 0.0f;
+ atomic->setFrame(nil);
+ atomic->object.object.privateFlags |= WORLDBOUNDDIRTY;
+ atomic->clump = nil;
+ atomic->inClump.init();
+ atomic->pipeline = nil;
+ atomic->renderCB = Atomic::defaultRenderCB;
+ atomic->object.object.flags = Atomic::COLLISIONTEST | Atomic::RENDER;
+ // TODO: interpolator
+
+ // World extension
+ atomic->world = nil;
+ atomic->originalSync = atomic->object.syncCB;
+ atomic->object.syncCB = worldAtomicSync;
+
+ s_plglist.construct(atomic);
+ return atomic;
+}
+
+Atomic*
+Atomic::clone()
+{
+ Atomic *atomic = Atomic::create();
+ if(atomic == nil)
+ return nil;
+ atomic->object.object.copy(&this->object.object);
+ atomic->object.object.privateFlags |= WORLDBOUNDDIRTY;
+ if(this->geometry)
+ atomic->setGeometry(this->geometry, 0);
+ atomic->renderCB = this->renderCB;
+ atomic->pipeline = this->pipeline;
+
+ // World extension doesn't add to world
+
+ s_plglist.copy(atomic, this);
+ return atomic;
+}
+
+void
+Atomic::destroy(void)
+{
+ s_plglist.destruct(this);
+ if(this->geometry)
+ this->geometry->destroy();
+ assert(this->clump == nil);
+ assert(this->world == nil);
+ this->setFrame(nil);
+ rwFree(this);
+ numAllocated--;
+}
+
+void
+Atomic::setGeometry(Geometry *geo, uint32 flags)
+{
+ if(this->geometry)
+ this->geometry->destroy();
+ if(geo)
+ geo->addRef();
+ this->geometry = geo;
+ if(flags & SAMEBOUNDINGSPHERE)
+ return;
+ if(geo){
+ this->boundingSphere = geo->morphTargets[0].boundingSphere;
+ if(this->getFrame()) // TODO: && getWorld???
+ this->getFrame()->updateObjects();
+ }
+}
+
+Sphere*
+Atomic::getWorldBoundingSphere(void)
+{
+ Sphere *s = &this->worldBoundingSphere;
+ // TODO: if we ever support morphing, check interpolation
+ if(!this->getFrame()->dirty() &&
+ (this->object.object.privateFlags & WORLDBOUNDDIRTY) == 0)
+ return s;
+ Matrix *ltm = this->getFrame()->getLTM();
+ // TODO: support scaling
+ V3d::transformPoints(&s->center, &this->boundingSphere.center, 1, ltm);
+ s->radius = this->boundingSphere.radius;
+ this->object.object.privateFlags &= ~WORLDBOUNDDIRTY;
+ return s;
+}
+
+static uint32 atomicRights[2];
+
+Atomic*
+Atomic::streamReadClump(Stream *stream,
+ FrameList_ *frameList, Geometry **geometryList)
+{
+ int32 buf[4];
+ uint32 version;
+ if(!findChunk(stream, ID_STRUCT, nil, &version)){
+ RWERROR((ERR_CHUNK, "STRUCT"));
+ return nil;
+ }
+ stream->read32(buf, version < 0x30400 ? 12 : 16);
+ Atomic *atomic = Atomic::create();
+ if(atomic == nil)
+ return nil;
+ atomic->setFrame(frameList->frames[buf[0]]);
+ Geometry *g;
+ if(version < 0x30400){
+ if(!findChunk(stream, ID_GEOMETRY, nil, nil)){
+ RWERROR((ERR_CHUNK, "STRUCT"));
+ goto fail;
+ }
+ g = Geometry::streamRead(stream);
+ if(g == nil)
+ goto fail;
+ atomic->setGeometry(g, 0);
+ g->destroy();
+ }else
+ atomic->setGeometry(geometryList[buf[1]], 0);
+ atomic->object.object.flags = buf[2];
+
+ atomicRights[0] = 0;
+ if(!s_plglist.streamRead(stream, atomic))
+ goto fail;
+ if(atomicRights[0])
+ s_plglist.assertRights(atomic, atomicRights[0], atomicRights[1]);
+ return atomic;
+
+fail:
+ atomic->destroy();
+ return nil;
+}
+
+bool
+Atomic::streamWriteClump(Stream *stream, FrameList_ *frmlst)
+{
+ int32 buf[4] = { 0, 0, 0, 0 };
+ Clump *c = this->clump;
+ if(c == nil)
+ return false;
+ writeChunkHeader(stream, ID_ATOMIC, this->streamGetSize());
+ writeChunkHeader(stream, ID_STRUCT, rw::version < 0x30400 ? 12 : 16);
+ buf[0] = findPointer(this->getFrame(), (void**)frmlst->frames, frmlst->numFrames);
+
+ if(version < 0x30400){
+ buf[1] = this->object.object.flags;
+ stream->write32(buf, sizeof(int32[3]));
+ this->geometry->streamWrite(stream);
+ }else{
+ buf[1] = 0;
+ FORLIST(lnk, c->atomics){
+ if(Atomic::fromClump(lnk)->geometry == this->geometry)
+ goto foundgeo;
+ buf[1]++;
+ }
+ return false;
+ foundgeo:
+ buf[2] = this->object.object.flags;
+ stream->write32(buf, sizeof(buf));
+ }
+
+ s_plglist.streamWrite(stream, this);
+ return true;
+}
+
+uint32
+Atomic::streamGetSize(void)
+{
+ uint32 size = 12 + 12 + 12 + s_plglist.streamGetSize(this);
+ if(rw::version < 0x30400)
+ size += 12 + this->geometry->streamGetSize();
+ else
+ size += 4;
+ return size;
+}
+
+ObjPipeline*
+Atomic::getPipeline(void)
+{
+ return this->pipeline ?
+ this->pipeline :
+ engine->driver[platform]->defaultPipeline;
+}
+
+void
+Atomic::instance(void)
+{
+ if(this->geometry->flags & Geometry::NATIVE)
+ return;
+ this->getPipeline()->instance(this);
+ this->geometry->flags |= Geometry::NATIVE;
+}
+
+void
+Atomic::uninstance(void)
+{
+ if(!(this->geometry->flags & Geometry::NATIVE))
+ return;
+ this->getPipeline()->uninstance(this);
+ // this should be done by the CB already, just make sure
+ this->geometry->flags &= ~Geometry::NATIVE;
+}
+
+void
+Atomic::defaultRenderCB(Atomic *atomic)
+{
+ atomic->getPipeline()->render(atomic);
+}
+
+// Atomic Rights plugin
+
+static Stream*
+readAtomicRights(Stream *stream, int32, void *, int32, int32)
+{
+ stream->read32(atomicRights, 8);
+ return stream;
+}
+
+static Stream*
+writeAtomicRights(Stream *stream, int32, void *object, int32, int32)
+{
+ Atomic *atomic = (Atomic*)object;
+ uint32 buffer[2];
+ buffer[0] = atomic->pipeline->pluginID;
+ buffer[1] = atomic->pipeline->pluginData;
+ stream->write32(buffer, 8);
+ return stream;
+}
+
+static int32
+getSizeAtomicRights(void *object, int32, int32)
+{
+ Atomic *atomic = (Atomic*)object;
+ if(atomic->pipeline == nil || atomic->pipeline->pluginID == 0)
+ return 0;
+ return 8;
+}
+
+void
+registerAtomicRightsPlugin(void)
+{
+ Atomic::registerPlugin(0, ID_RIGHTTORENDER, nil, nil, nil);
+ Atomic::registerPluginStream(ID_RIGHTTORENDER,
+ readAtomicRights,
+ writeAtomicRights,
+ getSizeAtomicRights);
+}
+
+}