This commit is contained in:
humbletim 2018-02-09 02:14:32 -05:00
parent 06afaa7470
commit a08770c816
9 changed files with 722 additions and 656 deletions

View file

@ -15,6 +15,9 @@
#include "OBJWriter.h"
#include "ModelFormatLogging.h"
// FIXME: should this live in shared? (it depends on gpu/)
#include <../graphics-scripting/src/graphics-scripting/BufferViewHelpers.h>
static QString formatFloat(double n) {
// limit precision to 6, but don't output trailing zeros.
QString s = QString::number(n, 'f', 6);
@ -91,7 +94,8 @@ bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes) {
const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(gpu::Stream::InputSlot::NORMAL);
gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements();
for (gpu::BufferView::Index i = 0; i < numNormals; i++) {
glm::vec3 normal = normalsBufferView.get<glm::vec3>(i);
glm::vec3 normal = glmVecFromVariant<glm::vec3>(bufferViewElementToVariant(normalsBufferView, i));
//glm::vec3 normal = normalsBufferView.get<glm::vec3>(i);
out << "vn ";
out << formatFloat(normal[0]) << " ";
out << formatFloat(normal[1]) << " ";

View file

@ -8,9 +8,15 @@
#include <gpu/Stream.h>
#include <glm/gtc/packing.hpp>
namespace glm {
using hvec2 = glm::tvec2<glm::detail::hdata>;
using hvec4 = glm::tvec4<glm::detail::hdata>;
}
//#define DEBUG_BUFFERVIEW_SCRIPTING
#ifdef DEBUG_BUFFERVIEW_SCRIPTING
#include "DebugNames.h"
QLoggingCategory bufferview_helpers{"hifi.bufferview"};
#endif
namespace {
@ -61,6 +67,9 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co
const auto dataType = element.getType();
const auto byteLength = element.getSize();
const auto BYTES_PER_ELEMENT = byteLength / vecN;
#ifdef DEBUG_BUFFERVIEW_SCRIPTING
qCDebug(bufferview_helpers) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN;
#endif
if (BYTES_PER_ELEMENT == 1) {
switch(vecN) {
case 2: setBufferViewElement<glm::u8vec2>(view, index, v); return true;
@ -71,16 +80,25 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co
glm::uint32 unused;
packNormalAndTangent(glmVecFromVariant<glm::vec3>(v), glm::vec3(), rawColor, unused);
view.edit<glm::uint32>(index) = rawColor;
return true;
} else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) {
glm::uint32 packedNormal;// = glm::packSnorm3x10_1x2(glm::vec4(glmVecFromVariant<glm::vec3>(v), 0.0f));
glm::uint32 unused;
packNormalAndTangent(glm::vec3(), glmVecFromVariant<glm::vec3>(v), unused, packedNormal);
view.edit<glm::uint32>(index) = packedNormal;
return true;
}
setBufferViewElement<glm::u8vec4>(view, index, v); return true;
}
}
} else if (BYTES_PER_ELEMENT == 2) {
if (dataType == gpu::HALF) {
switch(vecN) {
case 2: view.edit<glm::int16>(index) = glm::packSnorm2x8(glmVecFromVariant<glm::vec2>(v)); return true;
case 4: view.edit<glm::int32>(index) = glm::packSnorm4x8(glmVecFromVariant<glm::vec4>(v)); return true;
default: return false;
}
}
switch(vecN) {
case 2: setBufferViewElement<glm::u16vec2>(view, index, v); return true;
case 3: setBufferViewElement<glm::u16vec3>(view, index, v); return true;
@ -112,6 +130,9 @@ QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index,
const auto BYTES_PER_ELEMENT = byteLength / vecN;
Q_ASSERT(index < view.getNumElements());
Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT));
#ifdef DEBUG_BUFFERVIEW_SCRIPTING
qCDebug(bufferview_helpers) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN;
#endif
if (BYTES_PER_ELEMENT == 1) {
switch(vecN) {
case 2: return getBufferViewElement<glm::u8vec2>(view, index, asArray);
@ -129,6 +150,12 @@ QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index,
}
}
} else if (BYTES_PER_ELEMENT == 2) {
if (dataType == gpu::HALF) {
switch(vecN) {
case 2: return glmVecToVariant(glm::vec2(glm::unpackSnorm2x8(view.get<glm::int16>(index))));
case 4: return glmVecToVariant(glm::vec4(glm::unpackSnorm4x8(view.get<glm::int32>(index))));
}
}
switch(vecN) {
case 2: return getBufferViewElement<glm::u16vec2>(view, index, asArray);
case 3: return getBufferViewElement<glm::u16vec3>(view, index, asArray);
@ -193,3 +220,31 @@ const T glmVecFromVariant(const QVariant& v) {
return result;
}
template <typename T>
gpu::BufferView bufferViewFromVector(QVector<T> elements, gpu::Element elementType) {
auto vertexBuffer = std::make_shared<gpu::Buffer>(elements.size() * sizeof(T), (gpu::Byte*)elements.data());
return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType };
}
template<> gpu::BufferView bufferViewFromVector<unsigned int>(QVector<unsigned int> elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); }
template<> gpu::BufferView bufferViewFromVector<glm::vec3>(QVector<glm::vec3> elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); }
gpu::BufferView cloneBufferView(const gpu::BufferView& input) {
return gpu::BufferView(
std::make_shared<gpu::Buffer>(input._buffer->getSize(), input._buffer->getData()),
input._offset, input._size, input._stride, input._element
);
}
gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) {
auto effectiveSize = input._buffer->getSize() / input.getNumElements();
qDebug() << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize;
auto vsize = input._element.getSize() * numElements;
gpu::Byte *data = new gpu::Byte[vsize];
memset(data, 0, vsize);
auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data);
delete[] data;
auto output = gpu::BufferView(buffer, input._element);
qDebug() << "resized output" << output.getNumElements() << output._buffer->getSize();
return output;
}

View file

@ -8,11 +8,18 @@
#include <QtCore>
namespace gpu { class BufferView; }
namespace gpu {
class BufferView;
class Element;
}
template <typename T> QVariant glmVecToVariant(const T& v, bool asArray = false);
template <typename T> const T glmVecFromVariant(const QVariant& v);
QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = "");
bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v);
template <typename T> gpu::BufferView bufferViewFromVector(QVector<T> elements, gpu::Element elementType);
gpu::BufferView cloneBufferView(const gpu::BufferView& input);
gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements);

View file

@ -17,27 +17,16 @@
#include "BaseScriptEngine.h"
#include "ScriptEngineLogging.h"
#include "OBJWriter.h"
#include "OBJReader.h"
//#include "ui/overlays/Base3DOverlay.h"
//#include "EntityTreeRenderer.h"
//#include "avatar/AvatarManager.h"
//#include "RenderableEntityItem.h"
#include <glm/gtx/string_cast.hpp>
#include <GeometryUtil.h>
#include <shared/QtHelpers.h>
#include <graphics-scripting/DebugNames.h>
#include <graphics-scripting/BufferViewHelpers.h>
#include "BufferViewScripting.h"
#include "ScriptableMesh.h"
using ScriptableMesh = scriptable::ScriptableMesh;
#include "ModelScriptingInterface.moc"
namespace {
@ -50,13 +39,56 @@ ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(pare
}
}
void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) {
auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName);
Q_ASSERT(handler.engine() == this->engine());
QPointer<BaseScriptEngine> engine = dynamic_cast<BaseScriptEngine*>(handler.engine());
scriptable::ScriptableModel* meshes{ nullptr };
bool success = false;
QString error;
auto appProvider = DependencyManager::get<scriptable::ModelProviderFactory>();
qDebug() << "appProvider" << appProvider.data();
scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr;
QString providerType = provider ? provider->metadata.value("providerType").toString() : QString();
if (providerType.isEmpty()) {
providerType = "unknown";
}
if (provider) {
qCDebug(model_scripting) << "fetching meshes from " << providerType << "...";
auto scriptableMeshes = provider->getScriptableModel(&success);
qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <<success << "#" << scriptableMeshes.meshes.size();
if (success) {
meshes = new scriptable::ScriptableModel(scriptableMeshes);//SimpleModelProxy::fromScriptableModel(scriptableMeshes);
if (meshes->objectName().isEmpty()) {
meshes->setObjectName(providerType+"::meshes");
}
if (meshes->objectID.isNull()) {
meshes->objectID = uuid.toString();
}
meshes->metadata["provider"] = provider->metadata;
}
}
if (!success) {
error = QString("failed to get meshes from %1 provider for uuid %2").arg(providerType).arg(uuid.toString());
}
if (!error.isEmpty()) {
qCWarning(model_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error;
callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue);
} else {
callScopedHandlerObject(handler, QScriptValue::NullValue, engine->newQObject(meshes, QScriptEngine::ScriptOwnership));
}
}
QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) {
const auto& in = _in.getMeshes();
const auto& in = _in.getConstMeshes();
qCDebug(model_scripting) << "meshToOBJ" << in.size();
if (in.size()) {
QList<scriptable::MeshPointer> meshes;
foreach (const auto meshProxy, in) {
qCDebug(model_scripting) << "meshToOBJ" << meshProxy.get();
foreach (auto meshProxy, in) {
qCDebug(model_scripting) << "meshToOBJ" << meshProxy;
if (meshProxy) {
meshes.append(getMeshPointer(meshProxy));
}
@ -77,7 +109,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _
size_t totalColorCount { 0 };
size_t totalNormalCount { 0 };
size_t totalIndexCount { 0 };
foreach (const scriptable::ScriptableMeshPointer meshProxy, in) {
foreach (auto& meshProxy, in) {
scriptable::MeshPointer mesh = getMeshPointer(meshProxy);
totalVertexCount += mesh->getNumVertices();
@ -113,7 +145,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _
uint32_t indexStartOffset { 0 };
foreach (const scriptable::ScriptableMeshPointer meshProxy, in) {
foreach (const auto& meshProxy, in) {
scriptable::MeshPointer mesh = getMeshPointer(meshProxy);
mesh->forEach(
[&](glm::vec3 position){
@ -175,8 +207,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result));
return engine()->toScriptValue(result);
return engine()->toScriptValue(scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result)));
}
QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) {
@ -189,7 +220,7 @@ QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPo
[&](glm::vec3 color){ return color; },
[&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); },
[&](uint32_t index){ return index; });
scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result));
scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result));
return engine()->toScriptValue(resultProxy);
}
@ -201,8 +232,11 @@ QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshP
return (uint32_t)mesh->getNumVertices();
}
QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex) {
QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex) {
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return QScriptValue();
}
const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer();
auto numVertices = mesh->getNumVertices();
@ -266,480 +300,46 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector<glm::vec3>& vertices
scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, mesh));
scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, mesh));
return engine()->toScriptValue(meshProxy);
}
QScriptValue ModelScriptingInterface::mapAttributeValues(
QScriptValue _in,
QScriptValue scopeOrCallback,
QScriptValue methodOrName
) {
qCInfo(model_scripting) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject();
auto in = qscriptvalue_cast<scriptable::ScriptableModel>(_in).getMeshes();
if (in.size()) {
foreach (scriptable::ScriptableMeshPointer meshProxy, in) {
mapMeshAttributeValues(meshProxy, scopeOrCallback, methodOrName);
}
return thisObject();
} else if (auto meshProxy = qobject_cast<scriptable::ScriptableMesh*>(_in.toQObject())) {
return mapMeshAttributeValues(meshProxy->shared_from_this(), scopeOrCallback, methodOrName);
} else {
context()->throwError("invalid ModelProxy || MeshProxyPointer");
}
return false;
}
QScriptValue ModelScriptingInterface::unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) {
auto mesh = getMeshPointer(meshProxy);
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices" << !!mesh<< !!meshProxy;
if (!mesh) {
return QScriptValue();
}
auto positions = mesh->getVertexBuffer();
auto indices = mesh->getIndexBuffer();
quint32 numPoints = (quint32)indices.getNumElements();
auto buffer = new gpu::Buffer();
buffer->resize(numPoints * sizeof(uint32_t));
auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX });
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices numPoints" << numPoints;
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
for (const auto& a : attributeViews) {
auto& view = a.second;
auto sz = view._element.getSize();
auto buffer = new gpu::Buffer();
buffer->resize(numPoints * sz);
auto points = gpu::BufferView(buffer, view._element);
auto src = (uint8_t*)view._buffer->getData();
auto dest = (uint8_t*)points._buffer->getData();
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices buffer" << a.first;
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices source" << view.getNumElements();
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices dest" << points.getNumElements();
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices sz" << sz << src << dest << slot;
auto esize = indices._element.getSize();
const char* hint= a.first.toStdString().c_str();
for(quint32 i = 0; i < numPoints; i++) {
quint32 index = esize == 4 ? indices.get<quint32>(i) : indices.get<quint16>(i);
newindices.edit<uint32_t>(i) = i;
bufferViewElementFromVariant(
points, i,
bufferViewElementToVariant(view, index, false, hint)
);
}
if (slot == gpu::Stream::POSITION) {
mesh->setVertexBuffer(points);
} else {
mesh->addAttribute(slot, points);
}
}
mesh->setIndexBuffer(newindices);
if (recalcNormals) {
recalculateNormals(meshProxy);
}
return true;
}
namespace {
template <typename T>
gpu::BufferView bufferViewFromVector(QVector<T> elements, gpu::Element elementType) {
auto vertexBuffer = std::make_shared<gpu::Buffer>(
elements.size() * sizeof(T),
(gpu::Byte*)elements.data()
);
return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType };
}
gpu::BufferView cloneBufferView(const gpu::BufferView& input) {
//qCInfo(model_scripting) << "input" << input.getNumElements() << input._buffer->getSize();
auto output = gpu::BufferView(
std::make_shared<gpu::Buffer>(input._buffer->getSize(), input._buffer->getData()),
input._offset,
input._size,
input._stride,
input._element
);
//qCInfo(model_scripting) << "after" << output.getNumElements() << output._buffer->getSize();
return output;
}
gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) {
auto effectiveSize = input._buffer->getSize() / input.getNumElements();
qCInfo(model_scripting) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize;
auto vsize = input._element.getSize() * numElements;
gpu::Byte *data = new gpu::Byte[vsize];
memset(data, 0, vsize);
auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data);
delete[] data;
auto output = gpu::BufferView(buffer, input._element);
qCInfo(model_scripting) << "resized output" << output.getNumElements() << output._buffer->getSize();
return output;
}
}
bool ModelScriptingInterface::replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer src, const QVector<QString>& attributeNames) {
auto target = getMeshPointer(dest);
auto source = getMeshPointer(src);
if (!target || !source) {
context()->throwError("ModelScriptingInterface::replaceMeshData -- expected dest and src to be valid mesh proxy pointers");
return false;
}
QVector<QString> attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames;
//qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes;
// remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names
if (attributeNames.isEmpty()) {
auto attributeViews = ScriptableMesh::gatherBufferViews(target);
for (const auto& a : attributeViews) {
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
if (!attributes.contains(a.first)) {
//qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- pruning target attribute" << a.first << slot;
target->removeAttribute(slot);
}
QScriptValue meshPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) {
if (!in) {
return QScriptValue::NullValue;
}
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
}
target->setVertexBuffer(cloneBufferView(source->getVertexBuffer()));
target->setIndexBuffer(cloneBufferView(source->getIndexBuffer()));
target->setPartBuffer(cloneBufferView(source->getPartBuffer()));
for (const auto& a : attributes) {
auto slot = ScriptableMesh::ATTRIBUTES[a];
if (slot == gpu::Stream::POSITION) {
continue;
}
// auto& before = target->getAttributeBuffer(slot);
auto& input = source->getAttributeBuffer(slot);
if (input.getNumElements() == 0) {
//qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData buffer is empty -- pruning" << a << slot;
target->removeAttribute(slot);
} else {
// if (before.getNumElements() == 0) {
// qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer is empty -- adding" << a << slot;
// } else {
// qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer exists -- updating" << a << slot;
// }
target->addAttribute(slot, cloneBufferView(input));
}
// auto& after = target->getAttributeBuffer(slot);
// qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements();
}
return true;
}
bool ModelScriptingInterface::dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon) {
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return false;
}
auto positions = mesh->getVertexBuffer();
auto numPositions = positions.getNumElements();
const auto epsilon2 = epsilon*epsilon;
QVector<glm::vec3> uniqueVerts;
uniqueVerts.reserve((int)numPositions);
QMap<quint32,quint32> remapIndices;
for (quint32 i = 0; i < numPositions; i++) {
const quint32 numUnique = uniqueVerts.size();
const auto& position = positions.get<glm::vec3>(i);
bool unique = true;
for (quint32 j = 0; j < numUnique; j++) {
if (glm::length2(uniqueVerts[j] - position) <= epsilon2) {
remapIndices[i] = j;
unique = false;
break;
}
}
if (unique) {
uniqueVerts << position;
remapIndices[i] = numUnique;
}
}
qCInfo(model_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size();
auto indices = mesh->getIndexBuffer();
auto numIndices = indices.getNumElements();
auto esize = indices._element.getSize();
QVector<quint32> newIndices;
newIndices.reserve((int)numIndices);
for (quint32 i = 0; i < numIndices; i++) {
quint32 index = esize == 4 ? indices.get<quint32>(i) : indices.get<quint16>(i);
if (remapIndices.contains(index)) {
//qCInfo(model_scripting) << i << index << "->" << remapIndices[index];
newIndices << remapIndices[index];
} else {
qCInfo(model_scripting) << i << index << "!remapIndices[index]";
}
}
mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }));
mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ }));
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
quint32 numUniqueVerts = uniqueVerts.size();
for (const auto& a : attributeViews) {
auto& view = a.second;
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
if (slot == gpu::Stream::POSITION) {
continue;
}
qCInfo(model_scripting) << "ModelScriptingInterface::dedupeVertices" << a.first << slot << view.getNumElements();
auto newView = resizedBufferView(view, numUniqueVerts);
qCInfo(model_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements();
quint32 numElements = (quint32)view.getNumElements();
for (quint32 i = 0; i < numElements; i++) {
quint32 fromVertexIndex = i;
quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex;
bufferViewElementFromVariant(
newView, toVertexIndex,
bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe")
);
}
mesh->addAttribute(slot, newView);
}
return true;
}
QScriptValue ModelScriptingInterface::cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) {
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return QScriptValue::NullValue;
}
graphics::MeshPointer clone(new graphics::Mesh());
clone->displayName = mesh->displayName + "-clone";
qCInfo(model_scripting) << "ModelScriptingInterface::cloneMesh" << !!mesh<< !!meshProxy;
if (!mesh) {
return QScriptValue::NullValue;
}
clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer()));
clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer()));
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
for (const auto& a : attributeViews) {
auto& view = a.second;
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices buffer" << a.first << slot;
auto points = cloneBufferView(view);
qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices source" << view.getNumElements();
qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices dest" << points.getNumElements();
if (slot == gpu::Stream::POSITION) {
clone->setVertexBuffer(points);
} else {
clone->addAttribute(slot, points);
}
}
auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone));
if (recalcNormals) {
recalculateNormals(result);
}
return engine()->toScriptValue(result);
}
bool ModelScriptingInterface::recalculateNormals(scriptable::ScriptableMeshPointer meshProxy) {
qCInfo(model_scripting) << "Recalculating normals" << !!meshProxy;
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return false;
}
ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions
auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL);
auto verts = mesh->getVertexBuffer();
auto indices = mesh->getIndexBuffer();
auto esize = indices._element.getSize();
auto numPoints = indices.getNumElements();
const auto TRIANGLE = 3;
quint32 numFaces = (quint32)numPoints / TRIANGLE;
//QVector<Triangle> faces;
QVector<glm::vec3> faceNormals;
QMap<QString,QVector<quint32>> vertexToFaces;
//faces.resize(numFaces);
faceNormals.resize(numFaces);
auto numNormals = normals.getNumElements();
qCInfo(model_scripting) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints);
if (normals.getNumElements() != verts.getNumElements()) {
return false;
}
for (quint32 i = 0; i < numFaces; i++) {
quint32 I = TRIANGLE * i;
quint32 i0 = esize == 4 ? indices.get<quint32>(I+0) : indices.get<quint16>(I+0);
quint32 i1 = esize == 4 ? indices.get<quint32>(I+1) : indices.get<quint16>(I+1);
quint32 i2 = esize == 4 ? indices.get<quint32>(I+2) : indices.get<quint16>(I+2);
Triangle face = {
verts.get<glm::vec3>(i1),
verts.get<glm::vec3>(i2),
verts.get<glm::vec3>(i0)
};
faceNormals[i] = face.getNormal();
if (glm::isnan(faceNormals[i].x)) {
qCInfo(model_scripting) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2);
break;
}
vertexToFaces[glm::to_string(face.v0).c_str()] << i;
vertexToFaces[glm::to_string(face.v1).c_str()] << i;
vertexToFaces[glm::to_string(face.v2).c_str()] << i;
}
for (quint32 j = 0; j < numNormals; j++) {
//auto v = verts.get<glm::vec3>(j);
glm::vec3 normal { 0.0f, 0.0f, 0.0f };
QString key { glm::to_string(verts.get<glm::vec3>(j)).c_str() };
const auto& faces = vertexToFaces.value(key);
if (faces.size()) {
for (const auto i : faces) {
normal += faceNormals[i];
}
normal *= 1.0f / (float)faces.size();
} else {
static int logged = 0;
if (logged++ < 10) {
qCInfo(model_scripting) << "no faces for key!?" << key;
}
normal = verts.get<glm::vec3>(j);
}
if (glm::isnan(normal.x)) {
static int logged = 0;
if (logged++ < 10) {
qCInfo(model_scripting) << "isnan(normal.x)" << j << vec3toVariant(normal);
}
break;
}
normals.edit<glm::vec3>(j) = glm::normalize(normal);
}
return true;
}
QScriptValue ModelScriptingInterface::mapMeshAttributeValues(
scriptable::ScriptableMeshPointer meshProxy, QScriptValue scopeOrCallback, QScriptValue methodOrName
) {
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return false;
}
auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName);
// input buffers
gpu::BufferView positions = mesh->getVertexBuffer();
const auto nPositions = positions.getNumElements();
// destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh)
auto scope = scopedHandler.property("scope");
auto callback = scopedHandler.property("callback");
auto js = engine(); // cache value to avoid resolving each iteration
auto meshPart = js->toScriptValue(meshProxy);
auto obj = js->newObject();
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" });
for (uint32_t i=0; i < nPositions; i++) {
for (const auto& a : attributeViews) {
bool asArray = a.second._element.getType() != gpu::FLOAT;
obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str()));
}
auto result = callback.call(scope, { obj, i, meshPart });
if (js->hasUncaughtException()) {
context()->throwValue(js->uncaughtException());
return false;
}
if (result.isBool() && !result.toBool()) {
// bail without modifying data if user explicitly returns false
continue;
}
if (result.isObject() && !result.strictlyEquals(obj)) {
// user returned a new object (ie: instead of modifying input properties)
obj = result;
}
for (const auto& a : attributeViews) {
const auto& attribute = obj.property(a.first);
auto& view = a.second;
if (attribute.isValid()) {
bufferViewElementFromScriptValue(attribute, view, i);
}
}
}
return thisObject();
}
void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) {
auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName);
Q_ASSERT(handler.engine() == this->engine());
QPointer<BaseScriptEngine> engine = dynamic_cast<BaseScriptEngine*>(handler.engine());
scriptable::ScriptableModel meshes;
bool success = false;
QString error;
auto appProvider = DependencyManager::get<scriptable::ModelProviderFactory>();
qDebug() << "appProvider" << appProvider.data();
scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr;
QString providerType = provider ? provider->metadata.value("providerType").toString() : QString();
if (providerType.isEmpty()) {
providerType = "unknown";
}
if (provider) {
qCDebug(model_scripting) << "fetching meshes from " << providerType << "...";
auto scriptableMeshes = provider->getScriptableModel(&success);
qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <<success << "#" << scriptableMeshes.meshes.size();
if (success) {
meshes = scriptableMeshes;//SimpleModelProxy::fromScriptableModel(scriptableMeshes);
}
}
if (!success) {
error = QString("failed to get meshes from %1 provider for uuid %2").arg(providerType).arg(uuid.toString());
}
if (!error.isEmpty()) {
qCWarning(model_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error;
callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue);
} else {
callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes));
}
}
namespace {
QScriptValue meshToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) {
return engine->newQObject(in.get(), QScriptEngine::QtOwnership,
QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects
);
}
void meshFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) {
void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) {
auto obj = value.toQObject();
//qDebug() << "meshFromScriptValue" << obj;
qDebug() << "meshPointerFromScriptValue" << obj;
if (auto tmp = qobject_cast<scriptable::ScriptableMesh*>(obj)) {
out = tmp->shared_from_this();
out = tmp;
}
// FIXME: Why does above cast not work on Win32!?
if (!out) {
auto smp = static_cast<scriptable::ScriptableMesh*>(obj);
//qDebug() << "meshFromScriptValue2" << smp;
out = smp->shared_from_this();
if (auto smp = static_cast<scriptable::ScriptableMesh*>(obj)) {
qDebug() << "meshPointerFromScriptValue2" << smp;
out = smp;
}
}
}
QScriptValue meshesToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) {
// QScriptValueList result;
QScriptValue result = engine->newArray();
int i = 0;
foreach(scriptable::ScriptableMeshPointer const meshProxy, in->getMeshes()) {
result.setProperty(i++, meshToScriptValue(engine, meshProxy));
}
return result;
QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) {
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
// QScriptValue result = engine->newArray();
// int i = 0;
// foreach(auto& mesh, in->getMeshes()) {
// result.setProperty(i++, meshPointerToScriptValue(engine, mesh));
// }
// return result;
}
void meshesFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) {
void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) {
const auto length = value.property("length").toInt32();
qCDebug(model_scripting) << "in meshesFromScriptValue, length =" << length;
qCDebug(model_scripting) << "in modelPointerFromScriptValue, length =" << length;
for (int i = 0; i < length; i++) {
if (const auto meshProxy = qobject_cast<scriptable::ScriptableMesh*>(value.property(i).toQObject())) {
out->meshes.append(meshProxy->getMeshPointer());
@ -749,79 +349,64 @@ namespace {
}
}
void modelProxyFromScriptValue(const QScriptValue& object, scriptable::ScriptableModel &meshes) {
auto meshesProperty = object.property("meshes");
if (meshesProperty.property("length").toInt32() > 0) {
//meshes._meshes = qobject_cast<scriptable::ScriptableModelPointer>(meshesProperty.toQObject());
// qDebug() << "modelProxyFromScriptValue" << meshesProperty.property("length").toInt32() << meshesProperty.toVariant().typeName();
qScriptValueToSequence(meshesProperty, meshes.meshes);
} else if (auto mesh = qobject_cast<scriptable::ScriptableMesh*>(object.toQObject())) {
meshes.meshes << mesh->getMeshPointer();
} else {
qDebug() << "modelProxyFromScriptValue -- unrecognized input" << object.toVariant().toString();
}
// FIXME: MESHFACES:
// QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) {
// QScriptValue obj = engine->newObject();
// obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices));
// return obj;
// }
// void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) {
// qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices);
// }
// QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector<mesh::MeshFace>& vector) {
// return qScriptValueFromSequence(engine, vector);
// }
// void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<mesh::MeshFace>& result) {
// qScriptValueToSequence(array, result);
// }
meshes.metadata = object.property("metadata").toVariant().toMap();
}
QScriptValue modelProxyToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModel &in) {
QScriptValue obj = engine->newObject();
obj.setProperty("meshes", qScriptValueFromSequence(engine, in.meshes));
obj.setProperty("metadata", engine->toScriptValue(in.metadata));
return obj;
}
QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) {
QScriptValue obj = engine->newObject();
obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices));
return obj;
}
void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) {
qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices);
}
QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector<mesh::MeshFace>& vector) {
QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector<quint32>& vector) {
return qScriptValueFromSequence(engine, vector);
}
void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<mesh::MeshFace>& result) {
qScriptValueToSequence(array, result);
}
QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector<mesh::uint32>& vector) {
return qScriptValueFromSequence(engine, vector);
}
void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector<mesh::uint32>& result) {
void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector<quint32>& result) {
qScriptValueToSequence(array, result);
}
}
int meshUint32 = qRegisterMetaType<mesh::uint32>();
int meshUint32 = qRegisterMetaType<quint32>();
namespace mesh {
int meshUint32 = qRegisterMetaType<uint32>();
}
int qVectorMeshUint32 = qRegisterMetaType<QVector<mesh::uint32>>();
int qVectorMeshUint32 = qRegisterMetaType<QVector<quint32>>();
void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterSequenceMetaType<QVector<scriptable::ScriptableMeshPointer>>(engine);
qScriptRegisterSequenceMetaType<mesh::MeshFaces>(engine);
qScriptRegisterSequenceMetaType<QVector<mesh::uint32>>(engine);
qScriptRegisterMetaType(engine, modelProxyToScriptValue, modelProxyFromScriptValue);
qScriptRegisterSequenceMetaType<QVector<quint32>>(engine);
qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue);
qScriptRegisterMetaType(engine, meshToScriptValue, meshFromScriptValue);
qScriptRegisterMetaType(engine, meshesToScriptValue, meshesFromScriptValue);
qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue);
qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue);
qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue);
qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue);
// FIXME: MESHFACES: remove if MeshFace is not needed anywhere
// qScriptRegisterSequenceMetaType<mesh::MeshFaces>(engine);
// qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue);
// qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue);
}
MeshPointer ModelScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) {
return meshProxy._mesh;//getMeshPointer(&meshProxy);
}
MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMesh& meshProxy) {
return getMeshPointer(&meshProxy);
}
MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) {
MeshPointer result;
if (!meshProxy) {
if (context()){
context()->throwError("expected meshProxy as first parameter");
} else {
qDebug() << "expected meshProxy as first parameter";
}
return result;
}
@ -829,6 +414,8 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo
if (!mesh) {
if (context()) {
context()->throwError("expected valid meshProxy as first parameter");
} else {
qDebug() << "expected valid meshProxy as first parameter";
}
return result;
}

View file

@ -38,30 +38,20 @@ public slots:
*/
void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue());
bool dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon = 1e-6);
bool recalculateNormals(scriptable::ScriptableMeshPointer meshProxy);
QScriptValue cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true);
QScriptValue unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true);
QScriptValue mapAttributeValues(QScriptValue in,
QScriptValue scopeOrCallback,
QScriptValue methodOrName = QScriptValue());
QScriptValue mapMeshAttributeValues(scriptable::ScriptableMeshPointer meshProxy,
QScriptValue scopeOrCallback,
QScriptValue methodOrName = QScriptValue());
QString meshToOBJ(const scriptable::ScriptableModel& in);
bool replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer source, const QVector<QString>& attributeNames = QVector<QString>());
QScriptValue appendMeshes(scriptable::ScriptableModel in);
QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform);
QScriptValue newMesh(const QVector<glm::vec3>& vertices,
const QVector<glm::vec3>& normals,
const QVector<mesh::MeshFace>& faces);
QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy);
QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex);
QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex);
private:
scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy);
scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy);
scriptable::MeshPointer getMeshPointer(const scriptable::ScriptableMesh& meshProxy);
};

View file

@ -14,14 +14,20 @@
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtx/norm.hpp>
#include <glm/gtx/string_cast.hpp>
#include <graphics/Geometry.h>
#include <graphics-scripting/DebugNames.h>
#include <graphics-scripting/BufferViewHelpers.h>
#include <graphics-scripting/BufferViewScripting.h>
#include <Extents.h>
#include "ScriptableMesh.moc"
#include <RegisteredMetaTypes.h>
#include <BaseScriptEngine.h>
#include <QtScript/QScriptValue>
#include "OBJWriter.h"
QLoggingCategory mesh_logging { "hifi.scripting.mesh" };
@ -41,10 +47,26 @@ QMap<QString,int> ScriptableMesh::ATTRIBUTES{
{"texcoord4", gpu::Stream::TEXCOORD4 },
};
QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::getMeshes() const {
QString scriptable::ScriptableModel::toString() const {
return QString("[ScriptableModel%1%2]")
.arg(objectID.isNull() ? "" : " objectID="+objectID.toString())
.arg(objectName().isEmpty() ? "" : " name=" +objectName());
}
const QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::getConstMeshes() const {
QVector<scriptable::ScriptableMeshPointer> out;
for(const auto& mesh : meshes) {
const scriptable::ScriptableMeshPointer m = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(const_cast<scriptable::ScriptableModel*>(this), mesh));
out << m;
}
return out;
}
QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::getMeshes() {
QVector<scriptable::ScriptableMeshPointer> out;
for(auto& mesh : meshes) {
out << scriptable::ScriptableMeshPointer(new ScriptableMesh(std::const_pointer_cast<scriptable::ScriptableModel>(this->shared_from_this()), mesh));
scriptable::ScriptableMeshPointer m{new scriptable::ScriptableMesh(this, mesh)};
out << m;
}
return out;
}
@ -134,15 +156,16 @@ QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const {
}
bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) {
qDebug() << "setVertexAttributes" << vertexIndex << attributes;
//qDebug() << "setVertexAttributes" << vertexIndex << attributes;
for (auto& a : gatherBufferViews(getMeshPointer())) {
const auto& name = a.first;
const auto& value = attributes.value(name);
if (value.isValid()) {
auto& view = a.second;
//qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name;
bufferViewElementFromVariant(view, vertexIndex, value);
} else {
qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name;
//qCDebug(mesh_logging) << "(skipping) setVertexAttributes" << vertexIndex << name;
}
}
return true;
@ -357,3 +380,375 @@ std::map<QString, gpu::BufferView> ScriptableMesh::gatherBufferViews(scriptable:
}
return attributeViews;
}
QScriptValue ScriptableModel::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) {
auto context = scopeOrCallback.engine()->currentContext();
auto _in = context->thisObject();
qCInfo(mesh_logging) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject();
auto model = qscriptvalue_cast<scriptable::ScriptableModel>(_in);
QVector<scriptable::ScriptableMeshPointer> in = model.getMeshes();
if (in.size()) {
foreach (scriptable::ScriptableMeshPointer meshProxy, in) {
meshProxy->mapAttributeValues(scopeOrCallback, methodOrName);
}
return _in;
} else if (auto meshProxy = qobject_cast<scriptable::ScriptableMesh*>(_in.toQObject())) {
return meshProxy->mapAttributeValues(scopeOrCallback, methodOrName);
} else {
context->throwError("invalid ModelProxy || MeshProxyPointer");
}
return false;
}
QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) {
auto mesh = getMeshPointer();
if (!mesh) {
return false;
}
auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName);
// input buffers
gpu::BufferView positions = mesh->getVertexBuffer();
const auto nPositions = positions.getNumElements();
// destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh)
auto scope = scopedHandler.property("scope");
auto callback = scopedHandler.property("callback");
auto js = engine(); // cache value to avoid resolving each iteration
auto meshPart = thisObject();//js->toScriptValue(meshProxy);
auto obj = js->newObject();
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" });
for (uint32_t i=0; i < nPositions; i++) {
for (const auto& a : attributeViews) {
bool asArray = a.second._element.getType() != gpu::FLOAT;
obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str()));
}
auto result = callback.call(scope, { obj, i, meshPart });
if (js->hasUncaughtException()) {
context()->throwValue(js->uncaughtException());
return false;
}
if (result.isBool() && !result.toBool()) {
// bail without modifying data if user explicitly returns false
continue;
}
if (result.isObject() && !result.strictlyEquals(obj)) {
// user returned a new object (ie: instead of modifying input properties)
obj = result;
}
for (const auto& a : attributeViews) {
const auto& attribute = obj.property(a.first);
auto& view = a.second;
if (attribute.isValid()) {
bufferViewElementFromScriptValue(attribute, view, i);
}
}
}
return thisObject();
}
QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) {
auto meshProxy = this;
auto mesh = getMeshPointer();
qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices" << !!mesh<< !!meshProxy;
if (!mesh) {
return QScriptValue();
}
auto positions = mesh->getVertexBuffer();
auto indices = mesh->getIndexBuffer();
quint32 numPoints = (quint32)indices.getNumElements();
auto buffer = new gpu::Buffer();
buffer->resize(numPoints * sizeof(uint32_t));
auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX });
qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices numPoints" << numPoints;
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
for (const auto& a : attributeViews) {
auto& view = a.second;
auto sz = view._element.getSize();
auto buffer = new gpu::Buffer();
buffer->resize(numPoints * sz);
auto points = gpu::BufferView(buffer, view._element);
auto src = (uint8_t*)view._buffer->getData();
auto dest = (uint8_t*)points._buffer->getData();
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices buffer" << a.first;
qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices source" << view.getNumElements();
qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices dest" << points.getNumElements();
qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices sz" << sz << src << dest << slot;
auto esize = indices._element.getSize();
const char* hint= a.first.toStdString().c_str();
for(quint32 i = 0; i < numPoints; i++) {
quint32 index = esize == 4 ? indices.get<quint32>(i) : indices.get<quint16>(i);
newindices.edit<uint32_t>(i) = i;
bufferViewElementFromVariant(
points, i,
bufferViewElementToVariant(view, index, false, hint)
);
}
if (slot == gpu::Stream::POSITION) {
mesh->setVertexBuffer(points);
} else {
mesh->addAttribute(slot, points);
}
}
mesh->setIndexBuffer(newindices);
if (recalcNormals) {
recalculateNormals();
}
return true;
}
bool ScriptableMesh::replaceMeshData(scriptable::ScriptableMeshPointer src, const QVector<QString>& attributeNames) {
auto target = getMeshPointer();
auto source = src ? src->getMeshPointer() : nullptr;
if (!target || !source) {
context()->throwError("ScriptableMesh::replaceMeshData -- expected dest and src to be valid mesh proxy pointers");
return false;
}
QVector<QString> attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames;
//qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes;
// remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names
if (attributeNames.isEmpty()) {
auto attributeViews = ScriptableMesh::gatherBufferViews(target);
for (const auto& a : attributeViews) {
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
if (!attributes.contains(a.first)) {
//qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot;
target->removeAttribute(slot);
}
}
}
target->setVertexBuffer(cloneBufferView(source->getVertexBuffer()));
target->setIndexBuffer(cloneBufferView(source->getIndexBuffer()));
target->setPartBuffer(cloneBufferView(source->getPartBuffer()));
for (const auto& a : attributes) {
auto slot = ScriptableMesh::ATTRIBUTES[a];
if (slot == gpu::Stream::POSITION) {
continue;
}
// auto& before = target->getAttributeBuffer(slot);
auto& input = source->getAttributeBuffer(slot);
if (input.getNumElements() == 0) {
//qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData buffer is empty -- pruning" << a << slot;
target->removeAttribute(slot);
} else {
// if (before.getNumElements() == 0) {
// qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer is empty -- adding" << a << slot;
// } else {
// qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer exists -- updating" << a << slot;
// }
target->addAttribute(slot, cloneBufferView(input));
}
// auto& after = target->getAttributeBuffer(slot);
// qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements();
}
return true;
}
bool ScriptableMesh::dedupeVertices(float epsilon) {
scriptable::ScriptableMeshPointer meshProxy = this;
auto mesh = getMeshPointer();
if (!mesh) {
return false;
}
auto positions = mesh->getVertexBuffer();
auto numPositions = positions.getNumElements();
const auto epsilon2 = epsilon*epsilon;
QVector<glm::vec3> uniqueVerts;
uniqueVerts.reserve((int)numPositions);
QMap<quint32,quint32> remapIndices;
for (quint32 i = 0; i < numPositions; i++) {
const quint32 numUnique = uniqueVerts.size();
const auto& position = positions.get<glm::vec3>(i);
bool unique = true;
for (quint32 j = 0; j < numUnique; j++) {
if (glm::length2(uniqueVerts[j] - position) <= epsilon2) {
remapIndices[i] = j;
unique = false;
break;
}
}
if (unique) {
uniqueVerts << position;
remapIndices[i] = numUnique;
}
}
qCInfo(mesh_logging) << "//VERTS before" << numPositions << "after" << uniqueVerts.size();
auto indices = mesh->getIndexBuffer();
auto numIndices = indices.getNumElements();
auto esize = indices._element.getSize();
QVector<quint32> newIndices;
newIndices.reserve((int)numIndices);
for (quint32 i = 0; i < numIndices; i++) {
quint32 index = esize == 4 ? indices.get<quint32>(i) : indices.get<quint16>(i);
if (remapIndices.contains(index)) {
//qCInfo(mesh_logging) << i << index << "->" << remapIndices[index];
newIndices << remapIndices[index];
} else {
qCInfo(mesh_logging) << i << index << "!remapIndices[index]";
}
}
mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }));
mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ }));
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
quint32 numUniqueVerts = uniqueVerts.size();
for (const auto& a : attributeViews) {
auto& view = a.second;
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
if (slot == gpu::Stream::POSITION) {
continue;
}
qCInfo(mesh_logging) << "ScriptableMesh::dedupeVertices" << a.first << slot << view.getNumElements();
auto newView = resizedBufferView(view, numUniqueVerts);
qCInfo(mesh_logging) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements();
quint32 numElements = (quint32)view.getNumElements();
for (quint32 i = 0; i < numElements; i++) {
quint32 fromVertexIndex = i;
quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex;
bufferViewElementFromVariant(
newView, toVertexIndex,
bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe")
);
}
mesh->addAttribute(slot, newView);
}
return true;
}
QScriptValue ScriptableMesh::cloneMesh(bool recalcNormals) {
auto mesh = getMeshPointer();
if (!mesh) {
return QScriptValue::NullValue;
}
graphics::MeshPointer clone(new graphics::Mesh());
clone->displayName = mesh->displayName + "-clone";
qCInfo(mesh_logging) << "ScriptableMesh::cloneMesh" << !!mesh;
if (!mesh) {
return QScriptValue::NullValue;
}
clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer()));
clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer()));
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
for (const auto& a : attributeViews) {
auto& view = a.second;
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices buffer" << a.first << slot;
auto points = cloneBufferView(view);
qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices source" << view.getNumElements();
qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices dest" << points.getNumElements();
if (slot == gpu::Stream::POSITION) {
clone->setVertexBuffer(points);
} else {
clone->addAttribute(slot, points);
}
}
auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone));
if (recalcNormals) {
result->recalculateNormals();
}
return engine()->toScriptValue(result);
}
bool ScriptableMesh::recalculateNormals() {
scriptable::ScriptableMeshPointer meshProxy = this;
qCInfo(mesh_logging) << "Recalculating normals" << !!meshProxy;
auto mesh = getMeshPointer();
if (!mesh) {
return false;
}
ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions
auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL);
auto verts = mesh->getVertexBuffer();
auto indices = mesh->getIndexBuffer();
auto esize = indices._element.getSize();
auto numPoints = indices.getNumElements();
const auto TRIANGLE = 3;
quint32 numFaces = (quint32)numPoints / TRIANGLE;
//QVector<Triangle> faces;
QVector<glm::vec3> faceNormals;
QMap<QString,QVector<quint32>> vertexToFaces;
//faces.resize(numFaces);
faceNormals.resize(numFaces);
auto numNormals = normals.getNumElements();
qCInfo(mesh_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints);
if (normals.getNumElements() != verts.getNumElements()) {
return false;
}
for (quint32 i = 0; i < numFaces; i++) {
quint32 I = TRIANGLE * i;
quint32 i0 = esize == 4 ? indices.get<quint32>(I+0) : indices.get<quint16>(I+0);
quint32 i1 = esize == 4 ? indices.get<quint32>(I+1) : indices.get<quint16>(I+1);
quint32 i2 = esize == 4 ? indices.get<quint32>(I+2) : indices.get<quint16>(I+2);
Triangle face = {
verts.get<glm::vec3>(i1),
verts.get<glm::vec3>(i2),
verts.get<glm::vec3>(i0)
};
faceNormals[i] = face.getNormal();
if (glm::isnan(faceNormals[i].x)) {
qCInfo(mesh_logging) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2);
break;
}
vertexToFaces[glm::to_string(face.v0).c_str()] << i;
vertexToFaces[glm::to_string(face.v1).c_str()] << i;
vertexToFaces[glm::to_string(face.v2).c_str()] << i;
}
for (quint32 j = 0; j < numNormals; j++) {
//auto v = verts.get<glm::vec3>(j);
glm::vec3 normal { 0.0f, 0.0f, 0.0f };
QString key { glm::to_string(verts.get<glm::vec3>(j)).c_str() };
const auto& faces = vertexToFaces.value(key);
if (faces.size()) {
for (const auto i : faces) {
normal += faceNormals[i];
}
normal *= 1.0f / (float)faces.size();
} else {
static int logged = 0;
if (logged++ < 10) {
qCInfo(mesh_logging) << "no faces for key!?" << key;
}
normal = verts.get<glm::vec3>(j);
}
if (glm::isnan(normal.x)) {
static int logged = 0;
if (logged++ < 10) {
qCInfo(mesh_logging) << "isnan(normal.x)" << j << vec3toVariant(normal);
}
break;
}
normals.edit<glm::vec3>(j) = glm::normalize(normal);
}
return true;
}
QString ScriptableMesh::toOBJ() {
if (!getMeshPointer()) {
context()->throwError(QString("null mesh"));
}
return writeOBJToString({ getMeshPointer() });
}

View file

@ -6,12 +6,16 @@
#include <QtCore/QList>
#include <QtCore/QVariant>
#include <QtCore/QUuid>
#include <QPointer>
#include <memory>
#include <DependencyManager.h>
#include <graphics-scripting/ScriptableModel.h>
#include <QtScript/QScriptable>
#include <QtScript/QScriptValue>
namespace graphics {
class Mesh;
}
@ -19,91 +23,112 @@ namespace gpu {
class BufferView;
}
namespace scriptable {
class ScriptableMesh : public QObject, public std::enable_shared_from_this<ScriptableMesh> {
class ScriptableMeshPart;
using ScriptableMeshPartPointer = QPointer<ScriptableMeshPart>;
class ScriptableMesh : public QObject, QScriptable {
Q_OBJECT
public:
ScriptableModelPointer _model;
scriptable::MeshPointer _mesh;
QVariantMap _metadata;
ScriptableMesh() : QObject() {}
ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {}
ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {}
~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; }
Q_PROPERTY(quint32 numParts READ getNumParts)
Q_PROPERTY(quint32 numAttributes READ getNumAttributes)
Q_PROPERTY(quint32 numVertices READ getNumVertices)
Q_PROPERTY(quint32 numIndices READ getNumIndices)
Q_PROPERTY(QVariantMap metadata MEMBER _metadata)
Q_PROPERTY(QVector<QString> attributeNames READ getAttributeNames)
virtual scriptable::MeshPointer getMeshPointer() const { return _mesh; }
Q_INVOKABLE virtual quint32 getNumParts() const;
Q_INVOKABLE virtual quint32 getNumVertices() const;
Q_INVOKABLE virtual quint32 getNumAttributes() const;
Q_INVOKABLE virtual quint32 getNumIndices() const { return 0; }
Q_INVOKABLE virtual QVector<QString> getAttributeNames() const;
Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex) const;
Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex, QVector<QString> attributes) const;
Q_INVOKABLE virtual QVector<quint32> getIndices() const;
Q_INVOKABLE virtual QVector<quint32> findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const;
Q_INVOKABLE virtual QVariantMap getMeshExtents() const;
Q_INVOKABLE virtual bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes);
Q_INVOKABLE virtual QVariantMap scaleToFit(float unitScale);
static QMap<QString,int> ATTRIBUTES;
static std::map<QString, gpu::BufferView> gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList());
static QMap<QString,int> ATTRIBUTES;
static std::map<QString, gpu::BufferView> gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList());
ScriptableMesh& operator=(const ScriptableMesh& other) { _model=other._model; _mesh=other._mesh; _metadata=other._metadata; return *this; };
ScriptableMesh() : QObject(), _model(nullptr) {}
ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {}
ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {}
~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; }
Q_INVOKABLE QVariantList getAttributeValues(const QString& attributeName) const;
scriptable::MeshPointer getMeshPointer() const { return _mesh; }
public slots:
quint32 getNumParts() const;
quint32 getNumVertices() const;
quint32 getNumAttributes() const;
quint32 getNumIndices() const { return 0; }
QVector<QString> getAttributeNames() const;
Q_INVOKABLE int _getSlotNumber(const QString& attributeName) const;
QVariantMap getVertexAttributes(quint32 vertexIndex) const;
QVariantMap getVertexAttributes(quint32 vertexIndex, QVector<QString> attributes) const;
QVariantMap translate(const glm::vec3& translation);
QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN));
QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN));
QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN));
Q_INVOKABLE QVariantMap transform(const glm::mat4& transform);
QVector<quint32> getIndices() const;
QVector<quint32> findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const;
QVariantMap getMeshExtents() const;
bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes);
QVariantMap scaleToFit(float unitScale);
QVariantList getAttributeValues(const QString& attributeName) const;
int _getSlotNumber(const QString& attributeName) const;
QVariantMap translate(const glm::vec3& translation);
QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN));
QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN));
QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN));
QVariantMap transform(const glm::mat4& transform);
public:
operator bool() const { return _mesh != nullptr; }
ScriptableModelPointer _model;
scriptable::MeshPointer _mesh;
QVariantMap _metadata;
public slots:
// QScriptEngine-specific wrappers
QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue());
bool dedupeVertices(float epsilon = 1e-6);
bool recalculateNormals();
QScriptValue cloneMesh(bool recalcNormals = true);
QScriptValue unrollVertices(bool recalcNormals = true);
bool replaceMeshData(scriptable::ScriptableMeshPointer source, const QVector<QString>& attributeNames = QVector<QString>());
QString toOBJ();
};
// TODO: for now this is a part-specific wrapper around ScriptableMesh
class ScriptableMeshPart : public ScriptableMesh {
// TODO: part-specific wrapper for working with raw geometries
class ScriptableMeshPart : public QObject {
Q_OBJECT
public:
ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { _model=view._model; _mesh=view._mesh; return *this; };
ScriptableMeshPart(const ScriptableMeshPart& other) : ScriptableMesh(other._model, other._mesh) {}
ScriptableMeshPart() : ScriptableMesh(nullptr, nullptr) {}
~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; }
ScriptableMeshPart(ScriptableMeshPointer mesh) : ScriptableMesh(mesh->_model, mesh->_mesh) {}
Q_PROPERTY(QString topology READ getTopology)
Q_PROPERTY(quint32 numFaces READ getNumFaces)
scriptable::MeshPointer parentMesh;
int partIndex;
QString getTopology() const { return "triangles"; }
Q_INVOKABLE virtual quint32 getNumFaces() const { return getIndices().size() / 3; }
Q_INVOKABLE virtual QVector<quint32> getFace(quint32 faceIndex) const {
auto inds = getIndices();
ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; };
ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh) {}
ScriptableMeshPart() {}
~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; }
public slots:
QString getTopology() const { return "triangles"; }
quint32 getNumFaces() const { return parentMesh.getIndices().size() / 3; }
QVector<quint32> getFace(quint32 faceIndex) const {
auto inds = parentMesh.getIndices();
return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector<quint32>();
}
public:
scriptable::ScriptableMesh parentMesh;
int partIndex;
};
class GraphicsScriptingInterface : public QObject {
Q_OBJECT
public:
GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {}
GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {}
GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {}
public slots:
ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; }
};
}
Q_DECLARE_METATYPE(scriptable::ScriptableMesh)
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer)
Q_DECLARE_METATYPE(QVector<scriptable::ScriptableMeshPointer>)
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPart)
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer)
Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface)
// FIXME: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet?
// FIXME: MESHFACES: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet?
#include <memory>
namespace mesh {

View file

@ -6,6 +6,7 @@
#include <QtCore/QList>
#include <QtCore/QVariant>
#include <QtCore/QUuid>
#include <QPointer>
#include <memory>
#include <DependencyManager.h>
@ -16,50 +17,53 @@ namespace graphics {
namespace gpu {
class BufferView;
}
class QScriptValue;
namespace scriptable {
using Mesh = graphics::Mesh;
using MeshPointer = std::shared_ptr<scriptable::Mesh>;
class ScriptableModel;
using ScriptableModelPointer = QPointer<ScriptableModel>;
class ScriptableMesh;
class ScriptableMeshPart;
using ScriptableModelPointer = std::shared_ptr<scriptable::ScriptableModel>;
using ScriptableMeshPointer = std::shared_ptr<scriptable::ScriptableMesh>;
using ScriptableMeshPartPointer = std::shared_ptr<scriptable::ScriptableMeshPart>;
class ScriptableModel : public QObject, public std::enable_shared_from_this<ScriptableModel> {
using ScriptableMeshPointer = QPointer<ScriptableMesh>;
// abstract container for holding one or more scriptable meshes
class ScriptableModel : public QObject {
Q_OBJECT
public:
Q_PROPERTY(QVector<scriptable::ScriptableMeshPointer> meshes READ getMeshes)
Q_INVOKABLE QString toString() { return "[ScriptableModel " + objectName()+"]"; }
ScriptableModel(QObject* parent = nullptr) : QObject(parent) {}
ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {}
ScriptableModel& operator=(const ScriptableModel& view) {
objectID = view.objectID;
metadata = view.metadata;
meshes = view.meshes;
return *this;
}
~ScriptableModel() { qDebug() << "~ScriptableModel" << this; }
void mixin(const ScriptableModel& other) {
for (const auto& key : other.metadata.keys()) {
metadata[key] = other.metadata[key];
}
for(const auto&mesh : other.meshes) {
meshes << mesh;
}
}
QUuid objectID;
QVariantMap metadata;
QVector<scriptable::MeshPointer> meshes;
// TODO: in future accessors for these could go here
QVariantMap shapes;
QVariantMap materials;
QVariantMap armature;
QVector<scriptable::ScriptableMeshPointer> getMeshes() const;
Q_PROPERTY(QVector<scriptable::ScriptableMeshPointer> meshes READ getMeshes)
Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT)
Q_PROPERTY(QVariantMap metadata MEMBER metadata CONSTANT)
Q_INVOKABLE QString toString() const;
ScriptableModel(QObject* parent = nullptr) : QObject(parent) {}
ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {}
ScriptableModel& operator=(const ScriptableModel& view) { objectID = view.objectID; metadata = view.metadata; meshes = view.meshes; return *this; }
~ScriptableModel() { qDebug() << "~ScriptableModel" << this; }
void mixin(const ScriptableModel& other) {
for (const auto& key : other.metadata.keys()) { metadata[key] = other.metadata[key]; }
for (const auto& mesh : other.meshes) { meshes << mesh; }
}
// TODO: in future accessors for these could go here
// QVariantMap shapes;
// QVariantMap materials;
// QVariantMap armature;
QVector<scriptable::ScriptableMeshPointer> getMeshes();
const QVector<scriptable::ScriptableMeshPointer> getConstMeshes() const;
// QScriptEngine-specific wrappers
Q_INVOKABLE QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName);
};
// mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes
class ModelProvider {
public:
QVariantMap metadata;
@ -67,11 +71,12 @@ namespace scriptable {
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0;
};
using ModelProviderPointer = std::shared_ptr<scriptable::ModelProvider>;
// mixin class for Application to resolve UUIDs into a corresponding ModelProvider
class ModelProviderFactory : public Dependency {
public:
virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0;
};
}
Q_DECLARE_METATYPE(scriptable::MeshPointer)

View file

@ -579,38 +579,9 @@ scriptable::ScriptableModel Model::getScriptableModel(bool* ok) {
if (!isLoaded()) {
qDebug() << "Model::getScriptableModel -- !isLoaded";
if (ok) {
*ok = false;
}
return result;
return scriptable::ModelProvider::modelUnavailableError(ok);
}
// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry
#if 0 // renderGeometry approach
const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes();
Transform offset;
offset.setScale(_scale);
offset.postTranslate(_offset);
glm::mat4 offsetMat = offset.getMatrix();
for (std::shared_ptr<const graphics::Mesh> mesh : meshes) {
if (!mesh) {
continue;
}
qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName;
auto newmesh = mesh->map(
[=](glm::vec3 position) {
return glm::vec3(offsetMat * glm::vec4(position, 1.0f));
},
[=](glm::vec3 color) { return color; },
[=](glm::vec3 normal) {
return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f)));
},
[&](uint32_t index) { return index; });
newmesh->displayName = mesh->displayName;
result << newmesh;
}
#endif
const FBXGeometry& geometry = getFBXGeometry();
auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant {
QVector<float> floats;
@ -659,6 +630,33 @@ scriptable::ScriptableModel Model::getScriptableModel(bool* ok) {
qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size();
result.metadata["submeshes"] = submeshes;
return result;
// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry
#if 0 // renderGeometry approach
const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes();
Transform offset;
offset.setScale(_scale);
offset.postTranslate(_offset);
glm::mat4 offsetMat = offset.getMatrix();
for (std::shared_ptr<const graphics::Mesh> mesh : meshes) {
if (!mesh) {
continue;
}
qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName;
auto newmesh = mesh->map(
[=](glm::vec3 position) {
return glm::vec3(offsetMat * glm::vec4(position, 1.0f));
},
[=](glm::vec3 color) { return color; },
[=](glm::vec3 normal) {
return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f)));
},
[&](uint32_t index) { return index; });
newmesh->displayName = mesh->displayName;
result << newmesh;
}
#endif
}
void Model::calculateTriangleSets() {