Skip to content
Open
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ librwgta.VC.db
librw.props
librw_toolfolder.props
CLAUDE.md

# generated by premake5 vs20xx
build/*.vcxproj
build/*.vcxproj.filters
build/*.sln
16 changes: 16 additions & 0 deletions tools/euryopa/collision.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ CColModel::CColModel(void)
this->flags = 0;
this->allocFlag = 0; // SA does something strange here
this->rawdata = nil;

this->colHeaderSize = 0;
this->colVersion = 0;
this->rawdataSize = 0;
}

CColModel::~CColModel(void)
Expand Down Expand Up @@ -149,6 +153,10 @@ ReadColModelVer2(CColModel *colmodel, uint8 *buf, int32 size)
return;
colmodel->rawdata = rwNewT(uint8, datasize, 0);
memcpy(colmodel->rawdata, buf+COLHEADERSIZE, datasize);
memcpy(colmodel->colHeader, buf, COLHEADERSIZE); // Blender bridge: keep header for re-export
colmodel->colHeaderSize = COLHEADERSIZE;
colmodel->colVersion = 2;
colmodel->rawdataSize = datasize;
colmodel->numSpheres = header->numSpheres;
colmodel->numBoxes = header->numBoxes;
colmodel->numLines = header->numLines;
Expand Down Expand Up @@ -193,6 +201,10 @@ ReadColModelVer3(CColModel *colmodel, uint8 *buf, int32 size)
return;
colmodel->rawdata = rwNewT(uint8, datasize, 0);
memcpy(colmodel->rawdata, buf+COLHEADERSIZE, datasize);
memcpy(colmodel->colHeader, buf, COLHEADERSIZE); // Blender bridge: keep header for re-export
colmodel->colHeaderSize = COLHEADERSIZE;
colmodel->colVersion = 3;
colmodel->rawdataSize = datasize;
colmodel->numSpheres = header->numSpheres;
colmodel->numBoxes = header->numBoxes;
colmodel->numLines = header->numLines;
Expand Down Expand Up @@ -239,6 +251,10 @@ ReadColModelVer4(CColModel *colmodel, uint8 *buf, int32 size)
return;
colmodel->rawdata = rwNewT(uint8, datasize, 0);
memcpy(colmodel->rawdata, buf+COLHEADERSIZE, datasize);
memcpy(colmodel->colHeader, buf, COLHEADERSIZE); // Blender bridge: keep header for re-export
colmodel->colHeaderSize = COLHEADERSIZE;
colmodel->colVersion = 4;
colmodel->rawdataSize = datasize;
colmodel->numSpheres = header->numSpheres;
colmodel->numBoxes = header->numBoxes;
colmodel->numLines = header->numLines;
Expand Down
8 changes: 8 additions & 0 deletions tools/euryopa/collision.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ struct CColModel
uint8 allocFlag;
uint8 *rawdata;

// Blender bridge: keep the version header (0x4C/0x58/0x5C bytes) inline and the
// data size, so ExportColForModel can rebuild an exact .col (header + rawdata)
// without reconstructing offsets. colVersion 0 = not captured / unsupported (v1).
uint8 colHeader[0x5C];
uint8 colHeaderSize;
uint8 colVersion;
int32 rawdataSize;

char name[24];
GameFile *file;

Expand Down
92 changes: 92 additions & 0 deletions tools/euryopa/colstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,95 @@ LoadCol(int slot)
if(looseFile)
free(buffer);
}

// Blender bridge: reload collision straight from a loose .col file (may hold
// several models). Mirrors LoadCol's inner loop but reads an explicit path and
// frees the previous CColModel, so a single-model .col from Blender live-updates
// just that object's collision without touching the rest of its library.
void
ForceColReloadFromFile(const char *path)
{
int size;
uint8 *buffer = ReadLooseFile(path, &size);
if(buffer == nil){
log("BlenderBridge: can't read col %s\n", path);
return;
}
int offset = 0;
while(offset + (int)sizeof(ColFileHeader) <= size){
ColFileHeader *header = (ColFileHeader*)(buffer+offset);
int version;
switch(header->fourcc){
case 0x4C4C4F43: version = 1; break; // COLL
case 0x324C4F43: version = 2; break; // COL2
case 0x334C4F43: version = 3; break; // COL3
case 0x344C4F43: version = 4; break; // COL4
default: goto done;
}
offset += sizeof(ColFileHeader);
ObjectDef *obj = GetObjectDef(header->name, nil);
if(obj){
CColModel *col = new CColModel;
col->file = nil;
switch(version){
case 1: ReadColModel(col, buffer+offset, header->modelsize-24); break;
case 2: ReadColModelVer2(col, buffer+offset, header->modelsize-24); break;
case 3: ReadColModelVer3(col, buffer+offset, header->modelsize-24); break;
case 4: ReadColModelVer4(col, buffer+offset, header->modelsize-24); break;
}
CColModel *old = obj->m_colModel;
obj->m_colModel = col;
if(old) delete old;
log("BlenderBridge: reloaded collision for %s\n", header->name);
}else
log("BlenderBridge: no object %s for collision\n", header->name);
offset += header->modelsize-24;
}
done:
free(buffer);
}

// Blender bridge: rebuild a standalone .col from a model's live collision. The
// version header + rawdata were kept verbatim at load, so the body is byte-exact;
// only the 32-byte file header (fourcc + size + name) is synthesized. v1/uncaptured
// collisions can't be re-serialized → returns false.
bool
ExportColForModel(ObjectDef *obj, const char *path)
{
if(obj == nil)
return false;
CColModel *c = obj->m_colModel;
if(c == nil || c->colHeaderSize == 0 || c->rawdata == nil || c->rawdataSize <= 0){
log("BlenderBridge: no exportable collision for %s\n", obj->m_name);
return false;
}
uint32 fourcc = c->colVersion == 2 ? 0x324C4F43 : // COL2
c->colVersion == 4 ? 0x344C4F43 : // COL4
0x334C4F43; // COL3
int bodySize = c->colHeaderSize + c->rawdataSize; // == original modelsize - 24
uint32 modelsize = (uint32)(24 + bodySize);
int total = 8 + (int)modelsize;
uint8 *out = (uint8*)malloc(total);
if(out == nil)
return false;
int p = 0;
memcpy(out+p, &fourcc, 4); p += 4;
memcpy(out+p, &modelsize, 4); p += 4;
char nm[24];
memset(nm, 0, sizeof(nm));
strncpy(nm, obj->m_name, 21);
*(uint16*)(nm+22) = (uint16)obj->m_id; // model id in the trailing 2 bytes
memcpy(out+p, nm, 24); p += 24;
memcpy(out+p, c->colHeader, c->colHeaderSize); p += c->colHeaderSize;
memcpy(out+p, c->rawdata, c->rawdataSize); p += c->rawdataSize;
FILE *f = fopen(path, "wb");
bool ok = false;
if(f){
ok = ((int)fwrite(out, 1, total, f) == total);
fclose(f);
}
free(out);
if(!ok)
log("BlenderBridge: failed to write %s\n", path);
return ok;
}
24 changes: 20 additions & 4 deletions tools/euryopa/euryopa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2619,6 +2619,18 @@ Draw(void)

updateFPS();

// Finalize the camera BEFORE the timecycle so the whole frame uses ONE camera
// position. Timecycle::UpdateSA() reads TheCamera.m_position to pick the zone boxes
// that drive the sky colours / far plane. The Blender-driven snap used to land later
// in the frame (down in the poller block), so during a fast move the sky colours were
// computed for the previous position while the geometry rendered at the new one — the
// top of the screen flashed. Order now: ariane input → Blender snap → update → cycle.
CPad::UpdatePads();
updateRectSelectEarly();
TheCamera.Process(); // ariane's own camera input
PollBlenderCamIn(); // apply live camera from Blender (overrides if it drives)
TheCamera.update();

Weather::Update();
Timecycle::Update();
Timecycle::SetLights();
Expand All @@ -2630,10 +2642,6 @@ Draw(void)
TheCamera.m_rwcam_viewer->setFarPlane(5000.0f);
TheCamera.m_rwcam_viewer->fogPlane = Timecycle::currentColours.fogSt;

CPad::UpdatePads();
updateRectSelectEarly();
TheCamera.Process();
TheCamera.update();
if(gUseViewerCam)
Scene.camera = TheCamera.m_rwcam_viewer;
else
Expand Down Expand Up @@ -2668,6 +2676,14 @@ Draw(void)
}
}

PollBlenderOutbox(); // pick up edits sent back from Blender (hot-reload)
PollBlenderLiveIn(); // apply live moves from Blender (unless dragging here)
// PollBlenderCamIn() runs earlier (before Timecycle) so the sky uses the final camera
PollBlenderSelIn(); // apply live selection from Blender (unless selecting here)
PollBlenderCreate(); // create.job: new instances of existing models
PollBlenderCreateModel(); // createmodel.job: brand-new models (E-2)
PollBlenderDeletes(); // del_blender.txt: soft-delete/restore instances
WriteArianeLiveState(); // stream selected instance transforms to Blender (live)
LoadAllRequestedObjects();
BuildRenderList();

Expand Down
21 changes: 21 additions & 0 deletions tools/euryopa/euryopa.h
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,22 @@ const char *GetPrefabPlacePath(void);
void RenderPrefabPlacementGhost(const char *path, rw::V3d spawnPos);
int ExportSelectedDffs(const char *dir, int *numFailed);
int ExportSelectedTxds(const char *dir, int *numFailed);
int ExportSelectedToBlender(bool autoTxd, bool vanilla, bool ide, bool ipl, bool lodToo, bool colToo, bool hdToo, int *numFailed); // -> INU_ariane_bridge\inbox
void PollBlenderOutbox(void); // <- Blender edits: hot-reload DFF/TXD + move instance
void WriteArianeLiveState(void); // -> live position sync (selected instances) for Blender
void PollBlenderLiveIn(void); // <- live moves from Blender (when not dragging here)
void PollBlenderCamIn(void); // <- live camera from Blender (when not navigating here)
void PollBlenderSelIn(void); // <- live selection from Blender (when not selecting here)
void PollBlenderCreate(void); // <- create.job: new instances of existing models
void PollBlenderCreateModel(void); // <- createmodel.job: brand-new models (E-2)
void PollBlenderDeletes(void); // <- del_blender.txt: soft-delete/restore instances
bool CreateBridgeInstance(const char *name, rw::V3d pos, rw::Quat rot, char *guidOut, int guidSz);
bool CreateBridgeModel(const char *name, const char *dffPath, const char *txdPath, const char *colPath,
const char *lodName, const char *lodDffPath, const char *lodTxdPath,
float drawDist, rw::V3d pos, rw::Quat rot, char *guidOut, int guidSz, char *errOut, int errSz);
void MoveInstanceTo(ObjectInst *inst, rw::V3d pos, rw::Quat rot);
void GetInstGuid(ObjectInst *inst, char *buf, int sz); // stable cross-session instance id for the bridge
ObjectInst *FindInstByGuid(const char *guid);

// Toast notifications
enum ToastCategory {
Expand Down Expand Up @@ -580,6 +596,7 @@ void TxdPop(void);
bool IsTxdLoaded(int i);
void CreateTxd(int i);
void LoadTxd(int i);
void ForceTxdReload(int i); // free a slot's dict so Load() re-reads it (Blender live-reload)
void LoadTxd(int i, const char *path);
void TxdMakeCurrent(int i);
void TxdSetParent(const char *child, const char *parent);
Expand All @@ -602,6 +619,7 @@ ColDef *GetColDef(int i);
int AddColSlot(const char *name);
void LoadCol(int slot);
void LoadAllCollisions(void);
void ForceColReloadFromFile(const char *path); // Blender bridge: reload collision from a loose .col

// One class for all map objects
struct ObjectDef
Expand Down Expand Up @@ -680,6 +698,7 @@ struct ObjectDef
void LoadAtomic(void);
void LoadClump(void);
void Load(void);
void Reload(void); // free current geometry + Load() again (Blender live-reload)
void SetAtomic(int n, rw::Atomic *atomic);
void SetClump(rw::Clump *clump);
void CantLoad(void);
Expand All @@ -691,6 +710,7 @@ void RemoveObjectDef(int id);
ObjectDef *GetObjectDef(int id);
ObjectDef *GetObjectDef(const char *name, int *id);
bool CreateObjectPreviewRwObject(int id, rw::Atomic **atomicOut, rw::Clump **clumpOut);
bool ExportColForModel(ObjectDef *obj, const char *path); // Blender bridge: model's collision -> loose .col


struct FileObjectInstance
Expand Down Expand Up @@ -1071,6 +1091,7 @@ char *LoadLine(FILE *f);
void LoadLevel(const char *filename);
void LoadObjectTypes(const char *filename);
void LoadScene(const char *filename, bool loadRelatedStreaming = true);
int NextIplInstIndex(void); // running instance index across a map's text IPL + its streams
int GetLoadedSceneCount(void);
const char *GetLoadedScenePath(int index);
void LoadCollisionFile(const char *path);
Expand Down
7 changes: 6 additions & 1 deletion tools/euryopa/fileloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,12 @@ LoadTXDParent(char *line)


static std::vector<ObjectInst*> tmpInsts;
static int iplInstCounter; // tracks instance index within current IPL file
static int iplInstCounter; // tracks instance index within current IPL file (+ its streams)

// One running index across a map's text IPL AND all its streamed binary sections, so the
// bridge guid (<ipl>#<index>) stays unique — the streamed loader (iplstore.cpp) shares the
// same filter key, so a per-section index collided (LAw2_stream0#60 == LAw2_stream1#60).
int NextIplInstIndex(void) { return iplInstCounter++; }

void
LoadObjectInstance(char *line)
Expand Down
Loading