Merge branch 'master' of https://github.com/highfidelity/hifi into bugfixes

This commit is contained in:
Brad Hefta-Gaub 2016-03-09 11:47:55 -08:00
commit e038c8a43c
16 changed files with 765 additions and 40 deletions

View file

View file

@ -235,6 +235,44 @@ const QHash<QString, Application::AcceptURLMethod> 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<uint64_t> _heartbeat;
};
std::atomic<uint64_t> 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<NodeList>();
// 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()) {

View file

@ -276,6 +276,7 @@ public slots:
void reloadResourceCaches();
void crashApplication();
void deadlockApplication();
void rotationModeChanged();

View file

@ -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,

View file

@ -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";

View file

@ -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<std::string> 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<double>();
} 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<double>();
} 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<double>();
} 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<double>();
@ -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<double>();
}
#if defined(DEBUG_FBXREADER)
else {
const QString propname = property.properties.at(0).toString();
if (propname == "EmissiveFactor") {
}
}
#endif
}
}
} else if (material.isPBSMaterial && properties) {
std::vector<std::string> 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<double>();
} 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<double>();
} else if (property.properties.at(0) == "Maya|roughness") {
material.isPBSMaterial = true;
material.roughness = property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|use_roughness_map") {
material.isPBSMaterial = true;
material.useRoughnessMap = (bool)property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|metallic") {
material.isPBSMaterial = true;
material.metallic = property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|use_metallic_map") {
material.isPBSMaterial = true;
material.useMetallicMap = (bool)property.properties.at(index).value<double>();
} 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<double>();
} else if (property.properties.at(0) == "Maya|use_emissive_map") {
material.isPBSMaterial = true;
material.useEmissiveMap = (bool)property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|use_ao_map") {
material.isPBSMaterial = true;
material.useOcclusionMap = (bool)property.properties.at(index).value<double>();
} 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));

View file

@ -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<QString, QString> diffuseTextures;
QHash<QString, QString> diffuseFactorTextures;
QHash<QString, QString> transparentTextures;
QHash<QString, QString> bumpTextures;
QHash<QString, QString> normalTextures;

View file

@ -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<model::Material>();
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) {

View file

@ -344,6 +344,7 @@ void NetworkTexture::setImage(const QImage& image, void* voidTexture, int origin
_width = _height = 0;
}
_isCacheable = true;
finishedLoading(true);
emit networkTextureCreated(qWeakPointerCast<NetworkTexture, Resource> (_self));

View file

@ -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

View file

@ -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<Resource> self(this, &Resource::allReferencesCleared);
setSelf(self);
reinsert();
// add to the unused list
_cache->addUnusedResource(self);
} else {
delete this;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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();
});

View file

@ -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);

View file

@ -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();
});