diff --git a/examples/afk.js b/examples/afk.js
new file mode 100644
index 0000000000..5977c6384a
--- /dev/null
+++ b/examples/afk.js
@@ -0,0 +1,100 @@
+//
+// #20485: AFK - Away From Keyboard Setting
+// *****************************************
+//
+// Created by Kevin M. Thomas and Thoys 07/16/15.
+// Copyright 2015 High Fidelity, Inc.
+// kevintown.net
+//
+// JavaScript for the High Fidelity interface that creates an away from keyboard functionality by providing a UI and keyPressEvent which will mute toggle the connected microphone, face tracking dde and set the avatar to a hand raise pose.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+
+var originalOutputDevice;
+var originalName;
+var muted = false;
+var wasAudioEnabled;
+var afkText = "AFK - I Will Return!\n";
+
+// Set up toggleMuteButton text overlay.
+var toggleMuteButton = Overlays.addOverlay("text", {
+ x: 10,
+ y: 275,
+ width: 60,
+ height: 28,
+ backgroundColor: { red: 0, green: 0, blue: 0},
+ color: { red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8
+});
+
+// Function that overlays text upon state change.
+function onMuteStateChanged() {
+ Overlays.editOverlay(toggleMuteButton, muted ? {text: "Go Live", leftMargin: 5} : {text: "Go AFK", leftMargin: 5});
+}
+
+function toggleMute() {
+ if (!muted) {
+ if (!AudioDevice.getMuted()) {
+ AudioDevice.toggleMute();
+ }
+ originalOutputDevice = AudioDevice.getOutputDevice();
+ Menu.setIsOptionChecked("Mute Face Tracking", true);
+ originalName = MyAvatar.displayName;
+ AudioDevice.setOutputDevice("none");
+ MyAvatar.displayName = afkText + MyAvatar.displayName;
+ MyAvatar.setJointData("LeftShoulder", Quat.fromPitchYawRollDegrees(0, 180, 0));
+ MyAvatar.setJointData("RightShoulder", Quat.fromPitchYawRollDegrees(0, 180, 0));
+ } else {
+ if (AudioDevice.getMuted()) {
+ AudioDevice.toggleMute();
+ }
+ AudioDevice.setOutputDevice(originalOutputDevice);
+ Menu.setIsOptionChecked("Mute Face Tracking", false);
+ MyAvatar.setJointData("LeftShoulder", Quat.fromPitchYawRollDegrees(0, 0, 0));
+ MyAvatar.setJointData("RightShoulder", Quat.fromPitchYawRollDegrees(0, 0, 0));
+ MyAvatar.clearJointData("LeftShoulder");
+ MyAvatar.clearJointData("RightShoulder");
+ MyAvatar.displayName = originalName;
+ }
+ muted = !muted;
+ onMuteStateChanged();
+}
+
+// Function that adds mousePressEvent functionality to toggle mic mute, AFK message above display name and toggle avatar arms upward.
+function mousePressEvent(event) {
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleMuteButton) {
+ toggleMute();
+ }
+}
+
+// Call functions.
+onMuteStateChanged();
+
+//AudioDevice.muteToggled.connect(onMuteStateChanged);
+Controller.mousePressEvent.connect(mousePressEvent);
+
+// Function that adds keyPressEvent functionality to toggle mic mute, AFK message above display name and toggle avatar arms upward.
+Controller.keyPressEvent.connect(function(event) {
+ if (event.text == "y") {
+ toggleMute();
+ }
+});
+
+// Function that sets a timeout value of 1 second so that the display name does not get overwritten in the event of a crash.
+Script.setTimeout(function() {
+ MyAvatar.displayName = MyAvatar.displayName.replace(afkText, "");
+}, 1000);
+
+// Function that calls upon exit to restore avatar display name to original state.
+Script.scriptEnding.connect(function(){
+ if (muted) {
+ AudioDevice.setOutputDevice(originalOutputDevice);
+ Overlays.deleteOverlay(toggleMuteButton);
+ MyAvatar.displayName = originalName;
+ }
+ Overlays.deleteOverlay(toggleMuteButton);
+});
\ No newline at end of file
diff --git a/examples/edit.js b/examples/edit.js
index 1af016958f..ec3106e585 100644
--- a/examples/edit.js
+++ b/examples/edit.js
@@ -329,7 +329,7 @@ var toolBar = (function () {
Script.setTimeout(resize, RESIZE_INTERVAL);
} else {
- print("Can't add model: Model would be out of bounds.");
+ Window.alert("Can't add model: Model would be out of bounds.");
}
}
@@ -374,7 +374,7 @@ var toolBar = (function () {
});
} else {
- print("Can't create box: Box would be out of bounds.");
+ Window.alert("Can't create box: Box would be out of bounds.");
}
return true;
}
@@ -390,7 +390,7 @@ var toolBar = (function () {
color: { red: 255, green: 0, blue: 0 }
});
} else {
- print("Can't create sphere: Sphere would be out of bounds.");
+ Window.alert("Can't create sphere: Sphere would be out of bounds.");
}
return true;
}
@@ -413,7 +413,7 @@ var toolBar = (function () {
cutoff: 180, // in degrees
});
} else {
- print("Can't create Light: Light would be out of bounds.");
+ Window.alert("Can't create Light: Light would be out of bounds.");
}
return true;
}
@@ -433,7 +433,7 @@ var toolBar = (function () {
lineHeight: 0.06
});
} else {
- print("Can't create box: Text would be out of bounds.");
+ Window.alert("Can't create box: Text would be out of bounds.");
}
return true;
}
@@ -449,7 +449,7 @@ var toolBar = (function () {
sourceUrl: "https://highfidelity.com/",
});
} else {
- print("Can't create Web Entity: would be out of bounds.");
+ Window.alert("Can't create Web Entity: would be out of bounds.");
}
return true;
}
@@ -464,7 +464,7 @@ var toolBar = (function () {
dimensions: { x: 10, y: 10, z: 10 },
});
} else {
- print("Can't create box: Text would be out of bounds.");
+ Window.alert("Can't create box: Text would be out of bounds.");
}
return true;
}
@@ -482,7 +482,7 @@ var toolBar = (function () {
voxelSurfaceStyle: 1
});
} else {
- print("Can't create PolyVox: would be out of bounds.");
+ Window.alert("Can't create PolyVox: would be out of bounds.");
}
return true;
}
@@ -1068,13 +1068,16 @@ function importSVO(importURL) {
if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) {
position = getPositionToCreateEntity();
}
- var pastedEntityIDs = Clipboard.pasteEntities(position);
-
- if (isActive) {
- selectionManager.setSelections(pastedEntityIDs);
- }
+ if (position.x > 0 && position.y > 0 && position.z > 0) {
+ var pastedEntityIDs = Clipboard.pasteEntities(position);
+ if (isActive) {
+ selectionManager.setSelections(pastedEntityIDs);
+ }
Window.raiseMainWindow();
+ } else {
+ Window.alert("Can't import objects: objects would be out of bounds.");
+ }
} else {
Window.alert("There was an error importing the entity file.");
}
diff --git a/examples/example/audio/jsstreamplayer.js b/examples/example/audio/jsstreamplayer.js
new file mode 100644
index 0000000000..27ed32e3b5
--- /dev/null
+++ b/examples/example/audio/jsstreamplayer.js
@@ -0,0 +1,145 @@
+//
+// #20622: JS Stream Player
+// *************************
+//
+// Created by Kevin M. Thomas and Thoys 07/17/15.
+// Copyright 2015 High Fidelity, Inc.
+// kevintown.net
+//
+// JavaScript for the High Fidelity interface that creates a stream player with a UI and keyPressEvents for adding a stream URL in addition to play, stop and volume functionality.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+
+// Declare HiFi public bucket.
+HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
+
+// Declare variables and set up new WebWindow.
+var stream;
+var volume = 1;
+var streamWindow = new WebWindow('Stream', HIFI_PUBLIC_BUCKET + "examples/html/jsstreamplayer.html", 0, 0, false);
+
+// Set up toggleStreamURLButton overlay.
+var toggleStreamURLButton = Overlays.addOverlay("text", {
+ x: 76,
+ y: 275,
+ width: 40,
+ height: 28,
+ backgroundColor: {red: 0, green: 0, blue: 0},
+ color: {red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8,
+ text: " URL"
+});
+
+// Set up toggleStreamPlayButton overlay.
+var toggleStreamPlayButton = Overlays.addOverlay("text", {
+ x: 122,
+ y: 275,
+ width: 38,
+ height: 28,
+ backgroundColor: { red: 0, green: 0, blue: 0},
+ color: { red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8,
+ text: " Play"
+});
+
+// Set up toggleStreamStopButton overlay.
+var toggleStreamStopButton = Overlays.addOverlay("text", {
+ x: 166,
+ y: 275,
+ width: 40,
+ height: 28,
+ backgroundColor: { red: 0, green: 0, blue: 0},
+ color: { red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8,
+ text: " Stop"
+});
+
+// Set up increaseVolumeButton overlay.
+var toggleIncreaseVolumeButton = Overlays.addOverlay("text", {
+ x: 211,
+ y: 275,
+ width: 18,
+ height: 28,
+ backgroundColor: { red: 0, green: 0, blue: 0},
+ color: { red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8,
+ text: " +"
+});
+
+// Set up decreaseVolumeButton overlay.
+var toggleDecreaseVolumeButton = Overlays.addOverlay("text", {
+ x: 234,
+ y: 275,
+ width: 15,
+ height: 28,
+ backgroundColor: { red: 0, green: 0, blue: 0},
+ color: { red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8,
+ text: " -"
+});
+
+// Function that adds mousePressEvent functionality to connect UI to enter stream URL, play and stop stream.
+function mousePressEvent(event) {
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamURLButton) {
+ stream = Window.prompt("Enter Stream: ");
+ var streamJSON = {
+ action: "changeStream",
+ stream: stream
+ }
+ streamWindow.eventBridge.emitScriptEvent(JSON.stringify(streamJSON));
+ }
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamPlayButton) {
+ var streamJSON = {
+ action: "changeStream",
+ stream: stream
+ }
+ streamWindow.eventBridge.emitScriptEvent(JSON.stringify(streamJSON));
+ }
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamStopButton) {
+ var streamJSON = {
+ action: "changeStream",
+ stream: ""
+ }
+ streamWindow.eventBridge.emitScriptEvent(JSON.stringify(streamJSON));
+ }
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleIncreaseVolumeButton) {
+ volume += 0.2;
+ var volumeJSON = {
+ action: "changeVolume",
+ volume: volume
+ }
+ streamWindow.eventBridge.emitScriptEvent(JSON.stringify(volumeJSON));
+ }
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleDecreaseVolumeButton) {
+ volume -= 0.2;
+ var volumeJSON = {
+ action: "changeVolume",
+ volume: volume
+ }
+ streamWindow.eventBridge.emitScriptEvent(JSON.stringify(volumeJSON));
+ }
+}
+
+// Call function.
+Controller.mousePressEvent.connect(mousePressEvent);
+streamWindow.setVisible(false);
+
+// Function to delete overlays upon exit.
+function onScriptEnding() {
+ Overlays.deleteOverlay(toggleStreamURLButton);
+ Overlays.deleteOverlay(toggleStreamPlayButton);
+ Overlays.deleteOverlay(toggleStreamStopButton);
+ Overlays.deleteOverlay(toggleIncreaseVolumeButton);
+ Overlays.deleteOverlay(toggleDecreaseVolumeButton);
+}
+
+// Call function.
+Script.scriptEnding.connect(onScriptEnding);
\ No newline at end of file
diff --git a/examples/example/entities/jsstreamplayerdomain-zone-entity.js b/examples/example/entities/jsstreamplayerdomain-zone-entity.js
new file mode 100644
index 0000000000..9a8cb8b8b4
--- /dev/null
+++ b/examples/example/entities/jsstreamplayerdomain-zone-entity.js
@@ -0,0 +1,33 @@
+//
+// #20628: JS Stream Player Domain-Zone-Entity
+// ********************************************
+//
+// Created by Kevin M. Thomas and Thoys 07/20/15.
+// Copyright 2015 High Fidelity, Inc.
+// kevintown.net
+//
+// JavaScript for the High Fidelity interface that is an entity script to be placed in a chosen entity inside a domain-zone.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+
+// Function which exists inside of an entity which triggers as a user approches it.
+(function() {
+ const SCRIPT_NAME = "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.js";
+ function isScriptRunning(script) {
+ script = script.toLowerCase().trim();
+ var runningScripts = ScriptDiscoveryService.getRunning();
+ for (i in runningScripts) {
+ if (runningScripts[i].url.toLowerCase().trim() == script) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ if (!isScriptRunning(SCRIPT_NAME)) {
+ Script.load(SCRIPT_NAME);
+ }
+})
\ No newline at end of file
diff --git a/examples/example/games/planky.js b/examples/example/games/planky.js
index 00a4e7f61d..8abc697353 100644
--- a/examples/example/games/planky.js
+++ b/examples/example/games/planky.js
@@ -12,29 +12,297 @@
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
-const NUM_LAYERS = 16;
-const BASE_DIMENSION = { x: 7, y: 2, z: 7 };
-const BLOCKS_PER_LAYER = 3;
-const BLOCK_SIZE = {x: 0.2, y: 0.1, z: 0.8};
-const BLOCK_SPACING = BLOCK_SIZE.x / 3;
+Script.include("../../libraries/toolBars.js");
+
+const DEFAULT_NUM_LAYERS = 16;
+const DEFAULT_BASE_DIMENSION = { x: 7, y: 2, z: 7 };
+const DEFAULT_BLOCKS_PER_LAYER = 3;
+const DEFAULT_BLOCK_SIZE = {x: 0.2, y: 0.1, z: 0.8};
+const DEFAULT_BLOCK_SPACING = DEFAULT_BLOCK_SIZE.x / DEFAULT_BLOCKS_PER_LAYER;
// BLOCK_HEIGHT_VARIATION removes a random percentages of the default block height per block. (for example 0.001 %)
-const BLOCK_HEIGHT_VARIATION = 0.001;
-const GRAVITY = {x: 0, y: -2.8, z: 0};
-const DENSITY = 2000;
-const DAMPING_FACTOR = 0.98;
-const ANGULAR_DAMPING_FACTOR = 0.8;
-const FRICTION = 0.99;
-const RESTITUTION = 0.0;
-const SPAWN_DISTANCE = 3;
-const BLOCK_YAW_OFFSET = 45;
+const DEFAULT_BLOCK_HEIGHT_VARIATION = 0.001;
+const DEFAULT_GRAVITY = {x: 0, y: -2.8, z: 0};
+const DEFAULT_DENSITY = 2000;
+const DEFAULT_DAMPING_FACTOR = 0.98;
+const DEFAULT_ANGULAR_DAMPING_FACTOR = 0.8;
+const DEFAULT_FRICTION = 0.99;
+const DEFAULT_RESTITUTION = 0.0;
+const DEFAULT_SPAWN_DISTANCE = 3;
+const DEFAULT_BLOCK_YAW_OFFSET = 45;
+
+var editMode = false;
+
const BUTTON_DIMENSIONS = {width: 49, height: 49};
const MAXIMUM_PERCENTAGE = 100.0;
+const NO_ANGLE = 0;
+const RIGHT_ANGLE = 90;
var windowWidth = Window.innerWidth;
var size;
var pieces = [];
var ground = false;
var layerRotated = false;
+var button;
+var cogButton;
+var toolBar;
+
+SettingsWindow = function() {
+ var _this = this;
+ this.plankyStack = null;
+ this.webWindow = null;
+ this.init = function(plankyStack) {
+ _this.webWindow = new WebWindow('Planky', Script.resolvePath('../../html/plankySettings.html'), 255, 500, true);
+ _this.webWindow.setVisible(false);
+ _this.webWindow.eventBridge.webEventReceived.connect(_this.onWebEventReceived);
+ _this.plankyStack = plankyStack;
+ };
+ this.sendData = function(data) {
+ _this.webWindow.eventBridge.emitScriptEvent(JSON.stringify(data));
+ };
+ this.onWebEventReceived = function(data) {
+ data = JSON.parse(data);
+ switch (data.action) {
+ case 'loaded':
+ _this.sendData({action: 'load', options: _this.plankyStack.options.getJSON()})
+ break;
+ case 'value-change':
+ _this.plankyStack.onValueChanged(data.option, data.value);
+ break;
+ case 'factory-reset':
+ _this.plankyStack.options.factoryReset();
+ _this.sendData({action: 'load', options: _this.plankyStack.options.getJSON()})
+ break;
+ case 'save-default':
+ _this.plankyStack.options.save();
+ break;
+ case 'cleanup':
+ _this.plankyStack.deRez();
+ break;
+ default:
+ Window.alert('[planky] unknown action ' + data.action);
+ }
+ };
+};
+
+PlankyOptions = function() {
+ var _this = this;
+ this.factoryReset = function() {
+ _this.setDefaults();
+ Settings.setValue('plankyOptions', '');
+ };
+ this.save = function() {
+ Settings.setValue('plankyOptions', JSON.stringify(_this.getJSON()));
+ };
+ this.load = function() {
+ _this.setDefaults();
+ var plankyOptions = Settings.getValue('plankyOptions')
+ if (plankyOptions === null || plankyOptions === '') {
+ return;
+ }
+ var options = JSON.parse(plankyOptions);
+ for (option in options) {
+ _this[option] = options[option];
+ }
+ };
+ this.getJSON = function() {
+ return {
+ numLayers: _this.numLayers,
+ baseDimension: _this.baseDimension,
+ blocksPerLayer: _this.blocksPerLayer,
+ blockSize: _this.blockSize,
+ blockSpacing: _this.blockSpacing,
+ blockHeightVariation: _this.blockHeightVariation,
+ gravity: _this.gravity,
+ density: _this.density,
+ dampingFactor: _this.dampingFactor,
+ angularDampingFactor: _this.angularDampingFactor,
+ friction: _this.friction,
+ restitution: _this.restitution,
+ spawnDistance: _this.spawnDistance,
+ blockYawOffset: _this.blockYawOffset,
+ };
+ }
+ this.setDefaults = function() {
+ _this.numLayers = DEFAULT_NUM_LAYERS;
+ _this.baseDimension = DEFAULT_BASE_DIMENSION;
+ _this.blocksPerLayer = DEFAULT_BLOCKS_PER_LAYER;
+ _this.blockSize = DEFAULT_BLOCK_SIZE;
+ _this.blockSpacing = DEFAULT_BLOCK_SPACING;
+ _this.blockHeightVariation = DEFAULT_BLOCK_HEIGHT_VARIATION;
+ _this.gravity = DEFAULT_GRAVITY;
+ _this.density = DEFAULT_DENSITY;
+ _this.dampingFactor = DEFAULT_DAMPING_FACTOR;
+ _this.angularDampingFactor = DEFAULT_ANGULAR_DAMPING_FACTOR;
+ _this.friction = DEFAULT_FRICTION;
+ _this.restitution = DEFAULT_RESTITUTION;
+ _this.spawnDistance = DEFAULT_SPAWN_DISTANCE;
+ _this.blockYawOffset = DEFAULT_BLOCK_YAW_OFFSET;
+ };
+ this.load();
+};
+
+// The PlankyStack exists out of rows and layers
+PlankyStack = function() {
+ var _this = this;
+ this.planks = [];
+ this.ground = false;
+ this.editLines = [];
+ this.options = new PlankyOptions();
+
+ this.deRez = function() {
+ _this.planks.forEach(function(plank) {
+ Entities.deleteEntity(plank.entity);
+ });
+ _this.planks = [];
+ if (_this.ground) {
+ Entities.deleteEntity(_this.ground);
+ }
+ _this.editLines.forEach(function(line) {
+ Entities.deleteEntity(line);
+ })
+ _this.editLines = [];
+ if (_this.centerLine) {
+ Entities.deleteEntity(_this.centerLine);
+ }
+ _this.ground = false;
+ _this.centerLine = false;
+ };
+
+ this.rez = function() {
+ if (_this.planks.length > 0) {
+ _this.deRez();
+ }
+ _this.baseRotation = Quat.fromPitchYawRollDegrees(0.0, MyAvatar.bodyYaw, 0.0);
+ var basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(_this.options.spawnDistance, Quat.getFront(_this.baseRotation)));
+ basePosition.y = grabLowestJointY();
+ _this.basePosition = basePosition;
+ _this.refresh();
+ };
+
+ //private function
+ var refreshGround = function() {
+ if (!_this.ground) {
+ _this.ground = Entities.addEntity({
+ type: 'Model',
+ modelURL: HIFI_PUBLIC_BUCKET + 'eric/models/woodFloor.fbx',
+ dimensions: _this.options.baseDimension,
+ position: Vec3.sum(_this.basePosition, {y: -(_this.options.baseDimension.y / 2)}),
+ rotation: _this.baseRotation,
+ shapeType: 'box'
+ });
+ return;
+ }
+ // move ground to rez position/rotation
+ Entities.editEntity(_this.ground, {dimensions: _this.options.baseDimension, position: Vec3.sum(_this.basePosition, {y: -(_this.options.baseDimension.y / 2)}), rotation: _this.baseRotation});
+ };
+
+ var refreshLines = function() {
+ if (_this.editLines.length === 0) {
+ _this.editLines.push(Entities.addEntity({
+ type: 'Line',
+ dimensions: {x: 5, y: 21, z: 5},
+ position: Vec3.sum(_this.basePosition, {y: -(_this.options.baseDimension.y / 2)}),
+ lineWidth: 7,
+ color: {red: 20, green: 20, blue: 20},
+ linePoints: [{x: 0, y: 0, z: 0}, {x: 0, y: 10, z: 0}],
+ visible: editMode
+ }));
+ return;
+ }
+ _this.editLines.forEach(function(line) {
+ Entities.editEntity(line, {visible: editMode});
+ })
+ };
+
+ var trimDimension = function(dimension, maxIndex) {
+ var removingPlanks = [];
+ _this.planks.forEach(function(plank, index, object) {
+ if (plank[dimension] > maxIndex) {
+ removingPlanks.push(index);
+ }
+ });
+ removingPlanks.reverse();
+ for (var i = 0; i < removingPlanks.length; i++) {
+ Entities.deleteEntity(_this.planks[removingPlanks[i]].entity);
+ _this.planks.splice(removingPlanks[i], 1);
+ }
+ };
+
+ var createOrUpdate = function(layer, row) {
+ var found = false;
+ var layerRotated = layer % 2 === 0;
+ var layerRotation = Quat.fromPitchYawRollDegrees(0, layerRotated ? NO_ANGLE : RIGHT_ANGLE, 0.0);
+ var blockPositionXZ = (row - (_this.options.blocksPerLayer / 2) + 0.5) * (_this.options.blockSpacing + _this.options.blockSize.x);
+ var localTransform = Vec3.multiplyQbyV(_this.offsetRot, {
+ x: (layerRotated ? blockPositionXZ : 0),
+ y: (_this.options.blockSize.y / 2) + (_this.options.blockSize.y * layer),
+ z: (layerRotated ? 0 : blockPositionXZ)
+ });
+ var newProperties = {
+ type: 'Model',
+ modelURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/block.fbx',
+ shapeType: 'box',
+ name: 'PlankyBlock' + layer + '-' + row,
+ dimensions: Vec3.sum(_this.options.blockSize, {x: 0, y: -((_this.options.blockSize.y * (_this.options.blockHeightVariation / MAXIMUM_PERCENTAGE)) * Math.random()), z: 0}),
+ position: Vec3.sum(_this.basePosition, localTransform),
+ rotation: Quat.multiply(layerRotation, _this.offsetRot),
+ damping: _this.options.dampingFactor,
+ restitution: _this.options.restitution,
+ friction: _this.options.friction,
+ angularDamping: _this.options.angularDampingFactor,
+ gravity: _this.options.gravity,
+ density: _this.options.density,
+ velocity: {x: 0, y: 0, z: 0},
+ angularVelocity: Quat.fromPitchYawRollDegrees(0, 0, 0),
+ ignoreForCollisions: true
+ };
+ _this.planks.forEach(function(plank, index, object) {
+ if (plank.layer === layer && plank.row === row) {
+ Entities.editEntity(plank.entity, newProperties);
+ found = true;
+ // break loop:
+ return false;
+ }
+ });
+ if (!found) {
+ _this.planks.push({layer: layer, row: row, entity: Entities.addEntity(newProperties)})
+ }
+ };
+
+ this.onValueChanged = function(option, value) {
+ _this.options[option] = value;
+ if (['numLayers', 'blocksPerLayer', 'blockSize', 'blockSpacing', 'blockHeightVariation'].indexOf(option) !== -1) {
+ _this.refresh();
+ }
+ };
+
+ this.refresh = function() {
+ refreshGround();
+ refreshLines();
+ trimDimension('layer', _this.options.numLayers - 1);
+ trimDimension('row', _this.options.blocksPerLayer - 1);
+ _this.offsetRot = Quat.multiply(_this.baseRotation, Quat.fromPitchYawRollDegrees(0.0, _this.options.blockYawOffset, 0.0));
+ for (var layer = 0; layer < _this.options.numLayers; layer++) {
+ for (var row = 0; row < _this.options.blocksPerLayer; row++) {
+ createOrUpdate(layer, row);
+ }
+ }
+ if (!editMode) {
+ _this.planks.forEach(function(plank, index, object) {
+ Entities.editEntity(plank.entity, {ignoreForCollisions: false, collisionsWillMove: true});
+ });
+ }
+ };
+
+ this.isFound = function() {
+ //TODO: identify entities here until one is found
+ return _this.planks.length > 0;
+ };
+};
+
+var settingsWindow = new SettingsWindow();
+var plankyStack = new PlankyStack();
+settingsWindow.init(plankyStack);
function grabLowestJointY() {
var jointNames = MyAvatar.getJointNames();
@@ -47,108 +315,60 @@ function grabLowestJointY() {
return floorY;
}
-function getButtonPosX() {
- return windowWidth - ((BUTTON_DIMENSIONS.width / 2) + BUTTON_DIMENSIONS.width);
-}
+toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.games.planky", function (windowDimensions, toolbar) {
+ return {
+ x: windowDimensions.x - (toolbar.width * 1.1),
+ y: toolbar.height / 2
+ };
+});
-var button = Overlays.addOverlay('image', {
- x: getButtonPosX(),
- y: 10,
+button = toolBar.addTool({
width: BUTTON_DIMENSIONS.width,
height: BUTTON_DIMENSIONS.height,
imageURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/planky_button.svg',
- alpha: 0.8
+ alpha: 0.8,
+ visible: true
});
+cogButton = toolBar.addTool({
+ width: BUTTON_DIMENSIONS.width,
+ height: BUTTON_DIMENSIONS.height,
+ imageURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/cog.svg',
+ subImage: { x: 0, y: BUTTON_DIMENSIONS.height, width: BUTTON_DIMENSIONS.width, height: BUTTON_DIMENSIONS.height },
+ alpha: 0.8,
+ visible: true
+}, true, false);
-function resetBlocks() {
- pieces.forEach(function(piece) {
- Entities.deleteEntity(piece);
- });
- pieces = [];
- var avatarRot = Quat.fromPitchYawRollDegrees(0.0, MyAvatar.bodyYaw, 0.0);
- basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(SPAWN_DISTANCE, Quat.getFront(avatarRot)));
- basePosition.y = grabLowestJointY() - (BASE_DIMENSION.y / 2);
- if (!ground) {
- ground = Entities.addEntity({
- type: 'Model',
- modelURL: HIFI_PUBLIC_BUCKET + 'eric/models/woodFloor.fbx',
- dimensions: BASE_DIMENSION,
- position: basePosition,
- rotation: avatarRot,
- shapeType: 'box'
- });
- } else {
- Entities.editEntity(ground, {position: basePosition, rotation: avatarRot});
- }
- var offsetRot = Quat.multiply(avatarRot, Quat.fromPitchYawRollDegrees(0.0, BLOCK_YAW_OFFSET, 0.0));
- basePosition.y += (BASE_DIMENSION.y / 2);
- for (var layerIndex = 0; layerIndex < NUM_LAYERS; layerIndex++) {
- var layerRotated = layerIndex % 2 === 0;
- var offset = -(BLOCK_SPACING);
- var layerRotation = Quat.fromPitchYawRollDegrees(0, layerRotated ? 0 : 90, 0.0);
- for (var blockIndex = 0; blockIndex < BLOCKS_PER_LAYER; blockIndex++) {
- var blockPositionXZ = BLOCK_SIZE.x * blockIndex - (BLOCK_SIZE.x * 3 / 2 - BLOCK_SIZE.x / 2);
- var localTransform = Vec3.multiplyQbyV(offsetRot, {
- x: (layerRotated ? blockPositionXZ + offset: 0),
- y: (BLOCK_SIZE.y / 2) + (BLOCK_SIZE.y * layerIndex),
- z: (layerRotated ? 0 : blockPositionXZ + offset)
- });
- pieces.push(Entities.addEntity({
- type: 'Model',
- modelURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/block.fbx',
- shapeType: 'box',
- name: 'PlankyBlock' + ((layerIndex * BLOCKS_PER_LAYER) + blockIndex),
- dimensions: {
- x: BLOCK_SIZE.x,
- y: BLOCK_SIZE.y - ((BLOCK_SIZE.y * (BLOCK_HEIGHT_VARIATION / MAXIMUM_PERCENTAGE)) * Math.random()),
- z: BLOCK_SIZE.z
- },
- position: {
- x: basePosition.x + localTransform.x,
- y: basePosition.y + localTransform.y,
- z: basePosition.z + localTransform.z
- },
- rotation: Quat.multiply(layerRotation, offsetRot),
- collisionsWillMove: true,
- damping: DAMPING_FACTOR,
- restitution: RESTITUTION,
- friction: FRICTION,
- angularDamping: ANGULAR_DAMPING_FACTOR,
- gravity: GRAVITY,
- density: DENSITY
- }));
- offset += BLOCK_SPACING;
+Controller.mousePressEvent.connect(function(event) {
+ var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
+ if (toolBar.clicked(clickedOverlay) === button) {
+ if (!plankyStack.isFound()) {
+ plankyStack.rez();
+ return;
+ }
+ plankyStack.refresh();
+ } else if (toolBar.clicked(clickedOverlay) === cogButton) {
+ editMode = !editMode;
+ toolBar.selectTool(cogButton, editMode);
+ settingsWindow.webWindow.setVisible(editMode);
+ if(plankyStack.planks.length) {
+ plankyStack.refresh();
}
}
-}
+});
-function mousePressEvent(event) {
- var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
- if (clickedOverlay === button) {
- resetBlocks();
+Script.update.connect(function() {
+ if (windowWidth !== Window.innerWidth) {
+ windowWidth = Window.innerWidth;
+ Overlays.editOverlay(button, {x: getButtonPosX()});
+ Overlays.editOverlay(cogButton, {x: getCogButtonPosX()});
}
-}
+})
-Controller.mousePressEvent.connect(mousePressEvent);
-
-function cleanup() {
- Overlays.deleteOverlay(button);
+Script.scriptEnding.connect(function() {
+ toolBar.cleanup();
if (ground) {
Entities.deleteEntity(ground);
}
- pieces.forEach(function(piece) {
- Entities.deleteEntity(piece);
- });
- pieces = [];
-}
-
-function onUpdate() {
- if (windowWidth != Window.innerWidth) {
- windowWidth = Window.innerWidth;
- Overlays.editOverlay(button, {x: getButtonPosX()});
- }
-}
-
-Script.update.connect(onUpdate)
-Script.scriptEnding.connect(cleanup);
+ plankyStack.deRez();
+});
diff --git a/examples/html/jsstreamplayer.html b/examples/html/jsstreamplayer.html
new file mode 100644
index 0000000000..7cf827309c
--- /dev/null
+++ b/examples/html/jsstreamplayer.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/html/plankySettings.html b/examples/html/plankySettings.html
new file mode 100644
index 0000000000..0eea4948ad
--- /dev/null
+++ b/examples/html/plankySettings.html
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/particles.js b/examples/particles.js
index c26458a9af..deb6228fff 100644
--- a/examples/particles.js
+++ b/examples/particles.js
@@ -44,6 +44,7 @@
emitStrength: emitStrength,
emitDirection: emitDirection,
color: color,
+ lifespan: 1.0,
visible: true,
locked: false });
@@ -67,13 +68,13 @@
var objs = [];
function Init() {
objs.push(new TestBox());
- objs.push(new TestFx({ red: 255, blue: 0, green: 0 },
+ objs.push(new TestFx({ red: 255, green: 0, blue: 0 },
{ x: 0.5, y: 1.0, z: 0.0 },
100, 3, 1));
- objs.push(new TestFx({ red: 0, blue: 255, green: 0 },
+ objs.push(new TestFx({ red: 0, green: 255, blue: 0 },
{ x: 0, y: 1, z: 0 },
1000, 5, 0.5));
- objs.push(new TestFx({ red: 0, blue: 0, green: 255 },
+ objs.push(new TestFx({ red: 0, green: 0, blue: 255 },
{ x: -0.5, y: 1, z: 0 },
100, 3, 1));
}
diff --git a/examples/zones/jsstreamplayerdomain-zone-entity.js b/examples/zones/jsstreamplayerdomain-zone-entity.js
new file mode 100644
index 0000000000..9a8cb8b8b4
--- /dev/null
+++ b/examples/zones/jsstreamplayerdomain-zone-entity.js
@@ -0,0 +1,33 @@
+//
+// #20628: JS Stream Player Domain-Zone-Entity
+// ********************************************
+//
+// Created by Kevin M. Thomas and Thoys 07/20/15.
+// Copyright 2015 High Fidelity, Inc.
+// kevintown.net
+//
+// JavaScript for the High Fidelity interface that is an entity script to be placed in a chosen entity inside a domain-zone.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+
+// Function which exists inside of an entity which triggers as a user approches it.
+(function() {
+ const SCRIPT_NAME = "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.js";
+ function isScriptRunning(script) {
+ script = script.toLowerCase().trim();
+ var runningScripts = ScriptDiscoveryService.getRunning();
+ for (i in runningScripts) {
+ if (runningScripts[i].url.toLowerCase().trim() == script) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ if (!isScriptRunning(SCRIPT_NAME)) {
+ Script.load(SCRIPT_NAME);
+ }
+})
\ No newline at end of file
diff --git a/examples/zones/jsstreamplayerdomain-zone.html b/examples/zones/jsstreamplayerdomain-zone.html
new file mode 100644
index 0000000000..28b2202591
--- /dev/null
+++ b/examples/zones/jsstreamplayerdomain-zone.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/zones/jsstreamplayerdomain-zone.js b/examples/zones/jsstreamplayerdomain-zone.js
new file mode 100644
index 0000000000..33d7364709
--- /dev/null
+++ b/examples/zones/jsstreamplayerdomain-zone.js
@@ -0,0 +1,176 @@
+//
+// #20628: JS Stream Player Domain-Zone
+// *************************************
+//
+// Created by Kevin M. Thomas, Thoys and Konstantin 07/24/15.
+// Copyright 2015 High Fidelity, Inc.
+// kevintown.net
+//
+// JavaScript for the High Fidelity interface that creates a stream player with a UI for playing a domain-zone specificed stream URL in addition to play, stop and volume functionality which is resident only in the domain-zone.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+
+// Declare variables and set up new WebWindow.
+var lastZone = "";
+var volume = 0.5;
+var stream = "";
+var streamWindow = new WebWindow('Stream', "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.html", 0, 0, false);
+var visible = false;
+
+// Set up toggleStreamPlayButton overlay.
+var toggleStreamPlayButton = Overlays.addOverlay("text", {
+ x: 122,
+ y: 310,
+ width: 38,
+ height: 28,
+ backgroundColor: { red: 0, green: 0, blue: 0},
+ color: { red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8,
+ visible: false,
+ text: " Play"
+});
+
+// Set up toggleStreamStopButton overlay.
+var toggleStreamStopButton = Overlays.addOverlay("text", {
+ x: 166,
+ y: 310,
+ width: 40,
+ height: 28,
+ backgroundColor: { red: 0, green: 0, blue: 0},
+ color: { red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8,
+ visible: false,
+ text: " Stop"
+});
+
+// Set up increaseVolumeButton overlay.
+var toggleIncreaseVolumeButton = Overlays.addOverlay("text", {
+ x: 211,
+ y: 310,
+ width: 18,
+ height: 28,
+ backgroundColor: { red: 0, green: 0, blue: 0},
+ color: { red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8,
+ visible: false,
+ text: " +"
+});
+
+// Set up decreaseVolumeButton overlay.
+var toggleDecreaseVolumeButton = Overlays.addOverlay("text", {
+ x: 234,
+ y: 310,
+ width: 15,
+ height: 28,
+ backgroundColor: { red: 0, green: 0, blue: 0},
+ color: { red: 255, green: 255, blue: 0},
+ font: {size: 15},
+ topMargin: 8,
+ visible: false,
+ text: " -"
+});
+
+// Function to change JSON object stream.
+function changeStream(stream) {
+ var streamJSON = {
+ action: "changeStream",
+ stream: stream
+ }
+ streamWindow.eventBridge.emitScriptEvent(JSON.stringify(streamJSON));
+}
+
+// Function to change JSON object volume.
+function changeVolume(volume) {
+ var volumeJSON = {
+ action: "changeVolume",
+ volume: volume
+ }
+ streamWindow.eventBridge.emitScriptEvent(JSON.stringify(volumeJSON));
+}
+
+// Function that adds mousePressEvent functionality to connect UI to enter stream URL, play and stop stream.
+function mousePressEvent(event) {
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamPlayButton) {
+ changeStream(stream);
+ volume = 0.25;
+ changeVolume(volume);
+ }
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamStopButton) {
+ changeStream("");
+ }
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleIncreaseVolumeButton) {
+ volume += 0.25;
+ changeVolume(volume);
+ }
+ if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleDecreaseVolumeButton) {
+ volume -= 0.25;
+ changeVolume(volume);
+ }
+}
+
+// Function checking bool if in proper zone.
+function isOurZone(properties) {
+ return stream != "" && properties.type == "Zone";
+}
+
+// Function to toggle visibile the overlay.
+function toggleVisible(newVisibility) {
+ if (newVisibility != visible) {
+ visible = newVisibility;
+ Overlays.editOverlay(toggleStreamPlayButton, {visible: visible});
+ Overlays.editOverlay(toggleStreamStopButton, {visible: visible});
+ Overlays.editOverlay(toggleIncreaseVolumeButton, {visible: visible});
+ Overlays.editOverlay(toggleDecreaseVolumeButton, {visible: visible});
+ }
+}
+
+// Function to check if avatar is in proper domain.
+Window.domainChanged.connect(function() {
+ Script.stop();
+});
+
+// Function to check if avatar is within zone.
+Entities.enterEntity.connect(function(entityID) {
+ print("Entered..." + JSON.stringify(entityID));
+ var properties = Entities.getEntityProperties(entityID);
+ stream = properties.userData;
+ if(isOurZone(properties))
+ {
+ lastZone = properties.name;
+ toggleVisible(true);
+ }
+})
+
+// Function to check if avatar is leaving zone.
+Entities.leaveEntity.connect(function(entityID) {
+ print("Left..." + JSON.stringify(entityID));
+ var properties = Entities.getEntityProperties(entityID);
+ if (properties.name == lastZone && properties.type == "Zone") {
+ print("Leaving Zone!");
+ toggleVisible(false);
+ changeStream("");
+ }
+})
+
+// Function to delete overlays upon exit.
+function onScriptEnding() {
+ Overlays.deleteOverlay(toggleStreamPlayButton);
+ Overlays.deleteOverlay(toggleStreamStopButton);
+ Overlays.deleteOverlay(toggleIncreaseVolumeButton);
+ Overlays.deleteOverlay(toggleDecreaseVolumeButton);
+ changeStream("");
+ streamWindow.deleteLater();
+}
+
+// Connect mouse and hide WebWindow.
+Controller.mousePressEvent.connect(mousePressEvent);
+streamWindow.setVisible(false);
+
+// Call function upon ending script.
+Script.scriptEnding.connect(onScriptEnding);
\ No newline at end of file
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 4f53b85f83..9c0c0e80fc 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -334,7 +334,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow()),
_isVSyncOn(true),
- _isThrottleFPSEnabled(false),
+ _isThrottleFPSEnabled(true),
_aboutToQuit(false),
_notifiedPacketVersionMismatchThisDomain(false),
_domainConnectionRefusals(QList()),
@@ -3529,6 +3529,8 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
renderContext._drawItemStatus = sceneInterface->doEngineDisplayItemStatus();
+ renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion);
+
renderArgs->_shouldRender = LODManager::shouldRender;
renderContext.args = renderArgs;
@@ -3841,24 +3843,15 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
int statsMessageLength = 0;
const QUuid& nodeUUID = sendingNode->getUUID();
- OctreeSceneStats* octreeStats;
-
+
// now that we know the node ID, let's add these stats to the stats for that node...
_octreeSceneStatsLock.lockForWrite();
- auto it = _octreeServerSceneStats.find(nodeUUID);
- if (it != _octreeServerSceneStats.end()) {
- octreeStats = &it->second;
- statsMessageLength = octreeStats->unpackFromPacket(packet);
- } else {
- OctreeSceneStats temp;
- statsMessageLength = temp.unpackFromPacket(packet);
- octreeStats = &temp;
- }
+
+ OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID];
+ statsMessageLength = octreeStats.unpackFromPacket(packet);
+
_octreeSceneStatsLock.unlock();
- VoxelPositionSize rootDetails;
- voxelDetailsForCode(octreeStats->getJurisdictionRoot(), rootDetails);
-
// see if this is the first we've heard of this node...
NodeToJurisdictionMap* jurisdiction = NULL;
QString serverType;
@@ -3870,6 +3863,9 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
jurisdiction->lockForRead();
if (jurisdiction->find(nodeUUID) == jurisdiction->end()) {
jurisdiction->unlock();
+
+ VoxelPositionSize rootDetails;
+ voxelDetailsForCode(octreeStats.getJurisdictionRoot(), rootDetails);
qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]",
qPrintable(serverType),
@@ -3882,7 +3878,7 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
// but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the
// details from the OctreeSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap;
- jurisdictionMap.copyContents(octreeStats->getJurisdictionRoot(), octreeStats->getJurisdictionEndNodes());
+ jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes());
jurisdiction->lockForWrite();
(*jurisdiction)[nodeUUID] = jurisdictionMap;
jurisdiction->unlock();
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index e24ba1c492..890c1ef96a 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -313,7 +313,7 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere,
0, // QML Qt::SHIFT | Qt::Key_A,
true);
- addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion);
+ addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion);
MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);
@@ -351,7 +351,7 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::RenderTargetFramerateVSyncOn, 0, true,
qApp, SLOT(setVSyncEnabled()));
#endif
- addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, false,
+ addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, true,
qApp, SLOT(setThrottleFPSEnabled()));
}
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 896be9969e..276ea44043 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -133,7 +133,6 @@ namespace MenuOption {
const QString AddressBar = "Show Address Bar";
const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
const QString AlternateIK = "Alternate IK";
- const QString AmbientOcclusion = "Ambient Occlusion";
const QString Animations = "Animations...";
const QString Atmosphere = "Atmosphere";
const QString Attachments = "Attachments...";
@@ -164,6 +163,7 @@ namespace MenuOption {
const QString ControlWithSpeech = "Control With Speech";
const QString CopyAddress = "Copy Address to Clipboard";
const QString CopyPath = "Copy Path to Clipboard";
+ const QString DebugAmbientOcclusion = "Debug Ambient Occlusion";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DeleteBookmark = "Delete Bookmark...";
const QString DisableActivityLogger = "Disable Activity Logger";
diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt
index 810d5f5843..e9adf2b750 100644
--- a/libraries/entities-renderer/CMakeLists.txt
+++ b/libraries/entities-renderer/CMakeLists.txt
@@ -1,5 +1,7 @@
set(TARGET_NAME entities-renderer)
+AUTOSCRIBE_SHADER_LIB(gpu model render)
+
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Widgets OpenGL Network Script)
diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
index 48ac83dfc2..2b4626c2c3 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
@@ -14,22 +14,141 @@
#include
#include
#include
+#include
#include "EntitiesRendererLogging.h"
#include "RenderableParticleEffectEntityItem.h"
+#include "untextured_particle_vert.h"
+#include "untextured_particle_frag.h"
+#include "textured_particle_vert.h"
+#include "textured_particle_frag.h"
+
+class ParticlePayload {
+public:
+ typedef render::Payload Payload;
+ typedef Payload::DataPointer Pointer;
+ typedef RenderableParticleEffectEntityItem::Vertex Vertex;
+
+ ParticlePayload() : _vertexFormat(std::make_shared()),
+ _vertexBuffer(std::make_shared()),
+ _indexBuffer(std::make_shared()) {
+ _vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element::VEC3F_XYZ, 0);
+ _vertexFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), offsetof(Vertex, uv));
+ _vertexFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::COLOR_RGBA_32, offsetof(Vertex, rgba));
+ }
+
+ void setPipeline(gpu::PipelinePointer pipeline) { _pipeline = pipeline; }
+ const gpu::PipelinePointer& getPipeline() const { return _pipeline; }
+
+ const Transform& getModelTransform() const { return _modelTransform; }
+ void setModelTransform(const Transform& modelTransform) { _modelTransform = modelTransform; }
+
+ const AABox& getBound() const { return _bound; }
+ void setBound(AABox& bound) { _bound = bound; }
+
+ gpu::BufferPointer getVertexBuffer() { return _vertexBuffer; }
+ const gpu::BufferPointer& getVertexBuffer() const { return _vertexBuffer; }
+
+ gpu::BufferPointer getIndexBuffer() { return _indexBuffer; }
+ const gpu::BufferPointer& getIndexBuffer() const { return _indexBuffer; }
+
+ void setTexture(gpu::TexturePointer texture) { _texture = texture; }
+ const gpu::TexturePointer& getTexture() const { return _texture; }
+
+ bool getVisibleFlag() const { return _visibleFlag; }
+ void setVisibleFlag(bool visibleFlag) { _visibleFlag = visibleFlag; }
+
+ void render(RenderArgs* args) const {
+ assert(_pipeline);
+
+ gpu::Batch& batch = *args->_batch;
+ batch.setPipeline(_pipeline);
+
+ if (_texture) {
+ batch.setResourceTexture(0, _texture);
+ }
+
+ batch.setModelTransform(_modelTransform);
+ batch.setInputFormat(_vertexFormat);
+ batch.setInputBuffer(0, _vertexBuffer, 0, sizeof(Vertex));
+ batch.setIndexBuffer(gpu::UINT16, _indexBuffer, 0);
+
+ auto numIndices = _indexBuffer->getSize() / sizeof(uint16_t);
+ batch.drawIndexed(gpu::TRIANGLES, numIndices);
+ }
+
+protected:
+ Transform _modelTransform;
+ AABox _bound;
+ gpu::PipelinePointer _pipeline;
+ gpu::Stream::FormatPointer _vertexFormat;
+ gpu::BufferPointer _vertexBuffer;
+ gpu::BufferPointer _indexBuffer;
+ gpu::TexturePointer _texture;
+ bool _visibleFlag = true;
+};
+
+namespace render {
+ template <>
+ const ItemKey payloadGetKey(const ParticlePayload::Pointer& payload) {
+ if (payload->getVisibleFlag()) {
+ return ItemKey::Builder::transparentShape();
+ } else {
+ return ItemKey::Builder().withInvisible().build();
+ }
+ }
+
+ template <>
+ const Item::Bound payloadGetBound(const ParticlePayload::Pointer& payload) {
+ return payload->getBound();
+ }
+
+ template <>
+ void payloadRender(const ParticlePayload::Pointer& payload, RenderArgs* args) {
+ payload->render(args);
+ }
+}
+
+gpu::PipelinePointer RenderableParticleEffectEntityItem::_texturedPipeline;
+gpu::PipelinePointer RenderableParticleEffectEntityItem::_untexturedPipeline;
+
EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return std::make_shared(entityID, properties);
}
RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
ParticleEffectEntityItem(entityItemID, properties) {
- _cacheID = DependencyManager::get()->allocateID();
+
+ // lazy creation of particle system pipeline
+ if (!_untexturedPipeline && !_texturedPipeline) {
+ createPipelines();
+ }
}
-void RenderableParticleEffectEntityItem::render(RenderArgs* args) {
- Q_ASSERT(getType() == EntityTypes::ParticleEffect);
- PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render");
+bool RenderableParticleEffectEntityItem::addToScene(EntityItemPointer self,
+ render::ScenePointer scene,
+ render::PendingChanges& pendingChanges) {
+
+ auto particlePayload = std::shared_ptr(new ParticlePayload());
+ particlePayload->setPipeline(_untexturedPipeline);
+ _renderItemId = scene->allocateID();
+ auto renderData = ParticlePayload::Pointer(particlePayload);
+ auto renderPayload = render::PayloadPointer(new ParticlePayload::Payload(renderData));
+ pendingChanges.resetItem(_renderItemId, renderPayload);
+ _scene = scene;
+ return true;
+}
+
+void RenderableParticleEffectEntityItem::removeFromScene(EntityItemPointer self,
+ render::ScenePointer scene,
+ render::PendingChanges& pendingChanges) {
+ pendingChanges.removeItem(_renderItemId);
+ _scene = nullptr;
+};
+
+void RenderableParticleEffectEntityItem::update(const quint64& now) {
+ ParticleEffectEntityItem::update(now);
if (_texturesChangedFlag) {
if (_textures.isEmpty()) {
@@ -42,71 +161,155 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) {
_texturesChangedFlag = false;
}
- bool textured = _texture && _texture->isLoaded();
- updateQuads(args, textured);
-
- Q_ASSERT(args->_batch);
- gpu::Batch& batch = *args->_batch;
- if (textured) {
- batch.setResourceTexture(0, _texture->getGPUTexture());
- }
- batch.setModelTransform(getTransformToCenter());
- DependencyManager::get()->bindSimpleProgram(batch, textured);
- DependencyManager::get()->renderVertices(batch, gpu::QUADS, _cacheID);
-};
+ updateRenderItem();
+}
static glm::vec3 zSortAxis;
static bool zSort(const glm::vec3& rhs, const glm::vec3& lhs) {
return glm::dot(rhs, ::zSortAxis) > glm::dot(lhs, ::zSortAxis);
}
-void RenderableParticleEffectEntityItem::updateQuads(RenderArgs* args, bool textured) {
- float particleRadius = getParticleRadius();
- glm::vec4 particleColor(toGlm(getXColor()), getLocalRenderAlpha());
-
- glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius;
- glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius;
-
- QVector vertices;
- QVector positions;
- QVector textureCoords;
- vertices.reserve(getLivingParticleCount() * VERTS_PER_PARTICLE);
-
- if (textured) {
- textureCoords.reserve(getLivingParticleCount() * VERTS_PER_PARTICLE);
- }
- positions.reserve(getLivingParticleCount());
-
-
- for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) {
- positions.append(_particlePositions[i]);
- if (textured) {
- textureCoords.append(glm::vec2(0, 1));
- textureCoords.append(glm::vec2(1, 1));
- textureCoords.append(glm::vec2(1, 0));
- textureCoords.append(glm::vec2(0, 0));
- }
- }
-
- // sort particles back to front
- ::zSortAxis = args->_viewFrustum->getDirection();
- qSort(positions.begin(), positions.end(), zSort);
-
- for (int i = 0; i < positions.size(); i++) {
- glm::vec3 pos = (textured) ? positions[i] : _particlePositions[i];
-
- // generate corners of quad aligned to face the camera.
- vertices.append(pos + rightOffset + upOffset);
- vertices.append(pos - rightOffset + upOffset);
- vertices.append(pos - rightOffset - upOffset);
- vertices.append(pos + rightOffset - upOffset);
-
- }
-
- if (textured) {
- DependencyManager::get()->updateVertices(_cacheID, vertices, textureCoords, particleColor);
- } else {
- DependencyManager::get()->updateVertices(_cacheID, vertices, particleColor);
- }
+uint32_t toRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
+ return ((uint32_t)r | (uint32_t)g << 8 | (uint32_t)b << 16 | (uint32_t)a << 24);
}
+void RenderableParticleEffectEntityItem::updateRenderItem() {
+
+ if (!_scene) {
+ return;
+ }
+
+ float particleRadius = getParticleRadius();
+ auto xcolor = getXColor();
+ auto alpha = (uint8_t)(glm::clamp(getLocalRenderAlpha(), 0.0f, 1.0f) * 255.0f);
+ auto rgba = toRGBA(xcolor.red, xcolor.green, xcolor.blue, alpha);
+
+ // make a copy of each particle position
+ std::vector positions;
+ positions.reserve(getLivingParticleCount());
+ for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) {
+ positions.push_back(_particlePositions[i]);
+ }
+
+ // sort particles back to front
+ // NOTE: this is view frustum might be one frame out of date.
+ auto frustum = AbstractViewStateInterface::instance()->getCurrentViewFrustum();
+ ::zSortAxis = frustum->getDirection();
+ qSort(positions.begin(), positions.end(), zSort);
+
+ // allocate vertices
+ _vertices.clear();
+
+ // build vertices from particle positions
+ const glm::vec3 upOffset = frustum->getUp() * particleRadius;
+ const glm::vec3 rightOffset = frustum->getRight() * particleRadius;
+ for (auto&& pos : positions) {
+ // generate corners of quad aligned to face the camera.
+ _vertices.emplace_back(pos + rightOffset + upOffset, glm::vec2(1.0f, 1.0f), rgba);
+ _vertices.emplace_back(pos - rightOffset + upOffset, glm::vec2(0.0f, 1.0f), rgba);
+ _vertices.emplace_back(pos - rightOffset - upOffset, glm::vec2(0.0f, 0.0f), rgba);
+ _vertices.emplace_back(pos + rightOffset - upOffset, glm::vec2(1.0f, 0.0f), rgba);
+ }
+
+ render::PendingChanges pendingChanges;
+ pendingChanges.updateItem(_renderItemId, [&](ParticlePayload& payload) {
+
+ // update vertex buffer
+ auto vertexBuffer = payload.getVertexBuffer();
+ size_t numBytes = sizeof(Vertex) * _vertices.size();
+ vertexBuffer->resize(numBytes);
+ gpu::Byte* data = vertexBuffer->editData();
+ memcpy(data, &(_vertices[0]), numBytes);
+
+ // FIXME, don't update index buffer if num particles has not changed.
+ // update index buffer
+ auto indexBuffer = payload.getIndexBuffer();
+ const size_t NUM_VERTS_PER_PARTICLE = 4;
+ const size_t NUM_INDICES_PER_PARTICLE = 6;
+ auto numQuads = (_vertices.size() / NUM_VERTS_PER_PARTICLE);
+ numBytes = sizeof(uint16_t) * numQuads * NUM_INDICES_PER_PARTICLE;
+ indexBuffer->resize(numBytes);
+ data = indexBuffer->editData();
+ auto indexPtr = reinterpret_cast(data);
+ for (size_t i = 0; i < numQuads; ++i) {
+ indexPtr[i * NUM_INDICES_PER_PARTICLE + 0] = i * NUM_VERTS_PER_PARTICLE + 0;
+ indexPtr[i * NUM_INDICES_PER_PARTICLE + 1] = i * NUM_VERTS_PER_PARTICLE + 1;
+ indexPtr[i * NUM_INDICES_PER_PARTICLE + 2] = i * NUM_VERTS_PER_PARTICLE + 3;
+ indexPtr[i * NUM_INDICES_PER_PARTICLE + 3] = i * NUM_VERTS_PER_PARTICLE + 1;
+ indexPtr[i * NUM_INDICES_PER_PARTICLE + 4] = i * NUM_VERTS_PER_PARTICLE + 2;
+ indexPtr[i * NUM_INDICES_PER_PARTICLE + 5] = i * NUM_VERTS_PER_PARTICLE + 3;
+ }
+
+ // update transform
+ glm::quat rot = _transform.getRotation();
+ glm::vec3 pos = _transform.getTranslation();
+ Transform t;
+ t.setRotation(rot);
+ t.setTranslation(pos);
+ payload.setModelTransform(t);
+
+ // transform _particleMinBound and _particleMaxBound corners into world coords
+ glm::vec3 d = _particleMaxBound - _particleMinBound;
+ const size_t NUM_BOX_CORNERS = 8;
+ glm::vec3 corners[NUM_BOX_CORNERS] = {
+ pos + rot * (_particleMinBound + glm::vec3(0.0f, 0.0f, 0.0f)),
+ pos + rot * (_particleMinBound + glm::vec3(d.x, 0.0f, 0.0f)),
+ pos + rot * (_particleMinBound + glm::vec3(0.0f, d.y, 0.0f)),
+ pos + rot * (_particleMinBound + glm::vec3(d.x, d.y, 0.0f)),
+ pos + rot * (_particleMinBound + glm::vec3(0.0f, 0.0f, d.z)),
+ pos + rot * (_particleMinBound + glm::vec3(d.x, 0.0f, d.z)),
+ pos + rot * (_particleMinBound + glm::vec3(0.0f, d.y, d.z)),
+ pos + rot * (_particleMinBound + glm::vec3(d.x, d.y, d.z))
+ };
+ glm::vec3 min(FLT_MAX, FLT_MAX, FLT_MAX);
+ glm::vec3 max = -min;
+ for (size_t i = 0; i < NUM_BOX_CORNERS; i++) {
+ min.x = std::min(min.x, corners[i].x);
+ min.y = std::min(min.y, corners[i].y);
+ min.z = std::min(min.z, corners[i].z);
+ max.x = std::max(max.x, corners[i].x);
+ max.y = std::max(max.y, corners[i].y);
+ max.z = std::max(max.z, corners[i].z);
+ }
+ AABox bound(min, max - min);
+ payload.setBound(bound);
+
+ bool textured = _texture && _texture->isLoaded();
+ if (textured) {
+ payload.setTexture(_texture->getGPUTexture());
+ payload.setPipeline(_texturedPipeline);
+ } else {
+ payload.setTexture(nullptr);
+ payload.setPipeline(_untexturedPipeline);
+ }
+ });
+
+ _scene->enqueuePendingChanges(pendingChanges);
+}
+
+void RenderableParticleEffectEntityItem::createPipelines() {
+ if (!_untexturedPipeline) {
+ auto state = std::make_shared();
+ state->setCullMode(gpu::State::CULL_BACK);
+ state->setDepthTest(true, true, gpu::LESS_EQUAL);
+ state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD,
+ gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA,
+ gpu::State::BLEND_OP_ADD, gpu::State::ONE);
+ auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(untextured_particle_vert)));
+ auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(untextured_particle_frag)));
+ auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader));
+ _untexturedPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state));
+ }
+ if (!_texturedPipeline) {
+ auto state = std::make_shared();
+ state->setCullMode(gpu::State::CULL_BACK);
+ state->setDepthTest(true, true, gpu::LESS_EQUAL);
+ state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD,
+ gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA,
+ gpu::State::BLEND_OP_ADD, gpu::State::ONE);
+ auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(textured_particle_vert)));
+ auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(textured_particle_frag)));
+ auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader));
+ _texturedPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state));
+ }
+}
diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h
index 4ecea45ad0..9581c43ca5 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h
@@ -16,20 +16,35 @@
#include "RenderableEntityItem.h"
class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem {
+friend class ParticlePayload;
public:
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
- virtual void render(RenderArgs* args);
- void updateQuads(RenderArgs* args, bool textured);
+ virtual void update(const quint64& now) override;
- SIMPLE_RENDERABLE();
+ void updateRenderItem();
+
+ virtual bool addToScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges);
+ virtual void removeFromScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges);
protected:
+ render::ItemID _renderItemId;
- int _cacheID;
- const int VERTS_PER_PARTICLE = 4;
+ struct Vertex {
+ Vertex(glm::vec3 xyzIn, glm::vec2 uvIn, uint32_t rgbaIn) : xyz(xyzIn), uv(uvIn), rgba(rgbaIn) {}
+ glm::vec3 xyz;
+ glm::vec2 uv;
+ uint32_t rgba;
+ };
+ static void createPipelines();
+
+ std::vector _vertices;
+ static gpu::PipelinePointer _untexturedPipeline;
+ static gpu::PipelinePointer _texturedPipeline;
+
+ render::ScenePointer _scene;
NetworkTexturePointer _texture;
};
diff --git a/libraries/entities-renderer/src/textured_particle.slf b/libraries/entities-renderer/src/textured_particle.slf
new file mode 100644
index 0000000000..543aa643aa
--- /dev/null
+++ b/libraries/entities-renderer/src/textured_particle.slf
@@ -0,0 +1,20 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+// fragment shader
+//
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+uniform sampler2D colorMap;
+
+varying vec4 varColor;
+varying vec2 varTexCoord;
+
+void main(void) {
+ vec4 color = texture2D(colorMap, varTexCoord);
+ gl_FragColor = color * varColor;
+}
diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv
new file mode 100644
index 0000000000..7564feb1ce
--- /dev/null
+++ b/libraries/entities-renderer/src/textured_particle.slv
@@ -0,0 +1,28 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// particle vertex shader
+//
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include gpu/Transform.slh@>
+
+<$declareStandardTransform()$>
+
+varying vec4 varColor;
+varying vec2 varTexCoord;
+
+void main(void) {
+ // pass along the color & uvs to fragment shader
+ varColor = gl_Color;
+ varTexCoord = gl_MultiTexCoord0.xy;
+
+ TransformCamera cam = getTransformCamera();
+ TransformObject obj = getTransformObject();
+ <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$>
+}
diff --git a/libraries/entities-renderer/src/untextured_particle.slf b/libraries/entities-renderer/src/untextured_particle.slf
new file mode 100644
index 0000000000..bb3ed77e3f
--- /dev/null
+++ b/libraries/entities-renderer/src/untextured_particle.slf
@@ -0,0 +1,16 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+// fragment shader
+//
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+varying vec4 varColor;
+
+void main(void) {
+ gl_FragColor = varColor;
+}
diff --git a/libraries/entities-renderer/src/untextured_particle.slv b/libraries/entities-renderer/src/untextured_particle.slv
new file mode 100644
index 0000000000..2975dab046
--- /dev/null
+++ b/libraries/entities-renderer/src/untextured_particle.slv
@@ -0,0 +1,24 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// particle vertex shader
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include gpu/Transform.slh@>
+
+<$declareStandardTransform()$>
+
+varying vec4 varColor;
+
+void main(void) {
+ // pass along the diffuse color
+ varColor = gl_Color;
+
+ TransformCamera cam = getTransformCamera();
+ TransformObject obj = getTransformObject();
+ <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$>
+}
\ No newline at end of file
diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp
index 95effa2980..4dfc9dd436 100644
--- a/libraries/entities/src/ParticleEffectEntityItem.cpp
+++ b/libraries/entities/src/ParticleEffectEntityItem.cpp
@@ -39,6 +39,7 @@
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "EntitiesLogging.h"
+#include "EntityScriptingInterface.h"
#include "ParticleEffectEntityItem.h"
const xColor ParticleEffectEntityItem::DEFAULT_COLOR = { 255, 255, 255 };
@@ -92,6 +93,75 @@ ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityIte
ParticleEffectEntityItem::~ParticleEffectEntityItem() {
}
+void ParticleEffectEntityItem::setDimensions(const glm::vec3& value) {
+ computeAndUpdateDimensions();
+}
+
+void ParticleEffectEntityItem::setLifespan(float lifespan) {
+ _lifespan = lifespan;
+ computeAndUpdateDimensions();
+}
+
+void ParticleEffectEntityItem::setEmitDirection(glm::vec3 emitDirection) {
+ _emitDirection = glm::normalize(emitDirection);
+ computeAndUpdateDimensions();
+}
+
+void ParticleEffectEntityItem::setEmitStrength(float emitStrength) {
+ _emitStrength = emitStrength;
+ computeAndUpdateDimensions();
+}
+
+void ParticleEffectEntityItem::setLocalGravity(float localGravity) {
+ _localGravity = localGravity;
+ computeAndUpdateDimensions();
+}
+
+void ParticleEffectEntityItem::setParticleRadius(float particleRadius) {
+ _particleRadius = particleRadius;
+ computeAndUpdateDimensions();
+}
+
+void ParticleEffectEntityItem::computeAndUpdateDimensions() {
+
+ const float t = _lifespan * 1.1f; // add 10% extra time, to account for incremental timer accumulation error.
+ const float MAX_RANDOM_FACTOR = (0.5f * 0.25f);
+ const float maxOffset = (MAX_RANDOM_FACTOR * _emitStrength) + _particleRadius;
+
+ // bounds for x and z is easy to compute because there is no at^2 term.
+ float xMax = (_emitDirection.x * _emitStrength + maxOffset) * t;
+ float xMin = (_emitDirection.x * _emitStrength - maxOffset) * t;
+
+ float zMax = (_emitDirection.z * _emitStrength + maxOffset) * t;
+ float zMin = (_emitDirection.z * _emitStrength - maxOffset) * t;
+
+ // yEnd is where the particle will end.
+ float a = _localGravity;
+ float atSquared = a * t * t;
+ float v = _emitDirection.y * _emitStrength + maxOffset;
+ float vt = v * t;
+ float yEnd = 0.5f * atSquared + vt;
+
+ // yApex is where the particle is at it's apex.
+ float yApexT = (-v / a);
+ float yApex = 0.0f;
+
+ // only set apex if it's within the lifespan of the particle.
+ if (yApexT >= 0.0f && yApexT <= t) {
+ yApex = -(v * v) / (2.0f * a);
+ }
+
+ float yMax = std::max(yApex, yEnd);
+ float yMin = std::min(yApex, yEnd);
+
+ // times 2 because dimensions are diameters not radii.
+ glm::vec3 dims(2.0f * std::max(fabs(xMin), fabs(xMax)),
+ 2.0f * std::max(fabs(yMin), fabs(yMax)),
+ 2.0f * std::max(fabs(zMin), fabs(zMax)));
+
+ EntityItem::setDimensions(dims);
+}
+
EntityItemProperties ParticleEffectEntityItem::getProperties() const {
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
@@ -245,7 +315,7 @@ bool ParticleEffectEntityItem::isAnimatingSomething() const {
}
bool ParticleEffectEntityItem::needsToCallUpdate() const {
- return isAnimatingSomething() ? true : EntityItem::needsToCallUpdate();
+ return true;
}
void ParticleEffectEntityItem::update(const quint64& now) {
@@ -260,13 +330,6 @@ void ParticleEffectEntityItem::update(const quint64& now) {
if (isAnimatingSomething()) {
stepSimulation(deltaTime);
-
- // update the dimensions
- glm::vec3 dims;
- dims.x = glm::max(glm::abs(_particleMinBound.x), glm::abs(_particleMaxBound.x)) * 2.0f;
- dims.y = glm::max(glm::abs(_particleMinBound.y), glm::abs(_particleMaxBound.y)) * 2.0f;
- dims.z = glm::max(glm::abs(_particleMinBound.z), glm::abs(_particleMaxBound.z)) * 2.0f;
- setDimensions(dims);
}
EntityItem::update(now); // let our base class handle it's updates...
@@ -319,7 +382,7 @@ void ParticleEffectEntityItem::setAnimationSettings(const QString& value) {
qCDebug(entities) << "ParticleEffectEntityItem::setAnimationSettings() calling setAnimationFrameIndex()...";
qCDebug(entities) << " settings:" << value;
qCDebug(entities) << " settingsMap[frameIndex]:" << settingsMap["frameIndex"];
- qCDebug(entities" frameIndex: %20.5f", frameIndex);
+ qCDebug(entities, " frameIndex: %20.5f", frameIndex);
}
#endif
diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h
index 3136ab6c7c..994c609f0f 100644
--- a/libraries/entities/src/ParticleEffectEntityItem.h
+++ b/libraries/entities/src/ParticleEffectEntityItem.h
@@ -86,12 +86,14 @@ public:
void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); }
float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); }
+ virtual void setDimensions(const glm::vec3& value) override;
+
static const quint32 DEFAULT_MAX_PARTICLES;
void setMaxParticles(quint32 maxParticles);
quint32 getMaxParticles() const { return _maxParticles; }
static const float DEFAULT_LIFESPAN;
- void setLifespan(float lifespan) { _lifespan = lifespan; }
+ void setLifespan(float lifespan);
float getLifespan() const { return _lifespan; }
static const float DEFAULT_EMIT_RATE;
@@ -99,21 +101,23 @@ public:
float getEmitRate() const { return _emitRate; }
static const glm::vec3 DEFAULT_EMIT_DIRECTION;
- void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = glm::normalize(emitDirection); }
+ void setEmitDirection(glm::vec3 emitDirection);
const glm::vec3& getEmitDirection() const { return _emitDirection; }
static const float DEFAULT_EMIT_STRENGTH;
- void setEmitStrength(float emitStrength) { _emitStrength = emitStrength; }
+ void setEmitStrength(float emitStrength);
float getEmitStrength() const { return _emitStrength; }
static const float DEFAULT_LOCAL_GRAVITY;
- void setLocalGravity(float localGravity) { _localGravity = localGravity; }
+ void setLocalGravity(float localGravity);
float getLocalGravity() const { return _localGravity; }
static const float DEFAULT_PARTICLE_RADIUS;
- void setParticleRadius(float particleRadius) { _particleRadius = particleRadius; }
+ void setParticleRadius(float particleRadius);
float getParticleRadius() const { return _particleRadius; }
+ void computeAndUpdateDimensions();
+
bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); }
float getAnimationFrameIndex() const { return _animationLoop.getFrameIndex(); }
float getAnimationFPS() const { return _animationLoop.getFPS(); }
diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp
index 2ec80e3d85..f16c6ba215 100644
--- a/libraries/fbx/src/OBJReader.cpp
+++ b/libraries/fbx/src/OBJReader.cpp
@@ -195,7 +195,10 @@ void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f
}
bool OBJReader::isValidTexture(const QByteArray &filename) {
- QUrl candidateUrl = url->resolved(QUrl(filename));
+ if (!_url) {
+ return false;
+ }
+ QUrl candidateUrl = _url->resolved(QUrl(filename));
QNetworkReply *netReply = request(candidateUrl, true);
bool isValid = netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200);
netReply->deleteLater();
@@ -242,7 +245,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
} else if ((token == "map_Kd") || (token == "map_Ks")) {
QByteArray filename = QUrl(tokenizer.getLineAsDatum()).fileName().toUtf8();
if (filename.endsWith(".tga")) {
- qCDebug(modelformat) << "OBJ Reader WARNING: currently ignoring tga texture " << filename << " in " << url;
+ qCDebug(modelformat) << "OBJ Reader WARNING: currently ignoring tga texture " << filename << " in " << _url;
break;
}
if (isValidTexture(filename)) {
@@ -252,7 +255,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
currentMaterial.specularTextureFilename = filename;
}
} else {
- qCDebug(modelformat) << "OBJ Reader WARNING: " << url << " ignoring missing texture " << filename;
+ qCDebug(modelformat) << "OBJ Reader WARNING: " << _url << " ignoring missing texture " << filename;
}
}
}
@@ -316,7 +319,7 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi
QByteArray groupName = tokenizer.getDatum();
currentGroup = groupName;
//qCDebug(modelformat) << "new group:" << groupName;
- } else if (token == "mtllib") {
+ } else if (token == "mtllib" && _url) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
}
@@ -325,13 +328,15 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi
break; // Some files use mtllib over and over again for the same libraryName
}
librariesSeen[libraryName] = true;
- QUrl libraryUrl = url->resolved(QUrl(libraryName).fileName()); // Throw away any path part of libraryName, and merge against original url.
+ // Throw away any path part of libraryName, and merge against original url.
+ QUrl libraryUrl = _url->resolved(QUrl(libraryName).fileName());
qCDebug(modelformat) << "OBJ Reader new library:" << libraryName << " at:" << libraryUrl;
QNetworkReply* netReply = request(libraryUrl, false);
if (netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200)) {
parseMaterialLibrary(netReply);
} else {
- qCDebug(modelformat) << "OBJ Reader " << libraryName << " did not answer. Got " << netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
+ qCDebug(modelformat) << "OBJ Reader " << libraryName << " did not answer. Got "
+ << netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
}
netReply->deleteLater();
} else if (token == "usemtl") {
@@ -406,10 +411,10 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
OBJTokenizer tokenizer(device);
float scaleGuess = 1.0f;
- this->url = url;
+ _url = url;
geometry.meshExtents.reset();
geometry.meshes.append(FBXMesh());
-
+
try {
// call parseOBJGroup as long as it's returning true. Each successful call will
// add a new meshPart to the geometry's single mesh.
@@ -417,7 +422,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
FBXMesh& mesh = geometry.meshes[0];
mesh.meshIndex = 0;
-
+
geometry.joints.resize(1);
geometry.joints[0].isFree = false;
geometry.joints[0].parentIndex = -1;
@@ -440,37 +445,44 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
0, 0, 1, 0,
0, 0, 0, 1);
mesh.clusters.append(cluster);
-
- // Some .obj files use the convention that a group with uv coordinates that doesn't define a material, should use a texture with the same basename as the .obj file.
- QString filename = url->fileName();
- int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail
- QString basename = filename.remove(extIndex + 1, sizeof("obj"));
- OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME];
- preDefinedMaterial.diffuseColor = glm::vec3(1.0f);
- QVector extensions = {"jpg", "jpeg", "png", "tga"};
- QByteArray base = basename.toUtf8(), textName = "";
- for (int i = 0; i < extensions.count(); i++) {
- QByteArray candidateString = base + extensions[i];
- if (isValidTexture(candidateString)) {
- textName = candidateString;
- break;
+
+ // Some .obj files use the convention that a group with uv coordinates that doesn't define a material, should use
+ // a texture with the same basename as the .obj file.
+ if (url) {
+ QString filename = url->fileName();
+ int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail
+ QString basename = filename.remove(extIndex + 1, sizeof("obj"));
+ OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME];
+ preDefinedMaterial.diffuseColor = glm::vec3(1.0f);
+ QVector extensions = {"jpg", "jpeg", "png", "tga"};
+ QByteArray base = basename.toUtf8(), textName = "";
+ for (int i = 0; i < extensions.count(); i++) {
+ QByteArray candidateString = base + extensions[i];
+ if (isValidTexture(candidateString)) {
+ textName = candidateString;
+ break;
+ }
}
+
+ if (!textName.isEmpty()) {
+ preDefinedMaterial.diffuseTextureFilename = textName;
+ }
+ materials[SMART_DEFAULT_MATERIAL_NAME] = preDefinedMaterial;
}
- if (!textName.isEmpty()) {
- preDefinedMaterial.diffuseTextureFilename = textName;
- }
- materials[SMART_DEFAULT_MATERIAL_NAME] = preDefinedMaterial;
-
+
for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) {
FBXMeshPart& meshPart = mesh.parts[i];
FaceGroup faceGroup = faceGroups[meshPartCount];
OBJFace leadFace = faceGroup[0]; // All the faces in the same group will have the same name and material.
QString groupMaterialName = leadFace.materialName;
if (groupMaterialName.isEmpty() && (leadFace.textureUVIndices.count() > 0)) {
- qCDebug(modelformat) << "OBJ Reader WARNING: " << url << " needs a texture that isn't specified. Using default mechanism.";
+ qCDebug(modelformat) << "OBJ Reader WARNING: " << url
+ << " needs a texture that isn't specified. Using default mechanism.";
groupMaterialName = SMART_DEFAULT_MATERIAL_NAME;
} else if (!groupMaterialName.isEmpty() && !materials.contains(groupMaterialName)) {
- qCDebug(modelformat) << "OBJ Reader WARNING: " << url << " specifies a material " << groupMaterialName << " that is not defined. Using default mechanism.";
+ qCDebug(modelformat) << "OBJ Reader WARNING: " << url
+ << " specifies a material " << groupMaterialName
+ << " that is not defined. Using default mechanism.";
groupMaterialName = SMART_DEFAULT_MATERIAL_NAME;
}
if (!groupMaterialName.isEmpty()) {
diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h
index 771c0b1b63..2e7b050b0a 100644
--- a/libraries/fbx/src/OBJReader.h
+++ b/libraries/fbx/src/OBJReader.h
@@ -69,12 +69,13 @@ public:
QVector faceGroups;
QString currentMaterialName;
QHash materials;
- QUrl* url;
-
+
QNetworkReply* request(QUrl& url, bool isTest);
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url);
private:
+ QUrl* _url = nullptr;
+
QHash librariesSeen;
bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess);
void parseMaterialLibrary(QIODevice* device);
@@ -83,4 +84,4 @@ private:
// What are these utilities doing here? One is used by fbx loading code in VHACD Utils, and the other a general debugging utility.
void setMeshPartDefaults(FBXMeshPart& meshPart, QString materialID);
-void fbxDebugDump(const FBXGeometry& fbxgeo);
\ No newline at end of file
+void fbxDebugDump(const FBXGeometry& fbxgeo);
diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp
new file mode 100644
index 0000000000..f19fa6e18a
--- /dev/null
+++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp
@@ -0,0 +1,255 @@
+//
+// AmbientOcclusionEffect.cpp
+// libraries/render-utils/src/
+//
+// Created by Niraj Venkat on 7/15/15.
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// include this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL
+#include
+
+#include
+
+#include
+
+#include
+#include
+
+#include "gpu/StandardShaderLib.h"
+#include "AmbientOcclusionEffect.h"
+#include "TextureCache.h"
+#include "FramebufferCache.h"
+#include "DependencyManager.h"
+#include "ViewFrustum.h"
+#include "GeometryCache.h"
+
+#include "ambient_occlusion_vert.h"
+#include "ambient_occlusion_frag.h"
+#include "gaussian_blur_vertical_vert.h"
+#include "gaussian_blur_horizontal_vert.h"
+#include "gaussian_blur_frag.h"
+#include "occlusion_blend_frag.h"
+
+
+AmbientOcclusion::AmbientOcclusion() {
+}
+
+const gpu::PipelinePointer& AmbientOcclusion::getOcclusionPipeline() {
+ if (!_occlusionPipeline) {
+ auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(ambient_occlusion_vert)));
+ auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(ambient_occlusion_frag)));
+ gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps));
+
+ gpu::Shader::BindingSet slotBindings;
+ slotBindings.insert(gpu::Shader::Binding(std::string("depthTexture"), 0));
+ slotBindings.insert(gpu::Shader::Binding(std::string("normalTexture"), 1));
+
+ gpu::Shader::makeProgram(*program, slotBindings);
+
+ _gScaleLoc = program->getUniforms().findLocation("g_scale");
+ _gBiasLoc = program->getUniforms().findLocation("g_bias");
+ _gSampleRadiusLoc = program->getUniforms().findLocation("g_sample_rad");
+ _gIntensityLoc = program->getUniforms().findLocation("g_intensity");
+ _bufferWidthLoc = program->getUniforms().findLocation("bufferWidth");
+ _bufferHeightLoc = program->getUniforms().findLocation("bufferHeight");
+
+ gpu::StatePointer state = gpu::StatePointer(new gpu::State());
+
+ state->setDepthTest(false, false, gpu::LESS_EQUAL);
+
+ // Blend on transparent
+ state->setBlendFunction(false,
+ gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
+ gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);
+
+ // Link the occlusion FBO to texture
+ _occlusionBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32,
+ DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height()));
+ auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
+ auto width = _occlusionBuffer->getWidth();
+ auto height = _occlusionBuffer->getHeight();
+ auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
+ _occlusionTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler));
+
+ // Good to go add the brand new pipeline
+ _occlusionPipeline.reset(gpu::Pipeline::create(program, state));
+ }
+ return _occlusionPipeline;
+}
+
+const gpu::PipelinePointer& AmbientOcclusion::getVBlurPipeline() {
+ if (!_vBlurPipeline) {
+ auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(gaussian_blur_vertical_vert)));
+ auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(gaussian_blur_frag)));
+ gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps));
+
+ gpu::Shader::BindingSet slotBindings;
+ gpu::Shader::makeProgram(*program, slotBindings);
+
+ gpu::StatePointer state = gpu::StatePointer(new gpu::State());
+
+ state->setDepthTest(false, false, gpu::LESS_EQUAL);
+
+ // Blend on transparent
+ state->setBlendFunction(false,
+ gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
+ gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);
+
+ // Link the horizontal blur FBO to texture
+ _vBlurBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32,
+ DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height()));
+ auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
+ auto width = _vBlurBuffer->getWidth();
+ auto height = _vBlurBuffer->getHeight();
+ auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
+ _vBlurTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler));
+
+ // Good to go add the brand new pipeline
+ _vBlurPipeline.reset(gpu::Pipeline::create(program, state));
+ }
+ return _vBlurPipeline;
+}
+
+const gpu::PipelinePointer& AmbientOcclusion::getHBlurPipeline() {
+ if (!_hBlurPipeline) {
+ auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(gaussian_blur_horizontal_vert)));
+ auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(gaussian_blur_frag)));
+ gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps));
+
+ gpu::Shader::BindingSet slotBindings;
+ gpu::Shader::makeProgram(*program, slotBindings);
+
+ gpu::StatePointer state = gpu::StatePointer(new gpu::State());
+
+ state->setDepthTest(false, false, gpu::LESS_EQUAL);
+
+ // Blend on transparent
+ state->setBlendFunction(false,
+ gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
+ gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);
+
+ // Link the horizontal blur FBO to texture
+ _hBlurBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32,
+ DependencyManager::get()->getFrameBufferSize().width(), DependencyManager::get()->getFrameBufferSize().height()));
+ auto format = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
+ auto width = _hBlurBuffer->getWidth();
+ auto height = _hBlurBuffer->getHeight();
+ auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
+ _hBlurTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler));
+
+ // Good to go add the brand new pipeline
+ _hBlurPipeline.reset(gpu::Pipeline::create(program, state));
+ }
+ return _hBlurPipeline;
+}
+
+const gpu::PipelinePointer& AmbientOcclusion::getBlendPipeline() {
+ if (!_blendPipeline) {
+ auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(ambient_occlusion_vert)));
+ auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(occlusion_blend_frag)));
+ gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps));
+
+ gpu::Shader::BindingSet slotBindings;
+ slotBindings.insert(gpu::Shader::Binding(std::string("blurredOcclusionTexture"), 0));
+
+ gpu::Shader::makeProgram(*program, slotBindings);
+
+ gpu::StatePointer state = gpu::StatePointer(new gpu::State());
+
+ state->setDepthTest(false, false, gpu::LESS_EQUAL);
+
+ // Blend on transparent
+ state->setBlendFunction(true,
+ gpu::State::SRC_COLOR, gpu::State::BLEND_OP_ADD, gpu::State::DEST_COLOR);
+
+ // Good to go add the brand new pipeline
+ _blendPipeline.reset(gpu::Pipeline::create(program, state));
+ }
+ return _blendPipeline;
+}
+
+void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) {
+ assert(renderContext->args);
+ assert(renderContext->args->_viewFrustum);
+ RenderArgs* args = renderContext->args;
+ auto& scene = sceneContext->_scene;
+
+ gpu::Batch batch;
+
+ glm::mat4 projMat;
+ Transform viewMat;
+ args->_viewFrustum->evalProjectionMatrix(projMat);
+ args->_viewFrustum->evalViewTransform(viewMat);
+ batch.setProjectionTransform(projMat);
+ batch.setViewTransform(viewMat);
+ batch.setModelTransform(Transform());
+
+ // Occlusion step
+ getOcclusionPipeline();
+ batch.setResourceTexture(0, DependencyManager::get()->getPrimaryDepthTexture());
+ batch.setResourceTexture(1, DependencyManager::get()->getPrimaryNormalTexture());
+ _occlusionBuffer->setRenderBuffer(0, _occlusionTexture);
+ batch.setFramebuffer(_occlusionBuffer);
+
+ // Occlusion uniforms
+ g_scale = 1.0f;
+ g_bias = 1.0f;
+ g_sample_rad = 1.0f;
+ g_intensity = 1.0f;
+
+ // Bind the first gpu::Pipeline we need - for calculating occlusion buffer
+ batch.setPipeline(getOcclusionPipeline());
+ batch._glUniform1f(_gScaleLoc, g_scale);
+ batch._glUniform1f(_gBiasLoc, g_bias);
+ batch._glUniform1f(_gSampleRadiusLoc, g_sample_rad);
+ batch._glUniform1f(_gIntensityLoc, g_intensity);
+ batch._glUniform1f(_bufferWidthLoc, DependencyManager::get()->getFrameBufferSize().width());
+ batch._glUniform1f(_bufferHeightLoc, DependencyManager::get()->getFrameBufferSize().height());
+
+ glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f);
+ glm::vec2 bottomLeft(-1.0f, -1.0f);
+ glm::vec2 topRight(1.0f, 1.0f);
+ glm::vec2 texCoordTopLeft(0.0f, 0.0f);
+ glm::vec2 texCoordBottomRight(1.0f, 1.0f);
+ DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
+
+ // Vertical blur step
+ getVBlurPipeline();
+ batch.setResourceTexture(0, _occlusionTexture);
+ _vBlurBuffer->setRenderBuffer(0, _vBlurTexture);
+ batch.setFramebuffer(_vBlurBuffer);
+
+ // Bind the second gpu::Pipeline we need - for calculating blur buffer
+ batch.setPipeline(getVBlurPipeline());
+
+ DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
+
+ // Horizontal blur step
+ getHBlurPipeline();
+ batch.setResourceTexture(0, _vBlurTexture);
+ _hBlurBuffer->setRenderBuffer(0, _hBlurTexture);
+ batch.setFramebuffer(_hBlurBuffer);
+
+ // Bind the third gpu::Pipeline we need - for calculating blur buffer
+ batch.setPipeline(getHBlurPipeline());
+
+ DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
+
+ // Blend step
+ getBlendPipeline();
+ batch.setResourceTexture(0, _hBlurTexture);
+ batch.setFramebuffer(DependencyManager::get()->getPrimaryFramebuffer());
+
+ // Bind the fourth gpu::Pipeline we need - for blending the primary color buffer with blurred occlusion texture
+ batch.setPipeline(getBlendPipeline());
+
+ DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
+
+ // Ready to render
+ args->_context->syncCache();
+ args->_context->render((batch));
+}
diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h
new file mode 100644
index 0000000000..0b695dd2ad
--- /dev/null
+++ b/libraries/render-utils/src/AmbientOcclusionEffect.h
@@ -0,0 +1,61 @@
+//
+// AmbientOcclusionEffect.h
+// libraries/render-utils/src/
+//
+// Created by Niraj Venkat on 7/15/15.
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_AmbientOcclusionEffect_h
+#define hifi_AmbientOcclusionEffect_h
+
+#include
+
+#include "render/DrawTask.h"
+
+class AmbientOcclusion {
+public:
+
+ AmbientOcclusion();
+
+ void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
+ typedef render::Job::Model JobModel;
+
+ const gpu::PipelinePointer& getOcclusionPipeline();
+ const gpu::PipelinePointer& getHBlurPipeline();
+ const gpu::PipelinePointer& getVBlurPipeline();
+ const gpu::PipelinePointer& getBlendPipeline();
+
+private:
+
+ // Uniforms for AO
+ gpu::int32 _gScaleLoc;
+ gpu::int32 _gBiasLoc;
+ gpu::int32 _gSampleRadiusLoc;
+ gpu::int32 _gIntensityLoc;
+ gpu::int32 _bufferWidthLoc;
+ gpu::int32 _bufferHeightLoc;
+ float g_scale;
+ float g_bias;
+ float g_sample_rad;
+ float g_intensity;
+
+ gpu::PipelinePointer _occlusionPipeline;
+ gpu::PipelinePointer _hBlurPipeline;
+ gpu::PipelinePointer _vBlurPipeline;
+ gpu::PipelinePointer _blendPipeline;
+
+ gpu::FramebufferPointer _occlusionBuffer;
+ gpu::FramebufferPointer _hBlurBuffer;
+ gpu::FramebufferPointer _vBlurBuffer;
+
+ gpu::TexturePointer _occlusionTexture;
+ gpu::TexturePointer _hBlurTexture;
+ gpu::TexturePointer _vBlurTexture;
+
+};
+
+#endif // hifi_AmbientOcclusionEffect_h
diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index 0c8d19250b..cf9b455059 100755
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -22,6 +22,7 @@
#include "TextureCache.h"
#include "render/DrawStatus.h"
+#include "AmbientOcclusionEffect.h"
#include "overlay3D_vert.h"
#include "overlay3D_frag.h"
@@ -80,6 +81,11 @@ RenderDeferredTask::RenderDeferredTask() : Task() {
_jobs.push_back(Job(new DrawLight::JobModel("DrawLight")));
_jobs.push_back(Job(new RenderDeferred::JobModel("RenderDeferred")));
_jobs.push_back(Job(new ResolveDeferred::JobModel("ResolveDeferred")));
+ _jobs.push_back(Job(new AmbientOcclusion::JobModel("AmbientOcclusion")));
+
+ _jobs.back().setEnabled(false);
+ _occlusionJobIndex = _jobs.size() - 1;
+
_jobs.push_back(Job(new FetchItems::JobModel("FetchTransparent",
FetchItems(
ItemFilter::Builder::transparentShape().withoutLayered(),
@@ -93,10 +99,13 @@ RenderDeferredTask::RenderDeferredTask() : Task() {
_jobs.push_back(Job(new DrawTransparentDeferred::JobModel("TransparentDeferred", _jobs.back().getOutput())));
_jobs.push_back(Job(new render::DrawStatus::JobModel("DrawStatus", renderedOpaques)));
+
+
_jobs.back().setEnabled(false);
_drawStatusJobIndex = _jobs.size() - 1;
_jobs.push_back(Job(new DrawOverlay3D::JobModel("DrawOverlay3D")));
+
_jobs.push_back(Job(new ResetGLState::JobModel()));
// Give ourselves 3 frmaes of timer queries
@@ -125,6 +134,9 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend
// Make sure we turn the displayItemStatus on/off
setDrawItemStatus(renderContext->_drawItemStatus);
+ // TODO: turn on/off AO through menu item
+ setOcclusionStatus(renderContext->_occlusionStatus);
+
renderContext->args->_context->syncCache();
for (auto job : _jobs) {
diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h
index 1fec1c936f..fdb75a87b1 100755
--- a/libraries/render-utils/src/RenderDeferredTask.h
+++ b/libraries/render-utils/src/RenderDeferredTask.h
@@ -82,6 +82,11 @@ public:
void setDrawItemStatus(bool draw) { if (_drawStatusJobIndex >= 0) { _jobs[_drawStatusJobIndex].setEnabled(draw); } }
bool doDrawItemStatus() const { if (_drawStatusJobIndex >= 0) { return _jobs[_drawStatusJobIndex].isEnabled(); } else { return false; } }
+ int _occlusionJobIndex = -1;
+
+ void setOcclusionStatus(bool draw) { if (_occlusionJobIndex >= 0) { _jobs[_occlusionJobIndex].setEnabled(draw); } }
+ bool doOcclusionStatus() const { if (_occlusionJobIndex >= 0) { return _jobs[_occlusionJobIndex].isEnabled(); } else { return false; } }
+
virtual void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
diff --git a/libraries/render-utils/src/ambient_occlusion.slf b/libraries/render-utils/src/ambient_occlusion.slf
new file mode 100644
index 0000000000..3a49accf58
--- /dev/null
+++ b/libraries/render-utils/src/ambient_occlusion.slf
@@ -0,0 +1,109 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// ambient_occlusion.frag
+// fragment shader
+//
+// Created by Niraj Venkat on 7/15/15.
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include DeferredBufferWrite.slh@>
+
+<@include gpu/Transform.slh@>
+
+<$declareStandardTransform()$>
+
+varying vec2 varTexcoord;
+
+uniform sampler2D depthTexture;
+uniform sampler2D normalTexture;
+
+uniform float g_scale;
+uniform float g_bias;
+uniform float g_sample_rad;
+uniform float g_intensity;
+uniform float bufferWidth;
+uniform float bufferHeight;
+
+#define SAMPLE_COUNT 4
+
+float getRandom(vec2 uv) {
+ return fract(sin(dot(uv.xy ,vec2(12.9898,78.233))) * 43758.5453);
+}
+
+void main(void) {
+ vec3 sampleKernel[4] = { vec3(0.2, 0.0, 0.0),
+ vec3(0.0, 0.2, 0.0),
+ vec3(0.0, 0.0, 0.2),
+ vec3(0.2, 0.2, 0.2) };
+
+ TransformCamera cam = getTransformCamera();
+ TransformObject obj = getTransformObject();
+
+ vec3 eyeDir = vec3(0.0, 0.0, -3.0);
+ vec3 cameraPositionWorldSpace;
+ <$transformEyeToWorldDir(cam, eyeDir, cameraPositionWorldSpace)$>
+
+ vec4 depthColor = texture2D(depthTexture, varTexcoord);
+
+ // z in non linear range [0,1]
+ float depthVal = depthColor.r;
+ // conversion into NDC [-1,1]
+ float zNDC = depthVal * 2.0 - 1.0;
+ float n = 1.0; // the near plane
+ float f = 30.0; // the far plane
+ float l = -1.0; // left
+ float r = 1.0; // right
+ float b = -1.0; // bottom
+ float t = 1.0; // top
+
+ // conversion into eye space
+ float zEye = 2*f*n / (zNDC*(f-n)-(f+n));
+ // Converting from pixel coordinates to NDC
+ float xNDC = gl_FragCoord.x/bufferWidth * 2.0 - 1.0;
+ float yNDC = gl_FragCoord.y/bufferHeight * 2.0 - 1.0;
+ // Unprojecting X and Y from NDC to eye space
+ float xEye = -zEye*(xNDC*(r-l)+(r+l))/(2.0*n);
+ float yEye = -zEye*(yNDC*(t-b)+(t+b))/(2.0*n);
+ vec3 currentFragEyeSpace = vec3(xEye, yEye, zEye);
+ vec3 currentFragWorldSpace;
+ <$transformEyeToWorldDir(cam, currentFragEyeSpace, currentFragWorldSpace)$>
+
+ vec3 cameraToPositionRay = normalize(currentFragWorldSpace - cameraPositionWorldSpace);
+ vec3 origin = cameraToPositionRay * depthVal + cameraPositionWorldSpace;
+
+ vec3 normal = normalize(texture2D(normalTexture, varTexcoord).xyz);
+ //normal = normalize(normal * normalMatrix);
+
+ vec3 rvec = vec3(getRandom(varTexcoord.xy), getRandom(varTexcoord.yx), getRandom(varTexcoord.xx)) * 2.0 - 1.0;
+ vec3 tangent = normalize(rvec - normal * dot(rvec, normal));
+ vec3 bitangent = cross(normal, tangent);
+ mat3 tbn = mat3(tangent, bitangent, normal);
+
+ float occlusion = 0.0;
+
+ for (int i = 0; i < SAMPLE_COUNT; ++i) {
+ vec3 samplePos = origin + (tbn * sampleKernel[i]) * g_sample_rad;
+ vec4 offset = cam._projectionViewUntranslated * vec4(samplePos, 1.0);
+
+ offset.xy = (offset.xy / offset.w) * 0.5 + 0.5;
+ float depth = length(samplePos - cameraPositionWorldSpace);
+
+ float sampleDepthVal = texture2D(depthTexture, offset.xy).r;
+
+ float rangeDelta = abs(depthVal - sampleDepthVal);
+ float rangeCheck = smoothstep(0.0, 1.0, g_sample_rad / rangeDelta);
+
+ occlusion += rangeCheck * step(sampleDepthVal, depth);
+ }
+
+ occlusion = 1.0 - occlusion / float(SAMPLE_COUNT);
+ occlusion = clamp(pow(occlusion, g_intensity), 0.0, 1.0);
+ gl_FragColor = vec4(vec3(occlusion), 1.0);
+}
+
diff --git a/libraries/render-utils/src/ambient_occlusion.slv b/libraries/render-utils/src/ambient_occlusion.slv
new file mode 100644
index 0000000000..81f196dd46
--- /dev/null
+++ b/libraries/render-utils/src/ambient_occlusion.slv
@@ -0,0 +1,24 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// ambient_occlusion.vert
+// vertex shader
+//
+// Created by Niraj Venkat on 7/15/15.
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include gpu/Transform.slh@>
+
+<$declareStandardTransform()$>
+
+varying vec2 varTexcoord;
+
+void main(void) {
+ varTexcoord = gl_MultiTexCoord0.xy;
+ gl_Position = gl_Vertex;
+}
diff --git a/libraries/render-utils/src/gaussian_blur.slf b/libraries/render-utils/src/gaussian_blur.slf
new file mode 100644
index 0000000000..63ba14a07c
--- /dev/null
+++ b/libraries/render-utils/src/gaussian_blur.slf
@@ -0,0 +1,42 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// gaussian_blur.frag
+// fragment shader
+//
+// Created by Niraj Venkat on 7/17/15.
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include DeferredBufferWrite.slh@>
+
+// the interpolated normal
+//varying vec4 interpolatedNormal;
+
+varying vec2 varTexcoord;
+varying vec2 varBlurTexcoords[14];
+
+uniform sampler2D occlusionTexture;
+
+void main(void) {
+ gl_FragColor = vec4(0.0);
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[0])*0.0044299121055113265;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[1])*0.00895781211794;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[2])*0.0215963866053;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[3])*0.0443683338718;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[4])*0.0776744219933;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[5])*0.115876621105;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[6])*0.147308056121;
+ gl_FragColor += texture2D(occlusionTexture, varTexcoord)*0.159576912161;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[7])*0.147308056121;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[8])*0.115876621105;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[9])*0.0776744219933;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[10])*0.0443683338718;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[11])*0.0215963866053;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[12])*0.00895781211794;
+ gl_FragColor += texture2D(occlusionTexture, varBlurTexcoords[13])*0.0044299121055113265;
+}
diff --git a/libraries/render-utils/src/gaussian_blur_horizontal.slv b/libraries/render-utils/src/gaussian_blur_horizontal.slv
new file mode 100644
index 0000000000..c3f326daac
--- /dev/null
+++ b/libraries/render-utils/src/gaussian_blur_horizontal.slv
@@ -0,0 +1,41 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// guassian_blur_horizontal.vert
+// vertex shader
+//
+// Created by Niraj Venkat on 7/17/15.
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include gpu/Transform.slh@>
+
+<$declareStandardTransform()$>
+
+varying vec2 varTexcoord;
+varying vec2 varBlurTexcoords[14];
+
+void main(void) {
+ varTexcoord = gl_MultiTexCoord0.xy;
+ gl_Position = gl_Vertex;
+
+ varBlurTexcoords[0] = varTexcoord + vec2(-0.028, 0.0);
+ varBlurTexcoords[1] = varTexcoord + vec2(-0.024, 0.0);
+ varBlurTexcoords[2] = varTexcoord + vec2(-0.020, 0.0);
+ varBlurTexcoords[3] = varTexcoord + vec2(-0.016, 0.0);
+ varBlurTexcoords[4] = varTexcoord + vec2(-0.012, 0.0);
+ varBlurTexcoords[5] = varTexcoord + vec2(-0.008, 0.0);
+ varBlurTexcoords[6] = varTexcoord + vec2(-0.004, 0.0);
+ varBlurTexcoords[7] = varTexcoord + vec2(0.004, 0.0);
+ varBlurTexcoords[8] = varTexcoord + vec2(0.008, 0.0);
+ varBlurTexcoords[9] = varTexcoord + vec2(0.012, 0.0);
+ varBlurTexcoords[10] = varTexcoord + vec2(0.016, 0.0);
+ varBlurTexcoords[11] = varTexcoord + vec2(0.020, 0.0);
+ varBlurTexcoords[12] = varTexcoord + vec2(0.024, 0.0);
+ varBlurTexcoords[13] = varTexcoord + vec2(0.028, 0.0);
+}
+
\ No newline at end of file
diff --git a/libraries/render-utils/src/gaussian_blur_vertical.slv b/libraries/render-utils/src/gaussian_blur_vertical.slv
new file mode 100644
index 0000000000..fc35a96bf0
--- /dev/null
+++ b/libraries/render-utils/src/gaussian_blur_vertical.slv
@@ -0,0 +1,41 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// guassian_blur_vertical.vert
+// vertex shader
+//
+// Created by Niraj Venkat on 7/17/15.
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include gpu/Transform.slh@>
+
+<$declareStandardTransform()$>
+
+varying vec2 varTexcoord;
+varying vec2 varBlurTexcoords[14];
+
+void main(void) {
+ varTexcoord = gl_MultiTexCoord0.xy;
+ gl_Position = gl_Vertex;
+
+ varBlurTexcoords[0] = varTexcoord + vec2(0.0, -0.028);
+ varBlurTexcoords[1] = varTexcoord + vec2(0.0, -0.024);
+ varBlurTexcoords[2] = varTexcoord + vec2(0.0, -0.020);
+ varBlurTexcoords[3] = varTexcoord + vec2(0.0, -0.016);
+ varBlurTexcoords[4] = varTexcoord + vec2(0.0, -0.012);
+ varBlurTexcoords[5] = varTexcoord + vec2(0.0, -0.008);
+ varBlurTexcoords[6] = varTexcoord + vec2(0.0, -0.004);
+ varBlurTexcoords[7] = varTexcoord + vec2(0.0, 0.004);
+ varBlurTexcoords[8] = varTexcoord + vec2(0.0, 0.008);
+ varBlurTexcoords[9] = varTexcoord + vec2(0.0, 0.012);
+ varBlurTexcoords[10] = varTexcoord + vec2(0.0, 0.016);
+ varBlurTexcoords[11] = varTexcoord + vec2(0.0, 0.020);
+ varBlurTexcoords[12] = varTexcoord + vec2(0.0, 0.024);
+ varBlurTexcoords[13] = varTexcoord + vec2(0.0, 0.028);
+}
+
\ No newline at end of file
diff --git a/libraries/render-utils/src/occlusion_blend.slf b/libraries/render-utils/src/occlusion_blend.slf
new file mode 100644
index 0000000000..965d806759
--- /dev/null
+++ b/libraries/render-utils/src/occlusion_blend.slf
@@ -0,0 +1,29 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// occlusion_blend.frag
+// fragment shader
+//
+// Created by Niraj Venkat on 7/20/15.
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+<@include DeferredBufferWrite.slh@>
+
+varying vec2 varTexcoord;
+
+uniform sampler2D blurredOcclusionTexture;
+
+void main(void) {
+ vec4 occlusionColor = texture2D(blurredOcclusionTexture, varTexcoord);
+
+ if(occlusionColor.r > 0.8 && occlusionColor.r <= 1.0) {
+ gl_FragColor = vec4(vec3(0.0), 0.0);
+ } else {
+ gl_FragColor = vec4(vec3(occlusionColor.r), 1.0);
+ }
+}
diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h
index 1c600b13d6..5bfece96cc 100644
--- a/libraries/render/src/render/Engine.h
+++ b/libraries/render/src/render/Engine.h
@@ -51,6 +51,8 @@ public:
bool _drawItemStatus = false;
+ bool _occlusionStatus = false;
+
RenderContext() {}
};
typedef std::shared_ptr RenderContextPointer;
diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp
index 3eb01071ba..71033406e5 100644
--- a/tests/ui/src/main.cpp
+++ b/tests/ui/src/main.cpp
@@ -86,7 +86,6 @@ public:
AddressBar,
AlignForearmsWithWrists,
AlternateIK,
- AmbientOcclusion,
Animations,
Atmosphere,
Attachments,
@@ -111,6 +110,7 @@ public:
ControlWithSpeech,
CopyAddress,
CopyPath,
+ DebugAmbientOcclusion,
DecreaseAvatarSize,
DeleteBookmark,
DisableActivityLogger,