resolve conflicts on merge with upstream master

This commit is contained in:
Stephen Birarda 2015-08-05 11:16:20 -07:00
commit 052978898e
262 changed files with 12160 additions and 6455 deletions
assignment-client/src
cmake/modules
domain-server/resources
examples
interface

View file

@ -70,7 +70,7 @@ void Agent::handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointe
// pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
int piggyBackedSizeWithHeader = packet->getPayloadSize() - statsMessageLength;
std::unique_ptr<char> buffer = std::unique_ptr<char>(new char[piggyBackedSizeWithHeader]);
auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
memcpy(buffer.get(), packet->getPayload() + statsMessageLength, piggyBackedSizeWithHeader);
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, packet->getSenderSockAddr());
@ -107,6 +107,7 @@ void Agent::handleAudioPacket(QSharedPointer<NLPacket> packet) {
}
const QString AGENT_LOGGING_NAME = "agent";
const int PING_INTERVAL = 1000;
void Agent::run() {
ThreadedAssignment::commonInit(AGENT_LOGGING_NAME, NodeType::Agent);
@ -118,6 +119,10 @@ void Agent::run() {
<< NodeType::EntityServer
);
_pingTimer = new QTimer(this);
connect(_pingTimer, SIGNAL(timeout()), SLOT(sendPingRequests()));
_pingTimer->start(PING_INTERVAL);
// figure out the URL for the script for this agent assignment
QUrl scriptURL;
if (_payload.isEmpty()) {
@ -193,7 +198,27 @@ void Agent::run() {
void Agent::aboutToFinish() {
_scriptEngine.stop();
_pingTimer->stop();
delete _pingTimer;
// our entity tree is going to go away so tell that to the EntityScriptingInterface
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(NULL);
}
void Agent::sendPingRequests() {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->eachMatchingNode([](const SharedNodePointer& node)->bool {
switch (node->getType()) {
case NodeType::AvatarMixer:
case NodeType::AudioMixer:
case NodeType::EntityServer:
return true;
default:
return false;
}
}, [nodeList](const SharedNodePointer& node) {
nodeList->sendPacket(nodeList->constructPingPacket(), *node);
});
}

View file

@ -58,11 +58,13 @@ private slots:
void handleAudioPacket(QSharedPointer<NLPacket> packet);
void handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
void sendPingRequests();
private:
ScriptEngine _scriptEngine;
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;
QTimer* _pingTimer;
MixedAudioStream _receivedAudioStream;
float _lastReceivedAudioLoudness;

View file

@ -924,9 +924,9 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
const QString USE_STDEV_FOR_DESIRED_CALC_JSON_KEY = "use_stdev_for_desired_calc";
_streamSettings._useStDevForJitterCalc = audioBufferGroupObject[USE_STDEV_FOR_DESIRED_CALC_JSON_KEY].toBool();
if (_streamSettings._useStDevForJitterCalc) {
qDebug() << "Using Philip's stdev method for jitter calc if dynamic jitter buffers enabled";
qDebug() << "Using stdev method for jitter calc if dynamic jitter buffers enabled";
} else {
qDebug() << "Using Fred's max-gap method for jitter calc if dynamic jitter buffers enabled";
qDebug() << "Using max-gap method for jitter calc if dynamic jitter buffers enabled";
}
const QString WINDOW_STARVE_THRESHOLD_JSON_KEY = "window_starve_threshold";

View file

@ -38,5 +38,19 @@ if (WIN32)
find_package_handle_standard_args(GLEW DEFAULT_MSG GLEW_INCLUDE_DIRS GLEW_LIBRARIES GLEW_DLL_PATH)
add_paths_to_fixup_libs(${GLEW_DLL_PATH})
elseif (APPLE)
else ()
find_path(GLEW_INCLUDE_DIR GL/glew.h)
find_library(GLEW_LIBRARY NAMES GLEW glew32 glew glew32s PATH_SUFFIXES lib64)
set(GLEW_INCLUDE_DIRS ${GLEW_INCLUDE_DIR})
set(GLEW_LIBRARIES ${GLEW_LIBRARY})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(GLEW
REQUIRED_VARS GLEW_INCLUDE_DIR GLEW_LIBRARY)
mark_as_advanced(GLEW_INCLUDE_DIR GLEW_LIBRARY)
endif ()

View file

@ -0,0 +1,38 @@
#
# FindconnexionClient.cmake
#
# Once done this will define
#
# 3DCONNEXIONCLIENT_INCLUDE_DIRS
#
# Created on 10/06/2015 by Marcel Verhagen
# 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
#
# setup hints for 3DCONNEXIONCLIENT search
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("connexionclient")
if (APPLE)
find_library(3DconnexionClient 3DconnexionClient)
if(EXISTS ${3DconnexionClient})
set(CONNEXIONCLIENT_FOUND true)
set(CONNEXIONCLIENT_INCLUDE_DIR ${3DconnexionClient})
set(CONNEXIONCLIENT_LIBRARY ${3DconnexionClient})
set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "-weak_framework 3DconnexionClient")
message(STATUS "Found 3Dconnexion")
mark_as_advanced(CONNEXIONCLIENT_INCLUDE_DIR CONNEXIONCLIENT_LIBRARY)
endif()
endif()
if (WIN32)
find_path(CONNEXIONCLIENT_INCLUDE_DIRS I3dMouseParams.h PATH_SUFFIXES Inc HINTS ${CONNEXIONCLIENT_SEARCH_DIRS})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(connexionClient DEFAULT_MSG CONNEXIONCLIENT_INCLUDE_DIRS)
mark_as_advanced(CONNEXIONCLIENT_INCLUDE_DIRS CONNEXIONCLIENT_SEARCH_DIRS)
endif()

View file

@ -389,6 +389,10 @@
{
"value": "json",
"label": "Entity server persists data as JSON"
},
{
"value": "json.gz",
"label": "Entity server persists data as gzipped JSON"
}
],
"advanced": true

100
examples/afk.js Normal file
View 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);
});

214
examples/controlPanel.js Normal file
View file

@ -0,0 +1,214 @@
//
// controlPanel.js
// examples
//
// Created by Zander Otavka on 7/15/15.
// Copyright 2015 High Fidelity, Inc.
//
// Shows a few common controls in a FloatingUIPanel on right click.
//
// 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([
"libraries/globals.js",
"libraries/overlayManager.js",
]);
var BG_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/card-bg.svg";
var CLOSE_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/tools/close.svg";
var MIC_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/tools/mic-toggle.svg";
var FACE_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/tools/face-toggle.svg";
var ADDRESS_BAR_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/tools/address-bar-toggle.svg";
var panel = new FloatingUIPanel({
anchorPosition: {
bind: "myAvatar"
},
offsetPosition: { x: 0, y: 0.4, z: 1 }
});
var background = new BillboardOverlay({
url: BG_IMAGE_URL,
dimensions: {
x: 0.5,
y: 0.5,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false
});
panel.addChild(background);
var closeButton = new BillboardOverlay({
url: CLOSE_IMAGE_URL,
dimensions: {
x: 0.15,
y: 0.15,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: -0.1,
y: 0.1,
z: -0.001
}
});
closeButton.onClick = function(event) {
panel.visible = false;
};
panel.addChild(closeButton);
var micMuteButton = new BillboardOverlay({
url: MIC_IMAGE_URL,
subImage: {
x: 0,
y: 0,
width: 45,
height: 45
},
dimensions: {
x: 0.15,
y: 0.15,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0.1,
y: 0.1,
z: -0.001
}
});
micMuteButton.onClick = function(event) {
AudioDevice.toggleMute();
};
panel.addChild(micMuteButton);
var faceMuteButton = new BillboardOverlay({
url: FACE_IMAGE_URL,
subImage: {
x: 0,
y: 0,
width: 45,
height: 45
},
dimensions: {
x: 0.15,
y: 0.15,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0.1,
y: -0.1,
z: -0.001
}
});
faceMuteButton.onClick = function(event) {
FaceTracker.toggleMute();
};
panel.addChild(faceMuteButton);
var addressBarButton = new BillboardOverlay({
url: ADDRESS_BAR_IMAGE_URL,
subImage: {
x: 0,
y: 0,
width: 45,
height: 45
},
dimensions: {
x: 0.15,
y: 0.15,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: -0.1,
y: -0.1,
z: -0.001
}
});
addressBarButton.onClick = function(event) {
DialogsManager.toggleAddressBar();
};
panel.addChild(addressBarButton);
function onMicMuteToggled() {
var offset;
if (AudioDevice.getMuted()) {
offset = 45;
} else {
offset = 0;
}
micMuteButton.subImage = {
x: offset,
y: 0,
width: 45,
height: 45
};
}
onMicMuteToggled();
function onFaceMuteToggled() {
var offset;
if (FaceTracker.getMuted()) {
offset = 45;
} else {
offset = 0;
}
faceMuteButton.subImage = {
x: offset,
y: 0,
width: 45,
height: 45
};
}
onFaceMuteToggled();
var mouseDown = {};
function onMouseDown(event) {
if (event.isLeftButton) {
mouseDown.overlay = OverlayManager.findAtPoint({ x: event.x, y: event.y });
}
if (event.isRightButton) {
mouseDown.pos = { x: event.x, y: event.y };
}
}
function onMouseUp(event) {
if (event.isLeftButton) {
var overlay = OverlayManager.findAtPoint({ x: event.x, y: event.y });
if (overlay && overlay === mouseDown.overlay && overlay.onClick) {
overlay.onClick(event);
}
}
if (event.isRightButton && Vec3.distance(mouseDown.pos, { x: event.x, y: event.y }) < 5) {
panel.setProperties({
visible: !panel.visible,
offsetRotation: {
bind: "quat",
value: Quat.multiply(MyAvatar.orientation, { x: 0, y: 1, z: 0, w: 0 })
}
});
}
mouseDown = {};
}
function onScriptEnd(event) {
panel.destroy();
}
Controller.mousePressEvent.connect(onMouseDown);
Controller.mouseReleaseEvent.connect(onMouseUp);
AudioDevice.muteToggled.connect(onMicMuteToggled);
FaceTracker.muteToggled.connect(onFaceMuteToggled);
Script.scriptEnding.connect(onScriptEnd);

View file

@ -291,22 +291,18 @@ var toolBar = (function () {
var RESIZE_TIMEOUT = 120000; // 2 minutes
var RESIZE_MAX_CHECKS = RESIZE_TIMEOUT / RESIZE_INTERVAL;
function addModel(url) {
var position;
var entityID = createNewEntity({
type: "Model",
dimensions: DEFAULT_DIMENSIONS,
modelURL: url
}, false);
position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
if (position.x > 0 && position.y > 0 && position.z > 0) {
var entityId = Entities.addEntity({
type: "Model",
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
dimensions: DEFAULT_DIMENSIONS,
modelURL: url
});
if (entityID) {
print("Model added: " + url);
var checkCount = 0;
function resize() {
var entityProperties = Entities.getEntityProperties(entityId);
var entityProperties = Entities.getEntityProperties(entityID);
var naturalDimensions = entityProperties.naturalDimensions;
checkCount++;
@ -318,21 +314,41 @@ var toolBar = (function () {
print("Resize failed: timed out waiting for model (" + url + ") to load");
}
} else {
Entities.editEntity(entityId, { dimensions: naturalDimensions });
Entities.editEntity(entityID, { dimensions: naturalDimensions });
// Reset selection so that the selection overlays will be updated
selectionManager.setSelections([entityId]);
selectionManager.setSelections([entityID]);
}
}
selectionManager.setSelections([entityId]);
selectionManager.setSelections([entityID]);
Script.setTimeout(resize, RESIZE_INTERVAL);
} else {
print("Can't add model: Model would be out of bounds.");
}
}
function createNewEntity(properties, dragOnCreate) {
// Default to true if not passed in
dragOnCreate = dragOnCreate == undefined ? true : dragOnCreate;
var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS;
var position = getPositionToCreateEntity();
var entityID = null;
if (position != null) {
position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions),
properties.position = position;
entityID = Entities.addEntity(properties);
if (dragOnCreate) {
placingEntityID = entityID;
}
} else {
Window.alert("Can't create " + properties.type + ": " + properties.type + " would be out of bounds.");
}
return entityID;
}
var newModelButtonDown = false;
var browseMarketplaceButtonDown = false;
that.mousePressEvent = function (event) {
@ -363,127 +379,82 @@ var toolBar = (function () {
}
if (newCubeButton === toolBar.clicked(clickedOverlay)) {
var position = getPositionToCreateEntity();
createNewEntity({
type: "Box",
dimensions: DEFAULT_DIMENSIONS,
color: { red: 255, green: 0, blue: 0 }
});
if (position.x > 0 && position.y > 0 && position.z > 0) {
placingEntityID = Entities.addEntity({
type: "Box",
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
dimensions: DEFAULT_DIMENSIONS,
color: { red: 255, green: 0, blue: 0 }
});
} else {
print("Can't create box: Box would be out of bounds.");
}
return true;
}
if (newSphereButton === toolBar.clicked(clickedOverlay)) {
var position = getPositionToCreateEntity();
createNewEntity({
type: "Sphere",
dimensions: DEFAULT_DIMENSIONS,
color: { red: 255, green: 0, blue: 0 }
});
if (position.x > 0 && position.y > 0 && position.z > 0) {
placingEntityID = Entities.addEntity({
type: "Sphere",
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
dimensions: DEFAULT_DIMENSIONS,
color: { red: 255, green: 0, blue: 0 }
});
} else {
print("Can't create sphere: Sphere would be out of bounds.");
}
return true;
}
if (newLightButton === toolBar.clicked(clickedOverlay)) {
var position = getPositionToCreateEntity();
createNewEntity({
type: "Light",
dimensions: DEFAULT_LIGHT_DIMENSIONS,
isSpotlight: false,
color: { red: 150, green: 150, blue: 150 },
if (position.x > 0 && position.y > 0 && position.z > 0) {
placingEntityID = Entities.addEntity({
type: "Light",
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_LIGHT_DIMENSIONS), DEFAULT_LIGHT_DIMENSIONS),
dimensions: DEFAULT_LIGHT_DIMENSIONS,
isSpotlight: false,
color: { red: 150, green: 150, blue: 150 },
constantAttenuation: 1,
linearAttenuation: 0,
quadraticAttenuation: 0,
exponent: 0,
cutoff: 180, // in degrees
});
constantAttenuation: 1,
linearAttenuation: 0,
quadraticAttenuation: 0,
exponent: 0,
cutoff: 180, // in degrees
});
} else {
print("Can't create Light: Light would be out of bounds.");
}
return true;
}
if (newTextButton === toolBar.clicked(clickedOverlay)) {
var position = getPositionToCreateEntity();
createNewEntity({
type: "Text",
dimensions: { x: 0.65, y: 0.3, z: 0.01 },
backgroundColor: { red: 64, green: 64, blue: 64 },
textColor: { red: 255, green: 255, blue: 255 },
text: "some text",
lineHeight: 0.06
});
if (position.x > 0 && position.y > 0 && position.z > 0) {
placingEntityID = Entities.addEntity({
type: "Text",
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
dimensions: { x: 0.65, y: 0.3, z: 0.01 },
backgroundColor: { red: 64, green: 64, blue: 64 },
textColor: { red: 255, green: 255, blue: 255 },
text: "some text",
lineHeight: 0.06
});
} else {
print("Can't create box: Text would be out of bounds.");
}
return true;
}
if (newWebButton === toolBar.clicked(clickedOverlay)) {
var position = getPositionToCreateEntity();
createNewEntity({
type: "Web",
dimensions: { x: 1.6, y: 0.9, z: 0.01 },
sourceUrl: "https://highfidelity.com/",
});
if (position.x > 0 && position.y > 0 && position.z > 0) {
placingEntityID = Entities.addEntity({
type: "Web",
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
dimensions: { x: 1.6, y: 0.9, z: 0.01 },
sourceUrl: "https://highfidelity.com/",
});
} else {
print("Can't create Web Entity: would be out of bounds.");
}
return true;
}
if (newZoneButton === toolBar.clicked(clickedOverlay)) {
var position = getPositionToCreateEntity();
createNewEntity({
type: "Zone",
dimensions: { x: 10, y: 10, z: 10 },
});
if (position.x > 0 && position.y > 0 && position.z > 0) {
placingEntityID = Entities.addEntity({
type: "Zone",
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
dimensions: { x: 10, y: 10, z: 10 },
});
} else {
print("Can't create box: Text would be out of bounds.");
}
return true;
}
if (newPolyVoxButton === toolBar.clicked(clickedOverlay)) {
var position = getPositionToCreateEntity();
createNewEntity({
type: "PolyVox",
dimensions: { x: 10, y: 10, z: 10 },
voxelVolumeSize: {x:16, y:16, z:16},
voxelSurfaceStyle: 1
});
if (position.x > 0 && position.y > 0 && position.z > 0) {
placingEntityID = Entities.addEntity({
type: "PolyVox",
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS),
DEFAULT_DIMENSIONS),
dimensions: { x: 10, y: 10, z: 10 },
voxelVolumeSize: {x:16, y:16, z:16},
voxelSurfaceStyle: 1
});
} else {
print("Can't create PolyVox: would be out of bounds.");
}
return true;
}
@ -666,7 +637,7 @@ function handleIdleMouse() {
idleMouseTimerId = null;
if (isActive) {
highlightEntityUnderCursor(lastMousePosition, true);
}
}
}
function highlightEntityUnderCursor(position, accurateRay) {
@ -837,15 +808,15 @@ function setupModelMenus() {
}
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Entity List...", shortcutKey: "CTRL+META+L", afterItem: "Models" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L",
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Entity List...", isCheckable: true, isChecked: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S",
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S",
afterItem: "Allow Selecting of Large Models", isCheckable: true, isChecked: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L",
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L",
afterItem: "Allow Selecting of Small Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities In Box", shortcutKey: "CTRL+SHIFT+META+A",
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities In Box", shortcutKey: "CTRL+SHIFT+META+A",
afterItem: "Allow Selecting of Lights" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities Touching Box", shortcutKey: "CTRL+SHIFT+META+T",
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities Touching Box", shortcutKey: "CTRL+SHIFT+META+T",
afterItem: "Select All Entities In Box" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" });
@ -962,7 +933,7 @@ function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) {
entities.splice(i, 1);
--i;
}
}
}
}
selectionManager.setSelections(entities);
}
@ -1038,17 +1009,29 @@ function handeMenuEvent(menuItem) {
tooltip.show(false);
}
// This function tries to find a reasonable position to place a new entity based on the camera
// position. If a reasonable position within the world bounds can't be found, `null` will
// be returned. The returned position will also take into account grid snapping settings.
function getPositionToCreateEntity() {
var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE;
var direction = Quat.getFront(Camera.orientation);
var offset = Vec3.multiply(distance, direction);
var position = Vec3.sum(Camera.position, offset);
var placementPosition = Vec3.sum(Camera.position, offset);
position.x = Math.max(0, position.x);
position.y = Math.max(0, position.y);
position.z = Math.max(0, position.z);
var cameraPosition = Camera.position;
return position;
var cameraOutOfBounds = cameraPosition.x < 0 || cameraPosition.y < 0 || cameraPosition.z < 0;
var placementOutOfBounds = placementPosition.x < 0 || placementPosition.y < 0 || placementPosition.z < 0;
if (cameraOutOfBounds && placementOutOfBounds) {
return null;
}
placementPosition.x = Math.max(0, placementPosition.x);
placementPosition.y = Math.max(0, placementPosition.y);
placementPosition.z = Math.max(0, placementPosition.z);
return placementPosition;
}
function importSVO(importURL) {
@ -1064,17 +1047,21 @@ function importSVO(importURL) {
if (success) {
var VERY_LARGE = 10000;
var position = { x: 0, y: 0, z: 0};
var position = { x: 0, y: 0, z: 0 };
if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) {
position = getPositionToCreateEntity();
}
var pastedEntityIDs = Clipboard.pasteEntities(position);
if (position != null) {
var pastedEntityIDs = Clipboard.pasteEntities(position);
if (isActive) {
selectionManager.setSelections(pastedEntityIDs);
if (isActive) {
selectionManager.setSelections(pastedEntityIDs);
}
Window.raiseMainWindow();
} else {
Window.alert("Can't import objects: objects would be out of bounds.");
}
Window.raiseMainWindow();
} else {
Window.alert("There was an error importing the entity file.");
}
@ -1261,7 +1248,7 @@ PropertiesTool = function(opts) {
if (data.properties.keyLightDirection !== undefined) {
data.properties.keyLightDirection = Vec3.fromPolar(
data.properties.keyLightDirection.x * DEGREES_TO_RADIANS, data.properties.keyLightDirection.y * DEGREES_TO_RADIANS);
}
}
Entities.editEntity(selectionManager.selections[0], data.properties);
if (data.properties.name != undefined) {
entityListTool.sendUpdate();
@ -1357,8 +1344,8 @@ PropertiesTool = function(opts) {
var properties = selectionManager.savedProperties[selectionManager.selections[i]];
if (properties.type == "Zone") {
var centerOfZone = properties.boundingBox.center;
var atmosphereCenter = { x: centerOfZone.x,
y: centerOfZone.y - properties.atmosphere.innerRadius,
var atmosphereCenter = { x: centerOfZone.x,
y: centerOfZone.y - properties.atmosphere.innerRadius,
z: centerOfZone.z };
Entities.editEntity(selectionManager.selections[i], {

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

View 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);
}
})

View file

@ -0,0 +1,28 @@
//
// hitEffect.js
// examples
//
// Created by Eric Levin on July 20, 2015
// Copyright 2015 High Fidelity, Inc.
//
// An example of how to toggle a screen-space hit effect using the Scene global object.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var hitEffectEnabled = false;
toggleHitEffect();
function toggleHitEffect() {
Script.setTimeout(function() {
hitEffectEnabled = !hitEffectEnabled;
Scene.setEngineDisplayHitEffect(hitEffectEnabled);
toggleHitEffect();
}, 1000);
}

View file

@ -12,29 +12,297 @@
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
const NUM_LAYERS = 16;
const BASE_DIMENSION = { x: 7, y: 2, z: 7 };
const BLOCKS_PER_LAYER = 3;
const BLOCK_SIZE = {x: 0.2, y: 0.1, z: 0.8};
const BLOCK_SPACING = BLOCK_SIZE.x / 3;
Script.include("../../libraries/toolBars.js");
const DEFAULT_NUM_LAYERS = 16;
const DEFAULT_BASE_DIMENSION = { x: 7, y: 2, z: 7 };
const DEFAULT_BLOCKS_PER_LAYER = 3;
const DEFAULT_BLOCK_SIZE = {x: 0.2, y: 0.1, z: 0.8};
const DEFAULT_BLOCK_SPACING = DEFAULT_BLOCK_SIZE.x / DEFAULT_BLOCKS_PER_LAYER;
// BLOCK_HEIGHT_VARIATION removes a random percentages of the default block height per block. (for example 0.001 %)
const BLOCK_HEIGHT_VARIATION = 0.001;
const GRAVITY = {x: 0, y: -2.8, z: 0};
const DENSITY = 2000;
const DAMPING_FACTOR = 0.98;
const ANGULAR_DAMPING_FACTOR = 0.8;
const FRICTION = 0.99;
const RESTITUTION = 0.0;
const SPAWN_DISTANCE = 3;
const BLOCK_YAW_OFFSET = 45;
const DEFAULT_BLOCK_HEIGHT_VARIATION = 0.001;
const DEFAULT_GRAVITY = {x: 0, y: -2.8, z: 0};
const DEFAULT_DENSITY = 2000;
const DEFAULT_DAMPING_FACTOR = 0.98;
const DEFAULT_ANGULAR_DAMPING_FACTOR = 0.8;
const DEFAULT_FRICTION = 0.99;
const DEFAULT_RESTITUTION = 0.0;
const DEFAULT_SPAWN_DISTANCE = 3;
const DEFAULT_BLOCK_YAW_OFFSET = 45;
var editMode = false;
const BUTTON_DIMENSIONS = {width: 49, height: 49};
const MAXIMUM_PERCENTAGE = 100.0;
const NO_ANGLE = 0;
const RIGHT_ANGLE = 90;
var windowWidth = Window.innerWidth;
var size;
var pieces = [];
var ground = false;
var layerRotated = false;
var button;
var cogButton;
var toolBar;
SettingsWindow = function() {
var _this = this;
this.plankyStack = null;
this.webWindow = null;
this.init = function(plankyStack) {
_this.webWindow = new WebWindow('Planky', Script.resolvePath('../../html/plankySettings.html'), 255, 500, true);
_this.webWindow.setVisible(false);
_this.webWindow.eventBridge.webEventReceived.connect(_this.onWebEventReceived);
_this.plankyStack = plankyStack;
};
this.sendData = function(data) {
_this.webWindow.eventBridge.emitScriptEvent(JSON.stringify(data));
};
this.onWebEventReceived = function(data) {
data = JSON.parse(data);
switch (data.action) {
case 'loaded':
_this.sendData({action: 'load', options: _this.plankyStack.options.getJSON()})
break;
case 'value-change':
_this.plankyStack.onValueChanged(data.option, data.value);
break;
case 'factory-reset':
_this.plankyStack.options.factoryReset();
_this.sendData({action: 'load', options: _this.plankyStack.options.getJSON()})
break;
case 'save-default':
_this.plankyStack.options.save();
break;
case 'cleanup':
_this.plankyStack.deRez();
break;
default:
Window.alert('[planky] unknown action ' + data.action);
}
};
};
PlankyOptions = function() {
var _this = this;
this.factoryReset = function() {
_this.setDefaults();
Settings.setValue('plankyOptions', '');
};
this.save = function() {
Settings.setValue('plankyOptions', JSON.stringify(_this.getJSON()));
};
this.load = function() {
_this.setDefaults();
var plankyOptions = Settings.getValue('plankyOptions')
if (plankyOptions === null || plankyOptions === '') {
return;
}
var options = JSON.parse(plankyOptions);
for (option in options) {
_this[option] = options[option];
}
};
this.getJSON = function() {
return {
numLayers: _this.numLayers,
baseDimension: _this.baseDimension,
blocksPerLayer: _this.blocksPerLayer,
blockSize: _this.blockSize,
blockSpacing: _this.blockSpacing,
blockHeightVariation: _this.blockHeightVariation,
gravity: _this.gravity,
density: _this.density,
dampingFactor: _this.dampingFactor,
angularDampingFactor: _this.angularDampingFactor,
friction: _this.friction,
restitution: _this.restitution,
spawnDistance: _this.spawnDistance,
blockYawOffset: _this.blockYawOffset,
};
}
this.setDefaults = function() {
_this.numLayers = DEFAULT_NUM_LAYERS;
_this.baseDimension = DEFAULT_BASE_DIMENSION;
_this.blocksPerLayer = DEFAULT_BLOCKS_PER_LAYER;
_this.blockSize = DEFAULT_BLOCK_SIZE;
_this.blockSpacing = DEFAULT_BLOCK_SPACING;
_this.blockHeightVariation = DEFAULT_BLOCK_HEIGHT_VARIATION;
_this.gravity = DEFAULT_GRAVITY;
_this.density = DEFAULT_DENSITY;
_this.dampingFactor = DEFAULT_DAMPING_FACTOR;
_this.angularDampingFactor = DEFAULT_ANGULAR_DAMPING_FACTOR;
_this.friction = DEFAULT_FRICTION;
_this.restitution = DEFAULT_RESTITUTION;
_this.spawnDistance = DEFAULT_SPAWN_DISTANCE;
_this.blockYawOffset = DEFAULT_BLOCK_YAW_OFFSET;
};
this.load();
};
// The PlankyStack exists out of rows and layers
PlankyStack = function() {
var _this = this;
this.planks = [];
this.ground = false;
this.editLines = [];
this.options = new PlankyOptions();
this.deRez = function() {
_this.planks.forEach(function(plank) {
Entities.deleteEntity(plank.entity);
});
_this.planks = [];
if (_this.ground) {
Entities.deleteEntity(_this.ground);
}
_this.editLines.forEach(function(line) {
Entities.deleteEntity(line);
})
_this.editLines = [];
if (_this.centerLine) {
Entities.deleteEntity(_this.centerLine);
}
_this.ground = false;
_this.centerLine = false;
};
this.rez = function() {
if (_this.planks.length > 0) {
_this.deRez();
}
_this.baseRotation = Quat.fromPitchYawRollDegrees(0.0, MyAvatar.bodyYaw, 0.0);
var basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(_this.options.spawnDistance, Quat.getFront(_this.baseRotation)));
basePosition.y = grabLowestJointY();
_this.basePosition = basePosition;
_this.refresh();
};
//private function
var refreshGround = function() {
if (!_this.ground) {
_this.ground = Entities.addEntity({
type: 'Model',
modelURL: HIFI_PUBLIC_BUCKET + 'eric/models/woodFloor.fbx',
dimensions: _this.options.baseDimension,
position: Vec3.sum(_this.basePosition, {y: -(_this.options.baseDimension.y / 2)}),
rotation: _this.baseRotation,
shapeType: 'box'
});
return;
}
// move ground to rez position/rotation
Entities.editEntity(_this.ground, {dimensions: _this.options.baseDimension, position: Vec3.sum(_this.basePosition, {y: -(_this.options.baseDimension.y / 2)}), rotation: _this.baseRotation});
};
var refreshLines = function() {
if (_this.editLines.length === 0) {
_this.editLines.push(Entities.addEntity({
type: 'Line',
dimensions: {x: 5, y: 21, z: 5},
position: Vec3.sum(_this.basePosition, {y: -(_this.options.baseDimension.y / 2)}),
lineWidth: 7,
color: {red: 20, green: 20, blue: 20},
linePoints: [{x: 0, y: 0, z: 0}, {x: 0, y: 10, z: 0}],
visible: editMode
}));
return;
}
_this.editLines.forEach(function(line) {
Entities.editEntity(line, {visible: editMode});
})
};
var trimDimension = function(dimension, maxIndex) {
var removingPlanks = [];
_this.planks.forEach(function(plank, index, object) {
if (plank[dimension] > maxIndex) {
removingPlanks.push(index);
}
});
removingPlanks.reverse();
for (var i = 0; i < removingPlanks.length; i++) {
Entities.deleteEntity(_this.planks[removingPlanks[i]].entity);
_this.planks.splice(removingPlanks[i], 1);
}
};
var createOrUpdate = function(layer, row) {
var found = false;
var layerRotated = layer % 2 === 0;
var layerRotation = Quat.fromPitchYawRollDegrees(0, layerRotated ? NO_ANGLE : RIGHT_ANGLE, 0.0);
var blockPositionXZ = (row - (_this.options.blocksPerLayer / 2) + 0.5) * (_this.options.blockSpacing + _this.options.blockSize.x);
var localTransform = Vec3.multiplyQbyV(_this.offsetRot, {
x: (layerRotated ? blockPositionXZ : 0),
y: (_this.options.blockSize.y / 2) + (_this.options.blockSize.y * layer),
z: (layerRotated ? 0 : blockPositionXZ)
});
var newProperties = {
type: 'Model',
modelURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/block.fbx',
shapeType: 'box',
name: 'PlankyBlock' + layer + '-' + row,
dimensions: Vec3.sum(_this.options.blockSize, {x: 0, y: -((_this.options.blockSize.y * (_this.options.blockHeightVariation / MAXIMUM_PERCENTAGE)) * Math.random()), z: 0}),
position: Vec3.sum(_this.basePosition, localTransform),
rotation: Quat.multiply(layerRotation, _this.offsetRot),
damping: _this.options.dampingFactor,
restitution: _this.options.restitution,
friction: _this.options.friction,
angularDamping: _this.options.angularDampingFactor,
gravity: _this.options.gravity,
density: _this.options.density,
velocity: {x: 0, y: 0, z: 0},
angularVelocity: Quat.fromPitchYawRollDegrees(0, 0, 0),
ignoreForCollisions: true
};
_this.planks.forEach(function(plank, index, object) {
if (plank.layer === layer && plank.row === row) {
Entities.editEntity(plank.entity, newProperties);
found = true;
// break loop:
return false;
}
});
if (!found) {
_this.planks.push({layer: layer, row: row, entity: Entities.addEntity(newProperties)})
}
};
this.onValueChanged = function(option, value) {
_this.options[option] = value;
if (['numLayers', 'blocksPerLayer', 'blockSize', 'blockSpacing', 'blockHeightVariation'].indexOf(option) !== -1) {
_this.refresh();
}
};
this.refresh = function() {
refreshGround();
refreshLines();
trimDimension('layer', _this.options.numLayers - 1);
trimDimension('row', _this.options.blocksPerLayer - 1);
_this.offsetRot = Quat.multiply(_this.baseRotation, Quat.fromPitchYawRollDegrees(0.0, _this.options.blockYawOffset, 0.0));
for (var layer = 0; layer < _this.options.numLayers; layer++) {
for (var row = 0; row < _this.options.blocksPerLayer; row++) {
createOrUpdate(layer, row);
}
}
if (!editMode) {
_this.planks.forEach(function(plank, index, object) {
Entities.editEntity(plank.entity, {ignoreForCollisions: false, collisionsWillMove: true});
});
}
};
this.isFound = function() {
//TODO: identify entities here until one is found
return _this.planks.length > 0;
};
};
var settingsWindow = new SettingsWindow();
var plankyStack = new PlankyStack();
settingsWindow.init(plankyStack);
function grabLowestJointY() {
var jointNames = MyAvatar.getJointNames();
@ -47,108 +315,60 @@ function grabLowestJointY() {
return floorY;
}
function getButtonPosX() {
return windowWidth - ((BUTTON_DIMENSIONS.width / 2) + BUTTON_DIMENSIONS.width);
}
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.games.planky", function (windowDimensions, toolbar) {
return {
x: windowDimensions.x - (toolbar.width * 1.1),
y: toolbar.height / 2
};
});
var button = Overlays.addOverlay('image', {
x: getButtonPosX(),
y: 10,
button = toolBar.addTool({
width: BUTTON_DIMENSIONS.width,
height: BUTTON_DIMENSIONS.height,
imageURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/planky_button.svg',
alpha: 0.8
alpha: 0.8,
visible: true
});
cogButton = toolBar.addTool({
width: BUTTON_DIMENSIONS.width,
height: BUTTON_DIMENSIONS.height,
imageURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/cog.svg',
subImage: { x: 0, y: BUTTON_DIMENSIONS.height, width: BUTTON_DIMENSIONS.width, height: BUTTON_DIMENSIONS.height },
alpha: 0.8,
visible: true
}, true, false);
function resetBlocks() {
pieces.forEach(function(piece) {
Entities.deleteEntity(piece);
});
pieces = [];
var avatarRot = Quat.fromPitchYawRollDegrees(0.0, MyAvatar.bodyYaw, 0.0);
basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(SPAWN_DISTANCE, Quat.getFront(avatarRot)));
basePosition.y = grabLowestJointY() - (BASE_DIMENSION.y / 2);
if (!ground) {
ground = Entities.addEntity({
type: 'Model',
modelURL: HIFI_PUBLIC_BUCKET + 'eric/models/woodFloor.fbx',
dimensions: BASE_DIMENSION,
position: basePosition,
rotation: avatarRot,
shapeType: 'box'
});
} else {
Entities.editEntity(ground, {position: basePosition, rotation: avatarRot});
}
var offsetRot = Quat.multiply(avatarRot, Quat.fromPitchYawRollDegrees(0.0, BLOCK_YAW_OFFSET, 0.0));
basePosition.y += (BASE_DIMENSION.y / 2);
for (var layerIndex = 0; layerIndex < NUM_LAYERS; layerIndex++) {
var layerRotated = layerIndex % 2 === 0;
var offset = -(BLOCK_SPACING);
var layerRotation = Quat.fromPitchYawRollDegrees(0, layerRotated ? 0 : 90, 0.0);
for (var blockIndex = 0; blockIndex < BLOCKS_PER_LAYER; blockIndex++) {
var blockPositionXZ = BLOCK_SIZE.x * blockIndex - (BLOCK_SIZE.x * 3 / 2 - BLOCK_SIZE.x / 2);
var localTransform = Vec3.multiplyQbyV(offsetRot, {
x: (layerRotated ? blockPositionXZ + offset: 0),
y: (BLOCK_SIZE.y / 2) + (BLOCK_SIZE.y * layerIndex),
z: (layerRotated ? 0 : blockPositionXZ + offset)
});
pieces.push(Entities.addEntity({
type: 'Model',
modelURL: HIFI_PUBLIC_BUCKET + 'marketplace/hificontent/Games/blocks/block.fbx',
shapeType: 'box',
name: 'PlankyBlock' + ((layerIndex * BLOCKS_PER_LAYER) + blockIndex),
dimensions: {
x: BLOCK_SIZE.x,
y: BLOCK_SIZE.y - ((BLOCK_SIZE.y * (BLOCK_HEIGHT_VARIATION / MAXIMUM_PERCENTAGE)) * Math.random()),
z: BLOCK_SIZE.z
},
position: {
x: basePosition.x + localTransform.x,
y: basePosition.y + localTransform.y,
z: basePosition.z + localTransform.z
},
rotation: Quat.multiply(layerRotation, offsetRot),
collisionsWillMove: true,
damping: DAMPING_FACTOR,
restitution: RESTITUTION,
friction: FRICTION,
angularDamping: ANGULAR_DAMPING_FACTOR,
gravity: GRAVITY,
density: DENSITY
}));
offset += BLOCK_SPACING;
Controller.mousePressEvent.connect(function(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (toolBar.clicked(clickedOverlay) === button) {
if (!plankyStack.isFound()) {
plankyStack.rez();
return;
}
plankyStack.refresh();
} else if (toolBar.clicked(clickedOverlay) === cogButton) {
editMode = !editMode;
toolBar.selectTool(cogButton, editMode);
settingsWindow.webWindow.setVisible(editMode);
if(plankyStack.planks.length) {
plankyStack.refresh();
}
}
}
});
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay === button) {
resetBlocks();
Script.update.connect(function() {
if (windowWidth !== Window.innerWidth) {
windowWidth = Window.innerWidth;
Overlays.editOverlay(button, {x: getButtonPosX()});
Overlays.editOverlay(cogButton, {x: getCogButtonPosX()});
}
}
})
Controller.mousePressEvent.connect(mousePressEvent);
function cleanup() {
Overlays.deleteOverlay(button);
Script.scriptEnding.connect(function() {
toolBar.cleanup();
if (ground) {
Entities.deleteEntity(ground);
}
pieces.forEach(function(piece) {
Entities.deleteEntity(piece);
});
pieces = [];
}
function onUpdate() {
if (windowWidth != Window.innerWidth) {
windowWidth = Window.innerWidth;
Overlays.editOverlay(button, {x: getButtonPosX()});
}
}
Script.update.connect(onUpdate)
Script.scriptEnding.connect(cleanup);
plankyStack.deRez();
});

View 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);
}

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

View file

@ -0,0 +1,169 @@
//
// floatingUI.js
// examples/example/ui
//
// Created by Alexander Otavka
// 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
//
Script.include([
"../../libraries/globals.js",
"../../libraries/overlayManager.js",
]);
var BG_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/card-bg.svg";
var RED_DOT_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/red-dot.svg";
var BLUE_SQUARE_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/blue-square.svg";
var mainPanel = new FloatingUIPanel({
offsetRotation: {
bind: "quat",
value: { w: 1, x: 0, y: 0, z: 0 }
},
offsetPosition: { x: 0, y: 0.4, z: 1 }
});
var bluePanel = mainPanel.addChild(new FloatingUIPanel ({
offsetPosition: { x: 0.1, y: 0.1, z: -0.2 }
}));
var mainPanelBackground = new BillboardOverlay({
url: BG_IMAGE_URL,
dimensions: {
x: 0.5,
y: 0.5,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0,
y: 0,
z: 0.001
}
});
var bluePanelBackground = mainPanelBackground.clone();
bluePanelBackground.dimensions = {
x: 0.3,
y: 0.3
};
mainPanel.addChild(mainPanelBackground);
bluePanel.addChild(bluePanelBackground);
var redDot = mainPanel.addChild(new BillboardOverlay({
url: RED_DOT_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: -0.15,
y: -0.15,
z: 0
}
}));
var redDot2 = mainPanel.addChild(new BillboardOverlay({
url: RED_DOT_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: -0.155,
y: 0.005,
z: 0
}
}));
var blueSquare = bluePanel.addChild(new BillboardOverlay({
url: BLUE_SQUARE_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0.055,
y: -0.055,
z: 0
}
}));
var blueSquare2 = bluePanel.addChild(new BillboardOverlay({
url: BLUE_SQUARE_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0.055,
y: 0.055,
z: 0
}
}));
var blueSquare3 = blueSquare2.clone();
blueSquare3.offsetPosition = {
x: -0.055,
y: 0.055,
z: 0
};
var mouseDown = {};
function onMouseDown(event) {
if (event.isLeftButton) {
mouseDown.overlay = OverlayManager.findAtPoint({ x: event.x, y: event.y });
}
if (event.isRightButton) {
mouseDown.pos = { x: event.x, y: event.y };
}
}
function onMouseUp(event) {
if (event.isLeftButton) {
var overlay = OverlayManager.findAtPoint({ x: event.x, y: event.y });
if (overlay === mouseDown.overlay) {
if (overlay.attachedPanel === bluePanel) {
overlay.destroy();
} else if (overlay) {
var oldPos = overlay.offsetPosition;
var newPos = {
x: Number(oldPos.x),
y: Number(oldPos.y),
z: Number(oldPos.z) + 0.1
};
overlay.offsetPosition = newPos;
}
}
}
if (event.isRightButton && Vec3.distance(mouseDown.pos, { x: event.x, y: event.y }) < 5) {
mainPanel.visible = !mainPanel.visible;
}
}
function onScriptEnd() {
mainPanel.destroy();
}
Controller.mousePressEvent.connect(onMouseDown);
Controller.mouseReleaseEvent.connect(onMouseUp);
Script.scriptEnding.connect(onScriptEnd);

78
examples/gridTest.js Normal file
View file

@ -0,0 +1,78 @@
//
// Created by Philip Rosedale on July 28, 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
//
// Creates a rectangular grid of objects, starting at the origin and proceeding along the X/Z plane.
// Useful for testing the rendering, LOD, and octree storage aspects of the system.
//
// Note that when creating things quickly, the entity server will ignore data if we send updates too quickly.
// like Internet MTU, these rates are set by th domain operator, so in this script there is a RATE_PER_SECOND
// variable letting you set this speed. If entities are missing from the grid after a relog, this number
// being too high may be the reason.
var SIZE = 10.0;
var SEPARATION = 20.0;
var ROWS_X = 30;
var ROWS_Z = 30;
var TYPE = "Sphere"; // Right now this can be "Box" or "Model" or "Sphere"
var MODEL_URL = "https://hifi-public.s3.amazonaws.com/models/props/LowPolyIsland/CypressTreeGroup.fbx";
var MODEL_DIMENSION = { x: 33, y: 16, z: 49 };
var RATE_PER_SECOND = 1000; // The entity server will drop data if we create things too fast.
var SCRIPT_INTERVAL = 100;
var LIFETIME = 600; // By default, these entities will live in the server for 10 minutes
var addRandom = false;
var x = 0;
var z = 0;
var totalCreated = 0;
Script.setInterval(function () {
if (!Entities.serversExist() || !Entities.canRez()) {
return;
}
var numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0);
for (var i = 0; i < numToCreate; i++) {
var position = { x: SIZE + (x * SEPARATION), y: SIZE, z: SIZE + (z * SEPARATION) };
if (TYPE == "Model") {
Entities.addEntity({
type: TYPE,
name: "gridTest",
modelURL: MODEL_URL,
position: position,
dimensions: MODEL_DIMENSION,
ignoreCollisions: true,
collisionsWillMove: false,
lifetime: LIFETIME
});
} else {
Entities.addEntity({
type: TYPE,
name: "gridTest",
position: position,
dimensions: { x: SIZE, y: SIZE, z: SIZE },
color: { red: x / ROWS_X * 255, green: 50, blue: z / ROWS_Z * 255 },
ignoreCollisions: true,
collisionsWillMove: false,
lifetime: LIFETIME
});
}
totalCreated++;
x++;
if (x == ROWS_X) {
x = 0;
z++;
print("Created: " + totalCreated);
}
if (z == ROWS_Z) {
Script.stop();
}
}
}, SCRIPT_INTERVAL);

View file

@ -9,7 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var MOVE_DISTANCE = 10.0;
var MOVE_DISTANCE = 2.0;
var PITCH_INCREMENT = 0.5; // degrees
var pitchChange = 0; // degrees
var YAW_INCREMENT = 0.5; // degrees

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

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

View file

@ -23,7 +23,7 @@ var RAD_TO_DEG = 180.0 / PI;
var AZIMUTH_RATE = 90.0;
var ALTITUDE_RATE = 200.0;
var RADIUS_RATE = 1.0 / 100.0;
var PAN_RATE = 50.0;
var PAN_RATE = 250.0;
var Y_AXIS = {
x: 0,
@ -139,7 +139,7 @@ function handlePanMode(dx, dy) {
var right = Quat.getRight(Camera.getOrientation());
var distance = Vec3.length(vector);
var dv = Vec3.sum(Vec3.multiply(up, -distance * dy / PAN_RATE), Vec3.multiply(right, distance * dx / PAN_RATE));
var dv = Vec3.sum(Vec3.multiply(up, distance * dy / PAN_RATE), Vec3.multiply(right, -distance * dx / PAN_RATE));
center = Vec3.sum(center, dv);
position = Vec3.sum(position, dv);

331
examples/leaves.js Executable file
View 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

View file

@ -141,7 +141,7 @@ CameraManager = function() {
// Pick a point INITIAL_ZOOM_DISTANCE in front of the camera to use as a focal point
that.zoomDistance = INITIAL_ZOOM_DISTANCE;
that.targetZoomDistance = that.zoomDistance;
that.targetZoomDistance = that.zoomDistance + 3.0;
var focalPoint = Vec3.sum(Camera.getPosition(),
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);
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.pitch = that.targetPitch;
that.yaw = that.targetYaw;

View file

@ -0,0 +1,455 @@
//
// overlayManager.js
// examples/libraries
//
// Created by Zander Otavka on 7/24/15
// Copyright 2015 High Fidelity, Inc.
//
// Manage overlays with object oriented goodness, instead of ugly `Overlays.h` methods.
// Instead of:
//
// var billboard = Overlays.addOverlay("billboard", { visible: false });
// ...
// Overlays.editOverlay(billboard, { visible: true });
// ...
// Overlays.deleteOverlay(billboard);
//
// You can now do:
//
// var billboard = new BillboardOverlay({ visible: false });
// ...
// billboard.visible = true;
// ...
// billboard.destroy();
//
// See more on usage below.
//
// Note that including this file will delete Overlays from the global scope. All the
// functionality of Overlays is represented here, just better. If you try to use Overlays in
// tandem, there may be performance problems or nasty surprises.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
// Delete `Overlays` from the global scope.
var Overlays = this.Overlays;
delete this.Overlays;
var overlays = {};
var panels = {};
var overlayTypes;
var Overlay, Overlay2D, Base3DOverlay, Planar3DOverlay, Volume3DOverlay;
//
// Create a new JavaScript object for an overlay of given ID.
//
function makeOverlayFromId(id) {
var type = Overlays.getOverlayType(id);
if (!type) {
return null;
}
var overlay = new overlayTypes[type]();
overlay._id = id;
var panelID = Overlays.getAttachedPanel(id)
if (panelID && panelID in panels) {
panels[panelID].addChild(overlay);
}
overlays[id] = overlay;
return overlay;
}
//
// Get or create an overlay object from the id.
//
// @param knownOverlaysOnly (Optional: Boolean)
// If true, a new object will not be created.
// @param searchList (Optional: Object)
// Map of overlay id's and overlay objects. Can be generated with
// `OverlayManager.makeSearchList`.
//
function findOverlay(id, knownOverlaysOnly, searchList) {
if (id > 0) {
knownOverlaysOnly = Boolean(knownOverlaysOnly) || Boolean(searchList);
searchList = searchList || overlays;
var foundOverlay = searchList[id];
if (foundOverlay) {
return foundOverlay;
}
if (!knownOverlaysOnly) {
return makeOverlayFromId(id);
}
}
return null;
}
//
// Perform global scoped operations on overlays, such as finding by ray intersection.
//
OverlayManager = {
findOnRay: function(pickRay, knownOverlaysOnly, searchList) {
var rayPickResult = Overlays.findRayIntersection(pickRay);
print("raypick " + rayPickResult.overlayID);
if (rayPickResult.intersects) {
return findOverlay(rayPickResult.overlayID, knownOverlaysOnly, searchList);
}
return null;
},
findAtPoint: function(point, knownOverlaysOnly, searchList) {
var foundID = Overlays.getOverlayAtPoint(point);
print("at point " + foundID);
if (foundID) {
return findOverlay(foundID, knownOverlaysOnly, searchList);
} else {
var pickRay = Camera.computePickRay(point.x, point.y);
return OverlayManager.findOnRay(pickRay, knownOverlaysOnly, searchList);
}
},
makeSearchList: function(overlayArray) {
var searchList = {};
overlayArray.forEach(function(overlay){
searchList[overlay._id] = overlay;
});
return searchList;
}
};
//
// Object oriented abstraction layer for overlays.
//
// Usage:
// // Create an overlay
// var billboard = new BillboardOverlay({
// visible: true,
// isFacingAvatar: true,
// ignoreRayIntersections: false
// });
//
// // Get a property
// var isVisible = billboard.visible;
//
// // Set a single property
// billboard.position = { x: 1, y: 3, z: 2 };
//
// // Set multiple properties at the same time
// billboard.setProperties({
// url: "http://images.com/overlayImage.jpg",
// dimensions: { x: 2, y: 2 }
// });
//
// // Clone an overlay
// var clonedBillboard = billboard.clone();
//
// // Remove an overlay from the world
// billboard.destroy();
//
// // Remember, there is a poor orphaned JavaScript object left behind. You should
// // remove any references to it so you don't accidentally try to modify an overlay that
// // isn't there.
// billboard = undefined;
//
(function() {
var ABSTRACT = null;
overlayTypes = {};
function generateOverlayClass(superclass, type, properties) {
var that;
if (type == ABSTRACT) {
that = function(type, params) {
superclass.call(this, type, params);
};
} else {
that = function(params) {
superclass.call(this, type, params);
};
overlayTypes[type] = that;
}
that.prototype = new superclass();
that.prototype.constructor = that;
properties.forEach(function(prop) {
Object.defineProperty(that.prototype, prop, {
get: function() {
return Overlays.getProperty(this._id, prop);
},
set: function(newValue) {
var keyValuePair = {};
keyValuePair[prop] = newValue;
this.setProperties(keyValuePair);
},
configurable: false
});
});
return that;
}
// Supports multiple inheritance of properties. Just `concat` them onto the end of the
// properties list.
var PANEL_ATTACHABLE_FIELDS = ["offsetPosition", "facingRotation"];
Overlay = (function() {
var that = function(type, params) {
if (type && params) {
this._id = Overlays.addOverlay(type, params);
overlays[this._id] = this;
} else {
this._id = 0;
}
this._attachedPanelPointer = null;
};
that.prototype.constructor = that;
Object.defineProperty(that.prototype, "isLoaded", {
get: function() {
return Overlays.isLoaded(this._id);
}
});
Object.defineProperty(that.prototype, "attachedPanel", {
get: function() {
return this._attachedPanelPointer;
}
});
that.prototype.getTextSize = function(text) {
return Overlays.textSize(this._id, text);
};
that.prototype.setProperties = function(properties) {
Overlays.editOverlay(this._id, properties);
};
that.prototype.clone = function() {
return makeOverlayFromId(Overlays.cloneOverlay(this._id));
};
that.prototype.destroy = function() {
Overlays.deleteOverlay(this._id);
};
return generateOverlayClass(that, ABSTRACT, [
"alpha", "glowLevel", "pulseMax", "pulseMin", "pulsePeriod", "glowLevelPulse",
"alphaPulse", "colorPulse", "visible", "anchor"
]);
})();
Overlay2D = generateOverlayClass(Overlay, ABSTRACT, [
"bounds", "x", "y", "width", "height"
]);
Base3DOverlay = generateOverlayClass(Overlay, ABSTRACT, [
"position", "lineWidth", "rotation", "isSolid", "isFilled", "isWire", "isDashedLine",
"ignoreRayIntersection", "drawInFront", "drawOnHUD"
]);
Planar3DOverlay = generateOverlayClass(Base3DOverlay, ABSTRACT, [
"dimensions"
]);
Volume3DOverlay = generateOverlayClass(Base3DOverlay, ABSTRACT, [
"dimensions"
]);
generateOverlayClass(Overlay2D, "image", [
"subImage", "imageURL"
]);
generateOverlayClass(Overlay2D, "text", [
"font", "text", "backgroundColor", "backgroundAlpha", "leftMargin", "topMargin"
]);
generateOverlayClass(Planar3DOverlay, "text3d", [
"text", "backgroundColor", "backgroundAlpha", "lineHeight", "leftMargin", "topMargin",
"rightMargin", "bottomMargin", "isFacingAvatar"
]);
generateOverlayClass(Volume3DOverlay, "cube", [
"borderSize"
]);
generateOverlayClass(Volume3DOverlay, "sphere", [
]);
generateOverlayClass(Planar3DOverlay, "circle3d", [
"startAt", "endAt", "outerRadius", "innerRadius", "hasTickMarks",
"majorTickMarksAngle", "minorTickMarksAngle", "majorTickMarksLength",
"minorTickMarksLength", "majorTickMarksColor", "minorTickMarksColor"
]);
generateOverlayClass(Planar3DOverlay, "rectangle3d", [
]);
generateOverlayClass(Base3DOverlay, "line3d", [
"start", "end"
]);
generateOverlayClass(Planar3DOverlay, "grid", [
"minorGridWidth", "majorGridEvery"
]);
generateOverlayClass(Volume3DOverlay, "localmodels", [
]);
generateOverlayClass(Volume3DOverlay, "model", [
"url", "dimensions", "textures"
]);
generateOverlayClass(Planar3DOverlay, "billboard", [
"url", "subImage", "isFacingAvatar"
].concat(PANEL_ATTACHABLE_FIELDS));
})();
ImageOverlay = overlayTypes["image"];
TextOverlay = overlayTypes["text"];
Text3DOverlay = overlayTypes["text3d"];
Cube3DOverlay = overlayTypes["cube"];
Sphere3DOverlay = overlayTypes["sphere"];
Circle3DOverlay = overlayTypes["circle3d"];
Rectangle3DOverlay = overlayTypes["rectangle3d"];
Line3DOverlay = overlayTypes["line3d"];
Grid3DOverlay = overlayTypes["grid"];
LocalModelsOverlay = overlayTypes["localmodels"];
ModelOverlay = overlayTypes["model"];
BillboardOverlay = overlayTypes["billboard"];
//
// Object oriented abstraction layer for panels.
//
FloatingUIPanel = (function() {
var that = function(params) {
this._id = Overlays.addPanel(params);
this._children = [];
this._visible = Boolean(params.visible);
panels[this._id] = this;
this._attachedPanelPointer = null;
};
that.prototype.constructor = that;
var FIELDS = ["offsetPosition", "offsetRotation", "facingRotation"];
FIELDS.forEach(function(prop) {
Object.defineProperty(that.prototype, prop, {
get: function() {
return Overlays.getPanelProperty(this._id, prop);
},
set: function(newValue) {
var keyValuePair = {};
keyValuePair[prop] = newValue;
this.setProperties(keyValuePair);
},
configurable: false
});
});
var PSEUDO_FIELDS = [];
PSEUDO_FIELDS.push("children");
Object.defineProperty(that.prototype, "children", {
get: function() {
return this._children.slice();
}
});
PSEUDO_FIELDS.push("visible");
Object.defineProperty(that.prototype, "visible", {
get: function() {
return this._visible;
},
set: function(visible) {
this._visible = visible;
this._children.forEach(function(child) {
child.visible = visible;
});
}
});
that.prototype.addChild = function(child) {
if (child instanceof Overlay) {
Overlays.setAttachedPanel(child._id, this._id);
} else if (child instanceof FloatingUIPanel) {
child.setProperties({
anchorPosition: {
bind: "panel",
value: this._id
},
offsetRotation: {
bind: "panel",
value: this._id
}
});
}
child._attachedPanelPointer = this;
child.visible = this.visible;
this._children.push(child);
return child;
};
that.prototype.removeChild = function(child) {
var i = this._children.indexOf(child);
if (i >= 0) {
if (child instanceof Overlay) {
Overlays.setAttachedPanel(child._id, 0);
} else if (child instanceof FloatingUIPanel) {
child.setProperties({
anchorPosition: {
bind: "myAvatar"
},
offsetRotation: {
bind: "myAvatar"
}
});
}
child._attachedPanelPointer = null;
this._children.splice(i, 1);
}
};
that.prototype.setProperties = function(properties) {
for (var i in PSEUDO_FIELDS) {
if (properties[PSEUDO_FIELDS[i]] !== undefined) {
this[PSEUDO_FIELDS[i]] = properties[PSEUDO_FIELDS[i]];
}
}
Overlays.editPanel(this._id, properties);
};
that.prototype.destroy = function() {
Overlays.deletePanel(this._id);
};
return that;
})();
function onOverlayDeleted(id) {
if (id in overlays) {
if (overlays[id]._attachedPanelPointer) {
overlays[id]._attachedPanelPointer.removeChild(overlays[id]);
}
delete overlays[id];
}
}
function onPanelDeleted(id) {
if (id in panels) {
panels[id]._children.forEach(function(child) {
print(JSON.stringify(child.destroy));
child.destroy();
});
delete panels[id];
}
}
Overlays.overlayDeleted.connect(onOverlayDeleted);
Overlays.panelDeleted.connect(onPanelDeleted);
})();

View file

@ -1,7 +1,17 @@
/**
* OverlayGroup provides a way to create composite overlays and control their
* position relative to a settable rootPosition and rootRotation.
*/
//
// overlayUtils.js
// examples/libraries
//
// Copyright 2015 High Fidelity, Inc.
//
//
// DEPRECATION WARNING: Will be deprecated soon in favor of FloatingUIPanel.
//
// OverlayGroup provides a way to create composite overlays and control their
// position relative to a settable rootPosition and rootRotation.
//
OverlayGroup = function(opts) {
var that = {};
@ -59,6 +69,6 @@ OverlayGroup = function(opts) {
}
overlays = {};
}
return that;
};

View file

@ -44,6 +44,7 @@
emitStrength: emitStrength,
emitDirection: emitDirection,
color: color,
lifespan: 1.0,
visible: true,
locked: false });
@ -67,13 +68,13 @@
var objs = [];
function Init() {
objs.push(new TestBox());
objs.push(new TestFx({ red: 255, blue: 0, green: 0 },
objs.push(new TestFx({ red: 255, green: 0, blue: 0 },
{ x: 0.5, y: 1.0, z: 0.0 },
100, 3, 1));
objs.push(new TestFx({ red: 0, blue: 255, green: 0 },
objs.push(new TestFx({ red: 0, green: 255, blue: 0 },
{ x: 0, y: 1, z: 0 },
1000, 5, 0.5));
objs.push(new TestFx({ red: 0, blue: 0, green: 255 },
objs.push(new TestFx({ red: 0, green: 0, blue: 255 },
{ x: -0.5, y: 1, z: 0 },
100, 3, 1));
}

File diff suppressed because it is too large Load diff

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

View 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);
}
})

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

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

View file

@ -2,7 +2,7 @@ set(TARGET_NAME interface)
project(${TARGET_NAME})
# set a default root dir for each of our optional externals if it was not passed
set(OPTIONAL_EXTERNALS "Faceshift" "Sixense" "LeapMotion" "RtMidi" "SDL2" "RSSDK")
set(OPTIONAL_EXTERNALS "Faceshift" "Sixense" "LeapMotion" "RtMidi" "SDL2" "RSSDK" "connexionClient")
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR)
@ -218,33 +218,13 @@ else (APPLE)
"${PROJECT_SOURCE_DIR}/resources"
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
)
find_package(OpenGL REQUIRED)
if (${OPENGL_INCLUDE_DIR})
include_directories(SYSTEM "${OPENGL_INCLUDE_DIR}")
endif ()
target_link_libraries(${TARGET_NAME} "${OPENGL_LIBRARY}")
# link target to external libraries
if (WIN32)
add_dependency_external_projects(glew)
find_package(GLEW REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${GLEW_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARIES} wsock32.lib opengl32.lib Winmm.lib)
if (USE_NSIGHT)
# try to find the Nsight package and add it to the build if we find it
find_package(NSIGHT)
if (NSIGHT_FOUND)
include_directories(${NSIGHT_INCLUDE_DIRS})
add_definitions(-DNSIGHT_FOUND)
target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}")
endif ()
endif()
# target_link_libraries(${TARGET_NAME} wsock32.lib Winmm.lib)
target_link_libraries(${TARGET_NAME} wsock32.lib Winmm.lib)
else (WIN32)
# Nothing else required on linux apparently
endif()
endif (APPLE)

View file

@ -0,0 +1,79 @@
//
// 3DConnexion.cpp
// hifi
//
// Created by MarcelEdward Verhagen on 09-06-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 I3D_MOUSE_PARAMS_H
#define I3D_MOUSE_PARAMS_H
// Parameters for the 3D mouse based on the SDK from 3Dconnexion
class I3dMouseSensor {
public:
enum Speed {
SPEED_LOW = 0,
SPEED_MID,
SPEED_HIGH
};
virtual bool IsPanZoom() const = 0;
virtual bool IsRotate() const = 0;
virtual Speed GetSpeed() const = 0;
virtual void SetPanZoom(bool isPanZoom) = 0;
virtual void SetRotate(bool isRotate) = 0;
virtual void SetSpeed(Speed speed) = 0;
protected:
virtual ~I3dMouseSensor() {}
};
class I3dMouseNavigation {
public:
enum Pivot {
PIVOT_MANUAL = 0,
PIVOT_AUTO,
PIVOT_AUTO_OVERRIDE
};
enum Navigation {
NAVIGATION_OBJECT_MODE = 0,
NAVIGATION_CAMERA_MODE,
NAVIGATION_FLY_MODE,
NAVIGATION_WALK_MODE,
NAVIGATION_HELICOPTER_MODE
};
enum PivotVisibility {
PIVOT_HIDE = 0,
PIVOT_SHOW,
PIVOT_SHOW_MOVING
};
virtual Navigation GetNavigationMode() const = 0;
virtual Pivot GetPivotMode() const = 0;
virtual PivotVisibility GetPivotVisibility() const = 0;
virtual bool IsLockHorizon() const = 0;
virtual void SetLockHorizon(bool bOn) = 0;
virtual void SetNavigationMode(Navigation navigation) = 0;
virtual void SetPivotMode(Pivot pivot) = 0;
virtual void SetPivotVisibility(PivotVisibility visibility) = 0;
protected:
virtual ~I3dMouseNavigation(){}
};
class I3dMouseParam : public I3dMouseSensor, public I3dMouseNavigation {
public:
virtual ~I3dMouseParam() {}
};
#endif

View file

@ -0,0 +1,4 @@
The mac version does not require any files here. 3D connexion should be installed from
http://www.3dconnexion.eu/service/drivers.html
For windows a header file is required Inc/I3dMouseParams.h

View file

@ -0,0 +1,64 @@
name = defaultAvatar_full
type = body+head
scale = 1
filename = defaultAvatar_full/defaultAvatar_full.fbx
texdir = defaultAvatar_full/textures
joint = jointNeck = Head
joint = jointLeftHand = LeftHand
joint = jointRoot = Hips
joint = jointHead = HeadTop_End
joint = jointRightHand = RightHand
joint = jointLean = Spine
freeJoint = LeftArm
freeJoint = LeftForeArm
freeJoint = RightArm
freeJoint = RightForeArm
jointIndex = LeftHand = 35
jointIndex = Reye = 3
jointIndex = Hips = 10
jointIndex = LeftHandIndex1 = 36
jointIndex = LeftHandIndex2 = 37
jointIndex = LeftHandIndex3 = 38
jointIndex = LeftHandIndex4 = 39
jointIndex = LeftShoulder = 32
jointIndex = RightLeg = 12
jointIndex = Grp_blendshapes = 0
jointIndex = Leye = 4
jointIndex = headphone = 8
jointIndex = RightForeArm = 26
jointIndex = Spine = 21
jointIndex = LeftFoot = 18
jointIndex = RightToeBase = 14
jointIndex = face = 1
jointIndex = LeftToe_End = 20
jointIndex = Spine1 = 22
jointIndex = body = 9
jointIndex = Spine2 = 23
jointIndex = RightUpLeg = 11
jointIndex = top1 = 7
jointIndex = Neck = 40
jointIndex = HeadTop_End = 42
jointIndex = RightShoulder = 24
jointIndex = RightArm = 25
jointIndex = Head = 41
jointIndex = LeftLeg = 17
jointIndex = LeftForeArm = 34
jointIndex = hair = 6
jointIndex = RightHand = 27
jointIndex = LeftToeBase = 19
jointIndex = LeftUpLeg = 16
jointIndex = mouth = 2
jointIndex = RightFoot = 13
jointIndex = LeftArm = 33
jointIndex = shield = 5
jointIndex = RightHandIndex1 = 28
jointIndex = RightHandIndex2 = 29
jointIndex = RightToe_End = 15
jointIndex = RightHandIndex3 = 30
jointIndex = RightHandIndex4 = 31
ry = 0
rz = 0
tx = 0
ty = 0
tz = 0
rx = 0

Binary file not shown.

After

(image error) Size: 4.6 KiB

View file

@ -115,6 +115,7 @@
#include "devices/MIDIManager.h"
#include "devices/OculusManager.h"
#include "devices/TV3DManager.h"
#include "devices/3Dconnexion.h"
#include "scripting/AccountScriptingInterface.h"
#include "scripting/AudioDeviceScriptingInterface.h"
@ -330,7 +331,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow()),
_isVSyncOn(true),
_isThrottleFPSEnabled(false),
_isThrottleFPSEnabled(true),
_aboutToQuit(false),
_notifiedPacketVersionMismatchThisDomain(false),
_glWidget(new GLCanvas()),
@ -638,6 +639,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog);
applicationUpdater->checkForUpdate();
// the 3Dconnexion device wants to be initiliazed after a window is displayed.
ConnexionClient::init();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket");
}
@ -749,6 +753,7 @@ Application::~Application() {
Leapmotion::destroy();
RealSense::destroy();
ConnexionClient::destroy();
qInstallMessageHandler(NULL); // NOTE: Do this as late as possible so we continue to get our log messages
}
@ -766,8 +771,9 @@ void Application::initializeGL() {
}
#endif
// Where the gpuContext is created and where the TRUE Backend is created and assigned
_gpuContext = std::make_shared<gpu::Context>(new gpu::GLBackend());
// Where the gpuContext is initialized and where the TRUE Backend is created and assigned
gpu::Context::init<gpu::GLBackend>();
_gpuContext = std::make_shared<gpu::Context>();
initDisplay();
qCDebug(interfaceapp, "Initialized Display.");
@ -994,7 +1000,8 @@ void Application::paintGL() {
_compositor.displayOverlayTexture(&renderArgs);
}
if (!OculusManager::isConnected() || OculusManager::allowSwap()) {
PROFILE_RANGE(__FUNCTION__ "/bufferSwap");
_glWidget->swapBuffers();
@ -1006,6 +1013,13 @@ void Application::paintGL() {
_frameCount++;
_numFramesSinceLastResize++;
Stats::getInstance()->setRenderDetails(renderArgs._details);
// Reset the gpu::Context Stages
// Back to the default framebuffer;
gpu::Batch batch;
batch.resetStages();
renderArgs._context->render(batch);
}
void Application::runTests() {
@ -1479,6 +1493,7 @@ void Application::focusOutEvent(QFocusEvent* event) {
_keyboardMouseDevice.focusOutEvent(event);
SixenseManager::getInstance().focusOutEvent();
SDL2Manager::getInstance()->focusOutEvent();
ConnexionData::getInstance().focusOutEvent();
// synthesize events for keys currently pressed, since we may not get their release events
foreach (int key, _keysPressed) {
@ -1997,6 +2012,7 @@ void Application::setActiveFaceTracker() {
#ifdef HAVE_DDE
bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera);
Menu::getInstance()->getActionForOption(MenuOption::BinaryEyelidControl)->setVisible(isUsingDDE);
Menu::getInstance()->getActionForOption(MenuOption::CoupleEyelids)->setVisible(isUsingDDE);
Menu::getInstance()->getActionForOption(MenuOption::UseAudioForMouth)->setVisible(isUsingDDE);
Menu::getInstance()->getActionForOption(MenuOption::VelocityFilter)->setVisible(isUsingDDE);
Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setVisible(isUsingDDE);
@ -3263,6 +3279,9 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
renderContext._maxDrawnOverlay3DItems = sceneInterface->getEngineMaxDrawnOverlay3DItems();
renderContext._drawItemStatus = sceneInterface->doEngineDisplayItemStatus();
renderContext._drawHitEffect = sceneInterface->doEngineDisplayHitEffect();
renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion);
renderArgs->_shouldRender = LODManager::shouldRender;
@ -3378,7 +3397,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi
// This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further
// investigated in order to adapt the technique while fixing the head rendering issue,
// 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());
}
_mirrorCamera.setProjection(glm::perspective(glm::radians(fov), aspect, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP));
@ -3388,14 +3407,10 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi
// set the bounds of rear mirror view
gpu::Vec4i viewport;
if (billboard) {
QSize size = DependencyManager::get<FramebufferCache>()->getFrameBufferSize();
viewport = gpu::Vec4i(0, 0, region.width(), region.height());
} else {
// if not rendering the billboard, the region is in device independent coordinates; must convert to device
QSize size = DependencyManager::get<FramebufferCache>()->getFrameBufferSize();
float ratio = (float)QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale();
int x = region.x() * ratio;
int y = region.y() * ratio;
int width = region.width() * ratio;
int height = region.height() * ratio;
viewport = gpu::Vec4i(0, 0, width, height);
@ -3599,24 +3614,15 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
int statsMessageLength = 0;
const QUuid& nodeUUID = sendingNode->getUUID();
OctreeSceneStats* octreeStats;
// now that we know the node ID, let's add these stats to the stats for that node...
_octreeSceneStatsLock.lockForWrite();
auto it = _octreeServerSceneStats.find(nodeUUID);
if (it != _octreeServerSceneStats.end()) {
octreeStats = &it->second;
statsMessageLength = octreeStats->unpackFromPacket(packet);
} else {
OctreeSceneStats temp;
statsMessageLength = temp.unpackFromPacket(packet);
octreeStats = &temp;
}
OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID];
statsMessageLength = octreeStats.unpackFromPacket(packet);
_octreeSceneStatsLock.unlock();
VoxelPositionSize rootDetails;
voxelDetailsForCode(octreeStats->getJurisdictionRoot(), rootDetails);
// see if this is the first we've heard of this node...
NodeToJurisdictionMap* jurisdiction = NULL;
QString serverType;
@ -3628,6 +3634,9 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
jurisdiction->lockForRead();
if (jurisdiction->find(nodeUUID) == jurisdiction->end()) {
jurisdiction->unlock();
VoxelPositionSize rootDetails;
voxelDetailsForCode(octreeStats.getJurisdictionRoot(), rootDetails);
qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]",
qPrintable(serverType),
@ -3640,7 +3649,7 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
// but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the
// details from the OctreeSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap;
jurisdictionMap.copyContents(octreeStats->getJurisdictionRoot(), octreeStats->getJurisdictionEndNodes());
jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes());
jurisdiction->lockForWrite();
(*jurisdiction)[nodeUUID] = jurisdictionMap;
jurisdiction->unlock();
@ -3749,10 +3758,13 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface);
scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance());
qScriptRegisterMetaType(scriptEngine, DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue);
scriptEngine->registerGlobalObject("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get<AvatarManager>().data());
qScriptRegisterMetaType(scriptEngine, joystickToScriptValue, joystickFromScriptValue);

View file

@ -49,6 +49,7 @@
#include "avatar/MyAvatar.h"
#include "devices/SixenseManager.h"
#include "scripting/ControllerScriptingInterface.h"
#include "scripting/DialogsManagerScriptingInterface.h"
#include "scripting/WebWindowClass.h"
#include "ui/AudioStatsDialog.h"
#include "ui/BandwidthDialog.h"
@ -69,6 +70,7 @@
#include "UndoStackScriptingInterface.h"
#include "gpu/Context.h"
#include "render/Engine.h"
class QGLWidget;
@ -643,6 +645,8 @@ private:
ApplicationOverlay _applicationOverlay;
ApplicationCompositor _compositor;
int _numFramesSinceLastResize = 0;
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
};
#endif // hifi_Application_h

View file

@ -13,7 +13,6 @@
#define hifi_GLCanvas_h
#include <QDebug>
#include <gpu/GPUConfig.h>
#include <QGLWidget>
#include <QTimer>

View file

@ -29,6 +29,7 @@
#include "devices/Faceshift.h"
#include "devices/RealSense.h"
#include "devices/SixenseManager.h"
#include "devices/3Dconnexion.h"
#include "MainWindow.h"
#include "scripting/MenuScriptingInterface.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
@ -248,12 +249,8 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true);
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true,
avatar, SLOT(updateMotionBehavior()));
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ShiftHipsForIdleAnimations, 0, false,
avatar, SLOT(updateMotionBehavior()));
MenuWrapper* viewMenu = addMenu("View");
addActionToQMenuAndActionHash(viewMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::Fullscreen,
@ -332,7 +329,7 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere,
0, // QML Qt::SHIFT | Qt::Key_A,
true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion);
MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);
@ -370,7 +367,7 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::RenderTargetFramerateVSyncOn, 0, true,
qApp, SLOT(setVSyncEnabled()));
#endif
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, false,
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, true,
qApp, SLOT(setThrottleFPSEnabled()));
}
@ -424,6 +421,8 @@ Menu::Menu() {
faceTrackingMenu->addSeparator();
QAction* binaryEyelidControl = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::BinaryEyelidControl, 0, true);
binaryEyelidControl->setVisible(true); // DDE face tracking is on by default
QAction* coupleEyelids = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::CoupleEyelids, 0, true);
coupleEyelids->setVisible(true); // DDE face tracking is on by default
QAction* useAudioForMouth = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseAudioForMouth, 0, true);
useAudioForMouth->setVisible(true); // DDE face tracking is on by default
QAction* ddeFiltering = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::VelocityFilter, 0, true);
@ -449,6 +448,11 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu,
MenuOption::Connexion,
0, false,
&ConnexionClient::getInstance(),
SLOT(toggleConnexion(bool)));
MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands");
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, false);
@ -489,6 +493,7 @@ Menu::Menu() {
#endif
MenuWrapper* networkMenu = developerMenu->addMenu("Network");
addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false,
qApp->getEntityEditPacketSender(),
SLOT(toggleNackPackets()));

View file

@ -134,7 +134,6 @@ namespace MenuOption {
const QString AddressBar = "Show Address Bar";
const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
const QString AlternateIK = "Alternate IK";
const QString AmbientOcclusion = "Ambient Occlusion";
const QString Animations = "Animations...";
const QString Atmosphere = "Atmosphere";
const QString Attachments = "Attachments...";
@ -161,10 +160,13 @@ namespace MenuOption {
const QString CenterPlayerInView = "Center Player In View";
const QString Chat = "Chat...";
const QString Collisions = "Collisions";
const QString Connexion = "Activate 3D Connexion Devices";
const QString Console = "Console...";
const QString ControlWithSpeech = "Control With Speech";
const QString CopyAddress = "Copy Address to Clipboard";
const QString CopyPath = "Copy Path to Clipboard";
const QString CoupleEyelids = "Couple Eyelids";
const QString DebugAmbientOcclusion = "Debug Ambient Occlusion";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DeleteBookmark = "Delete Bookmark...";
const QString DisableActivityLogger = "Disable Activity Logger";
@ -272,7 +274,6 @@ namespace MenuOption {
const QString SimpleShadows = "Simple";
const QString SixenseEnabled = "Enable Hydra Support";
const QString SixenseMouseInput = "Enable Sixense Mouse Input";
const QString ShiftHipsForIdleAnimations = "Shift hips for idle animations";
const QString Stars = "Stars";
const QString Stats = "Stats";
const QString StopAllScripts = "Stop All Scripts";

View file

@ -106,7 +106,7 @@ bool ModelPackager::loadModel() {
}
qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
QByteArray fbxContents = fbx.readAll();
_geometry = readFBX(fbxContents, QVariantHash());
_geometry = readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath());
// make sure we have some basic mappings
populateBasicMapping(_mapping, _fbxInfo.filePath(), _geometry);

View file

@ -14,8 +14,6 @@
#include <mutex>
#include <QElapsedTimer>
#include <gpu/GPUConfig.h>
#include <gpu/Batch.h>
#include <gpu/Context.h>
#include <NumericalConstants.h>
#include <DependencyManager.h>
@ -154,6 +152,7 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
auto state = gpu::StatePointer(new gpu::State());
// enable decal blend
state->setDepthTest(gpu::State::DepthTest(false));
state->setAntialiasedLineEnable(true); // line smoothing also smooth points
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
_starsPipeline.reset(gpu::Pipeline::create(program, state));
@ -207,8 +206,6 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
batch._glUniform1f(_timeSlot, secs);
geometryCache->renderUnitCube(batch);
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
static const size_t VERTEX_STRIDE = sizeof(StarVertex);
size_t offset = offsetof(StarVertex, position);
gpu::BufferView posView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, positionElement);
@ -217,14 +214,11 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
// Render the stars
batch.setPipeline(_starsPipeline);
batch._glEnable(GL_PROGRAM_POINT_SIZE_EXT);
batch._glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
batch._glEnable(GL_POINT_SMOOTH);
batch.setInputFormat(streamFormat);
batch.setInputBuffer(VERTICES_SLOT, posView);
batch.setInputBuffer(COLOR_SLOT, colView);
batch.draw(gpu::Primitive::POINTS, STARFIELD_NUM_STARS);
renderArgs->_context->render(batch);
}

View file

@ -45,6 +45,7 @@
#include "Util.h"
#include "world.h"
#include "InterfaceLogging.h"
#include "EntityRig.h"
using namespace std;
@ -75,9 +76,9 @@ namespace render {
}
}
Avatar::Avatar() :
Avatar::Avatar(RigPointer rig) :
AvatarData(),
_skeletonModel(this),
_skeletonModel(this, nullptr, rig),
_skeletonOffset(0.0f),
_bodyYawDelta(0.0f),
_positionDeltaAccumulator(0.0f),
@ -430,49 +431,72 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
}
}
/*
// TODO: re-implement these when we have more detailed avatar collision shapes
bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes);
bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
if (renderSkeleton) {
_skeletonModel.renderJointCollisionShapes(0.7f);
}
bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
if (renderHead && shouldRenderHead(renderArgs)) {
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
}
*/
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
if (renderBounding && shouldRenderHead(renderArgs)) {
_skeletonModel.renderBoundingCollisionShapes(*renderArgs->_batch, 0.7f);
}
// Stack indicator spheres
float indicatorOffset = 0.0f;
if (!_displayName.isEmpty() && _displayNameAlpha != 0.0f) {
const float DISPLAY_NAME_INDICATOR_OFFSET = 0.22f;
indicatorOffset = DISPLAY_NAME_INDICATOR_OFFSET;
}
const float INDICATOR_RADIUS = 0.03f;
const float INDICATOR_INDICATOR_OFFSET = 3.0f * INDICATOR_RADIUS;
// If this is the avatar being looked at, render a little ball above their head
if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) {
const float INDICATOR_OFFSET = 0.22f;
const float INDICATOR_RADIUS = 0.03f;
const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f };
glm::vec3 position = glm::vec3(_position.x, getDisplayNamePosition().y + indicatorOffset, _position.z);
glm::vec3 position = glm::vec3(_position.x, getDisplayNamePosition().y + INDICATOR_OFFSET, _position.z);
Transform transform;
transform.setTranslation(position);
batch.setModelTransform(transform);
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch, INDICATOR_RADIUS,
15, 15, LOOK_AT_INDICATOR_COLOR);
indicatorOffset += INDICATOR_INDICATOR_OFFSET;
}
// If the avatar is looking at me, render an indication that they area
if (getHead()->getIsLookingAtMe() && Menu::getInstance()->isOptionChecked(MenuOption::ShowWhosLookingAtMe)) {
const glm::vec4 LOOKING_AT_ME_COLOR = { 0.8f, 0.65f, 0.0f, 0.1f };
glm::vec3 position = glm::vec3(_position.x, getDisplayNamePosition().y + indicatorOffset, _position.z);
Transform transform;
transform.setTranslation(position);
batch.setModelTransform(transform);
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch, INDICATOR_RADIUS,
15, 15, LOOKING_AT_ME_COLOR);
// If the avatar is looking at me, indicate that they are
if (getHead()->isLookingAtMe() && Menu::getInstance()->isOptionChecked(MenuOption::ShowWhosLookingAtMe)) {
const glm::vec3 LOOKING_AT_ME_COLOR = { 1.0f, 1.0f, 1.0f };
const float LOOKING_AT_ME_ALPHA_START = 0.8f;
const float LOOKING_AT_ME_DURATION = 0.5f; // seconds
quint64 now = usecTimestampNow();
float alpha = LOOKING_AT_ME_ALPHA_START
* (1.0f - ((float)(now - getHead()->getLookingAtMeStarted()))
/ (LOOKING_AT_ME_DURATION * (float)USECS_PER_SECOND));
if (alpha > 0.0f) {
QSharedPointer<NetworkGeometry> geometry = getHead()->getFaceModel().getGeometry();
if (geometry) {
const float DEFAULT_EYE_DIAMETER = 0.048f; // Typical human eye
const float RADIUS_INCREMENT = 0.005f;
Transform transform;
glm::vec3 position = getHead()->getLeftEyePosition();
transform.setTranslation(position);
batch.setModelTransform(transform);
float eyeDiameter = geometry->getFBXGeometry().leftEyeSize;
if (eyeDiameter == 0.0f) {
eyeDiameter = DEFAULT_EYE_DIAMETER;
}
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch,
eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT, 15, 15, glm::vec4(LOOKING_AT_ME_COLOR, alpha));
position = getHead()->getRightEyePosition();
transform.setTranslation(position);
batch.setModelTransform(transform);
eyeDiameter = geometry->getFBXGeometry().rightEyeSize;
if (eyeDiameter == 0.0f) {
eyeDiameter = DEFAULT_EYE_DIAMETER;
}
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch,
eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT, 15, 15, glm::vec4(LOOKING_AT_ME_COLOR, alpha));
}
}
}
// quick check before falling into the code below:
@ -772,33 +796,6 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor);
}
bool Avatar::findRayIntersection(RayIntersectionInfo& intersection) const {
bool hit = _skeletonModel.findRayIntersection(intersection);
hit = getHead()->getFaceModel().findRayIntersection(intersection) || hit;
return hit;
}
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions) {
return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
// TODO: Andrew to fix: Temporarily disabling collisions against the head
//return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
}
bool Avatar::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
return _skeletonModel.findPlaneCollisions(plane, collisions) ||
getHead()->getFaceModel().findPlaneCollisions(plane, collisions);
}
bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions) {
// TODO: Andrew to fix: also collide against _skeleton
//bool collided = _skeletonModel.findCollisions(shapes, collisions);
Model& headModel = getHead()->getFaceModel();
//collided = headModel.findCollisions(shapes, collisions) || collided;
bool collided = headModel.findCollisions(shapes, collisions);
return collided;
}
void Avatar::setSkeletonOffset(const glm::vec3& offset) {
const float MAX_OFFSET_LENGTH = _scale * 0.5f;
float offsetLength = glm::length(offset);
@ -822,7 +819,7 @@ QVector<glm::quat> Avatar::getJointRotations() const {
}
QVector<glm::quat> jointRotations(_skeletonModel.getJointStateCount());
for (int i = 0; i < _skeletonModel.getJointStateCount(); ++i) {
_skeletonModel.getJointState(i, jointRotations[i]);
_skeletonModel.getJointRotation(i, jointRotations[i]);
}
return jointRotations;
}
@ -832,7 +829,7 @@ glm::quat Avatar::getJointRotation(int index) const {
return AvatarData::getJointRotation(index);
}
glm::quat rotation;
_skeletonModel.getJointState(index, rotation);
_skeletonModel.getJointRotation(index, rotation);
return rotation;
}
@ -939,8 +936,14 @@ void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
const QUrl DEFAULT_FULL_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full.fst");
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_body.fst");
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar());
if (isMyAvatar()) {
_skeletonModel.setURL(_skeletonModelURL,
getUseFullAvatar() ? DEFAULT_FULL_MODEL_URL : DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar());
} else {
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar());
}
}
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
@ -956,7 +959,7 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
if (_unusedAttachments.size() > 0) {
model = _unusedAttachments.takeFirst();
} else {
model = new Model(this);
model = new Model(std::make_shared<EntityRig>(), this);
}
model->init();
_attachmentModels.append(model);
@ -1118,9 +1121,8 @@ void Avatar::setShowDisplayName(bool showDisplayName) {
// virtual
void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
const CapsuleShape& capsule = _skeletonModel.getBoundingShape();
shapeInfo.setCapsuleY(capsule.getRadius(), capsule.getHalfHeight());
shapeInfo.setOffset(_skeletonModel.getBoundingShapeOffset());
shapeInfo.setCapsuleY(_skeletonModel.getBoundingCapsuleRadius(), 0.5f * _skeletonModel.getBoundingCapsuleHeight());
shapeInfo.setOffset(_skeletonModel.getBoundingCapsuleOffset());
}
// virtual

View file

@ -72,7 +72,7 @@ class Avatar : public AvatarData {
Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset)
public:
Avatar();
Avatar(RigPointer rig = nullptr);
~Avatar();
typedef render::Payload<AvatarData> Payload;
@ -110,26 +110,6 @@ public:
/// Returns the distance to use as a LOD parameter.
float getLODDistance() const;
bool findRayIntersection(RayIntersectionInfo& intersection) const;
/// \param shapes list of shapes to collide against avatar
/// \param collisions list to store collision results
/// \return true if at least one shape collided with avatar
bool findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions);
/// Checks for penetration between the a sphere and the avatar's models.
/// \param penetratorCenter the center of the penetration test sphere
/// \param penetratorRadius the radius of the penetration test sphere
/// \param collisions[out] a list to which collisions get appended
/// \return whether or not the sphere penetrated
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions);
/// Checks for penetration between the described plane and the avatar.
/// \param plane the penetration plane
/// \param collisions[out] a list to which collisions get appended
/// \return whether or not the plane penetrated
bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
virtual bool isMyAvatar() const { return false; }
virtual QVector<glm::quat> getJointRotations() const;
@ -170,6 +150,7 @@ public:
Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; }
Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; }
virtual bool getUseFullAvatar() const { return false; }
/// Scales a world space position vector relative to the avatar position and scale
/// \param vector position to be scaled. Will store the result
@ -186,10 +167,8 @@ public:
virtual void computeShapeInfo(ShapeInfo& shapeInfo);
friend class AvatarManager;
signals:
void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision);
void setMotionState(AvatarMotionState* motionState) { _motionState = motionState; }
AvatarMotionState* getMotionState() { return _motionState; }
protected:
SkeletonModel _skeletonModel;
@ -218,7 +197,7 @@ protected:
glm::vec3 _worldUpDirection;
float _stringLength;
bool _moving; ///< set when position is changing
// protected methods...
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }
@ -243,7 +222,7 @@ protected:
virtual void updateJointMappings();
render::ItemID _renderItemID;
private:
bool _initialized;
NetworkTexturePointer _billboardTexture;
@ -251,9 +230,9 @@ private:
bool _isLookAtTarget;
void renderBillboard(RenderArgs* renderArgs);
float getBillboardSize() const;
static int _jointConesID;
int _voiceSphereID;

View file

@ -35,6 +35,7 @@
#include "Menu.h"
#include "MyAvatar.h"
#include "SceneScriptingInterface.h"
#include "AvatarRig.h"
// 70 times per second - target is 60hz, but this helps account for any small deviations
// in the update loop
@ -65,7 +66,7 @@ AvatarManager::AvatarManager(QObject* parent) :
{
// register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar
qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer");
_myAvatar = std::make_shared<MyAvatar>();
_myAvatar = std::make_shared<MyAvatar>(std::make_shared<AvatarRig>());
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
@ -160,7 +161,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
}
AvatarSharedPointer AvatarManager::newSharedAvatar() {
return AvatarSharedPointer(std::make_shared<Avatar>());
return AvatarSharedPointer(std::make_shared<Avatar>(std::make_shared<AvatarRig>()));
}
// virtual
@ -178,11 +179,11 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe
// protected
void AvatarManager::removeAvatarMotionState(AvatarSharedPointer avatar) {
auto rawPointer = std::static_pointer_cast<Avatar>(avatar);
AvatarMotionState* motionState= rawPointer->_motionState;
AvatarMotionState* motionState = rawPointer->getMotionState();
if (motionState) {
// clean up physics stuff
motionState->clearObjectBackPointer();
rawPointer->_motionState = nullptr;
rawPointer->setMotionState(nullptr);
_avatarMotionStates.remove(motionState);
_motionStatesToAdd.remove(motionState);
_motionStatesToDelete.push_back(motionState);
@ -306,7 +307,7 @@ void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) {
AvatarHash::iterator avatarItr = _avatarHash.find(id);
if (avatarItr != _avatarHash.end()) {
auto avatar = std::static_pointer_cast<Avatar>(avatarItr.value());
AvatarMotionState* motionState = avatar->_motionState;
AvatarMotionState* motionState = avatar->getMotionState();
if (motionState) {
motionState->addDirtyFlags(EntityItem::DIRTY_SHAPE);
} else {
@ -315,7 +316,7 @@ void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) {
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
if (shape) {
AvatarMotionState* motionState = new AvatarMotionState(avatar.get(), shape);
avatar->_motionState = motionState;
avatar->setMotionState(motionState);
_motionStatesToAdd.insert(motionState);
_avatarMotionStates.insert(motionState);
}

View file

@ -16,13 +16,16 @@
#include "Head.h"
#include "Menu.h"
FaceModel::FaceModel(Head* owningHead) :
FaceModel::FaceModel(Head* owningHead, RigPointer rig) :
Model(rig, nullptr),
_owningHead(owningHead)
{
assert(_rig);
}
void FaceModel::simulate(float deltaTime, bool fullUpdate) {
updateGeometry();
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
glm::vec3 neckPosition;
if (!owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) {
@ -35,67 +38,73 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) {
}
setRotation(neckParentRotation);
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale());
setPupilDilation(_owningHead->getPupilDilation());
setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients());
// FIXME - this is very expensive, we shouldn't do it if we don't have to
//invalidCalculatedMeshBoxes();
if (isActive()) {
setOffset(-_geometry->getFBXGeometry().neckPivot);
for (int i = 0; i < _rig->getJointStateCount(); i++) {
maybeUpdateNeckAndEyeRotation(i);
}
Model::simulateInternal(deltaTime);
}
}
void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, int index) {
// get the rotation axes in joint space and use them to adjust the rotation
glm::mat3 axes = glm::mat3_cast(glm::quat());
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() *
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) *
joint.preTransform * glm::mat4_cast(joint.preRotation)));
glm::vec3 pitchYawRoll = safeEulerAngles(_owningHead->getFinalOrientationInLocalFrame());
glm::vec3 lean = glm::radians(glm::vec3(_owningHead->getFinalLeanForward(),
_owningHead->getTorsoTwist(),
_owningHead->getFinalLeanSideways()));
pitchYawRoll -= lean;
state.setRotationInConstrainedFrame(glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2]))
* glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1]))
* glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0]))
* joint.rotation, DEFAULT_PRIORITY);
_rig->setJointRotationInConstrainedFrame(index,
glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2]))
* glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1]))
* glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0]))
* joint.rotation, DEFAULT_PRIORITY);
}
void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state) {
void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, int index) {
// likewise with the eye joints
// NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual.
glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() *
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() *
glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) *
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f));
glm::vec3 lookAtDelta = _owningHead->getCorrectedLookAtPosition() - model->getTranslation();
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(lookAtDelta + glm::length(lookAtDelta) * _owningHead->getSaccade(), 1.0f));
glm::quat between = rotationBetween(front, lookAt);
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
joint.rotation, DEFAULT_PRIORITY);
_rig->setJointRotationInConstrainedFrame(index, glm::angleAxis(glm::clamp(glm::angle(between),
-MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
joint.rotation, DEFAULT_PRIORITY);
}
void FaceModel::updateJointState(int index) {
JointState& state = _jointStates[index];
void FaceModel::maybeUpdateNeckAndEyeRotation(int index) {
const JointState& state = _rig->getJointState(index);
const FBXJoint& joint = state.getFBXJoint();
const FBXGeometry& geometry = _geometry->getFBXGeometry();
// guard against out-of-bounds access to _jointStates
if (joint.parentIndex != -1 && joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(joint.parentIndex);
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (joint.parentIndex != -1 && joint.parentIndex >= 0 && joint.parentIndex < _rig->getJointStateCount()) {
const JointState& parentState = _rig->getJointState(joint.parentIndex);
if (index == geometry.neckJointIndex) {
maybeUpdateNeckRotation(parentState, joint, state);
maybeUpdateNeckRotation(parentState, joint, index);
} else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) {
maybeUpdateEyeRotation(this, parentState, joint, state);
maybeUpdateEyeRotation(this, parentState, joint, index);
}
}
Model::updateJointState(index);
}
bool FaceModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {

View file

@ -19,23 +19,23 @@ class Head;
/// A face formed from a linear mix of blendshapes according to a set of coefficients.
class FaceModel : public Model {
Q_OBJECT
public:
FaceModel(Head* owningHead);
FaceModel(Head* owningHead, RigPointer rig);
virtual void simulate(float deltaTime, bool fullUpdate = true);
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
virtual void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state);
virtual void updateJointState(int index);
void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, int index);
void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, int index);
void maybeUpdateNeckAndEyeRotation(int index);
/// Retrieve the positions of up to two eye meshes.
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
private:
Head* _owningHead;
};

View file

@ -40,65 +40,6 @@ void Hand::simulate(float deltaTime, bool isMine) {
}
}
// We create a static CollisionList that is recycled for each collision test.
const float MAX_COLLISIONS_PER_AVATAR = 32;
static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR);
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
if (!avatar || avatar == _owningAvatar) {
// don't collide hands against ourself (that is done elsewhere)
return;
}
const SkeletonModel& skeletonModel = _owningAvatar->getSkeletonModel();
int jointIndices[2];
jointIndices[0] = skeletonModel.getLeftHandJointIndex();
jointIndices[1] = skeletonModel.getRightHandJointIndex();
for (size_t i = 0; i < 2; i++) {
int jointIndex = jointIndices[i];
if (jointIndex < 0) {
continue;
}
handCollisions.clear();
QVector<const Shape*> shapes;
skeletonModel.getHandShapes(jointIndex, shapes);
if (avatar->findCollisions(shapes, handCollisions)) {
glm::vec3 totalPenetration(0.0f);
glm::vec3 averageContactPoint;
for (int j = 0; j < handCollisions.size(); ++j) {
CollisionInfo* collision = handCollisions.getCollision(j);
totalPenetration += collision->_penetration;
averageContactPoint += collision->_contactPoint;
}
if (isMyHand) {
// our hand against other avatar
// TODO: resolve this penetration when we don't think the other avatar will yield
//palm.addToPenetration(averagePenetration);
} else {
// someone else's hand against MyAvatar
// TODO: submit collision info to MyAvatar which should lean accordingly
averageContactPoint /= (float)handCollisions.size();
avatar->applyCollision(averageContactPoint, totalPenetration);
CollisionInfo collision;
collision._penetration = totalPenetration;
collision._contactPoint = averageContactPoint;
emit avatar->collisionWithAvatar(avatar->getSessionUUID(), _owningAvatar->getSessionUUID(), collision);
}
}
}
}
void Hand::resolvePenetrations() {
for (size_t i = 0; i < getNumPalms(); ++i) {
PalmData& palm = getPalms()[i];
palm.resolvePenetrations();
}
}
void Hand::render(RenderArgs* renderArgs, bool isMine) {
gpu::Batch& batch = *renderArgs->_batch;
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE &&

View file

@ -29,10 +29,6 @@
class Avatar;
const float HAND_PADDLE_OFFSET = 0.1f;
const float HAND_PADDLE_THICKNESS = 0.01f;
const float HAND_PADDLE_RADIUS = 0.15f;
class Hand : public HandData {
public:
Hand(Avatar* owningAvatar);
@ -40,10 +36,6 @@ public:
void simulate(float deltaTime, bool isMine);
void render(RenderArgs* renderArgs, bool isMine);
void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
void resolvePenetrations();
private:
// disallow copies of the Hand, copy of owning Avatar is disallowed too
Hand(const Hand&);

View file

@ -23,6 +23,7 @@
#include "Util.h"
#include "devices/DdeFaceTracker.h"
#include "devices/Faceshift.h"
#include "AvatarRig.h"
using namespace std;
@ -55,11 +56,12 @@ Head::Head(Avatar* owningAvatar) :
_deltaLeanForward(0.0f),
_isCameraMoving(false),
_isLookingAtMe(false),
_faceModel(this),
_lookingAtMeStarted(0),
_wasLastLookingAtMe(0),
_faceModel(this, std::make_shared<AvatarRig>()),
_leftEyeLookAtID(DependencyManager::get<GeometryCache>()->allocateID()),
_rightEyeLookAtID(DependencyManager::get<GeometryCache>()->allocateID())
{
}
void Head::init() {
@ -316,7 +318,7 @@ glm::quat Head::getFinalOrientationInLocalFrame() const {
}
glm::vec3 Head::getCorrectedLookAtPosition() {
if (_isLookingAtMe) {
if (isLookingAtMe()) {
return _correctedLookAtPosition;
} else {
return getLookAtPosition();
@ -324,10 +326,21 @@ glm::vec3 Head::getCorrectedLookAtPosition() {
}
void Head::setCorrectedLookAtPosition(glm::vec3 correctedLookAtPosition) {
if (!isLookingAtMe()) {
_lookingAtMeStarted = usecTimestampNow();
}
_isLookingAtMe = true;
_wasLastLookingAtMe = usecTimestampNow();
_correctedLookAtPosition = correctedLookAtPosition;
}
bool Head::isLookingAtMe() {
// Allow for outages such as may be encountered during avatar movement
quint64 now = usecTimestampNow();
const quint64 LOOKING_AT_ME_GAP_ALLOWED = 1000000; // microseconds
return _isLookingAtMe || (now - _wasLastLookingAtMe) < LOOKING_AT_ME_GAP_ALLOWED;
}
glm::quat Head::getCameraOrientation() const {
// NOTE: Head::getCameraOrientation() is not used for orienting the camera "view" while in Oculus mode, so
// you may wonder why this code is here. This method will be called while in Oculus mode to determine how

View file

@ -52,8 +52,9 @@ public:
void setCorrectedLookAtPosition(glm::vec3 correctedLookAtPosition);
glm::vec3 getCorrectedLookAtPosition();
void clearCorrectedLookAtPosition() { _isLookingAtMe = false; }
bool getIsLookingAtMe() { return _isLookingAtMe; }
bool isLookingAtMe();
quint64 getLookingAtMeStarted() { return _lookingAtMeStarted; }
float getScale() const { return _scale; }
glm::vec3 getPosition() const { return _position; }
const glm::vec3& getEyePosition() const { return _eyePosition; }
@ -139,6 +140,8 @@ private:
bool _isCameraMoving;
bool _isLookingAtMe;
quint64 _lookingAtMeStarted;
quint64 _wasLastLookingAtMe;
FaceModel _faceModel;
glm::vec3 _correctedLookAtPosition;

View file

@ -29,7 +29,6 @@
#include <udt/PacketHeaders.h>
#include <PathUtils.h>
#include <PerfStat.h>
#include <ShapeCollider.h>
#include <SharedUtil.h>
#include <TextRenderer3D.h>
#include <UserActivityLogger.h>
@ -78,8 +77,8 @@ const float MyAvatar::ZOOM_MIN = 0.5f;
const float MyAvatar::ZOOM_MAX = 25.0f;
const float MyAvatar::ZOOM_DEFAULT = 1.5f;
MyAvatar::MyAvatar() :
Avatar(),
MyAvatar::MyAvatar(RigPointer rig) :
Avatar(rig),
_gravity(0.0f, 0.0f, 0.0f),
_wasPushing(false),
_isPushing(false),
@ -98,22 +97,16 @@ MyAvatar::MyAvatar() :
_lookAtTargetAvatar(),
_shouldRender(true),
_billboardValid(false),
_feetTouchFloor(true),
_eyeContactTarget(LEFT_EYE),
_realWorldFieldOfView("realWorldFieldOfView",
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
_firstPersonSkeletonModel(this),
_rig(rig),
_prevShouldDrawHead(true)
{
_firstPersonSkeletonModel.setIsFirstPerson(true);
ShapeCollider::initDispatchTable();
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
}
_skeletonModel.setEnableShapes(true);
// connect to AddressManager signal for location jumps
connect(DependencyManager::get<AddressManager>().data(), &AddressManager::locationChangeRequired,
this, &MyAvatar::goToLocation);
@ -140,7 +133,6 @@ QByteArray MyAvatar::toByteArray() {
void MyAvatar::reset() {
_skeletonModel.reset();
_firstPersonSkeletonModel.reset();
getHead()->reset();
_targetVelocity = glm::vec3(0.0f);
@ -166,9 +158,6 @@ void MyAvatar::update(float deltaTime) {
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
simulate(deltaTime);
if (_feetTouchFloor) {
_skeletonModel.updateStandingFoot();
}
}
void MyAvatar::simulate(float deltaTime) {
@ -199,7 +188,6 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("skeleton");
_skeletonModel.simulate(deltaTime);
_firstPersonSkeletonModel.simulate(deltaTime);
}
if (!_skeletonModel.hasSkeleton()) {
@ -215,10 +203,10 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("joints");
// copy out the skeleton joints from the model
_jointData.resize(_skeletonModel.getJointStateCount());
_jointData.resize(_rig->getJointStateCount());
for (int i = 0; i < _jointData.size(); i++) {
JointData& data = _jointData[i];
data.valid = _skeletonModel.getJointState(i, data.rotation);
data.valid = _rig->getJointStateRotation(i, data.rotation);
}
}
@ -495,17 +483,6 @@ void MyAvatar::loadLastRecording() {
_player->loadRecording(_recorder->getRecording());
}
AnimationHandlePointer MyAvatar::addAnimationHandle() {
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
_animationHandles.append(handle);
return handle;
}
void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) {
handle->stop();
_animationHandles.removeOne(handle);
}
void MyAvatar::startAnimation(const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
if (QThread::currentThread() != thread()) {
@ -514,16 +491,7 @@ void MyAvatar::startAnimation(const QString& url, float fps, float priority,
Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints));
return;
}
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
handle->setURL(url);
handle->setFPS(fps);
handle->setPriority(priority);
handle->setLoop(loop);
handle->setHold(hold);
handle->setFirstFrame(firstFrame);
handle->setLastFrame(lastFrame);
handle->setMaskedJoints(maskedJoints);
handle->start();
_rig->startAnimation(url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints);
}
void MyAvatar::startAnimationByRole(const QString& role, const QString& url, float fps, float priority,
@ -534,25 +502,7 @@ void MyAvatar::startAnimationByRole(const QString& role, const QString& url, flo
Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints));
return;
}
// check for a configured animation for the role
foreach (const AnimationHandlePointer& handle, _animationHandles) {
if (handle->getRole() == role) {
handle->start();
return;
}
}
// no joy; use the parameters provided
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
handle->setRole(role);
handle->setURL(url);
handle->setFPS(fps);
handle->setPriority(priority);
handle->setLoop(loop);
handle->setHold(hold);
handle->setFirstFrame(firstFrame);
handle->setLastFrame(lastFrame);
handle->setMaskedJoints(maskedJoints);
handle->start();
_rig->startAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints);
}
void MyAvatar::stopAnimationByRole(const QString& role) {
@ -560,11 +510,7 @@ void MyAvatar::stopAnimationByRole(const QString& role) {
QMetaObject::invokeMethod(this, "stopAnimationByRole", Q_ARG(const QString&, role));
return;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
if (handle->getRole() == role) {
handle->stop();
}
}
_rig->stopAnimationByRole(role);
}
void MyAvatar::stopAnimation(const QString& url) {
@ -572,11 +518,7 @@ void MyAvatar::stopAnimation(const QString& url) {
QMetaObject::invokeMethod(this, "stopAnimation", Q_ARG(const QString&, url));
return;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
if (handle->getURL() == url) {
handle->stop();
}
}
_rig->stopAnimation(url);
}
AnimationDetails MyAvatar::getAnimationDetailsByRole(const QString& role) {
@ -587,7 +529,7 @@ AnimationDetails MyAvatar::getAnimationDetailsByRole(const QString& role) {
Q_ARG(const QString&, role));
return result;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
foreach (const AnimationHandlePointer& handle, _rig->getRunningAnimations()) {
if (handle->getRole() == role) {
result = handle->getAnimationDetails();
break;
@ -604,7 +546,7 @@ AnimationDetails MyAvatar::getAnimationDetails(const QString& url) {
Q_ARG(const QString&, url));
return result;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
foreach (const AnimationHandlePointer& handle, _rig->getRunningAnimations()) {
if (handle->getURL() == url) {
result = handle->getAnimationDetails();
break;
@ -650,9 +592,10 @@ void MyAvatar::saveData() {
settings.endArray();
settings.beginWriteArray("animationHandles");
for (int i = 0; i < _animationHandles.size(); i++) {
auto animationHandles = _rig->getAnimationHandles();
for (int i = 0; i < animationHandles.size(); i++) {
settings.setArrayIndex(i);
const AnimationHandlePointer& pointer = _animationHandles.at(i);
const AnimationHandlePointer& pointer = animationHandles.at(i);
settings.setValue("role", pointer->getRole());
settings.setValue("url", pointer->getURL());
settings.setValue("fps", pointer->getFPS());
@ -770,25 +713,19 @@ void MyAvatar::loadData() {
setAttachmentData(attachmentData);
int animationCount = settings.beginReadArray("animationHandles");
while (_animationHandles.size() > animationCount) {
_animationHandles.takeLast()->stop();
}
while (_animationHandles.size() < animationCount) {
addAnimationHandle();
}
_rig->deleteAnimations();
for (int i = 0; i < animationCount; i++) {
settings.setArrayIndex(i);
const AnimationHandlePointer& handle = _animationHandles.at(i);
handle->setRole(settings.value("role", "idle").toString());
handle->setURL(settings.value("url").toUrl());
handle->setFPS(loadSetting(settings, "fps", 30.0f));
handle->setPriority(loadSetting(settings, "priority", 1.0f));
handle->setLoop(settings.value("loop", true).toBool());
handle->setHold(settings.value("hold", false).toBool());
handle->setFirstFrame(settings.value("firstFrame", 0.0f).toFloat());
handle->setLastFrame(settings.value("lastFrame", INT_MAX).toFloat());
handle->setMaskedJoints(settings.value("maskedJoints").toStringList());
handle->setStartAutomatically(settings.value("startAutomatically", true).toBool());
_rig->addAnimationByRole(settings.value("role", "idle").toString(),
settings.value("url").toString(),
loadSetting(settings, "fps", 30.0f),
loadSetting(settings, "priority", 1.0f),
settings.value("loop", true).toBool(),
settings.value("hold", false).toBool(),
settings.value("firstFrame", 0.0f).toFloat(),
settings.value("lastFrame", INT_MAX).toFloat(),
settings.value("maskedJoints").toStringList(),
settings.value("startAutomatically", true).toBool());
}
settings.endArray();
@ -796,6 +733,7 @@ void MyAvatar::loadData() {
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
settings.endGroup();
_rig->setEnableRig(settings.value("enableRig").toBool());
}
void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const {
@ -965,15 +903,15 @@ void MyAvatar::setJointRotations(QVector<glm::quat> jointRotations) {
void MyAvatar::setJointData(int index, const glm::quat& rotation) {
if (QThread::currentThread() == thread()) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel.setJointState(index, true, rotation, SCRIPT_PRIORITY);
_rig->setJointState(index, true, rotation, SCRIPT_PRIORITY);
}
}
void MyAvatar::clearJointData(int index) {
if (QThread::currentThread() == thread()) {
// HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority
_skeletonModel.setJointState(index, false, glm::quat(), 0.0f);
_skeletonModel.clearJointAnimationPriority(index);
_rig->setJointState(index, false, glm::quat(), 0.0f);
_rig->clearJointAnimationPriority(index);
}
}
@ -984,7 +922,7 @@ void MyAvatar::clearJointsData() {
void MyAvatar::clearJointAnimationPriorities() {
int numStates = _skeletonModel.getJointStateCount();
for (int i = 0; i < numStates; ++i) {
_skeletonModel.clearJointAnimationPriority(i);
_rig->clearJointAnimationPriority(i);
}
}
@ -1024,19 +962,8 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
Avatar::setSkeletonModelURL(skeletonModelURL);
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
_billboardValid = false;
if (_useFullAvatar) {
_skeletonModel.setVisibleInScene(_prevShouldDrawHead, scene);
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_body.fst");
_firstPersonSkeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar());
_firstPersonSkeletonModel.setVisibleInScene(!_prevShouldDrawHead, scene);
} else {
_skeletonModel.setVisibleInScene(true, scene);
_firstPersonSkeletonModel.setVisibleInScene(false, scene);
_firstPersonSkeletonModel.reset();
}
_skeletonModel.setVisibleInScene(true, scene);
_headBoneSet.clear();
}
void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) {
@ -1140,22 +1067,17 @@ glm::vec3 MyAvatar::getSkeletonPosition() const {
// The avatar is rotated PI about the yAxis, so we have to correct for it
// to get the skeleton offset contribution in the world-frame.
const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 skeletonOffset = _skeletonOffset;
if (_feetTouchFloor) {
skeletonOffset += _skeletonModel.getStandingOffset();
}
return _position + getOrientation() * FLIP * skeletonOffset;
return _position + getOrientation() * FLIP * _skeletonOffset;
}
return Avatar::getPosition();
}
void MyAvatar::rebuildSkeletonBody() {
// compute localAABox
const CapsuleShape& capsule = _skeletonModel.getBoundingShape();
float radius = capsule.getRadius();
float height = 2.0f * (capsule.getHalfHeight() + radius);
float radius = _skeletonModel.getBoundingCapsuleRadius();
float height = _skeletonModel.getBoundingCapsuleHeight() + 2.0f * radius;
glm::vec3 corner(-radius, -0.5f * height, -radius);
corner += _skeletonModel.getBoundingShapeOffset();
corner += _skeletonModel.getBoundingCapsuleOffset();
glm::vec3 scale(2.0f * radius, height, 2.0f * radius);
_characterController.setLocalBoundingBox(corner, scale);
}
@ -1247,29 +1169,48 @@ void MyAvatar::setVisibleInSceneIfReady(Model* model, render::ScenePointer scene
}
}
void MyAvatar::initHeadBones() {
int neckJointIndex = -1;
if (_skeletonModel.getGeometry()) {
neckJointIndex = _skeletonModel.getGeometry()->getFBXGeometry().neckJointIndex;
}
if (neckJointIndex == -1) {
return;
}
_headBoneSet.clear();
std::queue<int> q;
q.push(neckJointIndex);
_headBoneSet.insert(neckJointIndex);
// fbxJoints only hold links to parents not children, so we have to do a bit of extra work here.
while (q.size() > 0) {
int jointIndex = q.front();
for (int i = 0; i < _skeletonModel.getJointStateCount(); i++) {
if (jointIndex == _skeletonModel.getParentJointIndex(i)) {
_headBoneSet.insert(i);
q.push(i);
}
}
q.pop();
}
}
void MyAvatar::preRender(RenderArgs* renderArgs) {
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
const bool shouldDrawHead = shouldRenderHead(renderArgs);
_skeletonModel.initWhenReady(scene);
if (_useFullAvatar) {
_firstPersonSkeletonModel.initWhenReady(scene);
if (_skeletonModel.initWhenReady(scene)) {
initHeadBones();
_skeletonModel.setCauterizeBoneSet(_headBoneSet);
}
if (shouldDrawHead != _prevShouldDrawHead) {
if (_useFullAvatar) {
if (shouldDrawHead) {
_skeletonModel.setVisibleInScene(true, scene);
_firstPersonSkeletonModel.setVisibleInScene(false, scene);
} else {
_skeletonModel.setVisibleInScene(false, scene);
_firstPersonSkeletonModel.setVisibleInScene(true, scene);
}
_skeletonModel.setCauterizeBones(!shouldDrawHead);
} else {
getHead()->getFaceModel().setVisibleInScene(shouldDrawHead, scene);
}
}
_prevShouldDrawHead = shouldDrawHead;
}
@ -1290,7 +1231,6 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
void MyAvatar::updateOrientation(float deltaTime) {
// Smoothly rotate body with arrow keys
float driveLeft = _driveKeys[ROT_LEFT] - _driveKeys[ROT_RIGHT];
float targetSpeed = (_driveKeys[ROT_LEFT] - _driveKeys[ROT_RIGHT]) * YAW_SPEED;
if (targetSpeed != 0.0f) {
const float ROTATION_RAMP_TIMESCALE = 0.1f;
@ -1623,7 +1563,6 @@ void MyAvatar::updateMotionBehavior() {
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
}
_characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
_feetTouchFloor = menu->isOptionChecked(MenuOption::ShiftHipsForIdleAnimations);
}
//Renders sixense laser pointers for UI selection with controllers

View file

@ -14,6 +14,7 @@
#include <SettingHandle.h>
#include <DynamicCharacterController.h>
#include <Rig.h>
#include "Avatar.h"
@ -35,54 +36,43 @@ class MyAvatar : public Avatar {
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
public:
MyAvatar();
MyAvatar(RigPointer rig);
~MyAvatar();
QByteArray toByteArray();
void reset();
void update(float deltaTime);
void simulate(float deltaTime);
void preRender(RenderArgs* renderArgs);
void updateFromTrackers(float deltaTime);
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override;
virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f) override;
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
// setters
void setLeanScale(float scale) { _leanScale = scale; }
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; }
void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); }
// getters
float getLeanScale() const { return _leanScale; }
Q_INVOKABLE glm::vec3 getDefaultEyePosition() const;
bool getShouldRenderLocally() const { return _shouldRender; }
float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); }
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
AnimationHandlePointer addAnimationHandle();
void removeAnimationHandle(const AnimationHandlePointer& handle);
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _rig->getAnimationHandles(); }
AnimationHandlePointer addAnimationHandle() { return _rig->createAnimationHandle(); }
void removeAnimationHandle(const AnimationHandlePointer& handle) { _rig->removeAnimationHandle(handle); }
/// Allows scripts to run animations.
Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false,
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
/// Stops an animation as identified by a URL.
Q_INVOKABLE void stopAnimation(const QString& url);
/// Starts an animation by its role, using the provided URL and parameters if the avatar doesn't have a custom
/// animation for the role.
Q_INVOKABLE void startAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f,
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
/// Stops an animation identified by its role.
Q_INVOKABLE void stopAnimationByRole(const QString& role);
Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role);
Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url);
void clearJointAnimationPriorities();
// get/set avatar data
void saveData();
void loadData();
@ -93,32 +83,27 @@ public:
// Set what driving keys are being pressed to control thrust levels
void clearDriveKeys();
void setDriveKeys(int key, float val) { _driveKeys[key] = val; };
bool getDriveKeys(int key) { return _driveKeys[key] != 0.0f; };
void relayDriveKeysToCharacterController();
bool isMyAvatar() const { return true; }
eyeContactTarget getEyeContactTarget();
virtual int parseDataFromBuffer(const QByteArray& buffer);
static void sendKillAvatar();
Q_INVOKABLE glm::vec3 getTrackedHeadPosition() const { return _trackedHeadPosition; }
Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); }
Q_INVOKABLE float getHeadFinalYaw() const { return getHead()->getFinalYaw(); }
Q_INVOKABLE float getHeadFinalRoll() const { return getHead()->getFinalRoll(); }
Q_INVOKABLE float getHeadFinalPitch() const { return getHead()->getFinalPitch(); }
Q_INVOKABLE float getHeadDeltaPitch() const { return getHead()->getDeltaPitch(); }
Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); }
Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; }
AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar();
virtual void setJointRotations(QVector<glm::quat> jointRotations);
virtual void setJointData(int index, const glm::quat& rotation);
virtual void clearJointData(int index);
@ -127,9 +112,10 @@ public:
Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
Q_INVOKABLE void useHeadURL(const QUrl& headURL, const QString& modelName = QString());
Q_INVOKABLE void useBodyURL(const QUrl& bodyURL, const QString& modelName = QString());
Q_INVOKABLE void useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL, const QString& headName = QString(), const QString& bodyName = QString());
Q_INVOKABLE void useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL,
const QString& headName = QString(), const QString& bodyName = QString());
Q_INVOKABLE bool getUseFullAvatar() const { return _useFullAvatar; }
Q_INVOKABLE virtual bool getUseFullAvatar() const { return _useFullAvatar; }
Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
Q_INVOKABLE const QUrl& getHeadURLFromPreferences() const { return _headURLFromPreferences; }
Q_INVOKABLE const QUrl& getBodyURLFromPreferences() const { return _skeletonURLFromPreferences; }
@ -142,48 +128,30 @@ public:
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
virtual glm::vec3 getSkeletonPosition() const;
void updateLocalAABox();
DynamicCharacterController* getCharacterController() { return &_characterController; }
void clearJointAnimationPriorities();
glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; }
float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; }
QString getScriptedMotorFrame() const;
void setScriptedMotorVelocity(const glm::vec3& velocity);
void setScriptedMotorTimescale(float timescale);
void setScriptedMotorFrame(QString frame);
const QString& getCollisionSoundURL() {return _collisionSoundURL; }
void setCollisionSoundURL(const QString& url);
void clearScriptableSettings();
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
bool allowDuplicates = false, bool useSaved = true);
/// Renders a laser pointer for UI picking
void renderLaserPointers(gpu::Batch& batch);
glm::vec3 getLaserPointerTipPosition(const PalmData* palm);
const RecorderPointer getRecorder() const { return _recorder; }
const PlayerPointer getPlayer() const { return _player; }
float getBoomLength() const { return _boomLength; }
void setBoomLength(float boomLength) { _boomLength = boomLength; }
static const float ZOOM_MIN;
static const float ZOOM_MAX;
static const float ZOOM_DEFAULT;
public slots:
void increaseSize();
void decreaseSize();
void resetSize();
void goToLocation(const glm::vec3& newPosition,
bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(),
bool shouldFaceLocation = false);
@ -203,7 +171,7 @@ public slots:
void clearReferential();
bool setModelReferential(const QUuid& id);
bool setJointReferential(const QUuid& id, int jointIndex);
bool isRecording();
qint64 recorderElapsed();
void startRecording();
@ -212,7 +180,7 @@ public slots:
void loadLastRecording();
virtual void rebuildSkeletonBody();
signals:
void transformChanged();
void newCollisionSoundURL(const QUrl& url);
@ -220,6 +188,33 @@ signals:
private:
QByteArray toByteArray();
void simulate(float deltaTime);
void updateFromTrackers(float deltaTime);
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override;
virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f) override;
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; }
bool getShouldRenderLocally() const { return _shouldRender; }
bool getDriveKeys(int key) { return _driveKeys[key] != 0.0f; };
bool isMyAvatar() const { return true; }
virtual int parseDataFromBuffer(const QByteArray& buffer);
virtual glm::vec3 getSkeletonPosition() const;
glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; }
float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; }
QString getScriptedMotorFrame() const;
void setScriptedMotorVelocity(const glm::vec3& velocity);
void setScriptedMotorTimescale(float timescale);
void setScriptedMotorFrame(QString frame);
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
bool allowDuplicates = false, bool useSaved = true);
void renderLaserPointers(gpu::Batch& batch);
const RecorderPointer getRecorder() const { return _recorder; }
const PlayerPointer getPlayer() const { return _player; }
bool cameraInsideHead() const;
// These are made private for MyAvatar so that you will use the "use" methods instead
@ -234,7 +229,7 @@ private:
bool _wasPushing;
bool _isPushing;
bool _isBraking;
float _boomLength;
float _trapDuration; // seconds that avatar has been trapped by collisions
@ -256,38 +251,36 @@ private:
bool _billboardValid;
float _oculusYawOffset;
QList<AnimationHandlePointer> _animationHandles;
bool _feetTouchFloor;
eyeContactTarget _eyeContactTarget;
RecorderPointer _recorder;
glm::vec3 _trackedHeadPosition;
Setting::Handle<float> _realWorldFieldOfView;
// private methods
// private methods
void updateOrientation(float deltaTime);
glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool isHovering);
glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity);
void updatePosition(float deltaTime);
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void maybeUpdateBillboard();
void initHeadBones();
// Avatar Preferences
bool _useFullAvatar = false;
QUrl _fullAvatarURLFromPreferences;
QUrl _headURLFromPreferences;
QUrl _skeletonURLFromPreferences;
QString _headModelName;
QString _bodyModelName;
QString _fullAvatarModelName;
// used for rendering when in first person view or when in an HMD.
SkeletonModel _firstPersonSkeletonModel;
RigPointer _rig;
bool _prevShouldDrawHead;
std::unordered_set<int> _headBoneSet;
};
#endif // hifi_MyAvatar_h

View file

@ -12,9 +12,7 @@
#include <glm/gtx/transform.hpp>
#include <QMultiMap>
#include <CapsuleShape.h>
#include <DeferredLightingEffect.h>
#include <SphereShape.h>
#include "Application.h"
#include "Avatar.h"
@ -24,38 +22,47 @@
#include "Util.h"
#include "InterfaceLogging.h"
enum StandingFootState {
LEFT_FOOT,
RIGHT_FOOT,
NO_FOOT
};
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
Model(parent),
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) :
Model(rig, parent),
_triangleFanID(DependencyManager::get<GeometryCache>()->allocateID()),
_owningAvatar(owningAvatar),
_boundingShape(),
_boundingShapeLocalOffset(0.0f),
_boundingCapsuleLocalOffset(0.0f),
_boundingCapsuleRadius(0.0f),
_boundingCapsuleHeight(0.0f),
_defaultEyeModelPosition(glm::vec3(0.0f, 0.0f, 0.0f)),
_standingFoot(NO_FOOT),
_standingOffset(0.0f),
_clampedFootPosition(0.0f),
_headClipDistance(DEFAULT_NEAR_CLIP),
_isFirstPerson(false)
_headClipDistance(DEFAULT_NEAR_CLIP)
{
assert(_rig);
assert(_owningAvatar);
_enableShapes = true;
}
SkeletonModel::~SkeletonModel() {
}
void SkeletonModel::initJointStates(QVector<JointState> states) {
Model::initJointStates(states);
const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
int rootJointIndex = geometry.rootJointIndex;
int leftHandJointIndex = geometry.leftHandJointIndex;
int leftElbowJointIndex = leftHandJointIndex >= 0 ? geometry.joints.at(leftHandJointIndex).parentIndex : -1;
int leftShoulderJointIndex = leftElbowJointIndex >= 0 ? geometry.joints.at(leftElbowJointIndex).parentIndex : -1;
int rightHandJointIndex = geometry.rightHandJointIndex;
int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1;
int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1;
_boundingRadius = _rig->initJointStates(states, parentTransform,
rootJointIndex,
leftHandJointIndex,
leftElbowJointIndex,
leftShoulderJointIndex,
rightHandJointIndex,
rightElbowJointIndex,
rightShoulderJointIndex);
// Determine the default eye position for avatar scale = 1.0
int headJointIndex = _geometry->getFBXGeometry().headJointIndex;
if (0 <= headJointIndex && headJointIndex < _jointStates.size()) {
if (0 <= headJointIndex && headJointIndex < _rig->getJointStateCount()) {
glm::vec3 leftEyePosition, rightEyePosition;
getEyeModelPositions(leftEyePosition, rightEyePosition);
@ -75,14 +82,11 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
// the SkeletonModel override of updateJointState() will clear the translation part
// of its root joint and we need that done before we try to build shapes hence we
// recompute all joint transforms at this time.
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i);
for (int i = 0; i < _rig->getJointStateCount(); i++) {
_rig->updateJointState(i, parentTransform);
}
clearShapes();
if (_enableShapes) {
buildShapes();
}
buildShapes();
Extents meshExtents = getMeshExtents();
_headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z);
@ -95,6 +99,29 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
const float PALM_PRIORITY = DEFAULT_PRIORITY;
const float LEAN_PRIORITY = DEFAULT_PRIORITY;
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation());
Model::updateRig(deltaTime, parentTransform);
if (_owningAvatar->isMyAvatar()) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
Rig::HeadParameters params;
params.leanSideways = _owningAvatar->getHead()->getFinalLeanSideways();
params.leanForward = _owningAvatar->getHead()->getFinalLeanSideways();
params.torsoTwist = _owningAvatar->getHead()->getTorsoTwist();
params.localHeadOrientation = _owningAvatar->getHead()->getFinalOrientationInLocalFrame();
params.worldHeadOrientation = _owningAvatar->getHead()->getFinalOrientationInWorldFrame();
params.eyeLookAt = _owningAvatar->getHead()->getCorrectedLookAtPosition();
params.eyeSaccade = _owningAvatar->getHead()->getSaccade();
params.leanJointIndex = geometry.leanJointIndex;
params.neckJointIndex = geometry.neckJointIndex;
params.leftEyeJointIndex = geometry.leftEyeJointIndex;
params.rightEyeJointIndex = geometry.rightEyeJointIndex;
_rig->updateFromHeadParameters(params);
}
}
void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
setTranslation(_owningAvatar->getSkeletonPosition());
static const glm::quat refOrientation = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
@ -150,49 +177,6 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
}
}
if (_isFirstPerson) {
cauterizeHead();
updateClusterMatrices();
}
_boundingShape.setTranslation(_translation + _rotation * _boundingShapeLocalOffset);
_boundingShape.setRotation(_rotation);
}
void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const {
if (jointIndex < 0 || jointIndex >= int(_shapes.size())) {
return;
}
if (jointIndex == getLeftHandJointIndex()
|| jointIndex == getRightHandJointIndex()) {
// get all shapes that have this hand as an ancestor in the skeleton heirarchy
const FBXGeometry& geometry = _geometry->getFBXGeometry();
for (int i = 0; i < _jointStates.size(); i++) {
const FBXJoint& joint = geometry.joints[i];
int parentIndex = joint.parentIndex;
Shape* shape = _shapes[i];
if (i == jointIndex) {
// this shape is the hand
if (shape) {
shapes.push_back(shape);
}
if (parentIndex != -1 && _shapes[parentIndex]) {
// also add the forearm
shapes.push_back(_shapes[parentIndex]);
}
} else if (shape) {
while (parentIndex != -1) {
if (parentIndex == jointIndex) {
// this shape is a child of the hand
shapes.push_back(shape);
break;
}
parentIndex = geometry.joints[parentIndex].parentIndex;
}
}
}
}
}
void SkeletonModel::renderIKConstraints(gpu::Batch& batch) {
@ -211,7 +195,7 @@ bool operator<(const IndexValue& firstIndex, const IndexValue& secondIndex) {
}
void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
return;
}
// NOTE: 'position' is in model-frame
@ -226,16 +210,20 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position)
if (forearmLength < EPSILON) {
return;
}
JointState& state = _jointStates[jointIndex];
glm::quat handRotation = state.getRotation();
glm::quat handRotation;
if (!_rig->getJointStateRotation(jointIndex, handRotation)) {
return;
}
// align hand with forearm
float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f;
state.applyRotationDelta(rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector), true, PALM_PRIORITY);
_rig->applyJointRotationDelta(jointIndex,
rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector),
true, PALM_PRIORITY);
}
void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -255,74 +243,22 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), fingerDirection) * palmRotation;
if (Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK)) {
setHandPosition(jointIndex, palmPosition, palmRotation);
_rig->setHandPosition(jointIndex, palmPosition, palmRotation, extractUniformScale(_scale), PALM_PRIORITY);
} else if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) {
float forearmLength = geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale);
glm::vec3 forearm = palmRotation * glm::vec3(sign * forearmLength, 0.0f, 0.0f);
setJointPosition(parentJointIndex, palmPosition + forearm,
glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY);
JointState& parentState = _jointStates[parentJointIndex];
parentState.setRotationInBindFrame(palmRotation, PALM_PRIORITY);
_rig->setJointRotationInBindFrame(parentJointIndex, palmRotation, PALM_PRIORITY);
// lock hand to forearm by slamming its rotation (in parent-frame) to identity
_jointStates[jointIndex].setRotationInConstrainedFrame(glm::quat(), PALM_PRIORITY);
_rig->setJointRotationInConstrainedFrame(jointIndex, glm::quat(), PALM_PRIORITY);
} else {
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
}
}
void SkeletonModel::updateJointState(int index) {
if (index < 0 && index >= _jointStates.size()) {
return; // bail
}
JointState& state = _jointStates[index];
const FBXJoint& joint = state.getFBXJoint();
if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(joint.parentIndex);
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (index == geometry.leanJointIndex) {
maybeUpdateLeanRotation(parentState, state);
} else if (index == geometry.neckJointIndex) {
maybeUpdateNeckRotation(parentState, joint, state);
} else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) {
maybeUpdateEyeRotation(parentState, joint, state);
}
}
Model::updateJointState(index);
if (index == _geometry->getFBXGeometry().rootJointIndex) {
state.clearTransformTranslation();
}
}
void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, JointState& state) {
if (!_owningAvatar->isMyAvatar()) {
return;
}
// get the rotation axes in joint space and use them to adjust the rotation
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
glm::quat inverse = glm::inverse(parentState.getRotation() * state.getDefaultRotationInParentFrame());
state.setRotationInConstrainedFrame(
glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), inverse * zAxis)
* glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), inverse * xAxis)
* glm::angleAxis(RADIANS_PER_DEGREE * _owningAvatar->getHead()->getTorsoTwist(), inverse * yAxis)
* state.getFBXJoint().rotation, LEAN_PRIORITY);
}
void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
_owningAvatar->getHead()->getFaceModel().maybeUpdateNeckRotation(parentState, joint, state);
}
void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(this, parentState, joint, state);
}
void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -331,9 +267,11 @@ void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) {
batch._glLineWidth(3.0f);
do {
const FBXJoint& joint = geometry.joints.at(jointIndex);
const JointState& jointState = _jointStates.at(jointIndex);
const JointState& jointState = _rig->getJointState(jointIndex);
glm::vec3 position = _rotation * jointState.getPosition() + _translation;
glm::quat parentRotation = (joint.parentIndex == -1) ? _rotation : _rotation * _jointStates.at(joint.parentIndex).getRotation();
glm::quat parentRotation = (joint.parentIndex == -1) ?
_rotation :
_rotation * _rig->getJointState(joint.parentIndex).getRotation();
float fanScale = directionSize * 0.75f;
Transform transform = Transform();
@ -410,72 +348,6 @@ void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointInde
geometryCache->renderLine(batch, position, pFront, blue, jointLineIDs._front);
}
void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation) {
// this algorithm is from sample code from sixense
const FBXGeometry& geometry = _geometry->getFBXGeometry();
int elbowJointIndex = geometry.joints.at(jointIndex).parentIndex;
if (elbowJointIndex == -1) {
return;
}
int shoulderJointIndex = geometry.joints.at(elbowJointIndex).parentIndex;
glm::vec3 shoulderPosition;
if (!getJointPosition(shoulderJointIndex, shoulderPosition)) {
return;
}
// precomputed lengths
float scale = extractUniformScale(_scale);
float upperArmLength = geometry.joints.at(elbowJointIndex).distanceToParent * scale;
float lowerArmLength = geometry.joints.at(jointIndex).distanceToParent * scale;
// first set wrist position
glm::vec3 wristPosition = position;
glm::vec3 shoulderToWrist = wristPosition - shoulderPosition;
float distanceToWrist = glm::length(shoulderToWrist);
// prevent gimbal lock
if (distanceToWrist > upperArmLength + lowerArmLength - EPSILON) {
distanceToWrist = upperArmLength + lowerArmLength - EPSILON;
shoulderToWrist = glm::normalize(shoulderToWrist) * distanceToWrist;
wristPosition = shoulderPosition + shoulderToWrist;
}
// cosine of angle from upper arm to hand vector
float cosA = (upperArmLength * upperArmLength + distanceToWrist * distanceToWrist - lowerArmLength * lowerArmLength) /
(2 * upperArmLength * distanceToWrist);
float mid = upperArmLength * cosA;
float height = sqrt(upperArmLength * upperArmLength + mid * mid - 2 * upperArmLength * mid * cosA);
// direction of the elbow
glm::vec3 handNormal = glm::cross(rotation * glm::vec3(0.0f, 1.0f, 0.0f), shoulderToWrist); // elbow rotating with wrist
glm::vec3 relaxedNormal = glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), shoulderToWrist); // elbow pointing straight down
const float NORMAL_WEIGHT = 0.5f;
glm::vec3 finalNormal = glm::mix(relaxedNormal, handNormal, NORMAL_WEIGHT);
bool rightHand = (jointIndex == geometry.rightHandJointIndex);
if (rightHand ? (finalNormal.y > 0.0f) : (finalNormal.y < 0.0f)) {
finalNormal.y = 0.0f; // dont allow elbows to point inward (y is vertical axis)
}
glm::vec3 tangent = glm::normalize(glm::cross(shoulderToWrist, finalNormal));
// ik solution
glm::vec3 elbowPosition = shoulderPosition + glm::normalize(shoulderToWrist) * mid - tangent * height;
glm::vec3 forwardVector(rightHand ? -1.0f : 1.0f, 0.0f, 0.0f);
glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition);
JointState& shoulderState = _jointStates[shoulderJointIndex];
shoulderState.setRotationInBindFrame(shoulderRotation, PALM_PRIORITY);
JointState& elbowState = _jointStates[elbowJointIndex];
elbowState.setRotationInBindFrame(rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) * shoulderRotation, PALM_PRIORITY);
JointState& handState = _jointStates[jointIndex];
handState.setRotationInBindFrame(rotation, PALM_PRIORITY);
}
bool SkeletonModel::getLeftHandPosition(glm::vec3& position) const {
return getJointPositionInWorldFrame(getLeftHandJointIndex(), position);
}
@ -528,7 +400,7 @@ bool SkeletonModel::getNeckParentRotationFromDefaultOrientation(glm::quat& neckP
glm::quat worldFrameRotation;
bool success = getJointRotationInWorldFrame(parentIndex, worldFrameRotation);
if (success) {
neckParentRotation = worldFrameRotation * _jointStates[parentIndex].getFBXJoint().inverseDefaultRotation;
neckParentRotation = worldFrameRotation * _rig->getJointState(parentIndex).getFBXJoint().inverseDefaultRotation;
}
return success;
}
@ -573,72 +445,13 @@ glm::vec3 SkeletonModel::getDefaultEyeModelPosition() const {
return _owningAvatar->getScale() * _defaultEyeModelPosition;
}
/// \return offset of hips after foot animation
void SkeletonModel::updateStandingFoot() {
if (_geometry == NULL) {
return;
}
glm::vec3 offset(0.0f);
int leftFootIndex = _geometry->getFBXGeometry().leftToeJointIndex;
int rightFootIndex = _geometry->getFBXGeometry().rightToeJointIndex;
if (leftFootIndex != -1 && rightFootIndex != -1) {
glm::vec3 leftPosition, rightPosition;
getJointPosition(leftFootIndex, leftPosition);
getJointPosition(rightFootIndex, rightPosition);
int lowestFoot = (leftPosition.y < rightPosition.y) ? LEFT_FOOT : RIGHT_FOOT;
const float MIN_STEP_HEIGHT_THRESHOLD = 0.05f;
bool oneFoot = fabsf(leftPosition.y - rightPosition.y) > MIN_STEP_HEIGHT_THRESHOLD;
int currentFoot = oneFoot ? lowestFoot : _standingFoot;
if (_standingFoot == NO_FOOT) {
currentFoot = lowestFoot;
}
if (currentFoot != _standingFoot) {
if (_standingFoot == NO_FOOT) {
// pick the lowest foot
glm::vec3 lowestPosition = (currentFoot == LEFT_FOOT) ? leftPosition : rightPosition;
// we ignore zero length positions which can happen for a few frames until skeleton is fully loaded
if (glm::length(lowestPosition) > 0.0f) {
_standingFoot = currentFoot;
_clampedFootPosition = lowestPosition;
}
} else {
// swap feet
_standingFoot = currentFoot;
glm::vec3 nextPosition = leftPosition;
glm::vec3 prevPosition = rightPosition;
if (_standingFoot == RIGHT_FOOT) {
nextPosition = rightPosition;
prevPosition = leftPosition;
}
glm::vec3 oldOffset = _clampedFootPosition - prevPosition;
_clampedFootPosition = oldOffset + nextPosition;
offset = _clampedFootPosition - nextPosition;
}
} else {
glm::vec3 nextPosition = (_standingFoot == LEFT_FOOT) ? leftPosition : rightPosition;
offset = _clampedFootPosition - nextPosition;
}
// clamp the offset to not exceed some max distance
const float MAX_STEP_OFFSET = 1.0f;
float stepDistance = glm::length(offset);
if (stepDistance > MAX_STEP_OFFSET) {
offset *= (MAX_STEP_OFFSET / stepDistance);
}
}
_standingOffset = offset;
}
float DENSITY_OF_WATER = 1000.0f; // kg/m^3
float MIN_JOINT_MASS = 1.0f;
float VERY_BIG_MASS = 1.0e6f;
// virtual
void SkeletonModel::buildShapes() {
if (_geometry == NULL || _jointStates.isEmpty()) {
if (_geometry == NULL || _rig->jointStatesEmpty()) {
return;
}
@ -647,46 +460,12 @@ void SkeletonModel::buildShapes() {
// rootJointIndex == -1 if the avatar model has no skeleton
return;
}
float uniformScale = extractUniformScale(_scale);
const int numStates = _jointStates.size();
for (int i = 0; i < numStates; i++) {
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
float radius = uniformScale * joint.boneRadius;
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
Shape::Type type = joint.shapeType;
int parentIndex = joint.parentIndex;
if (parentIndex == -1 || radius < EPSILON) {
type = INVALID_SHAPE;
} else if (type == CAPSULE_SHAPE && halfHeight < EPSILON) {
// this shape is forced to be a sphere
type = SPHERE_SHAPE;
}
Shape* shape = NULL;
if (type == SPHERE_SHAPE) {
shape = new SphereShape(radius);
shape->setEntity(this);
} else if (type == CAPSULE_SHAPE) {
assert(parentIndex != -1);
shape = new CapsuleShape(radius, halfHeight);
shape->setEntity(this);
}
if (shape && parentIndex != -1) {
// always disable collisions between joint and its parent
disableCollisions(i, parentIndex);
}
_shapes.push_back(shape);
}
// This method moves the shapes to their default positions in Model frame.
computeBoundingShape(geometry);
}
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
// compute default joint transforms
int numStates = _jointStates.size();
assert(numStates == _shapes.size());
int numStates = _rig->getJointStateCount();
QVector<glm::mat4> transforms;
transforms.fill(glm::mat4(), numStates);
@ -696,131 +475,63 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
totalExtents.addPoint(glm::vec3(0.0f));
for (int i = 0; i < numStates; i++) {
// compute the default transform of this joint
JointState& state = _jointStates[i];
const JointState& state = _rig->getJointState(i);
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
transforms[i] = _jointStates[i].getTransform();
transforms[i] = _rig->getJointTransform(i);
} else {
glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation;
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
* joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform;
}
// Each joint contributes its point to the bounding box
// Each joint contributes a sphere at its position
glm::vec3 axis(joint.boneRadius);
glm::vec3 jointPosition = extractTranslation(transforms[i]);
totalExtents.addPoint(jointPosition);
Shape* shape = _shapes[i];
if (!shape) {
continue;
}
// Each joint with a shape contributes to the totalExtents: a box
// that contains the sphere centered at the end of the joint with radius of the bone.
// TODO: skip hand and arm shapes for bounding box calculation
int type = shape->getType();
if (type == CAPSULE_SHAPE) {
// add the two furthest surface points of the capsule
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
float radius = capsule->getRadius();
glm::vec3 axis(radius);
Extents shapeExtents;
shapeExtents.reset();
shapeExtents.addPoint(jointPosition + axis);
shapeExtents.addPoint(jointPosition - axis);
totalExtents.addExtents(shapeExtents);
} else if (type == SPHERE_SHAPE) {
float radius = shape->getBoundingRadius();
glm::vec3 axis(radius);
Extents shapeExtents;
shapeExtents.reset();
shapeExtents.addPoint(jointPosition + axis);
shapeExtents.addPoint(jointPosition - axis);
totalExtents.addExtents(shapeExtents);
}
totalExtents.addPoint(jointPosition + axis);
totalExtents.addPoint(jointPosition - axis);
}
// compute bounding shape parameters
// NOTE: we assume that the longest side of totalExtents is the yAxis...
glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
// ... and assume the radius is half the RMS of the X and Z sides:
float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
_boundingShape.setRadius(capsuleRadius);
_boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
_boundingCapsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
_boundingCapsuleHeight = diagonal.y - 2.0f * _boundingCapsuleRadius;
glm::vec3 rootPosition = _jointStates[geometry.rootJointIndex].getPosition();
_boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition;
glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition();
_boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition;
_boundingRadius = 0.5f * glm::length(diagonal);
}
void SkeletonModel::resetShapePositionsToDefaultPose() {
// DEBUG method.
// Moves shapes to the joint default locations for debug visibility into
// how the bounding shape is computed.
if (!_geometry || _shapes.isEmpty()) {
// geometry or joints have not yet been created
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.joints.isEmpty()) {
return;
}
// The shapes are moved to their default positions in computeBoundingShape().
computeBoundingShape(geometry);
// Then we move them into world frame for rendering at the Model's location.
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (shape) {
shape->setTranslation(_translation + _rotation * shape->getTranslation());
shape->setRotation(_rotation * shape->getRotation());
}
}
_boundingShape.setTranslation(_translation + _rotation * _boundingShapeLocalOffset);
_boundingShape.setRotation(_rotation);
}
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
const int BALL_SUBDIVISIONS = 10;
if (_shapes.isEmpty()) {
// the bounding shape has not been properly computed
// so no need to render it
return;
}
auto geometryCache = DependencyManager::get<GeometryCache>();
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
Transform transform; // = Transform();
// draw a blue sphere at the capsule end point
glm::vec3 endPoint;
_boundingShape.getEndPoint(endPoint);
endPoint = endPoint + _translation;
transform.setTranslation(endPoint);
// draw a blue sphere at the capsule top point
glm::vec3 topPoint = _translation + _boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * glm::vec3(0.0f, 1.0f, 0.0f);
transform.setTranslation(topPoint);
batch.setModelTransform(transform);
deferredLighting->bindSimpleProgram(batch);
geometryCache->renderSphere(batch, _boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
glm::vec4(0.6f, 0.6f, 0.8f, alpha));
// draw a yellow sphere at the capsule start point
glm::vec3 startPoint;
_boundingShape.getStartPoint(startPoint);
startPoint = startPoint + _translation;
glm::vec3 axis = endPoint - startPoint;
transform.setTranslation(startPoint);
// draw a yellow sphere at the capsule bottom point
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, -_boundingCapsuleHeight, 0.0f);
glm::vec3 axis = topPoint - bottomPoint;
transform.setTranslation(bottomPoint);
batch.setModelTransform(transform);
deferredLighting->bindSimpleProgram(batch);
geometryCache->renderSphere(batch, _boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
glm::vec4(0.8f, 0.8f, 0.6f, alpha));
// draw a green cylinder between the two points
glm::vec3 origin(0.0f);
Avatar::renderJointConnectingCone(batch, origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius(),
Avatar::renderJointConnectingCone(batch, origin, axis, _boundingCapsuleRadius, _boundingCapsuleRadius,
glm::vec4(0.6f, 0.8f, 0.6f, alpha));
}
@ -828,56 +539,5 @@ bool SkeletonModel::hasSkeleton() {
return isActive() ? _geometry->getFBXGeometry().rootJointIndex != -1 : false;
}
void SkeletonModel::initHeadBones() {
_headBones.clear();
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
const int neckJointIndex = fbxGeometry.neckJointIndex;
std::queue<int> q;
q.push(neckJointIndex);
_headBones.push_back(neckJointIndex);
// fbxJoints only hold links to parents not children, so we have to do a bit of extra work here.
while (q.size() > 0) {
int jointIndex = q.front();
for (int i = 0; i < fbxGeometry.joints.size(); i++) {
const FBXJoint& fbxJoint = fbxGeometry.joints[i];
if (jointIndex == fbxJoint.parentIndex) {
_headBones.push_back(i);
q.push(i);
}
}
q.pop();
}
}
void SkeletonModel::invalidateHeadBones() {
_headBones.clear();
}
void SkeletonModel::cauterizeHead() {
if (isActive()) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const int neckJointIndex = geometry.neckJointIndex;
if (neckJointIndex > 0 && neckJointIndex < _jointStates.size()) {
// lazy init of headBones
if (_headBones.size() == 0) {
initHeadBones();
}
// preserve the translation for the neck
glm::vec4 trans = _jointStates[neckJointIndex].getTransform()[3];
glm::vec4 zero(0, 0, 0, 0);
for (const int &i : _headBones) {
JointState& joint = _jointStates[i];
glm::mat4 newXform(zero, zero, zero, trans);
joint.setTransform(newXform);
joint.setVisibleTransform(newXform);
}
}
}
}
void SkeletonModel::onInvalidate() {
invalidateHeadBones();
}

View file

@ -13,7 +13,6 @@
#define hifi_SkeletonModel_h
#include <CapsuleShape.h>
#include <Model.h>
class Avatar;
@ -22,72 +21,69 @@ class MuscleConstraint;
/// A skeleton loaded from a model.
class SkeletonModel : public Model {
Q_OBJECT
public:
SkeletonModel(Avatar* owningAvatar, QObject* parent = NULL);
SkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr, RigPointer rig = nullptr);
~SkeletonModel();
virtual void initJointStates(QVector<JointState> states);
void simulate(float deltaTime, bool fullUpdate = true);
/// \param jointIndex index of hand joint
/// \param shapes[out] list in which is stored pointers to hand shapes
void getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const;
virtual void simulate(float deltaTime, bool fullUpdate = true);
virtual void updateRig(float deltaTime, glm::mat4 parentTransform);
void renderIKConstraints(gpu::Batch& batch);
/// Returns the index of the left hand joint, or -1 if not found.
int getLeftHandJointIndex() const { return isActive() ? _geometry->getFBXGeometry().leftHandJointIndex : -1; }
/// Returns the index of the right hand joint, or -1 if not found.
int getRightHandJointIndex() const { return isActive() ? _geometry->getFBXGeometry().rightHandJointIndex : -1; }
/// Retrieve the position of the left hand
/// \return true whether or not the position was found
bool getLeftHandPosition(glm::vec3& position) const;
/// Retrieve the position of the right hand
/// \return true whether or not the position was found
bool getRightHandPosition(glm::vec3& position) const;
/// Restores some fraction of the default position of the left hand.
/// \param fraction the fraction of the default position to restore
/// \return whether or not the left hand joint was found
bool restoreLeftHandPosition(float fraction = 1.0f, float priority = 1.0f);
/// Gets the position of the left shoulder.
/// \return whether or not the left shoulder joint was found
bool getLeftShoulderPosition(glm::vec3& position) const;
/// Returns the extended length from the left hand to its last free ancestor.
float getLeftArmLength() const;
/// Restores some fraction of the default position of the right hand.
/// \param fraction the fraction of the default position to restore
/// \return whether or not the right hand joint was found
bool restoreRightHandPosition(float fraction = 1.0f, float priority = 1.0f);
/// Gets the position of the right shoulder.
/// \return whether or not the right shoulder joint was found
bool getRightShoulderPosition(glm::vec3& position) const;
/// Returns the extended length from the right hand to its first free ancestor.
float getRightArmLength() const;
/// Returns the position of the head joint.
/// \return whether or not the head was found
bool getHeadPosition(glm::vec3& headPosition) const;
/// Returns the position of the neck joint.
/// \return whether or not the neck was found
bool getNeckPosition(glm::vec3& neckPosition) const;
/// Returns the rotation of the neck joint's parent from default orientation
/// \return whether or not the neck was found
bool getNeckParentRotationFromDefaultOrientation(glm::quat& neckParentRotation) const;
/// Retrieve the positions of up to two eye meshes.
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
@ -96,25 +92,16 @@ public:
/// \return whether or not the head was found.
glm::vec3 getDefaultEyeModelPosition() const;
/// skeleton offset caused by moving feet
void updateStandingFoot();
const glm::vec3& getStandingOffset() const { return _standingOffset; }
void computeBoundingShape(const FBXGeometry& geometry);
void renderBoundingCollisionShapes(gpu::Batch& batch, float alpha);
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
const glm::vec3 getBoundingShapeOffset() const { return _boundingShapeLocalOffset; }
void resetShapePositionsToDefaultPose(); // DEBUG method
float getBoundingCapsuleRadius() const { return _boundingCapsuleRadius; }
float getBoundingCapsuleHeight() const { return _boundingCapsuleHeight; }
const glm::vec3 getBoundingCapsuleOffset() const { return _boundingCapsuleLocalOffset; }
bool hasSkeleton();
float getHeadClipDistance() const { return _headClipDistance; }
void setIsFirstPerson(bool value) { _isFirstPerson = value; }
bool getIsFirstPerson() const { return _isFirstPerson; }
virtual void onInvalidate() override;
signals:
@ -128,26 +115,14 @@ protected:
/// \param jointIndex index of joint in model
/// \param position position of joint in model-frame
void applyHandPosition(int jointIndex, const glm::vec3& position);
void applyPalmData(int jointIndex, PalmData& palm);
/// Updates the state of the joint at the specified index.
virtual void updateJointState(int index);
void maybeUpdateLeanRotation(const JointState& parentState, JointState& state);
void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
void cauterizeHead();
void initHeadBones();
void invalidateHeadBones();
private:
void renderJointConstraints(gpu::Batch& batch, int jointIndex);
void renderOrientationDirections(gpu::Batch& batch, int jointIndex,
void renderOrientationDirections(gpu::Batch& batch, int jointIndex,
glm::vec3 position, const glm::quat& orientation, float size);
struct OrientationLineIDs {
int _up;
int _front;
@ -156,27 +131,17 @@ private:
QHash<int, OrientationLineIDs> _jointOrientationLines;
int _triangleFanID;
/// \param jointIndex index of joint in model
/// \param position position of joint in model-frame
/// \param rotation rotation of joint in model-frame
void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation);
bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
Avatar* _owningAvatar;
CapsuleShape _boundingShape;
glm::vec3 _boundingShapeLocalOffset;
glm::vec3 _boundingCapsuleLocalOffset;
float _boundingCapsuleRadius;
float _boundingCapsuleHeight;
glm::vec3 _defaultEyeModelPosition;
int _standingFoot;
glm::vec3 _standingOffset;
glm::vec3 _clampedFootPosition;
float _headClipDistance; // Near clip distance to use if no separate head model
bool _isFirstPerson;
std::vector<int> _headBones;
};
#endif // hifi_SkeletonModel_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,244 @@
// 3DConnexion.h
// hifi
//
// Created by Marcel Verhagen on 09-06-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_ConnexionClient_h
#define hifi_ConnexionClient_h
#include <qobject.h>
#include <qlibrary.h>
#include "InterfaceLogging.h"
#include "Application.h"
#include "ui/UserInputMapper.h"
#ifndef HAVE_CONNEXIONCLIENT
class ConnexionClient : public QObject {
Q_OBJECT
public:
static ConnexionClient& getInstance();
static void init() {};
static void destroy() {};
static bool Is3dmouseAttached() { return false; };
public slots:
void toggleConnexion(bool shouldEnable) {};
};
#endif // NOT_HAVE_CONNEXIONCLIENT
#ifdef HAVE_CONNEXIONCLIENT
// the windows connexion rawinput
#ifdef _WIN32
#include "I3dMouseParams.h"
#include <QAbstractNativeEventFilter>
#include <QAbstractEventDispatcher>
#include <Winsock2.h>
#include <windows.h>
// windows rawinput parameters
class MouseParameters : public I3dMouseParam {
public:
MouseParameters();
~MouseParameters();
// I3dmouseSensor interface
bool IsPanZoom() const;
bool IsRotate() const;
Speed GetSpeed() const;
void SetPanZoom(bool isPanZoom);
void SetRotate(bool isRotate);
void SetSpeed(Speed speed);
// I3dmouseNavigation interface
Navigation GetNavigationMode() const;
Pivot GetPivotMode() const;
PivotVisibility GetPivotVisibility() const;
bool IsLockHorizon() const;
void SetLockHorizon(bool bOn);
void SetNavigationMode(Navigation navigation);
void SetPivotMode(Pivot pivot);
void SetPivotVisibility(PivotVisibility visibility);
static bool Is3dmouseAttached();
private:
MouseParameters(const MouseParameters&);
const MouseParameters& operator = (const MouseParameters&);
Navigation fNavigation;
Pivot fPivot;
PivotVisibility fPivotVisibility;
bool fIsLockHorizon;
bool fIsPanZoom;
bool fIsRotate;
Speed fSpeed;
};
class ConnexionClient : public QObject, public QAbstractNativeEventFilter {
Q_OBJECT
public:
ConnexionClient();
~ConnexionClient();
static ConnexionClient& getInstance();
ConnexionClient* client;
static void init();
static void destroy();
static bool Is3dmouseAttached();
I3dMouseParam& MouseParams();
const I3dMouseParam& MouseParams() const;
virtual void Move3d(HANDLE device, std::vector<float>& motionData);
virtual void On3dmouseKeyDown(HANDLE device, int virtualKeyCode);
virtual void On3dmouseKeyUp(HANDLE device, int virtualKeyCode);
virtual bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) Q_DECL_OVERRIDE
{
MSG* msg = static_cast< MSG * >(message);
return ConnexionClient::RawInputEventFilter(message, result);
}
public slots:
void toggleConnexion(bool shouldEnable);
signals:
void Move3d(std::vector<float>& motionData);
void On3dmouseKeyDown(int virtualKeyCode);
void On3dmouseKeyUp(int virtualKeyCode);
private:
bool InitializeRawInput(HWND hwndTarget);
static bool RawInputEventFilter(void* msg, long* result);
void OnRawInput(UINT nInputCode, HRAWINPUT hRawInput);
UINT GetRawInputBuffer(PRAWINPUT pData, PUINT pcbSize, UINT cbSizeHeader);
bool TranslateRawInputData(UINT nInputCode, PRAWINPUT pRawInput);
void On3dmouseInput();
class TInputData {
public:
TInputData() : fAxes(6) {}
bool IsZero() {
return (0.0f == fAxes[0] && 0.0f == fAxes[1] && 0.0f == fAxes[2] &&
0.0f == fAxes[3] && 0.0f == fAxes[4] && 0.0f == fAxes[5]);
}
int fTimeToLive; // For telling if the device was unplugged while sending data
bool fIsDirty;
std::vector<float> fAxes;
};
HWND fWindow;
// Data cache to handle multiple rawinput devices
std::map< HANDLE, TInputData> fDevice2Data;
std::map< HANDLE, unsigned long> fDevice2Keystate;
// 3dmouse parameters
MouseParameters f3dMouseParams; // Rotate, Pan Zoom etc.
// use to calculate distance traveled since last event
DWORD fLast3dmouseInputTime;
};
// the osx connexion api
#else
#include <glm/glm.hpp>
#include "3DconnexionClient/ConnexionClientAPI.h"
class ConnexionClient : public QObject {
Q_OBJECT
public:
static ConnexionClient& getInstance();
static bool Is3dmouseAttached();
static void init();
static void destroy();
public slots:
void toggleConnexion(bool shouldEnable);
};
#endif // __APPLE__
#endif // HAVE_CONNEXIONCLIENT
// connnects to the userinputmapper
class ConnexionData : public QObject {
Q_OBJECT
public:
static ConnexionData& getInstance();
ConnexionData();
enum PositionChannel {
POSITION_AXIS_X_POS = 1,
POSITION_AXIS_X_NEG = 2,
POSITION_AXIS_Y_POS = 3,
POSITION_AXIS_Y_NEG = 4,
POSITION_AXIS_Z_POS = 5,
POSITION_AXIS_Z_NEG = 6,
ROTATION_AXIS_X_POS = 7,
ROTATION_AXIS_X_NEG = 8,
ROTATION_AXIS_Y_POS = 9,
ROTATION_AXIS_Y_NEG = 10,
ROTATION_AXIS_Z_POS = 11,
ROTATION_AXIS_Z_NEG = 12
};
enum ButtonChannel {
BUTTON_1 = 1,
BUTTON_2 = 2,
BUTTON_3 = 3
};
typedef std::unordered_set<int> ButtonPressedMap;
typedef std::map<int, float> AxisStateMap;
float getButton(int channel) const;
float getAxis(int channel) const;
UserInputMapper::Input makeInput(ConnexionData::PositionChannel axis);
UserInputMapper::Input makeInput(ConnexionData::ButtonChannel button);
void registerToUserInputMapper(UserInputMapper& mapper);
void assignDefaultInputMapping(UserInputMapper& mapper);
void update();
void focusOutEvent();
int getDeviceID() { return _deviceID; }
void setDeviceID(int deviceID) { _deviceID = deviceID; }
QString _name;
glm::vec3 cc_position;
glm::vec3 cc_rotation;
int clientId;
void setButton(int lastButtonState);
void handleAxisEvent();
protected:
int _deviceID = 0;
ButtonPressedMap _buttonPressedMap;
AxisStateMap _axisStateMap;
};
#endif // defined(hifi_ConnexionClient_h)

View file

@ -564,6 +564,13 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
eyeCoefficients[1] = _filteredEyeBlinks[1];
}
// Couple eyelid values if configured - use the most "open" value for both
if (Menu::getInstance()->isOptionChecked(MenuOption::CoupleEyelids)) {
float eyeCoefficient = std::min(eyeCoefficients[0], eyeCoefficients[1]);
eyeCoefficients[0] = eyeCoefficient;
eyeCoefficients[1] = eyeCoefficient;
}
// Use EyeBlink values to control both EyeBlink and EyeOpen
if (eyeCoefficients[0] > 0) {
_coefficients[_leftBlinkIndex] = eyeCoefficients[0];

View file

@ -91,13 +91,12 @@ private:
int _leftBlinkIndex;
int _rightBlinkIndex;
int _leftEyeOpenIndex;
int _rightEyeOpenIndex;
int _leftEyeDownIndex;
int _rightEyeDownIndex;
int _leftEyeInIndex;
int _rightEyeInIndex;
int _leftEyeOpenIndex;
int _rightEyeOpenIndex;
int _browDownLeftIndex;
int _browDownRightIndex;

View file

@ -47,7 +47,6 @@ public:
bool isMuted() const { return _isMuted; }
void setIsMuted(bool isMuted) { _isMuted = isMuted; }
void toggleMute();
static float getEyeDeflection() { return _eyeDeflection.get(); }
static void setEyeDeflection(float eyeDeflection);
@ -57,6 +56,8 @@ signals:
public slots:
virtual void setEnabled(bool enabled) = 0;
void toggleMute();
bool getMuted() { return _isMuted; }
protected:
virtual ~FaceTracker() {};

View file

@ -11,16 +11,16 @@
//
#include "OculusManager.h"
#include <gpu/GPUConfig.h>
#include <glm/glm.hpp>
#include <QDesktopWidget>
#include <QGuiApplication>
#include <gpu/GPUConfig.h>
#include <QScreen>
#include <CursorManager.h>
#include <QOpenGLTimerQuery>
#include <QGLWidget>
#include <CursorManager.h>
#include <glm/glm.hpp>
#include <avatar/AvatarManager.h>
#include <avatar/MyAvatar.h>

View file

@ -9,13 +9,14 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TV3DManager.h"
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "gpu/GLBackend.h"
#include "Application.h"
#include <RenderArgs.h>
#include "TV3DManager.h"
#include "Application.h"
#include "Menu.h"
int TV3DManager::_screenWidth = 1;
@ -63,6 +64,7 @@ void TV3DManager::setFrustum(Camera& whichCamera) {
}
void TV3DManager::configureCamera(Camera& whichCamera, int screenWidth, int screenHeight) {
#ifdef THIS_CURRENTLY_BROKEN_WAITING_FOR_DISPLAY_PLUGINS
if (screenHeight == 0) {
screenHeight = 1; // prevent divide by 0
}
@ -72,6 +74,7 @@ void TV3DManager::configureCamera(Camera& whichCamera, int screenWidth, int scre
setFrustum(whichCamera);
glViewport (0, 0, _screenWidth, _screenHeight); // sets drawing viewport
#endif
}
void TV3DManager::display(RenderArgs* renderArgs, Camera& whichCamera) {

View file

@ -17,6 +17,7 @@
#include <glm/glm.hpp>
class Camera;
class RenderArgs;
struct eyeFrustum {
double left;

View file

@ -57,7 +57,7 @@ void OctreePacketProcessor::processPacket(QSharedPointer<NLPacket> packet, Share
if (piggybackBytes) {
// construct a new packet from the piggybacked one
std::unique_ptr<char> buffer = std::unique_ptr<char>(new char[piggybackBytes]);
auto buffer = std::unique_ptr<char[]>(new char[piggybackBytes]);
memcpy(buffer.get(), packet->getPayload() + statsMessageLength, piggybackBytes);
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggybackBytes, packet->getSenderSockAddr());

View file

@ -24,7 +24,8 @@
ControllerScriptingInterface::ControllerScriptingInterface() :
_mouseCaptured(false),
_touchCaptured(false),
_wheelCaptured(false)
_wheelCaptured(false),
_actionsCaptured(false)
{
}

View file

@ -0,0 +1,26 @@
//
// DialogsManagerScriptingInterface.cpp
// interface/src/scripting
//
// Created by Zander Otavka 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 "DialogsManagerScriptingInterface.h"
#include <DependencyManager.h>
#include "ui/DialogsManager.h"
DialogsManagerScriptingInterface::DialogsManagerScriptingInterface() {
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::addressBarToggled,
this, &DialogsManagerScriptingInterface::addressBarToggled);
}
void DialogsManagerScriptingInterface::toggleAddressBar() {
QMetaObject::invokeMethod(DependencyManager::get<DialogsManager>().data(),
"toggleAddressBar", Qt::QueuedConnection);
}

View file

@ -0,0 +1,29 @@
//
// DialogsManagerScriptingInterface.h
// interface/src/scripting
//
// Created by Zander Otavka 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
//
#ifndef hifi_DialogsManagerScriptInterface_h
#define hifi_DialogsManagerScriptInterface_h
#include <QObject>
class DialogsManagerScriptingInterface : public QObject {
Q_OBJECT
public:
DialogsManagerScriptingInterface();
public slots:
void toggleAddressBar();
signals:
void addressBarToggled();
};
#endif

View file

@ -57,7 +57,7 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid
} else {
auto dialogWidget = new QDialog(Application::getInstance()->getWindow(), Qt::Window);
dialogWidget->setWindowTitle(title);
dialogWidget->setMinimumSize(width, height);
dialogWidget->resize(width, height);
connect(dialogWidget, &QDialog::finished, this, &WebWindowClass::hasClosed);
auto layout = new QVBoxLayout(dialogWidget);

View file

@ -156,7 +156,7 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo
buttons->addWidget(remove);
connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle()));
_stop->connect(_handle.data(), SIGNAL(runningChanged(bool)), SLOT(setEnabled(bool)));
_stop->connect(_handle.get(), SIGNAL(runningChanged(bool)), SLOT(setEnabled(bool)));
_stop->setEnabled(_handle->isRunning());
}

View file

@ -495,7 +495,7 @@ void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) {
glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * palmData->getFingerDirection();
// Get the angles, scaled between (-0.5,0.5)
float xAngle = (atan2(direction.z, direction.x) + PI_OVER_TWO);
float xAngle = (atan2f(direction.z, direction.x) + PI_OVER_TWO);
float yAngle = 0.5f - ((atan2f(direction.z, direction.y) + (float)PI_OVER_TWO));
// Get the pixel range over which the xAngle and yAngle are scaled

View file

@ -117,7 +117,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
batch.setProjectionTransform(mat4());
batch.setModelTransform(Transform());
batch.setViewTransform(Transform());
batch._glBindTexture(GL_TEXTURE_2D, _uiTexture);
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _uiTexture);
geometryCache->renderUnitQuad(batch, glm::vec4(1));
}

View file

@ -125,8 +125,10 @@ void AudioStatsDialog::renderStats() {
audioInputBufferLatency = (double)_stats->getAudioInputMsecsReadStats().getWindowAverage();
inputRingBufferLatency = (double)_stats->getInputRungBufferMsecsAvailableStats().getWindowAverage();
networkRoundtripLatency = (double) audioMixerNodePointer->getPingMs();
mixerRingBufferLatency = (double)_stats->getMixerAvatarStreamStats()._framesAvailableAverage * AudioConstants::NETWORK_FRAME_MSECS;
outputRingBufferLatency = (double)downstreamAudioStreamStats._framesAvailableAverage * AudioConstants::NETWORK_FRAME_MSECS;
mixerRingBufferLatency = (double)_stats->getMixerAvatarStreamStats()._framesAvailableAverage *
(double)AudioConstants::NETWORK_FRAME_MSECS;
outputRingBufferLatency = (double)downstreamAudioStreamStats._framesAvailableAverage *
(double)AudioConstants::NETWORK_FRAME_MSECS;
audioOutputBufferLatency = (double)_stats->getAudioOutputMsecsUnplayedStats().getWindowAverage();
}

View file

@ -36,6 +36,7 @@
void DialogsManager::toggleAddressBar() {
AddressBarDialog::toggle();
emit addressBarToggled();
}
void DialogsManager::toggleDiskCacheEditor() {

View file

@ -72,6 +72,9 @@ public slots:
// Application Update
void showUpdateDialog();
signals:
void addressBarToggled();
private slots:
void toggleToolWindow();
void hmdToolsClosed();

View file

@ -13,19 +13,25 @@
#include <QScriptValue>
#include <DeferredLightingEffect.h>
#include <DependencyManager.h>
#include <GeometryCache.h>
#include <gpu/Batch.h>
#include <GLMHelpers.h>
#include "Application.h"
#include "GeometryUtil.h"
QString const BillboardOverlay::TYPE = "billboard";
BillboardOverlay::BillboardOverlay() {
_isLoaded = false;
}
BillboardOverlay::BillboardOverlay(const BillboardOverlay* billboardOverlay) :
Planar3DOverlay(billboardOverlay),
PanelAttachable(billboardOverlay),
_url(billboardOverlay->_url),
_texture(billboardOverlay->_texture),
_fromImage(billboardOverlay->_fromImage),
@ -33,6 +39,19 @@ BillboardOverlay::BillboardOverlay(const BillboardOverlay* billboardOverlay) :
{
}
void BillboardOverlay::setTransforms(Transform& transform) {
PanelAttachable::setTransforms(transform);
if (_isFacingAvatar) {
glm::quat rotation = Application::getInstance()->getCamera()->getOrientation();
rotation *= glm::angleAxis(glm::pi<float>(), IDENTITY_UP);
setRotation(rotation);
}
}
void BillboardOverlay::update(float deltatime) {
setTransforms(_transform);
}
void BillboardOverlay::render(RenderArgs* args) {
if (!_texture) {
_isLoaded = true;
@ -43,15 +62,8 @@ void BillboardOverlay::render(RenderArgs* args) {
return;
}
glm::quat rotation;
if (_isFacingAvatar) {
// rotate about vertical to face the camera
rotation = args->_viewFrustum->getOrientation();
rotation *= glm::angleAxis(glm::pi<float>(), IDENTITY_UP);
rotation *= getRotation();
} else {
rotation = getRotation();
}
Q_ASSERT(args->_batch);
auto batch = args->_batch;
float imageWidth = _texture->getWidth();
float imageHeight = _texture->getHeight();
@ -86,25 +98,25 @@ void BillboardOverlay::render(RenderArgs* args) {
xColor color = getColor();
float alpha = getAlpha();
auto batch = args->_batch;
setTransforms(_transform);
Transform transform = _transform;
transform.postScale(glm::vec3(getDimensions(), 1.0f));
if (batch) {
Transform transform = _transform;
transform.postScale(glm::vec3(getDimensions(), 1.0f));
transform.setRotation(rotation);
batch->setModelTransform(transform);
batch->setResourceTexture(0, _texture->getGPUTexture());
DependencyManager::get<GeometryCache>()->renderQuad(*batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight,
glm::vec4(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha));
batch->setModelTransform(transform);
batch->setResourceTexture(0, _texture->getGPUTexture());
batch->setResourceTexture(0, args->_whiteTexture); // restore default white color after me
}
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(*batch, true, true, false, true);
DependencyManager::get<GeometryCache>()->renderQuad(
*batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight,
glm::vec4(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha)
);
batch->setResourceTexture(0, args->_whiteTexture); // restore default white color after me
}
void BillboardOverlay::setProperties(const QScriptValue &properties) {
Planar3DOverlay::setProperties(properties);
PanelAttachable::setProperties(properties);
QScriptValue urlValue = properties.property("url");
if (urlValue.isValid()) {
@ -161,7 +173,14 @@ QScriptValue BillboardOverlay::getProperty(const QString& property) {
if (property == "isFacingAvatar") {
return _isFacingAvatar;
}
if (property == "offsetPosition") {
return vec3toScriptValue(_scriptEngine, getOffsetPosition());
}
QScriptValue value = PanelAttachable::getProperty(_scriptEngine, property);
if (value.isValid()) {
return value;
}
return Planar3DOverlay::getProperty(property);
}
@ -175,15 +194,10 @@ void BillboardOverlay::setBillboardURL(const QString& url) {
}
bool BillboardOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) {
float& distance, BoxFace& face) {
if (_texture && _texture->isLoaded()) {
glm::quat rotation = getRotation();
if (_isFacingAvatar) {
// rotate about vertical to face the camera
rotation = Application::getInstance()->getCamera()->getRotation();
rotation *= glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 1.0f, 0.0f));
}
// Make sure position and rotation is updated.
setTransforms(_transform);
// Produce the dimensions of the billboard based on the image's aspect ratio and the overlay's scale.
bool isNull = _fromImage.isNull();
@ -192,7 +206,7 @@ bool BillboardOverlay::findRayIntersection(const glm::vec3& origin, const glm::v
float maxSize = glm::max(width, height);
glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize);
return findRayRectangleIntersection(origin, direction, rotation, getPosition(), dimensions, distance);
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), dimensions, distance);
}
return false;

View file

@ -15,15 +15,21 @@
#include <TextureCache.h>
#include "Planar3DOverlay.h"
#include "PanelAttachable.h"
class BillboardOverlay : public Planar3DOverlay {
class BillboardOverlay : public Planar3DOverlay, public PanelAttachable {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
BillboardOverlay();
BillboardOverlay(const BillboardOverlay* billboardOverlay);
virtual void render(RenderArgs* args);
virtual void update(float deltatime);
// setters
void setURL(const QString& url);
void setIsFacingAvatar(bool isFacingAvatar) { _isFacingAvatar = isFacingAvatar; }
@ -36,9 +42,12 @@ public:
virtual BillboardOverlay* createClone() const;
protected:
virtual void setTransforms(Transform& transform);
private:
void setBillboardURL(const QString& url);
QString _url;
NetworkTexturePointer _texture;

View file

@ -15,6 +15,8 @@
#include <RegisteredMetaTypes.h>
QString const Circle3DOverlay::TYPE = "circle3d";
Circle3DOverlay::Circle3DOverlay() :
_startAt(0.0f),
_endAt(360.0f),
@ -119,19 +121,21 @@ void Circle3DOverlay::render(RenderArgs* args) {
float angle = startAt;
float angleInRadians = glm::radians(angle);
glm::vec2 firstInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius);
glm::vec2 firstOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius);
points << firstInnerPoint << firstOuterPoint;
glm::vec2 mostRecentInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius);
glm::vec2 mostRecentOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius);
while (angle < endAt) {
angleInRadians = glm::radians(angle);
glm::vec2 thisInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius);
glm::vec2 thisOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius);
points << thisOuterPoint << thisInnerPoint;
points << mostRecentInnerPoint << mostRecentOuterPoint << thisOuterPoint; // first triangle
points << mostRecentInnerPoint << thisInnerPoint << thisOuterPoint; // second triangle
angle += SLICE_ANGLE;
mostRecentInnerPoint = thisInnerPoint;
mostRecentOuterPoint = thisOuterPoint;
}
// get the last slice portion....
@ -139,13 +143,14 @@ void Circle3DOverlay::render(RenderArgs* args) {
angleInRadians = glm::radians(angle);
glm::vec2 lastInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius);
glm::vec2 lastOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius);
points << lastOuterPoint << lastInnerPoint;
points << mostRecentInnerPoint << mostRecentOuterPoint << lastOuterPoint; // first triangle
points << mostRecentInnerPoint << lastInnerPoint << lastOuterPoint; // second triangle
geometryCache->updateVertices(_quadVerticesID, points, color);
}
geometryCache->renderVertices(batch, gpu::QUAD_STRIP, _quadVerticesID);
geometryCache->renderVertices(batch, gpu::TRIANGLES, _quadVerticesID);
} else {
if (_lineVerticesID == GeometryCache::UNKNOWN_ID) {

View file

@ -18,6 +18,9 @@ class Circle3DOverlay : public Planar3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Circle3DOverlay();
Circle3DOverlay(const Circle3DOverlay* circle3DOverlay);

View file

@ -19,6 +19,8 @@
#include <GeometryCache.h>
#include <DependencyManager.h>
QString const Cube3DOverlay::TYPE = "cube";
Cube3DOverlay::Cube3DOverlay(const Cube3DOverlay* cube3DOverlay) :
Volume3DOverlay(cube3DOverlay)
{
@ -36,7 +38,6 @@ void Cube3DOverlay::render(RenderArgs* args) {
// TODO: handle registration point??
glm::vec3 position = getPosition();
glm::vec3 center = getCenter();
glm::vec3 dimensions = getDimensions();
glm::quat rotation = getRotation();

View file

@ -17,6 +17,9 @@ class Cube3DOverlay : public Volume3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Cube3DOverlay() {}
Cube3DOverlay(const Cube3DOverlay* cube3DOverlay);

View file

@ -0,0 +1,197 @@
//
// FloatingUIPanel.cpp
// interface/src/ui/overlays
//
// Created by Zander Otavka on 7/2/15.
// 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 "FloatingUIPanel.h"
#include <QVariant>
#include <RegisteredMetaTypes.h>
#include <DependencyManager.h>
#include "avatar/AvatarManager.h"
#include "avatar/MyAvatar.h"
#include "Application.h"
#include "Base3DOverlay.h"
std::function<glm::vec3()> const FloatingUIPanel::AVATAR_POSITION = []() -> glm::vec3 {
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition();
};
std::function<glm::quat()> const FloatingUIPanel::AVATAR_ORIENTATION = []() -> glm::quat {
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getOrientation() *
glm::angleAxis(glm::pi<float>(), IDENTITY_UP);
};
glm::vec3 FloatingUIPanel::getPosition() const {
return getOffsetRotation() * getOffsetPosition() + getAnchorPosition();
}
glm::quat FloatingUIPanel::getRotation() const {
return getOffsetRotation() * getFacingRotation();
}
void FloatingUIPanel::setAnchorPosition(const glm::vec3& position) {
setAnchorPosition([position]() -> glm::vec3 {
return position;
});
}
void FloatingUIPanel::setOffsetRotation(const glm::quat& rotation) {
setOffsetRotation([rotation]() -> glm::quat {
return rotation;
});
}
void FloatingUIPanel::addChild(unsigned int childId) {
if (!_children.contains(childId)) {
_children.append(childId);
}
}
void FloatingUIPanel::removeChild(unsigned int childId) {
if (_children.contains(childId)) {
_children.removeOne(childId);
}
}
QScriptValue FloatingUIPanel::getProperty(const QString &property) {
if (property == "anchorPosition") {
return vec3toScriptValue(_scriptEngine, getAnchorPosition());
}
if (property == "offsetRotation") {
return quatToScriptValue(_scriptEngine, getOffsetRotation());
}
if (property == "offsetPosition") {
return vec3toScriptValue(_scriptEngine, getOffsetPosition());
}
if (property == "facingRotation") {
return quatToScriptValue(_scriptEngine, getFacingRotation());
}
return QScriptValue();
}
void FloatingUIPanel::setProperties(const QScriptValue &properties) {
QScriptValue anchor = properties.property("anchorPosition");
if (anchor.isValid()) {
QScriptValue bindType = anchor.property("bind");
QScriptValue value = anchor.property("value");
if (bindType.isValid()) {
QString bindTypeString = bindType.toVariant().toString();
if (bindTypeString == "myAvatar") {
setAnchorPosition(AVATAR_POSITION);
} else if (value.isValid()) {
if (bindTypeString == "overlay") {
Overlay::Pointer overlay = Application::getInstance()->getOverlays()
.getOverlay(value.toVariant().toUInt());
if (overlay->is3D()) {
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
setAnchorPosition([&overlay3D]() -> glm::vec3 {
return overlay3D->getPosition();
});
}
} else if (bindTypeString == "panel") {
FloatingUIPanel::Pointer panel = Application::getInstance()->getOverlays()
.getPanel(value.toVariant().toUInt());
setAnchorPosition([panel]() -> glm::vec3 {
return panel->getPosition();
});
} else if (bindTypeString == "vec3") {
QScriptValue x = value.property("x");
QScriptValue y = value.property("y");
QScriptValue z = value.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
setAnchorPosition(newPosition);
}
}
}
}
}
QScriptValue offsetRotation = properties.property("offsetRotation");
if (offsetRotation.isValid()) {
QScriptValue bindType = offsetRotation.property("bind");
QScriptValue value = offsetRotation.property("value");
if (bindType.isValid()) {
QString bindTypeString = bindType.toVariant().toString();
if (bindTypeString == "myAvatar") {
setOffsetRotation(AVATAR_ORIENTATION);
} else if (value.isValid()) {
if (bindTypeString == "overlay") {
Overlay::Pointer overlay = Application::getInstance()->getOverlays()
.getOverlay(value.toVariant().toUInt());
if (overlay->is3D()) {
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
setOffsetRotation([&overlay3D]() -> glm::quat {
return overlay3D->getRotation();
});
}
} else if (bindTypeString == "panel") {
FloatingUIPanel::Pointer panel = Application::getInstance()->getOverlays()
.getPanel(value.toVariant().toUInt());
setOffsetRotation([panel]() -> glm::quat {
return panel->getRotation();
});
} else if (bindTypeString == "quat") {
QScriptValue x = value.property("x");
QScriptValue y = value.property("y");
QScriptValue z = value.property("z");
QScriptValue w = value.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
setOffsetRotation(newRotation);
}
}
}
}
}
QScriptValue offsetPosition = properties.property("offsetPosition");
if (offsetPosition.isValid()) {
QScriptValue x = offsetPosition.property("x");
QScriptValue y = offsetPosition.property("y");
QScriptValue z = offsetPosition.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
setOffsetPosition(newPosition);
}
}
QScriptValue facingRotation = properties.property("facingRotation");
if (facingRotation.isValid()) {
QScriptValue x = facingRotation.property("x");
QScriptValue y = facingRotation.property("y");
QScriptValue z = facingRotation.property("z");
QScriptValue w = facingRotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
setFacingRotation(newRotation);
}
}
}

View file

@ -0,0 +1,63 @@
//
// FloatingUIPanel.h
// interface/src/ui/overlays
//
// Created by Zander Otavka on 7/2/15.
// 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_FloatingUIPanel_h
#define hifi_FloatingUIPanel_h
#include <functional>
#include <memory>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QScriptValue>
class FloatingUIPanel : public QObject {
Q_OBJECT
public:
typedef std::shared_ptr<FloatingUIPanel> Pointer;
void init(QScriptEngine* scriptEngine) { _scriptEngine = scriptEngine; }
glm::vec3 getAnchorPosition() const { return _anchorPosition(); }
glm::quat getOffsetRotation() const { return _offsetRotation(); }
glm::vec3 getOffsetPosition() const { return _offsetPosition; }
glm::quat getFacingRotation() const { return _facingRotation; }
glm::vec3 getPosition() const;
glm::quat getRotation() const;
void setAnchorPosition(const std::function<glm::vec3()>& func) { _anchorPosition = func; }
void setAnchorPosition(const glm::vec3& position);
void setOffsetRotation(const std::function<glm::quat()>& func) { _offsetRotation = func; }
void setOffsetRotation(const glm::quat& rotation);
void setOffsetPosition(const glm::vec3& position) { _offsetPosition = position; }
void setFacingRotation(const glm::quat& rotation) { _facingRotation = rotation; }
const QList<unsigned int>& getChildren() { return _children; }
void addChild(unsigned int childId);
void removeChild(unsigned int childId);
unsigned int popLastChild() { return _children.takeLast(); }
QScriptValue getProperty(const QString& property);
void setProperties(const QScriptValue& properties);
private:
static std::function<glm::vec3()> const AVATAR_POSITION;
static std::function<glm::quat()> const AVATAR_ORIENTATION;
std::function<glm::vec3()> _anchorPosition{AVATAR_POSITION};
std::function<glm::quat()> _offsetRotation{AVATAR_ORIENTATION};
glm::vec3 _offsetPosition{0, 0, 0};
glm::quat _facingRotation{1, 0, 0, 0};
QScriptEngine* _scriptEngine;
QList<unsigned int> _children;
};
#endif // hifi_FloatingUIPanel_h

View file

@ -18,6 +18,9 @@
#include <PathUtils.h>
#include <ViewFrustum.h>
QString const Grid3DOverlay::TYPE = "grid";
Grid3DOverlay::Grid3DOverlay() :
_minorGridWidth(1.0),
_majorGridEvery(5) {

View file

@ -18,6 +18,9 @@ class Grid3DOverlay : public Planar3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Grid3DOverlay();
Grid3DOverlay(const Grid3DOverlay* grid3DOverlay);

View file

@ -16,6 +16,9 @@
#include <gpu/StandardShaderLib.h>
#include <RegisteredMetaTypes.h>
QString const ImageOverlay::TYPE = "image";
ImageOverlay::ImageOverlay() :
_imageURL(),
_renderImage(false),

View file

@ -24,6 +24,9 @@ class ImageOverlay : public Overlay2D {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
ImageOverlay();
ImageOverlay(const ImageOverlay* imageOverlay);

View file

@ -13,6 +13,9 @@
#include <GeometryCache.h>
#include <RegisteredMetaTypes.h>
QString const Line3DOverlay::TYPE = "line3d";
Line3DOverlay::Line3DOverlay() :
_geometryCacheID(DependencyManager::get<GeometryCache>()->allocateID())
{

View file

@ -17,6 +17,9 @@ class Line3DOverlay : public Base3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Line3DOverlay();
Line3DOverlay(const Line3DOverlay* line3DOverlay);
~Line3DOverlay();

View file

@ -14,6 +14,9 @@
#include <EntityTreeRenderer.h>
#include <gpu/Batch.h>
QString const LocalModelsOverlay::TYPE = "localmodels";
LocalModelsOverlay::LocalModelsOverlay(EntityTreeRenderer* entityTreeRenderer) :
Volume3DOverlay(),
_entityTreeRenderer(entityTreeRenderer) {

View file

@ -19,6 +19,9 @@ class EntityTreeRenderer;
class LocalModelsOverlay : public Volume3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
LocalModelsOverlay(EntityTreeRenderer* entityTreeRenderer);
LocalModelsOverlay(const LocalModelsOverlay* localModelsOverlay);

View file

@ -10,11 +10,15 @@
//
#include "ModelOverlay.h"
#include "EntityRig.h"
#include "Application.h"
QString const ModelOverlay::TYPE = "model";
ModelOverlay::ModelOverlay()
: _model(),
: _model(std::make_shared<EntityRig>()),
_modelTextures(QVariantMap()),
_updateModel(false)
{
@ -24,7 +28,7 @@ ModelOverlay::ModelOverlay()
ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
Volume3DOverlay(modelOverlay),
_model(),
_model(std::make_shared<EntityRig>()),
_modelTextures(QVariantMap()),
_url(modelOverlay->_url),
_updateModel(false)

View file

@ -19,6 +19,9 @@
class ModelOverlay : public Volume3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
ModelOverlay();
ModelOverlay(const ModelOverlay* modelOverlay);

View file

@ -110,7 +110,8 @@ void Overlay::setProperties(const QScriptValue& properties) {
}
if (properties.property("visible").isValid()) {
setVisible(properties.property("visible").toVariant().toBool());
bool visible = properties.property("visible").toVariant().toBool();
setVisible(visible);
}
if (properties.property("anchor").isValid()) {

View file

@ -44,6 +44,7 @@ public:
virtual void removeFromScene(Overlay::Pointer overlay, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
// getters
virtual QString getType() const = 0;
virtual bool is3D() const = 0;
bool isLoaded() { return _isLoaded; }
bool getVisible() const { return _visible; }

View file

@ -48,6 +48,7 @@ Overlays::~Overlays() {
}
_overlaysHUD.clear();
_overlaysWorld.clear();
_panels.clear();
}
cleanupOverlaysToDelete();
@ -124,85 +125,84 @@ void Overlays::renderHUD(RenderArgs* renderArgs) {
}
}
unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) {
unsigned int thisID = 0;
Overlay* thisOverlay = NULL;
bool created = true;
if (type == "image") {
thisOverlay = new ImageOverlay();
} else if (type == "text") {
thisOverlay = new TextOverlay();
} else if (type == "text3d") {
thisOverlay = new Text3DOverlay();
} else if (type == "cube") {
thisOverlay = new Cube3DOverlay();
} else if (type == "sphere") {
thisOverlay = new Sphere3DOverlay();
} else if (type == "circle3d") {
thisOverlay = new Circle3DOverlay();
} else if (type == "rectangle3d") {
thisOverlay = new Rectangle3DOverlay();
} else if (type == "line3d") {
thisOverlay = new Line3DOverlay();
} else if (type == "grid") {
thisOverlay = new Grid3DOverlay();
} else if (type == "localmodels") {
thisOverlay = new LocalModelsOverlay(Application::getInstance()->getEntityClipboardRenderer());
} else if (type == "model") {
thisOverlay = new ModelOverlay();
} else if (type == "billboard") {
thisOverlay = new BillboardOverlay();
} else {
created = false;
Overlay::Pointer Overlays::getOverlay(unsigned int id) const {
if (_overlaysHUD.contains(id)) {
return _overlaysHUD[id];
}
if (created) {
thisOverlay->setProperties(properties);
thisID = addOverlay(thisOverlay);
if (_overlaysWorld.contains(id)) {
return _overlaysWorld[id];
}
return thisID;
return nullptr;
}
unsigned int Overlays::addOverlay(Overlay* overlay) {
Overlay::Pointer overlayPointer(overlay);
unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) {
Overlay::Pointer thisOverlay = nullptr;
if (type == ImageOverlay::TYPE) {
thisOverlay = std::make_shared<ImageOverlay>();
} else if (type == TextOverlay::TYPE) {
thisOverlay = std::make_shared<TextOverlay>();
} else if (type == Text3DOverlay::TYPE) {
thisOverlay = std::make_shared<Text3DOverlay>();
} else if (type == Cube3DOverlay::TYPE) {
thisOverlay = std::make_shared<Cube3DOverlay>();
} else if (type == Sphere3DOverlay::TYPE) {
thisOverlay = std::make_shared<Sphere3DOverlay>();
} else if (type == Circle3DOverlay::TYPE) {
thisOverlay = std::make_shared<Circle3DOverlay>();
} else if (type == Rectangle3DOverlay::TYPE) {
thisOverlay = std::make_shared<Rectangle3DOverlay>();
} else if (type == Line3DOverlay::TYPE) {
thisOverlay = std::make_shared<Line3DOverlay>();
} else if (type == Grid3DOverlay::TYPE) {
thisOverlay = std::make_shared<Grid3DOverlay>();
} else if (type == LocalModelsOverlay::TYPE) {
thisOverlay = std::make_shared<LocalModelsOverlay>(Application::getInstance()->getEntityClipboardRenderer());
} else if (type == ModelOverlay::TYPE) {
thisOverlay = std::make_shared<ModelOverlay>();
} else if (type == BillboardOverlay::TYPE) {
thisOverlay = std::make_shared<BillboardOverlay>();
}
if (thisOverlay) {
thisOverlay->setProperties(properties);
return addOverlay(thisOverlay);
}
return 0;
}
unsigned int Overlays::addOverlay(Overlay::Pointer overlay) {
overlay->init(_scriptEngine);
QWriteLocker lock(&_lock);
unsigned int thisID = _nextOverlayID;
_nextOverlayID++;
if (overlay->is3D()) {
Base3DOverlay* overlay3D = static_cast<Base3DOverlay*>(overlay);
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
if (overlay3D->getDrawOnHUD()) {
_overlaysHUD[thisID] = overlayPointer;
_overlaysHUD[thisID] = overlay;
} else {
_overlaysWorld[thisID] = overlayPointer;
_overlaysWorld[thisID] = overlay;
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
render::PendingChanges pendingChanges;
overlayPointer->addToScene(overlayPointer, scene, pendingChanges);
overlay->addToScene(overlay, scene, pendingChanges);
scene->enqueuePendingChanges(pendingChanges);
}
} else {
_overlaysHUD[thisID] = overlayPointer;
_overlaysHUD[thisID] = overlay;
}
return thisID;
}
unsigned int Overlays::cloneOverlay(unsigned int id) {
Overlay::Pointer thisOverlay = NULL;
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay) {
return addOverlay(thisOverlay->createClone());
return addOverlay(Overlay::Pointer(thisOverlay->createClone()));
}
return 0; // Not found
@ -210,14 +210,8 @@ unsigned int Overlays::cloneOverlay(unsigned int id) {
bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) {
QWriteLocker lock(&_lock);
Overlay::Pointer thisOverlay;
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay) {
if (thisOverlay->is3D()) {
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(thisOverlay);
@ -258,8 +252,51 @@ void Overlays::deleteOverlay(unsigned int id) {
}
}
auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlayToDelete);
if (attachable && attachable->getAttachedPanel()) {
attachable->getAttachedPanel()->removeChild(id);
attachable->setAttachedPanel(nullptr);
}
QWriteLocker lock(&_deleteLock);
_overlaysToDelete.push_back(overlayToDelete);
emit overlayDeleted(id);
}
QString Overlays::getOverlayType(unsigned int overlayId) const {
Overlay::Pointer overlay = getOverlay(overlayId);
if (overlay) {
return overlay->getType();
}
return "";
}
unsigned int Overlays::getAttachedPanel(unsigned int childId) const {
Overlay::Pointer overlay = getOverlay(childId);
auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlay);
if (attachable) {
return _panels.key(attachable->getAttachedPanel());
}
return 0;
}
void Overlays::setAttachedPanel(unsigned int childId, unsigned int panelId) {
Overlay::Pointer overlay = getOverlay(childId);
auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlay);
if (attachable) {
if (_panels.contains(panelId)) {
auto panel = _panels[panelId];
panel->addChild(childId);
attachable->setAttachedPanel(panel);
} else {
auto panel = attachable->getAttachedPanel();
if (panel) {
panel->removeChild(childId);
attachable->setAttachedPanel(nullptr);
}
}
}
}
unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
@ -302,13 +339,8 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
OverlayPropertyResult Overlays::getProperty(unsigned int id, const QString& property) {
OverlayPropertyResult result;
Overlay::Pointer thisOverlay;
Overlay::Pointer thisOverlay = getOverlay(id);
QReadLocker lock(&_lock);
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
if (thisOverlay) {
result.value = thisOverlay->getProperty(property);
}
@ -456,12 +488,8 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R
bool Overlays::isLoaded(unsigned int id) {
QReadLocker lock(&_lock);
Overlay::Pointer thisOverlay = NULL;
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
} else {
Overlay::Pointer thisOverlay = getOverlay(id);
if (!thisOverlay) {
return false; // not found
}
return thisOverlay->isLoaded();
@ -483,3 +511,56 @@ QSizeF Overlays::textSize(unsigned int id, const QString& text) const {
}
return QSizeF(0.0f, 0.0f);
}
unsigned int Overlays::addPanel(FloatingUIPanel::Pointer panel) {
QWriteLocker lock(&_lock);
unsigned int thisID = _nextOverlayID;
_nextOverlayID++;
_panels[thisID] = panel;
return thisID;
}
unsigned int Overlays::addPanel(const QScriptValue& properties) {
FloatingUIPanel::Pointer panel = std::make_shared<FloatingUIPanel>();
panel->init(_scriptEngine);
panel->setProperties(properties);
return addPanel(panel);
}
void Overlays::editPanel(unsigned int panelId, const QScriptValue& properties) {
if (_panels.contains(panelId)) {
_panels[panelId]->setProperties(properties);
}
}
OverlayPropertyResult Overlays::getPanelProperty(unsigned int panelId, const QString& property) {
OverlayPropertyResult result;
if (_panels.contains(panelId)) {
FloatingUIPanel::Pointer thisPanel = _panels[panelId];
QReadLocker lock(&_lock);
result.value = thisPanel->getProperty(property);
}
return result;
}
void Overlays::deletePanel(unsigned int panelId) {
FloatingUIPanel::Pointer panelToDelete;
{
QWriteLocker lock(&_lock);
if (_panels.contains(panelId)) {
panelToDelete = _panels.take(panelId);
} else {
return;
}
}
while (!panelToDelete->getChildren().isEmpty()) {
deleteOverlay(panelToDelete->popLastChild());
}
emit panelDeleted(panelId);
}

View file

@ -2,8 +2,14 @@
// Overlays.h
// interface/src/ui/overlays
//
// Modified by Zander Otavka on 7/15/15
// Copyright 2014 High Fidelity, Inc.
//
// Exposes methods for managing `Overlay`s and `FloatingUIPanel`s to scripts.
//
// YOU SHOULD NOT USE `Overlays` DIRECTLY, unless you like pain and deprecation. Instead, use the
// object oriented abstraction layer found in `examples/libraries/overlayUtils.js`.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
@ -16,6 +22,9 @@
#include "Overlay.h"
#include "FloatingUIPanel.h"
#include "PanelAttachable.h"
class PickRay;
class OverlayPropertyResult {
@ -57,12 +66,16 @@ public:
void update(float deltatime);
void renderHUD(RenderArgs* renderArgs);
Overlay::Pointer getOverlay(unsigned int id) const;
FloatingUIPanel::Pointer getPanel(unsigned int id) const { return _panels[id]; }
public slots:
/// adds an overlay with the specific properties
unsigned int addOverlay(const QString& type, const QScriptValue& properties);
/// adds an overlay that's already been created
unsigned int addOverlay(Overlay* overlay);
unsigned int addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
unsigned int addOverlay(Overlay::Pointer overlay);
/// clones an existing overlay
unsigned int cloneOverlay(unsigned int id);
@ -74,6 +87,12 @@ public slots:
/// deletes a particle
void deleteOverlay(unsigned int id);
/// get the string type of the overlay used in addOverlay
QString getOverlayType(unsigned int overlayId) const;
unsigned int getAttachedPanel(unsigned int childId) const;
void setAttachedPanel(unsigned int childId, unsigned int panelId);
/// returns the top most 2D overlay at the screen point, or 0 if not overlay at that point
unsigned int getOverlayAtPoint(const glm::vec2& point);
@ -90,12 +109,35 @@ public slots:
/// overlay; in meters if it is a 3D text overlay
QSizeF textSize(unsigned int id, const QString& text) const;
/// adds a panel that has already been created
unsigned int addPanel(FloatingUIPanel::Pointer panel);
/// creates and adds a panel based on a set of properties
unsigned int addPanel(const QScriptValue& properties);
/// edit the properties of a panel
void editPanel(unsigned int panelId, const QScriptValue& properties);
/// get a property of a panel
OverlayPropertyResult getPanelProperty(unsigned int panelId, const QString& property);
/// deletes a panel and all child overlays
void deletePanel(unsigned int panelId);
signals:
void overlayDeleted(unsigned int id);
void panelDeleted(unsigned int id);
private:
void cleanupOverlaysToDelete();
QMap<unsigned int, Overlay::Pointer> _overlaysHUD;
QMap<unsigned int, Overlay::Pointer> _overlaysWorld;
QMap<unsigned int, FloatingUIPanel::Pointer> _panels;
QList<Overlay::Pointer> _overlaysToDelete;
unsigned int _nextOverlayID;
QReadWriteLock _lock;
QReadWriteLock _deleteLock;
QScriptEngine* _scriptEngine;

View file

@ -0,0 +1,80 @@
//
// PanelAttachable.cpp
// hifi
//
// Created by Zander Otavka 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 "PanelAttachable.h"
#include <RegisteredMetaTypes.h>
PanelAttachable::PanelAttachable() :
_attachedPanel(nullptr),
_facingRotation(1, 0, 0, 0)
{
}
PanelAttachable::PanelAttachable(const PanelAttachable* panelAttachable) :
_attachedPanel(panelAttachable->_attachedPanel),
_offsetPosition(panelAttachable->_offsetPosition),
_facingRotation(panelAttachable->_facingRotation)
{
}
void PanelAttachable::setTransforms(Transform& transform) {
if (getAttachedPanel()) {
transform.setTranslation(getAttachedPanel()->getAnchorPosition());
transform.setRotation(getAttachedPanel()->getOffsetRotation());
transform.postTranslate(getOffsetPosition() + getAttachedPanel()->getOffsetPosition());
transform.postRotate(getFacingRotation() * getAttachedPanel()->getFacingRotation());
}
}
QScriptValue PanelAttachable::getProperty(QScriptEngine* scriptEngine, const QString &property) {
if (property == "offsetPosition") {
return vec3toScriptValue(scriptEngine, getOffsetPosition());
}
if (property == "facingRotation") {
return quatToScriptValue(scriptEngine, getFacingRotation());
}
return QScriptValue();
}
void PanelAttachable::setProperties(const QScriptValue &properties) {
QScriptValue offsetPosition = properties.property("offsetPosition");
if (offsetPosition.isValid()) {
QScriptValue x = offsetPosition.property("x");
QScriptValue y = offsetPosition.property("y");
QScriptValue z = offsetPosition.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
setOffsetPosition(newPosition);
}
}
QScriptValue facingRotation = properties.property("facingRotation");
if (facingRotation.isValid()) {
QScriptValue x = facingRotation.property("x");
QScriptValue y = facingRotation.property("y");
QScriptValue z = facingRotation.property("z");
QScriptValue w = facingRotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
setFacingRotation(newRotation);
}
}
}

View file

@ -0,0 +1,45 @@
//
// PanelAttachable.h
// interface/src/ui/overlays
//
// Created by Zander Otavka on 7/1/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_PanelAttachable_h
#define hifi_PanelAttachable_h
#include "FloatingUIPanel.h"
#include <glm/glm.hpp>
#include <Transform.h>
class PanelAttachable {
public:
PanelAttachable();
PanelAttachable(const PanelAttachable* panelAttachable);
FloatingUIPanel::Pointer getAttachedPanel() const { return _attachedPanel; }
glm::vec3 getOffsetPosition() const { return _offsetPosition; }
glm::quat getFacingRotation() const { return _facingRotation; }
void setAttachedPanel(FloatingUIPanel::Pointer panel) { _attachedPanel = panel; }
void setOffsetPosition(const glm::vec3& position) { _offsetPosition = position; }
void setFacingRotation(const glm::quat& rotation) { _facingRotation = rotation; }
QScriptValue getProperty(QScriptEngine* scriptEngine, const QString& property);
void setProperties(const QScriptValue& properties);
protected:
virtual void setTransforms(Transform& transform);
private:
FloatingUIPanel::Pointer _attachedPanel;
glm::vec3 _offsetPosition;
glm::quat _facingRotation;
};
#endif // hifi_PanelAttachable_h

Some files were not shown because too many files have changed in this diff Show more