summaryrefslogtreecommitdiffhomepage
path: root/StarsEx/Solid.cpp
diff options
context:
space:
mode:
authorAki <please@ignore.pl>2022-04-01 21:23:39 +0200
committerAki <please@ignore.pl>2022-04-01 21:23:39 +0200
commit3c487c5cd69c53d6fea948643c0a76df03516605 (patch)
tree72730c7b8b26a5ef8fc9a987ec4c16129efd5aac /StarsEx/Solid.cpp
parent8f353abd0bfe18baddd8a8250ab7c4f2d1c83a6e (diff)
downloadstarshatter-3c487c5cd69c53d6fea948643c0a76df03516605.zip
starshatter-3c487c5cd69c53d6fea948643c0a76df03516605.tar.gz
starshatter-3c487c5cd69c53d6fea948643c0a76df03516605.tar.bz2
Moved Stars45 to StarsEx
Diffstat (limited to 'StarsEx/Solid.cpp')
-rw-r--r--StarsEx/Solid.cpp2476
1 files changed, 2476 insertions, 0 deletions
diff --git a/StarsEx/Solid.cpp b/StarsEx/Solid.cpp
new file mode 100644
index 0000000..ff4f610
--- /dev/null
+++ b/StarsEx/Solid.cpp
@@ -0,0 +1,2476 @@
+/* Starshatter: The Open Source Project
+ Copyright (c) 2021-2022, Starshatter: The Open Source Project Contributors
+ Copyright (c) 2011-2012, Starshatter OpenSource Distribution Contributors
+ Copyright (c) 1997-2006, Destroyer Studios LLC.
+
+ AUTHOR: John DiCamillo
+
+
+ OVERVIEW
+ ========
+ Classes for rendering solid meshes of polygons
+*/
+
+#include "Solid.h"
+#include "Scene.h"
+#include "Bitmap.h"
+#include "DataLoader.h"
+#include "Light.h"
+#include "Shadow.h"
+#include "Projector.h"
+#include "Opcode.h"
+#include "Utils.h"
+
+#ifdef for
+#undef for
+#endif
+
+// +--------------------------------------------------------------------+
+
+static bool use_collision_detection = true;
+
+bool Solid::IsCollisionEnabled() { return use_collision_detection; }
+void Solid::EnableCollision(bool e) { use_collision_detection = e; }
+
+// +--------------------------------------------------------------------+
+
+Opcode::AABBTreeCollider opcode_collider;
+
+class OPCODE_data
+{
+public:
+ OPCODE_data(Surface* s) {
+ bool status = false;
+
+ if (s) {
+ using namespace Opcode;
+ opcode_collider.SetFirstContact(true);
+
+ npolys = s->NumPolys();
+ nverts = s->NumVerts();
+ ntris = s->NumIndices() / 3;
+
+ locs = new IcePoint[nverts];
+ tris = new IndexedTriangle[ntris];
+
+ if (locs && tris) {
+ int i, n = 0;
+
+ for (i = 0; i < nverts; i++) {
+ IcePoint* p = locs + i;
+ Vec3* v = s->GetVertexSet()->loc + i;
+
+ p->Set(v->x, v->y, v->z);
+ }
+
+ for (i = 0; i < npolys; i++) {
+ Poly* p = s->GetPolys() + i;
+
+ if (p->nverts == 3) {
+ IndexedTriangle& t = tris[n++];
+
+ t.mVRef[0] = p->verts[0];
+ t.mVRef[1] = p->verts[2];
+ t.mVRef[2] = p->verts[1];
+ }
+ else {
+ IndexedTriangle& t1 = tris[n++];
+ IndexedTriangle& t2 = tris[n++];
+
+ t1.mVRef[0] = p->verts[0];
+ t1.mVRef[1] = p->verts[2];
+ t1.mVRef[2] = p->verts[1];
+
+ t2.mVRef[0] = p->verts[0];
+ t2.mVRef[1] = p->verts[3];
+ t2.mVRef[2] = p->verts[2];
+ }
+ }
+
+ mesh.SetNbVertices(nverts);
+ mesh.SetNbTriangles(ntris);
+ mesh.SetPointers(tris, locs);
+
+ OPCODECREATE creator;
+ creator.mIMesh = &mesh;
+ status = model.Build(creator);
+ }
+ }
+ else {
+ tris = 0;
+ locs = 0;
+ npolys = 0;
+ nverts = 0;
+ ntris = 0;
+ }
+ }
+
+ ~OPCODE_data() {
+ delete [] tris;
+ delete [] locs;
+ }
+
+ Opcode::Model model;
+ Opcode::MeshInterface mesh;
+ IndexedTriangle* tris;
+ IcePoint* locs;
+ int npolys;
+ int nverts;
+ int ntris;
+};
+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+
+Solid::Solid()
+ : model(0), own_model(1), roll(0.0f), pitch(0.0f), yaw(0.0f), intersection_poly(0)
+{
+ shadow = true;
+ sprintf_s(name, "Solid %d", id);
+}
+
+// +--------------------------------------------------------------------+
+
+Solid::~Solid()
+{
+ if (own_model)
+ delete model;
+
+ shadows.destroy();
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::Update()
+{
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::SetOrientation(const Matrix& o)
+{
+ orientation = o;
+}
+
+void
+Solid::SetLuminous(bool l)
+{
+ luminous = l;
+
+ if (model && luminous) {
+ model->luminous = luminous;
+
+ ListIter<Material> iter = model->GetMaterials();
+
+ while (++iter) {
+ Material* mtl = iter.value();
+
+ mtl->Ka = Color::Black;
+ mtl->Kd = Color::Black;
+ mtl->Ks = Color::Black;
+ mtl->Ke = Color::White;
+
+ if (mtl->tex_diffuse && !mtl->tex_emissive)
+ mtl->tex_emissive = mtl->tex_diffuse;
+ }
+
+ ListIter<Surface> s_iter = model->GetSurfaces();
+ while (++s_iter) {
+ Surface* surface = s_iter.value();
+ VertexSet* vset = surface->GetVertexSet();
+
+ for (int i = 0; i < vset->nverts; i++) {
+ vset->diffuse[i] = Color::White.Value();
+ vset->specular[i] = Color::Black.Value();
+ }
+ }
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::SetOrientation(const Solid& match)
+{
+ if (!model || infinite)
+ return;
+
+ // copy the orientation matrix from the solid we are matching:
+ orientation = match.Orientation();
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::Render(Video* video, DWORD flags)
+{
+ if (flags & RENDER_ADDITIVE)
+ return;
+
+ if (video && model && model->NumPolys()) {
+ DWORD blend_modes = Video::BLEND_SOLID;
+
+ if (flags == RENDER_ALPHA)
+ blend_modes = Video::BLEND_ALPHA | Video::BLEND_ADDITIVE;
+
+ video->DrawSolid(this, blend_modes);
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::SelectDetail(Projector* p)
+{
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::ProjectScreenRect(Projector* p)
+{
+ if (model && p) {
+ Point tmp = loc;
+ p->Transform(tmp);
+
+ if (tmp.z > 1) {
+ int l = 2000;
+ int r = -2000;
+ int t = 2000;
+ int b = -2000;
+
+ for (int i = 0; i < 6; i++) {
+ Point extent;
+
+ if (i < 2)
+ extent.x = model->extents[i];
+
+ else if (i < 4)
+ extent.y = model->extents[i];
+
+ else
+ extent.z = model->extents[i];
+
+ extent = extent * orientation + loc;
+
+ p->Transform(extent);
+ p->Project(extent);
+
+ if (extent.x < l) l = (int) extent.x;
+ if (extent.x > r) r = (int) extent.x;
+ if (extent.y < t) t = (int) extent.y;
+ if (extent.y > b) b = (int) extent.y;
+ }
+
+ screen_rect.x = l;
+ screen_rect.y = t;
+ screen_rect.w = r-l;
+ screen_rect.h = b-t;
+ return;
+ }
+ }
+
+ screen_rect.x = 2000;
+ screen_rect.y = 2000;
+ screen_rect.w = 0;
+ screen_rect.h = 0;
+}
+
+// +--------------------------------------------------------------------+
+// Polygon Interference Detection:
+
+int
+Solid::CollidesWith(Graphic& o)
+{
+ Vec3 delta_loc = Location() - o.Location();
+
+ // bounding spheres test:
+ if (delta_loc.length() > Radius() + o.Radius())
+ return 0;
+
+ // possible collision, but no further refinement can be done:
+ if (!o.IsSolid())
+ return 1;
+
+ Solid& s = (Solid&) o;
+
+ // use the OPCODE library to check for polygon interference:
+ if (model && s.model) {
+ using namespace Opcode;
+
+ bool contact = false;
+
+ // first, reverse the orientation matrices for OPCODE:
+ Matrix m1 = orientation;
+ Matrix m2 = s.orientation;
+
+ Matrix4x4 world0;
+ Matrix4x4 world1;
+
+ world0.m[0][0] = (float) m1.elem[0][0];
+ world0.m[0][1] = (float) m1.elem[0][1];
+ world0.m[0][2] = (float) m1.elem[0][2];
+ world0.m[0][3] = 0.0f;
+
+ world0.m[1][0] = (float) m1.elem[1][0];
+ world0.m[1][1] = (float) m1.elem[1][1];
+ world0.m[1][2] = (float) m1.elem[1][2];
+ world0.m[1][3] = 0.0f;
+
+ world0.m[2][0] = (float) m1.elem[2][0];
+ world0.m[2][1] = (float) m1.elem[2][1];
+ world0.m[2][2] = (float) m1.elem[2][2];
+ world0.m[2][3] = 0.0f;
+
+ world0.m[3][0] = (float) Location().x;
+ world0.m[3][1] = (float) Location().y;
+ world0.m[3][2] = (float) Location().z;
+ world0.m[3][3] = 1.0f;
+
+ world1.m[0][0] = (float) m2.elem[0][0];
+ world1.m[0][1] = (float) m2.elem[1][0];
+ world1.m[0][2] = (float) m2.elem[2][0];
+ world1.m[0][3] = 0.0f;
+
+ world1.m[1][0] = (float) m2.elem[0][1];
+ world1.m[1][1] = (float) m2.elem[1][1];
+ world1.m[1][2] = (float) m2.elem[2][1];
+ world1.m[1][3] = 0.0f;
+
+ world1.m[2][0] = (float) m2.elem[0][2];
+ world1.m[2][1] = (float) m2.elem[1][2];
+ world1.m[2][2] = (float) m2.elem[2][2];
+ world1.m[2][3] = 0.0f;
+
+ world1.m[3][0] = (float) s.Location().x;
+ world1.m[3][1] = (float) s.Location().y;
+ world1.m[3][2] = (float) s.Location().z;
+ world1.m[3][3] = 1.0f;
+
+ ListIter<Surface> s1_iter = model->surfaces;
+ while (++s1_iter && !contact) {
+ Surface* s1 = s1_iter.value();
+
+ ListIter<Surface> s2_iter = s.model->surfaces;
+ while (++s2_iter && !contact) {
+ Surface* s2 = s2_iter.value();
+
+ if (s1->opcode && s2->opcode) {
+ BVTCache bvt;
+ bvt.Model0 = &s1->opcode->model;
+ bvt.Model1 = &s2->opcode->model;
+
+ if (opcode_collider.Collide(bvt, &world0, &world1))
+ if (opcode_collider.GetContactStatus() != 0)
+ contact = true;
+ }
+ }
+ }
+
+ return contact;
+ }
+
+
+ return 1;
+}
+
+// +--------------------------------------------------------------------+
+// Find the intersection of the ray (Q + w*len) with the solid.
+// If the ray intersects a polygon of the solid, place the intersection
+// point in ipt, and return 1. Otherwise, return 0.
+
+int
+Solid::CheckRayIntersection(Point Q, Point w, double len, Point& ipt,
+bool treat_translucent_polys_as_solid)
+{
+ int impact = 0;
+
+ if (!model || model->npolys < 1)
+ return impact;
+
+ // check right angle spherical distance:
+ Point d0 = loc - Q;
+ Point d1 = d0.cross(w);
+ double dlen = d1.length(); // distance of point from line
+
+ if (dlen > radius) // clean miss
+ return 0; // (no impact)
+
+ // possible collision course...
+
+ /**********************************
+
+
+ /--- + leading_edge = Q + w * len
+ / / \
+ delta2 / delta 0
+ / / \
+ / *........x <- solid location
+ / /
+ / / delta1
+/--- Q * = closest point
+
+
+************************************/
+
+ // find the point on the ray that is closest
+ // to the solid's location:
+ Point closest = Q + w * (d0 * w);
+
+ // find the leading edge, and it's distance from the location:
+ Point leading_edge = Q + w*len;
+ Point leading_delta = leading_edge - loc;
+ double leading_dist = leading_delta.length();
+
+ // if the leading edge is not within the bounding sphere,
+ if (leading_dist > radius) {
+ // check to see if the closest point is between the
+ // ray's endpoints:
+ Point delta1 = closest - Q;
+ Point delta2 = leading_edge - Q; // this is w*len
+
+ // if the closest point is not between the leading edge
+ // and the origin, this ray does not intersect:
+ if (delta1 * delta2 < 0 || delta1.length() > len) {
+ return 0;
+ }
+ }
+
+ // probable hit at this point...
+
+ // if not active, that's good enough:
+ if (GetScene() == 0) {
+ ipt = closest;
+ return 1;
+ }
+
+ // transform ray into object space:
+ Matrix xform(Orientation());
+
+ Vec3 tmp = w;
+
+ w.x = tmp * Vec3(xform(0,0), xform(0,1), xform(0,2));
+ w.y = tmp * Vec3(xform(1,0), xform(1,1), xform(1,2));
+ w.z = tmp * Vec3(xform(2,0), xform(2,1), xform(2,2));
+
+ tmp = Q-loc;
+
+ Q.x = tmp * Vec3(xform(0,0), xform(0,1), xform(0,2));
+ Q.y = tmp * Vec3(xform(1,0), xform(1,1), xform(1,2));
+ Q.z = tmp * Vec3(xform(2,0), xform(2,1), xform(2,2));
+
+ double min = len;
+ intersection_poly = 0;
+
+ // check each polygon:
+ ListIter<Surface> iter = model->surfaces;
+ while (++iter) {
+ Surface* s = iter.value();
+ Poly* p = s->GetPolys();
+
+ for (int i = 0; i < s->NumPolys(); i++) {
+ if (!treat_translucent_polys_as_solid && p->material && !p->material->IsSolid()) {
+ p++;
+ continue;
+ }
+
+ Point v = p->plane.normal;
+ double d = p->plane.distance;
+
+ double denom = w*v;
+
+ if (denom < -1.0e-5) {
+ Point P = v * d;
+ double ilen = ((P-Q)*v)/denom;
+
+ if (ilen > 0 && ilen < min) {
+ Point intersect = Q + w * ilen;
+
+ if (p->Contains(intersect)) {
+ intersection_poly = p;
+ ipt = intersect;
+ min = ilen;
+ impact = 1;
+ }
+ }
+ }
+
+ p++;
+ }
+ }
+
+ // xform impact point back into world coordinates:
+
+ if (impact) {
+ ipt = (ipt * Orientation()) + loc;
+ }
+
+ return impact;
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::ClearModel()
+{
+ if (own_model && model) {
+ delete model;
+ model = 0;
+ }
+
+ radius = 0.0f;
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::UseModel(Model* m)
+{
+ // get rid of the existing model:
+ ClearModel();
+
+ // point to the new model:
+ own_model = 0;
+ model = m;
+ radius = m->radius;
+}
+
+// +--------------------------------------------------------------------+
+
+bool
+Solid::Load(const char* mag_file, double scale)
+{
+ // get ready to load, delete existing model:
+ ClearModel();
+
+ // loading our own copy, so we own the model:
+ model = new Model;
+ own_model = 1;
+
+ // now load the model:
+ if (model->Load(mag_file, scale)) {
+ radius = model->radius;
+ strncpy_s(name, model->name, sizeof(name));
+ return true;
+ }
+
+ // load failed:
+ ClearModel();
+ return false;
+}
+
+bool
+Solid::Load(ModelFile* mod_file, double scale)
+{
+ // get ready to load, delete existing model:
+ ClearModel();
+
+ // loading our own copy, so we own the model:
+ model = new Model;
+ own_model = 1;
+
+ // now load the model:
+ if (model->Load(mod_file, scale)) {
+ radius = model->radius;
+ return true;
+ }
+
+ // load failed:
+ ClearModel();
+ return false;
+}
+
+bool
+Solid::Rescale(double scale)
+{
+ if (!own_model || !model)
+ return false;
+
+ radius = 0;
+
+ ListIter<Surface> iter = model->GetSurfaces();
+ while (++iter) {
+ Surface* s = iter.value();
+
+ for (int v = 0; v < s->NumVerts(); v++) {
+ s->vertex_set->loc[v] *= (float) scale;
+ s->vloc[v] *= (float) scale;
+
+ float lvi = s->vloc[v].length();
+ if (lvi > radius)
+ radius = lvi;
+ }
+ }
+
+ model->radius = radius;
+
+ InvalidateSurfaceData();
+
+ return true;
+}
+
+void
+Solid::CreateShadows(int nlights)
+{
+ while (shadows.size() < nlights) {
+ shadows.append(new Shadow(this));
+ }
+}
+
+void
+Solid::UpdateShadows(List<Light>& lights)
+{
+ List<Light> active_lights;
+ ListIter<Light> iter = lights;
+
+ while (++iter) {
+ Light* light = iter.value();
+
+ if (light->IsActive() && light->CastsShadow()) {
+ double distance = Point(Location() - light->Location()).length();
+ double intensity = light->Intensity();
+
+ if (light->Type() == Light::LIGHT_POINT) {
+ if (intensity / distance > 1)
+ active_lights.append(light);
+ }
+
+ else if (light->Type() == Light::LIGHT_DIRECTIONAL) {
+ if (intensity > 0.65)
+ active_lights.insert(light);
+ }
+ }
+ }
+
+ iter.attach(active_lights);
+
+ while (++iter) {
+ Light* light = iter.value();
+ int index = iter.index();
+
+ if (index < shadows.size()) {
+ shadows[index]->Update(light);
+ }
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::DeletePrivateData()
+{
+ if (model)
+ model->DeletePrivateData();
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::InvalidateSurfaceData()
+{
+ if (!model)
+ return;
+
+ bool invalidate = model->IsDynamic();
+
+ ListIter<Surface> iter = model->GetSurfaces();
+ while (++iter) {
+ Surface* s = iter.value();
+ VideoPrivateData* vpd = s->GetVideoPrivateData();
+
+ if (vpd) {
+ if (invalidate) {
+ vpd->Invalidate();
+ }
+ else {
+ delete vpd;
+ s->SetVideoPrivateData(0);
+ }
+ }
+ }
+}
+
+void
+Solid::InvalidateSegmentData()
+{
+ if (!model)
+ return;
+
+ bool invalidate = model->IsDynamic();
+
+ ListIter<Surface> iter = model->GetSurfaces();
+ while (++iter) {
+ Surface* s = iter.value();
+
+ ListIter<Segment> seg_iter = s->GetSegments();
+ while (++seg_iter) {
+ Segment* segment = seg_iter.value();
+ VideoPrivateData* vpd = segment->GetVideoPrivateData();
+
+ if (vpd) {
+ if (invalidate) {
+ vpd->Invalidate();
+ }
+ else {
+ delete vpd;
+ segment->SetVideoPrivateData(0);
+ }
+ }
+ }
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+bool
+Solid::IsDynamic() const
+{
+ if (model)
+ return model->IsDynamic();
+
+ return false;
+}
+
+void
+Solid::SetDynamic(bool d)
+{
+ if (model && own_model)
+ model->SetDynamic(d);
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Solid::GetAllTextures(List<Bitmap>& textures)
+{
+ if (model)
+ model->GetAllTextures(textures);
+}
+
+void
+Model::GetAllTextures(List<Bitmap>& textures)
+{
+ ListIter<Material> m_iter = materials;
+ while (++m_iter) {
+ Material* m = m_iter.value();
+
+ if (m->tex_diffuse && !textures.contains(m->tex_diffuse))
+ textures.append(m->tex_diffuse);
+
+ if (m->tex_specular && !textures.contains(m->tex_specular))
+ textures.append(m->tex_specular);
+
+ if (m->tex_emissive && !textures.contains(m->tex_emissive))
+ textures.append(m->tex_emissive);
+
+ if (m->tex_bumpmap && !textures.contains(m->tex_bumpmap))
+ textures.append(m->tex_bumpmap);
+
+ if (m->tex_detail && !textures.contains(m->tex_detail))
+ textures.append(m->tex_detail);
+ }
+}
+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+
+Model::Model()
+ : nverts(0), npolys(0), radius(0), luminous(false), dynamic(false)
+{
+ ZeroMemory(name, sizeof(name));
+}
+
+Model::Model(const Model& m)
+ : nverts(0), npolys(0), radius(0), luminous(false), dynamic(false)
+{
+ operator=(m);
+}
+
+// +--------------------------------------------------------------------+
+
+Model::~Model()
+{
+ surfaces.destroy();
+ materials.destroy();
+}
+
+Model&
+Model::operator = (const Model& m)
+{
+ if (this != &m) {
+ surfaces.destroy();
+ materials.destroy();
+
+ CopyMemory(name, m.name, Solid::NAMELEN);
+
+ nverts = m.nverts;
+ npolys = m.npolys;
+ radius = m.radius;
+ luminous = m.luminous;
+ dynamic = m.dynamic;
+
+ Model* pmod = (Model*) &m;
+
+ ListIter<Material> m_iter = pmod->materials;
+ while (++m_iter) {
+ Material* matl1 = m_iter.value();
+ Material* matl2 = new Material;
+
+ CopyMemory(matl2, matl1, sizeof(Material));
+ matl2->thumbnail = 0;
+
+ materials.append(matl2);
+ }
+
+ ListIter<Surface> s_iter = pmod->surfaces;
+ while (++s_iter) {
+ Surface* surf1 = s_iter.value();
+ Surface* surf2 = new Surface;
+
+ surf2->Copy(*surf1, this);
+ surfaces.append(surf2);
+ }
+ }
+
+ return *this;
+}
+
+// +--------------------------------------------------------------------+
+
+int
+Model::NumSegments() const
+{
+ int nsegments = 0;
+
+ for (int i = 0; i < surfaces.size(); i++) {
+ const Surface* s = surfaces[i];
+ nsegments += s->NumSegments();
+ }
+
+ return nsegments;
+}
+
+// +--------------------------------------------------------------------+
+
+inline bool Collinear(const double* a, const double* b, const double* c)
+{
+ Point ab(b[0]-a[0], b[1]-a[1], b[2]-a[2]);
+ Point ac(c[0]-a[0], c[1]-a[1], c[2]-a[2]);
+ Point cross = ab.cross(ac);
+ return (cross.length() == 0);
+}
+
+struct HomogenousPlane
+{
+ double distance;
+ double normal_x;
+ double normal_y;
+ double normal_z;
+ double normal_w;
+};
+
+static void LoadPlane(Plane& p, DataLoader* l, BYTE*& fp)
+{
+ HomogenousPlane tmp;
+ l->fread(&tmp, sizeof(HomogenousPlane), 1, fp);
+}
+
+static void LoadFlags(LPDWORD flags, DataLoader* l, BYTE*& fp)
+{
+ DWORD magic_flags;
+ l->fread(&magic_flags, sizeof(DWORD), 1, fp);
+
+ /** OLD MAGIC FLAGS
+enum { FLAT_SHADED = 1,
+ LUMINOUS = 2,
+ TRANSLUCENT = 4, \\ must swap
+ CHROMAKEY = 8, // these two
+ FOREGROUND = 16, -- not used
+ WIREFRAME = 32, -- not used
+ SPECULAR1 = 64,
+ SPECULAR2 = 128 };
+***/
+
+ const DWORD magic_mask = 0x0fc3;
+
+ *flags = magic_flags & magic_mask;
+}
+
+// +--------------------------------------------------------------------+
+
+bool
+Model::Load(const char* mag_file, double scale)
+{
+ BYTE* block;
+ DataLoader* loader = DataLoader::GetLoader();
+ bool result = false;
+
+ radius = 0.0f;
+ extents[0] = 0.0f;
+ extents[1] = 0.0f;
+ extents[2] = 0.0f;
+ extents[3] = 0.0f;
+ extents[4] = 0.0f;
+ extents[5] = 0.0f;
+
+ if (!loader) {
+ Print("MAG Open Failed: no data loader for file '%s'\n", mag_file);
+ return result;
+ }
+
+ int size = loader->LoadBuffer(mag_file, block);
+ BYTE* fp = block;
+
+ // check MAG file:
+ if (!size) {
+ Print("MAG Open Failed: could not open file '%s'\n", mag_file);
+ return result;
+ }
+
+ strncpy_s(name, mag_file, 31);
+ name[31] = 0;
+
+ char file_id[5];
+ CopyMemory(file_id, block, 4);
+ file_id[4] = '\0';
+ int version = 1;
+
+ if (!strcmp(file_id, "MAG6")) {
+ version = 6;
+ }
+ else if (!strcmp(file_id, "MAG5")) {
+ version = 5;
+ }
+ else if (!strcmp(file_id, "MAG4")) {
+ version = 4;
+ }
+ else {
+ Print("MAG Open Failed: File '%s' Invalid file type '%s'\n", mag_file, file_id);
+ loader->ReleaseBuffer(block);
+ return result;
+ }
+
+ // get ready to load, delete existing model:
+ surfaces.destroy();
+ materials.destroy();
+ nverts = 0;
+ npolys = 0;
+
+ // now load the model:
+ switch (version) {
+ case 4:
+ case 5:
+ result = LoadMag5(block, size, scale);
+ break;
+
+ case 6:
+ result = LoadMag6(block, size, scale);
+ break;
+
+ default:
+ break;
+ }
+
+ loader->ReleaseBuffer(block);
+ return result;
+}
+
+// +--------------------------------------------------------------------+
+
+bool
+Model::Load(ModelFile* mod_file, double scale)
+{
+ if (mod_file) {
+ return mod_file->Load(this, scale);
+ }
+
+ return false;
+}
+
+// +--------------------------------------------------------------------+
+
+static int mcomp(const void* a, const void* b)
+{
+ Poly* pa = (Poly*) a;
+ Poly* pb = (Poly*) b;
+
+ if (pa->sortval == pb->sortval)
+ return 0;
+
+ if (pa->sortval < pb->sortval)
+ return 1;
+
+ return -1;
+}
+
+bool
+Model::LoadMag5(BYTE* block, int len, double scale)
+{
+ bool result = false;
+
+ DataLoader* loader = DataLoader::GetLoader();
+ BYTE* fp = block + 4;
+ int ntex = 0;
+ int nsurfs = 0;
+
+ loader->fread(&ntex, sizeof(ntex), 1, fp);
+ loader->fread(&nsurfs, sizeof(nsurfs), 1, fp);
+
+ // create a default gray material:
+ Material* mtl = new Material;
+
+ if (mtl) {
+ mtl->Ka = Color::LightGray;
+ mtl->Kd = Color::LightGray;
+ mtl->Ks = ColorValue(0.2f,0.2f,0.2f);
+ mtl->power = 20.0f;
+
+ mtl->ambient_value = 1.0f;
+ mtl->ambient_color = Color::LightGray;
+ mtl->diffuse_value = 1.0f;
+ mtl->diffuse_color = Color::LightGray;
+ mtl->specular_value = 0.2f;
+ mtl->specular_color = Color::White;
+ strcpy_s(mtl->name, "(default)");
+
+ materials.append(mtl);
+ }
+
+ // read texture list:
+ for (int i = 0; i < ntex; i++) {
+ mtl = new Material;
+ char tname[32];
+
+ if (mtl) {
+ mtl->Ka = ColorValue(0.5f,0.5f,0.5f);
+ mtl->Kd = ColorValue(1.0f,1.0f,1.0f);
+ mtl->Ks = ColorValue(0.2f,0.2f,0.2f);
+ mtl->power = 20.0f;
+
+ mtl->ambient_value = 1.0f;
+ mtl->ambient_color = Color::Gray;
+ mtl->diffuse_value = 1.0f;
+ mtl->diffuse_color = Color::White;
+ mtl->specular_value = 0.2f;
+ mtl->specular_color = Color::White;
+
+ loader->fread(tname, 32, 1, fp);
+ loader->LoadTexture(tname, mtl->tex_diffuse, Bitmap::BMP_SOLID, true);
+ strcpy_s(mtl->name, tname);
+
+ char* dot = strrchr(mtl->name, '.');
+ if (dot)
+ *dot = 0;
+
+ char* plus = strrchr(mtl->name, '+');
+ if (plus)
+ *plus = 0;
+
+ materials.append(mtl);
+ }
+ }
+
+
+ loader->fread(&nverts, 4, 1, fp);
+ loader->fread(&npolys, 4, 1, fp);
+
+ // plan on creating four verts per poly:
+ int mag_nverts = nverts;
+ int next_vert = nverts;
+
+ nverts = npolys * 4;
+
+ Surface* s = new Surface;
+ VertexSet* vset = 0;
+
+ if (s) {
+ strcpy_s(s->name, "default");
+
+ s->model = this;
+ s->vertex_set = new VertexSet(nverts);
+ s->vloc = new Vec3[nverts];
+
+ ZeroMemory(s->vertex_set->loc, nverts * sizeof(Vec3));
+ ZeroMemory(s->vertex_set->diffuse, nverts * sizeof(DWORD));
+ ZeroMemory(s->vertex_set->specular, nverts * sizeof(DWORD));
+ ZeroMemory(s->vertex_set->tu, nverts * sizeof(float));
+ ZeroMemory(s->vertex_set->tv, nverts * sizeof(float));
+ ZeroMemory(s->vertex_set->rw, nverts * sizeof(float));
+ ZeroMemory(s->vloc, nverts * sizeof(Vec3));
+
+ s->npolys = npolys;
+ s->polys = new Poly[npolys];
+
+ ZeroMemory(s->polys, sizeof(Poly) * npolys);
+ surfaces.append(s);
+
+ vset = s->vertex_set;
+
+ int v;
+ // read vertex set:
+ for (v = 0; v < mag_nverts; v++) {
+ Vec3 vert, norm;
+ DWORD vstate;
+
+ loader->fread(&vert, sizeof(Vec3), 1, fp);
+ loader->fread(&norm, sizeof(Vec3), 1, fp);
+ loader->fread(&vstate, sizeof(DWORD), 1, fp);
+
+ vert.SwapYZ();
+ vert *= (float) scale;
+
+ vset->loc[v] = vert;
+ vset->nrm[v] = norm;
+
+ double d = vert.length();
+ if (d > radius)
+ radius = (float) d;
+
+ if (vert.x > extents[0]) extents[0] = vert.x;
+ if (vert.x < extents[1]) extents[1] = vert.x;
+ if (vert.y > extents[2]) extents[2] = vert.y;
+ if (vert.y < extents[3]) extents[3] = vert.y;
+ if (vert.z > extents[4]) extents[4] = vert.z;
+ if (vert.z < extents[5]) extents[5] = vert.z;
+ }
+
+ while (v < nverts)
+ vset->nrm[v++] = Vec3(1,0,0);
+
+ // read polys:
+ Vec3 dummy_center;
+ DWORD dummy_flags;
+ DWORD dummy_color;
+ Plane dummy_plane;
+ int texture_num;
+ int poly_nverts;
+ int vert_index_buffer[32];
+ float texture_index_buffer[32];
+
+ for (int n = 0; n < npolys; n++) {
+ Poly& poly = s->polys[n];
+ poly.vertex_set = vset;
+
+ loader->fread(&dummy_flags, sizeof(DWORD), 1, fp);
+ loader->fread(&dummy_center, sizeof(Vec3), 1, fp);
+
+ LoadPlane(dummy_plane, loader, fp);
+
+ loader->fread(&dummy_color, sizeof(DWORD), 1, fp);
+ loader->fread(&texture_num, sizeof(int), 1, fp);
+
+ if (texture_num >= 0 && texture_num < ntex) {
+ int mtl_num = texture_num + 1;
+ poly.material = materials[mtl_num];
+ poly.sortval = texture_num;
+
+ bool flag_translucent = (dummy_flags & 0x04) ? true : false;
+ bool flag_transparent = (dummy_flags & 0x08) ? true : false;
+
+ // luminous
+ if (dummy_flags & 2) {
+ mtl = materials[mtl_num];
+
+ mtl->Ka = ColorValue(0,0,0,0);
+ mtl->Kd = ColorValue(0,0,0,0);
+ mtl->Ks = ColorValue(0,0,0,0);
+ mtl->Ke = ColorValue(1,1,1,1);
+
+ mtl->tex_emissive = mtl->tex_diffuse;
+ }
+
+ // glowing (additive)
+ if (flag_translucent && flag_transparent)
+ materials[mtl_num]->blend = Material::MTL_ADDITIVE;
+
+ // translucent (alpha)
+ else if (flag_translucent)
+ materials[mtl_num]->blend = Material::MTL_TRANSLUCENT;
+
+ // transparent (just use alpha for this)
+ else if (flag_transparent)
+ materials[mtl_num]->blend = Material::MTL_TRANSLUCENT;
+ }
+ else {
+ poly.material = materials.first();
+ poly.sortval = 1000;
+ }
+
+ // hack: store flat shaded flag in unused visible byte
+ poly.visible = (BYTE) (dummy_flags & 1);
+
+ loader->fread(&poly_nverts, sizeof(int), 1, fp);
+ loader->fread(vert_index_buffer, sizeof(int), poly_nverts, fp);
+
+ if (poly_nverts == 3)
+ s->nindices += 3;
+
+ else if (poly_nverts == 4)
+ s->nindices += 6;
+
+ poly.nverts = poly_nverts;
+ for (int vi = 0; vi < poly_nverts; vi++) {
+ v = vert_index_buffer[vi];
+
+ if (vset->rw[v] > 0) {
+ vset->CopyVertex(next_vert, v);
+ v = next_vert++;
+ }
+
+ vset->rw[v] = 1;
+ poly.verts[vi] = v;
+ }
+
+ loader->fread(texture_index_buffer, sizeof(float), poly_nverts, fp); // tu's
+ for (int vi = 0; vi < poly_nverts; vi++) {
+ v = poly.verts[vi];
+ vset->tu[v] = texture_index_buffer[vi];
+ }
+
+ loader->fread(texture_index_buffer, sizeof(float), poly_nverts, fp); // tv's
+ for (int vi = 0; vi < poly_nverts; vi++) {
+ v = poly.verts[vi];
+ vset->tv[v] = texture_index_buffer[vi];
+ }
+
+ fp += 16;
+ }
+
+ // pass 2 (adjust vertex normals for flat polys):
+ for (int n = 0; n < npolys; n++) {
+ Poly& poly = s->polys[n];
+ poly.plane = Plane(vset->loc[poly.verts[0]],
+ vset->loc[poly.verts[2]],
+ vset->loc[poly.verts[1]]);
+
+ // hack: retrieve flat shaded flag from unused visible byte
+ if (poly.visible) {
+ poly_nverts = poly.nverts;
+
+ for (int vi = 0; vi < poly_nverts; vi++) {
+ v = poly.verts[vi];
+ vset->nrm[v] = poly.plane.normal;
+ }
+ }
+ }
+
+ // sort the polys by material index:
+ qsort((void*) s->polys, s->npolys, sizeof(Poly), mcomp);
+
+ // then assign them to cohesive segments:
+ Segment* segment = 0;
+
+ for (int n = 0; n < npolys; n++) {
+ if (segment && segment->material == s->polys[n].material) {
+ segment->npolys++;
+ }
+ else {
+ segment = 0;
+ }
+
+ if (!segment) {
+ segment = new Segment;
+
+ segment->npolys = 1;
+ segment->polys = &s->polys[n];
+ segment->material = segment->polys->material;
+ segment->model = this;
+
+ s->segments.append(segment);
+ }
+ }
+
+ s->BuildHull();
+
+ result = nverts && npolys;
+ }
+
+ return result;
+}
+
+// +--------------------------------------------------------------------+
+
+struct MaterialMag6 {
+ char name[Material::NAMELEN];
+ char shader[Material::NAMELEN];
+ float power; // highlight sharpness (big=shiny)
+ float brilliance; // diffuse power function
+ float bump; // bump level (0=none)
+ DWORD blend; // alpha blend type
+ bool shadow; // material casts shadow
+ bool luminous; // verts have their own lighting
+
+ Color ambient_color;
+ Color diffuse_color;
+ Color specular_color;
+ Color emissive_color;
+
+ float ambient_value;
+ float diffuse_value;
+ float specular_value;
+ float emissive_value;
+
+ BYTE tex_diffuse;
+ BYTE tex_specular;
+ BYTE tex_bumpmap;
+ BYTE tex_emissive;
+};
+
+// +--------------------------------------------------------------------+
+
+bool
+Model::LoadMag6(BYTE* block, int len, double scale)
+{
+ bool result = false;
+
+ DataLoader* loader = DataLoader::GetLoader();
+ BYTE* fp = block + 4;
+ int ntex = 0;
+ int nmtls = 0;
+ int nsurfs = 0;
+ List<Bitmap> textures;
+
+ loader->fread(&ntex, sizeof(ntex), 1, fp); // size of texture block
+ loader->fread(&nmtls, sizeof(nmtls), 1, fp); // number of materials
+ loader->fread(&nsurfs, sizeof(nsurfs), 1, fp); // number of surfaces
+
+ // read texture list:
+ if (ntex) {
+ char* buffer = new char[ntex];
+ char* p = buffer;
+ Bitmap* bmp = 0;
+
+ loader->fread(buffer, ntex, 1, fp);
+
+ while (p < buffer + ntex) {
+ loader->LoadTexture(p, bmp, Bitmap::BMP_SOLID, true);
+ textures.append(bmp);
+
+ p += strlen(p) + 1;
+ }
+
+ delete [] buffer;
+ }
+
+ for (int i = 0; i < nmtls; i++) {
+ MaterialMag6 m6;
+ Material* mtl = new Material;
+
+ loader->fread(&m6, sizeof(m6), 1, fp);
+
+ if (mtl) {
+ CopyMemory(mtl->name, m6.name, Material::NAMELEN);
+ CopyMemory(mtl->shader, m6.shader, Material::NAMELEN);
+
+ mtl->ambient_value = m6.ambient_value;
+ mtl->ambient_color = m6.ambient_color;
+ mtl->diffuse_value = m6.diffuse_value;
+ mtl->diffuse_color = m6.diffuse_color;
+ mtl->specular_value = m6.specular_value;
+ mtl->specular_color = m6.specular_color;
+ mtl->emissive_value = m6.emissive_value;
+ mtl->emissive_color = m6.emissive_color;
+
+ mtl->Ka = ColorValue(mtl->ambient_color) * mtl->ambient_value;
+ mtl->Kd = ColorValue(mtl->diffuse_color) * mtl->diffuse_value;
+ mtl->Ks = ColorValue(mtl->specular_color) * mtl->specular_value;
+ mtl->Ke = ColorValue(mtl->emissive_color) * mtl->emissive_value;
+
+ mtl->power = m6.power;
+ mtl->brilliance = m6.brilliance;
+ mtl->bump = m6.bump;
+ mtl->blend = m6.blend;
+ mtl->shadow = m6.shadow;
+ mtl->luminous = m6.luminous;
+
+ if (m6.tex_diffuse && m6.tex_diffuse <= textures.size())
+ mtl->tex_diffuse = textures[m6.tex_diffuse - 1];
+
+ if (m6.tex_specular && m6.tex_specular <= textures.size())
+ mtl->tex_specular = textures[m6.tex_specular - 1];
+
+ if (m6.tex_emissive && m6.tex_emissive <= textures.size())
+ mtl->tex_emissive = textures[m6.tex_emissive - 1];
+
+ if (m6.tex_bumpmap && m6.tex_bumpmap <= textures.size())
+ mtl->tex_bumpmap = textures[m6.tex_bumpmap - 1];
+
+ materials.append(mtl);
+ }
+ }
+
+ for (int i = 0; i < nsurfs; i++) {
+ int nverts = 0;
+ int npolys = 0;
+ BYTE namelen = 0;
+ char name[128];
+
+ loader->fread(&nverts, 4, 1, fp);
+ loader->fread(&npolys, 4, 1, fp);
+ loader->fread(&namelen, 1, 1, fp);
+ loader->fread(name, 1, namelen, fp);
+
+ Surface* surface = new Surface;
+ surface->model = this;
+ surface->SetName(name);
+ surface->CreateVerts(nverts);
+ surface->CreatePolys(npolys);
+
+ VertexSet* vset = surface->GetVertexSet();
+ Poly* polys = surface->GetPolys();
+
+ ZeroMemory(polys, sizeof(Poly) * npolys);
+
+ // read vertex set:
+ for (int v = 0; v < nverts; v++) {
+ loader->fread(&vset->loc[v], sizeof(float), 3, fp);
+ loader->fread(&vset->nrm[v], sizeof(float), 3, fp);
+ loader->fread(&vset->tu[v], sizeof(float), 1, fp);
+ loader->fread(&vset->tv[v], sizeof(float), 1, fp);
+
+ vset->loc[v] *= (float) scale;
+
+ Vec3 vert = vset->loc[v];
+
+ double d = vert.length();
+ if (d > radius)
+ radius = (float) d;
+
+ if (vert.x > extents[0]) extents[0] = vert.x;
+ if (vert.x < extents[1]) extents[1] = vert.x;
+ if (vert.y > extents[2]) extents[2] = vert.y;
+ if (vert.y < extents[3]) extents[3] = vert.y;
+ if (vert.z > extents[4]) extents[4] = vert.z;
+ if (vert.z < extents[5]) extents[5] = vert.z;
+ }
+
+ // read polys:
+ for (int n = 0; n < npolys; n++) {
+ Poly& poly = polys[n];
+ BYTE poly_nverts = 0;
+ BYTE material_index = 0;
+ WORD poly_verts[8];
+
+ loader->fread(&poly_nverts, sizeof(BYTE), 1, fp);
+ loader->fread(&material_index, sizeof(BYTE), 1, fp);
+ loader->fread(&poly_verts[0], sizeof(WORD), poly_nverts, fp);
+
+ if (poly_nverts >= 3) {
+ poly.nverts = poly_nverts;
+
+ for (int i = 0; i < poly_nverts; i++) {
+ poly.verts[i] = poly_verts[i];
+ }
+ }
+ else {
+ poly.sortval = 666;
+ }
+
+ if (material_index > 0) {
+ poly.material = materials[material_index-1];
+ poly.sortval = material_index;
+ }
+ else if (materials.size()) {
+ poly.material = materials.first();
+ poly.sortval = 1;
+ }
+ else {
+ poly.sortval = 1000;
+ }
+
+ if (poly.nverts == 3)
+ surface->AddIndices(3);
+
+ else if (poly.nverts == 4)
+ surface->AddIndices(6);
+
+ poly.vertex_set = vset;
+ poly.plane = Plane(vset->loc[poly.verts[0]],
+ vset->loc[poly.verts[2]],
+ vset->loc[poly.verts[1]]);
+ }
+
+ // sort the polys by material index:
+ qsort((void*) polys, npolys, sizeof(Poly), mcomp);
+
+ // then assign them to cohesive segments:
+ Segment* segment = 0;
+
+ for (int n = 0; n < npolys; n++) {
+ if (segment && segment->material == polys[n].material) {
+ segment->npolys++;
+ }
+ else {
+ segment = 0;
+ }
+
+ if (!segment) {
+ segment = new Segment;
+
+ segment->npolys = 1;
+ segment->polys = &polys[n];
+ segment->material = segment->polys->material;
+ segment->model = this;
+
+ surface->GetSegments().append(segment);
+ }
+ }
+
+ surface->BuildHull();
+ surfaces.append(surface);
+
+ this->nverts += nverts;
+ this->npolys += npolys;
+ }
+
+
+ result = nverts && npolys;
+ return result;
+}
+
+void
+Model::AddSurface(Surface* surface)
+{
+ if (surface) {
+ surface->model = this;
+
+ ListIter<Segment> iter = surface->segments;
+ while (++iter) {
+ Segment* segment = iter.value();
+ segment->model = this;
+ }
+
+ surface->BuildHull();
+ surfaces.append(surface);
+
+ nverts += surface->NumVerts();
+ npolys += surface->NumPolys();
+ }
+}
+
+
+// +--------------------------------------------------------------------+
+
+const Material*
+Model::FindMaterial(const char* mtl_name) const
+{
+ if (mtl_name && *mtl_name) {
+ Model* pThis = (Model*) this;
+
+ ListIter<Material> iter = pThis->materials;
+ while (++iter) {
+ Material* mtl = iter.value();
+
+ if (!strcmp(mtl->name, mtl_name))
+ return mtl;
+ }
+ }
+
+ return 0;
+}
+
+const Material*
+Model::ReplaceMaterial(const Material* mtl)
+{
+ const Material* mtl_orig = 0;
+
+ if (mtl) {
+ mtl_orig = FindMaterial(mtl->name);
+
+ if (mtl_orig) {
+ int n = materials.index(mtl_orig);
+ materials[n] = (Material*) mtl;
+
+ ListIter<Surface> surf_iter = surfaces;
+ while (++surf_iter) {
+ Surface* surf = surf_iter.value();
+
+ ListIter<Segment> seg_iter = surf->GetSegments();
+ while (++seg_iter) {
+ Segment* segment = seg_iter.value();
+
+ if (segment->material == mtl_orig)
+ segment->material = (Material*) mtl;
+ }
+ }
+ }
+ }
+
+ return mtl_orig;
+}
+
+// +--------------------------------------------------------------------+
+
+Poly*
+Model::AddPolys(int nsurf, int np, int nv)
+{
+ if (nsurf >= 0 && nsurf < surfaces.size())
+ return surfaces[nsurf]->AddPolys(np, nv);
+
+ ::Print("WARNING: AddPolys(%d,%d,%d) invalid surface\n", nsurf, np, nv);
+ return 0;
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Model::ExplodeMesh()
+{
+ ListIter<Surface> iter = surfaces;
+
+ int nv = 0;
+ int np = 0;
+
+ while (++iter) {
+ Surface* s = iter.value();
+ s->ExplodeMesh();
+
+ nv += s->NumVerts();
+ np += s->NumPolys();
+ }
+
+ nverts = nv;
+ npolys = np;
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Model::OptimizeMesh()
+{
+ ListIter<Surface> iter = surfaces;
+
+ int nv = 0;
+ int np = 0;
+
+ while (++iter) {
+ Surface* s = iter.value();
+ s->OptimizeMesh();
+
+ nv += s->NumVerts();
+ np += s->NumPolys();
+ }
+
+ nverts = nv;
+ npolys = np;
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Model::OptimizeMaterials()
+{
+ for (int i = 0; i < materials.size(); i++) {
+ Material* m1 = materials[i];
+
+ for (int n = i; n < materials.size(); n++) {
+ Material* m2 = materials[n];
+
+ // if they match, merge them:
+ if (*m1 == *m2) {
+ List<Poly> polys;
+ SelectPolys(polys, m2);
+
+ ListIter<Poly> iter = polys;
+ while (++iter) {
+ Poly* p = iter.value();
+ p->material = m1;
+ }
+
+ // and discard the duplicate:
+ materials.remove(m2);
+ delete m2;
+ }
+ }
+ }
+}
+
+void
+Model::ScaleBy(double factor)
+{
+ ListIter<Surface> iter = surfaces;
+
+ while (++iter) {
+ Surface* s = iter.value();
+ s->ScaleBy(factor);
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Model::Normalize()
+{
+ ListIter<Surface> iter = surfaces;
+
+ while (++iter) {
+ Surface* s = iter.value();
+ s->Normalize();
+ }
+}
+
+void
+Model::SelectPolys(List<Poly>& polys, Vec3 loc)
+{
+ ListIter<Surface> iter = surfaces;
+
+ while (++iter) {
+ Surface* s = iter.value();
+ s->SelectPolys(polys, loc);
+ }
+}
+
+void
+Model::SelectPolys(List<Poly>& polys, Material* m)
+{
+ ListIter<Surface> iter = surfaces;
+
+ while (++iter) {
+ Surface* s = iter.value();
+ s->SelectPolys(polys, m);
+ }
+}
+
+void
+Model::ComputeTangents()
+{
+ ListIter<Surface> iter = surfaces;
+
+ while (++iter) {
+ Surface* s = iter.value();
+ s->ComputeTangents();
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Model::DeletePrivateData()
+{
+ ListIter<Surface> iter = surfaces;
+ while (++iter) {
+ Surface* s = iter.value();
+ VideoPrivateData* vpd = s->GetVideoPrivateData();
+
+ if (vpd) {
+ delete vpd;
+ s->SetVideoPrivateData(0);
+ }
+
+ ListIter<Segment> seg_iter = s->GetSegments();
+ while (++seg_iter) {
+ Segment* segment = seg_iter.value();
+ VideoPrivateData* vpdp = segment->video_data;
+
+ if (vpdp) {
+ delete vpdp;
+ segment->video_data = 0;
+ }
+ }
+ }
+}
+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+
+Surface::Surface()
+ : model(0), vertex_set(0), vloc(0), nhull(0), npolys(0), nindices(0),
+ polys(0), state(0), video_data(0), opcode(0)
+{
+ ZeroMemory(name, sizeof(name));
+}
+
+Surface::~Surface()
+{
+ segments.destroy();
+
+ delete opcode;
+ delete vertex_set;
+ delete [] vloc;
+ delete [] polys;
+ delete video_data;
+
+ model = 0;
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Surface::Copy(Surface& s, Model* m)
+{
+ segments.destroy();
+
+ delete opcode;
+ delete vertex_set;
+ delete [] vloc;
+ delete [] polys;
+ delete video_data;
+
+ CopyMemory(name, s.name, Solid::NAMELEN);
+
+ model = m;
+ radius = s.radius;
+ nhull = s.nhull;
+ npolys = s.npolys;
+ nindices = s.nindices;
+ state = s.state;
+ offset = s.offset;
+ orientation = s.orientation;
+ opcode = 0;
+ video_data = 0;
+
+ vertex_set = s.vertex_set->Clone();
+
+ if (nhull > 0) {
+ vloc = new Vec3[nhull];
+ CopyMemory(vloc, s.vloc, nhull * sizeof(Vec3));
+ }
+ else {
+ vloc = 0;
+ }
+
+ polys = new Poly[npolys];
+ CopyMemory(polys, s.polys, npolys * sizeof(Poly));
+
+ for (int i = 0; i < npolys; i++) {
+ polys[i].vertex_set = vertex_set;
+
+ if (s.polys[i].material)
+ polys[i].material = (Material*) model->FindMaterial(s.polys[i].material->name);
+ }
+
+ ListIter<Segment> iter = s.segments;
+ while (++iter) {
+ Segment* seg1 = iter.value();
+ Segment* seg2 = new Segment;
+
+ seg2->npolys = seg1->npolys;
+ seg2->polys = polys + (seg1->polys - s.polys);
+
+ if (seg2->polys[0].material)
+ seg2->material = seg2->polys[0].material;
+
+ seg2->model = model;
+ seg2->video_data = 0;
+
+ segments.append(seg2);
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Surface::SetName(const char* n)
+{
+ int len = sizeof(name);
+
+ ZeroMemory(name, len);
+ strncpy_s(name, n, len-1);
+}
+
+void
+Surface::SetHidden(bool b)
+{
+ if (b)
+ state = state | HIDDEN;
+
+ else
+ state = state & ~HIDDEN;
+}
+
+void
+Surface::SetLocked(bool b)
+{
+ if (b)
+ state = state | LOCKED;
+
+ else
+ state = state & ~LOCKED;
+}
+
+void
+Surface::SetSimplified(bool b)
+{
+ if (b)
+ state = state | SIMPLE;
+
+ else
+ state = state & ~SIMPLE;
+}
+
+void
+Surface::CreateVerts(int nverts)
+{
+ if (!vertex_set && !vloc) {
+ vertex_set = new VertexSet(nverts);
+ vloc = new Vec3[nverts];
+ }
+}
+
+void
+Surface::CreatePolys(int np)
+{
+ if (!polys && !npolys) {
+ npolys = np;
+ polys = new Poly[npolys];
+
+ ZeroMemory(polys, npolys * sizeof(Poly));
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+Poly*
+Surface::AddPolys(int np, int nv)
+{
+ if ( polys && vertex_set &&
+ np > 0 && np + npolys < MAX_POLYS &&
+ nv > 0 && nv + vertex_set->nverts < MAX_VERTS)
+ {
+ int newverts = nv + vertex_set->nverts;
+ int newpolys = np + npolys;
+
+ vertex_set->Resize(newverts, true);
+
+ Poly* pset = new Poly[newpolys];
+ Poly* pnew = pset + npolys;
+
+ CopyMemory(pset, polys, npolys * sizeof(Poly));
+ ZeroMemory(pnew, np * sizeof(Poly));
+
+ if (segments.size() > 0) {
+ Segment* seg = segments.last();
+ Material* mtl = seg->material;
+
+ for (int i = 0; i < np; i++) {
+ Poly* p = pnew + i;
+ p->material = mtl;
+ }
+
+ seg->npolys += np;
+ }
+ }
+
+ return 0;
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Surface::ExplodeMesh()
+{
+ if (!vertex_set || vertex_set->nverts < 3)
+ return;
+
+ int i, j, v;
+ int nverts = 0;
+
+ // count max verts:
+ for (i = 0; i < npolys; i++) {
+ Poly* p = polys + i;
+ nverts += p->nverts;
+ }
+
+ // create target vertex set:
+ VertexSet* vset = new VertexSet(nverts);
+ v = 0;
+
+ // explode verts:
+ for (i = 0; i < npolys; i++) {
+ Poly* p = polys + i;
+ p->vertex_set = vset;
+
+ for (j = 0; j < p->nverts; j++) {
+ int vsrc = p->verts[j];
+
+ vset->loc[v] = vertex_set->loc[vsrc];
+ vset->nrm[v] = vertex_set->nrm[vsrc];
+ vset->tu[v] = vertex_set->tu[vsrc];
+ vset->tv[v] = vertex_set->tv[vsrc];
+ vset->rw[v] = vertex_set->rw[vsrc];
+ vset->diffuse[v] = vertex_set->diffuse[vsrc];
+ vset->specular[v] = vertex_set->specular[vsrc];
+
+ p->verts[j] = v++;
+ }
+ }
+
+ // finalize:
+ if (vset) {
+ delete vertex_set;
+ vertex_set = vset;
+ }
+
+ if (vloc)
+ delete [] vloc;
+
+ vloc = new Vec3[nverts];
+
+ ComputeTangents();
+ BuildHull();
+}
+
+// +--------------------------------------------------------------------+
+
+const double SELECT_EPSILON = 0.05;
+const double SELECT_TEXTURE = 0.0001;
+
+static bool MatchVerts(VertexSet* vset, int i, int j)
+{
+ double d = 0;
+ const Vec3& vl1 = vset->loc[i];
+ const Vec3& vn1 = vset->nrm[i];
+ float tu1 = vset->tu[i];
+ float tv1 = vset->tv[i];
+ const Vec3& vl2 = vset->loc[j];
+ const Vec3& vn2 = vset->nrm[j];
+ float tu2 = vset->tu[j];
+ float tv2 = vset->tv[j];
+
+ d = fabs(vl1.x - vl2.x);
+ if (d > SELECT_EPSILON)
+ return false;
+
+ d = fabs(vl1.y - vl2.y);
+ if (d > SELECT_EPSILON)
+ return false;
+
+ d = fabs(vl1.z - vl2.z);
+ if (d > SELECT_EPSILON)
+ return false;
+
+ d = fabs(vn1.x - vn2.x);
+ if (d > SELECT_EPSILON)
+ return false;
+
+ d = fabs(vn1.y - vn2.y);
+ if (d > SELECT_EPSILON)
+ return false;
+
+ d = fabs(vn1.z - vn2.z);
+ if (d > SELECT_EPSILON)
+ return false;
+
+ d = fabs(tu1 - tu2);
+ if (d > SELECT_TEXTURE)
+ return false;
+
+ d = fabs(tv1 - tv2);
+ if (d > SELECT_TEXTURE)
+ return false;
+
+ return true;
+}
+
+void
+Surface::OptimizeMesh()
+{
+ if (!vertex_set || vertex_set->nverts < 3)
+ return;
+
+ int nverts = vertex_set->nverts;
+ int used = 0;
+ int final = 0;
+ int nmatch = 0;
+
+ // create vertex maps:
+ BYTE* vert_map = new BYTE[nverts];
+ WORD* vert_dst = new WORD[nverts];
+ ZeroMemory(vert_map, nverts * sizeof(BYTE));
+ ZeroMemory(vert_dst, nverts * sizeof(WORD));
+
+ // count used verts:
+ for (int i = 0; i < npolys; i++) {
+ Poly* p = polys + i;
+
+ for (int j = 0; j < p->nverts; j++) {
+ WORD vert = p->verts[j];
+
+ if (vert < nverts) {
+ vert_map[vert]++;
+ used++;
+ }
+ }
+ }
+
+ // create target vertex set:
+ VertexSet* vset = new VertexSet(used);
+ int v = 0;
+
+ // compress verts:
+ for (int i = 0; i < nverts; i++) {
+ if (vert_map[i] == 0) continue;
+
+ vert_dst[i] = v;
+ vset->loc[v] = vertex_set->loc[i];
+ vset->nrm[v] = vertex_set->nrm[i];
+ vset->tu[v] = vertex_set->tu[i];
+ vset->tv[v] = vertex_set->tv[i];
+ vset->rw[v] = vertex_set->rw[i];
+ vset->diffuse[v] = vertex_set->diffuse[i];
+ vset->specular[v] = vertex_set->specular[i];
+
+ for (int j = i+1; j < nverts; j++) {
+ if (vert_map[j] == 0) continue;
+
+ if (MatchVerts(vertex_set, i, j)) {
+ vert_map[j] = 0;
+ vert_dst[j] = v;
+ nmatch++;
+ }
+ }
+
+ v++;
+ }
+
+ final = v;
+
+ // remap polys:
+ for (int n = 0; n < npolys; n++) {
+ Poly* p = polys + n;
+ p->vertex_set = vset;
+ for (int v = 0; v < p->nverts; v++) {
+ p->verts[v] = vert_dst[ p->verts[v] ];
+ }
+ }
+
+ // finalize:
+ if (vset && final < nverts) {
+ delete vertex_set;
+ vertex_set = vset;
+ vset->Resize(final, true);
+ nverts = final;
+ }
+
+ // clean up and rebuild hull:
+ delete [] vert_map;
+
+ if (vloc)
+ delete [] vloc;
+
+ vloc = new Vec3[nverts];
+
+ ComputeTangents();
+ BuildHull();
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Surface::ScaleBy(double factor)
+{
+ offset *= factor;
+
+ if (vertex_set && vertex_set->nverts) {
+ for (int i = 0; i < vertex_set->nverts; i++) {
+ vertex_set->loc[i] *= (float) factor;
+ }
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Surface::BuildHull()
+{
+ if (npolys < 1 || !vertex_set || vertex_set->nverts < 1)
+ return;
+
+ nhull = 0;
+
+ for (int i = 0; i < npolys; i++) {
+ Poly* p = polys + i;
+
+ for (int n = 0; n < p->nverts; n++) {
+ WORD v = p->verts[n];
+ WORD h;
+
+ for (h = 0; h < nhull; h++) {
+ Vec3& vl = vertex_set->loc[v];
+ Vec3& loc = vloc[h];
+
+ double d = vl.x - loc.x;
+
+ if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
+ continue;
+
+ d = vl.y - loc.y;
+
+ if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
+ continue;
+
+ d = vl.z - loc.z;
+
+ if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
+ continue;
+
+ // found a match:
+ break;
+ }
+
+ // didn't find a match:
+ if (h >= nhull) {
+ vloc[h] = vertex_set->loc[v];
+ nhull = h+1;
+ }
+
+ p->vlocs[n] = h;
+ }
+ }
+
+ if (use_collision_detection)
+ InitializeCollisionHull();
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Surface::Normalize()
+{
+ if (npolys < 1 || !vertex_set || vertex_set->nverts < 1)
+ return;
+
+ // STEP ONE: initialize poly planes
+
+ for (int i = 0; i < npolys; i++) {
+ Poly* p = polys + i;
+
+ p->plane = Plane( vertex_set->loc[ p->verts[0] ],
+ vertex_set->loc[ p->verts[2] ],
+ vertex_set->loc[ p->verts[1] ] );
+ }
+
+ // STEP TWO: compute vertex normals by averaging adjecent poly planes
+
+ List<Poly> faces;
+ for (int v = 0; v < vertex_set->nverts; v++) {
+ faces.clear();
+ SelectPolys(faces, vertex_set->loc[v]);
+
+ if (faces.size()) {
+ vertex_set->nrm[v] = Vec3(0.0f, 0.0f, 0.0f);
+
+ for (int i = 0; i < faces.size(); i++) {
+ vertex_set->nrm[v] += faces[i]->plane.normal;
+ }
+
+ vertex_set->nrm[v].Normalize();
+ }
+
+ else if (vertex_set->loc[v].length() > 0) {
+ vertex_set->nrm[v] = vertex_set->loc[v];
+ vertex_set->nrm[v].Normalize();
+ }
+
+ else {
+ vertex_set->nrm[v] = Vec3(0.0f, 1.0f, 0.0f);
+ }
+ }
+
+ // STEP THREE: adjust vertex normals for poly flatness
+
+ for (int i = 0; i < npolys; i++) {
+ Poly* p = polys + i;
+
+ for (int n = 0; n < p->nverts; n++) {
+ int v = p->verts[n];
+
+ vertex_set->nrm[v] = vertex_set->nrm[v] * (1.0f - p->flatness) +
+ p->plane.normal * ( p->flatness);
+ }
+ }
+}
+
+void
+Surface::SelectPolys(List<Poly>& selection, Vec3 loc)
+{
+ const double SELECT_EPSILON = 0.05;
+
+ for (int i = 0; i < npolys; i++) {
+ Poly* p = polys + i;
+
+ for (int n = 0; n < p->nverts; n++) {
+ int v = p->verts[n];
+ Vec3& vl = vertex_set->loc[v];
+ double d = vl.x - loc.x;
+
+ if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
+ continue;
+
+ d = vl.y - loc.y;
+
+ if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
+ continue;
+
+ d = vl.z - loc.z;
+
+ if (d < -SELECT_EPSILON || d > SELECT_EPSILON)
+ continue;
+
+ selection.append(p);
+ break;
+ }
+ }
+}
+
+void
+Surface::SelectPolys(List<Poly>& selection, Material* m)
+{
+ for (int i = 0; i < npolys; i++) {
+ Poly* p = polys + i;
+
+ if (p->material == m)
+ selection.append(p);
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+Surface::ComputeTangents()
+{
+ Vec3 tangent;
+ Vec3 binormal;
+
+ if (!vertex_set || !vertex_set->nverts)
+ return;
+
+ if (vertex_set->tangent)
+ return;
+
+ vertex_set->CreateTangents();
+
+ for (int i = 0; i < npolys; i++) {
+ Poly* p = polys + i;
+
+ CalcGradients(*p, tangent, binormal);
+
+ for (int n = 0; n < p->nverts; n++) {
+ vertex_set->tangent[p->verts[n]] = tangent;
+ vertex_set->binormal[p->verts[n]] = binormal;
+ }
+ }
+}
+
+void
+Surface::CalcGradients(Poly& p, Vec3& tangent, Vec3& binormal)
+{
+ // using Eric Lengyel's approach with a few modifications
+ // from Mathematics for 3D Game Programmming and Computer Graphics
+ // want to be able to trasform a vector in Object Space to Tangent Space
+ // such that the x-axis cooresponds to the 's' direction and the
+ // y-axis corresponds to the 't' direction, and the z-axis corresponds
+ // to <0,0,1>, straight up out of the texture map
+
+ VertexSet* vset = p.vertex_set;
+
+ Vec3 P = vset->loc[p.verts[1]] - vset->loc[p.verts[0]];
+ Vec3 Q = vset->loc[p.verts[2]] - vset->loc[p.verts[0]];
+
+ float s1 = vset->tu[p.verts[1]] - vset->tu[p.verts[0]];
+ float t1 = vset->tv[p.verts[1]] - vset->tv[p.verts[0]];
+ float s2 = vset->tu[p.verts[2]] - vset->tu[p.verts[0]];
+ float t2 = vset->tv[p.verts[2]] - vset->tv[p.verts[0]];
+
+ float tmp = 1.0f;
+ float denom = s1*t2 - s2*t1;
+
+ if (fabsf(denom) > 0.0001f)
+ tmp = 1.0f/(denom);
+
+ tangent.x = (t2*P.x - t1*Q.x) * tmp;
+ tangent.y = (t2*P.y - t1*Q.y) * tmp;
+ tangent.z = (t2*P.z - t1*Q.z) * tmp;
+
+ tangent.Normalize();
+
+ binormal.x = (s1*Q.x - s2*P.x) * tmp;
+ binormal.y = (s1*Q.y - s2*P.y) * tmp;
+ binormal.z = (s1*Q.z - s2*P.z) * tmp;
+
+ binormal.Normalize();
+}
+
+void
+Surface::InitializeCollisionHull()
+{
+ opcode = new OPCODE_data(this);
+}
+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+
+Segment::Segment()
+{
+ ZeroMemory(this, sizeof(Segment));
+}
+
+Segment::Segment(int n, Poly* p, Material* mtl, Model* mod)
+ : npolys(n), polys(p), material(mtl), model(mod), video_data(0)
+{
+}
+
+Segment::~Segment()
+{
+ delete video_data;
+
+ ZeroMemory(this, sizeof(Segment));
+}
+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+// +--------------------------------------------------------------------+
+
+ModelFile::ModelFile(const char* fname)
+ : model(0), pname(0), pnverts(0), pnpolys(0), pradius(0)
+{
+ int len = sizeof(filename);
+ ZeroMemory(filename, len);
+ strncpy_s(filename, fname, len);
+ filename[len-1] = 0;
+}
+
+ModelFile::~ModelFile()
+{
+}
+
+bool
+ModelFile::Load(Model* m, double scale)
+{
+ model = m;
+
+ // expose model innards for child classes:
+
+ if (model) {
+ pname = model->name;
+ pnverts = &model->nverts;
+ pnpolys = &model->npolys;
+ pradius = &model->radius;
+ }
+
+ return false;
+}
+
+bool
+ModelFile::Save(Model* m)
+{
+ model = m;
+
+ // expose model innards for child classes:
+
+ if (model) {
+ pname = model->name;
+ pnverts = &model->nverts;
+ pnpolys = &model->npolys;
+ pradius = &model->radius;
+ }
+
+ return false;
+}
+