mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-13 23:46:29 +02:00
* remove Model_temporary_hack
* split gpuhelpers and mesh part * fix objwriter * more work on bufferview helpers * cr cleanup
This commit is contained in:
parent
7c571cd431
commit
f824edd04e
33 changed files with 1938 additions and 1317 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ public:
|
|||
|
||||
virtual Sphere3DOverlay* createClone() const override;
|
||||
|
||||
virtual scriptable::ScriptableModelBase getScriptableModel() override;
|
||||
protected:
|
||||
Transform evalRenderTransform() override;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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>)
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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>)
|
|
@ -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"
|
||||
|
|
|
@ -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(); }
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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()) },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
122
libraries/graphics/src/graphics/GpuHelpers.cpp
Normal file
122
libraries/graphics/src/graphics/GpuHelpers.cpp
Normal 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" },
|
||||
};
|
||||
}
|
47
libraries/graphics/src/graphics/GpuHelpers.h
Normal file
47
libraries/graphics/src/graphics/GpuHelpers.h
Normal 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)
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in a new issue