* remove Model_temporary_hack

* split gpuhelpers and mesh part
* fix objwriter
* more work on bufferview helpers
* cr cleanup
This commit is contained in:
humbletim 2018-02-26 04:58:22 -05:00
parent 7c571cd431
commit f824edd04e
33 changed files with 1938 additions and 1317 deletions

View file

@ -608,22 +608,25 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt
class ApplicationMeshProvider : public scriptable::ModelProviderFactory {
public:
virtual scriptable::ModelProviderPointer lookupModelProvider(const QUuid& uuid) override {
QString error;
scriptable::ModelProviderPointer provider;
if (uuid.isNull()) {
provider = nullptr;
} else if (auto entityInterface = getEntityModelProvider(static_cast<EntityItemID>(uuid))) {
provider = entityInterface;
} else if (auto overlayInterface = getOverlayModelProvider(static_cast<OverlayID>(uuid))) {
provider = overlayInterface;
} else if (auto avatarInterface = getAvatarModelProvider(uuid)) {
provider = avatarInterface;
bool success;
if (auto nestable = DependencyManager::get<SpatialParentFinder>()->find(uuid, success).lock()) {
auto type = nestable->getNestableType();
#ifdef SCRIPTABLE_MESH_DEBUG
qCDebug(interfaceapp) << "ApplicationMeshProvider::lookupModelProvider" << uuid << SpatiallyNestable::nestableTypeToString(type);
#endif
switch (type) {
case NestableType::Entity:
return getEntityModelProvider(static_cast<EntityItemID>(uuid));
case NestableType::Overlay:
return getOverlayModelProvider(static_cast<OverlayID>(uuid));
case NestableType::Avatar:
return getAvatarModelProvider(uuid);
}
}
return provider;
return nullptr;
}
private:
scriptable::ModelProviderPointer getEntityModelProvider(EntityItemID entityID) {
scriptable::ModelProviderPointer provider;
auto entityTreeRenderer = qApp->getEntities();
@ -649,6 +652,8 @@ public:
} else {
qCWarning(interfaceapp) << "no renderer for overlay ID" << overlayID.toString();
}
} else {
qCWarning(interfaceapp) << "overlay not found" << overlayID.toString();
}
return provider;
}

View file

@ -200,3 +200,14 @@ Transform Cube3DOverlay::evalRenderTransform() {
transform.setRotation(rotation);
return transform;
}
scriptable::ScriptableModelBase Cube3DOverlay::getScriptableModel() {
auto geometryCache = DependencyManager::get<GeometryCache>();
auto vertexColor = ColorUtils::toVec3(_color);
scriptable::ScriptableModelBase result;
if (auto mesh = geometryCache->meshFromShape(GeometryCache::Cube, vertexColor)) {
result.objectID = getID();
result.append(mesh);
}
return result;
}

View file

@ -36,6 +36,7 @@ public:
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
virtual scriptable::ScriptableModelBase getScriptableModel() override;
protected:
Transform evalRenderTransform() override;

View file

@ -665,6 +665,16 @@ void ModelOverlay::processMaterials() {
}
}
bool ModelOverlay::canReplaceModelMeshPart(int meshIndex, int partIndex) {
// TODO: bounds checking; for now just used to indicate provider generally supports mesh updates
return _model && _model->isLoaded();
}
bool ModelOverlay::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) {
return canReplaceModelMeshPart(meshIndex, partIndex) &&
_model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex);
}
scriptable::ScriptableModelBase ModelOverlay::getScriptableModel() {
if (!_model || !_model->isLoaded()) {
return Base3DOverlay::getScriptableModel();

View file

@ -63,6 +63,9 @@ public:
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override;
virtual scriptable::ScriptableModelBase getScriptableModel() override;
virtual bool canReplaceModelMeshPart(int meshIndex, int partIndex) override;
virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override;
protected:
Transform evalRenderTransform() override;

View file

@ -123,3 +123,15 @@ Transform Sphere3DOverlay::evalRenderTransform() {
return transform;
}
scriptable::ScriptableModelBase Sphere3DOverlay::getScriptableModel() {
auto geometryCache = DependencyManager::get<GeometryCache>();
auto vertexColor = ColorUtils::toVec3(_color);
scriptable::ScriptableModelBase result;
if (auto mesh = geometryCache->meshFromShape(GeometryCache::Sphere, vertexColor)) {
result.objectID = getID();
result.append(mesh);
}
return result;
}

View file

@ -28,6 +28,7 @@ public:
virtual Sphere3DOverlay* createClone() const override;
virtual scriptable::ScriptableModelBase getScriptableModel() override;
protected:
Transform evalRenderTransform() override;
};

View file

@ -1800,6 +1800,6 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel() {
return scriptable::ScriptableModelBase();
}
auto result = _skeletonModel->getScriptableModel();
result.objectID = getSessionUUID();
result.objectID = getSessionUUID().isNull() ? AVATAR_SELF_ID : getSessionUUID();
return result;
}

View file

@ -961,8 +961,7 @@ bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) {
}
scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel() {
ModelPointer model;
withReadLock([&] { model = _model; });
auto model = resultWithReadLock<ModelPointer>([this]{ return _model; });
if (!model || !model->isLoaded()) {
return scriptable::ScriptableModelBase();
@ -973,9 +972,14 @@ scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScript
return result;
}
bool render::entities::ModelEntityRenderer::canReplaceModelMeshPart(int meshIndex, int partIndex) {
// TODO: for now this method is just used to indicate that this provider generally supports mesh updates
auto model = resultWithReadLock<ModelPointer>([this]{ return _model; });
return model && model->isLoaded();
}
bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) {
ModelPointer model;
withReadLock([&] { model = _model; });
auto model = resultWithReadLock<ModelPointer>([this]{ return _model; });
if (!model || !model->isLoaded()) {
return false;

View file

@ -143,6 +143,7 @@ class ModelEntityRenderer : public TypedEntityRenderer<RenderableModelEntityItem
public:
ModelEntityRenderer(const EntityItemPointer& entity);
virtual scriptable::ScriptableModelBase getScriptableModel() override;
virtual bool canReplaceModelMeshPart(int meshIndex, int partIndex) override;
virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override;
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override;

View file

@ -9,10 +9,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "OBJWriter.h"
#include <QFile>
#include <QFileInfo>
#include "graphics/Geometry.h"
#include "OBJWriter.h"
#include <graphics/BufferViewHelpers.h>
#include <graphics/Geometry.h>
#include "ModelFormatLogging.h"
static QString formatFloat(double n) {
@ -46,59 +48,60 @@ bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes) {
QList<int> meshNormalStartOffset;
int currentVertexStartOffset = 0;
int currentNormalStartOffset = 0;
int subMeshIndex = 0;
out << "# OBJWriter::writeOBJToTextStream\n";
// write out vertices (and maybe colors)
foreach (const MeshPointer& mesh, meshes) {
out << "# vertices::subMeshIndex " << subMeshIndex++ << "\n";
meshVertexStartOffset.append(currentVertexStartOffset);
const gpu::BufferView& vertexBuffer = mesh->getVertexBuffer();
const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(gpu::Stream::COLOR);
gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements();
gpu::BufferView::Index colorIndex = 0;
auto vertices = buffer_helpers::bufferToVector<glm::vec3>(mesh->getVertexBuffer(), "mesh.vertices");
auto colors = buffer_helpers::mesh::attributeToVector<glm::vec3>(mesh, gpu::Stream::COLOR);
int vertexCount = 0;
gpu::BufferView::Iterator<const glm::vec3> vertexItr = vertexBuffer.cbegin<const glm::vec3>();
while (vertexItr != vertexBuffer.cend<const glm::vec3>()) {
glm::vec3 v = *vertexItr;
gpu::BufferView::Index numColors = colors.size();
int i = 0;
for (const auto& v : vertices) {
out << "v ";
out << formatFloat(v[0]) << " ";
out << formatFloat(v[1]) << " ";
out << formatFloat(v[2]);
if (colorIndex < numColors) {
glm::vec3 color = colorsBufferView.get<glm::vec3>(colorIndex);
if (i < numColors) {
const glm::vec3& color = colors[i];
out << " " << formatFloat(color[0]);
out << " " << formatFloat(color[1]);
out << " " << formatFloat(color[2]);
colorIndex++;
}
out << "\n";
vertexItr++;
vertexCount++;
i++;
}
currentVertexStartOffset += vertexCount;
currentVertexStartOffset += i;
}
out << "\n";
// write out normals
bool haveNormals = true;
subMeshIndex = 0;
foreach (const MeshPointer& mesh, meshes) {
out << "# normals::subMeshIndex " << subMeshIndex++ << "\n";
meshNormalStartOffset.append(currentNormalStartOffset);
const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(gpu::Stream::InputSlot::NORMAL);
gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements();
for (gpu::BufferView::Index i = 0; i < numNormals; i++) {
glm::vec3 normal = normalsBufferView.get<glm::vec3>(i);
auto normals = buffer_helpers::mesh::attributeToVector<glm::vec3>(mesh, gpu::Stream::NORMAL);
for (const auto& normal : normals) {
out << "vn ";
out << formatFloat(normal[0]) << " ";
out << formatFloat(normal[1]) << " ";
out << formatFloat(normal[2]) << "\n";
}
currentNormalStartOffset += numNormals;
currentNormalStartOffset += normals.size();
}
out << "\n";
// write out faces
int nth = 0;
subMeshIndex = 0;
foreach (const MeshPointer& mesh, meshes) {
out << "# faces::subMeshIndex " << subMeshIndex++ << "\n";
currentVertexStartOffset = meshVertexStartOffset.takeFirst();
currentNormalStartOffset = meshNormalStartOffset.takeFirst();
@ -106,45 +109,46 @@ bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes) {
const gpu::BufferView& indexBuffer = mesh->getIndexBuffer();
graphics::Index partCount = (graphics::Index)mesh->getNumParts();
QString name = (!mesh->displayName.size() ? QString("mesh-%1-part").arg(nth) : QString::fromStdString(mesh->displayName))
.replace(QRegExp("[^-_a-zA-Z0-9]"), "_");
for (int partIndex = 0; partIndex < partCount; partIndex++) {
const graphics::Mesh::Part& part = partBuffer.get<graphics::Mesh::Part>(partIndex);
out << "g part-" << nth++ << "\n";
// graphics::Mesh::TRIANGLES
// TODO -- handle other formats
gpu::BufferView::Iterator<const uint32_t> indexItr = indexBuffer.cbegin<uint32_t>();
indexItr += part._startIndex;
int indexCount = 0;
while (indexItr != indexBuffer.cend<uint32_t>() && indexCount < part._numIndices) {
uint32_t index0 = *indexItr;
indexItr++;
indexCount++;
if (indexItr == indexBuffer.cend<uint32_t>() || indexCount >= part._numIndices) {
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3";
break;
}
uint32_t index1 = *indexItr;
indexItr++;
indexCount++;
if (indexItr == indexBuffer.cend<uint32_t>() || indexCount >= part._numIndices) {
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3";
break;
}
uint32_t index2 = *indexItr;
indexItr++;
indexCount++;
out << QString("g %1-%2-%3\n").arg(subMeshIndex, 3, 10, QChar('0')).arg(name).arg(partIndex);
auto indices = buffer_helpers::bufferToVector<gpu::uint32>(mesh->getIndexBuffer(), "mesh.indices");
auto face = [&](uint32_t i0, uint32_t i1, uint32_t i2) {
out << "f ";
if (haveNormals) {
out << currentVertexStartOffset + index0 + 1 << "//" << currentVertexStartOffset + index0 + 1 << " ";
out << currentVertexStartOffset + index1 + 1 << "//" << currentVertexStartOffset + index1 + 1 << " ";
out << currentVertexStartOffset + index2 + 1 << "//" << currentVertexStartOffset + index2 + 1 << "\n";
out << currentVertexStartOffset + indices[i0] + 1 << "//" << currentVertexStartOffset + indices[i0] + 1 << " ";
out << currentVertexStartOffset + indices[i1] + 1 << "//" << currentVertexStartOffset + indices[i1] + 1 << " ";
out << currentVertexStartOffset + indices[i2] + 1 << "//" << currentVertexStartOffset + indices[i2] + 1 << "\n";
} else {
out << currentVertexStartOffset + index0 + 1 << " ";
out << currentVertexStartOffset + index1 + 1 << " ";
out << currentVertexStartOffset + index2 + 1 << "\n";
out << currentVertexStartOffset + indices[i0] + 1 << " ";
out << currentVertexStartOffset + indices[i1] + 1 << " ";
out << currentVertexStartOffset + indices[i2] + 1 << "\n";
}
};
uint32_t len = part._startIndex + part._numIndices;
qCDebug(modelformat) << "OBJWriter -- part" << partIndex << "topo" << part._topology << "index elements";
if (part._topology == graphics::Mesh::TRIANGLES && len % 3 != 0) {
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 3" << len;
}
if (part._topology == graphics::Mesh::QUADS && len % 4 != 0) {
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 4" << len;
}
if (len > indexBuffer.getNumElements()) {
qCDebug(modelformat) << "OBJWriter -- len > index size" << len << indexBuffer.getNumElements();
}
if (part._topology == graphics::Mesh::QUADS) {
for (uint32_t idx = part._startIndex; idx+3 < len; idx += 4) {
face(idx+0, idx+1, idx+3);
face(idx+1, idx+2, idx+3);
}
} else if (part._topology == graphics::Mesh::TRIANGLES) {
for (uint32_t idx = part._startIndex; idx+2 < len; idx += 3) {
face(idx+0, idx+1, idx+2);
}
}
out << "\n";

View file

@ -19,7 +19,7 @@ using namespace gpu;
using ElementArray = std::array<Element, Stream::NUM_INPUT_SLOTS>;
const ElementArray& getDefaultElements() {
const ElementArray& Stream::getDefaultElements() {
static ElementArray defaultElements{{
//POSITION = 0,
Element::VEC3F_XYZ,

View file

@ -23,6 +23,8 @@
namespace gpu {
class Element;
// Stream namespace class
class Stream {
public:
@ -49,6 +51,8 @@ public:
typedef uint8 Slot;
static const std::array<Element, InputSlot::NUM_INPUT_SLOTS>& getDefaultElements();
// Frequency describer
enum Frequency {
PER_VERTEX = 0,

View file

@ -1,72 +0,0 @@
#include "BufferViewScripting.h"
#include <QDebug>
#include <QVariant>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptEngine>
#include <gpu/Buffer.h>
#include <gpu/Format.h>
#include <gpu/Stream.h>
#include <graphics/BufferViewHelpers.h>
template <typename T>
QScriptValue getBufferViewElement(QScriptEngine* js, const gpu::BufferView& view, quint32 index, bool asArray = false) {
return glmVecToScriptValue(js, view.get<T>(index), asArray);
}
QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) {
QVariant result = buffer_helpers::toVariant(view, index, asArray, hint);
if (!result.isValid()) {
return QScriptValue::NullValue;
}
return engine->toScriptValue(result);
}
template <typename T>
void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QScriptValue& v) {
view.edit<T>(index) = glmVecFromScriptValue<T>(v);
}
bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index) {
return buffer_helpers::fromVariant(view, index, v.toVariant());
}
template <typename T>
QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray) {
static const auto len = T().length();
const auto& components = asArray ? buffer_helpers::ZERO123 : buffer_helpers::XYZW;
auto obj = asArray ? js->newArray() : js->newObject();
for (int i = 0; i < len ; i++) {
const auto key = components[i];
const auto value = v[i];
if (value != value) { // NAN
#ifdef DEV_BUILD
qWarning().nospace()<< "vec" << len << "." << key << " converting NAN to javascript NaN.... " << value;
#endif
obj.setProperty(key, js->globalObject().property("NaN"));
} else {
obj.setProperty(key, value);
}
}
return obj;
}
template <typename T>
const T glmVecFromScriptValue(const QScriptValue& v) {
static const auto len = T().length();
const auto& components = v.property("x").isValid() ? buffer_helpers::XYZW : buffer_helpers::ZERO123;
T result;
for (int i = 0; i < len ; i++) {
const auto key = components[i];
const auto value = v.property(key).toNumber();
#ifdef DEV_BUILD
if (value != value) { // NAN
qWarning().nospace()<< "vec" << len << "." << key << " NAN received from script.... " << v.toVariant().toString();
}
#endif
result[i] = value;
}
return result;
}

View file

@ -1,11 +0,0 @@
#pragma once
#include <QtCore>
namespace gpu { class BufferView; }
class QScriptValue;
class QScriptEngine;
template <typename T> QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray = false);
template <typename T> const T glmVecFromScriptValue(const QScriptValue& v);
QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = "");
bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index);

View file

@ -74,7 +74,7 @@ namespace scriptable {
public:
NestableType modelProviderType;
virtual scriptable::ScriptableModelBase getScriptableModel() = 0;
virtual bool canReplaceModelMeshPart(int meshIndex, int partIndex) { return false; }
virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) { return false; }
};
@ -88,7 +88,6 @@ namespace scriptable {
void modelRemovedFromScene(const QUuid& objectID, NestableType nestableType, const ModelPointer& sender);
};
using uint32 = quint32;
class ScriptableModel;
using ScriptableModelPointer = QPointer<ScriptableModel>;
class ScriptableMesh;

View file

@ -15,13 +15,16 @@
#include "RegisteredMetaTypes.h"
#include "ScriptEngineLogging.h"
#include "ScriptableMesh.h"
#include "ScriptableMeshPart.h"
#include <GeometryUtil.h>
#include <QUuid>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptValueIterator>
#include <graphics/BufferViewHelpers.h>
#include <graphics/GpuHelpers.h>
#include <shared/QtHelpers.h>
#include <SpatiallyNestable.h>
GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent), QScriptable() {
if (auto scriptEngine = qobject_cast<QScriptEngine*>(parent)) {
@ -29,21 +32,45 @@ GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObjec
}
}
bool GraphicsScriptingInterface::updateModelObject(QUuid uuid, const scriptable::ScriptableModelPointer model) {
if (auto provider = getModelProvider(uuid)) {
if (auto base = model->operator scriptable::ScriptableModelBasePointer()) {
#ifdef SCRIPTABLE_MESH_DEBUG
qDebug() << "replaceScriptableModelMeshPart" << model->toString() << -1 << -1;
#endif
return provider->replaceScriptableModelMeshPart(base, -1, -1);
} else {
qDebug() << "replaceScriptableModelMeshPart -- !base" << model << base << -1 << -1;
}
void GraphicsScriptingInterface::jsThrowError(const QString& error) {
if (context()) {
context()->throwError(error);
} else {
qDebug() << "replaceScriptableModelMeshPart -- !provider";
qCWarning(graphics_scripting) << "GraphicsScriptingInterface::jsThrowError (without valid JS context):" << error;
}
}
bool GraphicsScriptingInterface::canUpdateModel(QUuid uuid, int meshIndex, int partNumber) {
auto provider = getModelProvider(uuid);
return provider && provider->canReplaceModelMeshPart(meshIndex, partNumber);
}
bool GraphicsScriptingInterface::updateModel(QUuid uuid, const scriptable::ScriptableModelPointer& model) {
if (!model) {
jsThrowError("null model argument");
}
return false;
auto base = model->operator scriptable::ScriptableModelBasePointer();
if (!base) {
jsThrowError("could not get base model pointer");
return false;
}
auto provider = getModelProvider(uuid);
if (!provider) {
jsThrowError("provider unavailable");
return false;
}
if (!provider->canReplaceModelMeshPart(-1, -1)) {
jsThrowError("provider does not support updating mesh parts");
return false;
}
#ifdef SCRIPTABLE_MESH_DEBUG
qDebug() << "replaceScriptableModelMeshPart" << model->toString() << -1 << -1;
#endif
return provider->replaceScriptableModelMeshPart(base, -1, -1);
}
scriptable::ModelProviderPointer GraphicsScriptingInterface::getModelProvider(QUuid uuid) {
@ -57,28 +84,26 @@ scriptable::ModelProviderPointer GraphicsScriptingInterface::getModelProvider(QU
} else {
error = "appProvider unavailable";
}
if (context()) {
context()->throwError(error);
} else {
qCWarning(graphics_scripting) << "GraphicsScriptingInterface::getModelProvider ERROR" << error;
}
jsThrowError(error);
return nullptr;
}
scriptable::ScriptableModelPointer GraphicsScriptingInterface::newModelObject(QVector<scriptable::ScriptableMeshPointer> meshes) {
scriptable::ScriptableModelPointer GraphicsScriptingInterface::newModel(const scriptable::ScriptableMeshes& meshes) {
auto modelWrapper = scriptable::make_scriptowned<scriptable::ScriptableModel>();
modelWrapper->setObjectName("js::model");
if (meshes.isEmpty()) {
if (context()) {
context()->throwError("expected [meshes] array as first argument");
}
jsThrowError("expected [meshes] array as first argument");
} else {
int i = 0;
for (const auto& mesh : meshes) {
#ifdef SCRIPTABLE_MESH_DEBUG
qDebug() << "newModel" << i << meshes.size() << mesh;
#endif
if (mesh) {
modelWrapper->append(*mesh);
} else if (context()) {
context()->throwError(QString("invalid mesh at index: %1").arg(i));
} else {
jsThrowError(QString("invalid mesh at index: %1").arg(i));
break;
}
i++;
}
@ -86,30 +111,37 @@ scriptable::ScriptableModelPointer GraphicsScriptingInterface::newModelObject(QV
return modelWrapper;
}
scriptable::ScriptableModelPointer GraphicsScriptingInterface::getModelObject(QUuid uuid) {
QString error, providerType = "unknown";
if (auto provider = getModelProvider(uuid)) {
providerType = SpatiallyNestable::nestableTypeToString(provider->modelProviderType);
auto modelObject = provider->getScriptableModel();
if (modelObject.objectID == uuid) {
if (modelObject.meshes.size()) {
auto modelWrapper = scriptable::make_scriptowned<scriptable::ScriptableModel>(modelObject);
modelWrapper->setObjectName(providerType+"::"+uuid.toString()+"::model");
return modelWrapper;
scriptable::ScriptableModelPointer GraphicsScriptingInterface::getModel(QUuid uuid) {
QString error;
bool success;
QString providerType = "unknown";
if (auto nestable = DependencyManager::get<SpatialParentFinder>()->find(uuid, success).lock()) {
providerType = SpatiallyNestable::nestableTypeToString(nestable->getNestableType());
if (auto provider = getModelProvider(uuid)) {
auto modelObject = provider->getScriptableModel();
const bool found = !modelObject.objectID.isNull();
if (found && uuid == AVATAR_SELF_ID) {
// special case override so that scripts can rely on matching intput/output UUIDs
modelObject.objectID = AVATAR_SELF_ID;
}
if (modelObject.objectID == uuid) {
if (modelObject.meshes.size()) {
auto modelWrapper = scriptable::make_scriptowned<scriptable::ScriptableModel>(modelObject);
modelWrapper->setObjectName(providerType+"::"+uuid.toString()+"::model");
return modelWrapper;
} else {
error = "no meshes available: " + modelObject.objectID.toString();
}
} else {
error = "no meshes available: " + modelObject.objectID.toString();
error = QString("objectID mismatch: %1 (result contained %2 meshes)").arg(modelObject.objectID.toString()).arg(modelObject.meshes.size());
}
} else {
error = QString("objectID mismatch: %1 (containing %2 meshes)").arg(modelObject.objectID.toString()).arg(modelObject.meshes.size());
error = "model provider unavailable";
}
} else {
error = "provider unavailable";
}
auto errorMessage = QString("failed to get meshes from %1 provider for uuid %2 (%3)").arg(providerType).arg(uuid.toString()).arg(error);
qCWarning(graphics_scripting) << "GraphicsScriptingInterface::getModelObject ERROR" << errorMessage;
if (context()) {
context()->throwError(errorMessage);
error = "model object not found";
}
jsThrowError(QString("failed to get meshes from %1 provider for uuid %2 (%3)").arg(providerType).arg(uuid.toString()).arg(error));
return nullptr;
}
@ -135,6 +167,85 @@ bool GraphicsScriptingInterface::updateMeshPart(scriptable::ScriptableMeshPointe
}
#endif
scriptable::ScriptableMeshPointer GraphicsScriptingInterface::newMesh(const QVariantMap& ifsMeshData) {
// TODO: this is bare-bones way for now to improvise a new mesh from the scripting side
// in the future we want to support a formal C++ structure data type here instead
QString meshName = ifsMeshData.value("name").toString();
QString topologyName = ifsMeshData.value("topology").toString();
QVector<glm::uint32> indices = buffer_helpers::variantToVector<glm::uint32>(ifsMeshData.value("indices"));
QVector<glm::vec3> vertices = buffer_helpers::variantToVector<glm::vec3>(ifsMeshData.value("positions"));
QVector<glm::vec3> normals = buffer_helpers::variantToVector<glm::vec3>(ifsMeshData.value("normals"));
QVector<glm::vec3> colors = buffer_helpers::variantToVector<glm::vec3>(ifsMeshData.value("colors"));
QVector<glm::vec2> texCoords0 = buffer_helpers::variantToVector<glm::vec2>(ifsMeshData.value("texCoords0"));
const auto numVertices = vertices.size();
const auto numIndices = indices.size();
const auto topology = graphics::Mesh::TRIANGLES;
// sanity checks
QString error;
if (!topologyName.isEmpty() && topologyName != "triangles") {
error = "expected 'triangles' or undefined for .topology";
} else if (!numIndices) {
error = QString("expected non-empty [uint32,...] array for .indices (got type=%1)").arg(ifsMeshData.value("indices").typeName());
} else if (numIndices % 3 != 0) {
error = QString("expected 'triangle faces' for .indices (ie: length to be divisible by 3) length=%1").arg(numIndices);
} else if (!numVertices) {
error = "expected non-empty [glm::vec3(),...] array for .positions";
} else {
const gpu::uint32 maxVertexIndex = numVertices;
int i = 0;
for (const auto& ind : indices) {
if (ind >= maxVertexIndex) {
error = QString("index out of .indices[%1] index=%2 >= maxVertexIndex=%3").arg(i).arg(ind).arg(maxVertexIndex);
break;
}
i++;
}
}
if (!error.isEmpty()) {
jsThrowError(error);
return nullptr;
}
if (ifsMeshData.contains("normals") && normals.size() < numVertices) {
qCInfo(graphics_scripting) << "newMesh -- expanding .normals to #" << numVertices;
normals.resize(numVertices);
}
if (ifsMeshData.contains("colors") && colors.size() < numVertices) {
qCInfo(graphics_scripting) << "newMesh -- expanding .colors to #" << numVertices;
colors.resize(numVertices);
}
if (ifsMeshData.contains("texCoords0") && texCoords0.size() < numVertices) {
qCInfo(graphics_scripting) << "newMesh -- expanding .texCoords0 to #" << numVertices;
texCoords0.resize(numVertices);
}
if (ifsMeshData.contains("texCoords1")) {
qCWarning(graphics_scripting) << "newMesh - texCoords1 not yet supported; ignoring";
}
graphics::MeshPointer mesh(new graphics::Mesh());
mesh->modelName = "graphics::newMesh";
mesh->displayName = meshName.toStdString();
// TODO: newFromVector does no conversion -- later we could autodetect if fitting into gpu::INDEX_UINT16
// and also pack other values (like NORMAL / TEXCOORD0 where relevant)
mesh->setIndexBuffer(buffer_helpers::newFromVector(indices, gpu::Format::INDEX_INT32));
mesh->setVertexBuffer(buffer_helpers::newFromVector(vertices, gpu::Format::VEC3F_XYZ));
if (normals.size()) {
mesh->addAttribute(gpu::Stream::NORMAL, buffer_helpers::newFromVector(normals, gpu::Format::VEC3F_XYZ));
}
if (colors.size()) {
mesh->addAttribute(gpu::Stream::COLOR, buffer_helpers::newFromVector(colors, gpu::Format::VEC3F_XYZ));
}
if (texCoords0.size()) {
mesh->addAttribute(gpu::Stream::TEXCOORD0, buffer_helpers::newFromVector(texCoords0, gpu::Format::VEC2F_UV));
}
QVector<graphics::Mesh::Part> parts = {{ 0, indices.size(), 0, topology}};
mesh->setPartBuffer(buffer_helpers::newFromVector(parts, gpu::Element::PART_DRAWCALL));
return scriptable::make_scriptowned<scriptable::ScriptableMesh>(mesh, nullptr);
}
QString GraphicsScriptingInterface::exportModelToOBJ(const scriptable::ScriptableModel& _in) {
const auto& in = _in.getConstMeshes();
if (in.size()) {
@ -148,11 +259,10 @@ QString GraphicsScriptingInterface::exportModelToOBJ(const scriptable::Scriptabl
return writeOBJToString(meshes);
}
}
if (context()) {
context()->throwError(QString("null mesh"));
}
jsThrowError("null mesh");
return QString();
}
void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) {
scriptable::registerMetaTypes(engine);
}
@ -166,23 +276,115 @@ MeshPointer GraphicsScriptingInterface::getMeshPointer(scriptable::ScriptableMes
MeshPointer GraphicsScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) {
MeshPointer result;
if (!meshProxy) {
if (context()){
context()->throwError("expected meshProxy as first parameter");
} else {
qCDebug(graphics_scripting) << "expected meshProxy as first parameter";
}
jsThrowError("expected meshProxy as first parameter");
return result;
}
auto mesh = meshProxy->getMeshPointer();
if (!mesh) {
if (context()) {
context()->throwError("expected valid meshProxy as first parameter");
} else {
qCDebug(graphics_scripting) << "expected valid meshProxy as first parameter";
}
jsThrowError("expected valid meshProxy as first parameter");
return result;
}
return mesh;
}
namespace {
QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector<glm::uint32>& vector) {
return qScriptValueFromSequence(engine, vector);
}
void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector<glm::uint32>& result) {
qScriptValueToSequence(array, result);
}
QVector<int> metaTypeIds{
qRegisterMetaType<glm::uint32>("uint32"),
qRegisterMetaType<glm::uint32>("glm::uint32"),
qRegisterMetaType<QVector<glm::uint32>>(),
qRegisterMetaType<QVector<glm::uint32>>("QVector<uint32>"),
qRegisterMetaType<scriptable::ScriptableMeshes>(),
qRegisterMetaType<scriptable::ScriptableMeshes>("ScriptableMeshes"),
qRegisterMetaType<scriptable::ScriptableMeshes>("scriptable::ScriptableMeshes"),
qRegisterMetaType<QVector<scriptable::ScriptableMeshPointer>>("QVector<scriptable::ScriptableMeshPointer>"),
qRegisterMetaType<scriptable::ScriptableMeshPointer>(),
qRegisterMetaType<scriptable::ScriptableModelPointer>(),
qRegisterMetaType<scriptable::ScriptableMeshPartPointer>(),
qRegisterMetaType<graphics::Mesh::Topology>(),
};
}
namespace scriptable {
template <typename T> int registerQPointerThing(QScriptEngine* engine) {
qScriptRegisterSequenceMetaType<QVector<QPointer<T>>>(engine);
return qScriptRegisterMetaType<QPointer<T>>(
engine,
[](QScriptEngine* engine, const QPointer<T>& object) -> QScriptValue {
if (!object) {
return QScriptValue::NullValue;
}
return engine->newQObject(object, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::AutoCreateDynamicProperties);
},
[](const QScriptValue& value, QPointer<T>& out) {
auto obj = value.toQObject();
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "qpointer_qobject_cast" << obj << value.toString();
#endif
if (auto tmp = qobject_cast<T*>(obj)) {
out = QPointer<T>(tmp);
return;
}
#if 0
if (auto tmp = static_cast<T*>(obj)) {
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "qpointer_qobject_cast -- via static_cast" << obj << tmp << value.toString();
#endif
out = QPointer<T>(tmp);
return;
}
#endif
out = nullptr;
}
);
}
template <typename T> int registerDebugEnum(QScriptEngine* engine, const DebugEnums<T>& debugEnums) {
static const DebugEnums<T>& poop = debugEnums;
return qScriptRegisterMetaType<T>(
engine,
[](QScriptEngine* engine, const T& topology) -> QScriptValue {
return poop.value(topology);
},
[](const QScriptValue& value, T& topology) {
topology = poop.key(value.toString());
}
);
}
bool registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterSequenceMetaType<QVector<glm::uint32>>(engine);
qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue);
registerQPointerThing<scriptable::ScriptableModel>(engine);
registerQPointerThing<scriptable::ScriptableMesh>(engine);
registerQPointerThing<scriptable::ScriptableMeshPart>(engine);
qScriptRegisterMetaType<QVector<scriptable::ScriptableMeshPointer>>(
engine,
[](QScriptEngine* engine, const QVector<scriptable::ScriptableMeshPointer>& vector) -> QScriptValue {
return qScriptValueFromSequence(engine, vector);
},
[](const QScriptValue& array, QVector<scriptable::ScriptableMeshPointer>& result) {
qScriptValueToSequence(array, result);
}
);
registerDebugEnum<graphics::Mesh::Topology>(engine, graphics::TOPOLOGIES);
registerDebugEnum<gpu::Type>(engine, gpu::TYPES);
registerDebugEnum<gpu::Semantic>(engine, gpu::SEMANTICS);
registerDebugEnum<gpu::Dimension>(engine, gpu::DIMENSIONS);
return metaTypeIds.size();
}
}
#include "GraphicsScriptingInterface.moc"

View file

@ -34,13 +34,14 @@ public slots:
* @function GraphicsScriptingInterface.getModel
* @param {UUID} The objectID of the model whose meshes are to be retrieve
*/
scriptable::ModelProviderPointer getModelProvider(QUuid uuid);
scriptable::ScriptableModelPointer getModelObject(QUuid uuid);
bool updateModelObject(QUuid uuid, const scriptable::ScriptableModelPointer model);
scriptable::ScriptableModelPointer newModelObject(QVector<scriptable::ScriptableMeshPointer> meshes);
scriptable::ScriptableModelPointer getModel(QUuid uuid);
bool updateModel(QUuid uuid, const scriptable::ScriptableModelPointer& model);
bool canUpdateModel(QUuid uuid, int meshIndex = -1, int partNumber = -1);
scriptable::ScriptableModelPointer newModel(const scriptable::ScriptableMeshes& meshes);
scriptable::ScriptableMeshPointer newMesh(const QVariantMap& ifsMeshData);
#ifdef SCRIPTABLE_MESH_TODO
scriptable::ScriptableMeshPartPointer exportMeshPart(scriptable::ScriptableMeshPointer mesh, int part=0) {
scriptable::ScriptableMeshPartPointer exportMeshPart(scriptable::ScriptableMeshPointer mesh, int partNumber = -1) {
return scriptable::make_scriptowned<scriptable::ScriptableMeshPart>(mesh, part);
}
bool updateMeshPart(scriptable::ScriptableMeshPointer mesh, scriptable::ScriptableMeshPartPointer part);
@ -49,10 +50,14 @@ public slots:
QString exportModelToOBJ(const scriptable::ScriptableModel& in);
private:
scriptable::ModelProviderPointer getModelProvider(QUuid uuid);
void jsThrowError(const QString& error);
scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy);
scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy);
scriptable::MeshPointer getMeshPointer(const scriptable::ScriptableMesh& meshProxy);
};
Q_DECLARE_METATYPE(scriptable::ModelProviderPointer)
#endif // hifi_GraphicsScriptingInterface_h

View file

@ -1,3 +1,114 @@
//
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "GraphicsScriptingUtil.h"
#include <BaseScriptEngine.h>
#include <graphics/BufferViewHelpers.h>
#include <AABox.h>
#include <Extents.h>
using buffer_helpers::glmVecToVariant;
Q_LOGGING_CATEGORY(graphics_scripting, "hifi.scripting.graphics")
namespace scriptable {
QVariant toVariant(const glm::mat4& mat4) {
QVector<float> floats;
floats.resize(16);
memcpy(floats.data(), &mat4, sizeof(glm::mat4));
QVariant v;
v.setValue<QVector<float>>(floats);
return v;
};
QVariant toVariant(const Extents& box) {
return QVariantMap{
{ "center", glmVecToVariant(box.minimum + (box.size() / 2.0f)) },
{ "minimum", glmVecToVariant(box.minimum) },
{ "maximum", glmVecToVariant(box.maximum) },
{ "dimensions", glmVecToVariant(box.size()) },
};
}
QVariant toVariant(const AABox& box) {
return QVariantMap{
{ "brn", glmVecToVariant(box.getCorner()) },
{ "tfl", glmVecToVariant(box.calcTopFarLeft()) },
{ "center", glmVecToVariant(box.calcCenter()) },
{ "minimum", glmVecToVariant(box.getMinimumPoint()) },
{ "maximum", glmVecToVariant(box.getMaximumPoint()) },
{ "dimensions", glmVecToVariant(box.getDimensions()) },
};
}
QVariant toVariant(const gpu::Element& element) {
return QVariantMap{
{ "type", gpu::toString(element.getType()) },
{ "semantic", gpu::toString(element.getSemantic()) },
{ "dimension", gpu::toString(element.getDimension()) },
{ "scalarCount", element.getScalarCount() },
{ "byteSize", element.getSize() },
{ "BYTES_PER_ELEMENT", element.getSize() / element.getScalarCount() },
};
}
QScriptValue jsBindCallback(QScriptValue value) {
if (value.isObject() && value.property("callback").isFunction()) {
// value is already a bound callback
return value;
}
auto engine = value.engine();
auto context = engine ? engine->currentContext() : nullptr;
auto length = context ? context->argumentCount() : 0;
QScriptValue scope = context ? context->thisObject() : QScriptValue::NullValue;
QScriptValue method;
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "jsBindCallback" << engine << length << scope.toQObject() << method.toString();
#endif
// find position in the incoming JS Function.arguments array (so we can test for the two-argument case)
for (int i = 0; context && i < length; i++) {
if (context->argument(i).strictlyEquals(value)) {
method = context->argument(i+1);
}
}
if (method.isFunction() || method.isString()) {
// interpret as `API.func(..., scope, function callback(){})` or `API.func(..., scope, "methodName")`
scope = value;
} else {
// interpret as `API.func(..., function callback(){})`
method = value;
}
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "scope:" << scope.toQObject() << "method:" << method.toString();
#endif
return ::makeScopedHandlerObject(scope, method);
}
template <typename T>
T this_qobject_cast(QScriptEngine* engine) {
auto context = engine ? engine->currentContext() : nullptr;
return qscriptvalue_cast<T>(context ? context->thisObject() : QScriptValue::NullValue);
}
QString toDebugString(QObject* tmp) {
QString s;
QTextStream out(&s);
out << tmp;
return s;
// return QString("%0 (0x%1%2)")
// .arg(tmp ? tmp->metaObject()->className() : "QObject")
// .arg(qulonglong(tmp), 16, 16, QChar('0'))
// .arg(tmp && tmp->objectName().size() ? " name=" + tmp->objectName() : "");
}
template <typename T> QString toDebugString(std::shared_ptr<T> tmp) {
return toDebugString(qobject_cast<QObject*>(tmp.get()));
}
}

View file

@ -8,46 +8,39 @@
#include <QDebug>
#include <memory>
#include <functional>
#include <glm/glm.hpp>
class Extents;
class AABox;
namespace gpu {
class Element;
}
Q_DECLARE_LOGGING_CATEGORY(graphics_scripting)
namespace scriptable {
// derive current context's C++ QObject (based on current JS "this" value)
template <typename T>
T this_qobject_cast(QScriptEngine* engine) {
auto context = engine ? engine->currentContext() : nullptr;
return qscriptvalue_cast<T>(context ? context->thisObject() : QScriptValue::NullValue);
}
QVariant toVariant(const Extents& box);
QVariant toVariant(const AABox& box);
QVariant toVariant(const gpu::Element& element);
QVariant toVariant(const glm::mat4& mat4);
// JS => QPointer<QObject>
template <typename T>
QPointer<T> qpointer_qobject_cast(const QScriptValue& value) {
auto obj = value.toQObject();
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "qpointer_qobject_cast" << obj << value.toString();
#endif
if (auto tmp = qobject_cast<T*>(obj)) {
return QPointer<T>(tmp);
}
if (auto tmp = static_cast<T*>(obj)) {
return QPointer<T>(tmp);
}
return nullptr;
}
inline QString toDebugString(QObject* tmp) {
return QString("%0 (0x%1%2)")
.arg(tmp ? tmp->metaObject()->className() : "QObject")
.arg(qulonglong(tmp), 16, 16, QChar('0'))
.arg(tmp && tmp->objectName().size() ? " name=" + tmp->objectName() : "");
}
template <typename T> QString toDebugString(std::shared_ptr<T> tmp) {
return toDebugString(qobject_cast<QObject*>(tmp.get()));
}
// helper that automatically resolves Qt-signal-like scoped callbacks
// ... C++ side: `void MyClass::asyncMethod(..., QScriptValue callback)`
// ... JS side:
// * `API.asyncMethod(..., function(){})`
// * `API.asyncMethod(..., scope, function(){})`
// * `API.asyncMethod(..., scope, "methodName")`
QScriptValue jsBindCallback(QScriptValue callback);
// cast engine->thisObject() => C++ class instance
template <typename T> T this_qobject_cast(QScriptEngine* engine);
QString toDebugString(QObject* tmp);
template <typename T> QString toDebugString(std::shared_ptr<T> tmp);
// Helper for creating C++ > ScriptOwned JS instances
// (NOTE: this also helps track in the code where we need to update later if switching to
// std::shared_ptr's -- something currently non-trivial given mixed JS/C++ object ownership)
template <typename T, class... Rest>
QPointer<T> make_scriptowned(Rest... rest) {
template <typename T, class... Rest> inline QPointer<T> make_scriptowned(Rest... rest) {
auto instance = QPointer<T>(new T(rest...));
Q_ASSERT(instance && instance->parent());
return instance;

View file

@ -8,6 +8,7 @@
#include "Forward.h"
#include "ScriptableMesh.h"
#include "ScriptableMeshPart.h"
#include "BufferViewScripting.h"
#include "GraphicsScriptingUtil.h"
@ -19,15 +20,11 @@
#include <glm/gtx/norm.hpp>
#include <glm/gtx/transform.hpp>
#include <graphics/BufferViewHelpers.h>
#include <graphics/GpuHelpers.h>
#include <graphics/Geometry.h>
// #define SCRIPTABLE_MESH_DEBUG 1
scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex)
: QObject(), parentMesh(parentMesh), partIndex(partIndex) {
setObjectName(QString("%1.part[%2]").arg(parentMesh ? parentMesh->objectName() : "").arg(partIndex));
}
scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other)
: ScriptableMeshBase(other), QScriptable() {
auto mesh = getMeshPointer();
@ -41,74 +38,55 @@ scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other)
QVector<scriptable::ScriptableMeshPartPointer> scriptable::ScriptableMesh::getMeshParts() const {
QVector<scriptable::ScriptableMeshPartPointer> out;
for (quint32 i = 0; i < getNumParts(); i++) {
for (glm::uint32 i = 0; i < getNumParts(); i++) {
out << scriptable::make_scriptowned<scriptable::ScriptableMeshPart>(getSelf(), i);
}
return out;
}
quint32 scriptable::ScriptableMesh::getNumIndices() const {
glm::uint32 scriptable::ScriptableMesh::getNumIndices() const {
if (auto mesh = getMeshPointer()) {
return (quint32)mesh->getNumIndices();
return (glm::uint32)mesh->getNumIndices();
}
return 0;
}
quint32 scriptable::ScriptableMesh::getNumVertices() const {
glm::uint32 scriptable::ScriptableMesh::getNumVertices() const {
if (auto mesh = getMeshPointer()) {
return (quint32)mesh->getNumVertices();
return (glm::uint32)mesh->getNumVertices();
}
return 0;
}
QVector<quint32> scriptable::ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const {
QVector<quint32> result;
if (auto mesh = getMeshPointer()) {
const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION);
const uint32_t num = (uint32_t)pos.getNumElements();
for (uint32_t i = 0; i < num; i++) {
const auto& position = pos.get<glm::vec3>(i);
if (glm::distance(position, origin) <= epsilon) {
result << i;
}
QVector<glm::uint32> scriptable::ScriptableMesh::findNearbyVertexIndices(const glm::vec3& origin, float epsilon) const {
QVector<glm::uint32> result;
if (!isValid()) {
return result;
}
const auto epsilon2 = epsilon*epsilon;
buffer_helpers::forEach<glm::vec3>(buffer_helpers::mesh::getBufferView(getMeshPointer(), gpu::Stream::POSITION), [&](glm::uint32 index, const glm::vec3& position) {
if (glm::length2(position - origin) <= epsilon2) {
result << index;
}
}
return true;
});
return result;
}
QVector<quint32> scriptable::ScriptableMesh::getIndices() const {
QVector<quint32> result;
QVector<glm::uint32> scriptable::ScriptableMesh::getIndices() const {
if (auto mesh = getMeshPointer()) {
#ifdef SCRIPTABLE_MESH_DEBUG
qCDebug(graphics_scripting, "getTriangleIndices mesh %p", mesh.get());
qCDebug(graphics_scripting, "getIndices mesh %p", mesh.get());
#endif
gpu::BufferView indexBufferView = mesh->getIndexBuffer();
if (quint32 count = (quint32)indexBufferView.getNumElements()) {
result.resize(count);
switch(indexBufferView._element.getType()) {
case gpu::UINT32:
// memcpy(result.data(), buffer->getData(), result.size()*sizeof(quint32));
for (quint32 i = 0; i < count; i++) {
result[i] = indexBufferView.get<quint32>(i);
}
break;
case gpu::UINT16:
for (quint32 i = 0; i < count; i++) {
result[i] = indexBufferView.get<quint16>(i);
}
break;
default:
assert(false);
Q_ASSERT(false);
}
}
return buffer_helpers::bufferToVector<glm::uint32>(mesh->getIndexBuffer());
}
return result;
return QVector<glm::uint32>();
}
quint32 scriptable::ScriptableMesh::getNumAttributes() const {
glm::uint32 scriptable::ScriptableMesh::getNumAttributes() const {
if (auto mesh = getMeshPointer()) {
return (quint32)mesh->getNumAttributes();
return (glm::uint32)mesh->getNumAttributes() + 1;
}
return 0;
}
@ -116,7 +94,7 @@ QVector<QString> scriptable::ScriptableMesh::getAttributeNames() const {
QVector<QString> result;
if (auto mesh = getMeshPointer()) {
for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) {
auto bufferView = buffer_helpers::getBufferView(mesh, a.second);
auto bufferView = buffer_helpers::mesh::getBufferView(mesh, a.second);
if (bufferView.getNumElements() > 0) {
result << a.first;
}
@ -125,22 +103,20 @@ QVector<QString> scriptable::ScriptableMesh::getAttributeNames() const {
return result;
}
QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const {
return getVertexAttributes(vertexIndex, getAttributeNames());
QVariantMap scriptable::ScriptableMesh::getVertexAttributes(glm::uint32 vertexIndex) const {
if (!isValidIndex(vertexIndex)) {
return QVariantMap();
}
return buffer_helpers::mesh::getVertexAttributes(getMeshPointer(), vertexIndex).toMap();
}
bool scriptable::ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) {
for (auto& a : buffer_helpers::gatherBufferViews(getMeshPointer())) {
const auto& name = a.first;
const auto& value = attributes.value(name);
if (value.isValid()) {
auto& view = a.second;
buffer_helpers::fromVariant(view, vertexIndex, value);
} else {
//qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name;
bool scriptable::ScriptableMesh::setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributes) {
for (const auto& name : attributes.keys()) {
if (!isValidIndex(vertexIndex, name)) {
return false;
}
}
return true;
return buffer_helpers::mesh::setVertexAttributes(getMeshPointer(), vertexIndex, attributes);
}
int scriptable::ScriptableMesh::_getSlotNumber(const QString& attributeName) const {
@ -150,112 +126,133 @@ int scriptable::ScriptableMesh::_getSlotNumber(const QString& attributeName) con
return -1;
}
QVariantMap scriptable::ScriptableMesh::getMeshExtents() const {
auto mesh = getMeshPointer();
auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox();
return buffer_helpers::toVariant(box).toMap();
QVariantMap scriptable::ScriptableMesh::getBufferFormats() const {
QVariantMap result;
for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) {
auto bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), a.second);
result[a.first] = QVariantMap{
{ "slot", a.second },
{ "length", (quint32)bufferView.getNumElements() },
{ "byteLength", (quint32)bufferView._size },
{ "offset", (quint32) bufferView._offset },
{ "stride", (quint32)bufferView._stride },
{ "element", scriptable::toVariant(bufferView._element) },
};
}
return result;
}
quint32 scriptable::ScriptableMesh::getNumParts() const {
if (auto mesh = getMeshPointer()) {
return (quint32)mesh->getNumParts();
bool scriptable::ScriptableMesh::removeAttribute(const QString& attributeName) {
auto slot = isValid() ? _getSlotNumber(attributeName) : -1;
if (slot < 0) {
return 0;
}
if (slot == gpu::Stream::POSITION) {
context()->throwError("cannot remove .position attribute");
return false;
}
if (buffer_helpers::mesh::getBufferView(getMeshPointer(), slot).getNumElements()) {
getMeshPointer()->removeAttribute(slot);
return true;
}
return false;
}
glm::uint32 scriptable::ScriptableMesh::addAttribute(const QString& attributeName, const QVariant& defaultValue) {
auto slot = isValid() ? _getSlotNumber(attributeName) : -1;
if (slot < 0) {
return 0;
}
auto mesh = getMeshPointer();
auto numVertices = getNumVertices();
if (!getAttributeNames().contains(attributeName)) {
QVector<QVariant> values;
values.fill(defaultValue, numVertices);
mesh->addAttribute(slot, buffer_helpers::newFromVector(values, gpu::Stream::getDefaultElements()[slot]));
return values.size();
} else {
auto bufferView = buffer_helpers::mesh::getBufferView(mesh, slot);
auto current = bufferView.getNumElements();
if (current < numVertices) {
bufferView = buffer_helpers::resized(bufferView, numVertices);
for (glm::uint32 i = current; i < numVertices; i++) {
buffer_helpers::setValue<QVariant>(bufferView, i, defaultValue);
}
return numVertices - current;
} else if (current > numVertices) {
qCDebug(graphics_scripting) << QString("current=%1 > numVertices=%2").arg(current).arg(numVertices);
return 0;
}
}
return 0;
}
QVariantMap scriptable::ScriptableMeshPart::scaleToFit(float unitScale) {
if (auto mesh = getMeshPointer()) {
auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts());
auto center = box.calcCenter();
float maxDimension = glm::distance(box.getMaximumPoint(), box.getMinimumPoint());
return scale(glm::vec3(unitScale / maxDimension), center);
glm::uint32 scriptable::ScriptableMesh::fillAttribute(const QString& attributeName, const QVariant& value) {
auto slot = isValid() ? _getSlotNumber(attributeName) : -1;
if (slot < 0) {
return 0;
}
return {};
}
QVariantMap scriptable::ScriptableMeshPart::translate(const glm::vec3& translation) {
return transform(glm::translate(translation));
}
QVariantMap scriptable::ScriptableMeshPart::scale(const glm::vec3& scale, const glm::vec3& origin) {
if (auto mesh = getMeshPointer()) {
auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts());
glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin;
return transform(glm::translate(center) * glm::scale(scale));
}
return {};
}
QVariantMap scriptable::ScriptableMeshPart::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) {
return rotate(glm::quat(glm::radians(eulerAngles)), origin);
}
QVariantMap scriptable::ScriptableMeshPart::rotate(const glm::quat& rotation, const glm::vec3& origin) {
if (auto mesh = getMeshPointer()) {
auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts());
glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin;
return transform(glm::translate(center) * glm::toMat4(rotation));
}
return {};
}
QVariantMap scriptable::ScriptableMeshPart::transform(const glm::mat4& transform) {
if (auto mesh = getMeshPointer()) {
const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION);
const uint32_t num = (uint32_t)pos.getNumElements();
for (uint32_t i = 0; i < num; i++) {
auto& position = pos.edit<glm::vec3>(i);
position = transform * glm::vec4(position, 1.0f);
}
return parentMesh->getMeshExtents();
}
return {};
auto mesh = getMeshPointer();
auto numVertices = getNumVertices();
QVector<QVariant> values;
values.fill(value, numVertices);
mesh->addAttribute(slot, buffer_helpers::newFromVector(values, gpu::Stream::getDefaultElements()[slot]));
return true;
}
QVariantList scriptable::ScriptableMesh::getAttributeValues(const QString& attributeName) const {
QVariantList result;
auto slotNum = _getSlotNumber(attributeName);
if (slotNum >= 0) {
auto slot = (gpu::Stream::Slot)slotNum;
const auto& bufferView = buffer_helpers::getBufferView(getMeshPointer(), slot);
if (auto len = bufferView.getNumElements()) {
bool asArray = bufferView._element.getType() != gpu::FLOAT;
for (quint32 i = 0; i < len; i++) {
result << buffer_helpers::toVariant(bufferView, i, asArray, attributeName.toStdString().c_str());
}
}
}
return result;
}
QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector<QString> names) const {
QVariantMap result;
QVariantMap scriptable::ScriptableMesh::getMeshExtents() const {
auto mesh = getMeshPointer();
if (!mesh || vertexIndex >= getNumVertices()) {
auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox();
return scriptable::toVariant(box).toMap();
}
glm::uint32 scriptable::ScriptableMesh::getNumParts() const {
if (auto mesh = getMeshPointer()) {
return (glm::uint32)mesh->getNumParts();
}
return 0;
}
QVariantList scriptable::ScriptableMesh::queryVertexAttributes(QVariant selector) const {
QVariantList result;
const auto& attributeName = selector.toString();
if (!isValidIndex(0, attributeName)) {
return result;
}
for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) {
auto name = a.first;
if (!names.contains(name)) {
continue;
}
auto slot = a.second;
const gpu::BufferView& bufferView = buffer_helpers::getBufferView(mesh, slot);
if (vertexIndex < bufferView.getNumElements()) {
bool asArray = bufferView._element.getType() != gpu::FLOAT;
result[name] = buffer_helpers::toVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str());
}
auto slotNum = _getSlotNumber(attributeName);
const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast<gpu::Stream::Slot>(slotNum));
glm::uint32 numElements = bufferView.getNumElements();
for (glm::uint32 i = 0; i < numElements; i++) {
result << buffer_helpers::getValue<QVariant>(bufferView, i, qUtf8Printable(attributeName));
}
return result;
}
quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) {
QVariant scriptable::ScriptableMesh::getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const {
if (!isValidIndex(vertexIndex, attributeName)) {
return QVariant();
}
auto slotNum = _getSlotNumber(attributeName);
const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast<gpu::Stream::Slot>(slotNum));
return buffer_helpers::getValue<QVariant>(bufferView, vertexIndex, qUtf8Printable(attributeName));
}
bool scriptable::ScriptableMesh::setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& value) {
if (!isValidIndex(vertexIndex, attributeName)) {
return false;
}
auto slotNum = _getSlotNumber(attributeName);
const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast<gpu::Stream::Slot>(slotNum));
return buffer_helpers::setValue(bufferView, vertexIndex, value);
}
glm::uint32 scriptable::ScriptableMesh::forEachVertex(QScriptValue _callback) {
auto mesh = getMeshPointer();
if (!mesh) {
return 0;
}
auto scopedHandler = jsBindCallback(_callback);
// 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");
@ -264,205 +261,104 @@ quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) {
return 0;
}
auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue;
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "mapAttributeValues" << mesh.get() << js->currentContext()->thisObject().toQObject();
#endif
auto obj = js->newObject();
auto attributeViews = buffer_helpers::gatherBufferViews(mesh, { "normal", "color" });
uint32_t i = 0;
for (; 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 });
int numProcessed = 0;
buffer_helpers::mesh::forEachVertex(mesh, [&](glm::uint32 index, const QVariantMap& values) {
auto result = callback.call(scope, { js->toScriptValue(values), index, meshPart });
if (js->hasUncaughtException()) {
js->currentContext()->throwValue(js->uncaughtException());
return i;
return false;
}
numProcessed++;
return true;
});
return numProcessed;
}
glm::uint32 scriptable::ScriptableMesh::updateVertexAttributes(QScriptValue _callback) {
auto mesh = getMeshPointer();
if (!mesh) {
return 0;
}
auto scopedHandler = jsBindCallback(_callback);
// 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() ? engine() : scopedHandler.engine(); // cache value to avoid resolving each iteration
if (!js) {
return 0;
}
auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue;
int numProcessed = 0;
auto attributeViews = buffer_helpers::mesh::getAllBufferViews(mesh);
buffer_helpers::mesh::forEachVertex(mesh, [&](glm::uint32 index, const QVariantMap& values) {
auto obj = js->toScriptValue(values);
auto result = callback.call(scope, { obj, index, meshPart });
if (js->hasUncaughtException()) {
js->currentContext()->throwValue(js->uncaughtException());
return false;
}
if (result.isBool() && !result.toBool()) {
// bail without modifying data if user explicitly returns false
continue;
return true;
}
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);
buffer_helpers::setValue(a.second, index, attribute.toVariant());
}
}
numProcessed++;
return true;
});
return numProcessed;
}
// protect against user scripts sending bogus values
bool scriptable::ScriptableMesh::isValidIndex(glm::uint32 vertexIndex, const QString& attributeName) const {
if (!isValid()) {
return false;
}
return i;
}
quint32 scriptable::ScriptableMeshPart::mapAttributeValues(QScriptValue callback) {
return parentMesh ? parentMesh->mapAttributeValues(callback) : 0;
}
bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshPartPointer src, const QVector<QString>& attributeNames) {
auto target = getMeshPointer();
auto source = src ? src->getMeshPointer() : nullptr;
if (!target || !source) {
const auto last = getNumVertices() - 1;
if (vertexIndex > last) {
if (context()) {
context()->throwError("ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers");
} else {
qCWarning(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers";
context()->throwError(QString("vertexIndex=%1 out of range (firstVertexIndex=%2, lastVertexIndex=%3)").arg(vertexIndex).arg(0).arg(last));
}
return false;
}
QVector<QString> attributes = attributeNames.isEmpty() ? src->parentMesh->getAttributeNames() : attributeNames;
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- " <<
"source:" << QString::fromStdString(source->displayName) <<
"target:" << QString::fromStdString(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 = buffer_helpers::gatherBufferViews(target);
for (const auto& a : attributeViews) {
auto slot = buffer_helpers::ATTRIBUTES[a.first];
if (!attributes.contains(a.first)) {
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot;
#endif
target->removeAttribute(slot);
if (!attributeName.isEmpty()) {
auto slotNum = _getSlotNumber(attributeName);
if (slotNum < 0) {
if (context()) {
context()->throwError(QString("invalid attributeName=%1").arg(attributeName));
}
return false;
}
}
target->setVertexBuffer(buffer_helpers::clone(source->getVertexBuffer()));
target->setIndexBuffer(buffer_helpers::clone(source->getIndexBuffer()));
target->setPartBuffer(buffer_helpers::clone(source->getPartBuffer()));
for (const auto& a : attributes) {
auto slot = buffer_helpers::ATTRIBUTES[a];
if (slot == gpu::Stream::POSITION) {
continue;
}
#ifdef SCRIPTABLE_MESH_DEBUG
auto& before = target->getAttributeBuffer(slot);
#endif
auto& input = source->getAttributeBuffer(slot);
if (input.getNumElements() == 0) {
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData buffer is empty -- pruning" << a << slot;
#endif
target->removeAttribute(slot);
} else {
#ifdef SCRIPTABLE_MESH_DEBUG
if (before.getNumElements() == 0) {
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer is empty -- adding" << a << slot;
} else {
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer exists -- updating" << a << slot;
auto view = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast<gpu::Stream::Slot>(slotNum));
if (vertexIndex >= view.getNumElements()) {
if (context()) {
context()->throwError(QString("vertexIndex=%1 out of range (attribute=%2, numElements=%3)").arg(vertexIndex).arg(attributeName).arg(view.getNumElements()));
}
#endif
target->addAttribute(slot, buffer_helpers::clone(input));
return false;
}
#ifdef SCRIPTABLE_MESH_DEBUG
auto& after = target->getAttributeBuffer(slot);
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements();
#endif
}
return true;
}
bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) {
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(graphics_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)) {
newIndices << remapIndices[index];
} else {
qCInfo(graphics_scripting) << i << index << "!remapIndices[index]";
}
}
mesh->setIndexBuffer(buffer_helpers::fromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }));
mesh->setVertexBuffer(buffer_helpers::fromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ }));
auto attributeViews = buffer_helpers::gatherBufferViews(mesh);
quint32 numUniqueVerts = uniqueVerts.size();
for (const auto& a : attributeViews) {
auto& view = a.second;
auto slot = buffer_helpers::ATTRIBUTES[a.first];
if (slot == gpu::Stream::POSITION) {
continue;
}
auto newView = buffer_helpers::resize(view, numUniqueVerts);
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "ScriptableMeshPart::dedupeVertices" << a.first << slot << view.getNumElements();
qCInfo(graphics_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements();
#endif
quint32 numElements = (quint32)view.getNumElements();
for (quint32 i = 0; i < numElements; i++) {
quint32 fromVertexIndex = i;
quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex;
buffer_helpers::fromVariant(
newView, toVertexIndex,
buffer_helpers::toVariant(view, fromVertexIndex, false, "dedupe")
);
}
mesh->addAttribute(slot, newView);
}
return true;
}
scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool recalcNormals) {
scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh() {
auto mesh = getMeshPointer();
if (!mesh) {
qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh -- !meshPointer";
return nullptr;
}
auto clone = buffer_helpers::cloneMesh(mesh);
auto clone = buffer_helpers::mesh::clone(mesh);
if (recalcNormals) {
buffer_helpers::recalculateNormals(clone);
}
auto meshPointer = scriptable::make_scriptowned<scriptable::ScriptableMesh>(provider, model, clone, nullptr);
return scriptable::ScriptableMeshPointer(meshPointer);
}
@ -505,114 +401,6 @@ scriptable::ScriptableMesh::~ScriptableMesh() {
strongMesh.reset();
}
QString scriptable::ScriptableMeshPart::toOBJ() {
if (!getMeshPointer()) {
if (context()) {
context()->throwError(QString("null mesh"));
} else {
qCWarning(graphics_scripting) << "null mesh";
}
return QString();
}
return writeOBJToString({ getMeshPointer() });
}
namespace {
template <typename T>
QScriptValue qObjectToScriptValue(QScriptEngine* engine, const T& object) {
if (!object) {
return QScriptValue::NullValue;
}
return engine->newQObject(object, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater);
}
QScriptValue meshPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPointer& in) {
return qObjectToScriptValue(engine, in);
}
QScriptValue meshPartPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPartPointer& in) {
return qObjectToScriptValue(engine, in);
}
QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer& in) {
return qObjectToScriptValue(engine, in);
}
void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) {
out = scriptable::qpointer_qobject_cast<scriptable::ScriptableMesh>(value);
}
void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) {
out = scriptable::qpointer_qobject_cast<scriptable::ScriptableModel>(value);
}
void meshPartPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPartPointer &out) {
out = scriptable::qpointer_qobject_cast<scriptable::ScriptableMeshPart>(value);
}
QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector<scriptable::uint32>& vector) {
return qScriptValueFromSequence(engine, vector);
}
void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector<scriptable::uint32>& result) {
qScriptValueToSequence(array, result);
}
QVector<int> metaTypeIds{
qRegisterMetaType<scriptable::uint32>("uint32"),
qRegisterMetaType<scriptable::uint32>("scriptable::uint32"),
qRegisterMetaType<QVector<scriptable::uint32>>(),
qRegisterMetaType<QVector<scriptable::uint32>>("QVector<uint32>"),
qRegisterMetaType<scriptable::ScriptableMeshPointer>(),
qRegisterMetaType<scriptable::ScriptableModelPointer>(),
qRegisterMetaType<scriptable::ScriptableMeshPartPointer>(),
};
}
namespace scriptable {
bool registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterSequenceMetaType<QVector<scriptable::ScriptableMeshPartPointer>>(engine);
qScriptRegisterSequenceMetaType<QVector<scriptable::ScriptableMeshPointer>>(engine);
qScriptRegisterSequenceMetaType<QVector<scriptable::uint32>>(engine);
qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue);
qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue);
qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue);
qScriptRegisterMetaType(engine, meshPartPointerToScriptValue, meshPartPointerFromScriptValue);
return metaTypeIds.size();
}
// callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while
// still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions
QScriptValue jsBindCallback(QScriptValue value) {
if (value.isObject() && value.property("callback").isFunction()) {
// value is already a bound callback
return value;
}
auto engine = value.engine();
auto context = engine ? engine->currentContext() : nullptr;
auto length = context ? context->argumentCount() : 0;
QScriptValue scope = context ? context->thisObject() : QScriptValue::NullValue;
QScriptValue method;
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "jsBindCallback" << engine << length << scope.toQObject() << method.toString();
#endif
// find position in the incoming JS Function.arguments array (so we can test for the two-argument case)
for (int i = 0; context && i < length; i++) {
if (context->argument(i).strictlyEquals(value)) {
method = context->argument(i+1);
}
}
if (method.isFunction() || method.isString()) {
// interpret as `API.func(..., scope, function callback(){})` or `API.func(..., scope, "methodName")`
scope = value;
} else {
// interpret as `API.func(..., function callback(){})`
method = value;
}
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "scope:" << scope.toQObject() << "method:" << method.toString();
#endif
return ::makeScopedHandlerObject(scope, method);
}
}
#include "ScriptableMesh.moc"

View file

@ -25,18 +25,23 @@
#include "GraphicsScriptingUtil.h"
#include <graphics/Geometry.h>
namespace scriptable {
class ScriptableMesh : public ScriptableMeshBase, QScriptable {
Q_OBJECT
public:
Q_PROPERTY(uint32 numParts READ getNumParts)
Q_PROPERTY(uint32 numAttributes READ getNumAttributes)
Q_PROPERTY(uint32 numVertices READ getNumVertices)
Q_PROPERTY(uint32 numIndices READ getNumIndices)
Q_PROPERTY(glm::uint32 numParts READ getNumParts)
Q_PROPERTY(glm::uint32 numAttributes READ getNumAttributes)
Q_PROPERTY(glm::uint32 numVertices READ getNumVertices)
Q_PROPERTY(glm::uint32 numIndices READ getNumIndices)
Q_PROPERTY(QVector<QString> attributeNames READ getAttributeNames)
Q_PROPERTY(QVector<scriptable::ScriptableMeshPartPointer> parts READ getMeshParts)
Q_PROPERTY(bool valid READ hasValidMesh)
Q_PROPERTY(bool valid READ isValid)
Q_PROPERTY(bool strong READ hasValidStrongMesh)
Q_PROPERTY(QVariantMap extents READ getMeshExtents)
Q_PROPERTY(QVariantMap bufferFormats READ getBufferFormats)
QVariantMap getBufferFormats() const;
operator const ScriptableMeshBase*() const { return (qobject_cast<const scriptable::ScriptableMeshBase*>(this)); }
@ -48,116 +53,49 @@ namespace scriptable {
ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other), QScriptable() {};
virtual ~ScriptableMesh();
Q_INVOKABLE const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast<scriptable::ScriptableModel*>(model); }
Q_INVOKABLE const scriptable::MeshPointer getOwnedMeshPointer() const { return strongMesh; }
const scriptable::MeshPointer getOwnedMeshPointer() const { return strongMesh; }
scriptable::ScriptableMeshPointer getSelf() const { return const_cast<scriptable::ScriptableMesh*>(this); }
bool hasValidMesh() const { return !weakMesh.expired(); }
bool isValid() const { return !weakMesh.expired(); }
bool hasValidStrongMesh() const { return (bool)strongMesh; }
public slots:
uint32 getNumParts() const;
uint32 getNumVertices() const;
uint32 getNumAttributes() const;
uint32 getNumIndices() const;
glm::uint32 getNumParts() const;
glm::uint32 getNumVertices() const;
glm::uint32 getNumAttributes() const;
glm::uint32 getNumIndices() const;
QVector<QString> getAttributeNames() const;
QVector<scriptable::ScriptableMeshPartPointer> getMeshParts() const;
QVariantMap getVertexAttributes(uint32 vertexIndex) const;
QVariantMap getVertexAttributes(uint32 vertexIndex, QVector<QString> attributes) const;
QVector<uint32> getIndices() const;
QVector<uint32> findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const;
QVariantMap getMeshExtents() const;
bool setVertexAttributes(uint32 vertexIndex, QVariantMap attributes);
QVariantList getAttributeValues(const QString& attributeName) const;
int _getSlotNumber(const QString& attributeName) const;
scriptable::ScriptableMeshPointer cloneMesh(bool recalcNormals = false);
public:
// TODO: remove Q_INVOKABLE (curently exposed for debugging )
Q_INVOKABLE int _getSlotNumber(const QString& attributeName) const;
operator bool() const { return !weakMesh.expired(); }
public slots:
const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast<scriptable::ScriptableModel*>(model); }
QVector<glm::uint32> getIndices() const;
QVector<glm::uint32> findNearbyVertexIndices(const glm::vec3& origin, float epsilon = 1e-6) const;
glm::uint32 addAttribute(const QString& attributeName, const QVariant& defaultValue = QVariant());
glm::uint32 fillAttribute(const QString& attributeName, const QVariant& value);
bool removeAttribute(const QString& attributeName);
QVariantList queryVertexAttributes(QVariant selector) const;
QVariantMap getVertexAttributes(glm::uint32 vertexIndex) const;
bool setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributeValues);
QVariant getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const;
bool setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& value);
scriptable::ScriptableMeshPointer cloneMesh();
// QScriptEngine-specific wrappers
uint32 mapAttributeValues(QScriptValue callback);
glm::uint32 updateVertexAttributes(QScriptValue callback);
glm::uint32 forEachVertex(QScriptValue callback);
bool isValidIndex(glm::uint32 vertexIndex, const QString& attributeName = QString()) const;
};
// TODO: part-specific wrapper for working with raw geometries
class ScriptableMeshPart : public QObject, QScriptable {
Q_OBJECT
public:
Q_PROPERTY(uint32 partIndex MEMBER partIndex CONSTANT)
Q_PROPERTY(int numElementsPerFace MEMBER _elementsPerFace CONSTANT)
Q_PROPERTY(QString topology MEMBER _topology CONSTANT)
Q_PROPERTY(uint32 numFaces READ getNumFaces)
Q_PROPERTY(uint32 numAttributes READ getNumAttributes)
Q_PROPERTY(uint32 numVertices READ getNumVertices)
Q_PROPERTY(uint32 numIndices READ getNumIndices)
Q_PROPERTY(QVector<QString> attributeNames READ getAttributeNames)
ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex);
ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; };
ScriptableMeshPart(const ScriptableMeshPart& other) : QObject(other.parent()), QScriptable(), parentMesh(other.parentMesh), partIndex(other.partIndex) {}
public slots:
scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; }
uint32 getNumAttributes() const { return parentMesh ? parentMesh->getNumAttributes() : 0; }
uint32 getNumVertices() const { return parentMesh ? parentMesh->getNumVertices() : 0; }
uint32 getNumIndices() const { return parentMesh ? parentMesh->getNumIndices() : 0; }
uint32 getNumFaces() const { return parentMesh ? parentMesh->getNumIndices() / _elementsPerFace : 0; }
QVector<QString> getAttributeNames() const { return parentMesh ? parentMesh->getAttributeNames() : QVector<QString>(); }
QVector<uint32> getFace(uint32 faceIndex) const {
if (parentMesh && faceIndex + 2 < parentMesh->getNumIndices()) {
return parentMesh->getIndices().mid(faceIndex*3, 3);
}
return QVector<uint32>();
}
QVariantMap scaleToFit(float unitScale);
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);
bool dedupeVertices(float epsilon = 1e-6);
bool recalculateNormals() { return buffer_helpers::recalculateNormals(getMeshPointer()); }
bool replaceMeshData(scriptable::ScriptableMeshPartPointer source, const QVector<QString>& attributeNames = QVector<QString>());
scriptable::ScriptableMeshPartPointer cloneMeshPart(bool recalcNormals = false) {
if (parentMesh) {
if (auto clone = parentMesh->cloneMesh(recalcNormals)) {
return clone->getMeshParts().value(partIndex);
}
}
return nullptr;
}
QString toOBJ();
public slots:
// QScriptEngine-specific wrappers
uint32 mapAttributeValues(QScriptValue callback);
public:
scriptable::ScriptableMeshPointer parentMesh;
uint32 partIndex;
protected:
int _elementsPerFace{ 3 };
QString _topology{ "triangles" };
scriptable::MeshPointer getMeshPointer() const { return parentMesh ? parentMesh->getMeshPointer() : nullptr; }
};
// callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while
// still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions
QScriptValue jsBindCallback(QScriptValue callback);
// derive a corresponding C++ class instance from the current script engine's thisObject
template <typename T> T this_qobject_cast(QScriptEngine* engine);
}
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer)
Q_DECLARE_METATYPE(QVector<scriptable::ScriptableMeshPointer>)
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer)
Q_DECLARE_METATYPE(QVector<scriptable::ScriptableMeshPartPointer>)
Q_DECLARE_METATYPE(scriptable::uint32)
Q_DECLARE_METATYPE(QVector<scriptable::uint32>)
Q_DECLARE_METATYPE(glm::uint32)
Q_DECLARE_METATYPE(QVector<glm::uint32>)

View file

@ -0,0 +1,438 @@
//
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Forward.h"
#include "ScriptableMeshPart.h"
#include "BufferViewScripting.h"
#include "GraphicsScriptingUtil.h"
#include "OBJWriter.h"
#include <BaseScriptEngine.h>
#include <QtScript/QScriptValue>
#include <RegisteredMetaTypes.h>
#include <glm/glm.hpp>
#include <glm/gtx/norm.hpp>
#include <glm/gtx/transform.hpp>
#include <graphics/BufferViewHelpers.h>
#include <graphics/GpuHelpers.h>
#include <graphics/Geometry.h>
QString scriptable::ScriptableMeshPart::toOBJ() {
if (!getMeshPointer()) {
if (context()) {
context()->throwError(QString("null mesh"));
} else {
qCWarning(graphics_scripting) << "null mesh";
}
return QString();
}
return writeOBJToString({ getMeshPointer() });
}
bool scriptable::ScriptableMeshPart::isValidIndex(glm::uint32 vertexIndex, const QString& attributeName) const {
return isValid() && parentMesh->isValidIndex(vertexIndex, attributeName);
}
bool scriptable::ScriptableMeshPart::setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributes) {
if (!isValidIndex(vertexIndex)) {
return false;
}
return buffer_helpers::mesh::setVertexAttributes(getMeshPointer(), vertexIndex, attributes);
}
QVariantMap scriptable::ScriptableMeshPart::getVertexAttributes(glm::uint32 vertexIndex) const {
if (!isValidIndex(vertexIndex)) {
return QVariantMap();
}
return parentMesh->getVertexAttributes(vertexIndex);
}
bool scriptable::ScriptableMeshPart::setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& value) {
if (!isValidIndex(vertexIndex, attributeName)) {
return false;
}
auto slotNum = parentMesh->_getSlotNumber(attributeName);
const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast<gpu::Stream::Slot>(slotNum));
return buffer_helpers::setValue(bufferView, vertexIndex, value);
}
QVariant scriptable::ScriptableMeshPart::getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const {
if (!isValidIndex(vertexIndex, attributeName)) {
return false;
}
return parentMesh->getVertexProperty(vertexIndex, attributeName);
}
QVariantList scriptable::ScriptableMeshPart::queryVertexAttributes(QVariant selector) const {
QVariantList result;
if (!isValid()) {
return result;
}
return parentMesh->queryVertexAttributes(selector);
}
glm::uint32 scriptable::ScriptableMeshPart::forEachVertex(QScriptValue _callback) {
// TODO: limit to vertices within the part's indexed range?
return isValid() ? parentMesh->forEachVertex(_callback) : 0;
}
glm::uint32 scriptable::ScriptableMeshPart::updateVertexAttributes(QScriptValue _callback) {
// TODO: limit to vertices within the part's indexed range?
return isValid() ? parentMesh->updateVertexAttributes(_callback) : 0;
}
bool scriptable::ScriptableMeshPart::replaceMeshPartData(scriptable::ScriptableMeshPartPointer src, const QVector<QString>& attributeNames) {
auto target = getMeshPointer();
auto source = src ? src->getMeshPointer() : nullptr;
if (!target || !source) {
if (context()) {
context()->throwError("ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers");
} else {
qCWarning(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers";
}
return false;
}
QVector<QString> attributes = attributeNames.isEmpty() ? src->parentMesh->getAttributeNames() : attributeNames;
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- " <<
"source:" << QString::fromStdString(source->displayName) <<
"target:" << QString::fromStdString(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 = buffer_helpers::mesh::getAllBufferViews(target);
for (const auto& a : attributeViews) {
auto slot = buffer_helpers::ATTRIBUTES[a.first];
if (!attributes.contains(a.first)) {
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot;
#endif
target->removeAttribute(slot);
}
}
}
target->setVertexBuffer(buffer_helpers::clone(source->getVertexBuffer()));
target->setIndexBuffer(buffer_helpers::clone(source->getIndexBuffer()));
target->setPartBuffer(buffer_helpers::clone(source->getPartBuffer()));
for (const auto& a : attributes) {
auto slot = buffer_helpers::ATTRIBUTES[a];
if (slot == gpu::Stream::POSITION) {
continue;
}
#ifdef SCRIPTABLE_MESH_DEBUG
auto& before = target->getAttributeBuffer(slot);
#endif
auto& input = source->getAttributeBuffer(slot);
if (input.getNumElements() == 0) {
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData buffer is empty -- pruning" << a << slot;
#endif
target->removeAttribute(slot);
} else {
#ifdef SCRIPTABLE_MESH_DEBUG
if (before.getNumElements() == 0) {
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer is empty -- adding" << a << slot;
} else {
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer exists -- updating" << a << slot;
}
#endif
target->addAttribute(slot, buffer_helpers::clone(input));
}
#ifdef SCRIPTABLE_MESH_DEBUG
auto& after = target->getAttributeBuffer(slot);
qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements();
#endif
}
return true;
}
bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) {
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<glm::uint32,glm::uint32> remapIndices;
for (glm::uint32 i = 0; i < numPositions; i++) {
const glm::uint32 numUnique = uniqueVerts.size();
const auto& position = positions.get<glm::vec3>(i);
bool unique = true;
for (glm::uint32 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(graphics_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size();
auto indices = mesh->getIndexBuffer();
auto numIndices = indices.getNumElements();
auto esize = indices._element.getSize();
QVector<glm::uint32> newIndices;
newIndices.reserve((int)numIndices);
for (glm::uint32 i = 0; i < numIndices; i++) {
glm::uint32 index = esize == 4 ? indices.get<glm::uint32>(i) : indices.get<quint16>(i);
if (remapIndices.contains(index)) {
newIndices << remapIndices[index];
} else {
qCInfo(graphics_scripting) << i << index << "!remapIndices[index]";
}
}
mesh->setIndexBuffer(buffer_helpers::newFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }));
mesh->setVertexBuffer(buffer_helpers::newFromVector(uniqueVerts, gpu::Element::VEC3F_XYZ));
auto attributeViews = buffer_helpers::mesh::getAllBufferViews(mesh);
glm::uint32 numUniqueVerts = uniqueVerts.size();
for (const auto& a : attributeViews) {
auto& view = a.second;
auto slot = buffer_helpers::ATTRIBUTES[a.first];
if (slot == gpu::Stream::POSITION) {
continue;
}
auto newView = buffer_helpers::resized(view, numUniqueVerts);
#ifdef SCRIPTABLE_MESH_DEBUG
qCInfo(graphics_scripting) << "ScriptableMeshPart::dedupeVertices" << a.first << slot << view.getNumElements();
qCInfo(graphics_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements();
#endif
glm::uint32 numElements = (glm::uint32)view.getNumElements();
for (glm::uint32 i = 0; i < numElements; i++) {
glm::uint32 fromVertexIndex = i;
glm::uint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex;
buffer_helpers::setValue<QVariant>(newView, toVertexIndex, buffer_helpers::getValue<QVariant>(view, fromVertexIndex, "dedupe"));
}
mesh->addAttribute(slot, newView);
}
return true;
}
bool scriptable::ScriptableMeshPart::removeAttribute(const QString& attributeName) {
return isValid() && parentMesh->removeAttribute(attributeName);
}
glm::uint32 scriptable::ScriptableMeshPart::addAttribute(const QString& attributeName, const QVariant& defaultValue) {
return isValid() ? parentMesh->addAttribute(attributeName, defaultValue): 0;
}
glm::uint32 scriptable::ScriptableMeshPart::fillAttribute(const QString& attributeName, const QVariant& value) {
return isValid() ? parentMesh->fillAttribute(attributeName, value) : 0;
}
QVector<glm::uint32> scriptable::ScriptableMeshPart::findNearbyPartVertexIndices(const glm::vec3& origin, float epsilon) const {
QSet<glm::uint32> result;
if (!isValid()) {
return result.toList().toVector();
}
auto mesh = getMeshPointer();
auto offset = getFirstVertexIndex();
auto numIndices = getNumIndices();
auto vertexBuffer = mesh->getVertexBuffer();
auto indexBuffer = mesh->getIndexBuffer();
const auto epsilon2 = epsilon*epsilon;
for (glm::uint32 i = 0; i < numIndices; i++) {
auto vertexIndex = buffer_helpers::getValue<glm::uint32>(indexBuffer, offset + i);
if (result.contains(vertexIndex)) {
continue;
}
const auto& position = buffer_helpers::getValue<glm::vec3>(vertexBuffer, vertexIndex);
if (glm::length2(position - origin) <= epsilon2) {
result << vertexIndex;
}
}
return result.toList().toVector();
}
scriptable::ScriptableMeshPartPointer scriptable::ScriptableMeshPart::cloneMeshPart() {
if (parentMesh) {
if (auto clone = parentMesh->cloneMesh()) {
return clone->getMeshParts().value(partIndex);
}
}
return nullptr;
}
QVariantMap scriptable::ScriptableMeshPart::scaleToFit(float unitScale) {
if (auto mesh = getMeshPointer()) {
auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts());
auto center = box.calcCenter();
float maxDimension = glm::distance(box.getMaximumPoint(), box.getMinimumPoint());
return scale(glm::vec3(unitScale / maxDimension), center);
}
return {};
}
QVariantMap scriptable::ScriptableMeshPart::translate(const glm::vec3& translation) {
return transform(glm::translate(translation));
}
QVariantMap scriptable::ScriptableMeshPart::scale(const glm::vec3& scale, const glm::vec3& origin) {
if (auto mesh = getMeshPointer()) {
auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts());
glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin;
return transform(glm::translate(center) * glm::scale(scale));
}
return {};
}
QVariantMap scriptable::ScriptableMeshPart::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) {
return rotate(glm::quat(glm::radians(eulerAngles)), origin);
}
QVariantMap scriptable::ScriptableMeshPart::rotate(const glm::quat& rotation, const glm::vec3& origin) {
if (auto mesh = getMeshPointer()) {
auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts());
glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin;
return transform(glm::translate(center) * glm::toMat4(rotation));
}
return {};
}
QVariantMap scriptable::ScriptableMeshPart::transform(const glm::mat4& transform) {
if (auto mesh = getMeshPointer()) {
const auto& pos = buffer_helpers::mesh::getBufferView(mesh, gpu::Stream::POSITION);
const glm::uint32 num = (glm::uint32)pos.getNumElements();
for (glm::uint32 i = 0; i < num; i++) {
auto& position = pos.edit<glm::vec3>(i);
position = transform * glm::vec4(position, 1.0f);
}
return parentMesh->getMeshExtents();
}
return {};
}
scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex)
: QObject(), parentMesh(parentMesh), partIndex(partIndex) {
setObjectName(QString("%1.part[%2]").arg(parentMesh ? parentMesh->objectName() : "").arg(partIndex));
}
QVector<glm::uint32> scriptable::ScriptableMeshPart::getIndices() const {
if (auto mesh = getMeshPointer()) {
#ifdef SCRIPTABLE_MESH_DEBUG
qCDebug(graphics_scripting, "getIndices mesh %p", mesh.get());
#endif
return buffer_helpers::bufferToVector<glm::uint32>(mesh->getIndexBuffer());
}
return QVector<glm::uint32>();
}
bool scriptable::ScriptableMeshPart::setFirstVertexIndex( glm::uint32 vertexIndex) {
if (!isValidIndex(vertexIndex)) {
return false;
}
auto& part = getMeshPointer()->getPartBuffer().edit<graphics::Mesh::Part>(partIndex);
part._startIndex = vertexIndex;
return true;
}
bool scriptable::ScriptableMeshPart::setBaseVertexIndex( glm::uint32 vertexIndex) {
if (!isValidIndex(vertexIndex)) {
return false;
}
auto& part = getMeshPointer()->getPartBuffer().edit<graphics::Mesh::Part>(partIndex);
part._baseVertex = vertexIndex;
return true;
}
bool scriptable::ScriptableMeshPart::setLastVertexIndex( glm::uint32 vertexIndex) {
if (!isValidIndex(vertexIndex) || vertexIndex <= getFirstVertexIndex()) {
return false;
}
auto& part = getMeshPointer()->getPartBuffer().edit<graphics::Mesh::Part>(partIndex);
part._numIndices = vertexIndex - part._startIndex;
return true;
}
bool scriptable::ScriptableMeshPart::setIndices(const QVector<glm::uint32>& indices) {
if (!isValid()) {
return false;
}
glm::uint32 len = indices.size();
if (len != getNumVertices()) {
context()->throwError(QString("setIndices: currently new indicies must be assign 1:1 across old indicies (indicies.size()=%1, numIndices=%2)")
.arg(len).arg(getNumIndices()));
}
auto mesh = getMeshPointer();
auto indexBuffer = mesh->getIndexBuffer();
// first loop to validate all indices are valid
for (glm::uint32 i = 0; i < len; i++) {
if (!isValidIndex(indices.at(i))) {
return false;
}
}
const auto first = getFirstVertexIndex();
// now actually apply them
for (glm::uint32 i = 0; i < len; i++) {
buffer_helpers::setValue(indexBuffer, first + i, indices.at(i));
}
return true;
}
const graphics::Mesh::Part& scriptable::ScriptableMeshPart::getMeshPart() const {
static const graphics::Mesh::Part invalidPart;
if (!isValid()) {
return invalidPart;
}
return getMeshPointer()->getPartBuffer().get<graphics::Mesh::Part>(partIndex);
}
bool scriptable::ScriptableMeshPart::setTopology(graphics::Mesh::Topology topology) {
if (!isValid()) {
return false;
}
auto& part = getMeshPointer()->getPartBuffer().edit<graphics::Mesh::Part>(partIndex);
if (topology == graphics::Mesh::Topology::POINTS ||
topology == graphics::Mesh::Topology::LINES ||
topology == graphics::Mesh::Topology::TRIANGLES) {
part._topology = topology;
return true;
}
return false;
}
glm::uint32 scriptable::ScriptableMeshPart::getTopologyLength() const {
switch(getTopology()) {
case graphics::Mesh::Topology::POINTS: return 1;
case graphics::Mesh::Topology::LINES: return 2;
case graphics::Mesh::Topology::TRIANGLES: return 3;
default: qCDebug(graphics_scripting) << "getTopologyLength -- unrecognized topology" << getTopology();
}
return 0;
}
QVector<glm::uint32> scriptable::ScriptableMeshPart::getFace(glm::uint32 faceIndex) const {
if (faceIndex < getNumFaces()) {
return getIndices().mid(faceIndex * getTopologyLength(), getTopologyLength());
}
return QVector<glm::uint32>();
}
QVariantMap scriptable::ScriptableMeshPart::getPartExtents() const {
graphics::Box box;
if (auto mesh = getMeshPointer()) {
box = mesh->evalPartBound(partIndex);
}
return scriptable::toVariant(box).toMap();
}

View file

@ -0,0 +1,106 @@
//
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#include "ScriptableMesh.h"
namespace scriptable {
class ScriptableMeshPart : public QObject, QScriptable {
Q_OBJECT
Q_PROPERTY(bool valid READ isValid)
Q_PROPERTY(glm::uint32 partIndex MEMBER partIndex CONSTANT)
Q_PROPERTY(glm::uint32 firstVertexIndex READ getFirstVertexIndex WRITE setFirstVertexIndex)
Q_PROPERTY(glm::uint32 baseVertexIndex READ getBaseVertexIndex WRITE setBaseVertexIndex)
Q_PROPERTY(glm::uint32 lastVertexIndex READ getLastVertexIndex WRITE setLastVertexIndex)
Q_PROPERTY(int numVerticesPerFace READ getTopologyLength)
Q_PROPERTY(graphics::Mesh::Topology topology READ getTopology WRITE setTopology)
Q_PROPERTY(glm::uint32 numFaces READ getNumFaces)
Q_PROPERTY(glm::uint32 numAttributes READ getNumAttributes)
Q_PROPERTY(glm::uint32 numVertices READ getNumVertices)
Q_PROPERTY(glm::uint32 numIndices READ getNumIndices WRITE setNumIndices)
Q_PROPERTY(QVariantMap extents READ getPartExtents)
Q_PROPERTY(QVector<QString> attributeNames READ getAttributeNames)
Q_PROPERTY(QVariantMap bufferFormats READ getBufferFormats)
public:
ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex);
ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; };
ScriptableMeshPart(const ScriptableMeshPart& other) : QObject(other.parent()), QScriptable(), parentMesh(other.parentMesh), partIndex(other.partIndex) {}
bool isValid() const { auto mesh = getMeshPointer(); return mesh && partIndex < mesh->getNumParts(); }
public slots:
QVector<glm::uint32> getIndices() const;
bool setIndices(const QVector<glm::uint32>& indices);
QVector<glm::uint32> findNearbyPartVertexIndices(const glm::vec3& origin, float epsilon = 1e-6) const;
QVariantList queryVertexAttributes(QVariant selector) const;
QVariantMap getVertexAttributes(glm::uint32 vertexIndex) const;
bool setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributeValues);
QVariant getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const;
bool setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& attributeValues);
QVector<glm::uint32> getFace(glm::uint32 faceIndex) const;
QVariantMap scaleToFit(float unitScale);
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);
glm::uint32 addAttribute(const QString& attributeName, const QVariant& defaultValue = QVariant());
glm::uint32 fillAttribute(const QString& attributeName, const QVariant& value);
bool removeAttribute(const QString& attributeName);
bool dedupeVertices(float epsilon = 1e-6);
scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; }
bool replaceMeshPartData(scriptable::ScriptableMeshPartPointer source, const QVector<QString>& attributeNames = QVector<QString>());
scriptable::ScriptableMeshPartPointer cloneMeshPart();
QString toOBJ();
// QScriptEngine-specific wrappers
glm::uint32 updateVertexAttributes(QScriptValue callback);
glm::uint32 forEachVertex(QScriptValue callback);
bool isValidIndex(glm::uint32 vertexIndex, const QString& attributeName = QString()) const;
public:
scriptable::ScriptableMeshPointer parentMesh;
glm::uint32 partIndex;
protected:
const graphics::Mesh::Part& getMeshPart() const;
scriptable::MeshPointer getMeshPointer() const { return parentMesh ? parentMesh->getMeshPointer() : nullptr; }
QVariantMap getBufferFormats() { return isValid() ? parentMesh->getBufferFormats() : QVariantMap(); }
glm::uint32 getNumAttributes() const { return isValid() ? parentMesh->getNumAttributes() : 0; }
bool setTopology(graphics::Mesh::Topology topology);
graphics::Mesh::Topology getTopology() const { return isValid() ? getMeshPart()._topology : graphics::Mesh::Topology(); }
glm::uint32 getTopologyLength() const;
glm::uint32 getNumIndices() const { return isValid() ? getMeshPart()._numIndices : 0; }
bool setNumIndices(glm::uint32 numIndices) { return setLastVertexIndex(getFirstVertexIndex() + numIndices); }
glm::uint32 getNumVertices() const { return isValid() ? parentMesh->getNumVertices() : 0; }
bool setFirstVertexIndex(glm::uint32 vertexIndex);
glm::uint32 getFirstVertexIndex() const { return isValid() ? getMeshPart()._startIndex : 0; }
bool setLastVertexIndex(glm::uint32 vertexIndex);
glm::uint32 getLastVertexIndex() const { return isValid() ? getFirstVertexIndex() + getNumIndices() - 1 : 0; }
bool setBaseVertexIndex(glm::uint32 vertexIndex);
glm::uint32 getBaseVertexIndex() const { return isValid() ? getMeshPart()._baseVertex : 0; }
glm::uint32 getNumFaces() const { return getNumIndices() / getTopologyLength(); }
QVector<QString> getAttributeNames() const { return isValid() ? parentMesh->getAttributeNames() : QVector<QString>(); }
QVariantMap getPartExtents() const;
};
}
Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer)
Q_DECLARE_METATYPE(QVector<scriptable::ScriptableMeshPartPointer>)

View file

@ -58,7 +58,7 @@ scriptable::ScriptableModelPointer scriptable::ScriptableModel::cloneModel(const
scriptable::ScriptableModelPointer clone = scriptable::ScriptableModelPointer(new scriptable::ScriptableModel(*this));
clone->meshes.clear();
for (const auto &mesh : getConstMeshes()) {
auto cloned = mesh->cloneMesh(options.value("recalculateNormals").toBool());
auto cloned = mesh->cloneMesh();
if (auto tmp = qobject_cast<scriptable::ScriptableMeshBase*>(cloned)) {
clone->meshes << *tmp;
tmp->deleteLater(); // schedule our copy for cleanup
@ -70,8 +70,8 @@ scriptable::ScriptableModelPointer scriptable::ScriptableModel::cloneModel(const
}
const QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::getConstMeshes() const {
QVector<scriptable::ScriptableMeshPointer> out;
const scriptable::ScriptableMeshes scriptable::ScriptableModel::getConstMeshes() const {
scriptable::ScriptableMeshes out;
for (const auto& mesh : meshes) {
const scriptable::ScriptableMesh* m = qobject_cast<const scriptable::ScriptableMesh*>(&mesh);
if (!m) {
@ -85,8 +85,8 @@ const QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::ge
return out;
}
QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::getMeshes() {
QVector<scriptable::ScriptableMeshPointer> out;
scriptable::ScriptableMeshes scriptable::ScriptableModel::getMeshes() {
scriptable::ScriptableMeshes out;
for (auto& mesh : meshes) {
scriptable::ScriptableMesh* m = qobject_cast<scriptable::ScriptableMesh*>(&mesh);
if (!m) {
@ -100,9 +100,10 @@ QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::getMeshe
return out;
}
quint32 scriptable::ScriptableModel::mapAttributeValues(QScriptValue callback) {
quint32 result = 0;
QVector<scriptable::ScriptableMeshPointer> in = getMeshes();
#if 0
glm::uint32 scriptable::ScriptableModel::forEachVertexAttribute(QScriptValue callback) {
glm::uint32 result = 0;
scriptable::ScriptableMeshes in = getMeshes();
if (in.size()) {
foreach (scriptable::ScriptableMeshPointer meshProxy, in) {
result += meshProxy->mapAttributeValues(callback);
@ -110,5 +111,6 @@ quint32 scriptable::ScriptableModel::mapAttributeValues(QScriptValue callback) {
}
return result;
}
#endif
#include "ScriptableModel.moc"

View file

@ -13,35 +13,34 @@
class QScriptValue;
namespace scriptable {
using ScriptableMeshes = QVector<scriptable::ScriptableMeshPointer>;
class ScriptableModel : public ScriptableModelBase {
Q_OBJECT
public:
Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT)
Q_PROPERTY(uint32 numMeshes READ getNumMeshes)
Q_PROPERTY(QVector<scriptable::ScriptableMeshPointer> meshes READ getMeshes)
Q_PROPERTY(glm::uint32 numMeshes READ getNumMeshes)
Q_PROPERTY(ScriptableMeshes meshes READ getMeshes)
public:
ScriptableModel(QObject* parent = nullptr) : ScriptableModelBase(parent) {}
ScriptableModel(const ScriptableModel& other) : ScriptableModelBase(other) {}
ScriptableModel(const ScriptableModelBase& other) : ScriptableModelBase(other) {}
ScriptableModel& operator=(const ScriptableModelBase& view) { ScriptableModelBase::operator=(view); return *this; }
Q_INVOKABLE scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap());
// 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;
operator scriptable::ScriptableModelBasePointer() {
return QPointer<scriptable::ScriptableModelBase>(qobject_cast<scriptable::ScriptableModelBase*>(this));
}
ScriptableMeshes getMeshes();
const ScriptableMeshes getConstMeshes() const;
public slots:
scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap());
QString toString() const;
// QScriptEngine-specific wrappers
Q_INVOKABLE uint32 mapAttributeValues(QScriptValue callback);
Q_INVOKABLE QString toString() const;
Q_INVOKABLE uint32 getNumMeshes() { return meshes.size(); }
//glm::uint32 forEachMeshVertexAttribute(QScriptValue callback);
protected:
glm::uint32 getNumMeshes() { return meshes.size(); }
};
}

View file

@ -15,11 +15,11 @@
#include <gpu/Stream.h>
#include "Geometry.h"
#include "GpuHelpers.h"
#include <Extents.h>
#include <AABox.h>
#include <Extents.h>
#include <glm/gtx/string_cast.hpp>
#include <glm/gtc/packing.hpp>
#include <glm/detail/type_vec.hpp>
@ -32,11 +32,10 @@ namespace {
QLoggingCategory bufferhelper_logging{ "hifi.bufferview" };
}
const std::array<const char*, 4> buffer_helpers::XYZW = { { "x", "y", "z", "w" } };
const std::array<const char*, 4> buffer_helpers::ZERO123 = { { "0", "1", "2", "3" } };
gpu::BufferView buffer_helpers::getBufferView(graphics::MeshPointer mesh, gpu::Stream::Slot slot) {
gpu::BufferView buffer_helpers::mesh::getBufferView(const graphics::MeshPointer& mesh, gpu::Stream::Slot slot) {
return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot);
}
@ -56,21 +55,13 @@ QMap<QString,int> buffer_helpers::ATTRIBUTES{
namespace {
bool boundsCheck(const gpu::BufferView& view, quint32 index) {
bool boundsCheck(const gpu::BufferView& view, glm::uint32 index) {
const auto byteLength = view._element.getSize();
return (
index < view.getNumElements() &&
index * byteLength < (view._size - 1) * byteLength
);
}
template <typename T> QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) {
return buffer_helpers::glmVecToVariant(view.get<T>(index), asArray);
}
template <typename T> void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QVariant& v) {
view.edit<T>(index) = buffer_helpers::glmVecFromVariant<T>(v);
}
}
void buffer_helpers::packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) {
@ -99,127 +90,23 @@ void buffer_helpers::packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, g
packedTangent = tangentStruct.pack;
}
bool buffer_helpers::fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) {
const auto& element = view._element;
const auto vecN = element.getScalarCount();
const auto dataType = element.getType();
const auto byteLength = element.getSize();
const auto BYTES_PER_ELEMENT = byteLength / vecN;
if (BYTES_PER_ELEMENT == 1) {
switch(vecN) {
case 2: setBufferViewElement<glm::u8vec2>(view, index, v); return true;
case 3: setBufferViewElement<glm::u8vec3>(view, index, v); return true;
case 4: {
if (element == gpu::Element::COLOR_RGBA_32) {
glm::uint32 rawColor;// = glm::packUnorm4x8(glm::vec4(glmVecFromVariant<glm::vec3>(v), 0.0f));
glm::uint32 unused;
packNormalAndTangent(glmVecFromVariant<glm::vec3>(v), glm::vec3(), rawColor, unused);
view.edit<glm::uint32>(index) = rawColor;
return true;
} else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) {
glm::uint32 packedNormal;// = glm::packSnorm3x10_1x2(glm::vec4(glmVecFromVariant<glm::vec3>(v), 0.0f));
glm::uint32 unused;
packNormalAndTangent(glm::vec3(), glmVecFromVariant<glm::vec3>(v), unused, packedNormal);
view.edit<glm::uint32>(index) = packedNormal;
return true;
}
setBufferViewElement<glm::u8vec4>(view, index, v); return true;
}
}
} else if (BYTES_PER_ELEMENT == 2) {
if (dataType == gpu::HALF) {
switch(vecN) {
case 2: view.edit<glm::int16>(index) = glm::packSnorm2x8(glmVecFromVariant<glm::vec2>(v)); return true;
case 4: view.edit<glm::int32>(index) = glm::packSnorm4x8(glmVecFromVariant<glm::vec4>(v)); return true;
default: return false;
}
}
switch(vecN) {
case 2: setBufferViewElement<glm::u16vec2>(view, index, v); return true;
case 3: setBufferViewElement<glm::u16vec3>(view, index, v); return true;
case 4: setBufferViewElement<glm::u16vec4>(view, index, v); return true;
}
} else if (BYTES_PER_ELEMENT == 4) {
if (dataType == gpu::FLOAT) {
switch(vecN) {
case 2: setBufferViewElement<glm::vec2>(view, index, v); return true;
case 3: setBufferViewElement<glm::vec3>(view, index, v); return true;
case 4: setBufferViewElement<glm::vec4>(view, index, v); return true;
}
} else {
switch(vecN) {
case 2: setBufferViewElement<glm::u32vec2>(view, index, v); return true;
case 3: setBufferViewElement<glm::u32vec3>(view, index, v); return true;
case 4: setBufferViewElement<glm::u32vec4>(view, index, v); return true;
namespace {
template <typename T>
glm::uint32 forEachGlmVec(const gpu::BufferView& view, std::function<bool(glm::uint32 index, const T& value)> func) {
QVector<glm::uint32> result;
const glm::uint32 num = (glm::uint32)view.getNumElements();
glm::uint32 i = 0;
for (; i < num; i++) {
if (!func(i, view.get<T>(i))) {
break;
}
}
return i;
}
return false;
}
QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) {
const auto& element = view._element;
const auto vecN = element.getScalarCount();
const auto dataType = element.getType();
const auto byteLength = element.getSize();
const auto BYTES_PER_ELEMENT = byteLength / vecN;
Q_ASSERT(index < view.getNumElements());
if (!boundsCheck(view, index)) {
// sanity checks
auto byteOffset = index * vecN * BYTES_PER_ELEMENT;
auto maxByteOffset = (view._size - 1) * vecN * BYTES_PER_ELEMENT;
if (byteOffset > maxByteOffset) {
qDebug() << "toVariant -- byteOffset out of range " << byteOffset << " < " << maxByteOffset;
qDebug() << "toVariant -- index: " << index << "numElements" << view.getNumElements();
qDebug() << "toVariant -- vecN: " << vecN << "byteLength" << byteLength << "BYTES_PER_ELEMENT" << BYTES_PER_ELEMENT;
}
Q_ASSERT(byteOffset <= maxByteOffset);
}
if (BYTES_PER_ELEMENT == 1) {
switch(vecN) {
case 2: return getBufferViewElement<glm::u8vec2>(view, index, asArray);
case 3: return getBufferViewElement<glm::u8vec3>(view, index, asArray);
case 4: {
if (element == gpu::Element::COLOR_RGBA_32) {
auto rawColor = view.get<glm::uint32>(index);
return glmVecToVariant(glm::vec3(glm::unpackUnorm4x8(rawColor)));
} else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) {
auto packedNormal = view.get<glm::uint32>(index);
return glmVecToVariant(glm::vec3(glm::unpackSnorm3x10_1x2(packedNormal)));
}
return getBufferViewElement<glm::u8vec4>(view, index, asArray);
}
}
} else if (BYTES_PER_ELEMENT == 2) {
if (dataType == gpu::HALF) {
switch(vecN) {
case 2: return glmVecToVariant(glm::vec2(glm::unpackSnorm2x8(view.get<glm::int16>(index))));
case 4: return glmVecToVariant(glm::vec4(glm::unpackSnorm4x8(view.get<glm::int32>(index))));
}
}
switch(vecN) {
case 2: return getBufferViewElement<glm::u16vec2>(view, index, asArray);
case 3: return getBufferViewElement<glm::u16vec3>(view, index, asArray);
case 4: return getBufferViewElement<glm::u16vec4>(view, index, asArray);
}
} else if (BYTES_PER_ELEMENT == 4) {
if (dataType == gpu::FLOAT) {
switch(vecN) {
case 2: return getBufferViewElement<glm::vec2>(view, index, asArray);
case 3: return getBufferViewElement<glm::vec3>(view, index, asArray);
case 4: return getBufferViewElement<glm::vec4>(view, index, asArray);
}
} else {
switch(vecN) {
case 2: return getBufferViewElement<glm::u32vec2>(view, index, asArray);
case 3: return getBufferViewElement<glm::u32vec3>(view, index, asArray);
case 4: return getBufferViewElement<glm::u32vec4>(view, index, asArray);
}
}
}
return QVariant();
template<> glm::uint32 buffer_helpers::forEach<glm::vec3>(const gpu::BufferView& view, std::function<bool(glm::uint32 index, const glm::vec3& value)> func) {
return forEachGlmVec<glm::vec3>(view, func);
}
template <typename T>
@ -256,59 +143,63 @@ const T buffer_helpers::glmVecFromVariant(const QVariant& v) {
} else {
value = list.value(i).toFloat();
}
#ifdef DEBUG_BUFFERVIEW_SCRIPTING
#ifdef DEBUG_BUFFERVIEW_HELPERS
if (value != value) { // NAN
qWarning().nospace()<< "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString();
}
#endif
#endif
result[i] = value;
}
return result;
}
// QVector<T> => BufferView
template <typename T>
gpu::BufferView buffer_helpers::fromVector(const QVector<T>& elements, const gpu::Element& elementType) {
gpu::BufferView buffer_helpers::newFromVector(const QVector<T>& elements, const 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 };
}
namespace {
template <typename T>
gpu::BufferView _fromVector(const QVector<T>& elements, const gpu::Element& elementType) {
gpu::BufferView bufferViewFromVector(const QVector<T>& elements, const 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 buffer_helpers::fromVector<unsigned int>(
const QVector<unsigned int>& elements, const gpu::Element& elementType
) { return _fromVector(elements, elementType); }
template<> gpu::BufferView buffer_helpers::fromVector<glm::vec3>(
const QVector<glm::vec3>& elements, const gpu::Element& elementType
) { return _fromVector(elements, elementType); }
template <typename T> struct GpuVec4ToGlm;
template <typename T> struct GpuScalarToGlm;
template<> gpu::BufferView buffer_helpers::newFromVector<unsigned int>(const QVector<unsigned int>& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); }
template<> gpu::BufferView buffer_helpers::newFromVector<glm::vec2>(const QVector<glm::vec2>& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); }
template<> gpu::BufferView buffer_helpers::newFromVector<glm::vec3>( const QVector<glm::vec3>& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); }
template<> gpu::BufferView buffer_helpers::newFromVector<glm::vec4>(const QVector<glm::vec4>& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); }
template<> gpu::BufferView buffer_helpers::newFromVector<graphics::Mesh::Part>(const QVector<graphics::Mesh::Part>& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); }
struct GpuToGlmAdapter {
static float error(const QString& name, const gpu::BufferView& view, quint32 index, const char *hint) {
qDebug() << QString("GpuToGlmAdapter:: unhandled type=%1(element=%2) size=%3(per=%4) vec%5 hint=%6 #%7")
static float error(const QString& name, const gpu::BufferView& view, glm::uint32 index, const char *hint) {
qDebug() << QString("GpuToGlmAdapter:: unhandled type=%1(element=%2) size=%3(location=%4,per=%5) vec%6 hint=%7 #%8 %9 %10")
.arg(name)
.arg(view._element.getType())
.arg(gpu::toString(view._element.getType()))
.arg(view._element.getSize())
.arg(view._element.getLocationSize())
.arg(view._element.getSize() / view._element.getScalarCount())
.arg(view._element.getScalarCount())
.arg(hint)
.arg(view.getNumElements());
.arg(view.getNumElements())
.arg(gpu::toString(view._element.getSemantic()))
.arg(gpu::toString(view._element.getDimension()));
Q_ASSERT(false);
assert(false);
return NAN;
}
};
#define CHECK_SIZE(T) if (view._element.getSize() != sizeof(T)) { qDebug() << "invalid elementSize" << hint << view._element.getSize() << "expected:" << sizeof(T); break; }
template <typename T> struct GpuScalarToGlm : GpuToGlmAdapter {
static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) {
static T get(const gpu::BufferView& view, glm::uint32 index, const char *hint) {
#ifdef DEBUG_BUFFERVIEW_HELPERS
if(!boundsCheck(view, index))return T(error("GpuScalarToGlm::get::out of bounds", view, index, hint));
#endif
switch(view._element.getType()) {
case gpu::UINT32: return view.get<glm::uint32>(index);
case gpu::UINT16: return view.get<glm::uint16>(index);
case gpu::UINT8: return view.get<glm::uint8>(index);
@ -316,44 +207,101 @@ template <typename T> struct GpuScalarToGlm : GpuToGlmAdapter {
case gpu::INT16: return view.get<glm::int16>(index);
case gpu::INT8: return view.get<glm::int8>(index);
case gpu::FLOAT: return view.get<glm::float32>(index);
case gpu::HALF: return T(glm::unpackSnorm1x8(view.get<glm::int8>(index)));
case gpu::HALF: return T(glm::unpackHalf1x16(view.get<glm::uint16>(index)));
case gpu::NUINT8: return T(glm::unpackUnorm1x8(view.get<glm::uint8>(index)));
default: break;
} return T(error("GpuScalarToGlm", view, index, hint));
} return T(error("GpuScalarToGlm::get", view, index, hint));
}
static bool set(const gpu::BufferView& view, glm::uint32 index, const T& value, const char *hint) {
#ifdef DEBUG_BUFFERVIEW_HELPERS
if(!boundsCheck(view, index))return T(error("GpuScalarToGlm::set::out of bounds", view, index, hint));
#endif
switch(view._element.getType()) {
case gpu::UINT32: view.edit<glm::uint32>(index) = value; return true;
case gpu::UINT16: view.edit<glm::uint16>(index) = value; return true;
case gpu::UINT8: view.edit<glm::uint8>(index) = value; return true;
case gpu::INT32: view.edit<glm::int32>(index) = value; return true;
case gpu::INT16: view.edit<glm::int16>(index) = value; return true;
case gpu::INT8: view.edit<glm::int8>(index) = value; return true;
case gpu::FLOAT: view.edit<glm::float32>(index) = value; return true;
case gpu::HALF: view.edit<glm::uint16>(index) = glm::packHalf1x16(value); return true;
case gpu::NUINT8: view.edit<glm::uint8>(index) = glm::packUnorm1x8(value); return true;
default: break;
} error("GpuScalarToGlm::set", view, index, hint); return false;
}
};
template <typename T> struct GpuVec2ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) {
case gpu::UINT32: return view.get<glm::u32vec2>(index);
case gpu::UINT16: return view.get<glm::u16vec2>(index);
case gpu::UINT8: return view.get<glm::u8vec2>(index);
case gpu::INT32: return view.get<glm::i32vec2>(index);
case gpu::INT16: return view.get<glm::i16vec2>(index);
case gpu::INT8: return view.get<glm::i8vec2>(index);
case gpu::FLOAT: return view.get<glm::fvec2>(index);
case gpu::HALF: return glm::unpackSnorm2x8(view.get<glm::int16>(index));
default: break;
} return T(error("GpuVec2ToGlm", view, index, hint)); }};
template <typename T> struct GpuVec2ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, glm::uint32 index, const char *hint) {
#ifdef DEBUG_BUFFERVIEW_HELPERS
if(!boundsCheck(view, index))return T(error("GpuVec2ToGlm::get::out of bounds", view, index, hint));
#endif
switch(view._element.getType()) {
case gpu::UINT32: return view.get<glm::u32vec2>(index);
case gpu::UINT16: return view.get<glm::u16vec2>(index);
case gpu::UINT8: return view.get<glm::u8vec2>(index);
case gpu::INT32: return view.get<glm::i32vec2>(index);
case gpu::INT16: return view.get<glm::i16vec2>(index);
case gpu::INT8: return view.get<glm::i8vec2>(index);
case gpu::FLOAT: return view.get<glm::fvec2>(index);
case gpu::HALF: CHECK_SIZE(glm::uint32); return glm::unpackHalf2x16(view.get<glm::uint32>(index));
case gpu::NUINT16: CHECK_SIZE(glm::uint32); return glm::unpackUnorm2x16(view.get<glm::uint32>(index));
case gpu::NUINT8: CHECK_SIZE(glm::uint16); return glm::unpackUnorm2x8(view.get<glm::uint16>(index));
default: break;
} return T(error("GpuVec2ToGlm::get", view, index, hint)); }
static bool set(const gpu::BufferView& view, glm::uint32 index, const T& value, const char *hint) {
#ifdef DEBUG_BUFFERVIEW_HELPERS
if(!boundsCheck(view, index))return T(error("GpuVec2ToGlm::set::out of bounds", view, index, hint));
#endif
switch(view._element.getType()) {
// TODO: flush out GpuVec2ToGlm<T>::set(value)
case gpu::FLOAT: view.edit<glm::fvec2>(index) = value; return true;
case gpu::HALF: view.edit<glm::uint32>(index) = glm::packHalf2x16(value); return true;
case gpu::NUINT16: view.edit<glm::uint32>(index) = glm::packUnorm2x16(value); return true;
case gpu::NUINT8: view.edit<glm::uint16>(index) = glm::packUnorm2x8(value); return true;
default: break;
} error("GpuVec2ToGlm::set", view, index, hint); return false;
}
};
template <typename T> struct GpuVec3ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) {
case gpu::UINT32: return view.get<glm::u32vec3>(index);
case gpu::UINT16: return view.get<glm::u16vec3>(index);
case gpu::UINT8: return view.get<glm::u8vec3>(index);
case gpu::INT32: return view.get<glm::i32vec3>(index);
case gpu::INT16: return view.get<glm::i16vec3>(index);
case gpu::INT8: return view.get<glm::i8vec3>(index);
case gpu::FLOAT: return view.get<glm::fvec3>(index);
case gpu::HALF:
case gpu::NUINT8:
case gpu::NINT2_10_10_10:
if (view._element.getSize() == sizeof(glm::int32)) {
return GpuVec4ToGlm<T>::get(view, index, hint);
}
default: break;
} return T(error("GpuVec3ToGlm", view, index, hint)); }};
template <typename T> struct GpuVec4ToGlm;
template <typename T> struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) {
assert(view._element.getSize() == sizeof(glm::int32));
template <typename T> struct GpuVec3ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, glm::uint32 index, const char *hint) {
#ifdef DEBUG_BUFFERVIEW_HELPERS
if(!boundsCheck(view, index))return T(error("GpuVec3ToGlm::get::out of bounds", view, index, hint));
#endif
switch(view._element.getType()) {
case gpu::UINT32: return view.get<glm::u32vec3>(index);
case gpu::UINT16: return view.get<glm::u16vec3>(index);
case gpu::UINT8: return view.get<glm::u8vec3>(index);
case gpu::INT32: return view.get<glm::i32vec3>(index);
case gpu::INT16: return view.get<glm::i16vec3>(index);
case gpu::INT8: return view.get<glm::i8vec3>(index);
case gpu::FLOAT: return view.get<glm::fvec3>(index);
case gpu::HALF: CHECK_SIZE(glm::uint64); return T(glm::unpackHalf4x16(view.get<glm::uint64>(index)));
case gpu::NUINT8: CHECK_SIZE(glm::uint32); return T(glm::unpackUnorm4x8(view.get<glm::uint32>(index)));
case gpu::NINT2_10_10_10: return T(glm::unpackSnorm3x10_1x2(view.get<glm::uint32>(index)));
default: break;
} return T(error("GpuVec3ToGlm::get", view, index, hint)); }
static bool set(const gpu::BufferView& view, glm::uint32 index, const T& value, const char *hint) {
#ifdef DEBUG_BUFFERVIEW_HELPERS
if(!boundsCheck(view, index))return T(error("GpuVec3ToGlm::set::out of bounds", view, index, hint));
#endif
switch(view._element.getType()) {
// TODO: flush out GpuVec3ToGlm<T>::set(value)
case gpu::FLOAT: view.edit<glm::fvec3>(index) = value; return true;
case gpu::NUINT8: CHECK_SIZE(glm::uint32); view.edit<glm::uint32>(index) = glm::packUnorm4x8(glm::fvec4(value,0.0f)); return true;
case gpu::UINT8: view.edit<glm::u8vec3>(index) = value; return true;
case gpu::NINT2_10_10_10: view.edit<glm::uint32>(index) = glm::packSnorm3x10_1x2(glm::fvec4(value,0.0f)); return true;
default: break;
} error("GpuVec3ToGlm::set", view, index, hint); return false;
}
};
template <typename T> struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, glm::uint32 index, const char *hint) {
#ifdef DEBUG_BUFFERVIEW_HELPERS
if(!boundsCheck(view, index))return T(error("GpuVec4ToGlm::get::out of bounds", view, index, hint));
#endif
switch(view._element.getType()) {
case gpu::UINT32: return view.get<glm::u32vec4>(index);
case gpu::UINT16: return view.get<glm::u16vec4>(index);
@ -362,8 +310,8 @@ template <typename T> struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const
case gpu::INT16: return view.get<glm::i16vec4>(index);
case gpu::INT8: return view.get<glm::i8vec4>(index);
case gpu::NUINT32: break;
case gpu::NUINT16: break;
case gpu::NUINT8: return glm::unpackUnorm4x8(view.get<glm::uint32>(index));
case gpu::NUINT16: CHECK_SIZE(glm::uint64); return glm::unpackUnorm4x16(view.get<glm::uint64>(index));
case gpu::NUINT8: CHECK_SIZE(glm::uint32); return glm::unpackUnorm4x8(view.get<glm::uint32>(index));
case gpu::NUINT2: break;
case gpu::NINT32: break;
case gpu::NINT16: break;
@ -371,56 +319,221 @@ template <typename T> struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const
case gpu::COMPRESSED: break;
case gpu::NUM_TYPES: break;
case gpu::FLOAT: return view.get<glm::fvec4>(index);
case gpu::HALF: return glm::unpackSnorm4x8(view.get<glm::int32>(index));
case gpu::HALF: CHECK_SIZE(glm::uint64); return glm::unpackHalf4x16(view.get<glm::uint64>(index));
case gpu::NINT2_10_10_10: return glm::unpackSnorm3x10_1x2(view.get<glm::uint32>(index));
} return T(error("GpuVec4ToGlm", view, index, hint)); }};
} return T(error("GpuVec4ToGlm::get", view, index, hint)); }
static bool set(const gpu::BufferView& view, glm::uint32 index, const T& value, const char *hint) {
#ifdef DEBUG_BUFFERVIEW_HELPERS
if(!boundsCheck(view, index))return T(error("GpuVec4ToGlm::set::out of bounds", view, index, hint));
#endif
switch(view._element.getType()) {
case gpu::FLOAT: view.edit<glm::fvec4>(index) = value; return true;
case gpu::HALF: CHECK_SIZE(glm::uint64); view.edit<glm::uint64_t>(index) = glm::packHalf4x16(value); return true;
case gpu::UINT8: view.edit<glm::u8vec4>(index) = value; return true;
case gpu::NINT2_10_10_10: view.edit<glm::uint32>(index) = glm::packSnorm3x10_1x2(value); return true;
case gpu::NUINT16: CHECK_SIZE(glm::uint64); view.edit<glm::uint64>(index) = glm::packUnorm4x16(value); return true;
case gpu::NUINT8: CHECK_SIZE(glm::uint32); view.edit<glm::uint32>(index) = glm::packUnorm4x8(value); return true;
default: break;
} error("GpuVec4ToGlm::set", view, index, hint); return false;
}
};
#undef CHECK_SIZE
template <typename FUNC, typename T>
struct getVec {
static QVector<T> __to_vector__(const gpu::BufferView& view, const char *hint) {
struct GpuValueResolver {
static QVector<T> toVector(const gpu::BufferView& view, const char *hint) {
QVector<T> result;
const quint32 count = (quint32)view.getNumElements();
const glm::uint32 count = (glm::uint32)view.getNumElements();
result.resize(count);
for (quint32 i = 0; i < count; i++) {
for (glm::uint32 i = 0; i < count; i++) {
result[i] = FUNC::get(view, i, hint);
}
return result;
}
static T __to_value__(const gpu::BufferView& view, quint32 index, const char *hint) {
assert(boundsCheck(view, index));
static T toValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) {
return FUNC::get(view, index, hint);
}
};
// BufferView => QVector<T>
template <> QVector<int> buffer_helpers::toVector<int>(const gpu::BufferView& view, const char *hint) {
return getVec<GpuScalarToGlm<int>,int>::__to_vector__(view, hint);
}
template <> QVector<glm::vec2> buffer_helpers::toVector<glm::vec2>(const gpu::BufferView& view, const char *hint) {
return getVec<GpuVec2ToGlm<glm::vec2>,glm::vec2>::__to_vector__(view, hint);
}
template <> QVector<glm::vec3> buffer_helpers::toVector<glm::vec3>(const gpu::BufferView& view, const char *hint) {
return getVec<GpuVec3ToGlm<glm::vec3>,glm::vec3>::__to_vector__(view, hint);
}
template <> QVector<glm::vec4> buffer_helpers::toVector<glm::vec4>(const gpu::BufferView& view, const char *hint) {
return getVec<GpuVec4ToGlm<glm::vec4>,glm::vec4>::__to_vector__(view, hint);
template <typename U> QVector<U> buffer_helpers::bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver<GpuScalarToGlm<U>,U>::toVector(view, hint); }
template<> QVector<int> buffer_helpers::bufferToVector<int>(const gpu::BufferView& view, const char *hint) { return GpuValueResolver<GpuScalarToGlm<int>,int>::toVector(view, hint); }
template<> QVector<glm::uint16> buffer_helpers::bufferToVector<glm::uint16>(const gpu::BufferView& view, const char *hint) { return GpuValueResolver<GpuScalarToGlm<glm::uint16>,glm::uint16>::toVector(view, hint); }
template<> QVector<glm::uint32> buffer_helpers::bufferToVector<glm::uint32>(const gpu::BufferView& view, const char *hint) { return GpuValueResolver<GpuScalarToGlm<glm::uint32>,glm::uint32>::toVector(view, hint); }
template<> QVector<glm::vec2> buffer_helpers::bufferToVector<glm::vec2>(const gpu::BufferView& view, const char *hint) { return GpuValueResolver<GpuVec2ToGlm<glm::vec2>,glm::vec2>::toVector(view, hint); }
template<> QVector<glm::vec3> buffer_helpers::bufferToVector<glm::vec3>(const gpu::BufferView& view, const char *hint) { return GpuValueResolver<GpuVec3ToGlm<glm::vec3>,glm::vec3>::toVector(view, hint); }
template<> QVector<glm::vec4> buffer_helpers::bufferToVector<glm::vec4>(const gpu::BufferView& view, const char *hint) { return GpuValueResolver<GpuVec4ToGlm<glm::vec4>,glm::vec4>::toVector(view, hint); }
// view.get<T> with conversion between types
template<> int buffer_helpers::getValue<int>(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuScalarToGlm<int>::get(view, index, hint); }
template<> glm::uint32 buffer_helpers::getValue<glm::uint32>(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuScalarToGlm<glm::uint32>::get(view, index, hint); }
template<> glm::vec2 buffer_helpers::getValue<glm::vec2>(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuVec2ToGlm<glm::vec2>::get(view, index, hint); }
template<> glm::vec3 buffer_helpers::getValue<glm::vec3>(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuVec3ToGlm<glm::vec3>::get(view, index, hint); }
template<> glm::vec4 buffer_helpers::getValue<glm::vec4>(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuVec4ToGlm<glm::vec4>::get(view, index, hint); }
// bufferView => QVariant
template<> QVariant buffer_helpers::getValue<QVariant>(const gpu::BufferView& view, glm::uint32 index, const char* hint) {
if (!boundsCheck(view, index)) {
qDebug() << "getValue<QVariant> -- out of bounds" << index << hint;
return false;
}
const auto dataType = view._element.getType();
switch(view._element.getScalarCount()) {
case 1:
if (dataType == gpu::Type::FLOAT) {
return GpuScalarToGlm<glm::float32>::get(view, index, hint);
} else {
switch(dataType) {
case gpu::INT8: case gpu::INT16: case gpu::INT32:
case gpu::NINT8: case gpu::NINT16: case gpu::NINT32:
case gpu::NINT2_10_10_10:
// signed
return GpuScalarToGlm<glm::int32>::get(view, index, hint);
default:
// unsigned
return GpuScalarToGlm<glm::uint32>::get(view, index, hint);
}
}
case 2: return glmVecToVariant(GpuVec2ToGlm<glm::vec2>::get(view, index, hint));
case 3: return glmVecToVariant(GpuVec3ToGlm<glm::vec3>::get(view, index, hint));
case 4: return glmVecToVariant(GpuVec4ToGlm<glm::vec4>::get(view, index, hint));
}
return QVariant();
}
glm::uint32 buffer_helpers::mesh::forEachVertex(const graphics::MeshPointer& mesh, std::function<bool(glm::uint32 index, const QVariantMap& values)> func) {
glm::uint32 i = 0;
auto attributeViews = getAllBufferViews(mesh);
auto nPositions = mesh->getNumVertices();
for (; i < nPositions; i++) {
QVariantMap values;
for (const auto& a : attributeViews) {
values[a.first] = buffer_helpers::getValue<QVariant>(a.second, i, qUtf8Printable(a.first));
}
if (!func(i, values)) {
break;
}
}
return i;
}
// indexed conversion accessors (like the hypothetical "view.convert<T>(i)")
template <> int buffer_helpers::convert<int>(const gpu::BufferView& view, quint32 index, const char *hint) {
return getVec<GpuScalarToGlm<int>,int>::__to_value__(view, index, hint);
// view.edit<T> with conversion between types
template<> bool buffer_helpers::setValue<QVariant>(const gpu::BufferView& view, glm::uint32 index, const QVariant& v, const char* hint) {
if (!boundsCheck(view, index)) {
qDebug() << "setValue<QVariant> -- out of bounds" << index << hint;
return false;
}
const auto dataType = view._element.getType();
switch(view._element.getScalarCount()) {
case 1:
if (dataType == gpu::Type::FLOAT) {
return GpuScalarToGlm<glm::float32>::set(view, index, v.toFloat(), hint);
} else {
switch(dataType) {
case gpu::INT8: case gpu::INT16: case gpu::INT32:
case gpu::NINT8: case gpu::NINT16: case gpu::NINT32:
case gpu::NINT2_10_10_10:
// signed
return GpuScalarToGlm<glm::int32>::set(view, index, v.toInt(), hint);
default:
// unsigned
return GpuScalarToGlm<glm::uint32>::set(view, index, v.toUInt(), hint);
}
}
return false;
case 2: return GpuVec2ToGlm<glm::vec2>::set(view, index, glmVecFromVariant<glm::vec2>(v), hint);
case 3: return GpuVec3ToGlm<glm::vec3>::set(view, index, glmVecFromVariant<glm::vec3>(v), hint);
case 4: return GpuVec4ToGlm<glm::vec4>::set(view, index, glmVecFromVariant<glm::vec4>(v), hint);
}
return false;
}
template <> glm::vec2 buffer_helpers::convert<glm::vec2>(const gpu::BufferView& view, quint32 index, const char *hint) {
return getVec<GpuVec2ToGlm<glm::vec2>,glm::vec2>::__to_value__(view, index, hint);
template<> bool buffer_helpers::setValue<glm::uint32>(const gpu::BufferView& view, glm::uint32 index, const glm::uint32& value, const char* hint) {
return GpuScalarToGlm<glm::uint32>::set(view, index, value, hint);
}
template <> glm::vec3 buffer_helpers::convert<glm::vec3>(const gpu::BufferView& view, quint32 index, const char *hint) {
return getVec<GpuVec3ToGlm<glm::vec3>,glm::vec3>::__to_value__(view, index, hint);
template<> bool buffer_helpers::setValue<glm::uint16>(const gpu::BufferView& view, glm::uint32 index, const glm::uint16& value, const char* hint) {
return GpuScalarToGlm<glm::uint16>::set(view, index, value, hint);
}
template <> glm::vec4 buffer_helpers::convert<glm::vec4>(const gpu::BufferView& view, quint32 index, const char *hint) {
return getVec<GpuVec4ToGlm<glm::vec4>,glm::vec4>::__to_value__(view, index, hint);
template<> bool buffer_helpers::setValue<glm::vec2>(const gpu::BufferView& view, glm::uint32 index, const glm::vec2& value, const char* hint) {
return GpuVec2ToGlm<glm::vec2>::set(view, index, value, hint);
}
template<> bool buffer_helpers::setValue<glm::vec3>(const gpu::BufferView& view, glm::uint32 index, const glm::vec3& value, const char* hint) {
return GpuVec3ToGlm<glm::vec3>::set(view, index, value, hint);
}
template<> bool buffer_helpers::setValue<glm::vec4>(const gpu::BufferView& view, glm::uint32 index, const glm::vec4& value, const char* hint) {
return GpuVec4ToGlm<glm::vec4>::set(view, index, value, hint);
}
bool buffer_helpers::mesh::setVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 index, const QVariantMap& attributes) {
bool ok = true;
for (auto& a : getAllBufferViews(mesh)) {
const auto& name = a.first;
if (attributes.contains(name)) {
const auto& value = attributes.value(name);
if (value.isValid()) {
auto& view = a.second;
buffer_helpers::setValue<QVariant>(view, index, value);
} else {
ok = false;
//qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name;
}
}
}
return ok;
}
QVariant buffer_helpers::mesh::getVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 vertexIndex) {
auto attributeViews = getAllBufferViews(mesh);
QVariantMap values;
for (const auto& a : attributeViews) {
values[a.first] = buffer_helpers::getValue<QVariant>(a.second, vertexIndex, qUtf8Printable(a.first));
}
return values;
}
// QVariantList => QVector<T>
namespace {
template <class T> QVector<T> qVariantListToGlmVector(const QVariantList& list) {
QVector<T> output;
output.resize(list.size());
int i = 0;
for (const auto& v : list) {
output[i++] = buffer_helpers::glmVecFromVariant<T>(v);
}
return output;
}
template <typename T> QVector<T> qVariantListToScalarVector(const QVariantList& list) {
QVector<T> output;
output.resize(list.size());
int i = 0;
for (const auto& v : list) {
output[i++] = v.value<T>();
}
return output;
}
}
template <class T> QVector<T> buffer_helpers::variantToVector(const QVariant& value) { qDebug() << "variantToVector[class]"; return qVariantListToGlmVector<T>(value.toList()); }
template<> QVector<glm::float32> buffer_helpers::variantToVector<glm::float32>(const QVariant& value) { return qVariantListToScalarVector<glm::float32>(value.toList()); }
template<> QVector<glm::uint32> buffer_helpers::variantToVector<glm::uint32>(const QVariant& value) { return qVariantListToScalarVector<glm::uint32>(value.toList()); }
template<> QVector<glm::int32> buffer_helpers::variantToVector<glm::int32>(const QVariant& value) { return qVariantListToScalarVector<glm::int32>(value.toList()); }
template<> QVector<glm::vec2> buffer_helpers::variantToVector<glm::vec2>(const QVariant& value) { return qVariantListToGlmVector<glm::vec2>(value.toList()); }
template<> QVector<glm::vec3> buffer_helpers::variantToVector<glm::vec3>(const QVariant& value) { return qVariantListToGlmVector<glm::vec3>(value.toList()); }
template<> QVector<glm::vec4> buffer_helpers::variantToVector<glm::vec4>(const QVariant& value) { return qVariantListToGlmVector<glm::vec4>(value.toList()); }
template<> gpu::BufferView buffer_helpers::newFromVector<QVariant>(const QVector<QVariant>& _elements, const gpu::Element& elementType) {
glm::uint32 numElements = _elements.size();
auto buffer = new gpu::Buffer();
buffer->resize(elementType.getSize() * numElements);
auto bufferView = gpu::BufferView(buffer, elementType);
for (glm::uint32 i = 0; i < numElements; i++) {
setValue<QVariant>(bufferView, i, _elements[i]);
}
return bufferView;
}
gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) {
return gpu::BufferView(
@ -429,25 +542,29 @@ gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) {
);
}
// TODO: preserve existing data
gpu::BufferView buffer_helpers::resize(const gpu::BufferView& input, quint32 numElements) {
gpu::BufferView buffer_helpers::resized(const gpu::BufferView& input, glm::uint32 numElements) {
#ifdef DEBUG_BUFFERVIEW_HELPERS
auto effectiveSize = input._buffer->getSize() / input.getNumElements();
qCDebug(bufferhelper_logging) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize;
#endif
auto vsize = input._element.getSize() * numElements;
std::unique_ptr<gpu::Byte[]> data{ new gpu::Byte[vsize] };
memset(data.get(), 0, vsize);
auto buffer = new gpu::Buffer(vsize, data.get());
memcpy(data.get(), input._buffer->getData(), std::min(vsize, (glm::uint32)input._buffer->getSize()));
auto output = gpu::BufferView(buffer, input._element);
#ifdef DEBUG_BUFFERVIEW_HELPERS
qCDebug(bufferhelper_logging) << "resized output" << output.getNumElements() << output._buffer->getSize();
#endif
return output;
}
graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) {
graphics::MeshPointer buffer_helpers::mesh::clone(const graphics::MeshPointer& mesh) {
auto clone = std::make_shared<graphics::Mesh>();
clone->displayName = (QString::fromStdString(mesh->displayName) + "-clone").toStdString();
clone->setIndexBuffer(buffer_helpers::clone(mesh->getIndexBuffer()));
clone->setPartBuffer(buffer_helpers::clone(mesh->getPartBuffer()));
auto attributeViews = buffer_helpers::gatherBufferViews(mesh);
auto attributeViews = buffer_helpers::mesh::getAllBufferViews(mesh);
for (const auto& a : attributeViews) {
auto& view = a.second;
auto slot = buffer_helpers::ATTRIBUTES[a.first];
@ -461,195 +578,17 @@ graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) {
return clone;
}
namespace {
// expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type
gpu::BufferView _expandedAttributeBuffer(const graphics::MeshPointer mesh, gpu::Stream::Slot slot) {
gpu::BufferView bufferView = buffer_helpers::getBufferView(mesh, slot);
const auto& elementType = bufferView._element;
gpu::Size elementSize = elementType.getSize();
auto nPositions = mesh->getNumVertices();
auto vsize = nPositions * elementSize;
auto diffTypes = (elementType.getType() != bufferView._element.getType() ||
elementType.getSize() > bufferView._element.getSize() ||
elementType.getScalarCount() > bufferView._element.getScalarCount() ||
vsize > bufferView._size
);
QString hint = QString("%1").arg(slot);
#ifdef DEV_BUILD
auto beforeCount = bufferView.getNumElements();
auto beforeTotal = bufferView._size;
#endif
if (bufferView.getNumElements() < nPositions || diffTypes) {
if (!bufferView._buffer || bufferView.getNumElements() == 0) {
qCInfo(bufferhelper_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView";
std::unique_ptr<gpu::Byte[]> data{ new gpu::Byte[vsize] };
memset(data.get(), 0, vsize);
auto buffer = new gpu::Buffer(vsize, data.get());
bufferView = gpu::BufferView(buffer, elementType);
mesh->addAttribute(slot, bufferView);
} else {
qCInfo(bufferhelper_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize;
bufferView._element = elementType;
bufferView._buffer->resize(vsize);
bufferView._size = bufferView._buffer->getSize();
}
}
#ifdef DEV_BUILD
auto afterCount = bufferView.getNumElements();
auto afterTotal = bufferView._size;
if (beforeTotal != afterTotal || beforeCount != afterCount) {
QString typeName = QString("%1").arg(bufferView._element.getType());
qCDebug(bufferhelper_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)",
hint.toStdString().c_str(), bufferView._element.getScalarCount(),
typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal);
}
#endif
return bufferView;
}
gpu::BufferView expandAttributeToMatchPositions(graphics::MeshPointer mesh, gpu::Stream::Slot slot) {
if (slot == gpu::Stream::POSITION) {
return buffer_helpers::getBufferView(mesh, slot);
}
return _expandedAttributeBuffer(mesh, slot);
}
}
std::map<QString, gpu::BufferView> buffer_helpers::gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions) {
std::map<QString, gpu::BufferView> buffer_helpers::mesh::getAllBufferViews(const graphics::MeshPointer& mesh) {
std::map<QString, gpu::BufferView> attributeViews;
if (!mesh) {
return attributeViews;
}
for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) {
auto name = a.first;
auto slot = a.second;
auto view = getBufferView(mesh, slot);
auto beforeCount = view.getNumElements();
#if DEV_BUILD
auto beforeTotal = view._size;
#endif
if (expandToMatchPositions.contains(name)) {
expandAttributeToMatchPositions(mesh, slot);
}
if (beforeCount > 0) {
auto element = view._element;
QString typeName = QString("%1").arg(element.getType());
attributeViews[name] = getBufferView(mesh, slot);
#if DEV_BUILD
const auto vecN = element.getScalarCount();
auto afterTotal = attributeViews[name]._size;
auto afterCount = attributeViews[name].getNumElements();
if (beforeTotal != afterTotal || beforeCount != afterCount) {
qCDebug(bufferhelper_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)",
name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal);
}
#endif
auto bufferView = getBufferView(mesh, a.second);
if (bufferView.getNumElements()) {
attributeViews[a.first] = bufferView;
}
}
return attributeViews;
}
bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) {
qCInfo(bufferhelper_logging) << "Recalculating normals" << !!mesh;
if (!mesh) {
return false;
}
buffer_helpers::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<glm::vec3> faceNormals;
QMap<QString,QVector<quint32>> vertexToFaces;
faceNormals.resize(numFaces);
auto numNormals = normals.getNumElements();
qCInfo(bufferhelper_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)) {
#ifdef DEBUG_BUFFERVIEW_SCRIPTING
qCInfo(bufferhelper_logging) << i << i0 << i1 << i2 << glmVecToVariant(face.v0) << glmVecToVariant(face.v1) << glmVecToVariant(face.v2);
#endif
break;
}
vertexToFaces[glm::to_string(glm::dvec3(face.v0)).c_str()] << i;
vertexToFaces[glm::to_string(glm::dvec3(face.v1)).c_str()] << i;
vertexToFaces[glm::to_string(glm::dvec3(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(glm::dvec3(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(bufferhelper_logging) << "no faces for key!?" << key;
}
normal = verts.get<glm::vec3>(j);
}
if (glm::isnan(normal.x)) {
#ifdef DEBUG_BUFFERVIEW_SCRIPTING
static int logged = 0;
if (logged++ < 10) {
qCInfo(bufferhelper_logging) << "isnan(normal.x)" << j << glmVecToVariant(normal);
}
#endif
break;
}
buffer_helpers::fromVariant(normals, j, glmVecToVariant(glm::normalize(normal)));
}
return true;
}
QVariant buffer_helpers::toVariant(const glm::mat4& mat4) {
QVector<float> floats;
floats.resize(16);
memcpy(floats.data(), &mat4, sizeof(glm::mat4));
QVariant v;
v.setValue<QVector<float>>(floats);
return v;
};
QVariant buffer_helpers::toVariant(const Extents& box) {
return QVariantMap{
{ "center", glmVecToVariant(box.minimum + (box.size() / 2.0f)) },
{ "minimum", glmVecToVariant(box.minimum) },
{ "maximum", glmVecToVariant(box.maximum) },
{ "dimensions", glmVecToVariant(box.size()) },
};
}
QVariant buffer_helpers::toVariant(const AABox& box) {
return QVariantMap{
{ "brn", glmVecToVariant(box.getCorner()) },
{ "tfl", glmVecToVariant(box.calcTopFarLeft()) },
{ "center", glmVecToVariant(box.calcCenter()) },
{ "minimum", glmVecToVariant(box.getMinimumPoint()) },
{ "maximum", glmVecToVariant(box.getMaximumPoint()) },
{ "dimensions", glmVecToVariant(box.getDimensions()) },
};
}

View file

@ -10,10 +10,7 @@
#include <memory>
#include <glm/glm.hpp>
namespace gpu {
class BufferView;
class Element;
}
#include "GpuHelpers.h"
namespace graphics {
class Mesh;
@ -23,33 +20,41 @@ namespace graphics {
class Extents;
class AABox;
struct buffer_helpers {
template <typename T> static QVariant glmVecToVariant(const T& v, bool asArray = false);
template <typename T> static const T glmVecFromVariant(const QVariant& v);
namespace buffer_helpers {
extern QMap<QString,int> ATTRIBUTES;
extern const std::array<const char*, 4> XYZW;
extern const std::array<const char*, 4> ZERO123;
static graphics::MeshPointer cloneMesh(graphics::MeshPointer mesh);
static QMap<QString,int> ATTRIBUTES;
static std::map<QString, gpu::BufferView> gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList());
static bool recalculateNormals(graphics::MeshPointer meshProxy);
static gpu::BufferView getBufferView(graphics::MeshPointer mesh, quint8 slot);
template <typename T> QVariant glmVecToVariant(const T& v, bool asArray = false);
template <typename T> const T glmVecFromVariant(const QVariant& v);
static QVariant toVariant(const Extents& box);
static QVariant toVariant(const AABox& box);
static QVariant toVariant(const glm::mat4& mat4);
static QVariant toVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = "");
glm::uint32 forEachVariant(const gpu::BufferView& view, std::function<bool(glm::uint32 index, const QVariant& value)> func, const char* hint = "");
template <typename T> glm::uint32 forEach(const gpu::BufferView& view, std::function<bool(glm::uint32 index, const T& value)> func);
static bool fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v);
template <typename T> gpu::BufferView newFromVector(const QVector<T>& elements, const gpu::Element& elementType);
template <typename T> gpu::BufferView newFromVariantList(const QVariantList& list, const gpu::Element& elementType);
template <typename T> static gpu::BufferView fromVector(const QVector<T>& elements, const gpu::Element& elementType);
template <typename T> QVector<T> variantToVector(const QVariant& list);
template <typename T> QVector<T> bufferToVector(const gpu::BufferView& view, const char *hint = "");
template <typename T> static QVector<T> toVector(const gpu::BufferView& view, const char *hint = "");
template <typename T> static T convert(const gpu::BufferView& view, quint32 index, const char* hint = "");
// note: these do value conversions from the underlying buffer type into the template type
template <typename T> T getValue(const gpu::BufferView& view, glm::uint32 index, const char* hint = "");
template <typename T> bool setValue(const gpu::BufferView& view, glm::uint32 index, const T& value, const char* hint = "");
static gpu::BufferView clone(const gpu::BufferView& input);
static gpu::BufferView resize(const gpu::BufferView& input, quint32 numElements);
gpu::BufferView clone(const gpu::BufferView& input);
gpu::BufferView resized(const gpu::BufferView& input, glm::uint32 numElements);
static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent);
void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent);
static const std::array<const char*, 4> XYZW;
static const std::array<const char*, 4> ZERO123;
};
namespace mesh {
glm::uint32 forEachVertex(const graphics::MeshPointer& mesh, std::function<bool(glm::uint32 index, const QVariantMap& attributes)> func);
bool setVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 index, const QVariantMap& attributes);
QVariant getVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 index);
graphics::MeshPointer clone(const graphics::MeshPointer& mesh);
gpu::BufferView getBufferView(const graphics::MeshPointer& mesh, quint8 slot);
std::map<QString, gpu::BufferView> getAllBufferViews(const graphics::MeshPointer& mesh);
template <typename T> QVector<T> attributeToVector(const graphics::MeshPointer& mesh, gpu::Stream::InputSlot slot) {
return bufferToVector<T>(getBufferView(mesh, slot), qUtf8Printable(gpu::toString(slot)));
}
}
}

View file

@ -0,0 +1,122 @@
//
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
//
#include "GpuHelpers.h"
namespace graphics {
DebugEnums<Mesh::Topology> TOPOLOGIES{
{ Mesh::Topology::POINTS, "points" },
{ Mesh::Topology::LINES, "lines" },
{ Mesh::Topology::LINE_STRIP, "line_strip" },
{ Mesh::Topology::TRIANGLES, "triangles" },
{ Mesh::Topology::TRIANGLE_STRIP, "triangle_strip" },
{ Mesh::Topology::QUADS, "quads" },
{ Mesh::Topology::QUAD_STRIP, "quad_strip" },
{ Mesh::Topology::NUM_TOPOLOGIES, "num_topologies" },
};
}
namespace gpu {
DebugEnums<Type> TYPES{
{ Type::FLOAT, "float" },
{ Type::INT32, "int32" },
{ Type::UINT32, "uint32" },
{ Type::HALF, "half" },
{ Type::INT16, "int16" },
{ Type::UINT16, "uint16" },
{ Type::INT8, "int8" },
{ Type::UINT8, "uint8" },
{ Type::NINT32, "nint32" },
{ Type::NUINT32, "nuint32" },
{ Type::NINT16, "nint16" },
{ Type::NUINT16, "nuint16" },
{ Type::NINT8, "nint8" },
{ Type::NUINT8, "nuint8" },
{ Type::NUINT2, "nuint2" },
{ Type::NINT2_10_10_10, "nint2_10_10_10" },
{ Type::COMPRESSED, "compressed" },
{ Type::NUM_TYPES, "num_types" },
};
DebugEnums<Dimension> DIMENSIONS{
{ Dimension::SCALAR, "scalar" },
{ Dimension::VEC2, "vec2" },
{ Dimension::VEC3, "vec3" },
{ Dimension::VEC4, "vec4" },
{ Dimension::MAT2, "mat2" },
{ Dimension::MAT3, "mat3" },
{ Dimension::MAT4, "mat4" },
{ Dimension::TILE4x4, "tile4x4" },
{ Dimension::NUM_DIMENSIONS, "num_dimensions" },
};
DebugEnums<Semantic> SEMANTICS{
{ Semantic::RAW, "raw" },
{ Semantic::RED, "red" },
{ Semantic::RGB, "rgb" },
{ Semantic::RGBA, "rgba" },
{ Semantic::BGRA, "bgra" },
{ Semantic::XY, "xy" },
{ Semantic::XYZ, "xyz" },
{ Semantic::XYZW, "xyzw" },
{ Semantic::QUAT, "quat" },
{ Semantic::UV, "uv" },
{ Semantic::INDEX, "index" },
{ Semantic::PART, "part" },
{ Semantic::DEPTH, "depth" },
{ Semantic::STENCIL, "stencil" },
{ Semantic::DEPTH_STENCIL, "depth_stencil" },
{ Semantic::SRED, "sred" },
{ Semantic::SRGB, "srgb" },
{ Semantic::SRGBA, "srgba" },
{ Semantic::SBGRA, "sbgra" },
{ Semantic::_FIRST_COMPRESSED, "_first_compressed" },
{ Semantic::COMPRESSED_BC1_SRGB, "compressed_bc1_srgb" },
{ Semantic::COMPRESSED_BC1_SRGBA, "compressed_bc1_srgba" },
{ Semantic::COMPRESSED_BC3_SRGBA, "compressed_bc3_srgba" },
{ Semantic::COMPRESSED_BC4_RED, "compressed_bc4_red" },
{ Semantic::COMPRESSED_BC5_XY, "compressed_bc5_xy" },
{ Semantic::COMPRESSED_BC6_RGB, "compressed_bc6_rgb" },
{ Semantic::COMPRESSED_BC7_SRGBA, "compressed_bc7_srgba" },
{ Semantic::_LAST_COMPRESSED, "_last_compressed" },
{ Semantic::R11G11B10, "r11g11b10" },
{ Semantic::RGB9E5, "rgb9e5" },
{ Semantic::UNIFORM, "uniform" },
{ Semantic::UNIFORM_BUFFER, "uniform_buffer" },
{ Semantic::RESOURCE_BUFFER, "resource_buffer" },
{ Semantic::SAMPLER, "sampler" },
{ Semantic::SAMPLER_MULTISAMPLE, "sampler_multisample" },
{ Semantic::SAMPLER_SHADOW, "sampler_shadow" },
{ Semantic::NUM_SEMANTICS, "num_semantics" },
};
DebugEnums<Stream::InputSlot> SLOTS{
{ Stream::InputSlot::POSITION, "position" },
{ Stream::InputSlot::NORMAL, "normal" },
{ Stream::InputSlot::COLOR, "color" },
{ Stream::InputSlot::TEXCOORD0, "texcoord0" },
{ Stream::InputSlot::TEXCOORD, "texcoord" },
{ Stream::InputSlot::TANGENT, "tangent" },
{ Stream::InputSlot::SKIN_CLUSTER_INDEX, "skin_cluster_index" },
{ Stream::InputSlot::SKIN_CLUSTER_WEIGHT, "skin_cluster_weight" },
{ Stream::InputSlot::TEXCOORD1, "texcoord1" },
{ Stream::InputSlot::TEXCOORD2, "texcoord2" },
{ Stream::InputSlot::TEXCOORD3, "texcoord3" },
{ Stream::InputSlot::TEXCOORD4, "texcoord4" },
{ Stream::InputSlot::NUM_INPUT_SLOTS, "num_input_slots" },
{ Stream::InputSlot::DRAW_CALL_INFO, "draw_call_info" },
};
}

View file

@ -0,0 +1,47 @@
//
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#include <QtCore>
#include <gpu/Format.h>
#include <gpu/Stream.h>
#include "Geometry.h"
template <typename T>
using DebugEnums = QMap<T, QString>;
namespace graphics {
extern DebugEnums<Mesh::Topology> TOPOLOGIES;
inline QDebug operator<<(QDebug dbg, Mesh::Topology type) { return dbg << TOPOLOGIES.value(type);}
inline const QString toString(Mesh::Topology v) { return TOPOLOGIES.value(v); }
}
namespace gpu {
extern DebugEnums<Type> TYPES;
extern DebugEnums<Dimension> DIMENSIONS;
extern DebugEnums<Semantic> SEMANTICS;
extern DebugEnums<Stream::InputSlot> SLOTS;
inline QDebug operator<<(QDebug dbg, gpu::Type type) { return dbg << TYPES.value(type); }
inline QDebug operator<<(QDebug dbg, gpu::Dimension type) { return dbg << DIMENSIONS.value(type); }
inline QDebug operator<<(QDebug dbg, gpu::Semantic type) { return dbg << SEMANTICS.value(type); }
inline QDebug operator<<(QDebug dbg, gpu::Stream::InputSlot type) { return dbg << SLOTS.value(type); }
inline const QString toString(gpu::Type v) { return TYPES.value(v); }
inline const QString toString(gpu::Dimension v) { return DIMENSIONS.value(v); }
inline const QString toString(gpu::Semantic v) { return SEMANTICS.value(v); }
inline const QString toString(gpu::Stream::InputSlot v) { return SLOTS.value(v); }
inline const QString toString(gpu::Element v) {
return QString("[Element semantic=%1 type=%1 dimension=%2]")
.arg(toString(v.getSemantic()))
.arg(toString(v.getType()))
.arg(toString(v.getDimension()));
}
}
Q_DECLARE_METATYPE(gpu::Type)
Q_DECLARE_METATYPE(gpu::Dimension)
Q_DECLARE_METATYPE(gpu::Semantic)
Q_DECLARE_METATYPE(graphics::Mesh::Topology)

View file

@ -440,7 +440,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
const FBXGeometry& geometry = getFBXGeometry();
if (!_triangleSetsValid) {
calculateTriangleSets();
calculateTriangleSets(geometry);
}
glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset);
@ -525,7 +525,7 @@ bool Model::convexHullContains(glm::vec3 point) {
QMutexLocker locker(&_mutex);
if (!_triangleSetsValid) {
calculateTriangleSets();
calculateTriangleSets(getFBXGeometry());
}
// If we are inside the models box, then consider the submeshes...
@ -587,9 +587,6 @@ MeshProxyList Model::getMeshes() const {
return result;
}
// FIXME: temporary workaround that updates the whole FBXGeometry (to keep findRayIntersection in sync)
#include "Model_temporary_hack.cpp.h"
bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) {
QMutexLocker lock(&_mutex);
@ -603,18 +600,60 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe
return false;
}
auto resource = new MyGeometryResource(_url, _renderGeometry, newModel);
_needsReload = false;
_needsUpdateTextures = false;
_visualGeometryRequestFailed = false;
_needsFixupInScene = true;
const auto& meshes = newModel->meshes;
render::Transaction transaction;
const render::ScenePointer& scene = AbstractViewStateInterface::instance()->getMain3DScene();
invalidCalculatedMeshBoxes();
deleteGeometry();
_renderGeometry.reset(resource);
updateGeometry();
calculateTriangleSets();
setRenderItemsNeedUpdate();
meshIndex = meshIndex >= 0 ? meshIndex : 0;
partIndex = partIndex >= 0 ? partIndex : 0;
if (meshIndex >= meshes.size()) {
qDebug() << meshIndex << "meshIndex >= newModel.meshes.size()" << meshes.size();
return false;
}
auto mesh = meshes[meshIndex].getMeshPointer();
{
// update visual geometry
render::Transaction transaction;
for (int i = 0; i < (int) _modelMeshRenderItemIDs.size(); i++) {
auto itemID = _modelMeshRenderItemIDs[i];
auto shape = _modelMeshRenderItemShapes[i];
// TODO: check to see if .partIndex matches too
if (shape.meshIndex == meshIndex) {
transaction.updateItem<ModelMeshPartPayload>(itemID, [=](ModelMeshPartPayload& data) {
data.updateMeshPart(mesh, partIndex);
});
}
}
scene->enqueueTransaction(transaction);
}
// update triangles for ray picking
{
FBXGeometry geometry;
for (const auto& newMesh : meshes) {
FBXMesh mesh;
mesh._mesh = newMesh.getMeshPointer();
mesh.vertices = buffer_helpers::mesh::attributeToVector<glm::vec3>(mesh._mesh, gpu::Stream::POSITION);
int numParts = newMesh.getMeshPointer()->getNumParts();
for (int partID = 0; partID < numParts; partID++) {
FBXMeshPart part;
part.triangleIndices = buffer_helpers::bufferToVector<int>(mesh._mesh->getIndexBuffer(), "part.triangleIndices");
mesh.parts << part;
}
{
foreach (const glm::vec3& vertex, mesh.vertices) {
glm::vec3 transformedVertex = glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f));
geometry.meshExtents.minimum = glm::min(geometry.meshExtents.minimum, transformedVertex);
geometry.meshExtents.maximum = glm::max(geometry.meshExtents.maximum, transformedVertex);
mesh.meshExtents.minimum = glm::min(mesh.meshExtents.minimum, transformedVertex);
mesh.meshExtents.maximum = glm::max(mesh.meshExtents.maximum, transformedVertex);
}
}
geometry.meshes << mesh;
}
calculateTriangleSets(geometry);
}
return true;
}
@ -638,10 +677,9 @@ scriptable::ScriptableModelBase Model::getScriptableModel() {
return result;
}
void Model::calculateTriangleSets() {
void Model::calculateTriangleSets(const FBXGeometry& geometry) {
PROFILE_RANGE(render, __FUNCTION__);
const FBXGeometry& geometry = getFBXGeometry();
int numberOfMeshes = geometry.meshes.size();
_triangleSetsValid = true;
@ -664,7 +702,7 @@ void Model::calculateTriangleSets() {
int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris;
_modelSpaceMeshTriangleSets[i].reserve(totalTriangles);
auto meshTransform = getFBXGeometry().offset * mesh.modelTransform;
auto meshTransform = geometry.offset * mesh.modelTransform;
if (part.quadIndices.size() > 0) {
int vIndex = 0;

View file

@ -419,7 +419,7 @@ protected:
bool _overrideModelTransform { false };
bool _triangleSetsValid { false };
void calculateTriangleSets();
void calculateTriangleSets(const FBXGeometry& geometry);
QVector<TriangleSet> _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes

View file

@ -1,84 +0,0 @@
// FIXME: temporary workaround for duplicating the FBXModel when dynamically replacing an underlying mesh part
#include <graphics/BufferViewHelpers.h>
#include <graphics-scripting/GraphicsScriptingUtil.h>
class MyGeometryResource : public GeometryResource {
public:
shared_ptr<FBXGeometry> fbxGeometry;
MyGeometryResource(const QUrl& url, Geometry::Pointer originalGeometry, scriptable::ScriptableModelBasePointer newModel) : GeometryResource(url) {
fbxGeometry = std::make_shared<FBXGeometry>();
FBXGeometry& geometry = *fbxGeometry.get();
const FBXGeometry* original;
shared_ptr<FBXGeometry> tmpGeometry;
if (originalGeometry) {
original = &originalGeometry->getFBXGeometry();
} else {
tmpGeometry = std::make_shared<FBXGeometry>();
original = tmpGeometry.get();
}
geometry.originalURL = original->originalURL;
geometry.bindExtents = original->bindExtents;
for (const auto &j : original->joints) {
geometry.joints << j;
}
for (const FBXMaterial& material : original->materials) {
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
}
std::shared_ptr<GeometryMeshes> meshes = std::make_shared<GeometryMeshes>();
std::shared_ptr<GeometryMeshParts> parts = std::make_shared<GeometryMeshParts>();
int meshID = 0;
if (newModel) {
geometry.meshExtents.reset();
for (const auto& newMesh : newModel->meshes) {
// qDebug() << "newMesh #" << meshID;
FBXMesh mesh;
if (meshID < original->meshes.size()) {
mesh = original->meshes.at(meshID); // copy
}
mesh._mesh = newMesh.getMeshPointer();
// duplicate the buffers
mesh.vertices = buffer_helpers::toVector<glm::vec3>(mesh._mesh->getVertexBuffer(), "mesh.vertices");
mesh.normals = buffer_helpers::toVector<glm::vec3>(buffer_helpers::getBufferView(mesh._mesh, gpu::Stream::NORMAL), "mesh.normals");
mesh.colors = buffer_helpers::toVector<glm::vec3>(buffer_helpers::getBufferView(mesh._mesh, gpu::Stream::COLOR), "mesh.colors");
mesh.texCoords = buffer_helpers::toVector<glm::vec2>(buffer_helpers::getBufferView(mesh._mesh, gpu::Stream::TEXCOORD0), "mesh.texCoords");
mesh.texCoords1 = buffer_helpers::toVector<glm::vec2>(buffer_helpers::getBufferView(mesh._mesh, gpu::Stream::TEXCOORD1), "mesh.texCoords1");
mesh.createMeshTangents(true);
mesh.createBlendShapeTangents(false);
geometry.meshes << mesh;
// Copy mesh pointers
meshes->emplace_back(newMesh.getMeshPointer());
int partID = 0;
const auto oldParts = mesh.parts;
mesh.parts.clear();
for (const FBXMeshPart& fbxPart : oldParts) {
FBXMeshPart part; // new copy
part.materialID = fbxPart.materialID;
// Construct local parts
part.triangleIndices = buffer_helpers::toVector<int>(mesh._mesh->getIndexBuffer(), "part.triangleIndices");
mesh.parts << part;
auto p = std::make_shared<MeshPart>(meshID, partID, 0);
parts->push_back(p);
partID++;
}
{
// accumulate local transforms
// compute the mesh extents from the transformed vertices
foreach (const glm::vec3& vertex, mesh.vertices) {
glm::vec3 transformedVertex = glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f));
geometry.meshExtents.minimum = glm::min(geometry.meshExtents.minimum, transformedVertex);
geometry.meshExtents.maximum = glm::max(geometry.meshExtents.maximum, transformedVertex);
mesh.meshExtents.minimum = glm::min(mesh.meshExtents.minimum, transformedVertex);
mesh.meshExtents.maximum = glm::max(mesh.meshExtents.maximum, transformedVertex);
}
}
meshID++;
}
}
_meshes = meshes;
_meshParts = parts;
_loaded = true;
_fbxGeometry = fbxGeometry;
};
};