2010-02-05 13:10:32 -07:00
|
|
|
// model.cpp
|
|
|
|
//
|
|
|
|
// Copyright (C) 2004-2010, Celestia Development Team
|
|
|
|
// Original version by Chris Laurel <claurel@gmail.com>
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License
|
|
|
|
// as published by the Free Software Foundation; either version 2
|
|
|
|
// of the License, or (at your option) any later version.
|
|
|
|
|
2021-11-23 14:11:13 -07:00
|
|
|
#include <algorithm>
|
2010-02-05 13:10:32 -07:00
|
|
|
#include <cassert>
|
|
|
|
#include <functional>
|
2021-11-23 14:11:13 -07:00
|
|
|
|
|
|
|
#include <celutil/reshandle.h>
|
|
|
|
#include "model.h"
|
2010-02-05 13:10:32 -07:00
|
|
|
|
|
|
|
using namespace cmod;
|
|
|
|
using namespace Eigen;
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
2018-09-22 07:13:49 -06:00
|
|
|
Model::Model()
|
2010-02-05 13:10:32 -07:00
|
|
|
{
|
2018-09-22 07:13:49 -06:00
|
|
|
textureUsage.fill(false);
|
2010-02-05 13:10:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Model::~Model()
|
|
|
|
{
|
2018-09-22 07:13:49 -06:00
|
|
|
for (const auto mesh : meshes)
|
|
|
|
delete mesh;
|
2010-02-05 13:10:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-09-22 07:13:49 -06:00
|
|
|
/*! Return the material with the specified index, or nullptr if
|
2010-07-05 16:53:06 -06:00
|
|
|
* the index is out of range. The returned material pointer
|
|
|
|
* is const.
|
|
|
|
*/
|
2010-02-05 13:10:32 -07:00
|
|
|
const Material*
|
|
|
|
Model::getMaterial(unsigned int index) const
|
|
|
|
{
|
|
|
|
if (index < materials.size())
|
|
|
|
return materials[index];
|
|
|
|
else
|
2018-09-22 07:13:49 -06:00
|
|
|
return nullptr;
|
2010-02-05 13:10:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-07-05 16:53:06 -06:00
|
|
|
/*! Add a new material to the model's material library; the
|
|
|
|
* return value is the number of materials in the model.
|
|
|
|
*/
|
2010-02-05 13:10:32 -07:00
|
|
|
unsigned int
|
|
|
|
Model::addMaterial(const Material* m)
|
|
|
|
{
|
|
|
|
// Update the texture map usage information for the model. Since
|
|
|
|
// the material being added isn't necessarily used by a mesh within
|
|
|
|
// the model, we could potentially end up with false positives--this
|
|
|
|
// won't cause any rendering troubles, but could hurt performance
|
|
|
|
// if it forces multipass rendering when it's not required.
|
|
|
|
for (int i = 0; i < Material::TextureSemanticMax; i++)
|
|
|
|
{
|
2021-11-23 14:11:13 -07:00
|
|
|
if (m->maps[i] != InvalidResource)
|
2010-02-05 13:10:32 -07:00
|
|
|
{
|
|
|
|
textureUsage[i] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
materials.push_back(m);
|
|
|
|
return materials.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int
|
|
|
|
Model::getMaterialCount() const
|
|
|
|
{
|
|
|
|
return materials.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int
|
|
|
|
Model::getVertexCount() const
|
|
|
|
{
|
|
|
|
unsigned int count = 0;
|
|
|
|
|
2018-09-22 07:13:49 -06:00
|
|
|
for (const auto mesh : meshes)
|
|
|
|
count += mesh->getVertexCount();
|
2010-02-05 13:10:32 -07:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int
|
|
|
|
Model::getPrimitiveCount() const
|
|
|
|
{
|
|
|
|
unsigned int count = 0;
|
|
|
|
|
2018-09-22 07:13:49 -06:00
|
|
|
for (const auto mesh : meshes)
|
|
|
|
count += mesh->getPrimitiveCount();
|
2010-02-05 13:10:32 -07:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int
|
|
|
|
Model::getMeshCount() const
|
|
|
|
{
|
|
|
|
return meshes.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Mesh*
|
|
|
|
Model::getMesh(unsigned int index) const
|
|
|
|
{
|
|
|
|
if (index < meshes.size())
|
|
|
|
return meshes[index];
|
|
|
|
else
|
2018-09-22 07:13:49 -06:00
|
|
|
return nullptr;
|
2010-02-05 13:10:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int
|
|
|
|
Model::addMesh(Mesh* m)
|
|
|
|
{
|
|
|
|
meshes.push_back(m);
|
|
|
|
return meshes.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
2010-02-10 13:52:43 -07:00
|
|
|
Model::pick(const Eigen::Vector3d& rayOrigin,
|
|
|
|
const Eigen::Vector3d& rayDirection,
|
|
|
|
Mesh::PickResult* result) const
|
2010-02-05 13:10:32 -07:00
|
|
|
{
|
|
|
|
double maxDistance = 1.0e30;
|
|
|
|
double closest = maxDistance;
|
2010-02-10 13:52:43 -07:00
|
|
|
Mesh::PickResult closestResult;
|
2010-02-05 13:10:32 -07:00
|
|
|
|
2018-09-22 07:13:49 -06:00
|
|
|
for (const auto mesh : meshes)
|
2010-02-05 13:10:32 -07:00
|
|
|
{
|
2010-02-10 13:52:43 -07:00
|
|
|
Mesh::PickResult result;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (mesh->pick(rayOrigin, rayDirection, &result))
|
2010-02-05 13:10:32 -07:00
|
|
|
{
|
2010-02-10 13:52:43 -07:00
|
|
|
if (result.distance < closest)
|
|
|
|
{
|
|
|
|
closestResult = result;
|
2018-09-22 07:13:49 -06:00
|
|
|
closestResult.mesh = mesh;
|
2010-02-10 13:52:43 -07:00
|
|
|
closest = result.distance;
|
|
|
|
}
|
2010-02-05 13:10:32 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (closest != maxDistance)
|
|
|
|
{
|
2018-09-22 07:13:49 -06:00
|
|
|
if (result != nullptr)
|
2010-02-10 13:52:43 -07:00
|
|
|
{
|
|
|
|
*result = closestResult;
|
|
|
|
}
|
2010-02-05 13:10:32 -07:00
|
|
|
return true;
|
|
|
|
}
|
2010-02-10 13:52:43 -07:00
|
|
|
|
2018-09-22 07:13:49 -06:00
|
|
|
return false;
|
2010-02-10 13:52:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
Model::pick(const Vector3d& rayOrigin, const Vector3d& rayDirection, double& distance) const
|
|
|
|
{
|
|
|
|
Mesh::PickResult result;
|
|
|
|
bool hit = pick(rayOrigin, rayDirection, &result);
|
|
|
|
if (hit)
|
|
|
|
{
|
|
|
|
distance = result.distance;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hit;
|
2010-02-05 13:10:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*! Translate and scale a model. The transformation applied to
|
|
|
|
* each vertex in the model is:
|
|
|
|
* v' = (v + translation) * scale
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
Model::transform(const Vector3f& translation, float scale)
|
|
|
|
{
|
2018-09-22 07:13:49 -06:00
|
|
|
for (const auto mesh : meshes)
|
|
|
|
mesh->transform(translation, scale);
|
2010-02-05 13:10:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
Model::normalize(const Vector3f& centerOffset)
|
|
|
|
{
|
|
|
|
AlignedBox<float, 3> bbox;
|
|
|
|
|
2018-09-22 07:13:49 -06:00
|
|
|
for (const auto mesh : meshes)
|
|
|
|
bbox.extend(mesh->getBoundingBox());
|
2010-02-05 13:10:32 -07:00
|
|
|
|
|
|
|
Vector3f center = (bbox.min() + bbox.max()) * 0.5f + centerOffset;
|
|
|
|
Vector3f extents = bbox.max() - bbox.min();
|
|
|
|
float maxExtent = extents.maxCoeff();
|
|
|
|
|
|
|
|
transform(-center, 2.0f / maxExtent);
|
|
|
|
|
|
|
|
normalized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
operator<(const Material::Color& c0, const Material::Color& c1)
|
|
|
|
{
|
|
|
|
if (c0.red() < c1.red())
|
|
|
|
return true;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (c0.red() > c1.red())
|
2010-02-05 13:10:32 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (c0.green() < c1.green())
|
|
|
|
return true;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (c0.green() > c1.green())
|
2010-02-05 13:10:32 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return c0.blue() < c1.blue();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Define an ordering for materials; required for elimination of duplicate
|
|
|
|
// materials.
|
|
|
|
static bool
|
|
|
|
operator<(const Material& m0, const Material& m1)
|
|
|
|
{
|
|
|
|
// Checking opacity first and doing it backwards is deliberate. It means
|
|
|
|
// that after sorting, translucent materials will end up with higher
|
|
|
|
// material indices than opaque ones. Ultimately, after sorting
|
|
|
|
// mesh primitive groups by material, translucent groups will end up
|
|
|
|
// rendered after opaque ones.
|
|
|
|
if (m0.opacity < m1.opacity)
|
|
|
|
return true;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (m0.opacity > m1.opacity)
|
2010-02-05 13:10:32 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// Reverse sense of comparison here--additive blending is 1, normal
|
|
|
|
// blending is 0, and we'd prefer to render additively blended submeshes
|
|
|
|
// last.
|
|
|
|
if (m0.blend > m1.blend)
|
|
|
|
return true;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (m0.blend < m1.blend)
|
2010-02-05 13:10:32 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (m0.diffuse < m1.diffuse)
|
|
|
|
return true;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (m1.diffuse < m0.diffuse)
|
2010-02-05 13:10:32 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (m0.emissive < m1.emissive)
|
|
|
|
return true;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (m1.emissive < m0.emissive)
|
2010-02-05 13:10:32 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (m0.specular < m1.specular)
|
|
|
|
return true;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (m1.specular < m0.specular)
|
2010-02-05 13:10:32 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (m0.specularPower < m1.specularPower)
|
|
|
|
return true;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (m0.specularPower > m1.specularPower)
|
2010-02-05 13:10:32 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < Material::TextureSemanticMax; i++)
|
|
|
|
{
|
|
|
|
if (m0.maps[i] < m1.maps[i])
|
|
|
|
return true;
|
2018-09-22 07:13:49 -06:00
|
|
|
if (m0.maps[i] > m1.maps[i])
|
2010-02-05 13:10:32 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Materials are identical
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Simple struct that pairs an index with a material; the index is used to
|
|
|
|
// keep track of the original material index after sorting.
|
|
|
|
struct IndexedMaterial
|
|
|
|
{
|
|
|
|
int originalIndex;
|
|
|
|
const Material* material;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
operator<(const IndexedMaterial& im0, const IndexedMaterial& im1)
|
|
|
|
{
|
|
|
|
return *(im0.material) < *(im1.material);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
operator!=(const IndexedMaterial& im0, const IndexedMaterial& im1)
|
|
|
|
{
|
|
|
|
return im0 < im1 || im1 < im0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
Model::uniquifyMaterials()
|
|
|
|
{
|
|
|
|
// No work to do if there's just a single material
|
|
|
|
if (materials.size() <= 1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Create an array of materials with the indices attached
|
|
|
|
vector<IndexedMaterial> indexedMaterials;
|
|
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < materials.size(); i++)
|
|
|
|
{
|
|
|
|
IndexedMaterial im;
|
|
|
|
im.originalIndex = i;
|
|
|
|
im.material = materials[i];
|
|
|
|
indexedMaterials.push_back(im);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort the indexed materials so that we can uniquify them
|
|
|
|
sort(indexedMaterials.begin(), indexedMaterials.end());
|
|
|
|
|
|
|
|
vector<const Material*> uniqueMaterials;
|
|
|
|
vector<unsigned int> materialMap(materials.size());
|
|
|
|
vector<unsigned int> duplicateMaterials;
|
|
|
|
|
|
|
|
// From the sorted material list construct the list of unique materials
|
|
|
|
// and a map to convert old material indices into indices that can be
|
|
|
|
// used with the uniquified material list.
|
|
|
|
unsigned int uniqueMaterialCount = 0;
|
|
|
|
for (i = 0; i < indexedMaterials.size(); i++)
|
|
|
|
{
|
|
|
|
if (i == 0 || indexedMaterials[i] != indexedMaterials[i - 1])
|
|
|
|
{
|
|
|
|
uniqueMaterialCount++;
|
|
|
|
uniqueMaterials.push_back(indexedMaterials[i].material);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
duplicateMaterials.push_back(i);
|
|
|
|
}
|
|
|
|
materialMap[indexedMaterials[i].originalIndex] = uniqueMaterialCount - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remap all the material indices in the model. Even if no materials have
|
|
|
|
// been eliminated we've still sorted them by opacity, which is useful
|
|
|
|
// when reordering meshes so that translucent ones are rendered last.
|
2018-09-22 07:13:49 -06:00
|
|
|
for (const auto mesh : meshes)
|
|
|
|
mesh->remapMaterials(materialMap);
|
2010-02-05 13:10:32 -07:00
|
|
|
|
2018-09-22 07:13:49 -06:00
|
|
|
for (const auto i : duplicateMaterials)
|
|
|
|
delete indexedMaterials[i].material;
|
2010-02-05 13:10:32 -07:00
|
|
|
|
|
|
|
materials = uniqueMaterials;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
Model::determineOpacity()
|
|
|
|
{
|
|
|
|
for (unsigned int i = 0; i < materials.size(); i++)
|
|
|
|
{
|
|
|
|
if ((materials[i]->opacity > 0.01f && materials[i]->opacity < 1.0f) ||
|
|
|
|
materials[i]->blend == Material::AdditiveBlend)
|
|
|
|
{
|
|
|
|
opaque = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
opaque = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
Model::usesTextureType(Material::TextureSemantic t) const
|
|
|
|
{
|
|
|
|
return textureUsage[static_cast<int>(t)];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-11-30 03:07:21 -07:00
|
|
|
class MeshComparatorAdapter
|
2010-02-05 13:10:32 -07:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
MeshComparatorAdapter(const Model::MeshComparator& c) :
|
|
|
|
comparator(c)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(const Mesh* a, const Mesh* b) const
|
|
|
|
{
|
|
|
|
return comparator(*a, *b);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
const Model::MeshComparator& comparator;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Look at the material used by last primitive group in the mesh for the
|
|
|
|
// opacity of the whole model. This is a very crude way to check the opacity
|
|
|
|
// of a mesh and misses many cases.
|
|
|
|
static unsigned int
|
|
|
|
getMeshMaterialIndex(const Mesh& mesh)
|
|
|
|
{
|
|
|
|
if (mesh.getGroupCount() == 0)
|
|
|
|
return 0;
|
2018-09-22 07:13:49 -06:00
|
|
|
|
|
|
|
return mesh.getGroup(mesh.getGroupCount() - 1)->materialIndex;
|
2010-02-05 13:10:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
Model::OpacityComparator::operator()(const Mesh& a, const Mesh& b) const
|
|
|
|
{
|
|
|
|
// Because materials are sorted by opacity, we can just compare
|
|
|
|
// the material index.
|
|
|
|
return getMeshMaterialIndex(a) > getMeshMaterialIndex(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
Model::sortMeshes(const MeshComparator& comparator)
|
|
|
|
{
|
|
|
|
// Sort submeshes by material; if materials have been uniquified,
|
|
|
|
// then the submeshes will be ordered so that opaque ones are first.
|
2018-09-22 07:13:49 -06:00
|
|
|
for (const auto mesh : meshes)
|
|
|
|
mesh->aggregateByMaterial();
|
2010-02-05 13:10:32 -07:00
|
|
|
|
|
|
|
// Sort the meshes so that completely opaque ones are first
|
|
|
|
sort(meshes.begin(), meshes.end(), MeshComparatorAdapter(comparator));
|
|
|
|
}
|