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 <GPUIdent.h>
#include <gl/GLHelpers.h>
#include <EntityScriptClient.h>
#include <ModelScriptingInterface.h>
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken.

View file

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

View file

@ -14,6 +14,7 @@
#include <QByteArray>
#include <QtConcurrent/QtConcurrentRun>
#include <glm/gtx/transform.hpp>
#include "ModelScriptingInterface.h"
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
@ -73,7 +74,7 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
_meshDirty
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
is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on
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
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
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) {
decompressVolumeData();
} else if (volDataDirty) {
getMesh();
recomputeMesh();
}
model::MeshPointer mesh;
@ -1199,7 +1200,7 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() {
}
}
void RenderablePolyVoxEntityItem::getMesh() {
void RenderablePolyVoxEntityItem::recomputeMesh() {
// use _volData to make a renderable mesh
PolyVoxSurfaceStyle voxelSurfaceStyle;
withReadLock([&] {
@ -1269,12 +1270,20 @@ void RenderablePolyVoxEntityItem::getMesh() {
vertexBufferPtr->getSize() ,
sizeof(PolyVox::PositionMaterialNormal),
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);
});
}
void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) {
// this catches the payload from getMesh
// this catches the payload from recomputeMesh
bool neighborsNeedUpdate;
withWriteLock([&] {
if (!_collisionless) {
@ -1531,7 +1540,6 @@ std::shared_ptr<RenderablePolyVoxEntityItem> RenderablePolyVoxEntityItem::getZPN
return std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(_zPNeighbor.lock());
}
void RenderablePolyVoxEntityItem::bonkNeighbors() {
// flag neighbors to the negative of this entity as needing to rebake their meshes.
cacheNeighbors();
@ -1551,7 +1559,6 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() {
}
}
void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) {
EntityItem::locationChanged(tellPhysics);
if (!_pipeline || !render::Item::isValidID(_myItem)) {
@ -1563,3 +1570,17 @@ void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) {
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;
void setMesh(model::MeshPointer mesh);
bool getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const override;
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
@ -167,7 +168,7 @@ private:
ShapeInfo _shapeInfo;
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
bool _neighborsNeedUpdate { false };
@ -178,7 +179,7 @@ private:
// these are run off the main thread
void decompressVolumeData();
void compressVolumeDataAndSendEditPacket();
virtual void getMesh() override; // recompute mesh
virtual void recomputeMesh() override; // recompute mesh
void computeShapeInfoWorker();
// 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,
std::function<bool(PolyVoxEntityItem&)> actor) {
bool EntityScriptingInterface::polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor) {
PROFILE_RANGE(script_entities, __FUNCTION__);
if (!_entityTree) {
@ -882,11 +881,9 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line
return success;
}
bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxWorker(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setSphere(center, radius, value);
});
}
@ -896,7 +893,7 @@ bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID,
float radius, int value) {
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);
});
}
@ -904,7 +901,7 @@ bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID,
bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxWorker(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
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) {
PROFILE_RANGE(script_entities, __FUNCTION__);
return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxWorker(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
return polyVoxEntity.setAll(value);
});
}
@ -921,11 +918,23 @@ bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3
const glm::vec3& cuboidSize, int value) {
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);
});
}
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) {
PROFILE_RANGE(script_entities, __FUNCTION__);

View file

@ -35,6 +35,7 @@
#include "EntityItemProperties.h"
class EntityTree;
class MeshProxy;
class RayToEntityIntersectionResult {
public:
@ -229,6 +230,7 @@ public slots:
Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value);
Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
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 appendPoint(QUuid entityID, const glm::vec3& point);
@ -325,7 +327,7 @@ signals:
private:
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);
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);

View file

@ -242,3 +242,7 @@ const QByteArray PolyVoxEntityItem::getVoxelData() const {
});
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() {};
void setVoxelDataDirty(bool value) { withWriteLock([&] { _voxelDataDirty = value; }); }
virtual void getMesh() {}; // recompute mesh
virtual void recomputeMesh() {};
virtual bool getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const;
protected:
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);
position += compressedLength;
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");
}
} 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 "ScriptEngines.h"
#include "TabletScriptingInterface.h"
#include "ModelScriptingInterface.h"
#include <Profile.h>
@ -594,7 +596,7 @@ void ScriptEngine::init() {
registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());
registerGlobalObject("File", new FileScriptingInterface(this));
qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue);
qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue);
@ -612,6 +614,10 @@ void ScriptEngine::init() {
registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());
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) {