first cut at running scripts from entities

This commit is contained in:
ZappoMan 2014-10-31 14:03:30 -07:00
parent 277da5a24f
commit a5088eece6
7 changed files with 167 additions and 34 deletions

View file

@ -140,6 +140,9 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Visible:", value: properties.visible });
index++;
array.push({ label: "Script:", value: properties.script });
index++;
if (properties.type == "Box" || properties.type == "Sphere") {
array.push({ label: "Color:", type: "header" });
@ -282,6 +285,7 @@ EntityPropertyDialogBox = (function () {
properties.lifetime = array[index++].value;
properties.visible = array[index++].value;
properties.script = array[index++].value;
if (properties.type == "Box" || properties.type == "Sphere") {
index++; // skip header

View file

@ -148,7 +148,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_voxelImporter(),
_importSucceded(false),
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
_entityClipboardRenderer(),
_entities(true),
_entityCollisionSystem(),
_entityClipboardRenderer(false),
_entityClipboard(),
_wantToKillLocalVoxels(false),
_viewFrustum(),
@ -3831,35 +3833,7 @@ void joystickFromScriptValue(const QScriptValue &object, Joystick* &out) {
out = qobject_cast<Joystick*>(object.toQObject());
}
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
bool loadScriptFromEditor, bool activateMainWindow) {
QUrl scriptUrl(scriptFilename);
const QString& scriptURLString = scriptUrl.toString();
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
&& !_scriptEnginesHash[scriptURLString]->isFinished()) {
return _scriptEnginesHash[scriptURLString];
}
ScriptEngine* scriptEngine;
if (scriptFilename.isNull()) {
scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
} else {
// start the script on a new thread...
scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
if (!scriptEngine->hasScript()) {
qDebug() << "Application::loadScript(), script failed to load...";
QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
return NULL;
}
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptURLString);
}
scriptEngine->setUserLoaded(isUserLoaded);
void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) {
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
// we can use the same ones from the application.
scriptEngine->getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender);
@ -3941,6 +3915,38 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
// Starts an event loop, and emits workerThread->started()
workerThread->start();
}
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
bool loadScriptFromEditor, bool activateMainWindow) {
QUrl scriptUrl(scriptFilename);
const QString& scriptURLString = scriptUrl.toString();
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
&& !_scriptEnginesHash[scriptURLString]->isFinished()) {
return _scriptEnginesHash[scriptURLString];
}
ScriptEngine* scriptEngine;
if (scriptFilename.isNull()) {
scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
} else {
// start the script on a new thread...
scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
if (!scriptEngine->hasScript()) {
qDebug() << "Application::loadScript(), script failed to load...";
QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
return NULL;
}
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptURLString);
}
scriptEngine->setUserLoaded(isUserLoaded);
registerScriptEngineWithApplicationServices(scriptEngine);
// restore the main window's active state
if (activateMainWindow && !loadScriptFromEditor) {

View file

@ -305,6 +305,8 @@ public:
float getRenderResolutionScale() const { return _renderResolutionScale; }
void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine);
signals:
/// Fired when we're simulating; allows external parties to hook in.

View file

@ -11,6 +11,8 @@
#include <glm/gtx/quaternion.hpp>
#include <QScriptSyntaxCheckResult>
#include <FBXReader.h>
#include "InterfaceConfig.h"
@ -39,8 +41,10 @@ QThread* EntityTreeRenderer::getMainThread() {
EntityTreeRenderer::EntityTreeRenderer() :
OctreeRenderer() {
EntityTreeRenderer::EntityTreeRenderer(bool wantScripts) :
OctreeRenderer(),
_wantScripts(wantScripts),
_entitiesScriptEngine(NULL) {
REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
@ -51,6 +55,7 @@ EntityTreeRenderer::EntityTreeRenderer() :
}
EntityTreeRenderer::~EntityTreeRenderer() {
// do we need to delete the _entitiesScriptEngine?? or is it deleted by default
}
void EntityTreeRenderer::clear() {
@ -60,6 +65,51 @@ void EntityTreeRenderer::clear() {
void EntityTreeRenderer::init() {
OctreeRenderer::init();
static_cast<EntityTree*>(_tree)->setFBXService(this);
if (_wantScripts) {
_entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities",
Application::getInstance()->getControllerScriptingInterface());
Application::getInstance()->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
}
}
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) {
EntityItem* entity = static_cast<EntityTree*>(_tree)->findEntityByEntityItemID(entityItemID);
return loadEntityScript(entity);
}
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
if (!entity) {
return QScriptValue(); // no entity...
}
if (_entityScripts.contains(entity->getEntityItemID())) {
return _entityScripts[entity->getEntityItemID()]; // already loaded
}
if (entity->getScript().isEmpty()) {
return QScriptValue(); // no script
}
if (QScriptEngine::checkSyntax(entity->getScript()).state() != QScriptSyntaxCheckResult::Valid) {
qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entity->getEntityItemID();
qDebug() << " INVALID SYNTAX";
qDebug() << " SCRIPT:" << entity->getScript();
return QScriptValue(); // invalid script
}
QScriptValue entityScriptConstructor = _entitiesScriptEngine->evaluate(entity->getScript());
if (!entityScriptConstructor.isFunction()) {
qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entity->getEntityItemID();
qDebug() << " NOT CONSTRUCTOR";
qDebug() << " SCRIPT:" << entity->getScript();
return QScriptValue(); // invalid script
}
QScriptValue entityScriptObject = entityScriptConstructor.construct();
_entityScripts[entity->getEntityItemID()] = entityScriptObject;
return entityScriptObject; // newly constructed
}
void EntityTreeRenderer::setTree(Octree* newTree) {
@ -417,6 +467,7 @@ RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(cons
result.entityID = intersectedEntity->getEntityItemID();
result.properties = intersectedEntity->getProperties();
result.intersection = ray.origin + (ray.direction * result.distance);
result.entity = intersectedEntity;
}
}
return result;
@ -443,9 +494,17 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device
if (rayPickResult.intersects) {
//qDebug() << "mousePressEvent over entity:" << rayPickResult.entityID;
emit mousePressOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
if (entityScript.property("mousePressOnEntity").isValid()) {
entityScript.property("mousePressOnEntity").call();
}
_currentClickingOnEntityID = rayPickResult.entityID;
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
if (entityScript.property("clickDownOnEntity").isValid()) {
entityScript.property("clickDownOnEntity").call();
}
}
}
@ -456,12 +515,22 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi
if (rayPickResult.intersects) {
//qDebug() << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
emit mouseReleaseOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
if (entityScript.property("mouseReleaseOnEntity").isValid()) {
entityScript.property("mouseReleaseOnEntity").call();
}
}
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
// we're releasing the button, then this is considered a clickOn event
if (!_currentClickingOnEntityID.isInvalidID()) {
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) {
currentClickingEntity.property("clickReleaseOnEntity").call();
}
}
// makes it the unknown ID, we just released so we can't be clicking on anything
@ -469,12 +538,33 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi
}
void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
// experimental...
/*
qDebug() << "TESTING - ";
qDebug() << " _experimentalScriptValue.isValid():" << _experimentalScriptValue.isValid();
qDebug() << " _experimentalConstructed.isValid():" << _experimentalConstructed.isValid();
qDebug() << " _experimentalConstructed.property(MethodA).isValid():" << _experimentalConstructed.property("MethodA").isValid();
_experimentalConstructed.property("MethodA").call();
*/
PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent");
PickRay ray = computePickRay(event->x(), event->y());
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock);
if (rayPickResult.intersects) {
// load the entity script if needed...
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
if (entityScript.property("mouseMoveEvent").isValid()) {
entityScript.property("mouseMoveEvent").call();
}
//qDebug() << "mouseMoveEvent over entity:" << rayPickResult.entityID;
emit mouseMoveOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
if (entityScript.property("mouseMoveOnEntity").isValid()) {
entityScript.property("mouseMoveOnEntity").call();
}
// handle the hover logic...
@ -482,17 +572,28 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
// then we need to send the hover leave.
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
currentHoverEntity.property("hoverLeaveEntity").call();
}
}
// If the new hover entity does not match the previous hover entity then we are entering the new one
// this is true if the _currentHoverOverEntityID is known or unknown
if (rayPickResult.entityID != _currentHoverOverEntityID) {
emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
if (entityScript.property("hoverEnterEntity").isValid()) {
entityScript.property("hoverEnterEntity").call();
}
}
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
// we should send our hover over event
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
if (entityScript.property("hoverOverEntity").isValid()) {
entityScript.property("hoverOverEntity").call();
}
// remember what we're hovering over
_currentHoverOverEntityID = rayPickResult.entityID;
@ -503,6 +604,12 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
// send the hover leave for our previous entity
if (!_currentHoverOverEntityID.isInvalidID()) {
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
currentHoverEntity.property("hoverLeaveEntity").call();
}
_currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
}
}
@ -511,6 +618,10 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
// not yet released the hold then this is still considered a holdingClickOnEntity event
if (!_currentClickingOnEntityID.isInvalidID()) {
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
currentClickingEntity.property("holdingClickOnEntity").call();
}
}
}

View file

@ -31,7 +31,7 @@
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
Q_OBJECT
public:
EntityTreeRenderer();
EntityTreeRenderer(bool wantScripts);
virtual ~EntityTreeRenderer();
virtual Octree* createTree() { return new EntityTree(true); }
@ -107,6 +107,14 @@ private:
EntityItemID _currentHoverOverEntityID;
EntityItemID _currentClickingOnEntityID;
bool _wantScripts;
ScriptEngine* _entitiesScriptEngine;
QScriptValue loadEntityScript(EntityItem* entity);
QScriptValue loadEntityScript(const EntityItemID& entityItemID);
QHash<EntityItemID, QScriptValue> _entityScripts;
};
#endif // hifi_EntityTreeRenderer_h

View file

@ -217,7 +217,8 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
entityID(),
properties(),
distance(0),
face()
face(),
entity(NULL)
{
}

View file

@ -38,6 +38,7 @@ public:
float distance;
BoxFace face;
glm::vec3 intersection;
EntityItem* entity;
};
Q_DECLARE_METATYPE(RayToEntityIntersectionResult)