mirror of
https://github.com/overte-org/overte.git
synced 2025-07-05 01:49:29 +02:00
Merge branch 'master' into 20639
This commit is contained in:
commit
ed259e6bbb
46 changed files with 4435 additions and 660 deletions
100
examples/afk.js
Normal file
100
examples/afk.js
Normal file
|
@ -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);
|
||||||
|
});
|
145
examples/example/audio/jsstreamplayer.js
Normal file
145
examples/example/audio/jsstreamplayer.js
Normal file
|
@ -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);
|
|
@ -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);
|
||||||
|
}
|
||||||
|
})
|
|
@ -12,29 +12,297 @@
|
||||||
//
|
//
|
||||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||||
|
|
||||||
const NUM_LAYERS = 16;
|
Script.include("../../libraries/toolBars.js");
|
||||||
const BASE_DIMENSION = { x: 7, y: 2, z: 7 };
|
|
||||||
const BLOCKS_PER_LAYER = 3;
|
const DEFAULT_NUM_LAYERS = 16;
|
||||||
const BLOCK_SIZE = {x: 0.2, y: 0.1, z: 0.8};
|
const DEFAULT_BASE_DIMENSION = { x: 7, y: 2, z: 7 };
|
||||||
const BLOCK_SPACING = BLOCK_SIZE.x / 3;
|
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 %)
|
// 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 DEFAULT_BLOCK_HEIGHT_VARIATION = 0.001;
|
||||||
const GRAVITY = {x: 0, y: -2.8, z: 0};
|
const DEFAULT_GRAVITY = {x: 0, y: -2.8, z: 0};
|
||||||
const DENSITY = 2000;
|
const DEFAULT_DENSITY = 2000;
|
||||||
const DAMPING_FACTOR = 0.98;
|
const DEFAULT_DAMPING_FACTOR = 0.98;
|
||||||
const ANGULAR_DAMPING_FACTOR = 0.8;
|
const DEFAULT_ANGULAR_DAMPING_FACTOR = 0.8;
|
||||||
const FRICTION = 0.99;
|
const DEFAULT_FRICTION = 0.99;
|
||||||
const RESTITUTION = 0.0;
|
const DEFAULT_RESTITUTION = 0.0;
|
||||||
const SPAWN_DISTANCE = 3;
|
const DEFAULT_SPAWN_DISTANCE = 3;
|
||||||
const BLOCK_YAW_OFFSET = 45;
|
const DEFAULT_BLOCK_YAW_OFFSET = 45;
|
||||||
|
|
||||||
|
var editMode = false;
|
||||||
|
|
||||||
const BUTTON_DIMENSIONS = {width: 49, height: 49};
|
const BUTTON_DIMENSIONS = {width: 49, height: 49};
|
||||||
const MAXIMUM_PERCENTAGE = 100.0;
|
const MAXIMUM_PERCENTAGE = 100.0;
|
||||||
|
const NO_ANGLE = 0;
|
||||||
|
const RIGHT_ANGLE = 90;
|
||||||
|
|
||||||
var windowWidth = Window.innerWidth;
|
var windowWidth = Window.innerWidth;
|
||||||
var size;
|
var size;
|
||||||
var pieces = [];
|
var pieces = [];
|
||||||
var ground = false;
|
var ground = false;
|
||||||
var layerRotated = 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() {
|
function grabLowestJointY() {
|
||||||
var jointNames = MyAvatar.getJointNames();
|
var jointNames = MyAvatar.getJointNames();
|
||||||
|
@ -47,108 +315,60 @@ function grabLowestJointY() {
|
||||||
return floorY;
|
return floorY;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getButtonPosX() {
|
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.games.planky", function (windowDimensions, toolbar) {
|
||||||
return windowWidth - ((BUTTON_DIMENSIONS.width / 2) + BUTTON_DIMENSIONS.width);
|
return {
|
||||||
}
|
x: windowDimensions.x - (toolbar.width * 1.1),
|
||||||
|
y: toolbar.height / 2
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
var button = Overlays.addOverlay('image', {
|
button = toolBar.addTool({
|
||||||
x: getButtonPosX(),
|
|
||||||
y: 10,
|
|
||||||
width: BUTTON_DIMENSIONS.width,
|
width: BUTTON_DIMENSIONS.width,
|
||||||
height: BUTTON_DIMENSIONS.height,
|
height: BUTTON_DIMENSIONS.height,
|
||||||
imageURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/planky_button.svg',
|
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() {
|
Controller.mousePressEvent.connect(function(event) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mousePressEvent(event) {
|
|
||||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||||
if (clickedOverlay === button) {
|
if (toolBar.clicked(clickedOverlay) === button) {
|
||||||
resetBlocks();
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Controller.mousePressEvent.connect(mousePressEvent);
|
Script.update.connect(function() {
|
||||||
|
if (windowWidth !== Window.innerWidth) {
|
||||||
|
windowWidth = Window.innerWidth;
|
||||||
|
Overlays.editOverlay(button, {x: getButtonPosX()});
|
||||||
|
Overlays.editOverlay(cogButton, {x: getCogButtonPosX()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function cleanup() {
|
Script.scriptEnding.connect(function() {
|
||||||
Overlays.deleteOverlay(button);
|
toolBar.cleanup();
|
||||||
if (ground) {
|
if (ground) {
|
||||||
Entities.deleteEntity(ground);
|
Entities.deleteEntity(ground);
|
||||||
}
|
}
|
||||||
pieces.forEach(function(piece) {
|
plankyStack.deRez();
|
||||||
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);
|
|
||||||
|
|
288
examples/example/games/satellite.js
Normal file
288
examples/example/games/satellite.js
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
//
|
||||||
|
// satellite.js
|
||||||
|
// games
|
||||||
|
//
|
||||||
|
// Created by Bridget Went 7/1/2015.
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// A game to bring a satellite model into orbit around an animated earth model .
|
||||||
|
// - Double click to create a new satellite
|
||||||
|
// - Click on the satellite, drag a vector arrow to specify initial velocity
|
||||||
|
// - Release mouse to launch the active satellite
|
||||||
|
// - Orbital movement is calculated using equations of gravitational physics
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
Script.include('../utilities/tools/vector.js');
|
||||||
|
|
||||||
|
var URL = "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/";
|
||||||
|
|
||||||
|
SatelliteGame = function() {
|
||||||
|
var MAX_RANGE = 50.0;
|
||||||
|
var Y_AXIS = {
|
||||||
|
x: 0,
|
||||||
|
y: 1,
|
||||||
|
z: 0
|
||||||
|
}
|
||||||
|
var LIFETIME = 6000;
|
||||||
|
var ERROR_THRESH = 20.0;
|
||||||
|
|
||||||
|
// Create the spinning earth model
|
||||||
|
var EARTH_SIZE = 20.0;
|
||||||
|
var CLOUDS_OFFSET = 0.5;
|
||||||
|
var SPIN = 0.1;
|
||||||
|
var ZONE_DIM = 100.0;
|
||||||
|
var LIGHT_INTENSITY = 1.5;
|
||||||
|
|
||||||
|
Earth = function(position, size) {
|
||||||
|
this.earth = Entities.addEntity({
|
||||||
|
type: "Model",
|
||||||
|
shapeType: 'sphere',
|
||||||
|
modelURL: URL + "earth.fbx",
|
||||||
|
position: position,
|
||||||
|
dimensions: {
|
||||||
|
x: size,
|
||||||
|
y: size,
|
||||||
|
z: size
|
||||||
|
},
|
||||||
|
rotation: Quat.angleAxis(180, {
|
||||||
|
x: 1,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
}),
|
||||||
|
angularVelocity: {
|
||||||
|
x: 0.00,
|
||||||
|
y: 0.5 * SPIN,
|
||||||
|
z: 0.00
|
||||||
|
},
|
||||||
|
angularDamping: 0.0,
|
||||||
|
damping: 0.0,
|
||||||
|
ignoreCollisions: false,
|
||||||
|
lifetime: 6000,
|
||||||
|
collisionsWillMove: false,
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.clouds = Entities.addEntity({
|
||||||
|
type: "Model",
|
||||||
|
shapeType: 'sphere',
|
||||||
|
modelURL: URL + "clouds.fbx?i=2",
|
||||||
|
position: position,
|
||||||
|
dimensions: {
|
||||||
|
x: size + CLOUDS_OFFSET,
|
||||||
|
y: size + CLOUDS_OFFSET,
|
||||||
|
z: size + CLOUDS_OFFSET
|
||||||
|
},
|
||||||
|
angularVelocity: {
|
||||||
|
x: 0.00,
|
||||||
|
y: SPIN,
|
||||||
|
z: 0.00
|
||||||
|
},
|
||||||
|
angularDamping: 0.0,
|
||||||
|
damping: 0.0,
|
||||||
|
ignoreCollisions: false,
|
||||||
|
lifetime: LIFETIME,
|
||||||
|
collisionsWillMove: false,
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.zone = Entities.addEntity({
|
||||||
|
type: "Zone",
|
||||||
|
position: position,
|
||||||
|
dimensions: {
|
||||||
|
x: ZONE_DIM,
|
||||||
|
y: ZONE_DIM,
|
||||||
|
z: ZONE_DIM
|
||||||
|
},
|
||||||
|
keyLightDirection: Vec3.normalize(Vec3.subtract(position, Camera.getPosition())),
|
||||||
|
keyLightIntensity: LIGHT_INTENSITY
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cleanup = function() {
|
||||||
|
Entities.deleteEntity(this.clouds);
|
||||||
|
Entities.deleteEntity(this.earth);
|
||||||
|
Entities.deleteEntity(this.zone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create earth model
|
||||||
|
var center = Vec3.sum(Camera.getPosition(), Vec3.multiply(MAX_RANGE, Quat.getFront(Camera.getOrientation())));
|
||||||
|
var distance = Vec3.length(Vec3.subtract(center, Camera.getPosition()));
|
||||||
|
var earth = new Earth(center, EARTH_SIZE);
|
||||||
|
|
||||||
|
var satellites = [];
|
||||||
|
var SATELLITE_SIZE = 2.0;
|
||||||
|
var launched = false;
|
||||||
|
var activeSatellite;
|
||||||
|
|
||||||
|
var PERIOD = 4.0;
|
||||||
|
var LARGE_BODY_MASS = 16000.0;
|
||||||
|
var SMALL_BODY_MASS = LARGE_BODY_MASS * 0.000000333;
|
||||||
|
|
||||||
|
Satellite = function(position, planetCenter) {
|
||||||
|
// The Satellite class
|
||||||
|
|
||||||
|
this.launched = false;
|
||||||
|
this.startPosition = position;
|
||||||
|
this.readyToLaunch = false;
|
||||||
|
this.radius = Vec3.length(Vec3.subtract(position, planetCenter));
|
||||||
|
|
||||||
|
this.satellite = Entities.addEntity({
|
||||||
|
type: "Model",
|
||||||
|
modelURL: "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/satellite/satellite.fbx",
|
||||||
|
position: this.startPosition,
|
||||||
|
dimensions: {
|
||||||
|
x: SATELLITE_SIZE,
|
||||||
|
y: SATELLITE_SIZE,
|
||||||
|
z: SATELLITE_SIZE
|
||||||
|
},
|
||||||
|
angularDamping: 0.0,
|
||||||
|
damping: 0.0,
|
||||||
|
ignoreCollisions: false,
|
||||||
|
lifetime: LIFETIME,
|
||||||
|
collisionsWillMove: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getProperties = function() {
|
||||||
|
return Entities.getEntityProperties(this.satellite);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.launch = function() {
|
||||||
|
var prop = Entities.getEntityProperties(this.satellite);
|
||||||
|
var between = Vec3.subtract(planetCenter, prop.position);
|
||||||
|
var radius = Vec3.length(between);
|
||||||
|
this.gravity = (4.0 * Math.PI * Math.PI * Math.pow(radius, 3.0)) / (LARGE_BODY_MASS * PERIOD * PERIOD);
|
||||||
|
|
||||||
|
var initialVelocity = Vec3.normalize(Vec3.cross(between, Y_AXIS));
|
||||||
|
initialVelocity = Vec3.multiply(Math.sqrt((this.gravity * LARGE_BODY_MASS) / radius), initialVelocity);
|
||||||
|
initialVelocity = Vec3.multiply(this.arrow.magnitude, initialVelocity);
|
||||||
|
initialVelocity = Vec3.multiply(Vec3.length(initialVelocity), this.arrow.direction);
|
||||||
|
|
||||||
|
Entities.editEntity(this.satellite, {
|
||||||
|
velocity: initialVelocity
|
||||||
|
});
|
||||||
|
this.launched = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this.update = function(deltaTime) {
|
||||||
|
var prop = Entities.getEntityProperties(this.satellite);
|
||||||
|
var between = Vec3.subtract(prop.position, planetCenter);
|
||||||
|
var radius = Vec3.length(between);
|
||||||
|
var acceleration = -(this.gravity * LARGE_BODY_MASS) * Math.pow(radius, (-2.0));
|
||||||
|
var speed = acceleration * deltaTime;
|
||||||
|
var vel = Vec3.multiply(speed, Vec3.normalize(between));
|
||||||
|
|
||||||
|
var newVelocity = Vec3.sum(prop.velocity, vel);
|
||||||
|
var newPos = Vec3.sum(prop.position, Vec3.multiply(newVelocity, deltaTime));
|
||||||
|
|
||||||
|
Entities.editEntity(this.satellite, {
|
||||||
|
velocity: newVelocity,
|
||||||
|
position: newPos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseDoublePressEvent(event) {
|
||||||
|
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||||
|
var addVector = Vec3.multiply(pickRay.direction, distance);
|
||||||
|
var point = Vec3.sum(Camera.getPosition(), addVector);
|
||||||
|
|
||||||
|
// Create a new satellite
|
||||||
|
activeSatellite = new Satellite(point, center);
|
||||||
|
satellites.push(activeSatellite);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mousePressEvent(event) {
|
||||||
|
if (!activeSatellite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Reset label
|
||||||
|
if (activeSatellite.arrow) {
|
||||||
|
activeSatellite.arrow.deleteLabel();
|
||||||
|
}
|
||||||
|
var statsPosition = Vec3.sum(Camera.getPosition(), Vec3.multiply(MAX_RANGE * 0.4, Quat.getFront(Camera.getOrientation())));
|
||||||
|
var pickRay = Camera.computePickRay(event.x, event.y)
|
||||||
|
var rayPickResult = Entities.findRayIntersection(pickRay, true);
|
||||||
|
if (rayPickResult.entityID === activeSatellite.satellite) {
|
||||||
|
// Create a draggable vector arrow at satellite position
|
||||||
|
activeSatellite.arrow = new VectorArrow(distance, true, "INITIAL VELOCITY", statsPosition);
|
||||||
|
activeSatellite.arrow.onMousePressEvent(event);
|
||||||
|
activeSatellite.arrow.isDragging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseMoveEvent(event) {
|
||||||
|
if (!activeSatellite || !activeSatellite.arrow || !activeSatellite.arrow.isDragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activeSatellite.arrow.onMouseMoveEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseReleaseEvent(event) {
|
||||||
|
if (!activeSatellite || !activeSatellite.arrow || !activeSatellite.arrow.isDragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activeSatellite.arrow.onMouseReleaseEvent(event);
|
||||||
|
activeSatellite.launch();
|
||||||
|
activeSatellite.arrow.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
var counter = 0.0;
|
||||||
|
var CHECK_ENERGY_PERIOD = 500;
|
||||||
|
|
||||||
|
function update(deltaTime) {
|
||||||
|
if (!activeSatellite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Update all satellites
|
||||||
|
for (var i = 0; i < satellites.length; i++) {
|
||||||
|
if (!satellites[i].launched) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
satellites[i].update(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
if (counter % CHECK_ENERGY_PERIOD == 0) {
|
||||||
|
var prop = activeSatellite.getProperties();
|
||||||
|
var error = calcEnergyError(prop.position, Vec3.length(prop.velocity));
|
||||||
|
if (Math.abs(error) <= ERROR_THRESH) {
|
||||||
|
activeSatellite.arrow.editLabel("Nice job! The satellite has reached a stable orbit.");
|
||||||
|
} else {
|
||||||
|
activeSatellite.arrow.editLabel("Try again! The satellite is in an unstable orbit.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.endGame = function() {
|
||||||
|
for (var i = 0; i < satellites.length; i++) {
|
||||||
|
Entities.deleteEntity(satellites[i].satellite);
|
||||||
|
satellites[i].arrow.cleanup();
|
||||||
|
}
|
||||||
|
earth.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function calcEnergyError(pos, vel) {
|
||||||
|
//Calculate total energy error for active satellite's orbital motion
|
||||||
|
var radius = activeSatellite.radius;
|
||||||
|
var gravity = (4.0 * Math.PI * Math.PI * Math.pow(radius, 3.0)) / (LARGE_BODY_MASS * PERIOD * PERIOD);
|
||||||
|
var initialVelocityCalculated = Math.sqrt((gravity * LARGE_BODY_MASS) / radius);
|
||||||
|
|
||||||
|
var totalEnergy = 0.5 * LARGE_BODY_MASS * Math.pow(initialVelocityCalculated, 2.0) - ((gravity * LARGE_BODY_MASS * SMALL_BODY_MASS) / radius);
|
||||||
|
var measuredEnergy = 0.5 * LARGE_BODY_MASS * Math.pow(vel, 2.0) - ((gravity * LARGE_BODY_MASS * SMALL_BODY_MASS) / Vec3.length(Vec3.subtract(pos, center)));
|
||||||
|
var error = ((measuredEnergy - totalEnergy) / totalEnergy) * 100;
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller.mousePressEvent.connect(mousePressEvent);
|
||||||
|
Controller.mouseDoublePressEvent.connect(mouseDoublePressEvent);
|
||||||
|
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||||
|
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||||
|
Script.update.connect(update);
|
||||||
|
Script.scriptEnding.connect(this.endGame);
|
||||||
|
|
||||||
|
}
|
669
examples/example/solarsystem.js
Normal file
669
examples/example/solarsystem.js
Normal file
|
@ -0,0 +1,669 @@
|
||||||
|
//
|
||||||
|
// solarsystem.js
|
||||||
|
// games
|
||||||
|
//
|
||||||
|
// Created by Bridget Went, 5/28/15.
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// The start to a project to build a virtual physics classroom to simulate the solar system, gravity, and orbital physics.
|
||||||
|
// - A sun with oribiting planets is created in front of the user
|
||||||
|
// - UI elements allow for adjusting the period, gravity, trails, and energy recalculations
|
||||||
|
// - Click "PAUSE" to pause the animation and show planet labels
|
||||||
|
// - In this mode, double-click a planet label to zoom in on that planet
|
||||||
|
// -Double-clicking on earth label initiates satellite orbiter game
|
||||||
|
// -Press "TAB" to toggle back to solar system view
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
Script.include('../utilities/tools/cookies.js');
|
||||||
|
Script.include('games/satellite.js');
|
||||||
|
|
||||||
|
var BASE_URL = "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/planets/";
|
||||||
|
|
||||||
|
var NUM_PLANETS = 8;
|
||||||
|
|
||||||
|
var trailsEnabled = true;
|
||||||
|
var energyConserved = true;
|
||||||
|
var planetView = false;
|
||||||
|
var earthView = false;
|
||||||
|
var satelliteGame;
|
||||||
|
|
||||||
|
var PANEL_X = 850;
|
||||||
|
var PANEL_Y = 600;
|
||||||
|
var BUTTON_SIZE = 20;
|
||||||
|
var PADDING = 20;
|
||||||
|
|
||||||
|
var DAMPING = 0.0;
|
||||||
|
var LIFETIME = 6000;
|
||||||
|
var ERROR_THRESH = 2.0;
|
||||||
|
var TIME_STEP = 70.0;
|
||||||
|
|
||||||
|
var MAX_POINTS_PER_LINE = 5;
|
||||||
|
var LINE_DIM = 10;
|
||||||
|
var LINE_WIDTH = 3.0;
|
||||||
|
var line;
|
||||||
|
var planetLines = [];
|
||||||
|
var trails = [];
|
||||||
|
|
||||||
|
var BOUNDS = 200;
|
||||||
|
|
||||||
|
|
||||||
|
// Alert user to move if they are too close to domain bounds
|
||||||
|
if (MyAvatar.position.x < BOUNDS || MyAvatar.position.x > TREE_SCALE - BOUNDS || MyAvatar.position.y < BOUNDS || MyAvatar.position.y > TREE_SCALE - BOUNDS || MyAvatar.position.z < BOUNDS || MyAvatar.position.z > TREE_SCALE - BOUNDS) {
|
||||||
|
Window.alert("Please move at least 200m away from domain bounds.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save intiial avatar and camera position
|
||||||
|
var startingPosition = MyAvatar.position;
|
||||||
|
var startFrame = Window.location.href;
|
||||||
|
|
||||||
|
// Place the sun
|
||||||
|
var MAX_RANGE = 80.0;
|
||||||
|
var SUN_SIZE = 8.0;
|
||||||
|
var center = Vec3.sum(startingPosition, Vec3.multiply(MAX_RANGE, Quat.getFront(Camera.getOrientation())));
|
||||||
|
|
||||||
|
var theSun = Entities.addEntity({
|
||||||
|
type: "Model",
|
||||||
|
modelURL: BASE_URL + "sun.fbx",
|
||||||
|
position: center,
|
||||||
|
dimensions: {
|
||||||
|
x: SUN_SIZE,
|
||||||
|
y: SUN_SIZE,
|
||||||
|
z: SUN_SIZE
|
||||||
|
},
|
||||||
|
angularDamping: DAMPING,
|
||||||
|
damping: DAMPING,
|
||||||
|
ignoreCollisions: false,
|
||||||
|
lifetime: LIFETIME,
|
||||||
|
collisionsWillMove: false
|
||||||
|
});
|
||||||
|
|
||||||
|
var planets = [];
|
||||||
|
var planet_properties = [];
|
||||||
|
|
||||||
|
// Reference values
|
||||||
|
var radius = 7.0;
|
||||||
|
var T_ref = 1.0;
|
||||||
|
var size = 1.0;
|
||||||
|
var M = 250.0;
|
||||||
|
var m = M * 0.000000333;
|
||||||
|
var G = (Math.pow(radius, 3.0) / Math.pow((T_ref / (2.0 * Math.PI)), 2.0)) / M;
|
||||||
|
var G_ref = G;
|
||||||
|
|
||||||
|
// Adjust size and distance as number of planets increases
|
||||||
|
var DELTA_RADIUS = 1.8;
|
||||||
|
var DELTA_SIZE = 0.2;
|
||||||
|
|
||||||
|
function initPlanets() {
|
||||||
|
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||||
|
var v0 = Math.sqrt((G * M) / radius);
|
||||||
|
var T = (2.0 * Math.PI) * Math.sqrt(Math.pow(radius, 3.0) / (G * M));
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
var color = {
|
||||||
|
red: 255,
|
||||||
|
green: 255,
|
||||||
|
blue: 255
|
||||||
|
};
|
||||||
|
} else if (i == 1) {
|
||||||
|
var color = {
|
||||||
|
red: 255,
|
||||||
|
green: 160,
|
||||||
|
blue: 110
|
||||||
|
};
|
||||||
|
} else if (i == 2) {
|
||||||
|
var color = {
|
||||||
|
red: 10,
|
||||||
|
green: 150,
|
||||||
|
blue: 160
|
||||||
|
};
|
||||||
|
} else if (i == 3) {
|
||||||
|
var color = {
|
||||||
|
red: 180,
|
||||||
|
green: 70,
|
||||||
|
blue: 10
|
||||||
|
};
|
||||||
|
} else if (i == 4) {
|
||||||
|
var color = {
|
||||||
|
red: 250,
|
||||||
|
green: 140,
|
||||||
|
blue: 0
|
||||||
|
};
|
||||||
|
} else if (i == 5) {
|
||||||
|
var color = {
|
||||||
|
red: 235,
|
||||||
|
green: 215,
|
||||||
|
blue: 0
|
||||||
|
};
|
||||||
|
} else if (i == 6) {
|
||||||
|
var color = {
|
||||||
|
red: 135,
|
||||||
|
green: 205,
|
||||||
|
blue: 240
|
||||||
|
};
|
||||||
|
} else if (i == 7) {
|
||||||
|
var color = {
|
||||||
|
red: 30,
|
||||||
|
green: 140,
|
||||||
|
blue: 255
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var prop = {
|
||||||
|
radius: radius,
|
||||||
|
position: Vec3.sum(center, {
|
||||||
|
x: radius,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0
|
||||||
|
}),
|
||||||
|
lineColor: color,
|
||||||
|
period: T,
|
||||||
|
dimensions: size,
|
||||||
|
velocity: Vec3.multiply(v0, Vec3.normalize({
|
||||||
|
x: 0,
|
||||||
|
y: -0.2,
|
||||||
|
z: 0.9
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
planet_properties.push(prop);
|
||||||
|
|
||||||
|
planets.push(Entities.addEntity({
|
||||||
|
type: "Model",
|
||||||
|
modelURL: BASE_URL + (i + 1) + ".fbx",
|
||||||
|
position: prop.position,
|
||||||
|
dimensions: {
|
||||||
|
x: prop.dimensions,
|
||||||
|
y: prop.dimensions,
|
||||||
|
z: prop.dimensions
|
||||||
|
},
|
||||||
|
velocity: prop.velocity,
|
||||||
|
angularDamping: DAMPING,
|
||||||
|
damping: DAMPING,
|
||||||
|
ignoreCollisions: false,
|
||||||
|
lifetime: LIFETIME,
|
||||||
|
collisionsWillMove: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
radius *= DELTA_RADIUS;
|
||||||
|
size += DELTA_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize planets
|
||||||
|
initPlanets();
|
||||||
|
|
||||||
|
|
||||||
|
var labels = [];
|
||||||
|
var labelLines = [];
|
||||||
|
var labelsShowing = false;
|
||||||
|
var LABEL_X = 8.0;
|
||||||
|
var LABEL_Y = 3.0;
|
||||||
|
var LABEL_Z = 1.0;
|
||||||
|
var LABEL_DIST = 8.0;
|
||||||
|
var TEXT_HEIGHT = 1.0;
|
||||||
|
var sunLabel;
|
||||||
|
|
||||||
|
function showLabels() {
|
||||||
|
labelsShowing = true;
|
||||||
|
for (var i = 0; i < NUM_PLANETS; i++) {
|
||||||
|
var properties = planet_properties[i];
|
||||||
|
var text;
|
||||||
|
if (i == 0) {
|
||||||
|
text = "Mercury";
|
||||||
|
} else if (i == 1) {
|
||||||
|
text = "Venus";
|
||||||
|
} else if (i == 2) {
|
||||||
|
text = "Earth";
|
||||||
|
} else if (i == 3) {
|
||||||
|
text = "Mars";
|
||||||
|
} else if (i == 4) {
|
||||||
|
text = "Jupiter";
|
||||||
|
} else if (i == 5) {
|
||||||
|
text = "Saturn";
|
||||||
|
} else if (i == 6) {
|
||||||
|
text = "Uranus";
|
||||||
|
} else if (i == 7) {
|
||||||
|
text = "Neptune";
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text + " Speed: " + Vec3.length(properties.velocity).toFixed(2);
|
||||||
|
|
||||||
|
var labelPos = Vec3.sum(planet_properties[i].position, {
|
||||||
|
x: 0.0,
|
||||||
|
y: LABEL_DIST,
|
||||||
|
z: LABEL_DIST
|
||||||
|
});
|
||||||
|
var linePos = planet_properties[i].position;
|
||||||
|
labelLines.push(Entities.addEntity({
|
||||||
|
type: "Line",
|
||||||
|
position: linePos,
|
||||||
|
dimensions: {
|
||||||
|
x: 20,
|
||||||
|
y: 20,
|
||||||
|
z: 20
|
||||||
|
},
|
||||||
|
lineWidth: 3.0,
|
||||||
|
color: {
|
||||||
|
red: 255,
|
||||||
|
green: 255,
|
||||||
|
blue: 255
|
||||||
|
},
|
||||||
|
linePoints: [{
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
}, computeLocalPoint(linePos, labelPos)]
|
||||||
|
}));
|
||||||
|
|
||||||
|
labels.push(Entities.addEntity({
|
||||||
|
type: "Text",
|
||||||
|
text: text,
|
||||||
|
lineHeight: TEXT_HEIGHT,
|
||||||
|
dimensions: {
|
||||||
|
x: LABEL_X,
|
||||||
|
y: LABEL_Y,
|
||||||
|
z: LABEL_Z
|
||||||
|
},
|
||||||
|
position: labelPos,
|
||||||
|
backgroundColor: {
|
||||||
|
red: 10,
|
||||||
|
green: 10,
|
||||||
|
blue: 10
|
||||||
|
},
|
||||||
|
textColor: {
|
||||||
|
red: 255,
|
||||||
|
green: 255,
|
||||||
|
blue: 255
|
||||||
|
},
|
||||||
|
faceCamera: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLabels() {
|
||||||
|
labelsShowing = false;
|
||||||
|
Entities.deleteEntity(sunLabel);
|
||||||
|
|
||||||
|
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||||
|
Entities.deleteEntity(labelLines[i]);
|
||||||
|
Entities.deleteEntity(labels[i]);
|
||||||
|
}
|
||||||
|
labels = [];
|
||||||
|
labelLines = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var time = 0.0;
|
||||||
|
var elapsed;
|
||||||
|
var counter = 0;
|
||||||
|
var dt = 1.0 / TIME_STEP;
|
||||||
|
|
||||||
|
function update(deltaTime) {
|
||||||
|
if (paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deltaTime = dt;
|
||||||
|
time++;
|
||||||
|
|
||||||
|
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||||
|
var properties = planet_properties[i];
|
||||||
|
var between = Vec3.subtract(properties.position, center);
|
||||||
|
var speed = getAcceleration(properties.radius) * deltaTime;
|
||||||
|
var vel = Vec3.multiply(speed, Vec3.normalize(between));
|
||||||
|
|
||||||
|
// Update velocity and position
|
||||||
|
properties.velocity = Vec3.sum(properties.velocity, vel);
|
||||||
|
properties.position = Vec3.sum(properties.position, Vec3.multiply(properties.velocity, deltaTime));
|
||||||
|
Entities.editEntity(planets[i], {
|
||||||
|
velocity: properties.velocity,
|
||||||
|
position: properties.position
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Create new or update current trail
|
||||||
|
if (trailsEnabled) {
|
||||||
|
var lineStack = planetLines[i];
|
||||||
|
var point = properties.position;
|
||||||
|
var prop = Entities.getEntityProperties(lineStack[lineStack.length - 1]);
|
||||||
|
var linePos = prop.position;
|
||||||
|
|
||||||
|
trails[i].push(computeLocalPoint(linePos, point));
|
||||||
|
|
||||||
|
Entities.editEntity(lineStack[lineStack.length - 1], {
|
||||||
|
linePoints: trails[i]
|
||||||
|
});
|
||||||
|
if (trails[i].length === MAX_POINTS_PER_LINE) {
|
||||||
|
trails[i] = newLine(lineStack, point, properties.period, properties.lineColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure total energy every 10 updates, recalibrate velocity if necessary
|
||||||
|
if (energyConserved) {
|
||||||
|
if (counter % 10 === 0) {
|
||||||
|
var error = calcEnergyError(planets[i], properties.radius, properties.v0, properties.velocity, properties.position);
|
||||||
|
if (Math.abs(error) >= ERROR_THRESH) {
|
||||||
|
var speed = adjustVelocity(planets[i], properties.position);
|
||||||
|
properties.velocity = Vec3.multiply(speed, Vec3.normalize(properties.velocity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
if (time % TIME_STEP == 0) {
|
||||||
|
elapsed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeLocalPoint(linePos, worldPoint) {
|
||||||
|
var localPoint = Vec3.subtract(worldPoint, linePos);
|
||||||
|
return localPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAcceleration(radius) {
|
||||||
|
var acc = -(G * M) * Math.pow(radius, (-2.0));
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new trail
|
||||||
|
function resetTrails(planetIndex) {
|
||||||
|
elapsed = 0.0;
|
||||||
|
var properties = planet_properties[planetIndex];
|
||||||
|
|
||||||
|
var trail = [];
|
||||||
|
var lineStack = [];
|
||||||
|
|
||||||
|
//add the first line to both the line entity stack and the trail
|
||||||
|
trails.push(newLine(lineStack, properties.position, properties.period, properties.lineColor));
|
||||||
|
planetLines.push(lineStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new line
|
||||||
|
function newLine(lineStack, point, period, color) {
|
||||||
|
if (elapsed < period) {
|
||||||
|
var line = Entities.addEntity({
|
||||||
|
position: point,
|
||||||
|
type: "Line",
|
||||||
|
color: color,
|
||||||
|
dimensions: {
|
||||||
|
x: LINE_DIM,
|
||||||
|
y: LINE_DIM,
|
||||||
|
z: LINE_DIM
|
||||||
|
},
|
||||||
|
lifetime: LIFETIME,
|
||||||
|
lineWidth: LINE_WIDTH
|
||||||
|
});
|
||||||
|
lineStack.push(line);
|
||||||
|
} else {
|
||||||
|
// Begin overwriting first lines after one full revolution (one period)
|
||||||
|
var firstLine = lineStack.shift();
|
||||||
|
Entities.editEntity(firstLine, {
|
||||||
|
position: point,
|
||||||
|
linePoints: [{
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
lineStack.push(firstLine);
|
||||||
|
|
||||||
|
}
|
||||||
|
var points = [];
|
||||||
|
points.push(computeLocalPoint(point, point));
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure energy error, recalculate velocity to return to initial net energy
|
||||||
|
var totalEnergy;
|
||||||
|
var measuredEnergy;
|
||||||
|
var measuredPE;
|
||||||
|
|
||||||
|
function calcEnergyError(planet, radius, v0, v, pos) {
|
||||||
|
totalEnergy = 0.5 * M * Math.pow(v0, 2.0) - ((G * M * m) / radius);
|
||||||
|
measuredEnergy = 0.5 * M * Math.pow(v, 2.0) - ((G * M * m) / Vec3.length(Vec3.subtract(center, pos)));
|
||||||
|
var error = ((measuredEnergy - totalEnergy) / totalEnergy) * 100;
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustVelocity(planet, pos) {
|
||||||
|
var measuredPE = -(G * M * m) / Vec3.length(Vec3.subtract(center, pos));
|
||||||
|
return Math.sqrt(2 * (totalEnergy - measuredPE) / M);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Allow user to toggle pausing the model, switch to planet view
|
||||||
|
var pauseButton = Overlays.addOverlay("text", {
|
||||||
|
backgroundColor: {
|
||||||
|
red: 200,
|
||||||
|
green: 200,
|
||||||
|
blue: 255
|
||||||
|
},
|
||||||
|
text: "Pause",
|
||||||
|
x: PANEL_X,
|
||||||
|
y: PANEL_Y - 30,
|
||||||
|
width: 70,
|
||||||
|
height: 20,
|
||||||
|
alpha: 1.0,
|
||||||
|
backgroundAlpha: 0.5,
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var paused = false;
|
||||||
|
|
||||||
|
function mousePressEvent(event) {
|
||||||
|
var clickedOverlay = Overlays.getOverlayAtPoint({
|
||||||
|
x: event.x,
|
||||||
|
y: event.y
|
||||||
|
});
|
||||||
|
if (clickedOverlay == pauseButton) {
|
||||||
|
paused = !paused;
|
||||||
|
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||||
|
Entities.editEntity(planets[i], {
|
||||||
|
velocity: {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (paused && !labelsShowing) {
|
||||||
|
Overlays.editOverlay(pauseButton, {
|
||||||
|
text: "Paused",
|
||||||
|
backgroundColor: {
|
||||||
|
red: 255,
|
||||||
|
green: 50,
|
||||||
|
blue: 50
|
||||||
|
}
|
||||||
|
});
|
||||||
|
showLabels();
|
||||||
|
}
|
||||||
|
if (paused == false && labelsShowing) {
|
||||||
|
Overlays.editOverlay(pauseButton, {
|
||||||
|
text: "Pause",
|
||||||
|
backgroundColor: {
|
||||||
|
red: 200,
|
||||||
|
green: 200,
|
||||||
|
blue: 255
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hideLabels();
|
||||||
|
}
|
||||||
|
planetView = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyPressEvent(event) {
|
||||||
|
// Jump back to solar system view
|
||||||
|
if (event.text == "TAB" && planetView) {
|
||||||
|
if (earthView) {
|
||||||
|
satelliteGame.endGame();
|
||||||
|
earthView = false;
|
||||||
|
}
|
||||||
|
MyAvatar.position = startingPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseDoublePressEvent(event) {
|
||||||
|
if (earthView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var pickRay = Camera.computePickRay(event.x, event.y)
|
||||||
|
var rayPickResult = Entities.findRayIntersection(pickRay, true);
|
||||||
|
|
||||||
|
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||||
|
if (rayPickResult.entityID === labels[i]) {
|
||||||
|
planetView = true;
|
||||||
|
if (i == 2) {
|
||||||
|
MyAvatar.position = Vec3.sum(center, {
|
||||||
|
x: 200,
|
||||||
|
y: 200,
|
||||||
|
z: 200
|
||||||
|
});
|
||||||
|
Camera.setPosition(Vec3.sum(center, {
|
||||||
|
x: 200,
|
||||||
|
y: 200,
|
||||||
|
z: 200
|
||||||
|
}));
|
||||||
|
earthView = true;
|
||||||
|
satelliteGame = new SatelliteGame();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
MyAvatar.position = Vec3.sum({
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 3.0
|
||||||
|
}, planet_properties[i].position);
|
||||||
|
Camera.lookAt(planet_properties[i].position);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Create UI panel
|
||||||
|
var panel = new Panel(PANEL_X, PANEL_Y);
|
||||||
|
var panelItems = [];
|
||||||
|
|
||||||
|
var g_multiplier = 1.0;
|
||||||
|
panelItems.push(panel.newSlider("Adjust Gravitational Force: ", 0.1, 5.0,
|
||||||
|
function(value) {
|
||||||
|
g_multiplier = value;
|
||||||
|
G = G_ref * g_multiplier;
|
||||||
|
},
|
||||||
|
|
||||||
|
function() {
|
||||||
|
return g_multiplier;
|
||||||
|
},
|
||||||
|
function(value) {
|
||||||
|
return value.toFixed(1) + "x";
|
||||||
|
}));
|
||||||
|
|
||||||
|
var period_multiplier = 1.0;
|
||||||
|
var last_alpha = period_multiplier;
|
||||||
|
panelItems.push(panel.newSlider("Adjust Orbital Period: ", 0.1, 3.0,
|
||||||
|
function(value) {
|
||||||
|
period_multiplier = value;
|
||||||
|
changePeriod(period_multiplier);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
return period_multiplier;
|
||||||
|
},
|
||||||
|
function(value) {
|
||||||
|
return (value).toFixed(2) + "x";
|
||||||
|
}));
|
||||||
|
|
||||||
|
panelItems.push(panel.newCheckbox("Leave Trails: ",
|
||||||
|
function(value) {
|
||||||
|
trailsEnabled = value;
|
||||||
|
if (trailsEnabled) {
|
||||||
|
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||||
|
resetTrails(i);
|
||||||
|
}
|
||||||
|
//if trails are off and we've already created trails, remove existing trails
|
||||||
|
} else if (planetLines.length != 0) {
|
||||||
|
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||||
|
for (var j = 0; j < planetLines[i].length; ++j) {
|
||||||
|
Entities.deleteEntity(planetLines[i][j]);
|
||||||
|
}
|
||||||
|
planetLines[i] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
return trailsEnabled;
|
||||||
|
},
|
||||||
|
function(value) {
|
||||||
|
return value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
panelItems.push(panel.newCheckbox("Energy Error Calculations: ",
|
||||||
|
function(value) {
|
||||||
|
energyConserved = value;
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
return energyConserved;
|
||||||
|
},
|
||||||
|
function(value) {
|
||||||
|
return value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Update global G constant, period, poke velocity to new value
|
||||||
|
function changePeriod(alpha) {
|
||||||
|
var ratio = last_alpha / alpha;
|
||||||
|
G = Math.pow(ratio, 2.0) * G;
|
||||||
|
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||||
|
var properties = planet_properties[i];
|
||||||
|
properties.period = ratio * properties.period;
|
||||||
|
properties.velocity = Vec3.multiply(ratio, properties.velocity);
|
||||||
|
|
||||||
|
}
|
||||||
|
last_alpha = alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Clean up models, UI panels, lines, and button overlays
|
||||||
|
function scriptEnding() {
|
||||||
|
|
||||||
|
satelliteGame.endGame();
|
||||||
|
|
||||||
|
Entities.deleteEntity(theSun);
|
||||||
|
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||||
|
Entities.deleteEntity(planets[i]);
|
||||||
|
}
|
||||||
|
Menu.removeMenu("Developer > Scene");
|
||||||
|
panel.destroy();
|
||||||
|
Overlays.deleteOverlay(pauseButton);
|
||||||
|
|
||||||
|
var e = Entities.findEntities(MyAvatar.position, 16000);
|
||||||
|
for (i = 0; i < e.length; i++) {
|
||||||
|
var props = Entities.getEntityProperties(e[i]);
|
||||||
|
if (props.type === "Line" || props.type === "Text") {
|
||||||
|
Entities.deleteEntity(e[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) {
|
||||||
|
return panel.mouseMoveEvent(event);
|
||||||
|
});
|
||||||
|
Controller.mousePressEvent.connect(function panelMousePressEvent(event) {
|
||||||
|
return panel.mousePressEvent(event);
|
||||||
|
});
|
||||||
|
Controller.mouseDoublePressEvent.connect(function panelMouseDoublePressEvent(event) {
|
||||||
|
return panel.mouseDoublePressEvent(event);
|
||||||
|
});
|
||||||
|
Controller.mouseReleaseEvent.connect(function(event) {
|
||||||
|
return panel.mouseReleaseEvent(event);
|
||||||
|
});
|
||||||
|
Controller.mousePressEvent.connect(mousePressEvent);
|
||||||
|
Controller.mouseDoublePressEvent.connect(mouseDoublePressEvent);
|
||||||
|
Controller.keyPressEvent.connect(keyPressEvent);
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(scriptEnding);
|
||||||
|
Script.update.connect(update);
|
42
examples/html/jsstreamplayer.html
Normal file
42
examples/html/jsstreamplayer.html
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<!-- -->
|
||||||
|
<!-- #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 -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function(){
|
||||||
|
if (window.EventBridge !== undefined) {
|
||||||
|
EventBridge.scriptEventReceived.connect(function(data) {
|
||||||
|
var myData = JSON.parse(data);
|
||||||
|
if (myData.action == "changeStream") {
|
||||||
|
$('body > audio').attr("src", myData.stream);
|
||||||
|
}
|
||||||
|
if (myData.action == "changeVolume") {
|
||||||
|
$('body > audio').prop("volume", myData.volume);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
EventBridge.emitWebEvent("loaded");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<audio controls src="" controls autoplay></audio>
|
||||||
|
</body>
|
||||||
|
</html>
|
140
examples/html/plankySettings.html
Normal file
140
examples/html/plankySettings.html
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var properties = [];
|
||||||
|
function sendWebEvent(data) {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
PropertyInput = function(key, label, value, attributes) {
|
||||||
|
this.key = key;
|
||||||
|
this.label = label;
|
||||||
|
this.value = value;
|
||||||
|
this.attributes = attributes;
|
||||||
|
var self = this;
|
||||||
|
this.construct = function() {
|
||||||
|
self.widget = $('<div>').addClass('property').append(self.createLabel()).append(self.createValueDiv());
|
||||||
|
$('#properties-list').append(self.widget);
|
||||||
|
};
|
||||||
|
this.createValue = self.__proto__.createValue;
|
||||||
|
this.getValue = self.__proto__.getValue;
|
||||||
|
this.createValueDiv = function() {
|
||||||
|
self.inputDiv = $('<div>').addClass('value').append(self.createValue());
|
||||||
|
return self.inputDiv;
|
||||||
|
};
|
||||||
|
this.addButton = function(id, buttonText) {
|
||||||
|
self.inputDiv.append($('<div>').append($('<input>').attr('type', 'button').attr('id', id).val(buttonText)));
|
||||||
|
};
|
||||||
|
this.createWidget = function() {
|
||||||
|
self.widget = $('<div>').addClass('property').append(self.createLabel()).append(self.inputDiv);
|
||||||
|
return self.widget;
|
||||||
|
};
|
||||||
|
this.createLabel = function() {
|
||||||
|
self.label = $('<div>').addClass('label').text(label);
|
||||||
|
return self.label;
|
||||||
|
};
|
||||||
|
this.setValue = function(value) {
|
||||||
|
self.input.val(value);
|
||||||
|
};
|
||||||
|
this.construct();
|
||||||
|
};
|
||||||
|
|
||||||
|
var valueChangeHandler = function() {
|
||||||
|
|
||||||
|
sendWebEvent({
|
||||||
|
action: 'value-change',
|
||||||
|
option: $(this).data('var-name'),
|
||||||
|
value: properties[$(this).data('var-name')].getValue()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
NumberInput = function(key, label, value, attributes) {
|
||||||
|
PropertyInput.call(this, key, label, value, attributes);
|
||||||
|
};
|
||||||
|
NumberInput.prototype = Object.create(PropertyInput.prototype);
|
||||||
|
NumberInput.prototype.constructor = NumberInput;
|
||||||
|
NumberInput.prototype.createValue = function() {
|
||||||
|
this.input = $('<input>').data('var-name', this.key).attr('name', this.key).attr('type', 'number').val(this.value).on('change', valueChangeHandler);
|
||||||
|
if (this.attributes !== undefined) {
|
||||||
|
this.input.attr(this.attributes);
|
||||||
|
}
|
||||||
|
return this.input;
|
||||||
|
};
|
||||||
|
NumberInput.prototype.getValue = function() {
|
||||||
|
return parseFloat(this.input.val());
|
||||||
|
};
|
||||||
|
|
||||||
|
CoordinateInput = function(key, label, value, attributes) {
|
||||||
|
PropertyInput.call(this, key, label, value, attributes);
|
||||||
|
};
|
||||||
|
CoordinateInput.prototype = Object.create(PropertyInput.prototype);
|
||||||
|
CoordinateInput.prototype.constructor = CoordinateInput;
|
||||||
|
CoordinateInput.prototype.createValue = function() {
|
||||||
|
this.inputX = $('<input>').data('var-name', this.key).attr('name', this.key + '-x').attr('type', 'number').addClass('coord').val(this.value.x).on('change', valueChangeHandler);
|
||||||
|
this.inputY = $('<input>').data('var-name', this.key).attr('name', this.key + '-y').attr('type', 'number').addClass('coord').val(this.value.y).on('change', valueChangeHandler);
|
||||||
|
this.inputZ = $('<input>').data('var-name', this.key).attr('name', this.key + '-z').attr('type', 'number').addClass('coord').val(this.value.z).on('change', valueChangeHandler);
|
||||||
|
if (this.attributes !== undefined) {
|
||||||
|
this.inputX.attr(this.attributes);
|
||||||
|
this.inputY.attr(this.attributes);
|
||||||
|
this.inputZ.attr(this.attributes);
|
||||||
|
}
|
||||||
|
return [encapsulateInput(this.inputX, 'X'), encapsulateInput(this.inputY, 'Y'), encapsulateInput(this.inputZ, 'Z')];
|
||||||
|
};
|
||||||
|
CoordinateInput.prototype.getValue = function() {
|
||||||
|
return {x: parseFloat(this.inputX.val()), y: parseFloat(this.inputY.val()), z: parseFloat(this.inputZ.val())};
|
||||||
|
};
|
||||||
|
function encapsulateInput(input, label) {
|
||||||
|
return $('<div>').addClass('input-area').append(label + ' ').append(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addHeader(label) {
|
||||||
|
$('#properties-list').append($('<div>').addClass('section-header').append($('<label>').text(label)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
addHeader('Stack Settings');
|
||||||
|
properties['numLayers'] = new NumberInput('numLayers', 'Layers', 17, {'min': 0, 'max': 300, 'step': 1});
|
||||||
|
properties['blocksPerLayer'] = new NumberInput('blocksPerLayer', 'Blocks per layer', 4, {'min': 1, 'max': 100, 'step': 1});
|
||||||
|
properties['blockSize'] = new CoordinateInput('blockSize', 'Block size', {x: 0.2, y: 0.1, z: 0.8}, {'min': 0.05, 'max': 20, 'step': 0.1});
|
||||||
|
properties['blockSpacing'] = new NumberInput('blockSpacing', 'Block spacing', properties['blockSize'].getValue().x / properties['blocksPerLayer'].getValue(), {'min': 0, 'max': 20, 'step': 0.01});
|
||||||
|
properties['blockSpacing'].addButton('btn-recalculate-spacing', 'Recalculate spacing');
|
||||||
|
$('#btn-recalculate-spacing').on('click', function() {
|
||||||
|
properties['blockSpacing'].setValue(properties['blockSize'].getValue().x / properties['blocksPerLayer'].getValue());
|
||||||
|
});
|
||||||
|
properties['blockHeightVariation'] = new NumberInput('blockHeightVariation', 'Block height variation (%)', 0.1, {'min': 0, 'max': 1, 'step': 0.01});
|
||||||
|
addHeader('Physics Settings');
|
||||||
|
properties['gravity'] = new CoordinateInput('gravity', 'Gravity', {x: 0, y: -2.8, z: 0}, {'step': 0.01});
|
||||||
|
properties['density'] = new NumberInput('density', 'Density', 4000, {'min': 0, 'max': 4000, 'step': 1});
|
||||||
|
properties['dampingFactor'] = new NumberInput('dampingFactor', 'Damping factor', 0.98, {'min': 0, 'max': 1, 'step': 0.01});
|
||||||
|
properties['angularDampingFactor'] = new NumberInput('angularDampingFactor', 'Angular damping factor', 0.8, {'min': 0, 'max': 1, 'step': 0.01});
|
||||||
|
properties['friction'] = new NumberInput('friction', 'Friction', 0.99, {'min': 0, 'max': 1, 'step': 0.01});
|
||||||
|
properties['restitution'] = new NumberInput('restitution', 'Restitution', 0.0, {'min': 0, 'max': 1, 'step': 0.01});
|
||||||
|
addHeader('Spawn Settings');
|
||||||
|
properties['spawnDistance'] = new NumberInput('spawnDistance', 'Spawn distance (meters)', 3);
|
||||||
|
properties['blockYawOffset'] = new NumberInput('blockYawOffset', 'Block yaw offset (degrees)', 45, {'min': 0, 'max': 360, 'step': 1});
|
||||||
|
properties['baseDimension'] = new CoordinateInput('baseDimension', 'Base dimension', {x: 7, y: 2, z: 7}, {'min': 0.5, 'max': 200, 'step': 0.1});
|
||||||
|
addHeader('Actions');
|
||||||
|
$('#properties-list')
|
||||||
|
.append($('<input>').val('factory reset').attr('type', 'button').on('click', function() { sendWebEvent({action: 'factory-reset'}); }))
|
||||||
|
.append($('<input>').val('save as default').attr('type', 'button').on('click', function() { sendWebEvent({action: 'save-default'}); }))
|
||||||
|
.append($('<input>').val('cleanup planky').attr('type', 'button').on('click', function() { sendWebEvent({action: 'cleanup'}); }));
|
||||||
|
if (window.EventBridge !== undefined) {
|
||||||
|
EventBridge.scriptEventReceived.connect(function(data) {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
if (data.action == 'load') {
|
||||||
|
$.each(data.options, function(option, value) {
|
||||||
|
properties[option].setValue(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sendWebEvent({action: 'loaded'});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="properties">
|
||||||
|
<div id="properties-list"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
331
examples/leaves.js
Executable file
331
examples/leaves.js
Executable file
|
@ -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
|
|
@ -141,7 +141,7 @@ CameraManager = function() {
|
||||||
|
|
||||||
// Pick a point INITIAL_ZOOM_DISTANCE in front of the camera to use as a focal point
|
// Pick a point INITIAL_ZOOM_DISTANCE in front of the camera to use as a focal point
|
||||||
that.zoomDistance = INITIAL_ZOOM_DISTANCE;
|
that.zoomDistance = INITIAL_ZOOM_DISTANCE;
|
||||||
that.targetZoomDistance = that.zoomDistance;
|
that.targetZoomDistance = that.zoomDistance + 3.0;
|
||||||
var focalPoint = Vec3.sum(Camera.getPosition(),
|
var focalPoint = Vec3.sum(Camera.getPosition(),
|
||||||
Vec3.multiply(that.zoomDistance, Quat.getFront(Camera.getOrientation())));
|
Vec3.multiply(that.zoomDistance, Quat.getFront(Camera.getOrientation())));
|
||||||
|
|
||||||
|
@ -150,6 +150,7 @@ CameraManager = function() {
|
||||||
var xzDist = Math.sqrt(dPos.x * dPos.x + dPos.z * dPos.z);
|
var xzDist = Math.sqrt(dPos.x * dPos.x + dPos.z * dPos.z);
|
||||||
|
|
||||||
that.targetPitch = -Math.atan2(dPos.y, xzDist) * 180 / Math.PI;
|
that.targetPitch = -Math.atan2(dPos.y, xzDist) * 180 / Math.PI;
|
||||||
|
that.targetPitch += (90 - that.targetPitch) / 3.0; // Swing camera "up" to look down at the focal point
|
||||||
that.targetYaw = Math.atan2(dPos.x, dPos.z) * 180 / Math.PI;
|
that.targetYaw = Math.atan2(dPos.x, dPos.z) * 180 / Math.PI;
|
||||||
that.pitch = that.targetPitch;
|
that.pitch = that.targetPitch;
|
||||||
that.yaw = that.targetYaw;
|
that.yaw = that.targetYaw;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
186
examples/utilities/tools/vector.js
Normal file
186
examples/utilities/tools/vector.js
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
//
|
||||||
|
// vector.js
|
||||||
|
// examples
|
||||||
|
//
|
||||||
|
// Created by Bridget Went on 7/1/15.
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// A template for creating vector arrows using line entities. A VectorArrow object creates a
|
||||||
|
// draggable vector arrow where the user clicked at a specified distance from the viewer.
|
||||||
|
// The relative magnitude and direction of the vector may be displayed.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
var LINE_DIMENSIONS = 100;
|
||||||
|
var LIFETIME = 6000;
|
||||||
|
var RAD_TO_DEG = 180.0 / Math.PI;
|
||||||
|
|
||||||
|
var LINE_WIDTH = 4;
|
||||||
|
var ARROW_WIDTH = 6;
|
||||||
|
var line, linePosition;
|
||||||
|
var arrow1, arrow2;
|
||||||
|
|
||||||
|
var SCALE = 0.15;
|
||||||
|
var ANGLE = 150.0;
|
||||||
|
|
||||||
|
|
||||||
|
VectorArrow = function(distance, showStats, statsTitle, statsPosition) {
|
||||||
|
this.magnitude = 0;
|
||||||
|
this.direction = {x: 0, y: 0, z: 0};
|
||||||
|
|
||||||
|
this.showStats = showStats;
|
||||||
|
this.isDragging = false;
|
||||||
|
|
||||||
|
this.newLine = function(position) {
|
||||||
|
linePosition = position;
|
||||||
|
var points = [];
|
||||||
|
|
||||||
|
line = Entities.addEntity({
|
||||||
|
position: linePosition,
|
||||||
|
type: "Line",
|
||||||
|
color: {red: 255, green: 255, blue: 255},
|
||||||
|
dimensions: {
|
||||||
|
x: LINE_DIMENSIONS,
|
||||||
|
y: LINE_DIMENSIONS,
|
||||||
|
z: LINE_DIMENSIONS
|
||||||
|
},
|
||||||
|
lineWidth: LINE_WIDTH,
|
||||||
|
lifetime: LIFETIME,
|
||||||
|
linePoints: []
|
||||||
|
});
|
||||||
|
|
||||||
|
arrow1 = Entities.addEntity({
|
||||||
|
position: {x: 0, y: 0, z: 0},
|
||||||
|
type: "Line",
|
||||||
|
dimensions: {
|
||||||
|
x: LINE_DIMENSIONS,
|
||||||
|
y: LINE_DIMENSIONS,
|
||||||
|
z: LINE_DIMENSIONS
|
||||||
|
},
|
||||||
|
color: {red: 255, green: 255, blue: 255},
|
||||||
|
lineWidth: ARROW_WIDTH,
|
||||||
|
linePoints: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
arrow2 = Entities.addEntity({
|
||||||
|
position: {x: 0, y: 0, z: 0},
|
||||||
|
type: "Line",
|
||||||
|
dimensions: {
|
||||||
|
x: LINE_DIMENSIONS,
|
||||||
|
y: LINE_DIMENSIONS,
|
||||||
|
z: LINE_DIMENSIONS
|
||||||
|
},
|
||||||
|
color: {red: 255, green: 255, blue: 255},
|
||||||
|
lineWidth: ARROW_WIDTH,
|
||||||
|
linePoints: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var STATS_DIMENSIONS = {
|
||||||
|
x: 4.0,
|
||||||
|
y: 1.5,
|
||||||
|
z: 0.1
|
||||||
|
};
|
||||||
|
var TEXT_HEIGHT = 0.3;
|
||||||
|
|
||||||
|
this.onMousePressEvent = function(event) {
|
||||||
|
|
||||||
|
this.newLine(computeWorldPoint(event));
|
||||||
|
|
||||||
|
if (this.showStats) {
|
||||||
|
this.label = Entities.addEntity({
|
||||||
|
type: "Text",
|
||||||
|
position: statsPosition,
|
||||||
|
dimensions: STATS_DIMENSIONS,
|
||||||
|
lineHeight: TEXT_HEIGHT,
|
||||||
|
faceCamera: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDragging = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.onMouseMoveEvent = function(event) {
|
||||||
|
|
||||||
|
if (!this.isDragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var worldPoint = computeWorldPoint(event);
|
||||||
|
var localPoint = computeLocalPoint(event, linePosition);
|
||||||
|
points = [{x: 0, y: 0, z: 0}, localPoint];
|
||||||
|
Entities.editEntity(line, { linePoints: points });
|
||||||
|
|
||||||
|
var nextOffset = Vec3.multiply(SCALE, localPoint);
|
||||||
|
var normOffset = Vec3.normalize(localPoint);
|
||||||
|
var axis = Vec3.cross(normOffset, Quat.getFront(Camera.getOrientation()) );
|
||||||
|
axis = Vec3.cross(axis, normOffset);
|
||||||
|
var rotate1 = Quat.angleAxis(ANGLE, axis);
|
||||||
|
var rotate2 = Quat.angleAxis(-ANGLE, axis);
|
||||||
|
|
||||||
|
// Rotate arrow head to follow direction of the line
|
||||||
|
Entities.editEntity(arrow1, {
|
||||||
|
visible: true,
|
||||||
|
position: worldPoint,
|
||||||
|
linePoints: [{x: 0, y: 0, z: 0}, nextOffset],
|
||||||
|
rotation: rotate1
|
||||||
|
});
|
||||||
|
Entities.editEntity(arrow2, {
|
||||||
|
visible: true,
|
||||||
|
position: worldPoint,
|
||||||
|
linePoints: [{x: 0, y: 0, z: 0}, nextOffset],
|
||||||
|
rotation: rotate2
|
||||||
|
});
|
||||||
|
|
||||||
|
this.magnitude = Vec3.length(localPoint) * 0.1;
|
||||||
|
this.direction = Vec3.normalize(Vec3.subtract(worldPoint, linePosition));
|
||||||
|
|
||||||
|
if (this.showStats) {
|
||||||
|
this.editLabel(statsTitle + " Magnitude " + this.magnitude.toFixed(2) + ", Direction: " +
|
||||||
|
this.direction.x.toFixed(2) + ", " + this.direction.y.toFixed(2) + ", " + this.direction.z.toFixed(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onMouseReleaseEvent = function() {
|
||||||
|
this.isDragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cleanup = function() {
|
||||||
|
Entities.deleteEntity(line);
|
||||||
|
Entities.deleteEntity(arrow1);
|
||||||
|
Entities.deleteEntity(arrow2);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deleteLabel = function() {
|
||||||
|
Entities.deleteEntity(this.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editLabel = function(str) {
|
||||||
|
if(!this.showStats) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Entities.editEntity(this.label, {
|
||||||
|
text: str
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeWorldPoint(event) {
|
||||||
|
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||||
|
var addVector = Vec3.multiply(pickRay.direction, distance);
|
||||||
|
return Vec3.sum(Camera.getPosition(), addVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeLocalPoint(event, linePosition) {
|
||||||
|
var localPoint = Vec3.subtract(computeWorldPoint(event), linePosition);
|
||||||
|
return localPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
examples/zones/jsstreamplayerdomain-zone-entity.js
Normal file
33
examples/zones/jsstreamplayerdomain-zone-entity.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
})
|
42
examples/zones/jsstreamplayerdomain-zone.html
Normal file
42
examples/zones/jsstreamplayerdomain-zone.html
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<!-- -->
|
||||||
|
<!-- #20628: JS Stream Player Domain-Zone -->
|
||||||
|
<!-- ************************************* -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- Created by Kevin M. Thomas and Thoys 07/20/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 -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function(){
|
||||||
|
if (window.EventBridge !== undefined) {
|
||||||
|
EventBridge.scriptEventReceived.connect(function(data) {
|
||||||
|
var myData = JSON.parse(data);
|
||||||
|
if (myData.action == "changeStream") {
|
||||||
|
$('body > audio').attr("src", myData.stream);
|
||||||
|
}
|
||||||
|
if (myData.action == "changeVolume") {
|
||||||
|
$('body > audio').prop("volume", myData.volume);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
EventBridge.emitWebEvent("loaded");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<audio controls src="" controls autoplay></audio>
|
||||||
|
</body>
|
||||||
|
</html>
|
176
examples/zones/jsstreamplayerdomain-zone.js
Normal file
176
examples/zones/jsstreamplayerdomain-zone.js
Normal file
|
@ -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);
|
|
@ -331,7 +331,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||||
_lastNackTime(usecTimestampNow()),
|
_lastNackTime(usecTimestampNow()),
|
||||||
_lastSendDownstreamAudioStats(usecTimestampNow()),
|
_lastSendDownstreamAudioStats(usecTimestampNow()),
|
||||||
_isVSyncOn(true),
|
_isVSyncOn(true),
|
||||||
_isThrottleFPSEnabled(false),
|
_isThrottleFPSEnabled(true),
|
||||||
_aboutToQuit(false),
|
_aboutToQuit(false),
|
||||||
_notifiedPacketVersionMismatchThisDomain(false),
|
_notifiedPacketVersionMismatchThisDomain(false),
|
||||||
_glWidget(new GLCanvas()),
|
_glWidget(new GLCanvas()),
|
||||||
|
@ -3291,6 +3291,8 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
||||||
|
|
||||||
renderContext._drawItemStatus = sceneInterface->doEngineDisplayItemStatus();
|
renderContext._drawItemStatus = sceneInterface->doEngineDisplayItemStatus();
|
||||||
|
|
||||||
|
renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion);
|
||||||
|
|
||||||
renderArgs->_shouldRender = LODManager::shouldRender;
|
renderArgs->_shouldRender = LODManager::shouldRender;
|
||||||
|
|
||||||
renderContext.args = renderArgs;
|
renderContext.args = renderArgs;
|
||||||
|
@ -3405,7 +3407,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi
|
||||||
// This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further
|
// This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further
|
||||||
// investigated in order to adapt the technique while fixing the head rendering issue,
|
// investigated in order to adapt the technique while fixing the head rendering issue,
|
||||||
// but the complexity of the hack suggests that a better approach
|
// but the complexity of the hack suggests that a better approach
|
||||||
_mirrorCamera.setPosition(_myAvatar->getHead()->getEyePosition() +
|
_mirrorCamera.setPosition(_myAvatar->getDefaultEyePosition() +
|
||||||
_myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
|
_myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
|
||||||
}
|
}
|
||||||
_mirrorCamera.setProjection(glm::perspective(glm::radians(fov), aspect, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP));
|
_mirrorCamera.setProjection(glm::perspective(glm::radians(fov), aspect, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP));
|
||||||
|
|
|
@ -330,7 +330,7 @@ Menu::Menu() {
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere,
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere,
|
||||||
0, // QML Qt::SHIFT | Qt::Key_A,
|
0, // QML Qt::SHIFT | Qt::Key_A,
|
||||||
true);
|
true);
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion);
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion);
|
||||||
|
|
||||||
MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
|
MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
|
||||||
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);
|
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);
|
||||||
|
@ -368,7 +368,7 @@ Menu::Menu() {
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::RenderTargetFramerateVSyncOn, 0, true,
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::RenderTargetFramerateVSyncOn, 0, true,
|
||||||
qApp, SLOT(setVSyncEnabled()));
|
qApp, SLOT(setVSyncEnabled()));
|
||||||
#endif
|
#endif
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, false,
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, true,
|
||||||
qApp, SLOT(setThrottleFPSEnabled()));
|
qApp, SLOT(setThrottleFPSEnabled()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,6 @@ namespace MenuOption {
|
||||||
const QString AddressBar = "Show Address Bar";
|
const QString AddressBar = "Show Address Bar";
|
||||||
const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
|
const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
|
||||||
const QString AlternateIK = "Alternate IK";
|
const QString AlternateIK = "Alternate IK";
|
||||||
const QString AmbientOcclusion = "Ambient Occlusion";
|
|
||||||
const QString Animations = "Animations...";
|
const QString Animations = "Animations...";
|
||||||
const QString Atmosphere = "Atmosphere";
|
const QString Atmosphere = "Atmosphere";
|
||||||
const QString Attachments = "Attachments...";
|
const QString Attachments = "Attachments...";
|
||||||
|
@ -165,6 +164,7 @@ namespace MenuOption {
|
||||||
const QString ControlWithSpeech = "Control With Speech";
|
const QString ControlWithSpeech = "Control With Speech";
|
||||||
const QString CopyAddress = "Copy Address to Clipboard";
|
const QString CopyAddress = "Copy Address to Clipboard";
|
||||||
const QString CopyPath = "Copy Path to Clipboard";
|
const QString CopyPath = "Copy Path to Clipboard";
|
||||||
|
const QString DebugAmbientOcclusion = "Debug Ambient Occlusion";
|
||||||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||||
const QString DeleteBookmark = "Delete Bookmark...";
|
const QString DeleteBookmark = "Delete Bookmark...";
|
||||||
const QString DisableActivityLogger = "Disable Activity Logger";
|
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||||
|
|
|
@ -195,7 +195,10 @@ void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OBJReader::isValidTexture(const QByteArray &filename) {
|
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);
|
QNetworkReply *netReply = request(candidateUrl, true);
|
||||||
bool isValid = netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200);
|
bool isValid = netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200);
|
||||||
netReply->deleteLater();
|
netReply->deleteLater();
|
||||||
|
@ -242,7 +245,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
|
||||||
} else if ((token == "map_Kd") || (token == "map_Ks")) {
|
} else if ((token == "map_Kd") || (token == "map_Ks")) {
|
||||||
QByteArray filename = QUrl(tokenizer.getLineAsDatum()).fileName().toUtf8();
|
QByteArray filename = QUrl(tokenizer.getLineAsDatum()).fileName().toUtf8();
|
||||||
if (filename.endsWith(".tga")) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
if (isValidTexture(filename)) {
|
if (isValidTexture(filename)) {
|
||||||
|
@ -252,7 +255,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
|
||||||
currentMaterial.specularTextureFilename = filename;
|
currentMaterial.specularTextureFilename = filename;
|
||||||
}
|
}
|
||||||
} else {
|
} 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();
|
QByteArray groupName = tokenizer.getDatum();
|
||||||
currentGroup = groupName;
|
currentGroup = groupName;
|
||||||
//qCDebug(modelformat) << "new group:" << groupName;
|
//qCDebug(modelformat) << "new group:" << groupName;
|
||||||
} else if (token == "mtllib") {
|
} else if (token == "mtllib" && _url) {
|
||||||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||||
break;
|
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
|
break; // Some files use mtllib over and over again for the same libraryName
|
||||||
}
|
}
|
||||||
librariesSeen[libraryName] = true;
|
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;
|
qCDebug(modelformat) << "OBJ Reader new library:" << libraryName << " at:" << libraryUrl;
|
||||||
QNetworkReply* netReply = request(libraryUrl, false);
|
QNetworkReply* netReply = request(libraryUrl, false);
|
||||||
if (netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200)) {
|
if (netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200)) {
|
||||||
parseMaterialLibrary(netReply);
|
parseMaterialLibrary(netReply);
|
||||||
} else {
|
} 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();
|
netReply->deleteLater();
|
||||||
} else if (token == "usemtl") {
|
} else if (token == "usemtl") {
|
||||||
|
@ -406,7 +411,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
|
||||||
OBJTokenizer tokenizer(device);
|
OBJTokenizer tokenizer(device);
|
||||||
float scaleGuess = 1.0f;
|
float scaleGuess = 1.0f;
|
||||||
|
|
||||||
this->url = url;
|
_url = url;
|
||||||
geometry.meshExtents.reset();
|
geometry.meshExtents.reset();
|
||||||
geometry.meshes.append(FBXMesh());
|
geometry.meshes.append(FBXMesh());
|
||||||
|
|
||||||
|
@ -441,7 +446,9 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
|
||||||
0, 0, 0, 1);
|
0, 0, 0, 1);
|
||||||
mesh.clusters.append(cluster);
|
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.
|
// 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();
|
QString filename = url->fileName();
|
||||||
int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail
|
int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail
|
||||||
QString basename = filename.remove(extIndex + 1, sizeof("obj"));
|
QString basename = filename.remove(extIndex + 1, sizeof("obj"));
|
||||||
|
@ -456,10 +463,12 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!textName.isEmpty()) {
|
if (!textName.isEmpty()) {
|
||||||
preDefinedMaterial.diffuseTextureFilename = textName;
|
preDefinedMaterial.diffuseTextureFilename = textName;
|
||||||
}
|
}
|
||||||
materials[SMART_DEFAULT_MATERIAL_NAME] = preDefinedMaterial;
|
materials[SMART_DEFAULT_MATERIAL_NAME] = preDefinedMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) {
|
for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) {
|
||||||
FBXMeshPart& meshPart = mesh.parts[i];
|
FBXMeshPart& meshPart = mesh.parts[i];
|
||||||
|
@ -467,10 +476,13 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
|
||||||
OBJFace leadFace = faceGroup[0]; // All the faces in the same group will have the same name and material.
|
OBJFace leadFace = faceGroup[0]; // All the faces in the same group will have the same name and material.
|
||||||
QString groupMaterialName = leadFace.materialName;
|
QString groupMaterialName = leadFace.materialName;
|
||||||
if (groupMaterialName.isEmpty() && (leadFace.textureUVIndices.count() > 0)) {
|
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;
|
groupMaterialName = SMART_DEFAULT_MATERIAL_NAME;
|
||||||
} else if (!groupMaterialName.isEmpty() && !materials.contains(groupMaterialName)) {
|
} 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;
|
groupMaterialName = SMART_DEFAULT_MATERIAL_NAME;
|
||||||
}
|
}
|
||||||
if (!groupMaterialName.isEmpty()) {
|
if (!groupMaterialName.isEmpty()) {
|
||||||
|
|
|
@ -69,12 +69,13 @@ public:
|
||||||
QVector<FaceGroup> faceGroups;
|
QVector<FaceGroup> faceGroups;
|
||||||
QString currentMaterialName;
|
QString currentMaterialName;
|
||||||
QHash<QString, OBJMaterial> materials;
|
QHash<QString, OBJMaterial> materials;
|
||||||
QUrl* url;
|
|
||||||
|
|
||||||
QNetworkReply* request(QUrl& url, bool isTest);
|
QNetworkReply* request(QUrl& url, bool isTest);
|
||||||
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
|
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
|
||||||
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url);
|
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url);
|
||||||
private:
|
private:
|
||||||
|
QUrl* _url = nullptr;
|
||||||
|
|
||||||
QHash<QByteArray, bool> librariesSeen;
|
QHash<QByteArray, bool> librariesSeen;
|
||||||
bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess);
|
bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess);
|
||||||
void parseMaterialLibrary(QIODevice* device);
|
void parseMaterialLibrary(QIODevice* device);
|
||||||
|
|
255
libraries/render-utils/src/AmbientOcclusionEffect.cpp
Normal file
255
libraries/render-utils/src/AmbientOcclusionEffect.cpp
Normal file
|
@ -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 <gpu/GPUConfig.h>
|
||||||
|
|
||||||
|
#include <gpu/GLBackend.h>
|
||||||
|
|
||||||
|
#include <glm/gtc/random.hpp>
|
||||||
|
|
||||||
|
#include <PathUtils.h>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#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<FramebufferCache>()->getFrameBufferSize().width(), DependencyManager::get<FramebufferCache>()->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<FramebufferCache>()->getFrameBufferSize().width(), DependencyManager::get<FramebufferCache>()->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<FramebufferCache>()->getFrameBufferSize().width(), DependencyManager::get<FramebufferCache>()->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<FramebufferCache>()->getPrimaryDepthTexture());
|
||||||
|
batch.setResourceTexture(1, DependencyManager::get<FramebufferCache>()->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<FramebufferCache>()->getFrameBufferSize().width());
|
||||||
|
batch._glUniform1f(_bufferHeightLoc, DependencyManager::get<FramebufferCache>()->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<GeometryCache>()->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<GeometryCache>()->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<GeometryCache>()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
|
||||||
|
|
||||||
|
// Blend step
|
||||||
|
getBlendPipeline();
|
||||||
|
batch.setResourceTexture(0, _hBlurTexture);
|
||||||
|
batch.setFramebuffer(DependencyManager::get<FramebufferCache>()->getPrimaryFramebuffer());
|
||||||
|
|
||||||
|
// Bind the fourth gpu::Pipeline we need - for blending the primary color buffer with blurred occlusion texture
|
||||||
|
batch.setPipeline(getBlendPipeline());
|
||||||
|
|
||||||
|
DependencyManager::get<GeometryCache>()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color);
|
||||||
|
|
||||||
|
// Ready to render
|
||||||
|
args->_context->syncCache();
|
||||||
|
args->_context->render((batch));
|
||||||
|
}
|
61
libraries/render-utils/src/AmbientOcclusionEffect.h
Normal file
61
libraries/render-utils/src/AmbientOcclusionEffect.h
Normal file
|
@ -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 <DependencyManager.h>
|
||||||
|
|
||||||
|
#include "render/DrawTask.h"
|
||||||
|
|
||||||
|
class AmbientOcclusion {
|
||||||
|
public:
|
||||||
|
|
||||||
|
AmbientOcclusion();
|
||||||
|
|
||||||
|
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
|
||||||
|
typedef render::Job::Model<AmbientOcclusion> 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
|
|
@ -22,6 +22,7 @@
|
||||||
#include "TextureCache.h"
|
#include "TextureCache.h"
|
||||||
|
|
||||||
#include "render/DrawStatus.h"
|
#include "render/DrawStatus.h"
|
||||||
|
#include "AmbientOcclusionEffect.h"
|
||||||
|
|
||||||
#include "overlay3D_vert.h"
|
#include "overlay3D_vert.h"
|
||||||
#include "overlay3D_frag.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 DrawLight::JobModel("DrawLight")));
|
||||||
_jobs.push_back(Job(new RenderDeferred::JobModel("RenderDeferred")));
|
_jobs.push_back(Job(new RenderDeferred::JobModel("RenderDeferred")));
|
||||||
_jobs.push_back(Job(new ResolveDeferred::JobModel("ResolveDeferred")));
|
_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",
|
_jobs.push_back(Job(new FetchItems::JobModel("FetchTransparent",
|
||||||
FetchItems(
|
FetchItems(
|
||||||
ItemFilter::Builder::transparentShape().withoutLayered(),
|
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 DrawTransparentDeferred::JobModel("TransparentDeferred", _jobs.back().getOutput())));
|
||||||
|
|
||||||
_jobs.push_back(Job(new render::DrawStatus::JobModel("DrawStatus", renderedOpaques)));
|
_jobs.push_back(Job(new render::DrawStatus::JobModel("DrawStatus", renderedOpaques)));
|
||||||
|
|
||||||
|
|
||||||
_jobs.back().setEnabled(false);
|
_jobs.back().setEnabled(false);
|
||||||
_drawStatusJobIndex = _jobs.size() - 1;
|
_drawStatusJobIndex = _jobs.size() - 1;
|
||||||
|
|
||||||
_jobs.push_back(Job(new DrawOverlay3D::JobModel("DrawOverlay3D")));
|
_jobs.push_back(Job(new DrawOverlay3D::JobModel("DrawOverlay3D")));
|
||||||
|
|
||||||
_jobs.push_back(Job(new ResetGLState::JobModel()));
|
_jobs.push_back(Job(new ResetGLState::JobModel()));
|
||||||
|
|
||||||
// Give ourselves 3 frmaes of timer queries
|
// 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
|
// Make sure we turn the displayItemStatus on/off
|
||||||
setDrawItemStatus(renderContext->_drawItemStatus);
|
setDrawItemStatus(renderContext->_drawItemStatus);
|
||||||
|
|
||||||
|
// TODO: turn on/off AO through menu item
|
||||||
|
setOcclusionStatus(renderContext->_occlusionStatus);
|
||||||
|
|
||||||
renderContext->args->_context->syncCache();
|
renderContext->args->_context->syncCache();
|
||||||
|
|
||||||
for (auto job : _jobs) {
|
for (auto job : _jobs) {
|
||||||
|
|
|
@ -82,6 +82,11 @@ public:
|
||||||
void setDrawItemStatus(bool draw) { if (_drawStatusJobIndex >= 0) { _jobs[_drawStatusJobIndex].setEnabled(draw); } }
|
void setDrawItemStatus(bool draw) { if (_drawStatusJobIndex >= 0) { _jobs[_drawStatusJobIndex].setEnabled(draw); } }
|
||||||
bool doDrawItemStatus() const { if (_drawStatusJobIndex >= 0) { return _jobs[_drawStatusJobIndex].isEnabled(); } else { return false; } }
|
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);
|
virtual void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
|
||||||
|
|
||||||
|
|
||||||
|
|
109
libraries/render-utils/src/ambient_occlusion.slf
Normal file
109
libraries/render-utils/src/ambient_occlusion.slf
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
|
24
libraries/render-utils/src/ambient_occlusion.slv
Normal file
24
libraries/render-utils/src/ambient_occlusion.slv
Normal file
|
@ -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;
|
||||||
|
}
|
42
libraries/render-utils/src/gaussian_blur.slf
Normal file
42
libraries/render-utils/src/gaussian_blur.slf
Normal file
|
@ -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;
|
||||||
|
}
|
41
libraries/render-utils/src/gaussian_blur_horizontal.slv
Normal file
41
libraries/render-utils/src/gaussian_blur_horizontal.slv
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
|
41
libraries/render-utils/src/gaussian_blur_vertical.slv
Normal file
41
libraries/render-utils/src/gaussian_blur_vertical.slv
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
|
29
libraries/render-utils/src/occlusion_blend.slf
Normal file
29
libraries/render-utils/src/occlusion_blend.slf
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,6 +51,8 @@ public:
|
||||||
|
|
||||||
bool _drawItemStatus = false;
|
bool _drawItemStatus = false;
|
||||||
|
|
||||||
|
bool _occlusionStatus = false;
|
||||||
|
|
||||||
RenderContext() {}
|
RenderContext() {}
|
||||||
};
|
};
|
||||||
typedef std::shared_ptr<RenderContext> RenderContextPointer;
|
typedef std::shared_ptr<RenderContext> RenderContextPointer;
|
||||||
|
|
|
@ -9,13 +9,16 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include "BulletUtilTests.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
//#include "PhysicsTestUtil.h"
|
|
||||||
#include <BulletUtil.h>
|
#include <BulletUtil.h>
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
|
|
||||||
#include "BulletUtilTests.h"
|
// Add additional qtest functionality (the include order is important!)
|
||||||
|
#include "GlmTestUtils.h"
|
||||||
|
#include "../QTestExtensions.h"
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const glm::vec3 origin(0.0f);
|
const glm::vec3 origin(0.0f);
|
||||||
|
|
|
@ -13,9 +13,6 @@
|
||||||
#define hifi_BulletUtilTests_h
|
#define hifi_BulletUtilTests_h
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
// Add additional qtest functionality (the include order is important!)
|
|
||||||
#include "GlmTestUtils.h"
|
|
||||||
#include "../QTestExtensions.h"
|
|
||||||
|
|
||||||
class BulletUtilTests : public QObject {
|
class BulletUtilTests : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
//
|
|
||||||
// CollisionInfoTests.cpp
|
|
||||||
// tests/physics/src
|
|
||||||
//
|
|
||||||
// Created by Andrew Meadows on 2/21/2014.
|
|
||||||
// Copyright 2014 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 <iostream>
|
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtx/quaternion.hpp>
|
|
||||||
|
|
||||||
#include <CollisionInfo.h>
|
|
||||||
#include <SharedUtil.h>
|
|
||||||
#include <StreamUtils.h>
|
|
||||||
|
|
||||||
#include "CollisionInfoTests.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
QTEST_MAIN(CollisionInfoTests)
|
|
||||||
/*
|
|
||||||
static glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
|
|
||||||
static glm::vec3 xZxis(0.0f, 1.0f, 0.0f);
|
|
||||||
static glm::vec3 xYxis(0.0f, 0.0f, 1.0f);
|
|
||||||
|
|
||||||
void CollisionInfoTests::rotateThenTranslate() {
|
|
||||||
CollisionInfo collision;
|
|
||||||
collision._penetration = xAxis;
|
|
||||||
collision._contactPoint = xYxis;
|
|
||||||
collision._addedVelocity = xAxis + xYxis + xZxis;
|
|
||||||
|
|
||||||
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
|
|
||||||
float distance = 3.0f;
|
|
||||||
glm::vec3 translation = distance * xYxis;
|
|
||||||
|
|
||||||
collision.rotateThenTranslate(rotation, translation);
|
|
||||||
QCOMPARE(collision._penetration, xYxis);
|
|
||||||
|
|
||||||
glm::vec3 expectedContactPoint = -xAxis + translation;
|
|
||||||
QCOMPARE(collision._contactPoint, expectedContactPoint);
|
|
||||||
|
|
||||||
glm::vec3 expectedAddedVelocity = xYxis - xAxis + xZxis;
|
|
||||||
QCOMPARE(collision._addedVelocity, expectedAddedVelocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollisionInfoTests::translateThenRotate() {
|
|
||||||
CollisionInfo collision;
|
|
||||||
collision._penetration = xAxis;
|
|
||||||
collision._contactPoint = xYxis;
|
|
||||||
collision._addedVelocity = xAxis + xYxis + xZxis;
|
|
||||||
|
|
||||||
glm::quat rotation = glm::angleAxis( -PI_OVER_TWO, zAxis);
|
|
||||||
float distance = 3.0f;
|
|
||||||
glm::vec3 translation = distance * xYxis;
|
|
||||||
|
|
||||||
collision.translateThenRotate(translation, rotation);
|
|
||||||
QCOMPARE(collision._penetration, -xYxis);
|
|
||||||
|
|
||||||
glm::vec3 expectedContactPoint = (1.0f + distance) * xAxis;
|
|
||||||
QCOMPARE(collision._contactPoint, expectedContactPoint);
|
|
||||||
|
|
||||||
glm::vec3 expectedAddedVelocity = - xYxis + xAxis + xYxis;
|
|
||||||
QCOMPARE(collision._addedVelocity, expectedAddedVelocity);
|
|
||||||
}*/
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
//
|
|
||||||
// CollisionInfoTests.h
|
|
||||||
// tests/physics/src
|
|
||||||
//
|
|
||||||
// Created by Andrew Meadows on 2/21/2014.
|
|
||||||
// Copyright 2014 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_CollisionInfoTests_h
|
|
||||||
#define hifi_CollisionInfoTests_h
|
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
|
||||||
|
|
||||||
// Add additional qtest functionality (the include order is important!)
|
|
||||||
#include "GlmTestUtils.h"
|
|
||||||
#include "../QTestExtensions.h"
|
|
||||||
|
|
||||||
class CollisionInfoTests : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
// void rotateThenTranslate();
|
|
||||||
// void translateThenRotate();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_CollisionInfoTests_h
|
|
|
@ -9,11 +9,15 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include "MeshMassPropertiesTests.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
|
||||||
#include <MeshMassProperties.h>
|
#include <MeshMassProperties.h>
|
||||||
|
|
||||||
#include "MeshMassPropertiesTests.h"
|
// Add additional qtest functionality (the include order is important!)
|
||||||
|
#include "BulletTestUtils.h"
|
||||||
|
#include "GlmTestUtils.h"
|
||||||
|
#include "../QTestExtensions.h"
|
||||||
|
|
||||||
const btScalar acceptableRelativeError(1.0e-5f);
|
const btScalar acceptableRelativeError(1.0e-5f);
|
||||||
const btScalar acceptableAbsoluteError(1.0e-4f);
|
const btScalar acceptableAbsoluteError(1.0e-4f);
|
||||||
|
|
|
@ -13,12 +13,6 @@
|
||||||
#define hifi_MeshMassPropertiesTests_h
|
#define hifi_MeshMassPropertiesTests_h
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
// Add additional qtest functionality (the include order is important!)
|
|
||||||
#include "BulletTestUtils.h"
|
|
||||||
#include "GlmTestUtils.h"
|
|
||||||
#include "../QTestExtensions.h"
|
|
||||||
|
|
||||||
// Relative error macro (see errorTest in BulletTestUtils.h)
|
// Relative error macro (see errorTest in BulletTestUtils.h)
|
||||||
#define QCOMPARE_WITH_RELATIVE_ERROR(actual, expected, relativeError) \
|
#define QCOMPARE_WITH_RELATIVE_ERROR(actual, expected, relativeError) \
|
||||||
|
|
|
@ -9,12 +9,15 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include "ShapeColliderTests.h"
|
||||||
|
|
||||||
//#include <stdio.h>
|
//#include <stdio.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtx/quaternion.hpp>
|
#include <glm/gtx/quaternion.hpp>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
#include <AACubeShape.h>
|
#include <AACubeShape.h>
|
||||||
#include <CapsuleShape.h>
|
#include <CapsuleShape.h>
|
||||||
|
@ -25,7 +28,10 @@
|
||||||
#include <SphereShape.h>
|
#include <SphereShape.h>
|
||||||
#include <StreamUtils.h>
|
#include <StreamUtils.h>
|
||||||
|
|
||||||
#include "ShapeColliderTests.h"
|
// Add additional qtest functionality (the include order is important!)
|
||||||
|
#include "BulletTestUtils.h"
|
||||||
|
#include "GlmTestUtils.h"
|
||||||
|
#include "../QTestExtensions.h"
|
||||||
|
|
||||||
|
|
||||||
const glm::vec3 origin(0.0f);
|
const glm::vec3 origin(0.0f);
|
||||||
|
@ -1553,8 +1559,6 @@ void ShapeColliderTests::rayHitsCapsule() {
|
||||||
intersection._rayDirection = - xAxis;
|
intersection._rayDirection = - xAxis;
|
||||||
QCOMPARE(capsule.findRayIntersection(intersection), true);
|
QCOMPARE(capsule.findRayIntersection(intersection), true);
|
||||||
float expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine
|
float expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine
|
||||||
// float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance;
|
|
||||||
// for edge cases we allow a LOT of error
|
|
||||||
QCOMPARE_WITH_ABS_ERROR(intersection._hitDistance, expectedDistance, startDistance * EDGE_CASE_SLOP_FACTOR * EPSILON);
|
QCOMPARE_WITH_ABS_ERROR(intersection._hitDistance, expectedDistance, startDistance * EDGE_CASE_SLOP_FACTOR * EPSILON);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1564,8 +1568,6 @@ void ShapeColliderTests::rayHitsCapsule() {
|
||||||
intersection._rayDirection = - xAxis;
|
intersection._rayDirection = - xAxis;
|
||||||
QCOMPARE(capsule.findRayIntersection(intersection), true);
|
QCOMPARE(capsule.findRayIntersection(intersection), true);
|
||||||
float expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine
|
float expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine
|
||||||
// float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance;
|
|
||||||
// for edge cases we allow a LOT of error
|
|
||||||
QCOMPARE_WITH_ABS_ERROR(intersection._hitDistance, expectedDistance, startDistance * EPSILON * EDGE_CASE_SLOP_FACTOR);
|
QCOMPARE_WITH_ABS_ERROR(intersection._hitDistance, expectedDistance, startDistance * EPSILON * EDGE_CASE_SLOP_FACTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1575,8 +1577,6 @@ void ShapeColliderTests::rayHitsCapsule() {
|
||||||
intersection._rayDirection = - xAxis;
|
intersection._rayDirection = - xAxis;
|
||||||
QCOMPARE(capsule.findRayIntersection(intersection), true);
|
QCOMPARE(capsule.findRayIntersection(intersection), true);
|
||||||
float expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine
|
float expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine
|
||||||
float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance;
|
|
||||||
// for edge cases we allow a LOT of error
|
|
||||||
QCOMPARE_WITH_ABS_ERROR(intersection._hitDistance, expectedDistance, startDistance * EPSILON * EDGE_CASE_SLOP_FACTOR);
|
QCOMPARE_WITH_ABS_ERROR(intersection._hitDistance, expectedDistance, startDistance * EPSILON * EDGE_CASE_SLOP_FACTOR);
|
||||||
}
|
}
|
||||||
// TODO: test at steep angles near cylinder/cap junction
|
// TODO: test at steep angles near cylinder/cap junction
|
||||||
|
|
|
@ -13,13 +13,6 @@
|
||||||
#define hifi_ShapeColliderTests_h
|
#define hifi_ShapeColliderTests_h
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
// Add additional qtest functionality (the include order is important!)
|
|
||||||
#include "BulletTestUtils.h"
|
|
||||||
#include "GlmTestUtils.h"
|
|
||||||
#include "../QTestExtensions.h"
|
|
||||||
|
|
||||||
|
|
||||||
class ShapeColliderTests : public QObject {
|
class ShapeColliderTests : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <StreamUtils.h>
|
#include <StreamUtils.h>
|
||||||
|
|
||||||
#include "AngularConstraintTests.h"
|
#include "AngularConstraintTests.h"
|
||||||
|
#include "../QTestExtensions.h"
|
||||||
|
|
||||||
// Computes the error value between two quaternions (using glm::dot)
|
// Computes the error value between two quaternions (using glm::dot)
|
||||||
float getErrorDifference(const glm::quat& a, const glm::quat& b) {
|
float getErrorDifference(const glm::quat& a, const glm::quat& b) {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#ifndef hifi_AngularConstraintTests_h
|
#ifndef hifi_AngularConstraintTests_h
|
||||||
#define hifi_AngularConstraintTests_h
|
#define hifi_AngularConstraintTests_h
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
class AngularConstraintTests : public QObject {
|
class AngularConstraintTests : public QObject {
|
||||||
|
@ -21,10 +22,6 @@ private slots:
|
||||||
void testConeRollerConstraint();
|
void testConeRollerConstraint();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use QCOMPARE_WITH_ABS_ERROR and define it for glm::quat
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
float getErrorDifference(const glm::quat& a, const glm::quat& b);
|
float getErrorDifference(const glm::quat& a, const glm::quat& b);
|
||||||
QTextStream & operator << (QTextStream& stream, const glm::quat& q);
|
|
||||||
#include "../QTestExtensions.h"
|
|
||||||
|
|
||||||
#endif // hifi_AngularConstraintTests_h
|
#endif // hifi_AngularConstraintTests_h
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <qqueue.h>
|
#include <qqueue.h>
|
||||||
|
#include <../QTestExtensions.h>
|
||||||
|
|
||||||
QTEST_MAIN(MovingPercentileTests)
|
QTEST_MAIN(MovingPercentileTests)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
#define hifi_MovingPercentileTests_h
|
#define hifi_MovingPercentileTests_h
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
#include <../QTestExtensions.h>
|
|
||||||
|
|
||||||
class MovingPercentileTests : public QObject {
|
class MovingPercentileTests : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include "TransformTests.h"
|
||||||
|
|
||||||
#include <Transform.h>
|
#include <Transform.h>
|
||||||
|
|
||||||
#include "TransformTests.h"
|
|
||||||
#include "SharedLogging.h"
|
#include "SharedLogging.h"
|
||||||
|
#include <../QTestExtensions.h>
|
||||||
|
|
||||||
using namespace glm;
|
using namespace glm;
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,6 @@ inline QTextStream& operator<< (QTextStream& stream, const glm::mat4& matrix) {
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <../QTestExtensions.h>
|
|
||||||
|
|
||||||
class TransformTests : public QObject {
|
class TransformTests : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private slots:
|
private slots:
|
||||||
|
|
|
@ -86,7 +86,6 @@ public:
|
||||||
AddressBar,
|
AddressBar,
|
||||||
AlignForearmsWithWrists,
|
AlignForearmsWithWrists,
|
||||||
AlternateIK,
|
AlternateIK,
|
||||||
AmbientOcclusion,
|
|
||||||
Animations,
|
Animations,
|
||||||
Atmosphere,
|
Atmosphere,
|
||||||
Attachments,
|
Attachments,
|
||||||
|
@ -111,6 +110,7 @@ public:
|
||||||
ControlWithSpeech,
|
ControlWithSpeech,
|
||||||
CopyAddress,
|
CopyAddress,
|
||||||
CopyPath,
|
CopyPath,
|
||||||
|
DebugAmbientOcclusion,
|
||||||
DecreaseAvatarSize,
|
DecreaseAvatarSize,
|
||||||
DeleteBookmark,
|
DeleteBookmark,
|
||||||
DisableActivityLogger,
|
DisableActivityLogger,
|
||||||
|
|
Loading…
Reference in a new issue