Merge pull request #9885 from sethalves/model-scripting

Model scripting
This commit is contained in:
Brad Hefta-Gaub 2017-03-17 13:13:17 -07:00 committed by GitHub
commit a75acd139b
15 changed files with 379 additions and 22 deletions

View file

@ -177,6 +177,8 @@
#include "FrameTimingsScriptingInterface.h" #include "FrameTimingsScriptingInterface.h"
#include <GPUIdent.h> #include <GPUIdent.h>
#include <gl/GLHelpers.h> #include <gl/GLHelpers.h>
#include <EntityScriptClient.h>
#include <ModelScriptingInterface.h>
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken. // FIXME seems to be broken.

View file

@ -72,6 +72,8 @@
#include <procedural/ProceduralSkybox.h> #include <procedural/ProceduralSkybox.h>
#include <model/Skybox.h> #include <model/Skybox.h>
#include <ModelScriptingInterface.h>
class OffscreenGLCanvas; class OffscreenGLCanvas;
class GLCanvas; class GLCanvas;

View file

@ -14,6 +14,7 @@
#include <QByteArray> #include <QByteArray>
#include <QtConcurrent/QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
#include <glm/gtx/transform.hpp> #include <glm/gtx/transform.hpp>
#include "ModelScriptingInterface.h"
#if defined(__GNUC__) && !defined(__clang__) #if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push #pragma GCC diagnostic push
@ -73,7 +74,7 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
_meshDirty _meshDirty
In RenderablePolyVoxEntityItem::render, these flags are checked and changes are propagated along the chain. In RenderablePolyVoxEntityItem::render, these flags are checked and changes are propagated along the chain.
decompressVolumeData() is called to decompress _voxelData into _volData. getMesh() is called to invoke the decompressVolumeData() is called to decompress _voxelData into _volData. recomputeMesh() is called to invoke the
polyVox surface extractor to create _mesh (as well as set Simulation _dirtyFlags). Because Simulation::DIRTY_SHAPE polyVox surface extractor to create _mesh (as well as set Simulation _dirtyFlags). Because Simulation::DIRTY_SHAPE
is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on
the surface style. the surface style.
@ -81,7 +82,7 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
When a script changes _volData, compressVolumeDataAndSendEditPacket is called to update _voxelData and to When a script changes _volData, compressVolumeDataAndSendEditPacket is called to update _voxelData and to
send a packet to the entity-server. send a packet to the entity-server.
decompressVolumeData, getMesh, computeShapeInfoWorker, and compressVolumeDataAndSendEditPacket are too expensive decompressVolumeData, recomputeMesh, computeShapeInfoWorker, and compressVolumeDataAndSendEditPacket are too expensive
to run on a thread that has other things to do. These use QtConcurrent::run to spawn a thread. As each thread to run on a thread that has other things to do. These use QtConcurrent::run to spawn a thread. As each thread
finishes, it adjusts the dirty flags so that the next call to render() will kick off the next step. finishes, it adjusts the dirty flags so that the next call to render() will kick off the next step.
@ -682,7 +683,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
if (voxelDataDirty) { if (voxelDataDirty) {
decompressVolumeData(); decompressVolumeData();
} else if (volDataDirty) { } else if (volDataDirty) {
getMesh(); recomputeMesh();
} }
model::MeshPointer mesh; model::MeshPointer mesh;
@ -1199,7 +1200,7 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() {
} }
} }
void RenderablePolyVoxEntityItem::getMesh() { void RenderablePolyVoxEntityItem::recomputeMesh() {
// use _volData to make a renderable mesh // use _volData to make a renderable mesh
PolyVoxSurfaceStyle voxelSurfaceStyle; PolyVoxSurfaceStyle voxelSurfaceStyle;
withReadLock([&] { withReadLock([&] {
@ -1269,12 +1270,20 @@ void RenderablePolyVoxEntityItem::getMesh() {
vertexBufferPtr->getSize() , vertexBufferPtr->getSize() ,
sizeof(PolyVox::PositionMaterialNormal), sizeof(PolyVox::PositionMaterialNormal),
gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)));
std::vector<model::Mesh::Part> parts;
parts.emplace_back(model::Mesh::Part((model::Index)0, // startIndex
(model::Index)vecIndices.size(), // numIndices
(model::Index)0, // baseVertex
model::Mesh::TRIANGLES)); // topology
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(model::Mesh::Part),
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
entity->setMesh(mesh); entity->setMesh(mesh);
}); });
} }
void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) { void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) {
// this catches the payload from getMesh // this catches the payload from recomputeMesh
bool neighborsNeedUpdate; bool neighborsNeedUpdate;
withWriteLock([&] { withWriteLock([&] {
if (!_collisionless) { if (!_collisionless) {
@ -1531,7 +1540,6 @@ std::shared_ptr<RenderablePolyVoxEntityItem> RenderablePolyVoxEntityItem::getZPN
return std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(_zPNeighbor.lock()); return std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(_zPNeighbor.lock());
} }
void RenderablePolyVoxEntityItem::bonkNeighbors() { void RenderablePolyVoxEntityItem::bonkNeighbors() {
// flag neighbors to the negative of this entity as needing to rebake their meshes. // flag neighbors to the negative of this entity as needing to rebake their meshes.
cacheNeighbors(); cacheNeighbors();
@ -1551,7 +1559,6 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() {
} }
} }
void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) { void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) {
EntityItem::locationChanged(tellPhysics); EntityItem::locationChanged(tellPhysics);
if (!_pipeline || !render::Item::isValidID(_myItem)) { if (!_pipeline || !render::Item::isValidID(_myItem)) {
@ -1563,3 +1570,17 @@ void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) {
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);
} }
bool RenderablePolyVoxEntityItem::getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const {
bool success = false;
MeshProxy* meshProxy = nullptr;
model::MeshPointer mesh = nullptr;
withReadLock([&] {
if (_meshInitialized) {
success = true;
meshProxy = new MeshProxy(_mesh);
}
});
result = meshToScriptValue(engine, meshProxy);
return success;
}

View file

@ -133,6 +133,7 @@ public:
QByteArray volDataToArray(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) const; QByteArray volDataToArray(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) const;
void setMesh(model::MeshPointer mesh); void setMesh(model::MeshPointer mesh);
bool getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const override;
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box); void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; } PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
@ -167,7 +168,7 @@ private:
ShapeInfo _shapeInfo; ShapeInfo _shapeInfo;
PolyVox::SimpleVolume<uint8_t>* _volData = nullptr; PolyVox::SimpleVolume<uint8_t>* _volData = nullptr;
bool _volDataDirty = false; // does getMesh need to be called? bool _volDataDirty = false; // does recomputeMesh need to be called?
int _onCount; // how many non-zero voxels are in _volData int _onCount; // how many non-zero voxels are in _volData
bool _neighborsNeedUpdate { false }; bool _neighborsNeedUpdate { false };
@ -178,7 +179,7 @@ private:
// these are run off the main thread // these are run off the main thread
void decompressVolumeData(); void decompressVolumeData();
void compressVolumeDataAndSendEditPacket(); void compressVolumeDataAndSendEditPacket();
virtual void getMesh() override; // recompute mesh virtual void recomputeMesh() override; // recompute mesh
void computeShapeInfoWorker(); void computeShapeInfoWorker();
// these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID // these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID

View file

@ -815,8 +815,7 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
} }
} }
bool EntityScriptingInterface::setVoxels(QUuid entityID, bool EntityScriptingInterface::polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor) {
std::function<bool(PolyVoxEntityItem&)> actor) {
PROFILE_RANGE(script_entities, __FUNCTION__); PROFILE_RANGE(script_entities, __FUNCTION__);
if (!_entityTree) { if (!_entityTree) {
@ -882,11 +881,9 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line
return success; return success;
} }
bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) { bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__); PROFILE_RANGE(script_entities, __FUNCTION__);
return polyVoxWorker(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setSphere(center, radius, value); return polyVoxEntity.setSphere(center, radius, value);
}); });
} }
@ -896,7 +893,7 @@ bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID,
float radius, int value) { float radius, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__); PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [start, end, radius, value](PolyVoxEntityItem& polyVoxEntity) { return polyVoxWorker(entityID, [start, end, radius, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setCapsule(start, end, radius, value); return polyVoxEntity.setCapsule(start, end, radius, value);
}); });
} }
@ -904,7 +901,7 @@ bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID,
bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) { bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__); PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) { return polyVoxWorker(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setVoxelInVolume(position, value); return polyVoxEntity.setVoxelInVolume(position, value);
}); });
} }
@ -912,7 +909,7 @@ bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& positio
bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) { bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__); PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) { return polyVoxWorker(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setAll(value); return polyVoxEntity.setAll(value);
}); });
} }
@ -921,11 +918,23 @@ bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3
const glm::vec3& cuboidSize, int value) { const glm::vec3& cuboidSize, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__); PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) { return polyVoxWorker(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value); return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value);
}); });
} }
void EntityScriptingInterface::voxelsToMesh(QUuid entityID, QScriptValue callback) {
PROFILE_RANGE(script_entities, __FUNCTION__);
polyVoxWorker(entityID, [callback](PolyVoxEntityItem& polyVoxEntity) mutable {
QScriptValue mesh;
polyVoxEntity.getMeshAsScriptValue(callback.engine(), mesh);
QScriptValueList args { mesh };
callback.call(QScriptValue(), args);
return true;
});
}
bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) { bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) {
PROFILE_RANGE(script_entities, __FUNCTION__); PROFILE_RANGE(script_entities, __FUNCTION__);

View file

@ -35,6 +35,7 @@
#include "EntityItemProperties.h" #include "EntityItemProperties.h"
class EntityTree; class EntityTree;
class MeshProxy;
class RayToEntityIntersectionResult { class RayToEntityIntersectionResult {
public: public:
@ -229,6 +230,7 @@ public slots:
Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value); Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value);
Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition, Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
const glm::vec3& cuboidSize, int value); const glm::vec3& cuboidSize, int value);
Q_INVOKABLE void voxelsToMesh(QUuid entityID, QScriptValue callback);
Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector<glm::vec3>& points); Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector<glm::vec3>& points);
Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point); Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point);
@ -325,7 +327,7 @@ signals:
private: private:
bool actionWorker(const QUuid& entityID, std::function<bool(EntitySimulationPointer, EntityItemPointer)> actor); bool actionWorker(const QUuid& entityID, std::function<bool(EntitySimulationPointer, EntityItemPointer)> actor);
bool setVoxels(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor); bool polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor);
bool setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor); bool setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor);
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);

View file

@ -242,3 +242,7 @@ const QByteArray PolyVoxEntityItem::getVoxelData() const {
}); });
return voxelDataCopy; return voxelDataCopy;
} }
bool PolyVoxEntityItem::getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const {
return false;
}

View file

@ -131,7 +131,9 @@ class PolyVoxEntityItem : public EntityItem {
virtual void rebakeMesh() {}; virtual void rebakeMesh() {};
void setVoxelDataDirty(bool value) { withWriteLock([&] { _voxelDataDirty = value; }); } void setVoxelDataDirty(bool value) { withWriteLock([&] { _voxelDataDirty = value; }); }
virtual void getMesh() {}; // recompute mesh virtual void recomputeMesh() {};
virtual bool getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const;
protected: protected:
glm::vec3 _voxelVolumeSize; // this is always 3 bytes glm::vec3 _voxelVolumeSize; // this is always 3 bytes

View file

@ -54,7 +54,8 @@ template<class T> QVariant readBinaryArray(QDataStream& in, int& position) {
in.readRawData(compressed.data() + sizeof(quint32), compressedLength); in.readRawData(compressed.data() + sizeof(quint32), compressedLength);
position += compressedLength; position += compressedLength;
arrayData = qUncompress(compressed); arrayData = qUncompress(compressed);
if (arrayData.isEmpty() || arrayData.size() != (sizeof(T) * arrayLength)) { // answers empty byte array if corrupt if (arrayData.isEmpty() ||
(unsigned int)arrayData.size() != (sizeof(T) * arrayLength)) { // answers empty byte array if corrupt
throw QString("corrupt fbx file"); throw QString("corrupt fbx file");
} }
} else { } else {

View file

@ -0,0 +1,148 @@
//
// OBJWriter.cpp
// libraries/fbx/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 <QFile>
#include <QFileInfo>
#include "model/Geometry.h"
#include "OBJWriter.h"
#include "ModelFormatLogging.h"
static QString formatFloat(double n) {
// limit precision to 6, but don't output trailing zeros.
QString s = QString::number(n, 'f', 6);
while (s.endsWith("0")) {
s.remove(s.size() - 1, 1);
}
if (s.endsWith(".")) {
s.remove(s.size() - 1, 1);
}
// check for non-numbers. if we get NaN or inf or scientific notation, just return 0
for (int i = 0; i < s.length(); i++) {
auto c = s.at(i).toLatin1();
if (c != '-' &&
c != '.' &&
(c < '0' || c > '9')) {
qCDebug(modelformat) << "OBJWriter zeroing bad vertex coordinate:" << s << "because of" << c;
return QString("0");
}
}
return s;
}
bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes) {
// each mesh's vertices are numbered from zero. We're combining all their vertices into one list here,
// so keep track of the start index for each mesh.
QList<int> meshVertexStartOffset;
int currentVertexStartOffset = 0;
// write out all vertices
foreach (const MeshPointer& mesh, meshes) {
meshVertexStartOffset.append(currentVertexStartOffset);
const gpu::BufferView& vertexBuffer = mesh->getVertexBuffer();
int vertexCount = 0;
gpu::BufferView::Iterator<const glm::vec3> vertexItr = vertexBuffer.cbegin<const glm::vec3>();
while (vertexItr != vertexBuffer.cend<const glm::vec3>()) {
glm::vec3 v = *vertexItr;
out << "v ";
out << formatFloat(v[0]) << " ";
out << formatFloat(v[1]) << " ";
out << formatFloat(v[2]) << "\n";
vertexItr++;
vertexCount++;
}
currentVertexStartOffset += vertexCount;
}
out << "\n";
// write out faces
int nth = 0;
foreach (const MeshPointer& mesh, meshes) {
currentVertexStartOffset = meshVertexStartOffset.takeFirst();
const gpu::BufferView& partBuffer = mesh->getPartBuffer();
const gpu::BufferView& indexBuffer = mesh->getIndexBuffer();
model::Index partCount = (model::Index)mesh->getNumParts();
for (int partIndex = 0; partIndex < partCount; partIndex++) {
const model::Mesh::Part& part = partBuffer.get<model::Mesh::Part>(partIndex);
out << "g part-" << nth++ << "\n";
// model::Mesh::TRIANGLES
// TODO -- handle other formats
gpu::BufferView::Iterator<const uint32_t> indexItr = indexBuffer.cbegin<uint32_t>();
indexItr += part._startIndex;
int indexCount = 0;
while (indexItr != indexBuffer.cend<uint32_t>() && indexCount < part._numIndices) {
uint32_t index0 = *indexItr;
indexItr++;
indexCount++;
if (indexItr == indexBuffer.cend<uint32_t>() || indexCount >= part._numIndices) {
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3";
break;
}
uint32_t index1 = *indexItr;
indexItr++;
indexCount++;
if (indexItr == indexBuffer.cend<uint32_t>() || indexCount >= part._numIndices) {
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3";
break;
}
uint32_t index2 = *indexItr;
indexItr++;
indexCount++;
out << "f ";
out << currentVertexStartOffset + index0 + 1 << " ";
out << currentVertexStartOffset + index1 + 1 << " ";
out << currentVertexStartOffset + index2 + 1 << "\n";
}
out << "\n";
}
}
return true;
}
bool writeOBJToFile(QString path, QList<MeshPointer> meshes) {
if (QFileInfo(path).exists() && !QFile::remove(path)) {
qCDebug(modelformat) << "OBJ writer failed, file exists:" << path;
return false;
}
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) {
qCDebug(modelformat) << "OBJ writer failed to open output file:" << path;
return false;
}
QTextStream outStream(&file);
bool success;
success = writeOBJToTextStream(outStream, meshes);
file.close();
return success;
}
QString writeOBJToString(QList<MeshPointer> meshes) {
QString result;
QTextStream outStream(&result, QIODevice::ReadWrite);
bool success;
success = writeOBJToTextStream(outStream, meshes);
if (success) {
return result;
}
return QString("");
}

View file

@ -0,0 +1,26 @@
//
// OBJWriter.h
// libraries/fbx/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_objwriter_h
#define hifi_objwriter_h
#include <QString>
#include <QList>
#include <model/Geometry.h>
using MeshPointer = std::shared_ptr<model::Mesh>;
bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes);
bool writeOBJToFile(QString path, QList<MeshPointer> meshes);
QString writeOBJToString(QList<MeshPointer> meshes);
#endif // hifi_objwriter_h

View file

@ -0,0 +1,41 @@
//
// MeshProxy.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_MeshProxy_h
#define hifi_MeshProxy_h
#include <model/Geometry.h>
using MeshPointer = std::shared_ptr<model::Mesh>;
class MeshProxy : public QObject {
Q_OBJECT
public:
MeshProxy(MeshPointer mesh) : _mesh(mesh) {}
~MeshProxy() {}
MeshPointer getMeshPointer() const { return _mesh; }
Q_INVOKABLE int getNumVertices() const { return (int)_mesh->getNumVertices(); }
Q_INVOKABLE glm::vec3 getPos3(int index) const { return _mesh->getPos3(index); }
protected:
MeshPointer _mesh;
};
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);
#endif // hifi_MeshProxy_h

View file

@ -0,0 +1,53 @@
//
// 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 <QScriptEngine>
#include <QScriptValueIterator>
#include <QtScript/QScriptValue>
#include "ModelScriptingInterface.h"
#include "OBJWriter.h"
ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) {
}
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) {
return engine->toScriptValue(in);
}
void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) {
QScriptValueIterator itr(value);
while(itr.hasNext()) {
itr.next();
MeshProxy* meshProxy = qscriptvalue_cast<MeshProxyList::value_type>(itr.value());
if (meshProxy) {
out.append(meshProxy);
}
}
}
QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) {
QList<MeshPointer> meshes;
foreach (const MeshProxy* meshProxy, in) {
meshes.append(meshProxy->getMeshPointer());
}
return writeOBJToString(meshes);
}

View file

@ -0,0 +1,39 @@
//
// 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 <QScriptValue>
#include <OBJWriter.h>
#include <model/Geometry.h>
#include "MeshProxy.h"
using MeshPointer = std::shared_ptr<model::Mesh>;
class ModelScriptingInterface : public QObject {
Q_OBJECT
public:
ModelScriptingInterface(QObject* parent);
Q_INVOKABLE QString meshToOBJ(MeshProxyList in);
};
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);
#endif // hifi_ModelScriptingInterface_h

View file

@ -65,6 +65,8 @@
#include "RecordingScriptingInterface.h" #include "RecordingScriptingInterface.h"
#include "ScriptEngines.h" #include "ScriptEngines.h"
#include "TabletScriptingInterface.h" #include "TabletScriptingInterface.h"
#include "ModelScriptingInterface.h"
#include <Profile.h> #include <Profile.h>
@ -594,7 +596,7 @@ void ScriptEngine::init() {
registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data()); registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());
registerGlobalObject("File", new FileScriptingInterface(this)); registerGlobalObject("File", new FileScriptingInterface(this));
qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue); qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue);
qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue); qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue);
@ -612,6 +614,10 @@ void ScriptEngine::init() {
registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data()); registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());
registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); registerGlobalObject("DebugDraw", &DebugDraw::getInstance());
registerGlobalObject("Model", new ModelScriptingInterface(this));
qScriptRegisterMetaType(this, meshToScriptValue, meshFromScriptValue);
qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue);
} }
void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {