diff --git a/examples/billiards.js b/examples/billiards.js new file mode 100644 index 0000000000..9bc21ff611 --- /dev/null +++ b/examples/billiards.js @@ -0,0 +1,197 @@ +// Pool Table +var tableParts = []; +var balls = []; + +var LENGTH = 2.84; +var WIDTH = 1.42; +var HEIGHT = 0.80; +var SCALE = 2.0; +var BALL_SIZE = 0.05715; +var BUMPER_WIDTH = 0.15; +var BUMPER_HEIGHT = BALL_SIZE * 2.0; +var HOLE_SIZE = BALL_SIZE; +var DROP_HEIGHT = BALL_SIZE * 3.0; +var GRAVITY = -9.8; +var BALL_GAP = 0.001; + +var startStroke = 0; + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var screenSize = Controller.getViewportDimensions(); +var reticle = Overlays.addOverlay("image", { + x: screenSize.x / 2 - 16, + y: screenSize.y / 2 - 16, + width: 32, + height: 32, + imageURL: HIFI_PUBLIC_BUCKET + "images/reticle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + +function makeTable(pos) { + // Top + tableParts.push(Entities.addEntity( + { type: "Box", + position: pos, + dimensions: { x: LENGTH * SCALE, y: HEIGHT, z: WIDTH * SCALE }, + color: { red: 0, green: 255, blue: 0 } })); + // Long Bumpers + tableParts.push(Entities.addEntity( + { type: "Box", + position: { x: pos.x - LENGTH / 2.0, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); + tableParts.push(Entities.addEntity( + { type: "Box", + position: { x: pos.x + LENGTH / 2.0, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); + + tableParts.push(Entities.addEntity( + { type: "Box", + position: { x: pos.x - LENGTH / 2.0, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); + tableParts.push(Entities.addEntity( + { type: "Box", + position: { x: pos.x + LENGTH / 2.0, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, + dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); + // End bumpers + tableParts.push(Entities.addEntity( + { type: "Box", + position: { x: pos.x + (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z }, + dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); + + tableParts.push(Entities.addEntity( + { type: "Box", + position: { x: pos.x - (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, + y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), + z: pos.z }, + dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, + color: { red: 237, green: 201, blue: 175 } })); + +} + +function makeBalls(pos) { + var colors = [{ red: 255, green: 255, blue: 0}, // Yellow + { red: 0, green: 0, blue: 255}, // Blue + { red: 255, green: 0, blue: 0}, // Red + { red: 128, green: 0, blue: 128}, // Purple + { red: 255, green: 165, blue: 0}, // Orange + { red: 0, green: 255, blue: 0}, // Green + { red: 128, green: 0, blue: 0}, // Maroon + { red: 0, green: 0, blue: 0}, // Black + { red: 255, green: 255, blue: 224}, // Light Yellow + { red: 173, green: 216, blue: 230}, // Light Blue + { red: 205, green: 92, blue: 92}, // Indian Red + { red: 218, green: 112, blue: 214}, // Orchid + { red: 218, green: 165, blue: 32}, // GoldenRod + { red: 255, green: 99, blue: 71}, // Tomato + { red: 128, green: 128, blue: 128}]; // Gray + + // Object balls + var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + DROP_HEIGHT, z: pos.z }; + for (var row = 1; row <= 5; row++) { + ballPosition.z = pos.z - ((row - 1.0) / 2.0 * (BALL_SIZE + BALL_GAP) * SCALE); + for (var spot = 0; spot < row; spot++) { + balls.push(Entities.addEntity( + { type: "Sphere", + position: ballPosition, + dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, + color: colors[balls.length], + gravity: { x: 0, y: GRAVITY, z: 0 }, + ignoreCollisions: false, + damping: 0.40, + collisionsWillMove: true })); + ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE; + } + ballPosition.x += (BALL_GAP + Math.sqrt(3.0) / 2.0 * BALL_SIZE) * SCALE; + } + // Cue Ball + ballPosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + DROP_HEIGHT, z: pos.z }; + balls.push(Entities.addEntity( + { type: "Sphere", + position: ballPosition, + dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, + color: { red: 255, green: 255, blue: 255 }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + ignoreCollisions: false, + damping: 0.40, + collisionsWillMove: true })); +} + +function shootCue(velocity) { + var DISTANCE_FROM_CAMERA = BALL_SIZE * 5.0 * SCALE; + var camera = Camera.getPosition(); + var forwardVector = Quat.getFront(Camera.getOrientation()); + var cuePosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA)); + var velocity = Vec3.multiply(forwardVector, velocity); + var BULLET_LIFETIME = 3.0; + var BULLET_GRAVITY = 0.0; + bulletID = Entities.addEntity( + { type: "Sphere", + position: cuePosition, + dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, + color: { red: 255, green: 255, blue: 255 }, + velocity: velocity, + lifetime: BULLET_LIFETIME, + gravity: { x: 0, y: BULLET_GRAVITY, z: 0 }, + damping: 0.10, + density: 1000, + ignoreCollisions: false, + collisionsWillMove: true + }); +} + +function keyReleaseEvent(event) { + if ((startStroke > 0) && event.text == "SPACE") { + var endTime = new Date().getTime(); + var delta = endTime - startStroke; + shootCue(delta / 100.0); + startStroke = 0; + } +} + +function keyPressEvent(event) { + // Fire a cue ball + if ((startStroke == 0) && (event.text == "SPACE")) { + startStroke = new Date().getTime(); + } +} + +function cleanup() { + for (var i = 0; i < tableParts.length; i++) { + if (!tableParts[i].isKnownID) { + tableParts[i] = Entities.identifyEntity(tableParts[i]); + } + Entities.deleteEntity(tableParts[i]); + } + for (var i = 0; i < balls.length; i++) { + if (!balls[i].isKnownID) { + balls[i] = Entities.identifyEntity(balls[i]); + } + Entities.deleteEntity(balls[i]); + } + Overlays.deleteOverlay(reticle); +} + +var tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation()))); + +makeTable(tableCenter); +makeBalls(tableCenter); + +Script.scriptEnding.connect(cleanup); +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 8f9f54c591..b08b429ad0 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -220,7 +220,12 @@ unsigned int Overlays::cloneOverlay(unsigned int id) { } else if (_overlaysWorld.contains(id)) { thisOverlay = _overlaysWorld[id]; } - return addOverlay(thisOverlay->createClone()); + + if (thisOverlay) { + return addOverlay(thisOverlay->createClone()); + } + + return 0; // Not found } bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) { diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 0c63ecc731..c4a4c4a458 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -158,7 +158,8 @@ void AccountManager::setAuthURL(const QUrl& authURL) { void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, - QHttpMultiPart* dataMultiPart) { + QHttpMultiPart* dataMultiPart, + const QVariantMap& propertyMap) { QMetaObject::invokeMethod(this, "invokedRequest", Q_ARG(const QString&, path), @@ -166,13 +167,15 @@ void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessMan Q_ARG(QNetworkAccessManager::Operation, operation), Q_ARG(const JSONCallbackParameters&, callbackParams), Q_ARG(const QByteArray&, dataByteArray), - Q_ARG(QHttpMultiPart*, dataMultiPart)); + Q_ARG(QHttpMultiPart*, dataMultiPart), + Q_ARG(QVariantMap, propertyMap)); } void AccountManager::unauthenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation, - const JSONCallbackParameters& callbackParams, - const QByteArray& dataByteArray, - QHttpMultiPart* dataMultiPart) { + const JSONCallbackParameters& callbackParams, + const QByteArray& dataByteArray, + QHttpMultiPart* dataMultiPart, + const QVariantMap& propertyMap) { QMetaObject::invokeMethod(this, "invokedRequest", Q_ARG(const QString&, path), @@ -180,14 +183,16 @@ void AccountManager::unauthenticatedRequest(const QString& path, QNetworkAccessM Q_ARG(QNetworkAccessManager::Operation, operation), Q_ARG(const JSONCallbackParameters&, callbackParams), Q_ARG(const QByteArray&, dataByteArray), - Q_ARG(QHttpMultiPart*, dataMultiPart)); + Q_ARG(QHttpMultiPart*, dataMultiPart), + Q_ARG(QVariantMap, propertyMap)); } void AccountManager::invokedRequest(const QString& path, bool requiresAuthentication, QNetworkAccessManager::Operation operation, const JSONCallbackParameters& callbackParams, - const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) { + const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart, + const QVariantMap& propertyMap) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); @@ -235,7 +240,9 @@ void AccountManager::invokedRequest(const QString& path, } else { networkReply = networkAccessManager.put(networkRequest, dataMultiPart); } - dataMultiPart->setParent(networkReply); + + // make sure dataMultiPart is destroyed when the reply is + connect(networkReply, &QNetworkReply::destroyed, dataMultiPart, &QHttpMultiPart::deleteLater); } else { networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); if (operation == QNetworkAccessManager::PostOperation) { @@ -255,6 +262,14 @@ void AccountManager::invokedRequest(const QString& path, } if (networkReply) { + if (!propertyMap.isEmpty()) { + // we have properties to set on the reply so the user can check them after + foreach(const QString& propertyKey, propertyMap.keys()) { + networkReply->setProperty(qPrintable(propertyKey), propertyMap.value(propertyKey)); + } + } + + if (!callbackParams.isEmpty()) { // if we have information for a callback, insert the callbackParams into our local map _pendingCallbackMap.insert(networkReply, callbackParams); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 53512a8bb3..06fe366d69 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -48,13 +48,15 @@ public: QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), const QByteArray& dataByteArray = QByteArray(), - QHttpMultiPart* dataMultiPart = NULL); + QHttpMultiPart* dataMultiPart = NULL, + const QVariantMap& propertyMap = QVariantMap()); void unauthenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), const QByteArray& dataByteArray = QByteArray(), - QHttpMultiPart* dataMultiPart = NULL); + QHttpMultiPart* dataMultiPart = NULL, + const QVariantMap& propertyMap = QVariantMap()) ; const QUrl& getAuthURL() const { return _authURL; } void setAuthURL(const QUrl& authURL); @@ -109,7 +111,8 @@ private: QNetworkAccessManager::Operation operation, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, - QHttpMultiPart* dataMultiPart); + QHttpMultiPart* dataMultiPart, + const QVariantMap& propertyMap); QUrl _authURL; QMap _pendingCallbackMap; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index fba3861f43..ff2925beea 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -119,14 +119,16 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { if (!handleUsername(lookupUrl.authority())) { // we're assuming this is either a network address or global place name // check if it is a network address first - if (!handleNetworkAddress(lookupUrl.host() + if (handleNetworkAddress(lookupUrl.host() + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())))) { + // we may have a path that defines a relative viewpoint - if so we should jump to that now + handleRelativeViewpoint(lookupUrl.path()); + } else { // wasn't an address - lookup the place name - attemptPlaceNameLookup(lookupUrl.host()); + // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after + attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path()); + } - - // we may have a path that defines a relative viewpoint - if so we should jump to that now - handleRelativeViewpoint(lookupUrl.path()); } return true; @@ -164,12 +166,14 @@ void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); - goToAddressFromObject(dataObject.toVariantMap()); + goToAddressFromObject(dataObject.toVariantMap(), requestReply); emit lookupResultsFinished(); } -void AddressManager::goToAddressFromObject(const QVariantMap& dataObject) { +const char OVERRIDE_PATH_KEY[] = "override_path"; + +void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply& reply) { const QString DATA_OBJECT_PLACE_KEY = "place"; const QString DATA_OBJECT_USER_LOCATION_KEY = "location"; @@ -203,6 +207,8 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject) { if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) { QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); + qDebug() << "Possible domain change required to connect to" << domainHostname + << "on" << DEFAULT_DOMAIN_SERVER_PORT; emit possibleDomainChangeRequired(domainHostname, DEFAULT_DOMAIN_SERVER_PORT); } else { QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); @@ -211,6 +217,9 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject) { QString domainIDString = domainObject[DOMAIN_ID_KEY].toString(); QUuid domainID(domainIDString); + qDebug() << "Possible domain change required to connect to domain with ID" << domainID + << "via ice-server at" << iceServerAddress; + emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID); } @@ -223,18 +232,29 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject) { QString newRootPlaceName = rootMap[PLACE_NAME_KEY].toString(); setRootPlaceName(newRootPlaceName); - // take the path that came back - const QString PLACE_PATH_KEY = "path"; - QString returnedPath = locationMap[PLACE_PATH_KEY].toString(); + // check if we had a path to override the path returned + QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString(); - bool shouldFaceViewpoint = locationMap.contains(LOCATION_API_ONLINE_KEY); - - if (!returnedPath.isEmpty()) { - // try to parse this returned path as a viewpoint, that's the only thing it could be for now - if (!handleRelativeViewpoint(returnedPath, shouldFaceViewpoint)) { - qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath; + if (!overridePath.isEmpty()) { + if (!handleRelativeViewpoint(overridePath)){ + qDebug() << "User entered path could not be handled as a viewpoint - " << overridePath; + } + } else { + // take the path that came back + const QString PLACE_PATH_KEY = "path"; + QString returnedPath = locationMap[PLACE_PATH_KEY].toString(); + + bool shouldFaceViewpoint = locationMap.contains(LOCATION_API_ONLINE_KEY); + + if (!returnedPath.isEmpty()) { + // try to parse this returned path as a viewpoint, that's the only thing it could be for now + if (!handleRelativeViewpoint(returnedPath, shouldFaceViewpoint)) { + qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath; + } } } + + } else { qDebug() << "Received an address manager API response with no domain key. Cannot parse."; qDebug() << locationMap; @@ -260,12 +280,21 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) { const QString GET_PLACE = "/api/v1/places/%1"; -void AddressManager::attemptPlaceNameLookup(const QString& lookupString) { +void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath) { // assume this is a place name and see if we can get any info on it QString placeName = QUrl::toPercentEncoding(lookupString); + + QVariantMap requestParams; + if (!overridePath.isEmpty()) { + requestParams.insert(OVERRIDE_PATH_KEY, overridePath); + } + AccountManager::getInstance().unauthenticatedRequest(GET_PLACE.arg(placeName), QNetworkAccessManager::GetOperation, - apiCallbackParameters()); + apiCallbackParameters(), + QByteArray(), + NULL, + requestParams); } bool AddressManager::handleNetworkAddress(const QString& lookupString) { diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 58e4e88330..3071d68ea2 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -47,7 +47,7 @@ public: const QString& getRootPlaceName() const { return _rootPlaceName; } void setRootPlaceName(const QString& rootPlaceName); - void attemptPlaceNameLookup(const QString& lookupString); + void attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath = QString()); void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; } void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; } @@ -57,7 +57,7 @@ public: public slots: void handleLookupString(const QString& lookupString); void goToUser(const QString& username); - void goToAddressFromObject(const QVariantMap& addressMap); + void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); void storeCurrentAddress();