diff --git a/examples/VR-VJ/cartridgesSpawner.js b/examples/VR-VJ/cartridgesSpawner.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 660e597752..ab1a326698 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -235,6 +235,44 @@ const QHash Application::_acceptedExtensi { AVA_JSON_EXTENSION, &Application::askToWearAvatarAttachmentUrl } }; +class DeadlockWatchdogThread : public QThread { +public: + static const unsigned long HEARTBEAT_CHECK_INTERVAL_SECS = 1; + static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1; + static const unsigned long MAX_HEARTBEAT_AGE_USECS = 10 * USECS_PER_SECOND; + + // Set the heartbeat on launch + DeadlockWatchdogThread() { + QTimer* heartbeatTimer = new QTimer(); + // Give the heartbeat an initial value + _heartbeat = usecTimestampNow(); + connect(heartbeatTimer, &QTimer::timeout, [this] { + _heartbeat = usecTimestampNow(); + }); + heartbeatTimer->start(HEARTBEAT_UPDATE_INTERVAL_SECS * MSECS_PER_SECOND); + } + + void deadlockDetectionCrash() { + uint32_t* crashTrigger = nullptr; + *crashTrigger = 0xDEAD10CC; + } + + void run() override { + while (!qApp->isAboutToQuit()) { + QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); + auto now = usecTimestampNow(); + auto lastHeartbeatAge = now - _heartbeat; + if (lastHeartbeatAge > MAX_HEARTBEAT_AGE_USECS) { + deadlockDetectionCrash(); + } + } + } + + static std::atomic _heartbeat; +}; + +std::atomic DeadlockWatchdogThread::_heartbeat; + #ifdef Q_OS_WIN class MyNativeEventFilter : public QAbstractNativeEventFilter { public: @@ -457,6 +495,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : auto nodeList = DependencyManager::get(); + // Set up a watchdog thread to intentionally crash the application on deadlocks + (new DeadlockWatchdogThread())->start(); + qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); _bookmarks = new Bookmarks(); // Before setting up the menu @@ -4960,6 +5001,15 @@ void Application::crashApplication() { Q_UNUSED(value); } +void Application::deadlockApplication() { + qCDebug(interfaceapp) << "Intentionally deadlocked Interface"; + // Using a loop that will *technically* eventually exit (in ~600 billion years) + // to avoid compiler warnings about a loop that will never exit + for (uint64_t i = 1; i != 0; ++i) { + QThread::sleep(1); + } +} + void Application::setActiveDisplayPlugin(const QString& pluginName) { auto menu = Menu::getInstance(); foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 3a727db533..c93b7431f3 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -276,6 +276,7 @@ public slots: void reloadResourceCaches(); void crashApplication(); + void deadlockApplication(); void rotationModeChanged(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d605516380..164f94a094 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -590,6 +590,8 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); // Developer > Crash Application addActionToQMenuAndActionHash(developerMenu, MenuOption::CrashInterface, 0, qApp, SLOT(crashApplication())); + // Developer > Deadlock Application + addActionToQMenuAndActionHash(developerMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication())); // Developer > Log... addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index fb00416af0..6d5fd45b66 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -66,6 +66,7 @@ namespace MenuOption { const QString CopyPath = "Copy Path to Clipboard"; const QString CoupleEyelids = "Couple Eyelids"; const QString CrashInterface = "Crash Interface"; + const QString DeadlockInterface = "Deadlock Interface"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; const QString DisableActivityLogger = "Disable Activity Logger"; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index ab11e72ffe..a63cf78393 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -867,11 +867,10 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } else if (object.name == "Material") { FBXMaterial material; - if (object.properties.at(1).toByteArray().contains("StingrayPBS")) { - material.isPBSMaterial = true; - } + material.name = (object.properties.at(1).toString()); foreach (const FBXNode& subobject, object.children) { bool properties = false; + QByteArray propertyName; int index; if (subobject.name == "Properties60") { @@ -884,25 +883,36 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS propertyName = "P"; index = 4; } - if (!material.isPBSMaterial && properties) { - foreach (const FBXNode& property, subobject.children) { + if (properties) { + std::vector unknowns; + foreach(const FBXNode& property, subobject.children) { if (property.name == propertyName) { if (property.properties.at(0) == "DiffuseColor") { material.diffuseColor = getVec3(property.properties, index); - } else if (property.properties.at(0) == "Diffuse") { - material.diffuseColor = getVec3(property.properties, index); } else if (property.properties.at(0) == "DiffuseFactor") { material.diffuseFactor = property.properties.at(index).value(); + } else if (property.properties.at(0) == "Diffuse") { + // NOTE: this is uneeded but keep it for now for debug + // material.diffuseColor = getVec3(property.properties, index); + // material.diffuseFactor = 1.0; } else if (property.properties.at(0) == "SpecularColor") { material.specularColor = getVec3(property.properties, index); - } else if (property.properties.at(0) == "Specular") { - material.specularColor = getVec3(property.properties, index); } else if (property.properties.at(0) == "SpecularFactor") { material.specularFactor = property.properties.at(index).value(); + } else if (property.properties.at(0) == "Specular") { + // NOTE: this is uneeded but keep it for now for debug + // material.specularColor = getVec3(property.properties, index); + // material.specularFactor = 1.0; - } else if (property.properties.at(0) == "Emissive") { + } else if (property.properties.at(0) == "EmissiveColor") { material.emissiveColor = getVec3(property.properties, index); + } else if (property.properties.at(0) == "EmissiveFactor") { + material.emissiveFactor = property.properties.at(index).value(); + } else if (property.properties.at(0) == "Emissive") { + // NOTE: this is uneeded but keep it for now for debug + // material.emissiveColor = getVec3(property.properties, index); + // material.emissiveFactor = 1.0; } else if (property.properties.at(0) == "Shininess") { material.shininess = property.properties.at(index).value(); @@ -910,45 +920,50 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } else if (property.properties.at(0) == "Opacity") { material.opacity = property.properties.at(index).value(); } -#if defined(DEBUG_FBXREADER) - else { - const QString propname = property.properties.at(0).toString(); - if (propname == "EmissiveFactor") { - } - } -#endif - } - } - } else if (material.isPBSMaterial && properties) { - std::vector unknowns; - foreach(const FBXNode& property, subobject.children) { - if (property.name == propertyName) { - if (property.properties.at(0) == "Maya|use_normal_map") { + + // Sting Ray Material Properties!!!! + else if (property.properties.at(0) == "Maya|use_normal_map") { + material.isPBSMaterial = true; material.useNormalMap = (bool)property.properties.at(index).value(); } else if (property.properties.at(0) == "Maya|base_color") { + material.isPBSMaterial = true; material.diffuseColor = getVec3(property.properties, index); + } else if (property.properties.at(0) == "Maya|use_color_map") { + material.isPBSMaterial = true; material.useAlbedoMap = (bool) property.properties.at(index).value(); } else if (property.properties.at(0) == "Maya|roughness") { + material.isPBSMaterial = true; material.roughness = property.properties.at(index).value(); + } else if (property.properties.at(0) == "Maya|use_roughness_map") { + material.isPBSMaterial = true; material.useRoughnessMap = (bool)property.properties.at(index).value(); } else if (property.properties.at(0) == "Maya|metallic") { + material.isPBSMaterial = true; material.metallic = property.properties.at(index).value(); + } else if (property.properties.at(0) == "Maya|use_metallic_map") { + material.isPBSMaterial = true; material.useMetallicMap = (bool)property.properties.at(index).value(); } else if (property.properties.at(0) == "Maya|emissive") { + material.isPBSMaterial = true; material.emissiveColor = getVec3(property.properties, index); + } else if (property.properties.at(0) == "Maya|emissive_intensity") { + material.isPBSMaterial = true; material.emissiveIntensity = property.properties.at(index).value(); + } else if (property.properties.at(0) == "Maya|use_emissive_map") { + material.isPBSMaterial = true; material.useEmissiveMap = (bool)property.properties.at(index).value(); } else if (property.properties.at(0) == "Maya|use_ao_map") { + material.isPBSMaterial = true; material.useOcclusionMap = (bool)property.properties.at(index).value(); } else { @@ -1073,7 +1088,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (connection.properties.at(0) == "OP") { int counter = 0; QByteArray type = connection.properties.at(3).toByteArray().toLower(); - if ((type.contains("diffuse") && !type.contains("tex_global_diffuse"))) { + if (type.contains("DiffuseFactor")) { + diffuseFactorTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if ((type.contains("diffuse") && !type.contains("tex_global_diffuse"))) { diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type.contains("tex_color_map")) { diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 8e1f0c681b..a1fc30d1f4 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -138,19 +138,22 @@ public: opacity(opacity) {} glm::vec3 diffuseColor{ 1.0f }; - float diffuseFactor = 1.0f; + float diffuseFactor{ 1.0f }; glm::vec3 specularColor{ 0.02f }; - float specularFactor = 1.0f; + float specularFactor{ 1.0f }; glm::vec3 emissiveColor{ 0.0f }; - float shininess = 23.0f; - float opacity = 1.0f; + float emissiveFactor{ 0.0f }; + + float shininess{ 23.0f }; + float opacity{ 1.0f }; float metallic{ 0.0f }; float roughness{ 1.0f }; float emissiveIntensity{ 1.0f }; QString materialID; + QString name; model::MaterialPointer _material; FBXTexture normalTexture; @@ -421,6 +424,7 @@ public: QHash diffuseTextures; + QHash diffuseFactorTextures; QHash transparentTextures; QHash bumpTextures; QHash normalTextures; diff --git a/libraries/fbx/src/FBXReader_Material.cpp b/libraries/fbx/src/FBXReader_Material.cpp index d422f079d2..fb272a1af9 100644 --- a/libraries/fbx/src/FBXReader_Material.cpp +++ b/libraries/fbx/src/FBXReader_Material.cpp @@ -75,12 +75,24 @@ void FBXReader::consolidateFBXMaterials() { // the pure material associated with this part bool detectDifferentUVs = false; FBXTexture diffuseTexture; + FBXTexture diffuseFactorTexture; QString diffuseTextureID = diffuseTextures.value(material.materialID); - if (!diffuseTextureID.isNull()) { + QString diffuseFactorTextureID = diffuseFactorTextures.value(material.materialID); + + // If both factor and color textures are specified, the texture bound to DiffuseColor wins + if (!diffuseFactorTextureID.isNull() || !diffuseTextureID.isNull()) { + if (!diffuseFactorTextureID.isNull() && diffuseTextureID.isNull()) { + diffuseTextureID = diffuseFactorTextureID; + // If the diffuseTextureID comes from the Texture bound to DiffuseFactor, we know it s exported from maya + // And the DiffuseFactor is forced to 0.5 by Maya which is bad + // So we need to force it to 1.0 + material.diffuseFactor = 1.0; + } + diffuseTexture = getTexture(diffuseTextureID); - + // FBX files generated by 3DSMax have an intermediate texture parent, apparently - foreach (const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) { + foreach(const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) { if (_textureFilenames.contains(childTextureID)) { diffuseTexture = getTexture(diffuseTextureID); } @@ -180,11 +192,13 @@ void FBXReader::consolidateFBXMaterials() { // Finally create the true material representation material._material = std::make_shared(); - material._material->setEmissive(material.emissiveColor); - auto diffuse = material.diffuseColor; - // FIXME: Do not use the Diffuse Factor yet as some FBX models have it set to 0 - // diffuse *= material.diffuseFactor; + // Emissive color is the mix of emissiveColor with emissiveFactor + auto emissive = material.emissiveColor * material.emissiveFactor; + material._material->setEmissive(emissive); + + // Final diffuse color is the mix of diffuseColor with diffuseFactor + auto diffuse = material.diffuseColor * material.diffuseFactor; material._material->setAlbedo(diffuse); if (material.isPBSMaterial) { diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index d49ff91abf..82a0b35cc9 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -344,6 +344,7 @@ void NetworkTexture::setImage(const QImage& image, void* voidTexture, int origin _width = _height = 0; } + _isCacheable = true; finishedLoading(true); emit networkTextureCreated(qWeakPointerCast (_self)); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index af82d2a1ad..ee2279540b 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -132,6 +132,8 @@ signals: protected: + virtual bool isCacheable() const override { return _isCacheable; } + virtual void downloadFinished(const QByteArray& data) override; Q_INVOKABLE void loadContent(const QByteArray& content); @@ -146,6 +148,7 @@ private: int _originalHeight { 0 }; int _width { 0 }; int _height { 0 }; + bool _isCacheable { false }; }; #endif // hifi_TextureCache_h diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index e5771401b9..b312067d49 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -278,20 +278,20 @@ void Resource::refresh() { } void Resource::allReferencesCleared() { - if (_cache) { + if (_cache && isCacheable()) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "allReferencesCleared"); return; } - + // create and reinsert new shared pointer QSharedPointer self(this, &Resource::allReferencesCleared); setSelf(self); reinsert(); - + // add to the unused list _cache->addUnusedResource(self); - + } else { delete this; } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 97e46f088a..2a89ad9b92 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -192,6 +192,9 @@ protected slots: protected: virtual void init(); + /// Checks whether the resource is cacheable. + virtual bool isCacheable() const { return true; } + /// Called when the download has finished virtual void downloadFinished(const QByteArray& data); diff --git a/unpublishedScripts/DomainContent/Home/plant/flower.fs b/unpublishedScripts/DomainContent/Home/plant/flower.fs new file mode 100644 index 0000000000..b67c910074 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/plant/flower.fs @@ -0,0 +1,94 @@ +// +// flowers.fs +// examples/homeContent/plant +// +// Created by Eric Levin on 3/7/16. +// Copyright 2016 High Fidelity, Inc. +// +// This fragment shader is designed to shader a sphere to create the effect of a blooming flower +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +#define TWO_PI 6.28318530718 + +uniform float iBloomPct = 0.2; +uniform vec3 iHSLColor = vec3(0.7, 0.5, 0.5); + + +float hue2rgb(float f1, float f2, float hue) { + if (hue < 0.0) + hue += 1.0; + else if (hue > 1.0) + hue -= 1.0; + float res; + if ((6.0 * hue) < 1.0) + res = f1 + (f2 - f1) * 6.0 * hue; + else if ((2.0 * hue) < 1.0) + res = f2; + else if ((3.0 * hue) < 2.0) + res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; + else + res = f1; + return res; +} + +vec3 hsl2rgb(vec3 hsl) { + vec3 rgb; + + if (hsl.y == 0.0) { + rgb = vec3(hsl.z); // Luminance + } else { + float f2; + + if (hsl.z < 0.5) + f2 = hsl.z * (1.0 + hsl.y); + else + f2 = hsl.z + hsl.y - hsl.y * hsl.z; + + float f1 = 2.0 * hsl.z - f2; + + rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); + rgb.g = hue2rgb(f1, f2, hsl.x); + rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); + } + return rgb; +} + +vec3 hsl2rgb(float h, float s, float l) { + return hsl2rgb(vec3(h, s, l)); +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + vec2 st = fragCoord.xy/iWorldScale.xz; + vec3 color = vec3(0.0, 0.0, 0.0); + + vec2 toCenter = vec2(0.5) - st; + float angle = atan(toCenter.y, toCenter.x); + float radius = length(toCenter) * 2.0; + + // Second check is so we discard the top half of the sphere + if ( iBloomPct < radius || _position.y > 0) { + discard; + } + + // simulate ambient occlusion + float brightness = pow(radius, 0.8); + vec3 hslColor = iHSLColor + (abs(angle) * 0.02); + hslColor.z = 0.15 + pow(radius, 2.); + vec3 rgbColor = hsl2rgb(hslColor); + fragColor = vec4(rgbColor, 1.0); +} + + + + +vec4 getProceduralColor() { + vec4 result; + vec2 position = _position.xz; + position += 0.5; + + mainImage(result, position * iWorldScale.xz); + + return result; +} \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/plant/growingPlantEntityScript.js b/unpublishedScripts/DomainContent/Home/plant/growingPlantEntityScript.js new file mode 100644 index 0000000000..585c603d02 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/plant/growingPlantEntityScript.js @@ -0,0 +1,154 @@ +// +// growingPlantEntityScript.js +// examples/homeContent/plant +// +// Created by Eric Levin on 2/10/16. +// Copyright 2016 High Fidelity, Inc. +// +// This entity script handles the logic for growing a plant when it has water poured on it +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + + +(function() { + Script.include('../../../../libraries/utils.js'); + + var _this; + GrowingPlant = function() { + _this = this; + _this.flowers = []; + // _this.STARTING_FLOWER_DIMENSIONS = {x: 0.1, y: 0.001, z: 0.1} + _this.STARTING_FLOWER_DIMENSIONS = { + x: 0.001, + y: 0.001, + z: 0.001 + } + + _this.MAX_FLOWERS = 50; + _this.MIN_FLOWER_TO_FLOWER_DISTANCE = 0.03; + + _this.debounceRange = { + min: 500, + max: 1000 + }; + _this.canCreateFlower = true; + _this.SHADER_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/shaders/flower.fs"; + // _this.SHADER_URL = "file:///C:/Users/Eric/hifi/unpublishedScripts/DomainContent/Home/plant/flower.fs"; + + _this.flowerHSLColors = [{ + hue: 19 / 360, + saturation: 0.92, + light: 0.31 + }, { + hue: 161 / 360, + saturation: 0.28, + light: 0.62 + }]; + + }; + + GrowingPlant.prototype = { + + + continueWatering: function(entityID, data) { + // we're being watered- every now and then spawn a new flower to add to our growing list + // If we don't have any flowers yet, immediately grow one + var data = JSON.parse(data[0]); + + if (_this.canCreateFlower && _this.flowers.length < _this.MAX_FLOWERS) { + _this.createFlower(data.position, data.surfaceNormal); + _this.canCreateFlower = false; + Script.setTimeout(function() { + _this.canCreateFlower = true; + }, randFloat(_this.debounceRange.min, this.debounceRange.max)); + + } + + _this.flowers.forEach(function(flower) { + flower.grow(); + }); + + + }, + + createFlower: function(position, surfaceNormal) { + if (_this.previousFlowerPosition && Vec3.distance(position, _this.previousFlowerPosition) < _this.MIN_FLOWER_TO_FLOWER_DISTANCE) { + // Reduces flower overlap + return; + } + var xzGrowthRate = randFloat(0.00006, 0.00016); + var growthRate = {x: xzGrowthRate, y: randFloat(0.001, 0.003), z: xzGrowthRate}; + + var flower = { + dimensions: { + x: _this.STARTING_FLOWER_DIMENSIONS.x, + y: _this.STARTING_FLOWER_DIMENSIONS.y, + z: _this.STARTING_FLOWER_DIMENSIONS.z + }, + startingPosition: position, + rotation: Quat.rotationBetween(Vec3.UNIT_Y, surfaceNormal), + maxYDimension: randFloat(0.4, 1.1), + // startingHSLColor: { + // hue: 80 / 360, + // saturation: 0.47, + // light: 0.48 + // }, + // endingHSLColor: { + // hue: 19 / 260, + // saturation: 0.92, + // light: 0.41 + // }, + hslColor: Math.random() < 0.5 ? _this.flowerHSLColors[0] : _this.flowerHSLColors[1], + growthRate: growthRate + }; + flower.userData = { + ProceduralEntity: { + shaderUrl: _this.SHADER_URL, + uniforms: { + iBloomPct: randFloat(0.4, 0.8), + iHSLColor: [flower.hslColor.hue, flower.hslColor.saturation, flower.hslColor.light] + } + } + }; + flower.id = Entities.addEntity({ + type: "Sphere", + name: "flower", + lifetime: 3600, + position: position, + collisionless: true, + rotation: flower.rotation, + dimensions: _this.STARTING_FLOWER_DIMENSIONS, + userData: JSON.stringify(flower.userData) + }); + flower.grow = function() { + // grow flower a bit + if (flower.dimensions.y > flower.maxYDimension) { + return; + } + flower.dimensions = Vec3.sum(flower.dimensions, flower.growthRate); + flower.position = Vec3.sum(flower.startingPosition, Vec3.multiply(Quat.getUp(flower.rotation), flower.dimensions.y / 2)); + //As we grow we must also move ourselves in direction we grow! + //TODO: Add this color changing back once we fix bug https://app.asana.com/0/inbox/31759584831096/96943843100173/98022172055918 + // var newHue = map(flower.dimensions.y, _this.STARTING_FLOWER_DIMENSIONS.y, flower.maxYDimension, flower.startingHSLColor.hue, flower.endingHSLColor.hue); + // var newSaturation = map(flower.dimensions.y, _this.STARTING_FLOWER_DIMENSIONS.y, flower.maxYDimension, flower.startingHSLColor.saturation, flower.endingHSLColor.saturation); + // var newLight = map(flower.dimensions.y, _this.STARTING_FLOWER_DIMENSIONS.y, flower.maxYDimension, flower.startingHSLColor.light, flower.endingHSLColor.light); + // flower.userData.PrsoceduralEntity.uniforms.iHSLColor = [newHue, newSaturation, newLight]; + Entities.editEntity(flower.id, { + dimensions: flower.dimensions, + position: flower.position, + }); + } + _this.flowers.push(flower); + _this.previousFlowerPosition = position; + }, + + preload: function(entityID) { + _this.entityID = entityID; + }, + + }; + + // entity scripts always need to return a newly constructed object of our type + return new GrowingPlant(); +}); \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/plant/growingPlantSpawner.js b/unpublishedScripts/DomainContent/Home/plant/growingPlantSpawner.js new file mode 100644 index 0000000000..a48708eb8a --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/plant/growingPlantSpawner.js @@ -0,0 +1,139 @@ +// +// growingPlantSpawner.js +// examples/homeContent/plant +// +// Created by Eric Levin on 2/10/16. +// Copyright 2016 High Fidelity, Inc. +// +// This entity script handles the logic for growing a plant when it has water poured on it +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +var orientation = Camera.getOrientation(); +orientation = Quat.safeEulerAngles(orientation); +orientation.x = 0; +orientation = Quat.fromVec3Degrees(orientation); + + +var bowlPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(orientation))); +var BOWL_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/Flowers--Bowl.fbx"; +var bowlDimensions = { + x: 0.518, + y: 0.1938, + z: 0.5518 +}; +var bowl = Entities.addEntity({ + type: "Model", + modelURL: BOWL_MODEL_URL, + dimensions: bowlDimensions, + name: "plant bowl", + position: bowlPosition +}); + + +var PLANT_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/Flowers--Moss-Rock.fbx"; +var PLANT_SCRIPT_URL = Script.resolvePath("growingPlantEntityScript.js?v1" + Math.random().toFixed(2)); +var plantDimensions = { + x: 0.52, + y: 0.2600, + z: 0.52 +}; +var plantPosition = Vec3.sum(bowlPosition, { + x: 0, + y: plantDimensions.y / 2, + z: 0 +}); +var plant = Entities.addEntity({ + type: "Model", + modelURL: PLANT_MODEL_URL, + name: "hifi-growable-plant", + dimensions: plantDimensions, + position: plantPosition, + script: PLANT_SCRIPT_URL, + parentID: bowl +}); + +var WATER_CAN_MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/waterCan.fbx?v1" + Math.random(); +var WATER_CAN_SCRIPT_URL = Script.resolvePath("waterCanEntityScript.js?v2" + Math.random().toFixed()); +var waterCanPosition = Vec3.sum(plantPosition, Vec3.multiply(0.6, Quat.getRight(orientation))); +var waterCanRotation = orientation; +var waterCan = Entities.addEntity({ + type: "Model", + shapeType: 'box', + name: "hifi-water-can", + modelURL: WATER_CAN_MODEL_URL, + script: WATER_CAN_SCRIPT_URL, + dimensions: { + x: 0.1859, + y: 0.2762, + z: 0.4115 + }, + position: waterCanPosition, + angularDamping: 1, + dynamic: true, + gravity: { + x: 0.0, + y: -2.0, + z: 0 + }, + rotation: waterCanRotation, + userData: JSON.stringify({ + wearable: { + joints: { + RightHand: [{ + x: 0.024, + y: 0.173, + z: 0.152 + }, { + x: 0.374, + y: 0.636, + z: -0.638, + w: -0.215 + }], + LeftHand: [{ + x: -0.0348, + y: 0.201, + z: 0.166 + }, { + x: 0.4095, + y: -0.625, + z: 0.616, + w: -0.247 + }] + } + } + }) +}); + + +var waterSpoutPosition = Vec3.sum(waterCanPosition, Vec3.multiply(0.2, Quat.getFront(orientation))) +var waterSpoutRotation = Quat.multiply(waterCanRotation, Quat.fromPitchYawRollDegrees(10, 0, 0)); +var waterSpout = Entities.addEntity({ + type: "Box", + name: "hifi-water-spout", + dimensions: { + x: 0.02, + y: 0.02, + z: 0.07 + }, + color: { + red: 200, + green: 10, + blue: 200 + }, + position: waterSpoutPosition, + rotation: waterSpoutRotation, + parentID: waterCan, + visible: false +}); + +function cleanup() { + // Entities.deleteEntity(plant); + // Entities.deleteEntity(bowl); + // Entities.deleteEntity(waterCan); + // Entities.deleteEntity(waterSpout); +} + + +Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/plant/waterCanEntityScript.js b/unpublishedScripts/DomainContent/Home/plant/waterCanEntityScript.js new file mode 100644 index 0000000000..d306cf8b6b --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/plant/waterCanEntityScript.js @@ -0,0 +1,242 @@ +// +// waterCanEntityScript.js +// examples/homeContent/plant +// +// Created by Eric Levin on 2/15/16. +// Copyright 2016 High Fidelity, Inc. +// +// This entity script handles the logic for pouring water when a user tilts the entity the script is attached too. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +(function() { + + Script.include('../../../../libraries/utils.js'); + + var _this; + WaterSpout = function() { + _this = this; + _this.waterSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/shower.wav"); + _this.POUR_ANGLE_THRESHOLD = 0; + _this.waterPouring = false; + _this.WATER_SPOUT_NAME = "hifi-water-spout"; + _this.GROWABLE_ENTITIES_SEARCH_RANGE = 100; + + }; + + WaterSpout.prototype = { + + startNearGrab: function() { + _this.startHold(); + }, + + startEquip: function() { + _this.startHold(); + }, + + startHold: function() { + _this.findGrowableEntities(); + }, + + releaseEquip: function() { + _this.releaseHold(); + }, + + releaseGrab: function() { + _this.releaseHold(); + }, + + releaseHold: function() { + _this.stopPouring(); + }, + + stopPouring: function() { + Entities.editEntity(_this.waterEffect, { + isEmitting: false + }); + _this.waterPouring = false; + //water no longer pouring... + if (_this.waterInjector) { + _this.waterInjector.stop(); + } + Entities.callEntityMethod(_this.mostRecentIntersectedGrowableEntity, 'stopWatering'); + }, + continueEquip: function() { + _this.continueHolding(); + }, + + continueNearGrab: function() { + _this.continueHolding(); + }, + + continueHolding: function() { + if (!_this.waterSpout) { + return; + } + // Check rotation of water can along it's z axis. If it's beyond a threshold, then start spraying water + _this.castRay(); + var rotation = Entities.getEntityProperties(_this.entityID, "rotation").rotation; + var pitch = Quat.safeEulerAngles(rotation).x; + if (pitch < _this.POUR_ANGLE_THRESHOLD) { + // Water is pouring + var spoutProps = Entities.getEntityProperties(_this.waterSpout, ["rotation", "position"]); + if (!_this.waterPouring) { + Entities.editEntity(_this.waterEffect, { + isEmitting: true + }); + _this.waterPouring = true; + if (!_this.waterInjector) { + _this.waterInjector = Audio.playSound(_this.waterSound, { + position: spoutProps.position, + loop: true + }); + + } else { + _this.waterInjector.restart(); + } + } + _this.waterSpoutRotation = spoutProps.rotation; + var waterEmitOrientation = Quat.multiply(_this.waterSpoutRotation, Quat.fromPitchYawRollDegrees(0, 180, 0)); + Entities.editEntity(_this.waterEffect, { + emitOrientation: waterEmitOrientation + }); + } else if (pitch > _this.POUR_ANGLE_THRESHOLD && _this.waterPouring) { + _this.stopPouring(); + } + }, + + castRay: function() { + var spoutProps = Entities.getEntityProperties(_this.waterSpout, ["position, rotation"]); + var direction = Quat.getFront(spoutProps.rotation) + var end = Vec3.sum(spoutProps.position, Vec3.multiply(5, direction)); + + var pickRay = { + origin: spoutProps.position, + direction: direction + }; + var intersection = Entities.findRayIntersection(pickRay, true, _this.growableEntities); + + if (intersection.intersects) { + //We've intersected with a waterable object + var data = JSON.stringify({ + position: intersection.intersection, + surfaceNormal: intersection.surfaceNormal + }); + _this.mostRecentIntersectedGrowableEntity = intersection.entityID; + Entities.callEntityMethod(intersection.entityID, 'continueWatering', [data]); + } + + }, + + + + createWaterEffect: function() { + var waterEffectPosition = Vec3.sum(_this.waterSpoutPosition, Vec3.multiply(Quat.getFront(_this.waterSpoutRotation), -0.04)); + _this.waterEffect = Entities.addEntity({ + type: "ParticleEffect", + name: "water particle effect", + position: waterEffectPosition, + isEmitting: false, + parentID: _this.waterSpout, + colorStart: { + red: 90, + green: 90, + blue: 110 + }, + color: { + red: 70, + green: 70, + blue: 130 + }, + colorFinish: { + red: 23, + green: 195, + blue: 206 + }, + maxParticles: 20000, + lifespan: 2, + emitRate: 2000, + emitSpeed: .3, + speedSpread: 0.1, + emitDimensions: { + x: 0.0, + y: 0.0, + z: 0.0 + }, + emitAcceleration: { + x: 0.0, + y: 0, + z: 0 + }, + polarStart: 0.0, + polarFinish: 0.1, + accelerationSpread: { + x: 0.01, + y: 0.0, + z: 0.01 + }, + emitOrientation: Quat.fromPitchYawRollDegrees(0, 0, 0), + radiusSpread: 0.0001, + radiusStart: 0.005, + particleRadius: 0.003, + radiusFinish: 0.001, + alphaSpread: 0, + alphaStart: 0.1, + alpha: 1.0, + alphaFinish: 1.0, + emitterShouldTrail: true, + textures: "https://s3-us-west-1.amazonaws.com/hifi-content/eric/images/raindrop.png", + }); + + }, + + findGrowableEntities: function() { + _this.growableEntities = []; + var entities = Entities.findEntities(_this.position, _this.GROWABLE_ENTITIES_SEARCH_RANGE); + entities.forEach(function(entity) { + var name = Entities.getEntityProperties(entity, "name").name; + if (name.length > 0 && name.indexOf("growable") !== -1) { + _this.growableEntities.push(entity); + } + }); + + }, + + preload: function(entityID) { + _this.entityID = entityID; + _this.position = Entities.getEntityProperties(_this.entityID, "position").position; + // Wait a a bit for spout to spawn for case where preload is initial spawn, then save it + Script.setTimeout(function() { + var entities = Entities.findEntities(_this.position, 1); + entities.forEach(function(entity) { + var name = Entities.getEntityProperties(entity, "name").name; + if (name === _this.WATER_SPOUT_NAME) { + _this.waterSpout = entity; + } + }); + + if (_this.waterSpout) { + _this.waterSpoutPosition = Entities.getEntityProperties(_this.waterSpout, "position").position; + _this.waterSpoutRotation = Entities.getEntityProperties(_this.waterSpout, "rotation").rotation; + _this.createWaterEffect(); + } + + }, 3000); + + }, + + + unload: function() { + Entities.deleteEntity(_this.waterEffect); + if (_this.waterInjector) { + _this.waterInjector.stop(); + delete _this.waterInjector; + } + } + + }; + + // entity scripts always need to return a newly constructed object of our type + return new WaterSpout(); +}); \ No newline at end of file