Showing the changes to libraries/entities/src/EntityScriptingInterface.cpp (1 of 364 files). Show all
Unified · Split
| @@ -8,8 +8,15 @@ · 7 unmodified lines | ||
| 8 | 8 | // Distributed under the Apache License, Version 2.0. |
| 9 | 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 10 | 10 | // |
| 11 | + | |
| 12 | +#include <glm/gtc/matrix_transform.hpp> | |
| 13 | +#include <glm/gtx/transform.hpp> | |
| 14 | + | |
| 11 | 15 | #include "EntityScriptingInterface.h" |
| 12 | 16 | |
| 17 | +#include <QFutureWatcher> | |
| 18 | +#include <QtConcurrent/QtConcurrentRun> | |
| 19 | + | |
| 13 | 20 | #include "EntityItemID.h" |
| 14 | 21 | #include <VariantMapToScriptValue.h> |
| 15 | 22 | #include <SharedUtil.h> |
| @@ -289,13 +296,11 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit · 273 unmodified lines | ||
| 289 | 296 | |
| 290 | 297 | results = entity->getProperties(desiredProperties); |
| 291 | 298 | |
| 292 | − // TODO: improve sitting points and naturalDimensions in the future, | |
| 293 | − // for now we've included the old sitting points model behavior for entity types that are models | |
| 294 | − // we've also added this hack for setting natural dimensions of models | |
| 299 | + // TODO: improve naturalDimensions in the future, | |
| 300 | + // for now we've added this hack for setting natural dimensions of models | |
| 295 | 301 | if (entity->getType() == EntityTypes::Model) { |
| 296 | 302 | const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity); |
| 297 | 303 | if (geometry) { |
| 298 | − results.setSittingPoints(geometry->sittingPoints); | |
| 299 | 304 | Extents meshExtents = geometry->getUnscaledMeshExtents(); |
| 300 | 305 | results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); |
| 301 | 306 | results.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum); |
| @@ -679,6 +684,118 @@ bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) { · 377 unmodified lines | ||
| 679 | 684 | return client->reloadServerScript(entityID); |
| 680 | 685 | } |
| 681 | 686 | |
| 687 | +bool EntityPropertyMetadataRequest::script(EntityItemID entityID, QScriptValue handler) { | |
| 688 | + using LocalScriptStatusRequest = QFutureWatcher<QVariant>; | |
| 689 | + | |
| 690 | + LocalScriptStatusRequest* request = new LocalScriptStatusRequest; | |
| 691 | + QObject::connect(request, &LocalScriptStatusRequest::finished, _engine, [=]() mutable { | |
| 692 | + auto details = request->result().toMap(); | |
| 693 | + QScriptValue err, result; | |
| 694 | + if (details.contains("isError")) { | |
| 695 | + if (!details.contains("message")) { | |
| 696 | + details["message"] = details["errorInfo"]; | |
| 697 | + } | |
| 698 | + err = _engine->makeError(_engine->toScriptValue(details)); | |
| 699 | + } else { | |
| 700 | + details["success"] = true; | |
| 701 | + result = _engine->toScriptValue(details); | |
| 702 | + } | |
| 703 | + callScopedHandlerObject(handler, err, result); | |
| 704 | + request->deleteLater(); | |
| 705 | + }); | |
| 706 | + auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); | |
| 707 | + entityScriptingInterface->withEntitiesScriptEngine([&](EntitiesScriptEngineProvider* entitiesScriptEngine) { | |
| 708 | + if (entitiesScriptEngine) { | |
| 709 | + request->setFuture(entitiesScriptEngine->getLocalEntityScriptDetails(entityID)); | |
| 710 | + } | |
| 711 | + }); | |
| 712 | + if (!request->isStarted()) { | |
| 713 | + request->deleteLater(); | |
| 714 | + callScopedHandlerObject(handler, _engine->makeError("Entities Scripting Provider unavailable", "InternalError"), QScriptValue()); | |
| 715 | + return false; | |
| 716 | + } | |
| 717 | + return true; | |
| 718 | +} | |
| 719 | + | |
| 720 | +bool EntityPropertyMetadataRequest::serverScripts(EntityItemID entityID, QScriptValue handler) { | |
| 721 | + auto client = DependencyManager::get<EntityScriptClient>(); | |
| 722 | + auto request = client->createScriptStatusRequest(entityID); | |
| 723 | + QPointer<BaseScriptEngine> engine = _engine; | |
| 724 | + QObject::connect(request, &GetScriptStatusRequest::finished, _engine, [=](GetScriptStatusRequest* request) mutable { | |
| 725 | + auto engine = _engine; | |
| 726 | + if (!engine) { | |
| 727 | + qCDebug(entities) << __FUNCTION__ << " -- engine destroyed while inflight" << entityID; | |
| 728 | + return; | |
| 729 | + } | |
| 730 | + QVariantMap details; | |
| 731 | + details["success"] = request->getResponseReceived(); | |
| 732 | + details["isRunning"] = request->getIsRunning(); | |
| 733 | + details["status"] = EntityScriptStatus_::valueToKey(request->getStatus()).toLower(); | |
| 734 | + details["errorInfo"] = request->getErrorInfo(); | |
| 735 | + | |
| 736 | + QScriptValue err, result; | |
| 737 | + if (!details["success"].toBool()) { | |
| 738 | + if (!details.contains("message") && details.contains("errorInfo")) { | |
| 739 | + details["message"] = details["errorInfo"]; | |
| 740 | + } | |
| 741 | + if (details["message"].toString().isEmpty()) { | |
| 742 | + details["message"] = "entity server script details not found"; | |
| 743 | + } | |
| 744 | + err = engine->makeError(engine->toScriptValue(details)); | |
| 745 | + } else { | |
| 746 | + result = engine->toScriptValue(details); | |
| 747 | + } | |
| 748 | + callScopedHandlerObject(handler, err, result); | |
| 749 | + request->deleteLater(); | |
| 750 | + }); | |
| 751 | + request->start(); | |
| 752 | + return true; | |
| 753 | +} | |
| 754 | + | |
| 755 | +bool EntityScriptingInterface::queryPropertyMetadata(QUuid entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName) { | |
| 756 | + auto name = property.toString(); | |
| 757 | + auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); | |
| 758 | + QPointer<BaseScriptEngine> engine = dynamic_cast<BaseScriptEngine*>(handler.engine()); | |
| 759 | + if (!engine) { | |
| 760 | + qCDebug(entities) << "queryPropertyMetadata without detectable engine" << entityID << name; | |
| 761 | + return false; | |
| 762 | + } | |
| 763 | +#ifdef DEBUG_ENGINE_STATE | |
| 764 | + connect(engine, &QObject::destroyed, this, [=]() { | |
| 765 | + qDebug() << "queryPropertyMetadata -- engine destroyed!" << (!engine ? "nullptr" : "engine"); | |
| 766 | + }); | |
| 767 | +#endif | |
| 768 | + if (!handler.property("callback").isFunction()) { | |
| 769 | + qDebug() << "!handler.callback.isFunction" << engine; | |
| 770 | + engine->raiseException(engine->makeError("callback is not a function", "TypeError")); | |
| 771 | + return false; | |
| 772 | + } | |
| 773 | + | |
| 774 | + // NOTE: this approach is a work-in-progress and for now just meant to work 100% correctly and provide | |
| 775 | + // some initial structure for organizing metadata adapters around. | |
| 776 | + | |
| 777 | + // The extra layer of indirection is *essential* because in real world conditions errors are often introduced | |
| 778 | + // by accident and sometimes without exact memory of "what just changed." | |
| 779 | + | |
| 780 | + // Here the scripter only needs to know an entityID and a property name -- which means all scripters can | |
| 781 | + // level this method when stuck in dead-end scenarios or to learn more about "magic" Entity properties | |
| 782 | + // like .script that work in terms of side-effects. | |
| 783 | + | |
| 784 | + // This is an async callback pattern -- so if needed C++ can easily throttle or restrict queries later. | |
| 785 | + | |
| 786 | + EntityPropertyMetadataRequest request(engine); | |
| 787 | + | |
| 788 | + if (name == "script") { | |
| 789 | + return request.script(entityID, handler); | |
| 790 | + } else if (name == "serverScripts") { | |
| 791 | + return request.serverScripts(entityID, handler); | |
| 792 | + } else { | |
| 793 | + engine->raiseException(engine->makeError("metadata for property " + name + " is not yet queryable")); | |
| 794 | + engine->maybeEmitUncaughtException(__FUNCTION__); | |
| 795 | + return false; | |
| 796 | + } | |
| 797 | +} | |
| 798 | + | |
| 682 | 799 | bool EntityScriptingInterface::getServerScriptStatus(QUuid entityID, QScriptValue callback) { |
| 683 | 800 | auto client = DependencyManager::get<EntityScriptClient>(); |
| 684 | 801 | auto request = client->createScriptStatusRequest(entityID); |
| @@ -806,8 +923,7 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra · 121 unmodified lines | ||
| 806 | 923 | } |
| 807 | 924 | } |
| 808 | 925 | |
| 809 | −bool EntityScriptingInterface::setVoxels(QUuid entityID, | |
| 810 | − std::function<bool(PolyVoxEntityItem&)> actor) { | |
| 926 | +bool EntityScriptingInterface::polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor) { | |
| 811 | 927 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 812 | 928 | |
| 813 | 929 | if (!_entityTree) { |
| @@ -873,11 +989,9 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line · 59 unmodified lines | ||
| 873 | 989 | return success; |
| 874 | 990 | } |
| 875 | 991 | |
| 876 | − | |
| 877 | 992 | bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) { |
| 878 | 993 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 879 | − | |
| 880 | − return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) { | |
| 994 | + return polyVoxWorker(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) { | |
| 881 | 995 | return polyVoxEntity.setSphere(center, radius, value); |
| 882 | 996 | }); |
| 883 | 997 | } |
| @@ -887,7 +1001,7 @@ bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID, · 3 unmodified lines | ||
| 887 | 1001 | float radius, int value) { |
| 888 | 1002 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 889 | 1003 | |
| 890 | − return setVoxels(entityID, [start, end, radius, value](PolyVoxEntityItem& polyVoxEntity) { | |
| 1004 | + return polyVoxWorker(entityID, [start, end, radius, value](PolyVoxEntityItem& polyVoxEntity) { | |
| 891 | 1005 | return polyVoxEntity.setCapsule(start, end, radius, value); |
| 892 | 1006 | }); |
| 893 | 1007 | } |
| @@ -895,7 +1009,7 @@ bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID, · 1 unmodified line | ||
| 895 | 1009 | bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) { |
| 896 | 1010 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 897 | 1011 | |
| 898 | − return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) { | |
| 1012 | + return polyVoxWorker(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) { | |
| 899 | 1013 | return polyVoxEntity.setVoxelInVolume(position, value); |
| 900 | 1014 | }); |
| 901 | 1015 | } |
| @@ -903,7 +1017,7 @@ bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& positio · 1 unmodified line | ||
| 903 | 1017 | bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) { |
| 904 | 1018 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 905 | 1019 | |
| 906 | − return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) { | |
| 1020 | + return polyVoxWorker(entityID, [value](PolyVoxEntityItem& polyVoxEntity) { | |
| 907 | 1021 | return polyVoxEntity.setAll(value); |
| 908 | 1022 | }); |
| 909 | 1023 | } |
| @@ -912,11 +1026,30 @@ bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3 · 2 unmodified lines | ||
| 912 | 1026 | const glm::vec3& cuboidSize, int value) { |
| 913 | 1027 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 914 | 1028 | |
| 915 | − return setVoxels(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) { | |
| 1029 | + return polyVoxWorker(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) { | |
| 916 | 1030 | return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value); |
| 917 | 1031 | }); |
| 918 | 1032 | } |
| 919 | 1033 | |
| 1034 | +void EntityScriptingInterface::voxelsToMesh(QUuid entityID, QScriptValue callback) { | |
| 1035 | + PROFILE_RANGE(script_entities, __FUNCTION__); | |
| 1036 | + | |
| 1037 | + bool success { false }; | |
| 1038 | + QScriptValue mesh { false }; | |
| 1039 | + | |
| 1040 | + polyVoxWorker(entityID, [&](PolyVoxEntityItem& polyVoxEntity) mutable { | |
| 1041 | + if (polyVoxEntity.getOnCount() == 0) { | |
| 1042 | + success = true; | |
| 1043 | + } else { | |
| 1044 | + success = polyVoxEntity.getMeshAsScriptValue(callback.engine(), mesh); | |
| 1045 | + } | |
| 1046 | + return true; | |
| 1047 | + }); | |
| 1048 | + | |
| 1049 | + QScriptValueList args { mesh, success }; | |
| 1050 | + callback.call(QScriptValue(), args); | |
| 1051 | +} | |
| 1052 | + | |
| 920 | 1053 | bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) { |
| 921 | 1054 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 922 | 1055 | |
| @@ -1532,3 +1665,20 @@ bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, cons · 609 unmodified lines | ||
| 1532 | 1665 | AABox aaBox(low, dimensions); |
| 1533 | 1666 | return aaBox.findCapsulePenetration(start, end, radius, penetration); |
| 1534 | 1667 | } |
| 1668 | + | |
| 1669 | +glm::mat4 EntityScriptingInterface::getEntityTransform(const QUuid& entityID) { | |
| 1670 | + glm::mat4 result; | |
| 1671 | + if (_entityTree) { | |
| 1672 | + _entityTree->withReadLock([&] { | |
| 1673 | + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); | |
| 1674 | + if (entity) { | |
| 1675 | + glm::mat4 translation = glm::translate(entity->getPosition()); | |
| 1676 | + glm::mat4 rotation = glm::mat4_cast(entity->getRotation()); | |
| 1677 | + glm::mat4 registration = glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - | |
| 1678 | + entity->getRegistrationPoint()); | |
| 1679 | + result = translation * rotation * registration; | |
| 1680 | + } | |
| 1681 | + }); | |
| 1682 | + } | |
| 1683 | + return result; | |
| 1684 | +} | |