// // Created by Sam Gondelman 10/20/2017 // 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 "PointerScriptingInterface.h" #include #include #include #include "Application.h" #include "LaserPointer.h" #include "StylusPointer.h" #include "ParabolaPointer.h" void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); } void PointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const { DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) { // Interaction with managers should always happen on the main thread if (QThread::currentThread() != qApp->thread()) { unsigned int result; BLOCKING_INVOKE_METHOD(this, "createPointer", Q_RETURN_ARG(unsigned int, result), Q_ARG(PickQuery::PickType, type), Q_ARG(QVariant, properties)); return result; } switch (type) { case PickQuery::PickType::Ray: return createLaserPointer(properties); case PickQuery::PickType::Stylus: return createStylus(properties); case PickQuery::PickType::Parabola: return createParabolaPointer(properties); default: return PointerEvent::INVALID_POINTER_ID; } } /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. * @typedef {object} Pointers.StylusPointerProperties * @property {boolean} [hover=false] If this pointer should generate hover events. * @property {boolean} [enabled=false] */ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); bool hover = false; if (propertyMap["hover"].isValid()) { hover = propertyMap["hover"].toBool(); } bool enabled = false; if (propertyMap["enabled"].isValid()) { enabled = propertyMap["enabled"].toBool(); } return DependencyManager::get()->addPointer(std::make_shared(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled)); } /**jsdoc * A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.RayPointerRenderState}, * but with an additional distance field. * * @typedef {object} Pointers.DefaultRayPointerRenderState * @augments Pointers.RayPointerRenderState * @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined. */ /**jsdoc * A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something. * * @typedef {object} Pointers.RayPointerRenderState * @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} * @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). * An overlay to represent the beginning of the Ray Pointer, if desired. * @property {Overlays.OverlayProperties} [path] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field), which must be "line3d". * An overlay to represent the path of the Ray Pointer, if desired. * @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). * An overlay to represent the end of the Ray Pointer, if desired. */ /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. * @typedef {object} Pointers.LaserPointerProperties * @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. * @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. * @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the pointer is pointing. * @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. * @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. * @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. * @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, * the normal will follow exactly. * @property {boolean} [enabled=false] * @property {Pointers.RayPointerRenderState[]} [renderStates] A list of different visual states to switch between. * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. * @property {boolean} [hover=false] If this Pointer should generate hover events. * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. */ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); bool faceAvatar = false; if (propertyMap["faceAvatar"].isValid()) { faceAvatar = propertyMap["faceAvatar"].toBool(); } bool centerEndY = true; if (propertyMap["centerEndY"].isValid()) { centerEndY = propertyMap["centerEndY"].toBool(); } bool lockEnd = false; if (propertyMap["lockEnd"].isValid()) { lockEnd = propertyMap["lockEnd"].toBool(); } bool distanceScaleEnd = false; if (propertyMap["distanceScaleEnd"].isValid()) { distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool(); } bool scaleWithAvatar = false; if (propertyMap["scaleWithAvatar"].isValid()) { scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); } bool followNormal = false; if (propertyMap["followNormal"].isValid()) { followNormal = propertyMap["followNormal"].toBool(); } float followNormalStrength = 0.0f; if (propertyMap["followNormalStrength"].isValid()) { followNormalStrength = propertyMap["followNormalStrength"].toFloat(); } bool enabled = false; if (propertyMap["enabled"].isValid()) { enabled = propertyMap["enabled"].toBool(); } RenderStateMap renderStates; if (propertyMap["renderStates"].isValid()) { QList renderStateVariants = propertyMap["renderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { if (renderStateVariant.isValid()) { QVariantMap renderStateMap = renderStateVariant.toMap(); if (renderStateMap["name"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); renderStates[name] = LaserPointer::buildRenderState(renderStateMap); } } } } DefaultRenderStateMap defaultRenderStates; if (propertyMap["defaultRenderStates"].isValid()) { QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { if (renderStateVariant.isValid()) { QVariantMap renderStateMap = renderStateVariant.toMap(); if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); float distance = renderStateMap["distance"].toFloat(); defaultRenderStates[name] = std::pair>(distance, LaserPointer::buildRenderState(renderStateMap)); } } } } bool hover = false; if (propertyMap["hover"].isValid()) { hover = propertyMap["hover"].toBool(); } PointerTriggers triggers; auto userInputMapper = DependencyManager::get(); if (propertyMap["triggers"].isValid()) { QList triggerVariants = propertyMap["triggers"].toList(); for (const QVariant& triggerVariant : triggerVariants) { if (triggerVariant.isValid()) { QVariantMap triggerMap = triggerVariant.toMap(); if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) { controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt())); if (endpoint) { std::string button = triggerMap["button"].toString().toStdString(); triggers.emplace_back(endpoint, button); } } } } } return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)); } /**jsdoc * The rendering properties of the parabolic path * * @typedef {object} Pointers.ParabolaProperties * @property {Color} color=255,255,255 The color of the parabola. * @property {number} alpha=1.0 The alpha of the parabola. * @property {number} width=0.01 The width of the parabola, in meters. * @property {boolean} isVisibleInSecondaryCamera=false The width of the parabola, in meters. */ /**jsdoc * A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState}, * but with an additional distance field. * * @typedef {object} Pointers.DefaultParabolaPointerRenderState * @augments Pointers.ParabolaPointerRenderState * @property {number} distance The distance along the parabola at which to render the end of this Parabola Pointer, if one is defined. */ /**jsdoc * A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is intersecting something. * * @typedef {object} Pointers.ParabolaPointerRenderState * @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} * @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). * An overlay to represent the beginning of the Parabola Pointer, if desired. * @property {Pointers.ParabolaProperties} [path] The rendering properties of the parabolic path defined by the Parabola Pointer. * @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). * An overlay to represent the end of the Parabola Pointer, if desired. */ /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. * @typedef {object} Pointers.ParabolaPointerProperties * @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. * @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. * @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the pointer is pointing. * @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. * @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. * @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. * @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, * the normal will follow exactly. * @property {boolean} [enabled=false] * @property {Pointers.ParabolaPointerRenderState[]} [renderStates] A list of different visual states to switch between. * @property {Pointers.DefaultParabolaPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. * @property {boolean} [hover=false] If this Pointer should generate hover events. * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. */ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); bool faceAvatar = false; if (propertyMap["faceAvatar"].isValid()) { faceAvatar = propertyMap["faceAvatar"].toBool(); } bool centerEndY = true; if (propertyMap["centerEndY"].isValid()) { centerEndY = propertyMap["centerEndY"].toBool(); } bool lockEnd = false; if (propertyMap["lockEnd"].isValid()) { lockEnd = propertyMap["lockEnd"].toBool(); } bool distanceScaleEnd = false; if (propertyMap["distanceScaleEnd"].isValid()) { distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool(); } bool scaleWithAvatar = false; if (propertyMap["scaleWithAvatar"].isValid()) { scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); } bool followNormal = false; if (propertyMap["followNormal"].isValid()) { followNormal = propertyMap["followNormal"].toBool(); } float followNormalStrength = 0.0f; if (propertyMap["followNormalStrength"].isValid()) { followNormalStrength = propertyMap["followNormalStrength"].toFloat(); } bool enabled = false; if (propertyMap["enabled"].isValid()) { enabled = propertyMap["enabled"].toBool(); } RenderStateMap renderStates; if (propertyMap["renderStates"].isValid()) { QList renderStateVariants = propertyMap["renderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { if (renderStateVariant.isValid()) { QVariantMap renderStateMap = renderStateVariant.toMap(); if (renderStateMap["name"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); renderStates[name] = ParabolaPointer::buildRenderState(renderStateMap); } } } } DefaultRenderStateMap defaultRenderStates; if (propertyMap["defaultRenderStates"].isValid()) { QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { if (renderStateVariant.isValid()) { QVariantMap renderStateMap = renderStateVariant.toMap(); if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); float distance = renderStateMap["distance"].toFloat(); defaultRenderStates[name] = std::pair>(distance, ParabolaPointer::buildRenderState(renderStateMap)); } } } } bool hover = false; if (propertyMap["hover"].isValid()) { hover = propertyMap["hover"].toBool(); } PointerTriggers triggers; auto userInputMapper = DependencyManager::get(); if (propertyMap["triggers"].isValid()) { QList triggerVariants = propertyMap["triggers"].toList(); for (const QVariant& triggerVariant : triggerVariants) { if (triggerVariant.isValid()) { QVariantMap triggerMap = triggerVariant.toMap(); if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) { controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt())); if (endpoint) { std::string button = triggerMap["button"].toString().toStdString(); triggers.emplace_back(endpoint, button); } } } } } return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)); } void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const { QVariantMap propMap = properties.toMap(); QVariant startProps; if (propMap["start"].isValid()) { startProps = propMap["start"]; } QVariant pathProps; if (propMap["path"].isValid()) { pathProps = propMap["path"]; } QVariant endProps; if (propMap["end"].isValid()) { endProps = propMap["end"]; } DependencyManager::get()->editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps); } QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const { QVariantMap result; auto pickResult = DependencyManager::get()->getPrevPickResult(uid); if (pickResult) { result = pickResult->toVariantMap(); } return result; }