diff --git a/examples/editModels.js b/examples/editModels.js index f2b76a28c3..64c203534c 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -752,7 +752,16 @@ function Tooltip() { text += "ID: " + properties.id + "\n" text += "model url: " + properties.modelURL + "\n" text += "animation url: " + properties.animationURL + "\n" - + if (properties.sittingPoints.length > 0) { + text += properties.sittingPoints.length + " sitting points: " + for (var i = 0; i < properties.sittingPoints.length; ++i) { + text += properties.sittingPoints[i].name + " " + } + } else { + text += "No sitting points" + } + + Overlays.editOverlay(this.textOverlay, { text: text }); } diff --git a/examples/sit.js b/examples/sit.js index 77a626c63f..57828afc52 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -38,9 +38,15 @@ var standUpButton = Overlays.addOverlay("image", { var passedTime = 0.0; var startPosition = null; +var startRotation = null; var animationLenght = 2.0; -var sitting = false; +var avatarOldPosition = { x: 0, y: 0, z: 0 }; + +var sitting = false; + +var seat = new Object(); +var hiddingSeats = false; // This is the pose we would like to end up var pose = [ @@ -83,7 +89,7 @@ var sittingDownAnimation = function(deltaTime) { } } -var standingUpAnimation = function(deltaTime){ +var standingUpAnimation = function(deltaTime) { passedTime += deltaTime; var factor = 1 - passedTime/animationLenght; @@ -97,6 +103,24 @@ var standingUpAnimation = function(deltaTime){ } } +var goToSeatAnimation = function(deltaTime) { + passedTime += deltaTime; + var factor = passedTime/animationLenght; + + if (passedTime <= animationLenght) { + var targetPosition = Vec3.sum(seat.position, { x: 0.3, y: 0.5, z: 0 }); + MyAvatar.position = Vec3.sum(Vec3.multiply(startPosition, 1 - factor), Vec3.multiply(targetPosition, factor)); + } else if (passedTime <= 2 * animationLenght) { + Quat.print("MyAvatar: ", MyAvatar.orientation); + Quat.print("Seat: ", seat.rotation); + MyAvatar.orientation = Quat.mix(startRotation, seat.rotation, factor - 1); + } else { + Script.update.disconnect(goToSeatAnimation); + sitDown(); + showIndicators(false); + } +} + function sitDown() { sitting = true; passedTime = 0.0; @@ -124,15 +148,104 @@ function standUp() { Overlays.editOverlay(sitDownButton, { visible: true }); } -Controller.mousePressEvent.connect(function(event){ +var models = new Object(); +function SeatIndicator(modelProperties, seatIndex) { + this.position = Vec3.sum(modelProperties.position, + Vec3.multiply(Vec3.multiplyQbyV(modelProperties.modelRotation, + modelProperties.sittingPoints[seatIndex].position), + modelProperties.radius)); + + this.orientation = Quat.multiply(modelProperties.modelRotation, + modelProperties.sittingPoints[seatIndex].rotation); + this.scale = MyAvatar.scale / 12; + + this.sphere = Overlays.addOverlay("sphere", { + position: this.position, + size: this.scale, + solid: true, + color: { red: 0, green: 0, blue: 255 }, + alpha: 1, + visible: true + }); + + this.show = function(doShow) { + Overlays.editOverlay(this.sphere, { visible: doShow }); + } + + this.update = function() { + Overlays.editOverlay(this.sphere, { + position: this.position, + size: this.scale + }); + } + + this.cleanup = function() { + Overlays.deleteOverlay(this.sphere); + } +} +Controller.mousePressEvent.connect(function(event) { var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); if (clickedOverlay == sitDownButton) { sitDown(); } else if (clickedOverlay == standUpButton) { standUp(); - } + } else { + var pickRay = Camera.computePickRay(event.x, event.y); + + var clickedOnSeat = false; + + for (index in models) { + var model = models[index]; + + for (var i = 0; i < model.properties.sittingPoints.length; ++i) { + if (raySphereIntersection(pickRay.origin, + pickRay.direction, + model.properties.sittingPoints[i].indicator.position, + model.properties.sittingPoints[i].indicator.scale / 2)) { + clickedOnSeat = true; + seat.position = model.properties.sittingPoints[i].indicator.position; + seat.rotation = model.properties.sittingPoints[i].indicator.orientation; + } + } + } + if (clickedOnSeat) { + passedTime = 0.0; + startPosition = MyAvatar.position; + startRotation = MyAvatar.orientation; + try{ Script.update.disconnect(standingUpAnimation); } catch(e){} + try{ Script.update.disconnect(sittingDownAnimation); } catch(e){} + Script.update.connect(goToSeatAnimation); + } + + + + return; + var intersection = Models.findRayIntersection(pickRay); + + if (intersection.accurate && intersection.intersects && false) { + var properties = intersection.modelProperties; + print("Intersecting with model, let's check for seats."); + + if (properties.sittingPoints.length > 0) { + print("Available seats, going to the first one: " + properties.sittingPoints[0].name); + seat.position = Vec3.sum(properties.position, Vec3.multiplyQbyV(properties.modelRotation, properties.sittingPoints[0].position)); + Vec3.print("Seat position: ", seat.position); + seat.rotation = Quat.multiply(properties.modelRotation, properties.sittingPoints[0].rotation); + Quat.print("Seat rotation: ", seat.rotation); + + passedTime = 0.0; + startPosition = MyAvatar.position; + startRotation = MyAvatar.orientation; + try{ Script.update.disconnect(standingUpAnimation); } catch(e){} + try{ Script.update.disconnect(sittingDownAnimation); } catch(e){} + Script.update.connect(goToSeatAnimation); + } else { + print ("Sorry, no seats here."); + } + } + } }) function update(deltaTime){ @@ -143,7 +256,76 @@ function update(deltaTime){ var newY = (windowDimensions.y - buttonHeight) / 2 ; Overlays.editOverlay( standUpButton, {x: newX, y: newY} ); Overlays.editOverlay( sitDownButton, {x: newX, y: newY} ); - } + } + + if (MyAvatar.position.x != avatarOldPosition.x && + MyAvatar.position.y != avatarOldPosition.y && + MyAvatar.position.z != avatarOldPosition.z) { + avatarOldPosition = MyAvatar.position; + + var SEARCH_RADIUS = 5; + var foundModels = Models.findModels(MyAvatar.position, SEARCH_RADIUS); + // Let's remove indicator that got out of radius + for (model in models) { + if (Vec3.distance(models[model].properties.position, MyAvatar.position) > SEARCH_RADIUS) { + removeIndicators(models[model]); + } + } + + // Let's add indicators to new seats in radius + for (var i = 0; i < foundModels.length; ++i) { + var model = foundModels[i]; + if (typeof(models[model.id]) == "undefined") { + addIndicators(model); + } + } + + if (hiddingSeats && passedTime >= animationLenght) { + showIndicators(true); + } + } +} + +function addIndicators(modelID) { + modelID.properties = Models.getModelProperties(modelID); + if (modelID.properties.sittingPoints.length > 0) { + for (var i = 0; i < modelID.properties.sittingPoints.length; ++i) { + modelID.properties.sittingPoints[i].indicator = new SeatIndicator(modelID.properties, i); + } + + models[modelID.id] = modelID; + } else { + Models.editModel(modelID, { glowLevel: 0.0 }); + } +} + +function removeIndicators(modelID) { + for (var i = 0; i < modelID.properties.sittingPoints.length; ++i) { + modelID.properties.sittingPoints[i].indicator.cleanup(); + } + delete models[modelID.id]; +} + +function showIndicators(doShow) { + for (model in models) { + var modelID = models[model]; + for (var i = 0; i < modelID.properties.sittingPoints.length; ++i) { + modelID.properties.sittingPoints[i].indicator.show(doShow); + } + } + hiddingSeats = !doShow; +} + +function raySphereIntersection(origin, direction, center, radius) { + var A = origin; + var B = Vec3.normalize(direction); + var P = center; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + + return (x > 0 && d <= radius); } function keyPressEvent(event) { @@ -161,11 +343,15 @@ Script.update.connect(update); Controller.keyPressEvent.connect(keyPressEvent); Script.scriptEnding.connect(function() { - for (var i = 0; i < pose.length; i++){ MyAvatar.clearJointData(pose[i].joint); - } + } Overlays.deleteOverlay(sitDownButton); Overlays.deleteOverlay(standUpButton); + for (model in models){ + for (var i = 0; i < models[model].properties.sittingPoints.length; ++i) { + models[model].properties.sittingPoints[i].indicator.cleanup(); + } + } }); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 33b7fca0ef..56fb566d6a 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1897,7 +1897,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } geometry.attachments.append(attachment); } - + + // Add sitting points + QVariantHash sittingPoints = mapping.value("sit").toHash(); + for (QVariantHash::const_iterator it = sittingPoints.constBegin(); it != sittingPoints.constEnd(); it++) { + SittingPoint sittingPoint; + sittingPoint.name = it.key(); + + QVariantList properties = it->toList(); + sittingPoint.position = parseVec3(properties.at(0).toString()); + sittingPoint.rotation = glm::quat(glm::radians(parseVec3(properties.at(1).toString()))); + + geometry.sittingPoints.append(sittingPoint); + } + return geometry; } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 4c93f3dc5e..c336252574 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -182,6 +182,14 @@ public: glm::vec3 scale; }; +/// A point where an avatar can sit +class SittingPoint { +public: + QString name; + glm::vec3 position; // relative postion + glm::quat rotation; // relative orientation +}; + /// A set of meshes extracted from an FBX document. class FBXGeometry { public: @@ -209,6 +217,8 @@ public: glm::vec3 palmDirection; + QVector sittingPoints; + glm::vec3 neckPivot; Extents bindExtents; diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp index b6f4fe6c1d..8c061102a0 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -886,7 +886,19 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const properties.setProperty("shouldDie", _shouldDie); properties.setProperty("modelURL", _modelURL); - + + + QScriptValue sittingPoints = engine->newObject(); + for (int i = 0; i < _sittingPoints.size(); ++i) { + QScriptValue sittingPoint = engine->newObject(); + sittingPoint.setProperty("name", _sittingPoints[i].name); + sittingPoint.setProperty("position", vec3toScriptValue(engine, _sittingPoints[i].position)); + sittingPoint.setProperty("rotation", quatToScriptValue(engine, _sittingPoints[i].rotation)); + sittingPoints.setProperty(i, sittingPoint); + } + sittingPoints.setProperty("length", _sittingPoints.size()); + properties.setProperty("sittingPoints", sittingPoints); + QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation); properties.setProperty("modelRotation", modelRotation); @@ -971,7 +983,7 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) { _modelURLChanged = true; } } - + QScriptValue modelRotation = object.property("modelRotation"); if (modelRotation.isValid()) { QScriptValue x = modelRotation.property("x"); @@ -1125,6 +1137,7 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _animationFrameIndex = modelItem.getAnimationFrameIndex(); _animationFPS = modelItem.getAnimationFPS(); _glowLevel = modelItem.getGlowLevel(); + _sittingPoints = modelItem.getSittingPoints(); _id = modelItem.getID(); _idSet = true; diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index cd191846d5..43aaca48a0 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -23,6 +23,7 @@ #include #include #include +#include class ModelItem; @@ -124,7 +125,8 @@ private: float _animationFrameIndex; float _animationFPS; float _glowLevel; - + QVector _sittingPoints; + uint32_t _id; bool _idSet; quint64 _lastEdited; @@ -213,6 +215,7 @@ public: bool hasAnimation() const { return !_animationURL.isEmpty(); } const QString& getAnimationURL() const { return _animationURL; } float getGlowLevel() const { return _glowLevel; } + QVector getSittingPoints() const { return _sittingPoints; } ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); } ModelItemProperties getProperties() const; @@ -256,6 +259,7 @@ public: void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; } void setAnimationFPS(float value) { _animationFPS = value; } void setGlowLevel(float glowLevel) { _glowLevel = glowLevel; } + void setSittingPoints(QVector sittingPoints) { _sittingPoints = sittingPoints; } void setProperties(const ModelItemProperties& properties); @@ -302,6 +306,8 @@ protected: QString _modelURL; glm::quat _modelRotation; + QVector _sittingPoints; + float _glowLevel; uint32_t _creatorTokenID; diff --git a/libraries/models/src/ModelTree.cpp b/libraries/models/src/ModelTree.cpp index 466d4c5273..763f0a969e 100644 --- a/libraries/models/src/ModelTree.cpp +++ b/libraries/models/src/ModelTree.cpp @@ -117,7 +117,7 @@ void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& send // if we didn't find it in the tree, then store it... if (!theOperator.wasFound()) { AACube modelCube = model.getAACube(); - ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementContaining(model.getAACube()); + ModelTreeElement* element = static_cast(getOrCreateChildElementContaining(model.getAACube())); element->storeModel(model); // In the case where we stored it, we also need to mark the entire "path" down to the model as diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index 75b9670d0f..960d1dd4cb 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -330,6 +330,9 @@ bool ModelTreeElement::updateModel(const ModelItemID& modelID, const ModelItemPr } if (found) { thisModel.setProperties(properties); + if (_myTree->getGeometryForModel(thisModel)) { + thisModel.setSittingPoints(_myTree->getGeometryForModel(thisModel)->sittingPoints); + } markWithChangedTime(); // mark our element as changed.. const bool wantDebug = false; if (wantDebug) { diff --git a/libraries/models/src/ModelsScriptingInterface.cpp b/libraries/models/src/ModelsScriptingInterface.cpp index 7e08571fe5..bac1213071 100644 --- a/libraries/models/src/ModelsScriptingInterface.cpp +++ b/libraries/models/src/ModelsScriptingInterface.cpp @@ -160,10 +160,8 @@ ModelItemID ModelsScriptingInterface::findClosestModel(const glm::vec3& center, QVector ModelsScriptingInterface::findModels(const glm::vec3& center, float radius) const { QVector result; if (_modelTree) { - _modelTree->lockForRead(); QVector models; _modelTree->findModels(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, models); - _modelTree->unlock(); foreach (const ModelItem* model, models) { ModelItemID thisModelItemID(model->getID(), UNKNOWN_MODEL_TOKEN, true);