/* Starshatter OpenSource Distribution Copyright (c) 1997-2004, Destroyer Studios LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name "Destroyer Studios" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SUBSYSTEM: nGenEx.lib FILE: Solid.cpp AUTHOR: John DiCamillo OVERVIEW ======== Classes for rendering solid meshes of polygons */ #include "MemDebug.h" #include "Solid.h" #include "Scene.h" #include "Bitmap.h" #include "DataLoader.h" #include "Light.h" #include "Shadow.h" #include "Projector.h" #include "Opcode.h" #ifdef for #undef for #endif void Print(const char* fmt, ...); // +--------------------------------------------------------------------+ 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(__FILE__,__LINE__) IcePoint[nverts]; tris = new(__FILE__,__LINE__) 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 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 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 s1_iter = model->surfaces; while (++s1_iter && !contact) { Surface* s1 = s1_iter.value(); ListIter 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 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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 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(__FILE__,__LINE__) Shadow(this)); } } void Solid::UpdateShadows(List& lights) { List active_lights; ListIter 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 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 iter = model->GetSurfaces(); while (++iter) { Surface* s = iter.value(); ListIter 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& textures) { if (model) model->GetAllTextures(textures); } void Model::GetAllTextures(List& textures) { ListIter 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 m_iter = pmod->materials; while (++m_iter) { Material* matl1 = m_iter.value(); Material* matl2 = new(__FILE__,__LINE__) Material; CopyMemory(matl2, matl1, sizeof(Material)); matl2->thumbnail = 0; materials.append(matl2); } ListIter s_iter = pmod->surfaces; while (++s_iter) { Surface* surf1 = s_iter.value(); Surface* surf2 = new(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) Surface; VertexSet* vset = 0; if (s) { strcpy_s(s->name, "default"); s->model = this; s->vertex_set = new(__FILE__,__LINE__) VertexSet(nverts); s->vloc = new(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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 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 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 surf_iter = surfaces; while (++surf_iter) { Surface* surf = surf_iter.value(); ListIter 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 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 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 polys; SelectPolys(polys, m2); ListIter 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 iter = surfaces; while (++iter) { Surface* s = iter.value(); s->ScaleBy(factor); } } // +--------------------------------------------------------------------+ void Model::Normalize() { ListIter iter = surfaces; while (++iter) { Surface* s = iter.value(); s->Normalize(); } } void Model::SelectPolys(List& polys, Vec3 loc) { ListIter iter = surfaces; while (++iter) { Surface* s = iter.value(); s->SelectPolys(polys, loc); } } void Model::SelectPolys(List& polys, Material* m) { ListIter iter = surfaces; while (++iter) { Surface* s = iter.value(); s->SelectPolys(polys, m); } } void Model::ComputeTangents() { ListIter iter = surfaces; while (++iter) { Surface* s = iter.value(); s->ComputeTangents(); } } // +--------------------------------------------------------------------+ void Model::DeletePrivateData() { ListIter iter = surfaces; while (++iter) { Surface* s = iter.value(); VideoPrivateData* vpd = s->GetVideoPrivateData(); if (vpd) { delete vpd; s->SetVideoPrivateData(0); } ListIter 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(__FILE__,__LINE__) Vec3[nhull]; CopyMemory(vloc, s.vloc, nhull * sizeof(Vec3)); } else { vloc = 0; } polys = new(__FILE__,__LINE__) 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 iter = s.segments; while (++iter) { Segment* seg1 = iter.value(); Segment* seg2 = new(__FILE__,__LINE__) 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(__FILE__,__LINE__) VertexSet(nverts); vloc = new(__FILE__,__LINE__) Vec3[nverts]; } } void Surface::CreatePolys(int np) { if (!polys && !npolys) { npolys = np; polys = new(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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(__FILE__,__LINE__) 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 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& 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& 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(__FILE__,__LINE__) 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; }