mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 12:17:45 +02:00
cleanup
This commit is contained in:
parent
06afaa7470
commit
a08770c816
9 changed files with 722 additions and 656 deletions
|
@ -15,6 +15,9 @@
|
||||||
#include "OBJWriter.h"
|
#include "OBJWriter.h"
|
||||||
#include "ModelFormatLogging.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) {
|
static QString formatFloat(double n) {
|
||||||
// limit precision to 6, but don't output trailing zeros.
|
// limit precision to 6, but don't output trailing zeros.
|
||||||
QString s = QString::number(n, 'f', 6);
|
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);
|
const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(gpu::Stream::InputSlot::NORMAL);
|
||||||
gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements();
|
gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements();
|
||||||
for (gpu::BufferView::Index i = 0; i < numNormals; i++) {
|
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 << "vn ";
|
||||||
out << formatFloat(normal[0]) << " ";
|
out << formatFloat(normal[0]) << " ";
|
||||||
out << formatFloat(normal[1]) << " ";
|
out << formatFloat(normal[1]) << " ";
|
||||||
|
|
|
@ -8,9 +8,15 @@
|
||||||
#include <gpu/Stream.h>
|
#include <gpu/Stream.h>
|
||||||
|
|
||||||
#include <glm/gtc/packing.hpp>
|
#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
|
#ifdef DEBUG_BUFFERVIEW_SCRIPTING
|
||||||
#include "DebugNames.h"
|
#include "DebugNames.h"
|
||||||
|
QLoggingCategory bufferview_helpers{"hifi.bufferview"};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -61,6 +67,9 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co
|
||||||
const auto dataType = element.getType();
|
const auto dataType = element.getType();
|
||||||
const auto byteLength = element.getSize();
|
const auto byteLength = element.getSize();
|
||||||
const auto BYTES_PER_ELEMENT = byteLength / vecN;
|
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) {
|
if (BYTES_PER_ELEMENT == 1) {
|
||||||
switch(vecN) {
|
switch(vecN) {
|
||||||
case 2: setBufferViewElement<glm::u8vec2>(view, index, v); return true;
|
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;
|
glm::uint32 unused;
|
||||||
packNormalAndTangent(glmVecFromVariant<glm::vec3>(v), glm::vec3(), rawColor, unused);
|
packNormalAndTangent(glmVecFromVariant<glm::vec3>(v), glm::vec3(), rawColor, unused);
|
||||||
view.edit<glm::uint32>(index) = rawColor;
|
view.edit<glm::uint32>(index) = rawColor;
|
||||||
|
return true;
|
||||||
} else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) {
|
} else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) {
|
||||||
glm::uint32 packedNormal;// = glm::packSnorm3x10_1x2(glm::vec4(glmVecFromVariant<glm::vec3>(v), 0.0f));
|
glm::uint32 packedNormal;// = glm::packSnorm3x10_1x2(glm::vec4(glmVecFromVariant<glm::vec3>(v), 0.0f));
|
||||||
glm::uint32 unused;
|
glm::uint32 unused;
|
||||||
packNormalAndTangent(glm::vec3(), glmVecFromVariant<glm::vec3>(v), unused, packedNormal);
|
packNormalAndTangent(glm::vec3(), glmVecFromVariant<glm::vec3>(v), unused, packedNormal);
|
||||||
view.edit<glm::uint32>(index) = packedNormal;
|
view.edit<glm::uint32>(index) = packedNormal;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
setBufferViewElement<glm::u8vec4>(view, index, v); return true;
|
setBufferViewElement<glm::u8vec4>(view, index, v); return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (BYTES_PER_ELEMENT == 2) {
|
} 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) {
|
switch(vecN) {
|
||||||
case 2: setBufferViewElement<glm::u16vec2>(view, index, v); return true;
|
case 2: setBufferViewElement<glm::u16vec2>(view, index, v); return true;
|
||||||
case 3: setBufferViewElement<glm::u16vec3>(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;
|
const auto BYTES_PER_ELEMENT = byteLength / vecN;
|
||||||
Q_ASSERT(index < view.getNumElements());
|
Q_ASSERT(index < view.getNumElements());
|
||||||
Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT));
|
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) {
|
if (BYTES_PER_ELEMENT == 1) {
|
||||||
switch(vecN) {
|
switch(vecN) {
|
||||||
case 2: return getBufferViewElement<glm::u8vec2>(view, index, asArray);
|
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) {
|
} 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) {
|
switch(vecN) {
|
||||||
case 2: return getBufferViewElement<glm::u16vec2>(view, index, asArray);
|
case 2: return getBufferViewElement<glm::u16vec2>(view, index, asArray);
|
||||||
case 3: return getBufferViewElement<glm::u16vec3>(view, index, asArray);
|
case 3: return getBufferViewElement<glm::u16vec3>(view, index, asArray);
|
||||||
|
@ -193,3 +220,31 @@ const T glmVecFromVariant(const QVariant& v) {
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -8,11 +8,18 @@
|
||||||
|
|
||||||
#include <QtCore>
|
#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> QVariant glmVecToVariant(const T& v, bool asArray = false);
|
||||||
template <typename T> const T glmVecFromVariant(const QVariant& v);
|
template <typename T> const T glmVecFromVariant(const QVariant& v);
|
||||||
QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = "");
|
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);
|
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);
|
||||||
|
|
||||||
|
|
|
@ -17,27 +17,16 @@
|
||||||
#include "BaseScriptEngine.h"
|
#include "BaseScriptEngine.h"
|
||||||
#include "ScriptEngineLogging.h"
|
#include "ScriptEngineLogging.h"
|
||||||
#include "OBJWriter.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 <GeometryUtil.h>
|
||||||
|
|
||||||
#include <shared/QtHelpers.h>
|
#include <shared/QtHelpers.h>
|
||||||
|
|
||||||
|
|
||||||
#include <graphics-scripting/DebugNames.h>
|
#include <graphics-scripting/DebugNames.h>
|
||||||
|
|
||||||
#include <graphics-scripting/BufferViewHelpers.h>
|
#include <graphics-scripting/BufferViewHelpers.h>
|
||||||
|
|
||||||
#include "BufferViewScripting.h"
|
#include "BufferViewScripting.h"
|
||||||
|
|
||||||
#include "ScriptableMesh.h"
|
#include "ScriptableMesh.h"
|
||||||
|
|
||||||
using ScriptableMesh = scriptable::ScriptableMesh;
|
|
||||||
|
|
||||||
#include "ModelScriptingInterface.moc"
|
#include "ModelScriptingInterface.moc"
|
||||||
|
|
||||||
namespace {
|
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) {
|
QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) {
|
||||||
const auto& in = _in.getMeshes();
|
const auto& in = _in.getConstMeshes();
|
||||||
qCDebug(model_scripting) << "meshToOBJ" << in.size();
|
qCDebug(model_scripting) << "meshToOBJ" << in.size();
|
||||||
if (in.size()) {
|
if (in.size()) {
|
||||||
QList<scriptable::MeshPointer> meshes;
|
QList<scriptable::MeshPointer> meshes;
|
||||||
foreach (const auto meshProxy, in) {
|
foreach (auto meshProxy, in) {
|
||||||
qCDebug(model_scripting) << "meshToOBJ" << meshProxy.get();
|
qCDebug(model_scripting) << "meshToOBJ" << meshProxy;
|
||||||
if (meshProxy) {
|
if (meshProxy) {
|
||||||
meshes.append(getMeshPointer(meshProxy));
|
meshes.append(getMeshPointer(meshProxy));
|
||||||
}
|
}
|
||||||
|
@ -77,7 +109,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _
|
||||||
size_t totalColorCount { 0 };
|
size_t totalColorCount { 0 };
|
||||||
size_t totalNormalCount { 0 };
|
size_t totalNormalCount { 0 };
|
||||||
size_t totalIndexCount { 0 };
|
size_t totalIndexCount { 0 };
|
||||||
foreach (const scriptable::ScriptableMeshPointer meshProxy, in) {
|
foreach (auto& meshProxy, in) {
|
||||||
scriptable::MeshPointer mesh = getMeshPointer(meshProxy);
|
scriptable::MeshPointer mesh = getMeshPointer(meshProxy);
|
||||||
totalVertexCount += mesh->getNumVertices();
|
totalVertexCount += mesh->getNumVertices();
|
||||||
|
|
||||||
|
@ -113,7 +145,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _
|
||||||
|
|
||||||
uint32_t indexStartOffset { 0 };
|
uint32_t indexStartOffset { 0 };
|
||||||
|
|
||||||
foreach (const scriptable::ScriptableMeshPointer meshProxy, in) {
|
foreach (const auto& meshProxy, in) {
|
||||||
scriptable::MeshPointer mesh = getMeshPointer(meshProxy);
|
scriptable::MeshPointer mesh = getMeshPointer(meshProxy);
|
||||||
mesh->forEach(
|
mesh->forEach(
|
||||||
[&](glm::vec3 position){
|
[&](glm::vec3 position){
|
||||||
|
@ -175,8 +207,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _
|
||||||
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
|
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
|
||||||
|
|
||||||
|
|
||||||
scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result));
|
return engine()->toScriptValue(scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result)));
|
||||||
return engine()->toScriptValue(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) {
|
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 color){ return color; },
|
||||||
[&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); },
|
[&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); },
|
||||||
[&](uint32_t index){ return index; });
|
[&](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);
|
return engine()->toScriptValue(resultProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,8 +232,11 @@ QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshP
|
||||||
return (uint32_t)mesh->getNumVertices();
|
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);
|
auto mesh = getMeshPointer(meshProxy);
|
||||||
|
if (!mesh) {
|
||||||
|
return QScriptValue();
|
||||||
|
}
|
||||||
|
|
||||||
const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer();
|
const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer();
|
||||||
auto numVertices = mesh->getNumVertices();
|
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);
|
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 {
|
namespace {
|
||||||
template <typename T>
|
QScriptValue meshPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) {
|
||||||
gpu::BufferView bufferViewFromVector(QVector<T> elements, gpu::Element elementType) {
|
if (!in) {
|
||||||
auto vertexBuffer = std::make_shared<gpu::Buffer>(
|
return QScriptValue::NullValue;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
target->setVertexBuffer(cloneBufferView(source->getVertexBuffer()));
|
void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) {
|
||||||
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) {
|
|
||||||
auto obj = value.toQObject();
|
auto obj = value.toQObject();
|
||||||
//qDebug() << "meshFromScriptValue" << obj;
|
qDebug() << "meshPointerFromScriptValue" << obj;
|
||||||
if (auto tmp = qobject_cast<scriptable::ScriptableMesh*>(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!?
|
// FIXME: Why does above cast not work on Win32!?
|
||||||
if (!out) {
|
if (!out) {
|
||||||
auto smp = static_cast<scriptable::ScriptableMesh*>(obj);
|
if (auto smp = static_cast<scriptable::ScriptableMesh*>(obj)) {
|
||||||
//qDebug() << "meshFromScriptValue2" << smp;
|
qDebug() << "meshPointerFromScriptValue2" << smp;
|
||||||
out = smp->shared_from_this();
|
out = smp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QScriptValue meshesToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) {
|
QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) {
|
||||||
// QScriptValueList result;
|
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
|
||||||
QScriptValue result = engine->newArray();
|
// QScriptValue result = engine->newArray();
|
||||||
int i = 0;
|
// int i = 0;
|
||||||
foreach(scriptable::ScriptableMeshPointer const meshProxy, in->getMeshes()) {
|
// foreach(auto& mesh, in->getMeshes()) {
|
||||||
result.setProperty(i++, meshToScriptValue(engine, meshProxy));
|
// result.setProperty(i++, meshPointerToScriptValue(engine, mesh));
|
||||||
}
|
// }
|
||||||
return result;
|
// 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();
|
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++) {
|
for (int i = 0; i < length; i++) {
|
||||||
if (const auto meshProxy = qobject_cast<scriptable::ScriptableMesh*>(value.property(i).toQObject())) {
|
if (const auto meshProxy = qobject_cast<scriptable::ScriptableMesh*>(value.property(i).toQObject())) {
|
||||||
out->meshes.append(meshProxy->getMeshPointer());
|
out->meshes.append(meshProxy->getMeshPointer());
|
||||||
|
@ -749,79 +349,64 @@ namespace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void modelProxyFromScriptValue(const QScriptValue& object, scriptable::ScriptableModel &meshes) {
|
// FIXME: MESHFACES:
|
||||||
auto meshesProperty = object.property("meshes");
|
// QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) {
|
||||||
if (meshesProperty.property("length").toInt32() > 0) {
|
// QScriptValue obj = engine->newObject();
|
||||||
//meshes._meshes = qobject_cast<scriptable::ScriptableModelPointer>(meshesProperty.toQObject());
|
// obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices));
|
||||||
// qDebug() << "modelProxyFromScriptValue" << meshesProperty.property("length").toInt32() << meshesProperty.toVariant().typeName();
|
// return obj;
|
||||||
qScriptValueToSequence(meshesProperty, meshes.meshes);
|
// }
|
||||||
} else if (auto mesh = qobject_cast<scriptable::ScriptableMesh*>(object.toQObject())) {
|
// void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) {
|
||||||
meshes.meshes << mesh->getMeshPointer();
|
// qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices);
|
||||||
} else {
|
// }
|
||||||
qDebug() << "modelProxyFromScriptValue -- unrecognized input" << object.toVariant().toString();
|
// 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 qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector<quint32>& vector) {
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return qScriptValueFromSequence(engine, vector);
|
return qScriptValueFromSequence(engine, vector);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<mesh::MeshFace>& result) {
|
void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector<quint32>& 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) {
|
|
||||||
qScriptValueToSequence(array, result);
|
qScriptValueToSequence(array, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int meshUint32 = qRegisterMetaType<mesh::uint32>();
|
int meshUint32 = qRegisterMetaType<quint32>();
|
||||||
namespace mesh {
|
namespace mesh {
|
||||||
int meshUint32 = qRegisterMetaType<uint32>();
|
int meshUint32 = qRegisterMetaType<uint32>();
|
||||||
}
|
}
|
||||||
int qVectorMeshUint32 = qRegisterMetaType<QVector<mesh::uint32>>();
|
int qVectorMeshUint32 = qRegisterMetaType<QVector<quint32>>();
|
||||||
|
|
||||||
void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) {
|
void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) {
|
||||||
qScriptRegisterSequenceMetaType<QVector<scriptable::ScriptableMeshPointer>>(engine);
|
qScriptRegisterSequenceMetaType<QVector<scriptable::ScriptableMeshPointer>>(engine);
|
||||||
qScriptRegisterSequenceMetaType<mesh::MeshFaces>(engine);
|
qScriptRegisterSequenceMetaType<QVector<quint32>>(engine);
|
||||||
qScriptRegisterSequenceMetaType<QVector<mesh::uint32>>(engine);
|
|
||||||
qScriptRegisterMetaType(engine, modelProxyToScriptValue, modelProxyFromScriptValue);
|
|
||||||
|
|
||||||
qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue);
|
qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, meshToScriptValue, meshFromScriptValue);
|
qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, meshesToScriptValue, meshesFromScriptValue);
|
qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue);
|
|
||||||
qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue);
|
// 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 ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) {
|
||||||
MeshPointer result;
|
MeshPointer result;
|
||||||
if (!meshProxy) {
|
if (!meshProxy) {
|
||||||
if (context()){
|
if (context()){
|
||||||
context()->throwError("expected meshProxy as first parameter");
|
context()->throwError("expected meshProxy as first parameter");
|
||||||
|
} else {
|
||||||
|
qDebug() << "expected meshProxy as first parameter";
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -829,6 +414,8 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo
|
||||||
if (!mesh) {
|
if (!mesh) {
|
||||||
if (context()) {
|
if (context()) {
|
||||||
context()->throwError("expected valid meshProxy as first parameter");
|
context()->throwError("expected valid meshProxy as first parameter");
|
||||||
|
} else {
|
||||||
|
qDebug() << "expected valid meshProxy as first parameter";
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,30 +38,20 @@ public slots:
|
||||||
*/
|
*/
|
||||||
void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue());
|
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);
|
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 appendMeshes(scriptable::ScriptableModel in);
|
||||||
QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform);
|
QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform);
|
||||||
QScriptValue newMesh(const QVector<glm::vec3>& vertices,
|
QScriptValue newMesh(const QVector<glm::vec3>& vertices,
|
||||||
const QVector<glm::vec3>& normals,
|
const QVector<glm::vec3>& normals,
|
||||||
const QVector<mesh::MeshFace>& faces);
|
const QVector<mesh::MeshFace>& faces);
|
||||||
QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy);
|
QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy);
|
||||||
QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex);
|
QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy);
|
scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy);
|
||||||
|
scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy);
|
||||||
|
scriptable::MeshPointer getMeshPointer(const scriptable::ScriptableMesh& meshProxy);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,20 @@
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
#include <glm/gtx/norm.hpp>
|
#include <glm/gtx/norm.hpp>
|
||||||
|
#include <glm/gtx/string_cast.hpp>
|
||||||
#include <graphics/Geometry.h>
|
#include <graphics/Geometry.h>
|
||||||
#include <graphics-scripting/DebugNames.h>
|
#include <graphics-scripting/DebugNames.h>
|
||||||
#include <graphics-scripting/BufferViewHelpers.h>
|
#include <graphics-scripting/BufferViewHelpers.h>
|
||||||
|
#include <graphics-scripting/BufferViewScripting.h>
|
||||||
#include <Extents.h>
|
#include <Extents.h>
|
||||||
|
|
||||||
#include "ScriptableMesh.moc"
|
#include "ScriptableMesh.moc"
|
||||||
|
|
||||||
#include <RegisteredMetaTypes.h>
|
#include <RegisteredMetaTypes.h>
|
||||||
|
#include <BaseScriptEngine.h>
|
||||||
|
#include <QtScript/QScriptValue>
|
||||||
|
|
||||||
|
#include "OBJWriter.h"
|
||||||
|
|
||||||
QLoggingCategory mesh_logging { "hifi.scripting.mesh" };
|
QLoggingCategory mesh_logging { "hifi.scripting.mesh" };
|
||||||
|
|
||||||
|
@ -41,10 +47,26 @@ QMap<QString,int> ScriptableMesh::ATTRIBUTES{
|
||||||
{"texcoord4", gpu::Stream::TEXCOORD4 },
|
{"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;
|
QVector<scriptable::ScriptableMeshPointer> out;
|
||||||
for(auto& mesh : meshes) {
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -134,15 +156,16 @@ QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) {
|
bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) {
|
||||||
qDebug() << "setVertexAttributes" << vertexIndex << attributes;
|
//qDebug() << "setVertexAttributes" << vertexIndex << attributes;
|
||||||
for (auto& a : gatherBufferViews(getMeshPointer())) {
|
for (auto& a : gatherBufferViews(getMeshPointer())) {
|
||||||
const auto& name = a.first;
|
const auto& name = a.first;
|
||||||
const auto& value = attributes.value(name);
|
const auto& value = attributes.value(name);
|
||||||
if (value.isValid()) {
|
if (value.isValid()) {
|
||||||
auto& view = a.second;
|
auto& view = a.second;
|
||||||
|
//qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name;
|
||||||
bufferViewElementFromVariant(view, vertexIndex, value);
|
bufferViewElementFromVariant(view, vertexIndex, value);
|
||||||
} else {
|
} else {
|
||||||
qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name;
|
//qCDebug(mesh_logging) << "(skipping) setVertexAttributes" << vertexIndex << name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -357,3 +380,375 @@ std::map<QString, gpu::BufferView> ScriptableMesh::gatherBufferViews(scriptable:
|
||||||
}
|
}
|
||||||
return attributeViews;
|
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() });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,16 @@
|
||||||
#include <QtCore/QList>
|
#include <QtCore/QList>
|
||||||
#include <QtCore/QVariant>
|
#include <QtCore/QVariant>
|
||||||
#include <QtCore/QUuid>
|
#include <QtCore/QUuid>
|
||||||
|
#include <QPointer>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
|
||||||
#include <graphics-scripting/ScriptableModel.h>
|
#include <graphics-scripting/ScriptableModel.h>
|
||||||
|
|
||||||
|
#include <QtScript/QScriptable>
|
||||||
|
#include <QtScript/QScriptValue>
|
||||||
|
|
||||||
namespace graphics {
|
namespace graphics {
|
||||||
class Mesh;
|
class Mesh;
|
||||||
}
|
}
|
||||||
|
@ -19,91 +23,112 @@ namespace gpu {
|
||||||
class BufferView;
|
class BufferView;
|
||||||
}
|
}
|
||||||
namespace scriptable {
|
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
|
Q_OBJECT
|
||||||
public:
|
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 numParts READ getNumParts)
|
||||||
Q_PROPERTY(quint32 numAttributes READ getNumAttributes)
|
Q_PROPERTY(quint32 numAttributes READ getNumAttributes)
|
||||||
Q_PROPERTY(quint32 numVertices READ getNumVertices)
|
Q_PROPERTY(quint32 numVertices READ getNumVertices)
|
||||||
Q_PROPERTY(quint32 numIndices READ getNumIndices)
|
Q_PROPERTY(quint32 numIndices READ getNumIndices)
|
||||||
|
Q_PROPERTY(QVariantMap metadata MEMBER _metadata)
|
||||||
Q_PROPERTY(QVector<QString> attributeNames READ getAttributeNames)
|
Q_PROPERTY(QVector<QString> attributeNames READ getAttributeNames)
|
||||||
|
|
||||||
virtual scriptable::MeshPointer getMeshPointer() const { return _mesh; }
|
static QMap<QString,int> ATTRIBUTES;
|
||||||
Q_INVOKABLE virtual quint32 getNumParts() const;
|
static std::map<QString, gpu::BufferView> gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList());
|
||||||
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;
|
ScriptableMesh& operator=(const ScriptableMesh& other) { _model=other._model; _mesh=other._mesh; _metadata=other._metadata; return *this; };
|
||||||
static std::map<QString, gpu::BufferView> gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList());
|
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);
|
QVector<quint32> getIndices() const;
|
||||||
QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN));
|
QVector<quint32> findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const;
|
||||||
QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN));
|
QVariantMap getMeshExtents() const;
|
||||||
QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN));
|
bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes);
|
||||||
Q_INVOKABLE QVariantMap transform(const glm::mat4& transform);
|
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
|
// TODO: part-specific wrapper for working with raw geometries
|
||||||
class ScriptableMeshPart : public ScriptableMesh {
|
class ScriptableMeshPart : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
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(QString topology READ getTopology)
|
||||||
Q_PROPERTY(quint32 numFaces READ getNumFaces)
|
Q_PROPERTY(quint32 numFaces READ getNumFaces)
|
||||||
|
|
||||||
scriptable::MeshPointer parentMesh;
|
ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; };
|
||||||
int partIndex;
|
ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh) {}
|
||||||
QString getTopology() const { return "triangles"; }
|
ScriptableMeshPart() {}
|
||||||
Q_INVOKABLE virtual quint32 getNumFaces() const { return getIndices().size() / 3; }
|
~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; }
|
||||||
Q_INVOKABLE virtual QVector<quint32> getFace(quint32 faceIndex) const {
|
|
||||||
auto inds = getIndices();
|
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>();
|
return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector<quint32>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
scriptable::ScriptableMesh parentMesh;
|
||||||
|
int partIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GraphicsScriptingInterface : public QObject {
|
class GraphicsScriptingInterface : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {}
|
GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {}
|
||||||
GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {}
|
GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {}
|
||||||
public slots:
|
public slots:
|
||||||
ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; }
|
ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; }
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(scriptable::ScriptableMesh)
|
|
||||||
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer)
|
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer)
|
||||||
Q_DECLARE_METATYPE(QVector<scriptable::ScriptableMeshPointer>)
|
Q_DECLARE_METATYPE(QVector<scriptable::ScriptableMeshPointer>)
|
||||||
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPart)
|
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer)
|
||||||
Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface)
|
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>
|
#include <memory>
|
||||||
|
|
||||||
namespace mesh {
|
namespace mesh {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <QtCore/QList>
|
#include <QtCore/QList>
|
||||||
#include <QtCore/QVariant>
|
#include <QtCore/QVariant>
|
||||||
#include <QtCore/QUuid>
|
#include <QtCore/QUuid>
|
||||||
|
#include <QPointer>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
@ -16,50 +17,53 @@ namespace graphics {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
class BufferView;
|
class BufferView;
|
||||||
}
|
}
|
||||||
|
class QScriptValue;
|
||||||
|
|
||||||
namespace scriptable {
|
namespace scriptable {
|
||||||
using Mesh = graphics::Mesh;
|
using Mesh = graphics::Mesh;
|
||||||
using MeshPointer = std::shared_ptr<scriptable::Mesh>;
|
using MeshPointer = std::shared_ptr<scriptable::Mesh>;
|
||||||
|
|
||||||
class ScriptableModel;
|
class ScriptableModel;
|
||||||
|
using ScriptableModelPointer = QPointer<ScriptableModel>;
|
||||||
class ScriptableMesh;
|
class ScriptableMesh;
|
||||||
class ScriptableMeshPart;
|
using ScriptableMeshPointer = QPointer<ScriptableMesh>;
|
||||||
using ScriptableModelPointer = std::shared_ptr<scriptable::ScriptableModel>;
|
|
||||||
using ScriptableMeshPointer = std::shared_ptr<scriptable::ScriptableMesh>;
|
// abstract container for holding one or more scriptable meshes
|
||||||
using ScriptableMeshPartPointer = std::shared_ptr<scriptable::ScriptableMeshPart>;
|
class ScriptableModel : public QObject {
|
||||||
class ScriptableModel : public QObject, public std::enable_shared_from_this<ScriptableModel> {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
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;
|
QUuid objectID;
|
||||||
QVariantMap metadata;
|
QVariantMap metadata;
|
||||||
QVector<scriptable::MeshPointer> meshes;
|
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 {
|
class ModelProvider {
|
||||||
public:
|
public:
|
||||||
QVariantMap metadata;
|
QVariantMap metadata;
|
||||||
|
@ -67,11 +71,12 @@ namespace scriptable {
|
||||||
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0;
|
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0;
|
||||||
};
|
};
|
||||||
using ModelProviderPointer = std::shared_ptr<scriptable::ModelProvider>;
|
using ModelProviderPointer = std::shared_ptr<scriptable::ModelProvider>;
|
||||||
|
|
||||||
|
// mixin class for Application to resolve UUIDs into a corresponding ModelProvider
|
||||||
class ModelProviderFactory : public Dependency {
|
class ModelProviderFactory : public Dependency {
|
||||||
public:
|
public:
|
||||||
virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0;
|
virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(scriptable::MeshPointer)
|
Q_DECLARE_METATYPE(scriptable::MeshPointer)
|
||||||
|
|
|
@ -579,38 +579,9 @@ scriptable::ScriptableModel Model::getScriptableModel(bool* ok) {
|
||||||
|
|
||||||
if (!isLoaded()) {
|
if (!isLoaded()) {
|
||||||
qDebug() << "Model::getScriptableModel -- !isLoaded";
|
qDebug() << "Model::getScriptableModel -- !isLoaded";
|
||||||
if (ok) {
|
return scriptable::ModelProvider::modelUnavailableError(ok);
|
||||||
*ok = false;
|
|
||||||
}
|
|
||||||
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
|
|
||||||
const FBXGeometry& geometry = getFBXGeometry();
|
const FBXGeometry& geometry = getFBXGeometry();
|
||||||
auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant {
|
auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant {
|
||||||
QVector<float> floats;
|
QVector<float> floats;
|
||||||
|
@ -659,6 +630,33 @@ scriptable::ScriptableModel Model::getScriptableModel(bool* ok) {
|
||||||
qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size();
|
qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size();
|
||||||
result.metadata["submeshes"] = submeshes;
|
result.metadata["submeshes"] = submeshes;
|
||||||
return result;
|
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() {
|
void Model::calculateTriangleSets() {
|
||||||
|
|
Loading…
Reference in a new issue