gltfio: Move the API for recomputeBoundingBoxes.
Prior to this change, `recomputeBoundingBoxes` was an opt-in config parameter in ResourceLoader. It is now a method on FilamentInstance. The old API did not work for dynamically created instances. Since this is a relatively obscure feature, we considered removing it completely, especially since the computation requires the presence of CPU-side vertex data combined with the transform hierarchy. Instead of removing the feature, we decided to move it to a better place. This paves the way for some upcoming improvements, which include reducing the memory footprint for assets. It also improves overall code organization and separation of concerns.
This commit is contained in:
@@ -7,6 +7,7 @@ A new header is inserted each time a *tag* is created.
|
||||
|
||||
- WebGL: reduce max instance count to work around Chrome issues [⚠️ **Recompile Materials**]
|
||||
- engine: rework material/shader sampler binding code [⚠️ **Recompile Materials**]
|
||||
- gltfio: move the API for `recomputeBoundingBoxes` [⚠️ **API Change**]
|
||||
|
||||
## v1.26.0
|
||||
|
||||
|
||||
+1
-3
@@ -73,8 +73,6 @@ class ModelViewer(
|
||||
get() = resourceLoader.asyncGetLoadProgress()
|
||||
|
||||
var normalizeSkinningWeights = true
|
||||
var recomputeBoundingBoxes = false
|
||||
var ignoreBindTransform = false
|
||||
|
||||
var cameraFocalLength = 28f
|
||||
set(value) {
|
||||
@@ -116,7 +114,7 @@ class ModelViewer(
|
||||
|
||||
materialProvider = UbershaderProvider(engine)
|
||||
assetLoader = AssetLoader(engine, materialProvider, EntityManager.get())
|
||||
resourceLoader = ResourceLoader(engine, normalizeSkinningWeights, recomputeBoundingBoxes, ignoreBindTransform)
|
||||
resourceLoader = ResourceLoader(engine, normalizeSkinningWeights)
|
||||
|
||||
// Always add a direct light source since it is required for shadowing.
|
||||
// We highly recommend adding an indirect light as well.
|
||||
|
||||
@@ -36,11 +36,9 @@ static void destroy(void*, size_t, void *userData) {
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_gltfio_ResourceLoader_nCreateResourceLoader(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jboolean normalizeSkinningWeights, jboolean recomputeBoundingBoxes,
|
||||
jboolean ignoreBindTransform) {
|
||||
jlong nativeEngine, jboolean normalizeSkinningWeights) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jlong) new ResourceLoader({ engine, {}, (bool) normalizeSkinningWeights,
|
||||
(bool) recomputeBoundingBoxes, (bool) ignoreBindTransform});
|
||||
return (jlong) new ResourceLoader({ engine, {}, (bool) normalizeSkinningWeights});
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
|
||||
+4
-8
@@ -47,7 +47,7 @@ public class ResourceLoader {
|
||||
*/
|
||||
public ResourceLoader(@NonNull Engine engine) {
|
||||
long nativeEngine = engine.getNativeObject();
|
||||
mNativeObject = nCreateResourceLoader(nativeEngine, false, false, false);
|
||||
mNativeObject = nCreateResourceLoader(nativeEngine, false);
|
||||
mNativeStbProvider = nCreateStbProvider(nativeEngine);
|
||||
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
|
||||
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
|
||||
@@ -60,16 +60,12 @@ public class ResourceLoader {
|
||||
*
|
||||
* @param engine the engine that gets passed to all builder methods
|
||||
* @param normalizeSkinningWeights scale non-conformant skinning weights so they sum to 1
|
||||
* @param recomputeBoundingBoxes use computed bounding boxes rather than the ones in the asset
|
||||
* @param ignoreBindTransform ignore skinned primitives bind transform when compute bounding boxes
|
||||
* @throws IllegalAccessException
|
||||
* @throws InvocationTargetException
|
||||
*/
|
||||
public ResourceLoader(@NonNull Engine engine, boolean normalizeSkinningWeights,
|
||||
boolean recomputeBoundingBoxes, boolean ignoreBindTransform) {
|
||||
public ResourceLoader(@NonNull Engine engine, boolean normalizeSkinningWeights) {
|
||||
long nativeEngine = engine.getNativeObject();
|
||||
mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights,
|
||||
recomputeBoundingBoxes, ignoreBindTransform);
|
||||
mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights);
|
||||
mNativeStbProvider = nCreateStbProvider(nativeEngine);
|
||||
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
|
||||
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
|
||||
@@ -181,7 +177,7 @@ public class ResourceLoader {
|
||||
}
|
||||
|
||||
private static native long nCreateResourceLoader(long nativeEngine,
|
||||
boolean normalizeSkinningWeights, boolean recomputeBoundingBoxes, boolean ignoreBindTransform);
|
||||
boolean normalizeSkinningWeights);
|
||||
private static native void nDestroyResourceLoader(long nativeLoader);
|
||||
private static native void nAddResourceData(long nativeLoader, String url, Buffer buffer,
|
||||
int remaining);
|
||||
|
||||
@@ -133,7 +133,7 @@ const float kSensitivity = 100.0f;
|
||||
NameComponentManager* ncm = new NameComponentManager(em);
|
||||
_assetLoader = AssetLoader::create({_engine, _materialProvider, ncm, &em});
|
||||
_resourceLoader = new ResourceLoader(
|
||||
{.engine = _engine, .normalizeSkinningWeights = true, .recomputeBoundingBoxes = false});
|
||||
{.engine = _engine, .normalizeSkinningWeights = true});
|
||||
_stbDecoder = createStbProvider(_engine);
|
||||
_ktxDecoder = createKtx2Provider(_engine);
|
||||
_resourceLoader->addTextureProvider("image/png", _stbDecoder);
|
||||
|
||||
@@ -141,8 +141,7 @@ void App::setupMesh() {
|
||||
|
||||
filament::gltfio::ResourceLoader({
|
||||
.engine = engine,
|
||||
.normalizeSkinningWeights = true,
|
||||
.recomputeBoundingBoxes = false
|
||||
.normalizeSkinningWeights = true
|
||||
}).loadResources(app.asset);
|
||||
|
||||
scene->addEntities(app.asset->getEntities(), app.asset->getEntityCount());
|
||||
|
||||
@@ -318,6 +318,11 @@ public:
|
||||
*/
|
||||
void detachMaterialInstances();
|
||||
|
||||
/**
|
||||
* Convenience function to get the first instance (which always exists).
|
||||
*/
|
||||
FilamentInstance* getInstance() noexcept { return getAssetInstances()[0]; }
|
||||
|
||||
/*! \cond PRIVATE */
|
||||
|
||||
FilamentInstance** getAssetInstances() noexcept;
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Entity.h>
|
||||
|
||||
#include <filament/Box.h>
|
||||
|
||||
namespace filament::gltfio {
|
||||
|
||||
class Animator;
|
||||
@@ -109,6 +111,24 @@ public:
|
||||
* This is a no-op if the given skin index or target is invalid.
|
||||
*/
|
||||
void detachSkin(size_t skinIndex, utils::Entity target) noexcept;
|
||||
|
||||
/**
|
||||
* Resets the AABB on all renderables by manually computing the bounding box.
|
||||
*
|
||||
* THIS IS ONLY USEFUL FOR MALFORMED ASSETS THAT DO NOT HAVE MIN/MAX SET UP CORRECTLY.
|
||||
*
|
||||
* Does not affect the return value of getBoundingBox() on the owning asset.
|
||||
* Cannot be called after releaseSourceData() on the owning asset.
|
||||
* Can only be called after loadResources() or asyncBeginLoad().
|
||||
*/
|
||||
void recomputeBoundingBoxes();
|
||||
|
||||
/**
|
||||
* Gets the axis-aligned bounding box from the supplied min / max values in glTF accessors.
|
||||
*
|
||||
* If recomputeBoundingBoxes() has been called, then this returns the recomputed AABB.
|
||||
*/
|
||||
Aabb getBoundingBox() const noexcept;
|
||||
};
|
||||
|
||||
} // namespace filament::gltfio
|
||||
|
||||
@@ -49,14 +49,6 @@ struct ResourceConfiguration {
|
||||
//! If true, adjusts skinning weights to sum to 1. Well formed glTF files do not need this,
|
||||
//! but it is useful for robustness.
|
||||
bool normalizeSkinningWeights;
|
||||
|
||||
//! If true, computes the bounding boxes of all \c POSITION attibutes. Well formed glTF files
|
||||
//! do not need this, but it is useful for robustness.
|
||||
bool recomputeBoundingBoxes;
|
||||
|
||||
//! If true, ignores skinning when computing bounding boxes. Implicitly true for instanced
|
||||
//! assets. Only applicable when recomputeBoundingBoxes is set to true.
|
||||
bool ignoreBindTransform;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -162,7 +154,6 @@ public:
|
||||
private:
|
||||
bool loadResources(FFilamentAsset* asset, bool async);
|
||||
void normalizeSkinningWeights(FFilamentAsset* asset) const;
|
||||
void updateBoundingBoxes(FFilamentAsset* asset) const;
|
||||
AssetPool* mPool;
|
||||
struct Impl;
|
||||
Impl* pImpl;
|
||||
|
||||
@@ -373,6 +373,8 @@ FFilamentInstance* FAssetLoader::createInstance(FFilamentAsset* primary,
|
||||
for (const auto& pair : mRootNodes) {
|
||||
createEntity(srcAsset, pair.first, pair.second, instanceRoot, false, instance);
|
||||
}
|
||||
|
||||
instance->boundingBox = primary->mBoundingBox;
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -539,8 +541,7 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node
|
||||
}
|
||||
|
||||
// Per the spec, glTF models must have valid mix / max annotations for position attributes.
|
||||
// However in practice these can be missing and we should be as robust as other glTF viewers.
|
||||
// If desired, clients can enable the "recomputeBoundingBoxes" feature in ResourceLoader.
|
||||
// If desired, clients can call "recomputeBoundingBoxes()" in FilamentInstance.
|
||||
Box box = Box().set(aabb.min, aabb.max);
|
||||
if (box.isEmpty()) {
|
||||
slog.w << "Missing bounding box in " << name << io::endl;
|
||||
|
||||
@@ -80,6 +80,7 @@ struct FFilamentInstance : public FilamentInstance {
|
||||
FFilamentAsset* owner;
|
||||
SkinVector skins;
|
||||
NodeMap nodeMap;
|
||||
filament::Aabb boundingBox;
|
||||
void createAnimator();
|
||||
Animator* getAnimator() const noexcept;
|
||||
size_t getSkinCount() const noexcept;
|
||||
@@ -89,6 +90,7 @@ struct FFilamentInstance : public FilamentInstance {
|
||||
void attachSkin(size_t skinIndex, utils::Entity target) noexcept;
|
||||
void detachSkin(size_t skinIndex, utils::Entity target) noexcept;
|
||||
void applyMaterialVariant(size_t variantIndex) noexcept;
|
||||
void recomputeBoundingBoxes();
|
||||
};
|
||||
|
||||
FILAMENT_UPCAST(FilamentInstance)
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
|
||||
#include <gltfio/Animator.h>
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::math;
|
||||
using namespace utils;
|
||||
|
||||
namespace filament::gltfio {
|
||||
@@ -87,6 +89,189 @@ void FFilamentInstance::applyMaterialVariant(size_t variantIndex) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
void FFilamentInstance::recomputeBoundingBoxes() {
|
||||
ASSERT_PRECONDITION(owner->mSourceAsset,
|
||||
"Do not call releaseSourceData before recomputeBoundingBoxes");
|
||||
|
||||
ASSERT_PRECONDITION(owner->mResourcesLoaded,
|
||||
"Do not call recomputeBoundingBoxes before loadResources or asyncBeginLoad");
|
||||
|
||||
auto& rm = owner->mEngine->getRenderableManager();
|
||||
auto& tm = owner->mEngine->getTransformManager();
|
||||
|
||||
// The purpose of the root node is to give the client a place for custom transforms.
|
||||
// Since it is not part of the source model, it should be ignored when computing the
|
||||
// bounding box.
|
||||
TransformManager::Instance root = tm.getInstance(owner->getRoot());
|
||||
utils::FixedCapacityVector<Entity> modelRoots(tm.getChildCount(root));
|
||||
tm.getChildren(root, modelRoots.data(), modelRoots.size());
|
||||
for (auto e : modelRoots) {
|
||||
tm.setParent(tm.getInstance(e), 0);
|
||||
}
|
||||
|
||||
auto computeBoundingBox = [](const cgltf_primitive* prim) -> Aabb {
|
||||
Aabb aabb;
|
||||
for (cgltf_size slot = 0; slot < prim->attributes_count; slot++) {
|
||||
const cgltf_attribute& attr = prim->attributes[slot];
|
||||
const cgltf_accessor* accessor = attr.data;
|
||||
const size_t dim = cgltf_num_components(accessor->type);
|
||||
if (attr.type == cgltf_attribute_type_position && dim >= 3) {
|
||||
utils::FixedCapacityVector<float> unpacked(accessor->count * dim);
|
||||
cgltf_accessor_unpack_floats(accessor, unpacked.data(), unpacked.size());
|
||||
for (cgltf_size i = 0, j = 0, n = accessor->count; i < n; ++i, j += dim) {
|
||||
float3 pt(unpacked[j + 0], unpacked[j + 1], unpacked[j + 2]);
|
||||
aabb.min = min(aabb.min, pt);
|
||||
aabb.max = max(aabb.max, pt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return aabb;
|
||||
};
|
||||
|
||||
struct Prim {
|
||||
cgltf_primitive const* prim;
|
||||
Skin const* skin;
|
||||
Entity node;
|
||||
};
|
||||
|
||||
auto computeBoundingBoxSkinned = [&tm](const Prim& prim) -> Aabb {
|
||||
FixedCapacityVector<float3> verts;
|
||||
FixedCapacityVector<uint4> joints;
|
||||
FixedCapacityVector<float4> weights;
|
||||
for (cgltf_size slot = 0, n = prim.prim->attributes_count; slot < n; ++slot) {
|
||||
const cgltf_attribute& attr = prim.prim->attributes[slot];
|
||||
const cgltf_accessor& accessor = *attr.data;
|
||||
switch (attr.type) {
|
||||
case cgltf_attribute_type_position:
|
||||
verts = FixedCapacityVector<float3>(accessor.count);
|
||||
cgltf_accessor_unpack_floats(&accessor, &verts.data()->x, accessor.count * 3);
|
||||
break;
|
||||
case cgltf_attribute_type_joints: {
|
||||
FixedCapacityVector<float4> tmp(accessor.count);
|
||||
cgltf_accessor_unpack_floats(&accessor, &tmp.data()->x, accessor.count * 4);
|
||||
joints = FixedCapacityVector<uint4>(accessor.count);
|
||||
for (size_t i = 0, n = accessor.count; i < n; ++i) {
|
||||
joints[i] = uint4(tmp[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cgltf_attribute_type_weights:
|
||||
weights = FixedCapacityVector<float4>(accessor.count);
|
||||
cgltf_accessor_unpack_floats(&accessor, &weights.data()->x, accessor.count * 4);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Aabb aabb;
|
||||
TransformManager::Instance transformable = tm.getInstance(prim.node);
|
||||
const mat4f inverseGlobalTransform = inverse(tm.getWorldTransform(transformable));
|
||||
for (size_t i = 0, n = verts.size(); i < n; i++) {
|
||||
mat4f tmp = mat4f(0.0f);
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
size_t jointIndex = joints[i][j];
|
||||
Entity jointEntity = prim.skin->joints[jointIndex];
|
||||
mat4f globalJointTransform = tm.getWorldTransform(tm.getInstance(jointEntity));
|
||||
mat4f inverseBindMatrix = prim.skin->inverseBindMatrices[jointIndex];
|
||||
tmp += weights[i][j] * globalJointTransform * inverseBindMatrix;
|
||||
}
|
||||
const mat4f skinMatrix = inverseGlobalTransform * tmp;
|
||||
|
||||
const float3 point = verts[i];
|
||||
|
||||
// NOTE: Filament's vertex shader assumes that last row is [0,0,0,1]
|
||||
// so we make the same assumption in the following transformation.
|
||||
|
||||
const float3 skinnedPoint =
|
||||
point.x * skinMatrix[0].xyz +
|
||||
point.y * skinMatrix[1].xyz +
|
||||
point.z * skinMatrix[2].xyz +
|
||||
skinMatrix[3].xyz;
|
||||
|
||||
aabb.min = min(aabb.min, skinnedPoint);
|
||||
aabb.max = max(aabb.max, skinnedPoint);
|
||||
}
|
||||
return aabb;
|
||||
};
|
||||
|
||||
// Collect all mesh primitives that we wish to find bounds for. For each mesh primitive, we also
|
||||
// collect the skin it is bound to (nullptr if not skinned) for bounds computation.
|
||||
size_t primCount = 0;
|
||||
for (auto iter : nodeMap) {
|
||||
const cgltf_mesh* mesh = iter.first->mesh;
|
||||
if (mesh) {
|
||||
primCount += mesh->primitives_count;
|
||||
}
|
||||
}
|
||||
auto primitives = FixedCapacityVector<Prim>::with_capacity(primCount);
|
||||
const cgltf_skin* baseSkin = &owner->mSourceAsset->hierarchy->skins[0];
|
||||
for (auto iter : nodeMap) {
|
||||
const cgltf_mesh* mesh = iter.first->mesh;
|
||||
if (mesh) {
|
||||
for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) {
|
||||
primitives.push_back({&mesh->primitives[index], nullptr, iter.second});
|
||||
}
|
||||
if (cgltf_skin* const skin = iter.first->skin; skin) {
|
||||
primitives.back().skin = &skins[skin - baseSkin];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kick off a bounding box job for every primitive.
|
||||
FixedCapacityVector<Aabb> bounds(primitives.size());
|
||||
JobSystem* js = &owner->mEngine->getJobSystem();
|
||||
JobSystem::Job* parent = js->createJob();
|
||||
for (size_t i = 0; i < primitives.size(); ++i) {
|
||||
Aabb* result = &bounds[i];
|
||||
if (primitives[i].skin == nullptr) {
|
||||
cgltf_primitive const* prim = primitives[i].prim;
|
||||
js->run(jobs::createJob(*js, parent, [prim, result, computeBoundingBox] {
|
||||
*result = computeBoundingBox(prim);
|
||||
}));
|
||||
} else {
|
||||
const Prim& prim = primitives[i];
|
||||
js->run(jobs::createJob(*js, parent, [&prim, result, computeBoundingBoxSkinned] {
|
||||
*result = computeBoundingBoxSkinned(prim);
|
||||
}));
|
||||
}
|
||||
}
|
||||
js->runAndWait(parent);
|
||||
|
||||
// Compute the asset-level bounding box.
|
||||
size_t primIndex = 0;
|
||||
Aabb assetBounds;
|
||||
for (auto iter : nodeMap) {
|
||||
const cgltf_mesh* mesh = iter.first->mesh;
|
||||
if (mesh) {
|
||||
// Find the object-space bounds for the renderable by unioning the bounds of each prim.
|
||||
Aabb aabb;
|
||||
for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) {
|
||||
Aabb primBounds = bounds[primIndex++];
|
||||
aabb.min = min(aabb.min, primBounds.min);
|
||||
aabb.max = max(aabb.max, primBounds.max);
|
||||
}
|
||||
auto renderable = rm.getInstance(iter.second);
|
||||
rm.setAxisAlignedBoundingBox(renderable, Box().set(aabb.min, aabb.max));
|
||||
|
||||
// Transform this bounding box, then update the asset-level bounding box.
|
||||
auto transformable = tm.getInstance(iter.second);
|
||||
const mat4f worldTransform = tm.getWorldTransform(transformable);
|
||||
const Aabb transformed = aabb.transform(worldTransform);
|
||||
assetBounds.min = min(assetBounds.min, transformed.min);
|
||||
assetBounds.max = max(assetBounds.max, transformed.max);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the root node.
|
||||
for (auto e : modelRoots) {
|
||||
tm.setParent(tm.getInstance(e), root);
|
||||
}
|
||||
|
||||
boundingBox = assetBounds;
|
||||
}
|
||||
|
||||
FilamentAsset* FilamentInstance::getAsset() const noexcept {
|
||||
return upcast(this)->owner;
|
||||
}
|
||||
@@ -136,5 +321,12 @@ void FilamentInstance::detachSkin(size_t skinIndex, Entity target) noexcept {
|
||||
return upcast(this)->detachSkin(skinIndex, target);
|
||||
}
|
||||
|
||||
void FilamentInstance::recomputeBoundingBoxes() {
|
||||
return upcast(this)->recomputeBoundingBoxes();
|
||||
}
|
||||
|
||||
Aabb FilamentInstance::getBoundingBox() const noexcept {
|
||||
return upcast(this)->boundingBox;
|
||||
}
|
||||
|
||||
} // namespace filament::gltfio
|
||||
|
||||
@@ -67,15 +67,11 @@ struct ResourceLoader::Impl {
|
||||
Impl(const ResourceConfiguration& config) :
|
||||
mEngine(config.engine),
|
||||
mNormalizeSkinningWeights(config.normalizeSkinningWeights),
|
||||
mRecomputeBoundingBoxes(config.recomputeBoundingBoxes),
|
||||
mGltfPath(config.gltfPath ? config.gltfPath : ""),
|
||||
mIgnoreBindTransform(config.ignoreBindTransform) {}
|
||||
mGltfPath(config.gltfPath ? config.gltfPath : "") {}
|
||||
|
||||
Engine* const mEngine;
|
||||
const bool mNormalizeSkinningWeights;
|
||||
const bool mRecomputeBoundingBoxes;
|
||||
const std::string mGltfPath;
|
||||
const bool mIgnoreBindTransform;
|
||||
|
||||
// User-provided resource data with URI string keys, populated with addResourceData().
|
||||
// This is used on platforms without traditional file systems, such as Android, iOS, and WebGL.
|
||||
@@ -446,10 +442,6 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
|
||||
}
|
||||
}
|
||||
|
||||
if (pImpl->mRecomputeBoundingBoxes) {
|
||||
updateBoundingBoxes(asset);
|
||||
}
|
||||
|
||||
Engine& engine = *pImpl->mEngine;
|
||||
|
||||
// Upload VertexBuffer and IndexBuffer data to the GPU.
|
||||
@@ -854,176 +846,4 @@ void ResourceLoader::normalizeSkinningWeights(FFilamentAsset* asset) const {
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceLoader::updateBoundingBoxes(FFilamentAsset* asset) const {
|
||||
SYSTRACE_CALL();
|
||||
auto& rm = pImpl->mEngine->getRenderableManager();
|
||||
auto& tm = pImpl->mEngine->getTransformManager();
|
||||
const NodeMap& nodeMap = asset->mInstances[0]->nodeMap;
|
||||
|
||||
// The purpose of the root node is to give the client a place for custom transforms.
|
||||
// Since it is not part of the source model, it should be ignored when computing the
|
||||
// bounding box.
|
||||
TransformManager::Instance root = tm.getInstance(asset->getRoot());
|
||||
std::vector<Entity> modelRoots(tm.getChildCount(root));
|
||||
tm.getChildren(root, modelRoots.data(), modelRoots.size());
|
||||
for (auto e : modelRoots) {
|
||||
tm.setParent(tm.getInstance(e), 0);
|
||||
}
|
||||
|
||||
auto computeBoundingBox = [](const cgltf_primitive* prim, Aabb* result) {
|
||||
Aabb aabb;
|
||||
for (cgltf_size slot = 0; slot < prim->attributes_count; slot++) {
|
||||
const cgltf_attribute& attr = prim->attributes[slot];
|
||||
const cgltf_accessor* accessor = attr.data;
|
||||
const size_t dim = cgltf_num_components(accessor->type);
|
||||
if (attr.type == cgltf_attribute_type_position && dim >= 3) {
|
||||
std::vector<float> unpacked(accessor->count * dim);
|
||||
cgltf_accessor_unpack_floats(accessor, unpacked.data(), unpacked.size());
|
||||
for (cgltf_size i = 0, j = 0, n = accessor->count; i < n; ++i, j += dim) {
|
||||
float3 pt(unpacked[j + 0], unpacked[j + 1], unpacked[j + 2]);
|
||||
aabb.min = min(aabb.min, pt);
|
||||
aabb.max = max(aabb.max, pt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
*result = aabb;
|
||||
};
|
||||
|
||||
struct Prim {
|
||||
cgltf_primitive const* prim;
|
||||
Skin const* skin;
|
||||
Entity node;
|
||||
};
|
||||
|
||||
auto computeBoundingBoxSkinned = [&](const Prim& prim, Aabb* result) {
|
||||
FixedCapacityVector<float3> verts;
|
||||
FixedCapacityVector<uint4> joints;
|
||||
FixedCapacityVector<float4> weights;
|
||||
for (cgltf_size slot = 0, n = prim.prim->attributes_count; slot < n; ++slot) {
|
||||
const cgltf_attribute& attr = prim.prim->attributes[slot];
|
||||
const cgltf_accessor& accessor = *attr.data;
|
||||
switch (attr.type) {
|
||||
case cgltf_attribute_type_position:
|
||||
verts = FixedCapacityVector<float3>(accessor.count);
|
||||
cgltf_accessor_unpack_floats(&accessor, &verts.data()->x, accessor.count * 3);
|
||||
break;
|
||||
case cgltf_attribute_type_joints: {
|
||||
FixedCapacityVector<float4> tmp(accessor.count);
|
||||
cgltf_accessor_unpack_floats(&accessor, &tmp.data()->x, accessor.count * 4);
|
||||
joints = FixedCapacityVector<uint4>(accessor.count);
|
||||
for (size_t i = 0, n = accessor.count; i < n; ++i) {
|
||||
joints[i] = uint4(tmp[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cgltf_attribute_type_weights:
|
||||
weights = FixedCapacityVector<float4>(accessor.count);
|
||||
cgltf_accessor_unpack_floats(&accessor, &weights.data()->x, accessor.count * 4);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Aabb aabb;
|
||||
TransformManager::Instance transformable = tm.getInstance(prim.node);
|
||||
const mat4f inverseGlobalTransform = inverse(tm.getWorldTransform(transformable));
|
||||
for (size_t i = 0, n = verts.size(); i < n; i++) {
|
||||
float3 point = verts[i];
|
||||
mat4f tmp = mat4f(0.0f);
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
size_t jointIndex = joints[i][j];
|
||||
Entity jointEntity = prim.skin->joints[jointIndex];
|
||||
mat4f globalJointTransform = tm.getWorldTransform(tm.getInstance(jointEntity));
|
||||
mat4f inverseBindMatrix = prim.skin->inverseBindMatrices[jointIndex];
|
||||
tmp += weights[i][j] * globalJointTransform * inverseBindMatrix;
|
||||
}
|
||||
mat4f skinMatrix = inverseGlobalTransform * tmp;
|
||||
if (!pImpl->mNormalizeSkinningWeights) {
|
||||
skinMatrix /= skinMatrix[3].w;
|
||||
}
|
||||
float3 skinnedPoint = (point.x * skinMatrix[0] +
|
||||
point.y * skinMatrix[1] + point.z * skinMatrix[2] + skinMatrix[3]).xyz;
|
||||
aabb.min = min(aabb.min, skinnedPoint);
|
||||
aabb.max = max(aabb.max, skinnedPoint);
|
||||
}
|
||||
*result = aabb;
|
||||
};
|
||||
|
||||
// Collect all mesh primitives that we wish to find bounds for. For each mesh primitive, we also
|
||||
// collect the skin it is bound to (nullptr if not skinned) for bounds computation.
|
||||
size_t primCount = 0;
|
||||
for (auto iter : nodeMap) {
|
||||
const cgltf_mesh* mesh = iter.first->mesh;
|
||||
if (mesh) {
|
||||
primCount += mesh->primitives_count;
|
||||
}
|
||||
}
|
||||
auto primitives = FixedCapacityVector<Prim>::with_capacity(primCount);
|
||||
const cgltf_skin* baseSkin = &asset->mSourceAsset->hierarchy->skins[0];
|
||||
for (auto iter : nodeMap) {
|
||||
const cgltf_mesh* mesh = iter.first->mesh;
|
||||
if (mesh) {
|
||||
for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) {
|
||||
primitives.push_back({&mesh->primitives[index], nullptr, iter.second});
|
||||
}
|
||||
if (cgltf_skin* const skin = iter.first->skin; skin) {
|
||||
primitives.back().skin = &asset->mInstances[0]->skins[skin - baseSkin];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kick off a bounding box job for every primitive.
|
||||
FixedCapacityVector<Aabb> bounds(primitives.size());
|
||||
JobSystem* js = &pImpl->mEngine->getJobSystem();
|
||||
JobSystem::Job* parent = js->createJob();
|
||||
for (size_t i = 0; i < primitives.size(); ++i) {
|
||||
Aabb* result = &bounds[i];
|
||||
if (pImpl->mIgnoreBindTransform || primitives[i].skin == nullptr) {
|
||||
cgltf_primitive const* prim = primitives[i].prim;
|
||||
js->run(jobs::createJob(*js, parent, [prim, result, computeBoundingBox] {
|
||||
computeBoundingBox(prim, result);
|
||||
}));
|
||||
} else {
|
||||
const Prim& prim = primitives[i];
|
||||
js->run(jobs::createJob(*js, parent, [&prim, result, computeBoundingBoxSkinned] {
|
||||
computeBoundingBoxSkinned(prim, result);
|
||||
}));
|
||||
}
|
||||
}
|
||||
js->runAndWait(parent);
|
||||
|
||||
// Compute the asset-level bounding box.
|
||||
size_t primIndex = 0;
|
||||
Aabb assetBounds;
|
||||
for (auto iter : nodeMap) {
|
||||
const cgltf_mesh* mesh = iter.first->mesh;
|
||||
if (mesh) {
|
||||
// Find the object-space bounds for the renderable by unioning the bounds of each prim.
|
||||
Aabb aabb;
|
||||
for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) {
|
||||
Aabb primBounds = bounds[primIndex++];
|
||||
aabb.min = min(aabb.min, primBounds.min);
|
||||
aabb.max = max(aabb.max, primBounds.max);
|
||||
}
|
||||
auto renderable = rm.getInstance(iter.second);
|
||||
rm.setAxisAlignedBoundingBox(renderable, Box().set(aabb.min, aabb.max));
|
||||
|
||||
// Transform this bounding box, then update the asset-level bounding box.
|
||||
auto transformable = tm.getInstance(iter.second);
|
||||
const mat4f worldTransform = tm.getWorldTransform(transformable);
|
||||
const Aabb transformed = aabb.transform(worldTransform);
|
||||
assetBounds.min = min(assetBounds.min, transformed.min);
|
||||
assetBounds.max = max(assetBounds.max, transformed.max);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto e : modelRoots) {
|
||||
tm.setParent(tm.getInstance(e), root);
|
||||
}
|
||||
|
||||
asset->mBoundingBox = assetBounds;
|
||||
}
|
||||
|
||||
} // namespace filament::gltfio
|
||||
|
||||
@@ -457,7 +457,7 @@ void ViewerGui::updateRootTransform() {
|
||||
auto root = tcm.getInstance(mAsset->getRoot());
|
||||
filament::math::mat4f transform;
|
||||
if (mSettings.viewer.autoScaleEnabled) {
|
||||
transform = fitIntoUnitCube(mAsset->getBoundingBox(), 4);
|
||||
transform = fitIntoUnitCube(mAsset->getInstance()->getBoundingBox(), 4);
|
||||
}
|
||||
tcm.setTransform(root, transform);
|
||||
}
|
||||
|
||||
@@ -211,7 +211,6 @@ int main(int argc, char** argv) {
|
||||
configuration.engine = app.engine;
|
||||
configuration.gltfPath = gltfPath.c_str();
|
||||
configuration.normalizeSkinningWeights = true;
|
||||
configuration.recomputeBoundingBoxes = false;
|
||||
if (!app.resourceLoader) {
|
||||
app.resourceLoader = new gltfio::ResourceLoader(configuration);
|
||||
app.stbDecoder = createStbProvider(app.engine);
|
||||
|
||||
+5
-10
@@ -89,7 +89,6 @@ struct App {
|
||||
gltfio::TextureProvider* stbDecoder = nullptr;
|
||||
gltfio::TextureProvider* ktxDecoder = nullptr;
|
||||
bool recomputeAabb = false;
|
||||
bool ignoreBindTransform = false;
|
||||
|
||||
bool actualSize = false;
|
||||
|
||||
@@ -139,8 +138,6 @@ static void printUsage(char* name) {
|
||||
" Do not scale the model to fit into a unit cube\n\n"
|
||||
" --recompute-aabb, -r\n"
|
||||
" Ignore the min/max attributes in the glTF file\n\n"
|
||||
" --ignore-bind-transform, -g\n"
|
||||
" Ignore bind transform when recomputing aabb (use with -r)\n\n"
|
||||
" --settings=<path to JSON file>, -t\n"
|
||||
" Apply the settings in the given JSON file\n\n"
|
||||
" --ubershader, -u\n"
|
||||
@@ -169,7 +166,7 @@ static std::ifstream::pos_type getFileSize(const char* filename) {
|
||||
}
|
||||
|
||||
static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
static constexpr const char* OPTSTR = "ha:i:usc:rgt:b:ev";
|
||||
static constexpr const char* OPTSTR = "ha:i:usc:rt:b:ev";
|
||||
static const struct option OPTIONS[] = {
|
||||
{ "help", no_argument, nullptr, 'h' },
|
||||
{ "api", required_argument, nullptr, 'a' },
|
||||
@@ -180,7 +177,6 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
{ "actual-size", no_argument, nullptr, 's' },
|
||||
{ "camera", required_argument, nullptr, 'c' },
|
||||
{ "recompute-aabb", no_argument, nullptr, 'r' },
|
||||
{ "ignore-bind-transform", no_argument, nullptr, 'g' },
|
||||
{ "settings", required_argument, nullptr, 't' },
|
||||
{ "split-view", no_argument, nullptr, 'v' },
|
||||
{ nullptr, 0, nullptr, 0 }
|
||||
@@ -229,9 +225,6 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
case 'r':
|
||||
app->recomputeAabb = true;
|
||||
break;
|
||||
case 'g':
|
||||
app->ignoreBindTransform = true;
|
||||
break;
|
||||
case 't':
|
||||
app->settingsFile = arg;
|
||||
break;
|
||||
@@ -429,8 +422,6 @@ int main(int argc, char** argv) {
|
||||
ResourceConfiguration configuration = {};
|
||||
configuration.engine = app.engine;
|
||||
configuration.gltfPath = gltfPath.c_str();
|
||||
configuration.recomputeBoundingBoxes = app.recomputeAabb;
|
||||
configuration.ignoreBindTransform = app.ignoreBindTransform;
|
||||
configuration.normalizeSkinningWeights = true;
|
||||
|
||||
if (!app.resourceLoader) {
|
||||
@@ -447,6 +438,10 @@ int main(int argc, char** argv) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (app.recomputeAabb) {
|
||||
app.asset->getInstance()->recomputeBoundingBoxes();
|
||||
}
|
||||
|
||||
app.asset->releaseSourceData();
|
||||
|
||||
auto ibl = FilamentApp::get().getIBL();
|
||||
|
||||
@@ -550,8 +550,7 @@ Filament.loadClassExtensions = function() {
|
||||
// The optional asyncInterval argument allows clients to control how decoding is amortized
|
||||
// over time. It represents the number of milliseconds between each texture decoding task.
|
||||
//
|
||||
// The optional config argument is an object with boolean fields `normalizeSkinningWeights` and
|
||||
// `recomputeBoundingBoxes`.
|
||||
// The optional config argument is an object with boolean field `normalizeSkinningWeights`.
|
||||
Filament.gltfio$FilamentAsset.prototype.loadResources = function(onDone, onFetched, basePath,
|
||||
asyncInterval, config) {
|
||||
const asset = this;
|
||||
@@ -559,8 +558,6 @@ Filament.loadClassExtensions = function() {
|
||||
const interval = asyncInterval || 30;
|
||||
const defaults = {
|
||||
normalizeSkinningWeights: true,
|
||||
recomputeBoundingBoxes: false,
|
||||
ignoreBindTransform: false
|
||||
};
|
||||
config = Object.assign(defaults, config || {});
|
||||
|
||||
@@ -586,9 +583,7 @@ Filament.loadClassExtensions = function() {
|
||||
|
||||
// Construct a resource loader and start decoding after all textures are fetched.
|
||||
const resourceLoader = new Filament.gltfio$ResourceLoader(engine,
|
||||
config.normalizeSkinningWeights,
|
||||
config.recomputeBoundingBoxes,
|
||||
config.ignoreBindTransform);
|
||||
config.normalizeSkinningWeights);
|
||||
|
||||
const stbProvider = new Filament.gltfio$StbProvider(engine);
|
||||
const ktx2Provider = new Filament.gltfio$Ktx2Provider(engine);
|
||||
|
||||
@@ -304,8 +304,6 @@ class FilamentViewer extends LitElement {
|
||||
|
||||
const config = {
|
||||
normalizeSkinningWeights: true,
|
||||
recomputeBoundingBoxes: false,
|
||||
ignoreBindTransform: false,
|
||||
asyncInterval: 30
|
||||
};
|
||||
|
||||
@@ -333,9 +331,7 @@ class FilamentViewer extends LitElement {
|
||||
this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
|
||||
|
||||
const resourceLoader = new Filament.gltfio$ResourceLoader(this.engine,
|
||||
config.normalizeSkinningWeights,
|
||||
config.recomputeBoundingBoxes,
|
||||
config.ignoreBindTransform);
|
||||
config.normalizeSkinningWeights);
|
||||
|
||||
const stbProvider = new Filament.gltfio$StbProvider(this.engine);
|
||||
const ktx2Provider = new Filament.gltfio$Ktx2Provider(this.engine);
|
||||
|
||||
@@ -1920,14 +1920,11 @@ class_<AssetLoader>("gltfio$AssetLoader")
|
||||
.function("destroyAsset", &AssetLoader::destroyAsset, allow_raw_pointers());
|
||||
|
||||
class_<ResourceLoader>("gltfio$ResourceLoader")
|
||||
.constructor(EMBIND_LAMBDA(ResourceLoader*, (Engine* engine, bool normalizeSkinningWeights,
|
||||
bool recomputeBoundingBoxes, bool ignoreBindTransform), {
|
||||
.constructor(EMBIND_LAMBDA(ResourceLoader*, (Engine* engine, bool normalizeSkinningWeights), {
|
||||
return new ResourceLoader({
|
||||
.engine = engine,
|
||||
.gltfPath = nullptr,
|
||||
.normalizeSkinningWeights = normalizeSkinningWeights,
|
||||
.recomputeBoundingBoxes = recomputeBoundingBoxes,
|
||||
.ignoreBindTransform = ignoreBindTransform
|
||||
.normalizeSkinningWeights = normalizeSkinningWeights
|
||||
});
|
||||
}), allow_raw_pointers())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user