librw/src/geometry.cpp

740 lines
18 KiB
C++

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <cmath>
#include "rwbase.h"
#include "rwerror.h"
#include "rwplg.h"
#include "rwpipeline.h"
#include "rwobjects.h"
#define PLUGIN_ID 2
namespace rw {
Geometry*
Geometry::create(int32 numVerts, int32 numTris, uint32 flags)
{
Geometry *geo = (Geometry*)malloc(s_plglist.size);
if(geo == nil){
RWERROR((ERR_ALLOC, s_plglist.size));
return nil;
}
geo->object.init(Geometry::ID, 0);
geo->geoflags = flags & 0xFF00FFFF;
geo->numTexCoordSets = (flags & 0xFF0000) >> 16;
if(geo->numTexCoordSets == 0)
geo->numTexCoordSets = (geo->geoflags & TEXTURED) ? 1 :
(geo->geoflags & TEXTURED2) ? 2 : 0;
geo->numTriangles = numTris;
geo->numVertices = numVerts;
geo->numMorphTargets = 1;
geo->colors = nil;
for(int32 i = 0; i < geo->numTexCoordSets; i++)
geo->texCoords[i] = nil;
geo->triangles = nil;
if(!(geo->geoflags & NATIVE) && geo->numVertices){
if(geo->geoflags & PRELIT)
geo->colors = new uint8[4*geo->numVertices];
if((geo->geoflags & TEXTURED) || (geo->geoflags & TEXTURED2))
for(int32 i = 0; i < geo->numTexCoordSets; i++)
geo->texCoords[i] =
new float32[2*geo->numVertices];
geo->triangles = new Triangle[geo->numTriangles];
}
geo->morphTargets = new MorphTarget[1];
MorphTarget *m = geo->morphTargets;
m->boundingSphere.center.set(0.0f, 0.0f, 0.0f);
m->boundingSphere.radius = 0.0f;
m->vertices = nil;
m->normals = nil;
if(!(geo->geoflags & NATIVE) && geo->numVertices){
m->vertices = new float32[3*geo->numVertices];
if(geo->geoflags & NORMALS)
m->normals = new float32[3*geo->numVertices];
}
geo->numMaterials = 0;
geo->materialList = nil;
geo->meshHeader = nil;
geo->instData = nil;
geo->refCount = 1;
s_plglist.construct(geo);
return geo;
}
void
Geometry::destroy(void)
{
this->refCount--;
if(this->refCount <= 0){
s_plglist.destruct(this);
delete[] this->colors;
for(int32 i = 0; i < this->numTexCoordSets; i++)
delete[] this->texCoords[i];
delete[] this->triangles;
for(int32 i = 0; i < this->numMorphTargets; i++){
MorphTarget *m = &this->morphTargets[i];
delete[] m->vertices;
delete[] m->normals;
}
delete[] this->morphTargets;
delete this->meshHeader;
for(int32 i = 0; i < this->numMaterials; i++)
if(this->materialList[i])
this->materialList[i]->destroy();
delete[] this->materialList;
free(this);
}
}
struct GeoStreamData
{
uint32 flags;
int32 numTriangles;
int32 numVertices;
int32 numMorphTargets;
};
Geometry*
Geometry::streamRead(Stream *stream)
{
uint32 version;
GeoStreamData buf;
if(!findChunk(stream, ID_STRUCT, nil, &version)){
RWERROR((ERR_CHUNK, "STRUCT"));
return nil;
}
stream->read(&buf, sizeof(buf));
Geometry *geo = Geometry::create(buf.numVertices,
buf.numTriangles, buf.flags);
if(geo == nil)
return nil;
geo->addMorphTargets(buf.numMorphTargets-1);
// skip surface properties
if(version < 0x34000)
stream->seek(12);
if(!(geo->geoflags & NATIVE)){
if(geo->geoflags & PRELIT)
stream->read(geo->colors, 4*geo->numVertices);
for(int32 i = 0; i < geo->numTexCoordSets; i++)
stream->read(geo->texCoords[i],
2*geo->numVertices*4);
for(int32 i = 0; i < geo->numTriangles; i++){
uint32 tribuf[2];
stream->read(tribuf, 8);
geo->triangles[i].v[0] = tribuf[0] >> 16;
geo->triangles[i].v[1] = tribuf[0];
geo->triangles[i].v[2] = tribuf[1] >> 16;
geo->triangles[i].matId = tribuf[1];
}
}
for(int32 i = 0; i < geo->numMorphTargets; i++){
MorphTarget *m = &geo->morphTargets[i];
stream->read(&m->boundingSphere, 4*4);
int32 hasVertices = stream->readI32();
int32 hasNormals = stream->readI32();
if(hasVertices)
stream->read(m->vertices, 3*geo->numVertices*4);
if(hasNormals)
stream->read(m->normals, 3*geo->numVertices*4);
}
if(!findChunk(stream, ID_MATLIST, nil, nil)){
RWERROR((ERR_CHUNK, "MATLIST"));
goto fail;
}
if(!findChunk(stream, ID_STRUCT, nil, nil)){
RWERROR((ERR_CHUNK, "STRUCT"));
goto fail;
}
geo->numMaterials = stream->readI32();
geo->materialList = new Material*[geo->numMaterials];
stream->seek(geo->numMaterials*4); // material indices...but always -1
Material *m;
for(int32 i = 0; i < geo->numMaterials; i++){
if(!findChunk(stream, ID_MATERIAL, nil, nil)){
RWERROR((ERR_CHUNK, "MATERIAL"));
goto fail;
}
m = Material::streamRead(stream);
if(m == nil)
goto fail;
geo->materialList[i] = m;
}
if(s_plglist.streamRead(stream, geo))
return geo;
fail:
geo->destroy();
return nil;
}
static uint32
geoStructSize(Geometry *geo)
{
uint32 size = 0;
size += sizeof(GeoStreamData);
if(version < 0x34000)
size += 12; // surface properties
if(!(geo->geoflags & Geometry::NATIVE)){
if(geo->geoflags&geo->PRELIT)
size += 4*geo->numVertices;
for(int32 i = 0; i < geo->numTexCoordSets; i++)
size += 2*geo->numVertices*4;
size += 4*geo->numTriangles*2;
}
for(int32 i = 0; i < geo->numMorphTargets; i++){
MorphTarget *m = &geo->morphTargets[i];
size += 4*4 + 2*4; // bounding sphere and bools
if(!(geo->geoflags & Geometry::NATIVE)){
if(m->vertices)
size += 3*geo->numVertices*4;
if(m->normals)
size += 3*geo->numVertices*4;
}
}
return size;
}
bool
Geometry::streamWrite(Stream *stream)
{
GeoStreamData buf;
uint32 size;
static float32 fbuf[3] = { 1.0f, 1.0f, 1.0f };
writeChunkHeader(stream, ID_GEOMETRY, this->streamGetSize());
writeChunkHeader(stream, ID_STRUCT, geoStructSize(this));
buf.flags = this->geoflags | this->numTexCoordSets << 16;
buf.numTriangles = this->numTriangles;
buf.numVertices = this->numVertices;
buf.numMorphTargets = this->numMorphTargets;
stream->write(&buf, sizeof(buf));
if(version < 0x34000)
stream->write(fbuf, sizeof(fbuf));
if(!(this->geoflags & NATIVE)){
if(this->geoflags & PRELIT)
stream->write(this->colors, 4*this->numVertices);
for(int32 i = 0; i < this->numTexCoordSets; i++)
stream->write(this->texCoords[i],
2*this->numVertices*4);
for(int32 i = 0; i < this->numTriangles; i++){
uint32 tribuf[2];
tribuf[0] = this->triangles[i].v[0] << 16 |
this->triangles[i].v[1];
tribuf[1] = this->triangles[i].v[2] << 16 |
this->triangles[i].matId;
stream->write(tribuf, 8);
}
}
for(int32 i = 0; i < this->numMorphTargets; i++){
MorphTarget *m = &this->morphTargets[i];
stream->write(&m->boundingSphere, 4*4);
if(!(this->geoflags & NATIVE)){
stream->writeI32(m->vertices != nil);
stream->writeI32(m->normals != nil);
if(m->vertices)
stream->write(m->vertices,
3*this->numVertices*4);
if(m->normals)
stream->write(m->normals,
3*this->numVertices*4);
}else{
stream->writeI32(0);
stream->writeI32(0);
}
}
size = 12 + 4;
for(int32 i = 0; i < this->numMaterials; i++)
size += 4 + 12 + this->materialList[i]->streamGetSize();
writeChunkHeader(stream, ID_MATLIST, size);
writeChunkHeader(stream, ID_STRUCT, 4 + this->numMaterials*4);
stream->writeI32(this->numMaterials);
for(int32 i = 0; i < this->numMaterials; i++)
stream->writeI32(-1);
for(int32 i = 0; i < this->numMaterials; i++)
this->materialList[i]->streamWrite(stream);
s_plglist.streamWrite(stream, this);
return true;
}
uint32
Geometry::streamGetSize(void)
{
uint32 size = 0;
size += 12 + geoStructSize(this);
size += 12 + 12 + 4;
for(int32 i = 0; i < this->numMaterials; i++)
size += 4 + 12 + this->materialList[i]->streamGetSize();
size += 12 + s_plglist.streamGetSize(this);
return size;
}
void
Geometry::addMorphTargets(int32 n)
{
if(n == 0)
return;
MorphTarget *morphTargets = new MorphTarget[this->numMorphTargets+n];
memcpy(morphTargets, this->morphTargets,
this->numMorphTargets*sizeof(MorphTarget));
delete[] this->morphTargets;
this->morphTargets = morphTargets;
for(int32 i = this->numMorphTargets; i < n; i++){
MorphTarget *m = &morphTargets[i];
m->vertices = nil;
m->normals = nil;
if(!(this->geoflags & NATIVE)){
m->vertices = new float32[3*this->numVertices];
if(this->geoflags & NORMALS)
m->normals = new float32[3*this->numVertices];
}
}
this->numMorphTargets += n;
}
void
Geometry::calculateBoundingSphere(void)
{
for(int32 i = 0; i < this->numMorphTargets; i++){
MorphTarget *m = &this->morphTargets[i];
V3d min( 1000000.0f, 1000000.0f, 1000000.0f);
V3d max(-1000000.0f, -1000000.0f, -1000000.0f);
float32 *v = m->vertices;
for(int32 j = 0; j < this->numVertices; j++){
if(v[0] > max.x) max.x = v[0];
if(v[0] < min.x) min.x = v[0];
if(v[1] > max.y) max.y = v[1];
if(v[1] < min.y) min.y = v[1];
if(v[2] > max.z) max.z = v[2];
if(v[2] < min.z) min.z = v[2];
v += 3;
}
m->boundingSphere.center = scale(add(min, max), 1/2.0f);
max = sub(max, m->boundingSphere.center);
m->boundingSphere.radius = length(max);
}
}
bool32
Geometry::hasColoredMaterial(void)
{
for(int32 i = 0; i < this->numMaterials; i++)
if(this->materialList[i]->color.red != 255 ||
this->materialList[i]->color.green != 255 ||
this->materialList[i]->color.blue != 255 ||
this->materialList[i]->color.alpha != 255)
return 1;
return 0;
}
void
Geometry::allocateData(void)
{
if(this->geoflags & PRELIT)
this->colors = new uint8[4*this->numVertices];
if((this->geoflags & TEXTURED) || (this->geoflags & TEXTURED2))
for(int32 i = 0; i < this->numTexCoordSets; i++)
this->texCoords[i] =
new float32[2*this->numVertices];
MorphTarget *m = this->morphTargets;
m->vertices = new float32[3*this->numVertices];
if(this->geoflags & NORMALS)
m->normals = new float32[3*this->numVertices];
// TODO: morph targets (who cares anyway?)
}
static int
isDegenerate(uint16 *idx)
{
return idx[0] == idx[1] ||
idx[0] == idx[2] ||
idx[1] == idx[2];
}
void
Geometry::generateTriangles(int8 *adc)
{
MeshHeader *header = this->meshHeader;
assert(header != nil);
this->numTriangles = 0;
Mesh *m = header->mesh;
int8 *adcbits = adc;
for(uint32 i = 0; i < header->numMeshes; i++){
if(m->numIndices < 3){
// shouldn't happen but it does
adcbits += m->numIndices;
m++;
continue;
}
if(header->flags == 1){ // tristrip
for(uint32 j = 0; j < m->numIndices-2; j++){
if(!(adc && adcbits[j+2]) &&
!isDegenerate(&m->indices[j]))
this->numTriangles++;
}
}else
this->numTriangles += m->numIndices/3;
adcbits += m->numIndices;
m++;
}
delete[] this->triangles;
this->triangles = new Triangle[this->numTriangles];
Triangle *tri = this->triangles;
m = header->mesh;
adcbits = adc;
for(uint32 i = 0; i < header->numMeshes; i++){
if(m->numIndices < 3){
adcbits += m->numIndices;
m++;
continue;
}
int32 matid = findPointer((void*)m->material,
(void**)this->materialList,
this->numMaterials);
if(header->flags == 1) // tristrip
for(uint32 j = 0; j < m->numIndices-2; j++){
if(adc && adcbits[j+2] ||
isDegenerate(&m->indices[j]))
continue;
tri->v[0] = m->indices[j+0];
tri->v[1] = m->indices[j+1 + (j%2)];
tri->v[2] = m->indices[j+2 - (j%2)];
tri->matId = matid;
tri++;
}
else
for(uint32 j = 0; j < m->numIndices-2; j+=3){
tri->v[0] = m->indices[j+0];
tri->v[1] = m->indices[j+1];
tri->v[2] = m->indices[j+2];
tri->matId = matid;
tri++;
}
adcbits += m->numIndices;
m++;
}
}
void
Geometry::buildMeshes(void)
{
delete this->meshHeader;
Triangle *tri;
MeshHeader *h = new MeshHeader;
this->meshHeader = h;
if((this->geoflags & Geometry::TRISTRIP) == 0){
h->flags = 0;
h->totalIndices = this->numTriangles*3;
h->numMeshes = this->numMaterials;
h->mesh = new Mesh[h->numMeshes];
for(uint32 i = 0; i < h->numMeshes; i++){
h->mesh[i].material = this->materialList[i];
h->mesh[i].numIndices = 0;
}
// count indices per mesh
tri = this->triangles;
for(int32 i = 0; i < this->numTriangles; i++){
h->mesh[tri->matId].numIndices += 3;
tri++;
}
h->allocateIndices();
for(uint32 i = 0; i < h->numMeshes; i++)
h->mesh[i].numIndices = 0;
// same as above but fill with indices
tri = this->triangles;
for(int32 i = 0; i < this->numTriangles; i++){
uint32 idx = h->mesh[tri->matId].numIndices;
h->mesh[tri->matId].indices[idx++] = tri->v[0];
h->mesh[tri->matId].indices[idx++] = tri->v[1];
h->mesh[tri->matId].indices[idx++] = tri->v[2];
h->mesh[tri->matId].numIndices = idx;
tri++;
}
}else{
assert(0 && "can't tristrip\n");
}
}
// HAS to be called with an existing mesh
void
Geometry::removeUnusedMaterials(void)
{
if(this->meshHeader == nil)
return;
MeshHeader *mh = this->meshHeader;
int32 *map = new int32[this->numMaterials];
Material **matlist = new Material*[this->numMaterials];
int32 numMaterials = 0;
/* Build new material list and map */
for(uint32 i = 0; i < mh->numMeshes; i++){
Mesh *m = &mh->mesh[i];
if(m->numIndices <= 0)
continue;
matlist[numMaterials] = m->material;
int32 oldid = findPointer((void*)m->material,
(void**)this->materialList,
this->numMaterials);
map[oldid] = numMaterials;
numMaterials++;
}
delete[] this->materialList;
this->materialList = matlist;
this->numMaterials = numMaterials;
/* Build new meshes */
MeshHeader *newmh = new MeshHeader;
newmh->flags = mh->flags;
newmh->numMeshes = numMaterials;
newmh->mesh = new Mesh[newmh->numMeshes];
newmh->totalIndices = mh->totalIndices;
Mesh *newm = newmh->mesh;
for(uint32 i = 0; i < mh->numMeshes; i++){
Mesh *oldm = &mh->mesh[i];
if(oldm->numIndices <= 0)
continue;
newm->numIndices = oldm->numIndices;
newm->material = oldm->material;
newm++;
}
newmh->allocateIndices();
/* Copy indices */
newm = newmh->mesh;
for(uint32 i = 0; i < mh->numMeshes; i++){
Mesh *oldm = &mh->mesh[i];
if(oldm->numIndices <= 0)
continue;
memcpy(newm->indices, oldm->indices,
oldm->numIndices*sizeof(*oldm->indices));
newm++;
}
delete this->meshHeader;
this->meshHeader = newmh;
/* Remap triangle material IDs */
for(int32 i = 0; i < this->numTriangles; i++)
this->triangles[i].matId = map[this->triangles[i].matId];
delete[] map;
}
//
// Material
//
Material*
Material::create(void)
{
Material *mat = (Material*)malloc(s_plglist.size);
if(mat == nil){
RWERROR((ERR_ALLOC, s_plglist.size));
return nil;
}
mat->texture = nil;
memset(&mat->color, 0xFF, 4);
mat->surfaceProps.ambient = 1.0f;
mat->surfaceProps.specular = 1.0f;
mat->surfaceProps.diffuse = 1.0f;
mat->pipeline = nil;
mat->refCount = 1;
s_plglist.construct(mat);
return mat;
}
Material*
Material::clone(void)
{
Material *mat = Material::create();
if(mat == nil){
RWERROR((ERR_ALLOC, s_plglist.size));
return nil;
}
mat->color = this->color;
mat->surfaceProps = this->surfaceProps;
if(this->texture)
mat->setTexture(this->texture);
mat->pipeline = this->pipeline;
s_plglist.copy(mat, this);
return mat;
}
void
Material::destroy(void)
{
this->refCount--;
if(this->refCount <= 0){
s_plglist.destruct(this);
if(this->texture)
this->texture->destroy();
free(this);
}
}
void
Material::setTexture(Texture *tex)
{
if(this->texture)
this->texture->destroy();
if(tex)
tex->refCount++;
this->texture = tex;
}
struct MatStreamData
{
int32 flags; // unused according to RW
RGBA color;
int32 unused;
int32 textured;
};
static uint32 materialRights[2];
Material*
Material::streamRead(Stream *stream)
{
uint32 length, version;
MatStreamData buf;
if(!findChunk(stream, ID_STRUCT, nil, &version)){
RWERROR((ERR_CHUNK, "STRUCT"));
return nil;
}
stream->read(&buf, sizeof(buf));
Material *mat = Material::create();
if(mat == nil)
return nil;
mat->color = buf.color;
if(version < 0x30400){
mat->surfaceProps.ambient = 1.0f;
mat->surfaceProps.specular = 1.0f;
mat->surfaceProps.diffuse = 1.0f;
}else{
float32 surfaceProps[3];
stream->read(surfaceProps, sizeof(surfaceProps));
mat->surfaceProps.ambient = surfaceProps[0];
mat->surfaceProps.specular = surfaceProps[1];
mat->surfaceProps.diffuse = surfaceProps[2];
}
if(buf.textured){
if(!findChunk(stream, ID_TEXTURE, &length, nil)){
RWERROR((ERR_CHUNK, "TEXTURE"));
goto fail;
}
Texture *t = Texture::streamRead(stream);
if(t == nil)
goto fail;
mat->setTexture(t);
}
materialRights[0] = 0;
if(!s_plglist.streamRead(stream, mat))
goto fail;
if(materialRights[0])
s_plglist.assertRights(mat, materialRights[0], materialRights[1]);
return mat;
fail:
mat->destroy();
return nil;
}
bool
Material::streamWrite(Stream *stream)
{
MatStreamData buf;
writeChunkHeader(stream, ID_MATERIAL, this->streamGetSize());
writeChunkHeader(stream, ID_STRUCT, sizeof(MatStreamData)
+ (rw::version >= 0x30400 ? 12 : 0));
buf.color = this->color;
buf.flags = 0;
buf.unused = 0;
buf.textured = this->texture != nil;
stream->write(&buf, sizeof(buf));
if(rw::version >= 0x30400){
float32 surfaceProps[3];
surfaceProps[0] = this->surfaceProps.ambient;
surfaceProps[1] = this->surfaceProps.specular;
surfaceProps[2] = this->surfaceProps.diffuse;
stream->write(surfaceProps, sizeof(surfaceProps));
}
if(this->texture)
this->texture->streamWrite(stream);
s_plglist.streamWrite(stream, this);
return true;
}
uint32
Material::streamGetSize(void)
{
uint32 size = 0;
size += 12 + sizeof(MatStreamData);
if(rw::version >= 0x30400)
size += 12;
if(this->texture)
size += 12 + this->texture->streamGetSize();
size += 12 + s_plglist.streamGetSize(this);
return size;
}
// Material Rights plugin
static Stream*
readMaterialRights(Stream *stream, int32, void *, int32, int32)
{
stream->read(materialRights, 8);
// printf("materialrights: %X %X\n", materialRights[0], materialRights[1]);
return stream;
}
static Stream*
writeMaterialRights(Stream *stream, int32, void *object, int32, int32)
{
Material *material = (Material*)object;
uint32 buffer[2];
buffer[0] = material->pipeline->pluginID;
buffer[1] = material->pipeline->pluginData;
stream->write(buffer, 8);
return stream;
}
static int32
getSizeMaterialRights(void *object, int32, int32)
{
Material *material = (Material*)object;
if(material->pipeline == nil || material->pipeline->pluginID == 0)
return 0;
return 8;
}
void
registerMaterialRightsPlugin(void)
{
Material::registerPlugin(0, ID_RIGHTTORENDER, nil, nil, nil);
Material::registerPluginStream(ID_RIGHTTORENDER,
readMaterialRights,
writeMaterialRights,
getSizeMaterialRights);
}
}