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/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/leaves.js b/examples/leaves.js new file mode 100755 index 0000000000..4610cd2ef0 --- /dev/null +++ b/examples/leaves.js @@ -0,0 +1,331 @@ +// +// Leaves.js +// examples +// +// Created by Bing Shearer on 14 Jul 2015 +// 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 +// +var leafName = "scriptLeaf"; +var leafSquall = function (properties) { + var // Properties + squallOrigin, + squallRadius, + leavesPerMinute = 60, + leafSize = { + x: 0.1, + y: 0.1, + z: 0.1 + }, + leafFallSpeed = 1, // m/s + leafLifetime = 60, // Seconds + leafSpinMax = 0, // Maximum angular velocity per axis; deg/s + debug = false, // Display origin circle; don't use running on Stack Manager + // Other + squallCircle, + SQUALL_CIRCLE_COLOR = { + red: 255, + green: 0, + blue: 0 + }, + SQUALL_CIRCLE_ALPHA = 0.5, + SQUALL_CIRCLE_ROTATION = Quat.fromPitchYawRollDegrees(90, 0, 0), + leafProperties, + leaf_MODEL_URL = "https://hifi-public.s3.amazonaws.com/ozan/support/forBing/palmLeaf.fbx", + leafTimer, + leaves = [], // HACK: Work around leaves not always getting velocities + leafVelocities = [], // HACK: Work around leaves not always getting velocities + DEGREES_TO_RADIANS = Math.PI / 180, + leafDeleteOnTearDown = true, + maxLeaves, + leafCount, + nearbyEntities, + complexMovement = false, + movementTime = 0, + maxSpinRadians = properties.leafSpinMax * DEGREES_TO_RADIANS, + windFactor, + leafDeleteOnGround = false, + floorHeight = null; + + + function processProperties() { + if (!properties.hasOwnProperty("origin")) { + print("ERROR: Leaf squall origin must be specified"); + return; + } + squallOrigin = properties.origin; + + if (!properties.hasOwnProperty("radius")) { + print("ERROR: Leaf squall radius must be specified"); + return; + } + squallRadius = properties.radius; + + if (properties.hasOwnProperty("leavesPerMinute")) { + leavesPerMinute = properties.leavesPerMinute; + } + + if (properties.hasOwnProperty("leafSize")) { + leafSize = properties.leafSize; + } + + if (properties.hasOwnProperty("leafFallSpeed")) { + leafFallSpeed = properties.leafFallSpeed; + } + + if (properties.hasOwnProperty("leafLifetime")) { + leafLifetime = properties.leafLifetime; + } + + if (properties.hasOwnProperty("leafSpinMax")) { + leafSpinMax = properties.leafSpinMax; + } + + if (properties.hasOwnProperty("debug")) { + debug = properties.debug; + } + if (properties.hasOwnProperty("floorHeight")) { + floorHeight = properties.floorHeight; + } + if (properties.hasOwnProperty("maxLeaves")) { + maxLeaves = properties.maxLeaves; + } + if (properties.hasOwnProperty("complexMovement")) { + complexMovement = properties.complexMovement; + } + if (properties.hasOwnProperty("leafDeleteOnGround")) { + leafDeleteOnGround = properties.leafDeleteOnGround; + } + if (properties.hasOwnProperty("windFactor")) { + windFactor = properties.windFactor; + } else if (complexMovement == true){ + print("ERROR: Wind Factor must be defined for complex movement") + } + + leafProperties = { + type: "Model", + name: leafName, + modelURL: leaf_MODEL_URL, + lifetime: leafLifetime, + dimensions: leafSize, + velocity: { + x: 0, + y: -leafFallSpeed, + z: 0 + }, + damping: 0, + angularDamping: 0, + ignoreForCollisions: true + + }; + } + + function createleaf() { + var angle, + radius, + offset, + leaf, + spin = { + x: 0, + y: 0, + z: 0 + }, + i; + + // HACK: Work around leaves not always getting velocities set at creation + for (i = 0; i < leaves.length; i++) { + Entities.editEntity(leaves[i], leafVelocities[i]); + } + + angle = Math.random() * leafSpinMax; + radius = Math.random() * squallRadius; + offset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, angle, 0), { + x: 0, + y: -0.1, + z: radius + }); + leafProperties.position = Vec3.sum(squallOrigin, offset); + if (properties.leafSpinMax > 0 && !complexMovement) { + spin = { + x: Math.random() * maxSpinRadians, + y: Math.random() * maxSpinRadians, + z: Math.random() * maxSpinRadians + }; + leafProperties.angularVelocity = spin; + } else if (complexMovement) { + spin = { + x: 0, + y: 0, + z: 0 + }; + leafProperties.angularVelocity = spin + } + leaf = Entities.addEntity(leafProperties); + + // HACK: Work around leaves not always getting velocities set at creation + leaves.push(leaf); + leafVelocities.push({ + velocity: leafProperties.velocity, + angularVelocity: spin + }); + if (leaves.length > 5) { + leaves.shift(); + leafVelocities.shift(); + } + } + + function setUp() { + if (debug) { + squallCircle = Overlays.addOverlay("circle3d", { + size: { + x: 2 * squallRadius, + y: 2 * squallRadius + }, + color: SQUALL_CIRCLE_COLOR, + alpha: SQUALL_CIRCLE_ALPHA, + solid: true, + visible: debug, + position: squallOrigin, + rotation: SQUALL_CIRCLE_ROTATION + }); + } + + leafTimer = Script.setInterval(function () { + if (leafCount <= maxLeaves - 1) { + createleaf() + } + }, 60000 / leavesPerMinute); + } + Script.setInterval(function () { + nearbyEntities = Entities.findEntities(squallOrigin, squallRadius); + newLeafMovement() + }, 100); + + function newLeafMovement() { //new additions to leaf code. Operates at 10 Hz or every 100 ms + movementTime += 0.1; + var currentLeaf, + randomRotationSpeed = { + x: maxSpinRadians * Math.sin(movementTime), + y: maxSpinRadians * Math.random(), + z: maxSpinRadians * Math.sin(movementTime / 7) + }; + for (var i = 0; i < nearbyEntities.length; i++) { + var entityProperties = Entities.getEntityProperties(nearbyEntities[i]); + var entityName = entityProperties.name; + if (leafName === entityName) { + currentLeaf = nearbyEntities[i]; + var leafHeight = entityProperties.position.y; + if (complexMovement && leafHeight > floorHeight || complexMovement && floorHeight == null) { //actual new movement code; + var leafCurrentVel = entityProperties.velocity, + leafCurrentRot = entityProperties.rotation, + yVec = { + x: 0, + y: 1, + z: 0 + }, + leafYinWFVec = Vec3.multiplyQbyV(leafCurrentRot, yVec), + leafLocalHorVec = Vec3.cross(leafYinWFVec, yVec), + leafMostDownVec = Vec3.cross(leafYinWFVec, leafLocalHorVec), + leafDesiredVel = Vec3.multiply(leafMostDownVec, windFactor), + leafVelDelt = Vec3.subtract(leafDesiredVel, leafCurrentVel), + leafNewVel = Vec3.sum(leafCurrentVel, Vec3.multiply(leafVelDelt, windFactor)); + Entities.editEntity(currentLeaf, { + angularVelocity: randomRotationSpeed, + velocity: leafNewVel + }) + } else if (leafHeight <= floorHeight) { + if (!leafDeleteOnGround) { + Entities.editEntity(nearbyEntities[i], { + locked: false, + velocity: { + x: 0, + y: 0, + z: 0 + }, + angularVelocity: { + x: 0, + y: 0, + z: 0 + } + }) + } else { + Entity.deleteEntity(currentLeaf); + } + } + } + } + } + + + + getLeafCount = Script.setInterval(function () { + leafCount = 0 + for (var i = 0; i < nearbyEntities.length; i++) { + var entityName = Entities.getEntityProperties(nearbyEntities[i]).name; + //Stop Leaves at floorHeight + if (leafName === entityName) { + leafCount++; + if (i == nearbyEntities.length - 1) { + //print(leafCount); + } + } + } + }, 1000) + + + + function tearDown() { + Script.clearInterval(leafTimer); + Overlays.deleteOverlay(squallCircle); + if (leafDeleteOnTearDown) { + for (var i = 0; i < nearbyEntities.length; i++) { + var entityName = Entities.getEntityProperties(nearbyEntities[i]).name; + if (leafName === entityName) { + //We have a match - delete this entity + Entities.editEntity(nearbyEntities[i], { + locked: false + }); + Entities.deleteEntity(nearbyEntities[i]); + } + } + } + } + + + + processProperties(); + setUp(); + Script.scriptEnding.connect(tearDown); + + return {}; +}; + +var leafSquall1 = new leafSquall({ + origin: { + x: 3071.5, + y: 2170, + z: 6765.3 + }, + radius: 100, + leavesPerMinute: 30, + leafSize: { + x: 0.3, + y: 0.00, + z: 0.3 + }, + leafFallSpeed: 0.4, + leafLifetime: 100, + leafSpinMax: 30, + debug: false, + maxLeaves: 100, + leafDeleteOnTearDown: true, + complexMovement: true, + floorHeight: 2143.5, + windFactor: 0.5, + leafDeleteOnGround: false +}); + +// todo +//deal with depth issue \ No newline at end of file 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 bb564824b0..3f185af5a1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -330,7 +330,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()), _isVSyncOn(true), - _isThrottleFPSEnabled(false), + _isThrottleFPSEnabled(true), _aboutToQuit(false), _notifiedPacketVersionMismatchThisDomain(false), _glWidget(new GLCanvas()), @@ -3265,6 +3265,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; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 91ae6a4d02..625d543544 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -330,7 +330,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); @@ -368,7 +368,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 bf0f89abb5..4c3e9332a1 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -134,7 +134,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..."; @@ -165,6 +164,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/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 35ba437745..aceec6703f 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; @@ -438,37 +443,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 f5647bd176..919c27f32f 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,