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 | // Distributed under the Apache License, Version 2.0. | 8 | // Distributed under the Apache License, Version 2.0. |
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 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 | #include "EntityScriptingInterface.h" | 15 | #include "EntityScriptingInterface.h" |
| 12 | 16 | ||
| 17 | #include <QFutureWatcher> | ||
| 18 | #include <QtConcurrent/QtConcurrentRun> | ||
| 19 | |||
| 13 | #include "EntityItemID.h" | 20 | #include "EntityItemID.h" |
| 14 | #include <VariantMapToScriptValue.h> | 21 | #include <VariantMapToScriptValue.h> |
| 15 | #include <SharedUtil.h> | 22 | #include <SharedUtil.h> |
| @@ -289,13 +296,11 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit · 273 unmodified lines | |||
| 289 | 296 | ||
| 290 | results = entity->getProperties(desiredProperties); | 297 | results = entity->getProperties(desiredProperties); |
| 291 | 298 | ||
| 292 | // TODO: improve sitting points and naturalDimensions in the future, | 299 | // TODO: improve naturalDimensions in the future, |
| 293 | // for now we've included the old sitting points model behavior for entity types that are models | 300 | // for now we've added this hack for setting natural dimensions of models |
| 294 | // we've also added this hack for setting natural dimensions of models | ||
| 295 | if (entity->getType() == EntityTypes::Model) { | 301 | if (entity->getType() == EntityTypes::Model) { |
| 296 | const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity); | 302 | const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity); |
| 297 | if (geometry) { | 303 | if (geometry) { |
| 298 | results.setSittingPoints(geometry->sittingPoints); | ||
| 299 | Extents meshExtents = geometry->getUnscaledMeshExtents(); | 304 | Extents meshExtents = geometry->getUnscaledMeshExtents(); |
| 300 | results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); | 305 | results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); |
| 301 | results.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum); | 306 | results.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum); |
| @@ -679,6 +684,118 @@ bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) { · 377 unmodified lines | |||
| 679 | return client->reloadServerScript(entityID); | 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 | bool EntityScriptingInterface::getServerScriptStatus(QUuid entityID, QScriptValue callback) { | 799 | bool EntityScriptingInterface::getServerScriptStatus(QUuid entityID, QScriptValue callback) { |
| 683 | auto client = DependencyManager::get<EntityScriptClient>(); | 800 | auto client = DependencyManager::get<EntityScriptClient>(); |
| 684 | auto request = client->createScriptStatusRequest(entityID); | 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, | 926 | bool EntityScriptingInterface::polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor) { |
| 810 | std::function<bool(PolyVoxEntityItem&)> actor) { | ||
| 811 | PROFILE_RANGE(script_entities, __FUNCTION__); | 927 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 812 | 928 | ||
| 813 | if (!_entityTree) { | 929 | if (!_entityTree) { |
| @@ -873,11 +989,9 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line · 59 unmodified lines | |||
| 873 | return success; | 989 | return success; |
| 874 | } | 990 | } |
| 875 | 991 | ||
| 876 | |||
| 877 | bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) { | 992 | bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) { |
| 878 | PROFILE_RANGE(script_entities, __FUNCTION__); | 993 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 879 | 994 | return polyVoxWorker(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) { | |
| 880 | return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) { | ||
| 881 | return polyVoxEntity.setSphere(center, radius, value); | 995 | return polyVoxEntity.setSphere(center, radius, value); |
| 882 | }); | 996 | }); |
| 883 | } | 997 | } |
| @@ -887,7 +1001,7 @@ bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID, · 3 unmodified lines | |||
| 887 | float radius, int value) { | 1001 | float radius, int value) { |
| 888 | PROFILE_RANGE(script_entities, __FUNCTION__); | 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 | return polyVoxEntity.setCapsule(start, end, radius, value); | 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 | bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) { | 1009 | bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) { |
| 896 | PROFILE_RANGE(script_entities, __FUNCTION__); | 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 | return polyVoxEntity.setVoxelInVolume(position, value); | 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 | bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) { | 1017 | bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) { |
| 904 | PROFILE_RANGE(script_entities, __FUNCTION__); | 1018 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 905 | 1019 | ||
| 906 | return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) { | 1020 | return polyVoxWorker(entityID, [value](PolyVoxEntityItem& polyVoxEntity) { |
| 907 | return polyVoxEntity.setAll(value); | 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 | const glm::vec3& cuboidSize, int value) { | 1026 | const glm::vec3& cuboidSize, int value) { |
| 913 | PROFILE_RANGE(script_entities, __FUNCTION__); | 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 | return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value); | 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 | bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) { | 1053 | bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) { |
| 921 | PROFILE_RANGE(script_entities, __FUNCTION__); | 1054 | PROFILE_RANGE(script_entities, __FUNCTION__); |
| 922 | 1055 | ||
| @@ -1532,3 +1665,20 @@ bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, cons · 609 unmodified lines | |||
| 1532 | AABox aaBox(low, dimensions); | 1665 | AABox aaBox(low, dimensions); |
| 1533 | return aaBox.findCapsulePenetration(start, end, radius, penetration); | 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 | } | ||