BufferView <-> QVariant/QScriptValue conversion

update MeshProxy/SimpleMeshProxy and ScriptableModel

ModelScriptingInterface / scriptable::ModelProvider

integration

update to RC-63

initial graphics-scripting refactoring

graphics-scripting baseline commit

wip commit

Geometry -> MeshPart

remove SimpleMeshProxy

collapse graphics-utils -> graphics-scripting

scriptable::Model => scriptable::ScriptableModel
This commit is contained in:
humbletim 2017-12-21 15:13:57 -05:00
parent bc819c698e
commit 06afaa7470
51 changed files with 2242 additions and 600 deletions

View file

@ -191,7 +191,7 @@ endif()
# link required hifi libraries
link_hifi_libraries(
shared octree ktx gpu gl procedural graphics render
shared octree ktx gpu gl procedural graphics graphics-scripting render
pointers
recording fbx networking model-networking entities avatars trackers
audio audio-client animation script-engine physics

View file

@ -166,6 +166,7 @@
#include "scripting/AccountServicesScriptingInterface.h"
#include "scripting/HMDScriptingInterface.h"
#include "scripting/MenuScriptingInterface.h"
#include "graphics-scripting/ModelScriptingInterface.h"
#include "scripting/SettingsScriptingInterface.h"
#include "scripting/WindowScriptingInterface.h"
#include "scripting/ControllerScriptingInterface.h"
@ -198,7 +199,6 @@
#include <src/scripting/LimitlessVoiceRecognitionScriptingInterface.h>
#include <src/scripting/GooglePolyScriptingInterface.h>
#include <EntityScriptClient.h>
#include <ModelScriptingInterface.h>
#include <PickManager.h>
#include <PointerManager.h>
@ -593,6 +593,66 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt
}
}
class ApplicationMeshProvider : public scriptable::ModelProviderFactory {
public:
virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) {
QString error;
scriptable::ModelProviderPointer provider;
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;
}
return provider;
}
scriptable::ModelProviderPointer getEntityModelProvider(EntityItemID entityID) {
scriptable::ModelProviderPointer provider;
auto entityTreeRenderer = qApp->getEntities();
auto entityTree = entityTreeRenderer->getTree();
if (auto entity = entityTree->findEntityByID(entityID)) {
if (auto renderer = entityTreeRenderer->renderableForEntityId(entityID)) {
provider = std::dynamic_pointer_cast<scriptable::ModelProvider>(renderer);
provider->metadata["providerType"] = "entity";
} else {
qCWarning(interfaceapp) << "no renderer for entity ID" << entityID.toString();
}
}
return provider;
}
scriptable::ModelProviderPointer getOverlayModelProvider(OverlayID overlayID) {
scriptable::ModelProviderPointer provider;
auto &overlays = qApp->getOverlays();
if (auto overlay = overlays.getOverlay(overlayID)) {
if (auto base3d = std::dynamic_pointer_cast<Base3DOverlay>(overlay)) {
provider = std::dynamic_pointer_cast<scriptable::ModelProvider>(base3d);
provider->metadata["providerType"] = "overlay";
} else {
qCWarning(interfaceapp) << "no renderer for overlay ID" << overlayID.toString();
}
}
return provider;
}
scriptable::ModelProviderPointer getAvatarModelProvider(QUuid sessionUUID) {
scriptable::ModelProviderPointer provider;
auto avatarManager = DependencyManager::get<AvatarManager>();
if (auto avatar = avatarManager->getAvatarBySessionID(sessionUUID)) {
if (avatar->getSessionUUID() == sessionUUID) {
provider = std::dynamic_pointer_cast<scriptable::ModelProvider>(avatar);
provider->metadata["providerType"] = "avatar";
}
}
return provider;
}
};
static const QString STATE_IN_HMD = "InHMD";
static const QString STATE_CAMERA_FULL_SCREEN_MIRROR = "CameraFSM";
static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson";
@ -737,6 +797,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<DesktopScriptingInterface>();
DependencyManager::set<EntityScriptingInterface>(true);
DependencyManager::set<ModelScriptingInterface>();
DependencyManager::registerInheritance<scriptable::ModelProviderFactory, ApplicationMeshProvider>();
DependencyManager::set<ApplicationMeshProvider>();
DependencyManager::set<RecordingScriptingInterface>();
DependencyManager::set<WindowScriptingInterface>();
DependencyManager::set<HMDScriptingInterface>();
@ -5915,6 +5978,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get());
ModelScriptingInterface::registerMetaTypes(scriptEngine.data());
scriptEngine->registerGlobalObject("Model", DependencyManager::get<ModelScriptingInterface>().data());
scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface());

View file

@ -72,7 +72,6 @@
#include <procedural/ProceduralSkybox.h>
#include <graphics/Skybox.h>
#include <ModelScriptingInterface.h>
#include "FrameTimingsScriptingInterface.h"
#include "Sound.h"

View file

@ -13,10 +13,11 @@
#include <Transform.h>
#include <SpatiallyNestable.h>
#include <graphics-scripting/ScriptableModel.h>
#include "Overlay.h"
class Base3DOverlay : public Overlay, public SpatiallyNestable {
namespace model { class Mesh; }
class Base3DOverlay : public Overlay, public SpatiallyNestable, public scriptable::ModelProvider {
Q_OBJECT
using Parent = Overlay;
@ -36,6 +37,7 @@ public:
virtual bool is3D() const override { return true; }
virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); }
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); }
// TODO: consider implementing registration points in this class
glm::vec3 getCenter() const { return getWorldPosition(); }

View file

@ -629,3 +629,10 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const {
}
return 0;
}
scriptable::ScriptableModel ModelOverlay::getScriptableModel(bool* ok) {
if (!_model || !_model->isLoaded()) {
return Base3DOverlay::getScriptableModel(ok);
}
return _model->getScriptableModel(ok);
}

View file

@ -59,6 +59,7 @@ public:
void setDrawInFront(bool drawInFront) override;
void setDrawHUDLayer(bool drawHUDLayer) override;
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
protected:
Transform evalRenderTransform() override;

View file

@ -179,3 +179,18 @@ Transform Shape3DOverlay::evalRenderTransform() {
transform.setRotation(rotation);
return transform;
}
scriptable::ScriptableModel Shape3DOverlay::getScriptableModel(bool* ok) {
auto geometryCache = DependencyManager::get<GeometryCache>();
auto vertexColor = ColorUtils::toVec3(_color);
scriptable::ScriptableModel result;
result.metadata = {
{ "origin", "Shape3DOverlay::"+shapeStrings[_shape] },
{ "overlayID", getID() },
};
result.meshes << geometryCache->meshFromShape(_shape, vertexColor);
if (ok) {
*ok = true;
}
return result;
}

View file

@ -37,6 +37,7 @@ public:
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
protected:
Transform evalRenderTransform() override;

View file

@ -13,5 +13,6 @@ include_hifi_library_headers(entities-renderer)
include_hifi_library_headers(audio)
include_hifi_library_headers(entities)
include_hifi_library_headers(octree)
include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h
target_bullet()

View file

@ -35,6 +35,8 @@
#include "ModelEntityItem.h"
#include "RenderableModelEntityItem.h"
#include <graphics-scripting/ScriptableModel.h>
#include "Logging.h"
using namespace std;
@ -1760,3 +1762,29 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const {
return DEFAULT_AVATAR_EYE_HEIGHT;
}
}
scriptable::ScriptableModel Avatar::getScriptableModel(bool* ok) {
qDebug() << "Avatar::getScriptableModel" ;
if (!_skeletonModel || !_skeletonModel->isLoaded()) {
return scriptable::ModelProvider::modelUnavailableError(ok);
}
scriptable::ScriptableModel result;
result.metadata = {
{ "avatarID", getSessionUUID().toString() },
{ "url", _skeletonModelURL.toString() },
{ "origin", "Avatar/avatar::" + _displayName },
{ "textures", _skeletonModel->getTextures() },
};
result.mixin(_skeletonModel->getScriptableModel(ok));
// FIXME: for now access to attachment models are merged with the main avatar model
for (auto& attachmentModel : _attachmentModels) {
if (attachmentModel->isLoaded()) {
result.mixin(attachmentModel->getScriptableModel(ok));
}
}
if (ok) {
*ok = true;
}
return result;
}

View file

@ -20,6 +20,7 @@
#include <AvatarData.h>
#include <ShapeInfo.h>
#include <render/Scene.h>
#include <graphics-scripting/ScriptableModel.h>
#include <GLMHelpers.h>
@ -53,7 +54,7 @@ class Texture;
using AvatarPhysicsCallback = std::function<void(uint32_t)>;
class Avatar : public AvatarData {
class Avatar : public AvatarData, public scriptable::ModelProvider {
Q_OBJECT
/**jsdoc
@ -272,6 +273,8 @@ public:
virtual void setAvatarEntityDataChanged(bool value) override;
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
public slots:
// FIXME - these should be migrated to use Pose data instead

View file

@ -13,6 +13,7 @@ include_hifi_library_headers(fbx)
include_hifi_library_headers(entities)
include_hifi_library_headers(avatars)
include_hifi_library_headers(controllers)
include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h
target_bullet()
target_polyvox()

View file

@ -17,13 +17,14 @@
#include <Sound.h>
#include "AbstractViewStateInterface.h"
#include "EntitiesRendererLogging.h"
#include <graphics-scripting/ScriptableModel.h>
class EntityTreeRenderer;
namespace render { namespace entities {
// Base class for all renderable entities
class EntityRenderer : public QObject, public std::enable_shared_from_this<EntityRenderer>, public PayloadProxyInterface, protected ReadWriteLockable {
class EntityRenderer : public QObject, public std::enable_shared_from_this<EntityRenderer>, public PayloadProxyInterface, protected ReadWriteLockable, public scriptable::ModelProvider {
Q_OBJECT
using Pointer = std::shared_ptr<EntityRenderer>;
@ -37,7 +38,7 @@ public:
virtual bool wantsKeyboardFocus() const { return false; }
virtual void setProxyWindow(QWindow* proxyWindow) {}
virtual QObject* getEventHandler() { return nullptr; }
const EntityItemPointer& getEntity() { return _entity; }
const EntityItemPointer& getEntity() const { return _entity; }
const ItemID& getRenderItemID() const { return _renderItemID; }
const SharedSoundPointer& getCollisionSound() { return _collisionSound; }
@ -54,6 +55,7 @@ public:
const uint64_t& getUpdateTime() const { return _updateTime; }
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); }
protected:
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
virtual void onAddToScene(const EntityItemPointer& entity);

View file

@ -950,13 +950,18 @@ QStringList RenderableModelEntityItem::getJointNames() const {
return result;
}
bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) {
auto model = getModel();
scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) {
ModelPointer model;
withReadLock([&] {
model = _model;
});
if (!model || !model->isLoaded()) {
return false;
return scriptable::ModelProvider::modelUnavailableError(ok);
}
BLOCKING_INVOKE_METHOD(model.get(), "getMeshes", Q_RETURN_ARG(MeshProxyList, result));
return !result.isEmpty();
return _model->getScriptableModel(ok);
}
void RenderableModelEntityItem::simulateRelayedJoints() {

View file

@ -111,7 +111,6 @@ public:
virtual int getJointIndex(const QString& name) const override;
virtual QStringList getJointNames() const override;
bool getMeshes(MeshProxyList& result) override;
const void* getCollisionMeshKey() const { return _collisionMeshKey; }
signals:
@ -141,6 +140,7 @@ class ModelEntityRenderer : public TypedEntityRenderer<RenderableModelEntityItem
public:
ModelEntityRenderer(const EntityItemPointer& entity);
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
protected:
virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override;

View file

@ -281,6 +281,10 @@ std::vector<PolyLineEntityRenderer::Vertex> PolyLineEntityRenderer::updateVertic
return vertices;
}
scriptable::ScriptableModel PolyLineEntityRenderer::getScriptableModel(bool *ok) {
// TODO: adapt polyline into a triangles mesh...
return EntityRenderer::getScriptableModel(ok);
}
void PolyLineEntityRenderer::doRender(RenderArgs* args) {
if (_empty) {
@ -319,4 +323,4 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) {
#endif
batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0);
}
}

View file

@ -25,6 +25,7 @@ class PolyLineEntityRenderer : public TypedEntityRenderer<PolyLineEntityItem> {
public:
PolyLineEntityRenderer(const EntityItemPointer& entity);
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
protected:
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene,

View file

@ -20,8 +20,6 @@
#include <QByteArray>
#include <QtConcurrent/QtConcurrentRun>
#include <model-networking/SimpleMeshProxy.h>
#include <ModelScriptingInterface.h>
#include <EntityEditPacketSender.h>
#include <PhysicalEntitySimulation.h>
#include <StencilMaskPass.h>
@ -1416,36 +1414,36 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() {
}
}
bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) {
if (!updateDependents()) {
return false;
scriptable::ScriptableModel RenderablePolyVoxEntityItem::getScriptableModel(bool * ok) {
if (!updateDependents() || !_mesh) {
return scriptable::ModelProvider::modelUnavailableError(ok);
}
bool success = false;
if (_mesh) {
MeshProxy* meshProxy = nullptr;
glm::mat4 transform = voxelToLocalMatrix();
withReadLock([&] {
gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices();
if (!_meshReady) {
// we aren't ready to return a mesh. the caller will have to try again later.
success = false;
} else if (numVertices == 0) {
// we are ready, but there are no triangles in the mesh.
success = true;
} else {
success = true;
// the mesh will be in voxel-space. transform it into object-space
meshProxy = new SimpleMeshProxy(
_mesh->map([=](glm::vec3 position) { return glm::vec3(transform * glm::vec4(position, 1.0f)); },
[=](glm::vec3 color) { return color; },
[=](glm::vec3 normal) { return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); },
[&](uint32_t index) { return index; }));
result << meshProxy;
}
});
glm::mat4 transform = voxelToLocalMatrix();
scriptable::ScriptableModel result;
withReadLock([&] {
gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices();
if (!_meshReady) {
// we aren't ready to return a mesh. the caller will have to try again later.
success = false;
} else if (numVertices == 0) {
// we are ready, but there are no triangles in the mesh.
success = true;
} else {
success = true;
// the mesh will be in voxel-space. transform it into object-space
result.meshes <<
_mesh->map([=](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); },
[=](glm::vec3 color){ return color; },
[=](glm::vec3 normal){ return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); },
[&](uint32_t index){ return index; });
}
});
if (ok) {
*ok = success;
}
return success;
return result;
}
using namespace render;

View file

@ -32,7 +32,7 @@ namespace render { namespace entities {
class PolyVoxEntityRenderer;
} }
class RenderablePolyVoxEntityItem : public PolyVoxEntityItem {
class RenderablePolyVoxEntityItem : public PolyVoxEntityItem, public scriptable::ModelProvider {
friend class render::entities::PolyVoxEntityRenderer;
public:
@ -113,7 +113,7 @@ public:
void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); }
bool getMeshes(MeshProxyList& result) override;
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
private:
bool updateOnCount(const ivec3& v, uint8_t toValue);
@ -163,6 +163,9 @@ class PolyVoxEntityRenderer : public TypedEntityRenderer<RenderablePolyVoxEntity
public:
PolyVoxEntityRenderer(const EntityItemPointer& entity);
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override {
return asTypedEntity<RenderablePolyVoxEntityItem>()->getScriptableModel(ok);
}
protected:
virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape(); }

View file

@ -156,3 +156,24 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
const auto triCount = geometryCache->getShapeTriangleCount(geometryShape);
args->_details._trianglesRendered += (int)triCount;
}
scriptable::ScriptableModel ShapeEntityRenderer::getScriptableModel(bool* ok) {
scriptable::ScriptableModel result;
result.metadata = {
{ "entityID", getEntity()->getID().toString() },
{ "shape", entity::stringFromShape(_shape) },
{ "userData", getEntity()->getUserData() },
};
auto geometryCache = DependencyManager::get<GeometryCache>();
auto geometryShape = geometryCache->getShapeForEntityShape(_shape);
auto vertexColor = glm::vec3(_color);
auto success = false;
if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) {
result.meshes << mesh;
success = true;
}
if (ok) {
*ok = success;
}
return result;
}

View file

@ -22,6 +22,8 @@ class ShapeEntityRenderer : public TypedEntityRenderer<ShapeEntityItem> {
public:
ShapeEntityRenderer(const EntityItemPointer& entity);
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
private:
virtual bool needsRenderUpdate() const override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;

View file

@ -57,8 +57,6 @@ using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr<EntityTreeElemen
#define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10))
#define debugTreeVector(V) V << "[" << V << " in meters ]"
class MeshProxyList;
/// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
/// one directly, instead you must only construct one of it's derived classes with additional features.
@ -456,8 +454,6 @@ public:
bool matchesJSONFilters(const QJsonObject& jsonFilters) const;
virtual bool getMeshes(MeshProxyList& result) { return true; }
virtual void locationChanged(bool tellPhysics = true) override;
virtual bool getScalesWithParent() const override;

View file

@ -1805,30 +1805,6 @@ bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, cons
return aaBox.findCapsulePenetration(start, end, radius, penetration);
}
void EntityScriptingInterface::getMeshes(QUuid entityID, QScriptValue callback) {
PROFILE_RANGE(script_entities, __FUNCTION__);
EntityItemPointer entity = static_cast<EntityItemPointer>(_entityTree->findEntityByEntityItemID(entityID));
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::getMeshes no entity with ID" << entityID;
QScriptValueList args { callback.engine()->undefinedValue(), false };
callback.call(QScriptValue(), args);
return;
}
MeshProxyList result;
bool success = entity->getMeshes(result);
if (success) {
QScriptValue resultAsScriptValue = meshesToScriptValue(callback.engine(), result);
QScriptValueList args { resultAsScriptValue, true };
callback.call(QScriptValue(), args);
} else {
QScriptValueList args { callback.engine()->undefinedValue(), false };
callback.call(QScriptValue(), args);
}
}
glm::mat4 EntityScriptingInterface::getEntityTransform(const QUuid& entityID) {
glm::mat4 result;
if (_entityTree) {

View file

@ -37,7 +37,6 @@
#include "BaseScriptEngine.h"
class EntityTree;
class MeshProxy;
// helper factory to compose standardized, async metadata queries for "magic" Entity properties
// like .script and .serverScripts. This is used for automated testing of core scripting features
@ -401,9 +400,6 @@ public slots:
Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions,
const glm::vec3& start, const glm::vec3& end, float radius);
// FIXME move to a renderable entity interface
Q_INVOKABLE void getMeshes(QUuid entityID, QScriptValue callback);
/**jsdoc
* Returns object to world transform, excluding scale
*

View file

@ -1967,19 +1967,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
}
}
}
{
int i = 0;
for (const auto& mesh : geometry.meshes) {
auto name = geometry.getModelNameOfMesh(i++);
if (!name.isEmpty()) {
if (mesh._mesh) {
mesh._mesh->displayName += "#" + name;
} else {
qDebug() << "modelName but no mesh._mesh" << name;
}
}
}
}
return geometryPtr;
}
@ -1995,7 +1983,7 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri
reader._loadLightmaps = loadLightmaps;
reader._lightmapLevel = lightmapLevel;
qCDebug(modelformat) << "Reading FBX: " << url;
qDebug() << "Reading FBX: " << url;
return reader.extractFBXGeometry(mapping, url);
}

View file

@ -46,9 +46,12 @@ 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();
@ -81,7 +84,9 @@ bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes) {
// 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();
@ -98,7 +103,9 @@ bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes) {
// 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,35 +113,25 @@ 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(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";
out << QString("g %1-%2-%3\n").arg(subMeshIndex, 3, 10, QChar('0')).arg(name).arg(partIndex);
// 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;
const bool shorts = indexBuffer._element == gpu::Element::INDEX_UINT16;
auto face = [&](uint32_t i0, uint32_t i1, uint32_t i2) {
uint32_t index0, index1, index2;
if (shorts) {
index0 = indexBuffer.get<uint16_t>(i0);
index1 = indexBuffer.get<uint16_t>(i1);
index2 = indexBuffer.get<uint16_t>(i2);
} else {
index0 = indexBuffer.get<uint32_t>(i0);
index1 = indexBuffer.get<uint32_t>(i1);
index2 = indexBuffer.get<uint32_t>(i2);
}
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 << "f ";
if (haveNormals) {
@ -146,6 +143,39 @@ bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes) {
out << currentVertexStartOffset + index1 + 1 << " ";
out << currentVertexStartOffset + index2 + 1 << "\n";
}
};
// graphics::Mesh::TRIANGLES / graphics::Mesh::QUADS
// TODO -- handle other formats
uint32_t len = part._startIndex + part._numIndices;
auto stringFromTopology = [&](graphics::Mesh::Topology topo) -> QString {
return topo == graphics::Mesh::Topology::QUADS ? "QUADS" :
topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" :
topo == graphics::Mesh::Topology::TRIANGLES ? "TRIANGLES" :
topo == graphics::Mesh::Topology::TRIANGLE_STRIP ? "TRIANGLE_STRIP" :
topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" :
QString("topo:%1").arg((int)topo);
};
qCDebug(modelformat) << "OBJWriter -- part" << partIndex << "topo" << stringFromTopology(part._topology) << "index elements" << (shorts ? "uint16_t" : "uint32_t");
if (part._topology == graphics::Mesh::TRIANGLES && len % 3 != 0) {
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 3" << len;
}
if (part._topology == graphics::Mesh::QUADS && len % 4 != 0) {
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 4" << len;
}
if (len > indexBuffer.getNumElements()) {
qCDebug(modelformat) << "OBJWriter -- len > index size" << len << indexBuffer.getNumElements();
}
if (part._topology == graphics::Mesh::QUADS) {
for (uint32_t idx = part._startIndex; idx+3 < len; idx += 4) {
face(idx+0, idx+1, idx+3);
face(idx+1, idx+2, idx+3);
}
} else if (part._topology == graphics::Mesh::TRIANGLES) {
for (uint32_t idx = part._startIndex; idx+2 < len; idx += 3) {
face(idx+0, idx+1, idx+2);
}
}
out << "\n";
}

View file

@ -0,0 +1,5 @@
set(TARGET_NAME graphics-scripting)
setup_hifi_library()
link_hifi_libraries(shared networking graphics fbx model-networking script-engine)
include_hifi_library_headers(gpu)
include_hifi_library_headers(graphics-scripting)

View file

@ -0,0 +1,195 @@
#include "./graphics-scripting/BufferViewHelpers.h"
#include <QDebug>
#include <QVariant>
#include <gpu/Buffer.h>
#include <gpu/Format.h>
#include <gpu/Stream.h>
#include <glm/gtc/packing.hpp>
#ifdef DEBUG_BUFFERVIEW_SCRIPTING
#include "DebugNames.h"
#endif
namespace {
const std::array<const char*, 4> XYZW = {{ "x", "y", "z", "w" }};
const std::array<const char*, 4> ZERO123 = {{ "0", "1", "2", "3" }};
}
template <typename T>
QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) {
return glmVecToVariant(view.get<T>(index), asArray);
}
template <typename T>
void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QVariant& v) {
view.edit<T>(index) = glmVecFromVariant<T>(v);
}
//FIXME copied from Model.cpp
static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) {
auto absNormal = glm::abs(normal);
auto absTangent = glm::abs(tangent);
normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z));
tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z));
normal = glm::clamp(normal, -1.0f, 1.0f);
tangent = glm::clamp(tangent, -1.0f, 1.0f);
normal *= 511.0f;
tangent *= 511.0f;
normal = glm::round(normal);
tangent = glm::round(tangent);
glm::detail::i10i10i10i2 normalStruct;
glm::detail::i10i10i10i2 tangentStruct;
normalStruct.data.x = int(normal.x);
normalStruct.data.y = int(normal.y);
normalStruct.data.z = int(normal.z);
normalStruct.data.w = 0;
tangentStruct.data.x = int(tangent.x);
tangentStruct.data.y = int(tangent.y);
tangentStruct.data.z = int(tangent.z);
tangentStruct.data.w = 0;
packedNormal = normalStruct.pack;
packedTangent = tangentStruct.pack;
}
bool bufferViewElementFromVariant(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;
} 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;
}
setBufferViewElement<glm::u8vec4>(view, index, v); return true;
}
}
} else if (BYTES_PER_ELEMENT == 2) {
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;
}
}
}
return false;
}
QVariant bufferViewElementToVariant(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());
Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT));
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) {
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 <typename T>
QVariant glmVecToVariant(const T& v, bool asArray /*= false*/) {
static const auto len = T().length();
if (asArray) {
QVariantList list;
for (int i = 0; i < len ; i++) {
list << v[i];
}
return list;
} else {
QVariantMap obj;
for (int i = 0; i < len ; i++) {
obj[XYZW[i]] = v[i];
}
return obj;
}
}
template <typename T>
const T glmVecFromVariant(const QVariant& v) {
auto isMap = v.type() == (QVariant::Type)QMetaType::QVariantMap;
static const auto len = T().length();
const auto& components = isMap ? XYZW : ZERO123;
T result;
QVariantMap map;
QVariantList list;
if (isMap) map = v.toMap(); else list = v.toList();
for (int i = 0; i < len ; i++) {
float value;
if (isMap) {
value = map.value(components[i]).toFloat();
} else {
value = list.value(i).toFloat();
}
if (value != value) { // NAN
qWarning().nospace()<< "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString();
}
result[i] = value;
}
return result;
}

View file

@ -0,0 +1,18 @@
//
// 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>
namespace gpu { class BufferView; }
template <typename T> QVariant glmVecToVariant(const T& v, bool asArray = false);
template <typename T> const T glmVecFromVariant(const QVariant& v);
QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = "");
bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v);

View file

@ -0,0 +1,83 @@
#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-scripting/BufferViewHelpers.h>
#ifdef DEBUG_BUFFERVIEW_SCRIPTING
#include <graphics/DebugNames.h>
#endif
namespace {
const std::array<const char*, 4> XYZW = {{ "x", "y", "z", "w" }};
const std::array<const char*, 4> ZERO123 = {{ "0", "1", "2", "3" }};
}
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 = bufferViewElementToVariant(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 bufferViewElementFromVariant(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 ? ZERO123 : 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() ? XYZW : ZERO123;
T result;
for (int i = 0; i < len ; i++) {
const auto key = components[i];
const auto value = v.property(key).toNumber();
#ifdef DEV_BUILD
if (value != value) { // NAN
qWarning().nospace()<< "vec" << len << "." << key << " NAN received from script.... " << v.toVariant().toString();
}
#endif
result[i] = value;
}
return result;
}

View file

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

View file

@ -0,0 +1,72 @@
#pragma once
#include <QtCore>
#include <QVariant>
#include <QObject>
#include <gpu/Format.h>
#include <gpu/Stream.h>
//#include <gpu/Buffer.h>
Q_DECLARE_METATYPE(gpu::Type);
#ifdef QT_MOC_RUN
class DebugNames {
Q_OBJECT
public:
#else
namespace DebugNames {
Q_NAMESPACE
#endif
enum Type : uint8_t {
FLOAT = 0,
INT32,
UINT32,
HALF,
INT16,
UINT16,
INT8,
UINT8,
NINT32,
NUINT32,
NINT16,
NUINT16,
NINT8,
NUINT8,
COMPRESSED,
NUM_TYPES,
BOOL = UINT8,
NORMALIZED_START = NINT32,
};
Q_ENUM_NS(Type)
enum InputSlot {
POSITION = 0,
NORMAL = 1,
COLOR = 2,
TEXCOORD0 = 3,
TEXCOORD = TEXCOORD0,
TANGENT = 4,
SKIN_CLUSTER_INDEX = 5,
SKIN_CLUSTER_WEIGHT = 6,
TEXCOORD1 = 7,
TEXCOORD2 = 8,
TEXCOORD3 = 9,
TEXCOORD4 = 10,
NUM_INPUT_SLOTS,
DRAW_CALL_INFO = 15, // Reserve last input slot for draw call infos
};
Q_ENUM_NS(InputSlot)
inline QString stringFrom(Type t) { return QVariant::fromValue(t).toString(); }
inline QString stringFrom(InputSlot t) { return QVariant::fromValue(t).toString(); }
inline QString stringFrom(gpu::Type t) { return stringFrom((Type)t); }
inline QString stringFrom(gpu::Stream::Slot t) { return stringFrom((InputSlot)t); }
extern const QMetaObject staticMetaObject;
};

View file

@ -0,0 +1,836 @@
//
// ModelScriptingInterface.cpp
// libraries/script-engine/src
//
// Created by Seth Alves on 2017-1-27.
// Copyright 2017 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 "ModelScriptingInterface.h"
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValueIterator>
#include <QtScript/QScriptValue>
#include <QUuid>
#include "BaseScriptEngine.h"
#include "ScriptEngineLogging.h"
#include "OBJWriter.h"
#include "OBJReader.h"
//#include "ui/overlays/Base3DOverlay.h"
//#include "EntityTreeRenderer.h"
//#include "avatar/AvatarManager.h"
//#include "RenderableEntityItem.h"
#include <glm/gtx/string_cast.hpp>
#include <GeometryUtil.h>
#include <shared/QtHelpers.h>
#include <graphics-scripting/DebugNames.h>
#include <graphics-scripting/BufferViewHelpers.h>
#include "BufferViewScripting.h"
#include "ScriptableMesh.h"
using ScriptableMesh = scriptable::ScriptableMesh;
#include "ModelScriptingInterface.moc"
namespace {
QLoggingCategory model_scripting { "hifi.model.scripting" };
}
ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) {
if (auto scriptEngine = qobject_cast<QScriptEngine*>(parent)) {
this->registerMetaTypes(scriptEngine);
}
}
QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) {
const auto& in = _in.getMeshes();
qCDebug(model_scripting) << "meshToOBJ" << in.size();
if (in.size()) {
QList<scriptable::MeshPointer> meshes;
foreach (const auto meshProxy, in) {
qCDebug(model_scripting) << "meshToOBJ" << meshProxy.get();
if (meshProxy) {
meshes.append(getMeshPointer(meshProxy));
}
}
if (meshes.size()) {
return writeOBJToString(meshes);
}
}
context()->throwError(QString("null mesh"));
return QString();
}
QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _in) {
const auto& in = _in.getMeshes();
// figure out the size of the resulting mesh
size_t totalVertexCount { 0 };
size_t totalColorCount { 0 };
size_t totalNormalCount { 0 };
size_t totalIndexCount { 0 };
foreach (const scriptable::ScriptableMeshPointer meshProxy, in) {
scriptable::MeshPointer mesh = getMeshPointer(meshProxy);
totalVertexCount += mesh->getNumVertices();
int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h
const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor);
gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements();
totalColorCount += numColors;
int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h
const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal);
gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements();
totalNormalCount += numNormals;
totalIndexCount += mesh->getNumIndices();
}
// alloc the resulting mesh
gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3);
unsigned char* combinedVertexData = new unsigned char[combinedVertexSize];
unsigned char* combinedVertexDataCursor = combinedVertexData;
gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3);
unsigned char* combinedColorData = new unsigned char[combinedColorSize];
unsigned char* combinedColorDataCursor = combinedColorData;
gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3);
unsigned char* combinedNormalData = new unsigned char[combinedNormalSize];
unsigned char* combinedNormalDataCursor = combinedNormalData;
gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t);
unsigned char* combinedIndexData = new unsigned char[combinedIndexSize];
unsigned char* combinedIndexDataCursor = combinedIndexData;
uint32_t indexStartOffset { 0 };
foreach (const scriptable::ScriptableMeshPointer meshProxy, in) {
scriptable::MeshPointer mesh = getMeshPointer(meshProxy);
mesh->forEach(
[&](glm::vec3 position){
memcpy(combinedVertexDataCursor, &position, sizeof(position));
combinedVertexDataCursor += sizeof(position);
},
[&](glm::vec3 color){
memcpy(combinedColorDataCursor, &color, sizeof(color));
combinedColorDataCursor += sizeof(color);
},
[&](glm::vec3 normal){
memcpy(combinedNormalDataCursor, &normal, sizeof(normal));
combinedNormalDataCursor += sizeof(normal);
},
[&](uint32_t index){
index += indexStartOffset;
memcpy(combinedIndexDataCursor, &index, sizeof(index));
combinedIndexDataCursor += sizeof(index);
});
gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices();
indexStartOffset += numVertices;
}
graphics::MeshPointer result(new graphics::Mesh());
gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ);
gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData);
gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer);
gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement);
result->setVertexBuffer(combinedVertexBufferView);
int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h
gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ);
gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData);
gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer);
gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement);
result->addAttribute(attributeTypeColor, combinedColorsBufferView);
int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h
gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ);
gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData);
gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer);
gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement);
result->addAttribute(attributeTypeNormal, combinedNormalsBufferView);
gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW);
gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData);
gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer);
gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement);
result->setIndexBuffer(combinedIndexesBufferView);
std::vector<graphics::Mesh::Part> parts;
parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex
(graphics::Index)result->getNumIndices(), // numIndices
(graphics::Index)0, // baseVertex
graphics::Mesh::TRIANGLES)); // topology
result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part),
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result));
return engine()->toScriptValue(result);
}
QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) {
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return false;
}
graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); },
[&](glm::vec3 color){ return color; },
[&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); },
[&](uint32_t index){ return index; });
scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result));
return engine()->toScriptValue(resultProxy);
}
QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshPointer meshProxy) {
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return -1;
}
return (uint32_t)mesh->getNumVertices();
}
QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex) {
auto mesh = getMeshPointer(meshProxy);
const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer();
auto numVertices = mesh->getNumVertices();
if (vertexIndex >= numVertices) {
context()->throwError(QString("invalid index: %1 [0,%2)").arg(vertexIndex).arg(numVertices));
return QScriptValue::NullValue;
}
glm::vec3 pos = vertexBufferView.get<glm::vec3>(vertexIndex);
return engine()->toScriptValue(pos);
}
QScriptValue ModelScriptingInterface::newMesh(const QVector<glm::vec3>& vertices,
const QVector<glm::vec3>& normals,
const QVector<mesh::MeshFace>& faces) {
graphics::MeshPointer mesh(new graphics::Mesh());
// vertices
auto vertexBuffer = std::make_shared<gpu::Buffer>(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data());
auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer);
gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(),
sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
mesh->setVertexBuffer(vertexBufferView);
if (vertices.size() == normals.size()) {
// normals
auto normalBuffer = std::make_shared<gpu::Buffer>(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data());
auto normalBufferPtr = gpu::BufferPointer(normalBuffer);
gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(),
sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView);
} else {
qCWarning(model_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices");
}
// indices (faces)
int VERTICES_PER_TRIANGLE = 3;
int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE;
unsigned char* indexData = new unsigned char[indexBufferSize];
unsigned char* indexDataCursor = indexData;
foreach(const mesh::MeshFace& meshFace, faces) {
for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) {
memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t));
indexDataCursor += sizeof(uint32_t);
}
}
auto indexBuffer = std::make_shared<gpu::Buffer>(indexBufferSize, (gpu::Byte*)indexData);
auto indexBufferPtr = gpu::BufferPointer(indexBuffer);
gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW));
mesh->setIndexBuffer(indexBufferView);
// parts
std::vector<graphics::Mesh::Part> parts;
parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex
(graphics::Index)faces.size() * 3, // numIndices
(graphics::Index)0, // baseVertex
graphics::Mesh::TRIANGLES)); // topology
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part),
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, mesh));
return engine()->toScriptValue(meshProxy);
}
QScriptValue ModelScriptingInterface::mapAttributeValues(
QScriptValue _in,
QScriptValue scopeOrCallback,
QScriptValue methodOrName
) {
qCInfo(model_scripting) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject();
auto in = qscriptvalue_cast<scriptable::ScriptableModel>(_in).getMeshes();
if (in.size()) {
foreach (scriptable::ScriptableMeshPointer meshProxy, in) {
mapMeshAttributeValues(meshProxy, scopeOrCallback, methodOrName);
}
return thisObject();
} else if (auto meshProxy = qobject_cast<scriptable::ScriptableMesh*>(_in.toQObject())) {
return mapMeshAttributeValues(meshProxy->shared_from_this(), scopeOrCallback, methodOrName);
} else {
context()->throwError("invalid ModelProxy || MeshProxyPointer");
}
return false;
}
QScriptValue ModelScriptingInterface::unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) {
auto mesh = getMeshPointer(meshProxy);
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices" << !!mesh<< !!meshProxy;
if (!mesh) {
return QScriptValue();
}
auto positions = mesh->getVertexBuffer();
auto indices = mesh->getIndexBuffer();
quint32 numPoints = (quint32)indices.getNumElements();
auto buffer = new gpu::Buffer();
buffer->resize(numPoints * sizeof(uint32_t));
auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX });
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices numPoints" << numPoints;
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
for (const auto& a : attributeViews) {
auto& view = a.second;
auto sz = view._element.getSize();
auto buffer = new gpu::Buffer();
buffer->resize(numPoints * sz);
auto points = gpu::BufferView(buffer, view._element);
auto src = (uint8_t*)view._buffer->getData();
auto dest = (uint8_t*)points._buffer->getData();
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices buffer" << a.first;
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices source" << view.getNumElements();
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices dest" << points.getNumElements();
qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices sz" << sz << src << dest << slot;
auto esize = indices._element.getSize();
const char* hint= a.first.toStdString().c_str();
for(quint32 i = 0; i < numPoints; i++) {
quint32 index = esize == 4 ? indices.get<quint32>(i) : indices.get<quint16>(i);
newindices.edit<uint32_t>(i) = i;
bufferViewElementFromVariant(
points, i,
bufferViewElementToVariant(view, index, false, hint)
);
}
if (slot == gpu::Stream::POSITION) {
mesh->setVertexBuffer(points);
} else {
mesh->addAttribute(slot, points);
}
}
mesh->setIndexBuffer(newindices);
if (recalcNormals) {
recalculateNormals(meshProxy);
}
return true;
}
namespace {
template <typename T>
gpu::BufferView bufferViewFromVector(QVector<T> elements, gpu::Element elementType) {
auto vertexBuffer = std::make_shared<gpu::Buffer>(
elements.size() * sizeof(T),
(gpu::Byte*)elements.data()
);
return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType };
}
gpu::BufferView cloneBufferView(const gpu::BufferView& input) {
//qCInfo(model_scripting) << "input" << input.getNumElements() << input._buffer->getSize();
auto output = gpu::BufferView(
std::make_shared<gpu::Buffer>(input._buffer->getSize(), input._buffer->getData()),
input._offset,
input._size,
input._stride,
input._element
);
//qCInfo(model_scripting) << "after" << output.getNumElements() << output._buffer->getSize();
return output;
}
gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) {
auto effectiveSize = input._buffer->getSize() / input.getNumElements();
qCInfo(model_scripting) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize;
auto vsize = input._element.getSize() * numElements;
gpu::Byte *data = new gpu::Byte[vsize];
memset(data, 0, vsize);
auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data);
delete[] data;
auto output = gpu::BufferView(buffer, input._element);
qCInfo(model_scripting) << "resized output" << output.getNumElements() << output._buffer->getSize();
return output;
}
}
bool ModelScriptingInterface::replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer src, const QVector<QString>& attributeNames) {
auto target = getMeshPointer(dest);
auto source = getMeshPointer(src);
if (!target || !source) {
context()->throwError("ModelScriptingInterface::replaceMeshData -- expected dest and src to be valid mesh proxy pointers");
return false;
}
QVector<QString> attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames;
//qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes;
// remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names
if (attributeNames.isEmpty()) {
auto attributeViews = ScriptableMesh::gatherBufferViews(target);
for (const auto& a : attributeViews) {
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
if (!attributes.contains(a.first)) {
//qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- pruning target attribute" << a.first << slot;
target->removeAttribute(slot);
}
}
}
target->setVertexBuffer(cloneBufferView(source->getVertexBuffer()));
target->setIndexBuffer(cloneBufferView(source->getIndexBuffer()));
target->setPartBuffer(cloneBufferView(source->getPartBuffer()));
for (const auto& a : attributes) {
auto slot = ScriptableMesh::ATTRIBUTES[a];
if (slot == gpu::Stream::POSITION) {
continue;
}
// auto& before = target->getAttributeBuffer(slot);
auto& input = source->getAttributeBuffer(slot);
if (input.getNumElements() == 0) {
//qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData buffer is empty -- pruning" << a << slot;
target->removeAttribute(slot);
} else {
// if (before.getNumElements() == 0) {
// qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer is empty -- adding" << a << slot;
// } else {
// qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer exists -- updating" << a << slot;
// }
target->addAttribute(slot, cloneBufferView(input));
}
// auto& after = target->getAttributeBuffer(slot);
// qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements();
}
return true;
}
bool ModelScriptingInterface::dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon) {
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return false;
}
auto positions = mesh->getVertexBuffer();
auto numPositions = positions.getNumElements();
const auto epsilon2 = epsilon*epsilon;
QVector<glm::vec3> uniqueVerts;
uniqueVerts.reserve((int)numPositions);
QMap<quint32,quint32> remapIndices;
for (quint32 i = 0; i < numPositions; i++) {
const quint32 numUnique = uniqueVerts.size();
const auto& position = positions.get<glm::vec3>(i);
bool unique = true;
for (quint32 j = 0; j < numUnique; j++) {
if (glm::length2(uniqueVerts[j] - position) <= epsilon2) {
remapIndices[i] = j;
unique = false;
break;
}
}
if (unique) {
uniqueVerts << position;
remapIndices[i] = numUnique;
}
}
qCInfo(model_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size();
auto indices = mesh->getIndexBuffer();
auto numIndices = indices.getNumElements();
auto esize = indices._element.getSize();
QVector<quint32> newIndices;
newIndices.reserve((int)numIndices);
for (quint32 i = 0; i < numIndices; i++) {
quint32 index = esize == 4 ? indices.get<quint32>(i) : indices.get<quint16>(i);
if (remapIndices.contains(index)) {
//qCInfo(model_scripting) << i << index << "->" << remapIndices[index];
newIndices << remapIndices[index];
} else {
qCInfo(model_scripting) << i << index << "!remapIndices[index]";
}
}
mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }));
mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ }));
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
quint32 numUniqueVerts = uniqueVerts.size();
for (const auto& a : attributeViews) {
auto& view = a.second;
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
if (slot == gpu::Stream::POSITION) {
continue;
}
qCInfo(model_scripting) << "ModelScriptingInterface::dedupeVertices" << a.first << slot << view.getNumElements();
auto newView = resizedBufferView(view, numUniqueVerts);
qCInfo(model_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements();
quint32 numElements = (quint32)view.getNumElements();
for (quint32 i = 0; i < numElements; i++) {
quint32 fromVertexIndex = i;
quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex;
bufferViewElementFromVariant(
newView, toVertexIndex,
bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe")
);
}
mesh->addAttribute(slot, newView);
}
return true;
}
QScriptValue ModelScriptingInterface::cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) {
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return QScriptValue::NullValue;
}
graphics::MeshPointer clone(new graphics::Mesh());
clone->displayName = mesh->displayName + "-clone";
qCInfo(model_scripting) << "ModelScriptingInterface::cloneMesh" << !!mesh<< !!meshProxy;
if (!mesh) {
return QScriptValue::NullValue;
}
clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer()));
clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer()));
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh);
for (const auto& a : attributeViews) {
auto& view = a.second;
auto slot = ScriptableMesh::ATTRIBUTES[a.first];
qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices buffer" << a.first << slot;
auto points = cloneBufferView(view);
qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices source" << view.getNumElements();
qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices dest" << points.getNumElements();
if (slot == gpu::Stream::POSITION) {
clone->setVertexBuffer(points);
} else {
clone->addAttribute(slot, points);
}
}
auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone));
if (recalcNormals) {
recalculateNormals(result);
}
return engine()->toScriptValue(result);
}
bool ModelScriptingInterface::recalculateNormals(scriptable::ScriptableMeshPointer meshProxy) {
qCInfo(model_scripting) << "Recalculating normals" << !!meshProxy;
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return false;
}
ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions
auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL);
auto verts = mesh->getVertexBuffer();
auto indices = mesh->getIndexBuffer();
auto esize = indices._element.getSize();
auto numPoints = indices.getNumElements();
const auto TRIANGLE = 3;
quint32 numFaces = (quint32)numPoints / TRIANGLE;
//QVector<Triangle> faces;
QVector<glm::vec3> faceNormals;
QMap<QString,QVector<quint32>> vertexToFaces;
//faces.resize(numFaces);
faceNormals.resize(numFaces);
auto numNormals = normals.getNumElements();
qCInfo(model_scripting) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints);
if (normals.getNumElements() != verts.getNumElements()) {
return false;
}
for (quint32 i = 0; i < numFaces; i++) {
quint32 I = TRIANGLE * i;
quint32 i0 = esize == 4 ? indices.get<quint32>(I+0) : indices.get<quint16>(I+0);
quint32 i1 = esize == 4 ? indices.get<quint32>(I+1) : indices.get<quint16>(I+1);
quint32 i2 = esize == 4 ? indices.get<quint32>(I+2) : indices.get<quint16>(I+2);
Triangle face = {
verts.get<glm::vec3>(i1),
verts.get<glm::vec3>(i2),
verts.get<glm::vec3>(i0)
};
faceNormals[i] = face.getNormal();
if (glm::isnan(faceNormals[i].x)) {
qCInfo(model_scripting) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2);
break;
}
vertexToFaces[glm::to_string(face.v0).c_str()] << i;
vertexToFaces[glm::to_string(face.v1).c_str()] << i;
vertexToFaces[glm::to_string(face.v2).c_str()] << i;
}
for (quint32 j = 0; j < numNormals; j++) {
//auto v = verts.get<glm::vec3>(j);
glm::vec3 normal { 0.0f, 0.0f, 0.0f };
QString key { glm::to_string(verts.get<glm::vec3>(j)).c_str() };
const auto& faces = vertexToFaces.value(key);
if (faces.size()) {
for (const auto i : faces) {
normal += faceNormals[i];
}
normal *= 1.0f / (float)faces.size();
} else {
static int logged = 0;
if (logged++ < 10) {
qCInfo(model_scripting) << "no faces for key!?" << key;
}
normal = verts.get<glm::vec3>(j);
}
if (glm::isnan(normal.x)) {
static int logged = 0;
if (logged++ < 10) {
qCInfo(model_scripting) << "isnan(normal.x)" << j << vec3toVariant(normal);
}
break;
}
normals.edit<glm::vec3>(j) = glm::normalize(normal);
}
return true;
}
QScriptValue ModelScriptingInterface::mapMeshAttributeValues(
scriptable::ScriptableMeshPointer meshProxy, QScriptValue scopeOrCallback, QScriptValue methodOrName
) {
auto mesh = getMeshPointer(meshProxy);
if (!mesh) {
return false;
}
auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName);
// input buffers
gpu::BufferView positions = mesh->getVertexBuffer();
const auto nPositions = positions.getNumElements();
// destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh)
auto scope = scopedHandler.property("scope");
auto callback = scopedHandler.property("callback");
auto js = engine(); // cache value to avoid resolving each iteration
auto meshPart = js->toScriptValue(meshProxy);
auto obj = js->newObject();
auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" });
for (uint32_t i=0; i < nPositions; i++) {
for (const auto& a : attributeViews) {
bool asArray = a.second._element.getType() != gpu::FLOAT;
obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str()));
}
auto result = callback.call(scope, { obj, i, meshPart });
if (js->hasUncaughtException()) {
context()->throwValue(js->uncaughtException());
return false;
}
if (result.isBool() && !result.toBool()) {
// bail without modifying data if user explicitly returns false
continue;
}
if (result.isObject() && !result.strictlyEquals(obj)) {
// user returned a new object (ie: instead of modifying input properties)
obj = result;
}
for (const auto& a : attributeViews) {
const auto& attribute = obj.property(a.first);
auto& view = a.second;
if (attribute.isValid()) {
bufferViewElementFromScriptValue(attribute, view, i);
}
}
}
return thisObject();
}
void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) {
auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName);
Q_ASSERT(handler.engine() == this->engine());
QPointer<BaseScriptEngine> engine = dynamic_cast<BaseScriptEngine*>(handler.engine());
scriptable::ScriptableModel meshes;
bool success = false;
QString error;
auto appProvider = DependencyManager::get<scriptable::ModelProviderFactory>();
qDebug() << "appProvider" << appProvider.data();
scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr;
QString providerType = provider ? provider->metadata.value("providerType").toString() : QString();
if (providerType.isEmpty()) {
providerType = "unknown";
}
if (provider) {
qCDebug(model_scripting) << "fetching meshes from " << providerType << "...";
auto scriptableMeshes = provider->getScriptableModel(&success);
qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <<success << "#" << scriptableMeshes.meshes.size();
if (success) {
meshes = scriptableMeshes;//SimpleModelProxy::fromScriptableModel(scriptableMeshes);
}
}
if (!success) {
error = QString("failed to get meshes from %1 provider for uuid %2").arg(providerType).arg(uuid.toString());
}
if (!error.isEmpty()) {
qCWarning(model_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error;
callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue);
} else {
callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes));
}
}
namespace {
QScriptValue meshToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) {
return engine->newQObject(in.get(), QScriptEngine::QtOwnership,
QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects
);
}
void meshFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) {
auto obj = value.toQObject();
//qDebug() << "meshFromScriptValue" << obj;
if (auto tmp = qobject_cast<scriptable::ScriptableMesh*>(obj)) {
out = tmp->shared_from_this();
}
// FIXME: Why does above cast not work on Win32!?
if (!out) {
auto smp = static_cast<scriptable::ScriptableMesh*>(obj);
//qDebug() << "meshFromScriptValue2" << smp;
out = smp->shared_from_this();
}
}
QScriptValue meshesToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) {
// QScriptValueList result;
QScriptValue result = engine->newArray();
int i = 0;
foreach(scriptable::ScriptableMeshPointer const meshProxy, in->getMeshes()) {
result.setProperty(i++, meshToScriptValue(engine, meshProxy));
}
return result;
}
void meshesFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) {
const auto length = value.property("length").toInt32();
qCDebug(model_scripting) << "in meshesFromScriptValue, length =" << length;
for (int i = 0; i < length; i++) {
if (const auto meshProxy = qobject_cast<scriptable::ScriptableMesh*>(value.property(i).toQObject())) {
out->meshes.append(meshProxy->getMeshPointer());
} else {
qCDebug(model_scripting) << "null meshProxy" << i;
}
}
}
void modelProxyFromScriptValue(const QScriptValue& object, scriptable::ScriptableModel &meshes) {
auto meshesProperty = object.property("meshes");
if (meshesProperty.property("length").toInt32() > 0) {
//meshes._meshes = qobject_cast<scriptable::ScriptableModelPointer>(meshesProperty.toQObject());
// qDebug() << "modelProxyFromScriptValue" << meshesProperty.property("length").toInt32() << meshesProperty.toVariant().typeName();
qScriptValueToSequence(meshesProperty, meshes.meshes);
} else if (auto mesh = qobject_cast<scriptable::ScriptableMesh*>(object.toQObject())) {
meshes.meshes << mesh->getMeshPointer();
} else {
qDebug() << "modelProxyFromScriptValue -- unrecognized input" << object.toVariant().toString();
}
meshes.metadata = object.property("metadata").toVariant().toMap();
}
QScriptValue modelProxyToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModel &in) {
QScriptValue obj = engine->newObject();
obj.setProperty("meshes", qScriptValueFromSequence(engine, in.meshes));
obj.setProperty("metadata", engine->toScriptValue(in.metadata));
return obj;
}
QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) {
QScriptValue obj = engine->newObject();
obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices));
return obj;
}
void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) {
qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices);
}
QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector<mesh::MeshFace>& vector) {
return qScriptValueFromSequence(engine, vector);
}
void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<mesh::MeshFace>& result) {
qScriptValueToSequence(array, result);
}
QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector<mesh::uint32>& vector) {
return qScriptValueFromSequence(engine, vector);
}
void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector<mesh::uint32>& result) {
qScriptValueToSequence(array, result);
}
}
int meshUint32 = qRegisterMetaType<mesh::uint32>();
namespace mesh {
int meshUint32 = qRegisterMetaType<uint32>();
}
int qVectorMeshUint32 = qRegisterMetaType<QVector<mesh::uint32>>();
void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterSequenceMetaType<QVector<scriptable::ScriptableMeshPointer>>(engine);
qScriptRegisterSequenceMetaType<mesh::MeshFaces>(engine);
qScriptRegisterSequenceMetaType<QVector<mesh::uint32>>(engine);
qScriptRegisterMetaType(engine, modelProxyToScriptValue, modelProxyFromScriptValue);
qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue);
qScriptRegisterMetaType(engine, meshToScriptValue, meshFromScriptValue);
qScriptRegisterMetaType(engine, meshesToScriptValue, meshesFromScriptValue);
qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue);
qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue);
}
MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) {
MeshPointer result;
if (!meshProxy) {
if (context()){
context()->throwError("expected meshProxy as first parameter");
}
return result;
}
auto mesh = meshProxy->getMeshPointer();
if (!mesh) {
if (context()) {
context()->throwError("expected valid meshProxy as first parameter");
}
return result;
}
return mesh;
}

View file

@ -0,0 +1,68 @@
//
// ModelScriptingInterface.h
// libraries/script-engine/src
//
// Created by Seth Alves on 2017-1-27.
// Copyright 2017 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
//
#ifndef hifi_ModelScriptingInterface_h
#define hifi_ModelScriptingInterface_h
#include <QtCore/QObject>
#include <QUrl>
#include <RegisteredMetaTypes.h>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptable>
#include "ScriptableMesh.h"
#include <DependencyManager.h>
class ModelScriptingInterface : public QObject, public QScriptable, public Dependency {
Q_OBJECT
public:
ModelScriptingInterface(QObject* parent = nullptr);
static void registerMetaTypes(QScriptEngine* engine);
public slots:
/**jsdoc
* Returns the meshes associated with a UUID (entityID, overlayID, or avatarID)
*
* @function ModelScriptingInterface.getMeshes
* @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve
*/
void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue());
bool dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon = 1e-6);
bool recalculateNormals(scriptable::ScriptableMeshPointer meshProxy);
QScriptValue cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true);
QScriptValue unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true);
QScriptValue mapAttributeValues(QScriptValue in,
QScriptValue scopeOrCallback,
QScriptValue methodOrName = QScriptValue());
QScriptValue mapMeshAttributeValues(scriptable::ScriptableMeshPointer meshProxy,
QScriptValue scopeOrCallback,
QScriptValue methodOrName = QScriptValue());
QString meshToOBJ(const scriptable::ScriptableModel& in);
bool replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer source, const QVector<QString>& attributeNames = QVector<QString>());
QScriptValue appendMeshes(scriptable::ScriptableModel in);
QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform);
QScriptValue newMesh(const QVector<glm::vec3>& vertices,
const QVector<glm::vec3>& normals,
const QVector<mesh::MeshFace>& faces);
QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy);
QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex);
private:
scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy);
};
#endif // hifi_ModelScriptingInterface_h

View file

@ -0,0 +1,359 @@
//
// SimpleMeshProxy.cpp
// libraries/model-networking/src/model-networking/
//
// Created by Seth Alves on 2017-3-22.
// Copyright 2017 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 "ScriptableMesh.h"
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtx/norm.hpp>
#include <graphics/Geometry.h>
#include <graphics-scripting/DebugNames.h>
#include <graphics-scripting/BufferViewHelpers.h>
#include <Extents.h>
#include "ScriptableMesh.moc"
#include <RegisteredMetaTypes.h>
QLoggingCategory mesh_logging { "hifi.scripting.mesh" };
// FIXME: unroll/resolve before PR
using namespace scriptable;
QMap<QString,int> ScriptableMesh::ATTRIBUTES{
{"position", gpu::Stream::POSITION },
{"normal", gpu::Stream::NORMAL },
{"color", gpu::Stream::COLOR },
{"tangent", gpu::Stream::TEXCOORD0 },
{"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX },
{"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT },
{"texcoord0", gpu::Stream::TEXCOORD0 },
{"texcoord1", gpu::Stream::TEXCOORD1 },
{"texcoord2", gpu::Stream::TEXCOORD2 },
{"texcoord3", gpu::Stream::TEXCOORD3 },
{"texcoord4", gpu::Stream::TEXCOORD4 },
};
QVector<scriptable::ScriptableMeshPointer> scriptable::ScriptableModel::getMeshes() const {
QVector<scriptable::ScriptableMeshPointer> out;
for(auto& mesh : meshes) {
out << scriptable::ScriptableMeshPointer(new ScriptableMesh(std::const_pointer_cast<scriptable::ScriptableModel>(this->shared_from_this()), mesh));
}
return out;
}
quint32 ScriptableMesh::getNumVertices() const {
if (auto mesh = getMeshPointer()) {
return (quint32)mesh->getNumVertices();
}
return 0;
}
// glm::vec3 ScriptableMesh::getPos3(quint32 index) const {
// if (auto mesh = getMeshPointer()) {
// if (index < getNumVertices()) {
// return mesh->getPos3(index);
// }
// }
// return glm::vec3(NAN);
// }
namespace {
gpu::BufferView getBufferView(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) {
return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot);
}
}
QVector<quint32> ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const {
QVector<quint32> result;
if (auto mesh = getMeshPointer()) {
const auto& pos = 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;
}
}
}
return result;
}
QVector<quint32> ScriptableMesh::getIndices() const {
QVector<quint32> result;
if (auto mesh = getMeshPointer()) {
qCDebug(mesh_logging, "getTriangleIndices mesh %p", mesh.get());
gpu::BufferView indexBufferView = mesh->getIndexBuffer();
if (quint32 count = (quint32)indexBufferView.getNumElements()) {
result.resize(count);
auto buffer = indexBufferView._buffer;
if (indexBufferView._element.getSize() == 4) {
// memcpy(result.data(), buffer->getData(), result.size()*sizeof(quint32));
for (quint32 i = 0; i < count; i++) {
result[i] = indexBufferView.get<quint32>(i);
}
} else {
for (quint32 i = 0; i < count; i++) {
result[i] = indexBufferView.get<quint16>(i);
}
}
}
}
return result;
}
quint32 ScriptableMesh::getNumAttributes() const {
if (auto mesh = getMeshPointer()) {
return (quint32)mesh->getNumAttributes();
}
return 0;
}
QVector<QString> ScriptableMesh::getAttributeNames() const {
QVector<QString> result;
if (auto mesh = getMeshPointer()) {
for (const auto& a : ATTRIBUTES.toStdMap()) {
auto bufferView = getBufferView(mesh, a.second);
if (bufferView.getNumElements() > 0) {
result << a.first;
}
}
}
return result;
}
// override
QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const {
return getVertexAttributes(vertexIndex, getAttributeNames());
}
bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) {
qDebug() << "setVertexAttributes" << vertexIndex << attributes;
for (auto& a : gatherBufferViews(getMeshPointer())) {
const auto& name = a.first;
const auto& value = attributes.value(name);
if (value.isValid()) {
auto& view = a.second;
bufferViewElementFromVariant(view, vertexIndex, value);
} else {
qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name;
}
}
return true;
}
int ScriptableMesh::_getSlotNumber(const QString& attributeName) const {
if (auto mesh = getMeshPointer()) {
return ATTRIBUTES.value(attributeName, -1);
}
return -1;
}
QVariantMap ScriptableMesh::getMeshExtents() const {
auto mesh = getMeshPointer();
auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox();
return {
{ "brn", glmVecToVariant(box.getCorner()) },
{ "tfl", glmVecToVariant(box.calcTopFarLeft()) },
{ "center", glmVecToVariant(box.calcCenter()) },
{ "min", glmVecToVariant(box.getMinimumPoint()) },
{ "max", glmVecToVariant(box.getMaximumPoint()) },
{ "dimensions", glmVecToVariant(box.getDimensions()) },
};
}
quint32 ScriptableMesh::getNumParts() const {
if (auto mesh = getMeshPointer()) {
return (quint32)mesh->getNumParts();
}
return 0;
}
QVariantMap ScriptableMesh::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 ScriptableMesh::translate(const glm::vec3& translation) {
return transform(glm::translate(translation));
}
QVariantMap ScriptableMesh::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 ScriptableMesh::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) {
return rotate(glm::quat(glm::radians(eulerAngles)), origin);
}
QVariantMap ScriptableMesh::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 ScriptableMesh::transform(const glm::mat4& transform) {
if (auto mesh = getMeshPointer()) {
const auto& pos = 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 getMeshExtents();
}
QVariantList ScriptableMesh::getAttributeValues(const QString& attributeName) const {
QVariantList result;
auto slotNum = _getSlotNumber(attributeName);
if (slotNum >= 0) {
auto slot = (gpu::Stream::Slot)slotNum;
const auto& bufferView = getBufferView(getMeshPointer(), slot);
if (auto len = bufferView.getNumElements()) {
bool asArray = bufferView._element.getType() != gpu::FLOAT;
for (quint32 i = 0; i < len; i++) {
result << bufferViewElementToVariant(bufferView, i, asArray, attributeName.toStdString().c_str());
}
}
}
return result;
}
QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector<QString> names) const {
QVariantMap result;
auto mesh = getMeshPointer();
if (!mesh || vertexIndex >= getNumVertices()) {
return result;
}
for (const auto& a : ATTRIBUTES.toStdMap()) {
auto name = a.first;
if (!names.contains(name)) {
continue;
}
auto slot = a.second;
const gpu::BufferView& bufferView = getBufferView(mesh, slot);
if (vertexIndex < bufferView.getNumElements()) {
bool asArray = bufferView._element.getType() != gpu::FLOAT;
result[name] = bufferViewElementToVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str());
}
}
return result;
}
/// --- buffer view <-> variant helpers
namespace {
// expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type
gpu::BufferView _expandedAttributeBuffer(const scriptable::MeshPointer mesh, gpu::Stream::Slot slot, const gpu::Element& elementType) {
gpu::Size elementSize = elementType.getSize();
gpu::BufferView bufferView = getBufferView(mesh, slot);
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
);
auto hint = DebugNames::stringFrom(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(mesh_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView";
gpu::Byte *data = new gpu::Byte[vsize];
memset(data, 0, vsize);
auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data);
delete[] data;
bufferView = gpu::BufferView(buffer, elementType);
mesh->addAttribute(slot, bufferView);
} else {
qCInfo(mesh_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) {
auto typeName = DebugNames::stringFrom(bufferView._element.getType());
qCDebug(mesh_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;
}
const gpu::Element UNUSED{ gpu::SCALAR, gpu::UINT8, gpu::RAW };
gpu::Element getVecNElement(gpu::Type T, int N) {
switch(N) {
case 2: return { gpu::VEC2, T, gpu::XY };
case 3: return { gpu::VEC3, T, gpu::XYZ };
case 4: return { gpu::VEC4, T, gpu::XYZW };
}
Q_ASSERT(false);
return UNUSED;
}
gpu::BufferView expandAttributeToMatchPositions(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) {
if (slot == gpu::Stream::POSITION) {
return getBufferView(mesh, slot);
}
return _expandedAttributeBuffer(mesh, slot, getVecNElement(gpu::FLOAT, 3));
}
}
std::map<QString, gpu::BufferView> ScriptableMesh::gatherBufferViews(scriptable::MeshPointer mesh, const QStringList& expandToMatchPositions) {
std::map<QString, gpu::BufferView> attributeViews;
if (!mesh) {
return attributeViews;
}
for (const auto& a : ScriptableMesh::ATTRIBUTES.toStdMap()) {
auto name = a.first;
auto slot = a.second;
if (expandToMatchPositions.contains(name)) {
expandAttributeToMatchPositions(mesh, slot);
}
auto view = getBufferView(mesh, slot);
auto beforeCount = view.getNumElements();
if (beforeCount > 0) {
auto element = view._element;
auto vecN = element.getScalarCount();
auto type = element.getType();
QString typeName = DebugNames::stringFrom(element.getType());
auto beforeTotal = view._size;
attributeViews[name] = _expandedAttributeBuffer(mesh, slot, getVecNElement(type, vecN));
#if DEV_BUILD
auto afterTotal = attributeViews[name]._size;
auto afterCount = attributeViews[name].getNumElements();
if (beforeTotal != afterTotal || beforeCount != afterCount) {
qCDebug(mesh_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
}
}
return attributeViews;
}

View file

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

View file

@ -0,0 +1,80 @@
#pragma once
#include <glm/glm.hpp>
#include <QtCore/QObject>
#include <QtCore/QVector>
#include <QtCore/QList>
#include <QtCore/QVariant>
#include <QtCore/QUuid>
#include <memory>
#include <DependencyManager.h>
namespace graphics {
class Mesh;
}
namespace gpu {
class BufferView;
}
namespace scriptable {
using Mesh = graphics::Mesh;
using MeshPointer = std::shared_ptr<scriptable::Mesh>;
class ScriptableModel;
class ScriptableMesh;
class ScriptableMeshPart;
using ScriptableModelPointer = std::shared_ptr<scriptable::ScriptableModel>;
using ScriptableMeshPointer = std::shared_ptr<scriptable::ScriptableMesh>;
using ScriptableMeshPartPointer = std::shared_ptr<scriptable::ScriptableMeshPart>;
class ScriptableModel : public QObject, public std::enable_shared_from_this<ScriptableModel> {
Q_OBJECT
public:
Q_PROPERTY(QVector<scriptable::ScriptableMeshPointer> meshes READ getMeshes)
Q_INVOKABLE QString toString() { return "[ScriptableModel " + objectName()+"]"; }
ScriptableModel(QObject* parent = nullptr) : QObject(parent) {}
ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {}
ScriptableModel& operator=(const ScriptableModel& view) {
objectID = view.objectID;
metadata = view.metadata;
meshes = view.meshes;
return *this;
}
~ScriptableModel() { qDebug() << "~ScriptableModel" << this; }
void mixin(const ScriptableModel& other) {
for (const auto& key : other.metadata.keys()) {
metadata[key] = other.metadata[key];
}
for(const auto&mesh : other.meshes) {
meshes << mesh;
}
}
QUuid objectID;
QVariantMap metadata;
QVector<scriptable::MeshPointer> meshes;
// TODO: in future accessors for these could go here
QVariantMap shapes;
QVariantMap materials;
QVariantMap armature;
QVector<scriptable::ScriptableMeshPointer> getMeshes() const;
};
class ModelProvider {
public:
QVariantMap metadata;
static scriptable::ScriptableModel modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; }
virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0;
};
using ModelProviderPointer = std::shared_ptr<scriptable::ModelProvider>;
class ModelProviderFactory : public Dependency {
public:
virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0;
};
}
Q_DECLARE_METATYPE(scriptable::MeshPointer)
Q_DECLARE_METATYPE(scriptable::ScriptableModel)
Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer)

View file

@ -42,6 +42,11 @@ void Mesh::addAttribute(Slot slot, const BufferView& buffer) {
evalVertexFormat();
}
void Mesh::removeAttribute(Slot slot) {
_attributeBuffers.erase(slot);
evalVertexFormat();
}
const BufferView Mesh::getAttributeBuffer(int attrib) const {
auto attribBuffer = _attributeBuffers.find(attrib);
if (attribBuffer != _attributeBuffers.end()) {
@ -224,6 +229,7 @@ graphics::MeshPointer Mesh::map(std::function<glm::vec3(glm::vec3)> vertexFunc,
}
graphics::MeshPointer result(new graphics::Mesh());
result->displayName = displayName;
gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ);
gpu::Buffer* resultVertexBuffer = new gpu::Buffer(vertexSize, resultVertexData.get());

View file

@ -56,6 +56,7 @@ public:
// Attribute Buffers
size_t getNumAttributes() const { return _attributeBuffers.size(); }
void addAttribute(Slot slot, const BufferView& buffer);
void removeAttribute(Slot slot);
const BufferView getAttributeBuffer(int attrib) const;
// Stream format

View file

@ -1,27 +0,0 @@
//
// SimpleMeshProxy.cpp
// libraries/model-networking/src/model-networking/
//
// Created by Seth Alves on 2017-3-22.
// Copyright 2017 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 "SimpleMeshProxy.h"
#include <graphics/Geometry.h>
MeshPointer SimpleMeshProxy::getMeshPointer() const {
return _mesh;
}
int SimpleMeshProxy::getNumVertices() const {
return (int)_mesh->getNumVertices();
}
glm::vec3 SimpleMeshProxy::getPos3(int index) const {
return _mesh->getPos3(index);
}

View file

@ -1,36 +0,0 @@
//
// SimpleMeshProxy.h
// libraries/model-networking/src/model-networking/
//
// Created by Seth Alves on 2017-1-27.
// Copyright 2017 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
//
#ifndef hifi_SimpleMeshProxy_h
#define hifi_SimpleMeshProxy_h
#include <QScriptEngine>
#include <QScriptValueIterator>
#include <QtScript/QScriptValue>
#include <RegisteredMetaTypes.h>
class SimpleMeshProxy : public MeshProxy {
public:
SimpleMeshProxy(const MeshPointer& mesh) : _mesh(mesh) { }
MeshPointer getMeshPointer() const override;
int getNumVertices() const override;
glm::vec3 getPos3(int index) const override;
protected:
const MeshPointer _mesh;
};
#endif // hifi_SimpleMeshProxy_h

View file

@ -7,6 +7,7 @@ link_hifi_libraries(shared ktx gpu graphics model-networking render animation fb
include_hifi_library_headers(networking)
include_hifi_library_headers(octree)
include_hifi_library_headers(audio)
include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h
if (NOT ANDROID)
target_nsight()

View file

@ -2407,3 +2407,48 @@ void GeometryCache::renderWireCubeInstance(RenderArgs* args, gpu::Batch& batch,
assert(pipeline != nullptr);
renderInstances(args, batch, color, true, pipeline, GeometryCache::Cube);
}
graphics::MeshPointer GeometryCache::meshFromShape(Shape geometryShape, glm::vec3 color) {
auto shapeData = getShapeData(geometryShape);
qDebug() << "GeometryCache::getMeshProxyListFromShape" << shapeData << stringFromShape(geometryShape);
auto cloneBufferView = [](const gpu::BufferView& in) -> gpu::BufferView {
auto buffer = std::make_shared<gpu::Buffer>(*in._buffer); // copy
// FIXME: gpu::BufferView seems to have a bug where constructing a new instance from an existing one
// results in over-multiplied buffer/view sizes -- hence constructing manually here from each input prop
auto out = gpu::BufferView(buffer, in._offset, in._size, in._stride, in._element);
Q_ASSERT(out.getNumElements() == in.getNumElements());
Q_ASSERT(out._size == in._size);
Q_ASSERT(out._buffer->getSize() == in._buffer->getSize());
return out;
};
auto positionsBufferView = cloneBufferView(shapeData->_positionView);
auto normalsBufferView = cloneBufferView(shapeData->_normalView);
auto indexBufferView = cloneBufferView(shapeData->_indicesView);
gpu::BufferView::Size numVertices = positionsBufferView.getNumElements();
Q_ASSERT(numVertices == normalsBufferView.getNumElements());
// apply input color across all vertices
auto colorsBufferView = cloneBufferView(shapeData->_normalView);
for (gpu::BufferView::Size i = 0; i < numVertices; i++) {
colorsBufferView.edit<glm::vec3>((gpu::BufferView::Index)i) = color;
}
graphics::MeshPointer mesh(new graphics::Mesh());
mesh->setVertexBuffer(positionsBufferView);
mesh->setIndexBuffer(indexBufferView);
mesh->addAttribute(gpu::Stream::NORMAL, normalsBufferView);
mesh->addAttribute(gpu::Stream::COLOR, colorsBufferView);
const auto startIndex = 0, baseVertex = 0;
graphics::Mesh::Part part(startIndex, (graphics::Index)indexBufferView.getNumElements(), baseVertex, graphics::Mesh::TRIANGLES);
auto partBuffer = new gpu::Buffer(sizeof(graphics::Mesh::Part), (gpu::Byte*)&part);
mesh->setPartBuffer(gpu::BufferView(partBuffer, gpu::Element::PART_DRAWCALL));
mesh->displayName = QString("GeometryCache/shape::%1").arg(GeometryCache::stringFromShape(geometryShape));
return mesh;
}

View file

@ -375,6 +375,7 @@ public:
/// otherwise nullptr in the event of an error.
const ShapeData * getShapeData(Shape shape) const;
graphics::MeshPointer meshFromShape(Shape geometryShape, glm::vec3 color);
private:
GeometryCache();

View file

@ -26,7 +26,7 @@
#include <GLMHelpers.h>
#include <TBBHelpers.h>
#include <model-networking/SimpleMeshProxy.h>
#include <graphics-scripting/ScriptableModel.h>
#include <DualQuaternion.h>
#include <glm/gtc/packing.hpp>
@ -573,15 +573,21 @@ bool Model::convexHullContains(glm::vec3 point) {
return false;
}
MeshProxyList Model::getMeshes() const {
MeshProxyList result;
scriptable::ScriptableModel Model::getScriptableModel(bool* ok) {
scriptable::ScriptableModel result;
const Geometry::Pointer& renderGeometry = getGeometry();
const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes();
if (!isLoaded()) {
qDebug() << "Model::getScriptableModel -- !isLoaded";
if (ok) {
*ok = false;
}
return result;
}
// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry
#if 0 // renderGeometry approach
const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes();
Transform offset;
offset.setScale(_scale);
offset.postTranslate(_offset);
@ -591,20 +597,67 @@ MeshProxyList Model::getMeshes() const {
if (!mesh) {
continue;
}
MeshProxy* meshProxy = new SimpleMeshProxy(
mesh->map(
[=](glm::vec3 position) {
return glm::vec3(offsetMat * glm::vec4(position, 1.0f));
},
[=](glm::vec3 color) { return color; },
[=](glm::vec3 normal) {
return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f)));
},
[&](uint32_t index) { return index; }));
result << meshProxy;
qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName;
auto newmesh = mesh->map(
[=](glm::vec3 position) {
return glm::vec3(offsetMat * glm::vec4(position, 1.0f));
},
[=](glm::vec3 color) { return color; },
[=](glm::vec3 normal) {
return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f)));
},
[&](uint32_t index) { return index; });
newmesh->displayName = mesh->displayName;
result << newmesh;
}
#endif
const FBXGeometry& geometry = getFBXGeometry();
auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant {
QVector<float> floats;
floats.resize(16);
memcpy(floats.data(), &mat4, sizeof(glm::mat4));
QVariant v;
v.setValue<QVector<float>>(floats);
return v;
};
result.metadata = {
{ "url", _url.toString() },
{ "textures", renderGeometry->getTextures() },
{ "offset", vec3toVariant(_offset) },
{ "scale", vec3toVariant(_scale) },
{ "rotation", quatToVariant(_rotation) },
{ "translation", vec3toVariant(_translation) },
{ "meshToModel", mat4toVariant(glm::scale(_scale) * glm::translate(_offset)) },
{ "meshToWorld", mat4toVariant(createMatFromQuatAndPos(_rotation, _translation) * (glm::scale(_scale) * glm::translate(_offset))) },
{ "geometryOffset", mat4toVariant(geometry.offset) },
};
QVariantList submeshes;
int numberOfMeshes = geometry.meshes.size();
for (int i = 0; i < numberOfMeshes; i++) {
const FBXMesh& fbxMesh = geometry.meshes.at(i);
auto mesh = fbxMesh._mesh;
if (!mesh) {
continue;
}
result.meshes << std::const_pointer_cast<graphics::Mesh>(mesh);
auto extraInfo = geometry.getModelNameOfMesh(i);
qDebug() << "Model::getScriptableModel #" << i << QString(mesh->displayName) << extraInfo;
submeshes << QVariantMap{
{ "index", i },
{ "meshIndex", fbxMesh.meshIndex },
{ "modelName", extraInfo },
{ "transform", mat4toVariant(fbxMesh.modelTransform) },
{ "extents", QVariantMap({
{ "minimum", vec3toVariant(fbxMesh.meshExtents.minimum) },
{ "maximum", vec3toVariant(fbxMesh.meshExtents.maximum) },
})},
};
}
if (ok) {
*ok = true;
}
qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size();
result.metadata["submeshes"] = submeshes;
return result;
}

View file

@ -27,6 +27,7 @@
#include <gpu/Batch.h>
#include <render/Forward.h>
#include <render/Scene.h>
#include <graphics-scripting/ScriptableModel.h>
#include <Transform.h>
#include <SpatiallyNestable.h>
#include <TriangleSet.h>
@ -64,7 +65,7 @@ using ModelWeakPointer = std::weak_ptr<Model>;
/// A generic 3D model displaying geometry loaded from a URL.
class Model : public QObject, public std::enable_shared_from_this<Model> {
class Model : public QObject, public std::enable_shared_from_this<Model>, public scriptable::ModelProvider {
Q_OBJECT
public:
@ -313,7 +314,7 @@ public:
int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); }
int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); }
Q_INVOKABLE MeshProxyList getMeshes() const;
Q_INVOKABLE virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override;
void scaleToFit();

View file

@ -1,251 +0,0 @@
//
// ModelScriptingInterface.cpp
// libraries/script-engine/src
//
// Created by Seth Alves on 2017-1-27.
// Copyright 2017 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 "ModelScriptingInterface.h"
#include <QScriptEngine>
#include <QScriptValueIterator>
#include <QtScript/QScriptValue>
#include <model-networking/SimpleMeshProxy.h>
#include "ScriptEngine.h"
#include "ScriptEngineLogging.h"
#include "OBJWriter.h"
ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) {
_modelScriptEngine = qobject_cast<QScriptEngine*>(parent);
qScriptRegisterSequenceMetaType<QList<MeshProxy*>>(_modelScriptEngine);
qScriptRegisterMetaType(_modelScriptEngine, meshFaceToScriptValue, meshFaceFromScriptValue);
qScriptRegisterMetaType(_modelScriptEngine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue);
}
QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) {
QList<MeshPointer> meshes;
foreach (const MeshProxy* meshProxy, in) {
meshes.append(meshProxy->getMeshPointer());
}
return writeOBJToString(meshes);
}
QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) {
// figure out the size of the resulting mesh
size_t totalVertexCount { 0 };
size_t totalColorCount { 0 };
size_t totalNormalCount { 0 };
size_t totalIndexCount { 0 };
foreach (const MeshProxy* meshProxy, in) {
MeshPointer mesh = meshProxy->getMeshPointer();
totalVertexCount += mesh->getNumVertices();
int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h
const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor);
gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements();
totalColorCount += numColors;
int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h
const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal);
gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements();
totalNormalCount += numNormals;
totalIndexCount += mesh->getNumIndices();
}
// alloc the resulting mesh
gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3);
std::unique_ptr<unsigned char> combinedVertexData{ new unsigned char[combinedVertexSize] };
unsigned char* combinedVertexDataCursor = combinedVertexData.get();
gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3);
std::unique_ptr<unsigned char> combinedColorData{ new unsigned char[combinedColorSize] };
unsigned char* combinedColorDataCursor = combinedColorData.get();
gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3);
std::unique_ptr<unsigned char> combinedNormalData{ new unsigned char[combinedNormalSize] };
unsigned char* combinedNormalDataCursor = combinedNormalData.get();
gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t);
std::unique_ptr<unsigned char> combinedIndexData{ new unsigned char[combinedIndexSize] };
unsigned char* combinedIndexDataCursor = combinedIndexData.get();
uint32_t indexStartOffset { 0 };
foreach (const MeshProxy* meshProxy, in) {
MeshPointer mesh = meshProxy->getMeshPointer();
mesh->forEach(
[&](glm::vec3 position){
memcpy(combinedVertexDataCursor, &position, sizeof(position));
combinedVertexDataCursor += sizeof(position);
},
[&](glm::vec3 color){
memcpy(combinedColorDataCursor, &color, sizeof(color));
combinedColorDataCursor += sizeof(color);
},
[&](glm::vec3 normal){
memcpy(combinedNormalDataCursor, &normal, sizeof(normal));
combinedNormalDataCursor += sizeof(normal);
},
[&](uint32_t index){
index += indexStartOffset;
memcpy(combinedIndexDataCursor, &index, sizeof(index));
combinedIndexDataCursor += sizeof(index);
});
gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices();
indexStartOffset += numVertices;
}
graphics::MeshPointer result(new graphics::Mesh());
gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ);
gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData.get());
gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer);
gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement);
result->setVertexBuffer(combinedVertexBufferView);
int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h
gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ);
gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData.get());
gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer);
gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement);
result->addAttribute(attributeTypeColor, combinedColorsBufferView);
int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h
gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ);
gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData.get());
gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer);
gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement);
result->addAttribute(attributeTypeNormal, combinedNormalsBufferView);
gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW);
gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData.get());
gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer);
gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement);
result->setIndexBuffer(combinedIndexesBufferView);
std::vector<graphics::Mesh::Part> parts;
parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex
(graphics::Index)result->getNumIndices(), // numIndices
(graphics::Index)0, // baseVertex
graphics::Mesh::TRIANGLES)); // topology
result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part),
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
MeshProxy* resultProxy = new SimpleMeshProxy(result);
return meshToScriptValue(_modelScriptEngine, resultProxy);
}
QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshProxy* meshProxy) {
if (!meshProxy) {
return QScriptValue(false);
}
MeshPointer mesh = meshProxy->getMeshPointer();
if (!mesh) {
return QScriptValue(false);
}
const auto inverseTransposeTransform = glm::inverse(glm::transpose(transform));
graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); },
[&](glm::vec3 color){ return color; },
[&](glm::vec3 normal){ return glm::vec3(inverseTransposeTransform * glm::vec4(normal, 0.0f)); },
[&](uint32_t index){ return index; });
MeshProxy* resultProxy = new SimpleMeshProxy(result);
return meshToScriptValue(_modelScriptEngine, resultProxy);
}
QScriptValue ModelScriptingInterface::getVertexCount(MeshProxy* meshProxy) {
if (!meshProxy) {
return QScriptValue(false);
}
MeshPointer mesh = meshProxy->getMeshPointer();
if (!mesh) {
return QScriptValue(false);
}
gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices();
return numVertices;
}
QScriptValue ModelScriptingInterface::getVertex(MeshProxy* meshProxy, int vertexIndex) {
if (!meshProxy) {
return QScriptValue(false);
}
MeshPointer mesh = meshProxy->getMeshPointer();
if (!mesh) {
return QScriptValue(false);
}
const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer();
gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices();
if (vertexIndex < 0 || vertexIndex >= numVertices) {
return QScriptValue(false);
}
glm::vec3 pos = vertexBufferView.get<glm::vec3>(vertexIndex);
return vec3toScriptValue(_modelScriptEngine, pos);
}
QScriptValue ModelScriptingInterface::newMesh(const QVector<glm::vec3>& vertices,
const QVector<glm::vec3>& normals,
const QVector<MeshFace>& faces) {
graphics::MeshPointer mesh(new graphics::Mesh());
// vertices
auto vertexBuffer = std::make_shared<gpu::Buffer>(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data());
auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer);
gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(),
sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
mesh->setVertexBuffer(vertexBufferView);
if (vertices.size() == normals.size()) {
// normals
auto normalBuffer = std::make_shared<gpu::Buffer>(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data());
auto normalBufferPtr = gpu::BufferPointer(normalBuffer);
gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(),
sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView);
} else {
qCDebug(scriptengine) << "ModelScriptingInterface::newMesh normals must be same length as vertices";
}
// indices (faces)
int VERTICES_PER_TRIANGLE = 3;
int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE;
unsigned char* indexData = new unsigned char[indexBufferSize];
unsigned char* indexDataCursor = indexData;
foreach(const MeshFace& meshFace, faces) {
for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) {
memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t));
indexDataCursor += sizeof(uint32_t);
}
}
auto indexBuffer = std::make_shared<gpu::Buffer>(indexBufferSize, (gpu::Byte*)indexData);
auto indexBufferPtr = gpu::BufferPointer(indexBuffer);
gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW));
mesh->setIndexBuffer(indexBufferView);
// parts
std::vector<graphics::Mesh::Part> parts;
parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex
(graphics::Index)faces.size() * 3, // numIndices
(graphics::Index)0, // baseVertex
graphics::Mesh::TRIANGLES)); // topology
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part),
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
MeshProxy* meshProxy = new SimpleMeshProxy(mesh);
return meshToScriptValue(_modelScriptEngine, meshProxy);
}

View file

@ -1,39 +0,0 @@
//
// ModelScriptingInterface.h
// libraries/script-engine/src
//
// Created by Seth Alves on 2017-1-27.
// Copyright 2017 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
//
#ifndef hifi_ModelScriptingInterface_h
#define hifi_ModelScriptingInterface_h
#include <QtCore/QObject>
#include <RegisteredMetaTypes.h>
class QScriptEngine;
class ModelScriptingInterface : public QObject {
Q_OBJECT
public:
ModelScriptingInterface(QObject* parent);
Q_INVOKABLE QString meshToOBJ(MeshProxyList in);
Q_INVOKABLE QScriptValue appendMeshes(MeshProxyList in);
Q_INVOKABLE QScriptValue transformMesh(glm::mat4 transform, MeshProxy* meshProxy);
Q_INVOKABLE QScriptValue newMesh(const QVector<glm::vec3>& vertices,
const QVector<glm::vec3>& normals,
const QVector<MeshFace>& faces);
Q_INVOKABLE QScriptValue getVertexCount(MeshProxy* meshProxy);
Q_INVOKABLE QScriptValue getVertex(MeshProxy* meshProxy, int vertexIndex);
private:
QScriptEngine* _modelScriptEngine { nullptr };
};
#endif // hifi_ModelScriptingInterface_h

View file

@ -73,8 +73,6 @@
#include "WebSocketClass.h"
#include "RecordingScriptingInterface.h"
#include "ScriptEngines.h"
#include "ModelScriptingInterface.h"
#include <Profile.h>
@ -711,10 +709,6 @@ void ScriptEngine::init() {
registerGlobalObject("DebugDraw", &DebugDraw::getInstance());
registerGlobalObject("Model", new ModelScriptingInterface(this));
qScriptRegisterMetaType(this, meshToScriptValue, meshFromScriptValue);
qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue);
registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
}

View file

@ -855,68 +855,3 @@ QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const Animatio
void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& details) {
// nothing for now...
}
QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in) {
return engine->newQObject(in, QScriptEngine::QtOwnership,
QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
}
void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out) {
out = qobject_cast<MeshProxy*>(value.toQObject());
}
QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in) {
// QScriptValueList result;
QScriptValue result = engine->newArray();
int i = 0;
foreach(MeshProxy* const meshProxy, in) {
result.setProperty(i++, meshToScriptValue(engine, meshProxy));
}
return result;
}
void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) {
QScriptValueIterator itr(value);
qDebug() << "in meshesFromScriptValue, value.length =" << value.property("length").toInt32();
while (itr.hasNext()) {
itr.next();
MeshProxy* meshProxy = qscriptvalue_cast<MeshProxyList::value_type>(itr.value());
if (meshProxy) {
out.append(meshProxy);
} else {
qDebug() << "null meshProxy";
}
}
}
QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) {
QScriptValue obj = engine->newObject();
obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices));
return obj;
}
void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult) {
qVectorIntFromScriptValue(object.property("vertices"), meshFaceResult.vertexIndices);
}
QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector<MeshFace>& vector) {
QScriptValue array = engine->newArray();
for (int i = 0; i < vector.size(); i++) {
array.setProperty(i, meshFaceToScriptValue(engine, vector.at(i)));
}
return array;
}
void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<MeshFace>& result) {
int length = array.property("length").toInteger();
result.clear();
for (int i = 0; i < length; i++) {
MeshFace meshFace = MeshFace();
meshFaceFromScriptValue(array.property(i), meshFace);
result << meshFace;
}
}

View file

@ -315,51 +315,9 @@ Q_DECLARE_METATYPE(AnimationDetails);
QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event);
void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event);
namespace graphics {
class Mesh;
}
using MeshPointer = std::shared_ptr<graphics::Mesh>;
class MeshProxy : public QObject {
Q_OBJECT
public:
virtual MeshPointer getMeshPointer() const = 0;
Q_INVOKABLE virtual int getNumVertices() const = 0;
Q_INVOKABLE virtual glm::vec3 getPos3(int index) const = 0;
};
Q_DECLARE_METATYPE(MeshProxy*);
class MeshProxyList : public QList<MeshProxy*> {}; // typedef and using fight with the Qt macros/templates, do this instead
Q_DECLARE_METATYPE(MeshProxyList);
QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in);
void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out);
QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in);
void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out);
class MeshFace {
public:
MeshFace() {}
~MeshFace() {}
QVector<uint32_t> vertexIndices;
// TODO -- material...
};
Q_DECLARE_METATYPE(MeshFace)
Q_DECLARE_METATYPE(QVector<MeshFace>)
QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace);
void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult);
QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector<MeshFace>& vector);
void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<MeshFace>& result);
#endif // hifi_RegisteredMetaTypes_h