mirror of
https://github.com/overte-org/overte.git
synced 2025-04-23 00:13:33 +02:00
resolve conflicts on merge with upstream master
This commit is contained in:
commit
052978898e
262 changed files with 12160 additions and 6455 deletions
assignment-client/src
cmake/modules
domain-server/resources
examples
afk.jscontrolPanel.jsedit.js
example
gridTest.jshmdControls.jshtml
inspect.jsleaves.jslibraries
particles.jsutilities/tools
zones
interface
CMakeLists.txt
external/connexionclient
resources/meshes
src
Application.cppApplication.hGLCanvas.hMenu.cppMenu.hModelPackager.cppStars.cpp
avatar
Avatar.cppAvatar.hAvatarManager.cppFaceModel.cppFaceModel.hHand.cppHand.hHead.cppHead.hMyAvatar.cppMyAvatar.hSkeletonModel.cppSkeletonModel.h
devices
3Dconnexion.cpp3Dconnexion.hDdeFaceTracker.cppDdeFaceTracker.hFaceTracker.hOculusManager.cppTV3DManager.cppTV3DManager.h
octree
scripting
ControllerScriptingInterface.cppDialogsManagerScriptingInterface.cppDialogsManagerScriptingInterface.hWebWindowClass.cpp
ui
AnimationsDialog.cppApplicationCompositor.cppApplicationOverlay.cppAudioStatsDialog.cppDialogsManager.cppDialogsManager.h
overlays
BillboardOverlay.cppBillboardOverlay.hCircle3DOverlay.cppCircle3DOverlay.hCube3DOverlay.cppCube3DOverlay.hFloatingUIPanel.cppFloatingUIPanel.hGrid3DOverlay.cppGrid3DOverlay.hImageOverlay.cppImageOverlay.hLine3DOverlay.cppLine3DOverlay.hLocalModelsOverlay.cppLocalModelsOverlay.hModelOverlay.cppModelOverlay.hOverlay.cppOverlay.hOverlays.cppOverlays.hPanelAttachable.cppPanelAttachable.h
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 ()
|
||||
|
||||
|
|
38
cmake/modules/FindconnexionClient.cmake
Normal file
38
cmake/modules/FindconnexionClient.cmake
Normal 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()
|
|
@ -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
100
examples/afk.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// #20485: AFK - Away From Keyboard Setting
|
||||
// *****************************************
|
||||
//
|
||||
// Created by Kevin M. Thomas and Thoys 07/16/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// kevintown.net
|
||||
//
|
||||
// JavaScript for the High Fidelity interface that creates an away from keyboard functionality by providing a UI and keyPressEvent which will mute toggle the connected microphone, face tracking dde and set the avatar to a hand raise pose.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
var originalOutputDevice;
|
||||
var originalName;
|
||||
var muted = false;
|
||||
var wasAudioEnabled;
|
||||
var afkText = "AFK - I Will Return!\n";
|
||||
|
||||
// Set up toggleMuteButton text overlay.
|
||||
var toggleMuteButton = Overlays.addOverlay("text", {
|
||||
x: 10,
|
||||
y: 275,
|
||||
width: 60,
|
||||
height: 28,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8
|
||||
});
|
||||
|
||||
// Function that overlays text upon state change.
|
||||
function onMuteStateChanged() {
|
||||
Overlays.editOverlay(toggleMuteButton, muted ? {text: "Go Live", leftMargin: 5} : {text: "Go AFK", leftMargin: 5});
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
if (!muted) {
|
||||
if (!AudioDevice.getMuted()) {
|
||||
AudioDevice.toggleMute();
|
||||
}
|
||||
originalOutputDevice = AudioDevice.getOutputDevice();
|
||||
Menu.setIsOptionChecked("Mute Face Tracking", true);
|
||||
originalName = MyAvatar.displayName;
|
||||
AudioDevice.setOutputDevice("none");
|
||||
MyAvatar.displayName = afkText + MyAvatar.displayName;
|
||||
MyAvatar.setJointData("LeftShoulder", Quat.fromPitchYawRollDegrees(0, 180, 0));
|
||||
MyAvatar.setJointData("RightShoulder", Quat.fromPitchYawRollDegrees(0, 180, 0));
|
||||
} else {
|
||||
if (AudioDevice.getMuted()) {
|
||||
AudioDevice.toggleMute();
|
||||
}
|
||||
AudioDevice.setOutputDevice(originalOutputDevice);
|
||||
Menu.setIsOptionChecked("Mute Face Tracking", false);
|
||||
MyAvatar.setJointData("LeftShoulder", Quat.fromPitchYawRollDegrees(0, 0, 0));
|
||||
MyAvatar.setJointData("RightShoulder", Quat.fromPitchYawRollDegrees(0, 0, 0));
|
||||
MyAvatar.clearJointData("LeftShoulder");
|
||||
MyAvatar.clearJointData("RightShoulder");
|
||||
MyAvatar.displayName = originalName;
|
||||
}
|
||||
muted = !muted;
|
||||
onMuteStateChanged();
|
||||
}
|
||||
|
||||
// Function that adds mousePressEvent functionality to toggle mic mute, AFK message above display name and toggle avatar arms upward.
|
||||
function mousePressEvent(event) {
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleMuteButton) {
|
||||
toggleMute();
|
||||
}
|
||||
}
|
||||
|
||||
// Call functions.
|
||||
onMuteStateChanged();
|
||||
|
||||
//AudioDevice.muteToggled.connect(onMuteStateChanged);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
|
||||
// Function that adds keyPressEvent functionality to toggle mic mute, AFK message above display name and toggle avatar arms upward.
|
||||
Controller.keyPressEvent.connect(function(event) {
|
||||
if (event.text == "y") {
|
||||
toggleMute();
|
||||
}
|
||||
});
|
||||
|
||||
// Function that sets a timeout value of 1 second so that the display name does not get overwritten in the event of a crash.
|
||||
Script.setTimeout(function() {
|
||||
MyAvatar.displayName = MyAvatar.displayName.replace(afkText, "");
|
||||
}, 1000);
|
||||
|
||||
// Function that calls upon exit to restore avatar display name to original state.
|
||||
Script.scriptEnding.connect(function(){
|
||||
if (muted) {
|
||||
AudioDevice.setOutputDevice(originalOutputDevice);
|
||||
Overlays.deleteOverlay(toggleMuteButton);
|
||||
MyAvatar.displayName = originalName;
|
||||
}
|
||||
Overlays.deleteOverlay(toggleMuteButton);
|
||||
});
|
214
examples/controlPanel.js
Normal file
214
examples/controlPanel.js
Normal 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);
|
239
examples/edit.js
239
examples/edit.js
|
@ -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], {
|
||||
|
|
145
examples/example/audio/jsstreamplayer.js
Normal file
145
examples/example/audio/jsstreamplayer.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
//
|
||||
// #20622: JS Stream Player
|
||||
// *************************
|
||||
//
|
||||
// Created by Kevin M. Thomas and Thoys 07/17/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// kevintown.net
|
||||
//
|
||||
// JavaScript for the High Fidelity interface that creates a stream player with a UI and keyPressEvents for adding a stream URL in addition to play, stop and volume functionality.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
// Declare HiFi public bucket.
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
// Declare variables and set up new WebWindow.
|
||||
var stream;
|
||||
var volume = 1;
|
||||
var streamWindow = new WebWindow('Stream', HIFI_PUBLIC_BUCKET + "examples/html/jsstreamplayer.html", 0, 0, false);
|
||||
|
||||
// Set up toggleStreamURLButton overlay.
|
||||
var toggleStreamURLButton = Overlays.addOverlay("text", {
|
||||
x: 76,
|
||||
y: 275,
|
||||
width: 40,
|
||||
height: 28,
|
||||
backgroundColor: {red: 0, green: 0, blue: 0},
|
||||
color: {red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8,
|
||||
text: " URL"
|
||||
});
|
||||
|
||||
// Set up toggleStreamPlayButton overlay.
|
||||
var toggleStreamPlayButton = Overlays.addOverlay("text", {
|
||||
x: 122,
|
||||
y: 275,
|
||||
width: 38,
|
||||
height: 28,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8,
|
||||
text: " Play"
|
||||
});
|
||||
|
||||
// Set up toggleStreamStopButton overlay.
|
||||
var toggleStreamStopButton = Overlays.addOverlay("text", {
|
||||
x: 166,
|
||||
y: 275,
|
||||
width: 40,
|
||||
height: 28,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8,
|
||||
text: " Stop"
|
||||
});
|
||||
|
||||
// Set up increaseVolumeButton overlay.
|
||||
var toggleIncreaseVolumeButton = Overlays.addOverlay("text", {
|
||||
x: 211,
|
||||
y: 275,
|
||||
width: 18,
|
||||
height: 28,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8,
|
||||
text: " +"
|
||||
});
|
||||
|
||||
// Set up decreaseVolumeButton overlay.
|
||||
var toggleDecreaseVolumeButton = Overlays.addOverlay("text", {
|
||||
x: 234,
|
||||
y: 275,
|
||||
width: 15,
|
||||
height: 28,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8,
|
||||
text: " -"
|
||||
});
|
||||
|
||||
// Function that adds mousePressEvent functionality to connect UI to enter stream URL, play and stop stream.
|
||||
function mousePressEvent(event) {
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamURLButton) {
|
||||
stream = Window.prompt("Enter Stream: ");
|
||||
var streamJSON = {
|
||||
action: "changeStream",
|
||||
stream: stream
|
||||
}
|
||||
streamWindow.eventBridge.emitScriptEvent(JSON.stringify(streamJSON));
|
||||
}
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamPlayButton) {
|
||||
var streamJSON = {
|
||||
action: "changeStream",
|
||||
stream: stream
|
||||
}
|
||||
streamWindow.eventBridge.emitScriptEvent(JSON.stringify(streamJSON));
|
||||
}
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamStopButton) {
|
||||
var streamJSON = {
|
||||
action: "changeStream",
|
||||
stream: ""
|
||||
}
|
||||
streamWindow.eventBridge.emitScriptEvent(JSON.stringify(streamJSON));
|
||||
}
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleIncreaseVolumeButton) {
|
||||
volume += 0.2;
|
||||
var volumeJSON = {
|
||||
action: "changeVolume",
|
||||
volume: volume
|
||||
}
|
||||
streamWindow.eventBridge.emitScriptEvent(JSON.stringify(volumeJSON));
|
||||
}
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleDecreaseVolumeButton) {
|
||||
volume -= 0.2;
|
||||
var volumeJSON = {
|
||||
action: "changeVolume",
|
||||
volume: volume
|
||||
}
|
||||
streamWindow.eventBridge.emitScriptEvent(JSON.stringify(volumeJSON));
|
||||
}
|
||||
}
|
||||
|
||||
// Call function.
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
streamWindow.setVisible(false);
|
||||
|
||||
// Function to delete overlays upon exit.
|
||||
function onScriptEnding() {
|
||||
Overlays.deleteOverlay(toggleStreamURLButton);
|
||||
Overlays.deleteOverlay(toggleStreamPlayButton);
|
||||
Overlays.deleteOverlay(toggleStreamStopButton);
|
||||
Overlays.deleteOverlay(toggleIncreaseVolumeButton);
|
||||
Overlays.deleteOverlay(toggleDecreaseVolumeButton);
|
||||
}
|
||||
|
||||
// Call function.
|
||||
Script.scriptEnding.connect(onScriptEnding);
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// #20628: JS Stream Player Domain-Zone-Entity
|
||||
// ********************************************
|
||||
//
|
||||
// Created by Kevin M. Thomas and Thoys 07/20/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// kevintown.net
|
||||
//
|
||||
// JavaScript for the High Fidelity interface that is an entity script to be placed in a chosen entity inside a domain-zone.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
// Function which exists inside of an entity which triggers as a user approches it.
|
||||
(function() {
|
||||
const SCRIPT_NAME = "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.js";
|
||||
function isScriptRunning(script) {
|
||||
script = script.toLowerCase().trim();
|
||||
var runningScripts = ScriptDiscoveryService.getRunning();
|
||||
for (i in runningScripts) {
|
||||
if (runningScripts[i].url.toLowerCase().trim() == script) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!isScriptRunning(SCRIPT_NAME)) {
|
||||
Script.load(SCRIPT_NAME);
|
||||
}
|
||||
})
|
28
examples/example/games/hitEffect.js
Normal file
28
examples/example/games/hitEffect.js
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
288
examples/example/games/satellite.js
Normal file
288
examples/example/games/satellite.js
Normal file
|
@ -0,0 +1,288 @@
|
|||
//
|
||||
// satellite.js
|
||||
// games
|
||||
//
|
||||
// Created by Bridget Went 7/1/2015.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// A game to bring a satellite model into orbit around an animated earth model .
|
||||
// - Double click to create a new satellite
|
||||
// - Click on the satellite, drag a vector arrow to specify initial velocity
|
||||
// - Release mouse to launch the active satellite
|
||||
// - Orbital movement is calculated using equations of gravitational physics
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include('../utilities/tools/vector.js');
|
||||
|
||||
var URL = "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/";
|
||||
|
||||
SatelliteGame = function() {
|
||||
var MAX_RANGE = 50.0;
|
||||
var Y_AXIS = {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
}
|
||||
var LIFETIME = 6000;
|
||||
var ERROR_THRESH = 20.0;
|
||||
|
||||
// Create the spinning earth model
|
||||
var EARTH_SIZE = 20.0;
|
||||
var CLOUDS_OFFSET = 0.5;
|
||||
var SPIN = 0.1;
|
||||
var ZONE_DIM = 100.0;
|
||||
var LIGHT_INTENSITY = 1.5;
|
||||
|
||||
Earth = function(position, size) {
|
||||
this.earth = Entities.addEntity({
|
||||
type: "Model",
|
||||
shapeType: 'sphere',
|
||||
modelURL: URL + "earth.fbx",
|
||||
position: position,
|
||||
dimensions: {
|
||||
x: size,
|
||||
y: size,
|
||||
z: size
|
||||
},
|
||||
rotation: Quat.angleAxis(180, {
|
||||
x: 1,
|
||||
y: 0,
|
||||
z: 0
|
||||
}),
|
||||
angularVelocity: {
|
||||
x: 0.00,
|
||||
y: 0.5 * SPIN,
|
||||
z: 0.00
|
||||
},
|
||||
angularDamping: 0.0,
|
||||
damping: 0.0,
|
||||
ignoreCollisions: false,
|
||||
lifetime: 6000,
|
||||
collisionsWillMove: false,
|
||||
visible: true
|
||||
});
|
||||
|
||||
this.clouds = Entities.addEntity({
|
||||
type: "Model",
|
||||
shapeType: 'sphere',
|
||||
modelURL: URL + "clouds.fbx?i=2",
|
||||
position: position,
|
||||
dimensions: {
|
||||
x: size + CLOUDS_OFFSET,
|
||||
y: size + CLOUDS_OFFSET,
|
||||
z: size + CLOUDS_OFFSET
|
||||
},
|
||||
angularVelocity: {
|
||||
x: 0.00,
|
||||
y: SPIN,
|
||||
z: 0.00
|
||||
},
|
||||
angularDamping: 0.0,
|
||||
damping: 0.0,
|
||||
ignoreCollisions: false,
|
||||
lifetime: LIFETIME,
|
||||
collisionsWillMove: false,
|
||||
visible: true
|
||||
});
|
||||
|
||||
this.zone = Entities.addEntity({
|
||||
type: "Zone",
|
||||
position: position,
|
||||
dimensions: {
|
||||
x: ZONE_DIM,
|
||||
y: ZONE_DIM,
|
||||
z: ZONE_DIM
|
||||
},
|
||||
keyLightDirection: Vec3.normalize(Vec3.subtract(position, Camera.getPosition())),
|
||||
keyLightIntensity: LIGHT_INTENSITY
|
||||
});
|
||||
|
||||
this.cleanup = function() {
|
||||
Entities.deleteEntity(this.clouds);
|
||||
Entities.deleteEntity(this.earth);
|
||||
Entities.deleteEntity(this.zone);
|
||||
}
|
||||
}
|
||||
|
||||
// Create earth model
|
||||
var center = Vec3.sum(Camera.getPosition(), Vec3.multiply(MAX_RANGE, Quat.getFront(Camera.getOrientation())));
|
||||
var distance = Vec3.length(Vec3.subtract(center, Camera.getPosition()));
|
||||
var earth = new Earth(center, EARTH_SIZE);
|
||||
|
||||
var satellites = [];
|
||||
var SATELLITE_SIZE = 2.0;
|
||||
var launched = false;
|
||||
var activeSatellite;
|
||||
|
||||
var PERIOD = 4.0;
|
||||
var LARGE_BODY_MASS = 16000.0;
|
||||
var SMALL_BODY_MASS = LARGE_BODY_MASS * 0.000000333;
|
||||
|
||||
Satellite = function(position, planetCenter) {
|
||||
// The Satellite class
|
||||
|
||||
this.launched = false;
|
||||
this.startPosition = position;
|
||||
this.readyToLaunch = false;
|
||||
this.radius = Vec3.length(Vec3.subtract(position, planetCenter));
|
||||
|
||||
this.satellite = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/satellite/satellite.fbx",
|
||||
position: this.startPosition,
|
||||
dimensions: {
|
||||
x: SATELLITE_SIZE,
|
||||
y: SATELLITE_SIZE,
|
||||
z: SATELLITE_SIZE
|
||||
},
|
||||
angularDamping: 0.0,
|
||||
damping: 0.0,
|
||||
ignoreCollisions: false,
|
||||
lifetime: LIFETIME,
|
||||
collisionsWillMove: false,
|
||||
});
|
||||
|
||||
this.getProperties = function() {
|
||||
return Entities.getEntityProperties(this.satellite);
|
||||
}
|
||||
|
||||
this.launch = function() {
|
||||
var prop = Entities.getEntityProperties(this.satellite);
|
||||
var between = Vec3.subtract(planetCenter, prop.position);
|
||||
var radius = Vec3.length(between);
|
||||
this.gravity = (4.0 * Math.PI * Math.PI * Math.pow(radius, 3.0)) / (LARGE_BODY_MASS * PERIOD * PERIOD);
|
||||
|
||||
var initialVelocity = Vec3.normalize(Vec3.cross(between, Y_AXIS));
|
||||
initialVelocity = Vec3.multiply(Math.sqrt((this.gravity * LARGE_BODY_MASS) / radius), initialVelocity);
|
||||
initialVelocity = Vec3.multiply(this.arrow.magnitude, initialVelocity);
|
||||
initialVelocity = Vec3.multiply(Vec3.length(initialVelocity), this.arrow.direction);
|
||||
|
||||
Entities.editEntity(this.satellite, {
|
||||
velocity: initialVelocity
|
||||
});
|
||||
this.launched = true;
|
||||
};
|
||||
|
||||
|
||||
this.update = function(deltaTime) {
|
||||
var prop = Entities.getEntityProperties(this.satellite);
|
||||
var between = Vec3.subtract(prop.position, planetCenter);
|
||||
var radius = Vec3.length(between);
|
||||
var acceleration = -(this.gravity * LARGE_BODY_MASS) * Math.pow(radius, (-2.0));
|
||||
var speed = acceleration * deltaTime;
|
||||
var vel = Vec3.multiply(speed, Vec3.normalize(between));
|
||||
|
||||
var newVelocity = Vec3.sum(prop.velocity, vel);
|
||||
var newPos = Vec3.sum(prop.position, Vec3.multiply(newVelocity, deltaTime));
|
||||
|
||||
Entities.editEntity(this.satellite, {
|
||||
velocity: newVelocity,
|
||||
position: newPos
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function mouseDoublePressEvent(event) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var addVector = Vec3.multiply(pickRay.direction, distance);
|
||||
var point = Vec3.sum(Camera.getPosition(), addVector);
|
||||
|
||||
// Create a new satellite
|
||||
activeSatellite = new Satellite(point, center);
|
||||
satellites.push(activeSatellite);
|
||||
}
|
||||
|
||||
function mousePressEvent(event) {
|
||||
if (!activeSatellite) {
|
||||
return;
|
||||
}
|
||||
// Reset label
|
||||
if (activeSatellite.arrow) {
|
||||
activeSatellite.arrow.deleteLabel();
|
||||
}
|
||||
var statsPosition = Vec3.sum(Camera.getPosition(), Vec3.multiply(MAX_RANGE * 0.4, Quat.getFront(Camera.getOrientation())));
|
||||
var pickRay = Camera.computePickRay(event.x, event.y)
|
||||
var rayPickResult = Entities.findRayIntersection(pickRay, true);
|
||||
if (rayPickResult.entityID === activeSatellite.satellite) {
|
||||
// Create a draggable vector arrow at satellite position
|
||||
activeSatellite.arrow = new VectorArrow(distance, true, "INITIAL VELOCITY", statsPosition);
|
||||
activeSatellite.arrow.onMousePressEvent(event);
|
||||
activeSatellite.arrow.isDragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
if (!activeSatellite || !activeSatellite.arrow || !activeSatellite.arrow.isDragging) {
|
||||
return;
|
||||
}
|
||||
activeSatellite.arrow.onMouseMoveEvent(event);
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
if (!activeSatellite || !activeSatellite.arrow || !activeSatellite.arrow.isDragging) {
|
||||
return;
|
||||
}
|
||||
activeSatellite.arrow.onMouseReleaseEvent(event);
|
||||
activeSatellite.launch();
|
||||
activeSatellite.arrow.cleanup();
|
||||
}
|
||||
|
||||
var counter = 0.0;
|
||||
var CHECK_ENERGY_PERIOD = 500;
|
||||
|
||||
function update(deltaTime) {
|
||||
if (!activeSatellite) {
|
||||
return;
|
||||
}
|
||||
// Update all satellites
|
||||
for (var i = 0; i < satellites.length; i++) {
|
||||
if (!satellites[i].launched) {
|
||||
continue;
|
||||
}
|
||||
satellites[i].update(deltaTime);
|
||||
}
|
||||
|
||||
counter++;
|
||||
if (counter % CHECK_ENERGY_PERIOD == 0) {
|
||||
var prop = activeSatellite.getProperties();
|
||||
var error = calcEnergyError(prop.position, Vec3.length(prop.velocity));
|
||||
if (Math.abs(error) <= ERROR_THRESH) {
|
||||
activeSatellite.arrow.editLabel("Nice job! The satellite has reached a stable orbit.");
|
||||
} else {
|
||||
activeSatellite.arrow.editLabel("Try again! The satellite is in an unstable orbit.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.endGame = function() {
|
||||
for (var i = 0; i < satellites.length; i++) {
|
||||
Entities.deleteEntity(satellites[i].satellite);
|
||||
satellites[i].arrow.cleanup();
|
||||
}
|
||||
earth.cleanup();
|
||||
}
|
||||
|
||||
|
||||
function calcEnergyError(pos, vel) {
|
||||
//Calculate total energy error for active satellite's orbital motion
|
||||
var radius = activeSatellite.radius;
|
||||
var gravity = (4.0 * Math.PI * Math.PI * Math.pow(radius, 3.0)) / (LARGE_BODY_MASS * PERIOD * PERIOD);
|
||||
var initialVelocityCalculated = Math.sqrt((gravity * LARGE_BODY_MASS) / radius);
|
||||
|
||||
var totalEnergy = 0.5 * LARGE_BODY_MASS * Math.pow(initialVelocityCalculated, 2.0) - ((gravity * LARGE_BODY_MASS * SMALL_BODY_MASS) / radius);
|
||||
var measuredEnergy = 0.5 * LARGE_BODY_MASS * Math.pow(vel, 2.0) - ((gravity * LARGE_BODY_MASS * SMALL_BODY_MASS) / Vec3.length(Vec3.subtract(pos, center)));
|
||||
var error = ((measuredEnergy - totalEnergy) / totalEnergy) * 100;
|
||||
return error;
|
||||
}
|
||||
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseDoublePressEvent.connect(mouseDoublePressEvent);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(this.endGame);
|
||||
|
||||
}
|
669
examples/example/solarsystem.js
Normal file
669
examples/example/solarsystem.js
Normal file
|
@ -0,0 +1,669 @@
|
|||
//
|
||||
// solarsystem.js
|
||||
// games
|
||||
//
|
||||
// Created by Bridget Went, 5/28/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// The start to a project to build a virtual physics classroom to simulate the solar system, gravity, and orbital physics.
|
||||
// - A sun with oribiting planets is created in front of the user
|
||||
// - UI elements allow for adjusting the period, gravity, trails, and energy recalculations
|
||||
// - Click "PAUSE" to pause the animation and show planet labels
|
||||
// - In this mode, double-click a planet label to zoom in on that planet
|
||||
// -Double-clicking on earth label initiates satellite orbiter game
|
||||
// -Press "TAB" to toggle back to solar system view
|
||||
//
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include('../utilities/tools/cookies.js');
|
||||
Script.include('games/satellite.js');
|
||||
|
||||
var BASE_URL = "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/planets/";
|
||||
|
||||
var NUM_PLANETS = 8;
|
||||
|
||||
var trailsEnabled = true;
|
||||
var energyConserved = true;
|
||||
var planetView = false;
|
||||
var earthView = false;
|
||||
var satelliteGame;
|
||||
|
||||
var PANEL_X = 850;
|
||||
var PANEL_Y = 600;
|
||||
var BUTTON_SIZE = 20;
|
||||
var PADDING = 20;
|
||||
|
||||
var DAMPING = 0.0;
|
||||
var LIFETIME = 6000;
|
||||
var ERROR_THRESH = 2.0;
|
||||
var TIME_STEP = 70.0;
|
||||
|
||||
var MAX_POINTS_PER_LINE = 5;
|
||||
var LINE_DIM = 10;
|
||||
var LINE_WIDTH = 3.0;
|
||||
var line;
|
||||
var planetLines = [];
|
||||
var trails = [];
|
||||
|
||||
var BOUNDS = 200;
|
||||
|
||||
|
||||
// Alert user to move if they are too close to domain bounds
|
||||
if (MyAvatar.position.x < BOUNDS || MyAvatar.position.x > TREE_SCALE - BOUNDS || MyAvatar.position.y < BOUNDS || MyAvatar.position.y > TREE_SCALE - BOUNDS || MyAvatar.position.z < BOUNDS || MyAvatar.position.z > TREE_SCALE - BOUNDS) {
|
||||
Window.alert("Please move at least 200m away from domain bounds.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Save intiial avatar and camera position
|
||||
var startingPosition = MyAvatar.position;
|
||||
var startFrame = Window.location.href;
|
||||
|
||||
// Place the sun
|
||||
var MAX_RANGE = 80.0;
|
||||
var SUN_SIZE = 8.0;
|
||||
var center = Vec3.sum(startingPosition, Vec3.multiply(MAX_RANGE, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var theSun = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: BASE_URL + "sun.fbx",
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: SUN_SIZE,
|
||||
y: SUN_SIZE,
|
||||
z: SUN_SIZE
|
||||
},
|
||||
angularDamping: DAMPING,
|
||||
damping: DAMPING,
|
||||
ignoreCollisions: false,
|
||||
lifetime: LIFETIME,
|
||||
collisionsWillMove: false
|
||||
});
|
||||
|
||||
var planets = [];
|
||||
var planet_properties = [];
|
||||
|
||||
// Reference values
|
||||
var radius = 7.0;
|
||||
var T_ref = 1.0;
|
||||
var size = 1.0;
|
||||
var M = 250.0;
|
||||
var m = M * 0.000000333;
|
||||
var G = (Math.pow(radius, 3.0) / Math.pow((T_ref / (2.0 * Math.PI)), 2.0)) / M;
|
||||
var G_ref = G;
|
||||
|
||||
// Adjust size and distance as number of planets increases
|
||||
var DELTA_RADIUS = 1.8;
|
||||
var DELTA_SIZE = 0.2;
|
||||
|
||||
function initPlanets() {
|
||||
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||
var v0 = Math.sqrt((G * M) / radius);
|
||||
var T = (2.0 * Math.PI) * Math.sqrt(Math.pow(radius, 3.0) / (G * M));
|
||||
|
||||
if (i == 0) {
|
||||
var color = {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
};
|
||||
} else if (i == 1) {
|
||||
var color = {
|
||||
red: 255,
|
||||
green: 160,
|
||||
blue: 110
|
||||
};
|
||||
} else if (i == 2) {
|
||||
var color = {
|
||||
red: 10,
|
||||
green: 150,
|
||||
blue: 160
|
||||
};
|
||||
} else if (i == 3) {
|
||||
var color = {
|
||||
red: 180,
|
||||
green: 70,
|
||||
blue: 10
|
||||
};
|
||||
} else if (i == 4) {
|
||||
var color = {
|
||||
red: 250,
|
||||
green: 140,
|
||||
blue: 0
|
||||
};
|
||||
} else if (i == 5) {
|
||||
var color = {
|
||||
red: 235,
|
||||
green: 215,
|
||||
blue: 0
|
||||
};
|
||||
} else if (i == 6) {
|
||||
var color = {
|
||||
red: 135,
|
||||
green: 205,
|
||||
blue: 240
|
||||
};
|
||||
} else if (i == 7) {
|
||||
var color = {
|
||||
red: 30,
|
||||
green: 140,
|
||||
blue: 255
|
||||
};
|
||||
}
|
||||
|
||||
var prop = {
|
||||
radius: radius,
|
||||
position: Vec3.sum(center, {
|
||||
x: radius,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
}),
|
||||
lineColor: color,
|
||||
period: T,
|
||||
dimensions: size,
|
||||
velocity: Vec3.multiply(v0, Vec3.normalize({
|
||||
x: 0,
|
||||
y: -0.2,
|
||||
z: 0.9
|
||||
}))
|
||||
};
|
||||
planet_properties.push(prop);
|
||||
|
||||
planets.push(Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: BASE_URL + (i + 1) + ".fbx",
|
||||
position: prop.position,
|
||||
dimensions: {
|
||||
x: prop.dimensions,
|
||||
y: prop.dimensions,
|
||||
z: prop.dimensions
|
||||
},
|
||||
velocity: prop.velocity,
|
||||
angularDamping: DAMPING,
|
||||
damping: DAMPING,
|
||||
ignoreCollisions: false,
|
||||
lifetime: LIFETIME,
|
||||
collisionsWillMove: true,
|
||||
}));
|
||||
|
||||
radius *= DELTA_RADIUS;
|
||||
size += DELTA_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize planets
|
||||
initPlanets();
|
||||
|
||||
|
||||
var labels = [];
|
||||
var labelLines = [];
|
||||
var labelsShowing = false;
|
||||
var LABEL_X = 8.0;
|
||||
var LABEL_Y = 3.0;
|
||||
var LABEL_Z = 1.0;
|
||||
var LABEL_DIST = 8.0;
|
||||
var TEXT_HEIGHT = 1.0;
|
||||
var sunLabel;
|
||||
|
||||
function showLabels() {
|
||||
labelsShowing = true;
|
||||
for (var i = 0; i < NUM_PLANETS; i++) {
|
||||
var properties = planet_properties[i];
|
||||
var text;
|
||||
if (i == 0) {
|
||||
text = "Mercury";
|
||||
} else if (i == 1) {
|
||||
text = "Venus";
|
||||
} else if (i == 2) {
|
||||
text = "Earth";
|
||||
} else if (i == 3) {
|
||||
text = "Mars";
|
||||
} else if (i == 4) {
|
||||
text = "Jupiter";
|
||||
} else if (i == 5) {
|
||||
text = "Saturn";
|
||||
} else if (i == 6) {
|
||||
text = "Uranus";
|
||||
} else if (i == 7) {
|
||||
text = "Neptune";
|
||||
}
|
||||
|
||||
text = text + " Speed: " + Vec3.length(properties.velocity).toFixed(2);
|
||||
|
||||
var labelPos = Vec3.sum(planet_properties[i].position, {
|
||||
x: 0.0,
|
||||
y: LABEL_DIST,
|
||||
z: LABEL_DIST
|
||||
});
|
||||
var linePos = planet_properties[i].position;
|
||||
labelLines.push(Entities.addEntity({
|
||||
type: "Line",
|
||||
position: linePos,
|
||||
dimensions: {
|
||||
x: 20,
|
||||
y: 20,
|
||||
z: 20
|
||||
},
|
||||
lineWidth: 3.0,
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
linePoints: [{
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}, computeLocalPoint(linePos, labelPos)]
|
||||
}));
|
||||
|
||||
labels.push(Entities.addEntity({
|
||||
type: "Text",
|
||||
text: text,
|
||||
lineHeight: TEXT_HEIGHT,
|
||||
dimensions: {
|
||||
x: LABEL_X,
|
||||
y: LABEL_Y,
|
||||
z: LABEL_Z
|
||||
},
|
||||
position: labelPos,
|
||||
backgroundColor: {
|
||||
red: 10,
|
||||
green: 10,
|
||||
blue: 10
|
||||
},
|
||||
textColor: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
faceCamera: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function hideLabels() {
|
||||
labelsShowing = false;
|
||||
Entities.deleteEntity(sunLabel);
|
||||
|
||||
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||
Entities.deleteEntity(labelLines[i]);
|
||||
Entities.deleteEntity(labels[i]);
|
||||
}
|
||||
labels = [];
|
||||
labelLines = [];
|
||||
}
|
||||
|
||||
var time = 0.0;
|
||||
var elapsed;
|
||||
var counter = 0;
|
||||
var dt = 1.0 / TIME_STEP;
|
||||
|
||||
function update(deltaTime) {
|
||||
if (paused) {
|
||||
return;
|
||||
}
|
||||
deltaTime = dt;
|
||||
time++;
|
||||
|
||||
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||
var properties = planet_properties[i];
|
||||
var between = Vec3.subtract(properties.position, center);
|
||||
var speed = getAcceleration(properties.radius) * deltaTime;
|
||||
var vel = Vec3.multiply(speed, Vec3.normalize(between));
|
||||
|
||||
// Update velocity and position
|
||||
properties.velocity = Vec3.sum(properties.velocity, vel);
|
||||
properties.position = Vec3.sum(properties.position, Vec3.multiply(properties.velocity, deltaTime));
|
||||
Entities.editEntity(planets[i], {
|
||||
velocity: properties.velocity,
|
||||
position: properties.position
|
||||
});
|
||||
|
||||
|
||||
// Create new or update current trail
|
||||
if (trailsEnabled) {
|
||||
var lineStack = planetLines[i];
|
||||
var point = properties.position;
|
||||
var prop = Entities.getEntityProperties(lineStack[lineStack.length - 1]);
|
||||
var linePos = prop.position;
|
||||
|
||||
trails[i].push(computeLocalPoint(linePos, point));
|
||||
|
||||
Entities.editEntity(lineStack[lineStack.length - 1], {
|
||||
linePoints: trails[i]
|
||||
});
|
||||
if (trails[i].length === MAX_POINTS_PER_LINE) {
|
||||
trails[i] = newLine(lineStack, point, properties.period, properties.lineColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Measure total energy every 10 updates, recalibrate velocity if necessary
|
||||
if (energyConserved) {
|
||||
if (counter % 10 === 0) {
|
||||
var error = calcEnergyError(planets[i], properties.radius, properties.v0, properties.velocity, properties.position);
|
||||
if (Math.abs(error) >= ERROR_THRESH) {
|
||||
var speed = adjustVelocity(planets[i], properties.position);
|
||||
properties.velocity = Vec3.multiply(speed, Vec3.normalize(properties.velocity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
if (time % TIME_STEP == 0) {
|
||||
elapsed++;
|
||||
}
|
||||
}
|
||||
|
||||
function computeLocalPoint(linePos, worldPoint) {
|
||||
var localPoint = Vec3.subtract(worldPoint, linePos);
|
||||
return localPoint;
|
||||
}
|
||||
|
||||
function getAcceleration(radius) {
|
||||
var acc = -(G * M) * Math.pow(radius, (-2.0));
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Create a new trail
|
||||
function resetTrails(planetIndex) {
|
||||
elapsed = 0.0;
|
||||
var properties = planet_properties[planetIndex];
|
||||
|
||||
var trail = [];
|
||||
var lineStack = [];
|
||||
|
||||
//add the first line to both the line entity stack and the trail
|
||||
trails.push(newLine(lineStack, properties.position, properties.period, properties.lineColor));
|
||||
planetLines.push(lineStack);
|
||||
}
|
||||
|
||||
// Create a new line
|
||||
function newLine(lineStack, point, period, color) {
|
||||
if (elapsed < period) {
|
||||
var line = Entities.addEntity({
|
||||
position: point,
|
||||
type: "Line",
|
||||
color: color,
|
||||
dimensions: {
|
||||
x: LINE_DIM,
|
||||
y: LINE_DIM,
|
||||
z: LINE_DIM
|
||||
},
|
||||
lifetime: LIFETIME,
|
||||
lineWidth: LINE_WIDTH
|
||||
});
|
||||
lineStack.push(line);
|
||||
} else {
|
||||
// Begin overwriting first lines after one full revolution (one period)
|
||||
var firstLine = lineStack.shift();
|
||||
Entities.editEntity(firstLine, {
|
||||
position: point,
|
||||
linePoints: [{
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
}]
|
||||
});
|
||||
lineStack.push(firstLine);
|
||||
|
||||
}
|
||||
var points = [];
|
||||
points.push(computeLocalPoint(point, point));
|
||||
return points;
|
||||
}
|
||||
|
||||
// Measure energy error, recalculate velocity to return to initial net energy
|
||||
var totalEnergy;
|
||||
var measuredEnergy;
|
||||
var measuredPE;
|
||||
|
||||
function calcEnergyError(planet, radius, v0, v, pos) {
|
||||
totalEnergy = 0.5 * M * Math.pow(v0, 2.0) - ((G * M * m) / radius);
|
||||
measuredEnergy = 0.5 * M * Math.pow(v, 2.0) - ((G * M * m) / Vec3.length(Vec3.subtract(center, pos)));
|
||||
var error = ((measuredEnergy - totalEnergy) / totalEnergy) * 100;
|
||||
return error;
|
||||
}
|
||||
|
||||
function adjustVelocity(planet, pos) {
|
||||
var measuredPE = -(G * M * m) / Vec3.length(Vec3.subtract(center, pos));
|
||||
return Math.sqrt(2 * (totalEnergy - measuredPE) / M);
|
||||
}
|
||||
|
||||
|
||||
// Allow user to toggle pausing the model, switch to planet view
|
||||
var pauseButton = Overlays.addOverlay("text", {
|
||||
backgroundColor: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 255
|
||||
},
|
||||
text: "Pause",
|
||||
x: PANEL_X,
|
||||
y: PANEL_Y - 30,
|
||||
width: 70,
|
||||
height: 20,
|
||||
alpha: 1.0,
|
||||
backgroundAlpha: 0.5,
|
||||
visible: true
|
||||
});
|
||||
|
||||
var paused = false;
|
||||
|
||||
function mousePressEvent(event) {
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({
|
||||
x: event.x,
|
||||
y: event.y
|
||||
});
|
||||
if (clickedOverlay == pauseButton) {
|
||||
paused = !paused;
|
||||
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||
Entities.editEntity(planets[i], {
|
||||
velocity: {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
}
|
||||
});
|
||||
}
|
||||
if (paused && !labelsShowing) {
|
||||
Overlays.editOverlay(pauseButton, {
|
||||
text: "Paused",
|
||||
backgroundColor: {
|
||||
red: 255,
|
||||
green: 50,
|
||||
blue: 50
|
||||
}
|
||||
});
|
||||
showLabels();
|
||||
}
|
||||
if (paused == false && labelsShowing) {
|
||||
Overlays.editOverlay(pauseButton, {
|
||||
text: "Pause",
|
||||
backgroundColor: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 255
|
||||
}
|
||||
});
|
||||
hideLabels();
|
||||
}
|
||||
planetView = false;
|
||||
}
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
// Jump back to solar system view
|
||||
if (event.text == "TAB" && planetView) {
|
||||
if (earthView) {
|
||||
satelliteGame.endGame();
|
||||
earthView = false;
|
||||
}
|
||||
MyAvatar.position = startingPosition;
|
||||
}
|
||||
}
|
||||
|
||||
function mouseDoublePressEvent(event) {
|
||||
if (earthView) {
|
||||
return;
|
||||
}
|
||||
var pickRay = Camera.computePickRay(event.x, event.y)
|
||||
var rayPickResult = Entities.findRayIntersection(pickRay, true);
|
||||
|
||||
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||
if (rayPickResult.entityID === labels[i]) {
|
||||
planetView = true;
|
||||
if (i == 2) {
|
||||
MyAvatar.position = Vec3.sum(center, {
|
||||
x: 200,
|
||||
y: 200,
|
||||
z: 200
|
||||
});
|
||||
Camera.setPosition(Vec3.sum(center, {
|
||||
x: 200,
|
||||
y: 200,
|
||||
z: 200
|
||||
}));
|
||||
earthView = true;
|
||||
satelliteGame = new SatelliteGame();
|
||||
|
||||
} else {
|
||||
MyAvatar.position = Vec3.sum({
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 3.0
|
||||
}, planet_properties[i].position);
|
||||
Camera.lookAt(planet_properties[i].position);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Create UI panel
|
||||
var panel = new Panel(PANEL_X, PANEL_Y);
|
||||
var panelItems = [];
|
||||
|
||||
var g_multiplier = 1.0;
|
||||
panelItems.push(panel.newSlider("Adjust Gravitational Force: ", 0.1, 5.0,
|
||||
function(value) {
|
||||
g_multiplier = value;
|
||||
G = G_ref * g_multiplier;
|
||||
},
|
||||
|
||||
function() {
|
||||
return g_multiplier;
|
||||
},
|
||||
function(value) {
|
||||
return value.toFixed(1) + "x";
|
||||
}));
|
||||
|
||||
var period_multiplier = 1.0;
|
||||
var last_alpha = period_multiplier;
|
||||
panelItems.push(panel.newSlider("Adjust Orbital Period: ", 0.1, 3.0,
|
||||
function(value) {
|
||||
period_multiplier = value;
|
||||
changePeriod(period_multiplier);
|
||||
},
|
||||
function() {
|
||||
return period_multiplier;
|
||||
},
|
||||
function(value) {
|
||||
return (value).toFixed(2) + "x";
|
||||
}));
|
||||
|
||||
panelItems.push(panel.newCheckbox("Leave Trails: ",
|
||||
function(value) {
|
||||
trailsEnabled = value;
|
||||
if (trailsEnabled) {
|
||||
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||
resetTrails(i);
|
||||
}
|
||||
//if trails are off and we've already created trails, remove existing trails
|
||||
} else if (planetLines.length != 0) {
|
||||
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||
for (var j = 0; j < planetLines[i].length; ++j) {
|
||||
Entities.deleteEntity(planetLines[i][j]);
|
||||
}
|
||||
planetLines[i] = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
function() {
|
||||
return trailsEnabled;
|
||||
},
|
||||
function(value) {
|
||||
return value;
|
||||
}));
|
||||
|
||||
panelItems.push(panel.newCheckbox("Energy Error Calculations: ",
|
||||
function(value) {
|
||||
energyConserved = value;
|
||||
},
|
||||
function() {
|
||||
return energyConserved;
|
||||
},
|
||||
function(value) {
|
||||
return value;
|
||||
}));
|
||||
|
||||
// Update global G constant, period, poke velocity to new value
|
||||
function changePeriod(alpha) {
|
||||
var ratio = last_alpha / alpha;
|
||||
G = Math.pow(ratio, 2.0) * G;
|
||||
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||
var properties = planet_properties[i];
|
||||
properties.period = ratio * properties.period;
|
||||
properties.velocity = Vec3.multiply(ratio, properties.velocity);
|
||||
|
||||
}
|
||||
last_alpha = alpha;
|
||||
}
|
||||
|
||||
|
||||
// Clean up models, UI panels, lines, and button overlays
|
||||
function scriptEnding() {
|
||||
|
||||
satelliteGame.endGame();
|
||||
|
||||
Entities.deleteEntity(theSun);
|
||||
for (var i = 0; i < NUM_PLANETS; ++i) {
|
||||
Entities.deleteEntity(planets[i]);
|
||||
}
|
||||
Menu.removeMenu("Developer > Scene");
|
||||
panel.destroy();
|
||||
Overlays.deleteOverlay(pauseButton);
|
||||
|
||||
var e = Entities.findEntities(MyAvatar.position, 16000);
|
||||
for (i = 0; i < e.length; i++) {
|
||||
var props = Entities.getEntityProperties(e[i]);
|
||||
if (props.type === "Line" || props.type === "Text") {
|
||||
Entities.deleteEntity(e[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) {
|
||||
return panel.mouseMoveEvent(event);
|
||||
});
|
||||
Controller.mousePressEvent.connect(function panelMousePressEvent(event) {
|
||||
return panel.mousePressEvent(event);
|
||||
});
|
||||
Controller.mouseDoublePressEvent.connect(function panelMouseDoublePressEvent(event) {
|
||||
return panel.mouseDoublePressEvent(event);
|
||||
});
|
||||
Controller.mouseReleaseEvent.connect(function(event) {
|
||||
return panel.mouseReleaseEvent(event);
|
||||
});
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseDoublePressEvent.connect(mouseDoublePressEvent);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.update.connect(update);
|
169
examples/example/ui/floatingUIExample.js
Normal file
169
examples/example/ui/floatingUIExample.js
Normal 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
78
examples/gridTest.js
Normal 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);
|
||||
|
|
@ -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
|
||||
|
|
42
examples/html/jsstreamplayer.html
Normal file
42
examples/html/jsstreamplayer.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<!-- -->
|
||||
<!-- #20622: JS Stream Player -->
|
||||
<!-- ************************* -->
|
||||
<!-- -->
|
||||
<!-- Created by Kevin M. Thomas and Thoys 07/17/15. -->
|
||||
<!-- Copyright 2015 High Fidelity, Inc. -->
|
||||
<!-- kevintown.net -->
|
||||
<!-- -->
|
||||
<!-- JavaScript for the High Fidelity interface that creates a stream player with a UI and keyPressEvents for adding a stream URL in addition to play, stop and volume functionality. -->
|
||||
<!-- -->
|
||||
<!-- Distributed under the Apache License, Version 2.0. -->
|
||||
<!-- See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -->
|
||||
<!-- -->
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
if (window.EventBridge !== undefined) {
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
var myData = JSON.parse(data);
|
||||
if (myData.action == "changeStream") {
|
||||
$('body > audio').attr("src", myData.stream);
|
||||
}
|
||||
if (myData.action == "changeVolume") {
|
||||
$('body > audio').prop("volume", myData.volume);
|
||||
}
|
||||
});
|
||||
}
|
||||
EventBridge.emitWebEvent("loaded");
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<audio controls src="" controls autoplay></audio>
|
||||
</body>
|
||||
</html>
|
140
examples/html/plankySettings.html
Normal file
140
examples/html/plankySettings.html
Normal file
|
@ -0,0 +1,140 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var properties = [];
|
||||
function sendWebEvent(data) {
|
||||
EventBridge.emitWebEvent(JSON.stringify(data));
|
||||
}
|
||||
PropertyInput = function(key, label, value, attributes) {
|
||||
this.key = key;
|
||||
this.label = label;
|
||||
this.value = value;
|
||||
this.attributes = attributes;
|
||||
var self = this;
|
||||
this.construct = function() {
|
||||
self.widget = $('<div>').addClass('property').append(self.createLabel()).append(self.createValueDiv());
|
||||
$('#properties-list').append(self.widget);
|
||||
};
|
||||
this.createValue = self.__proto__.createValue;
|
||||
this.getValue = self.__proto__.getValue;
|
||||
this.createValueDiv = function() {
|
||||
self.inputDiv = $('<div>').addClass('value').append(self.createValue());
|
||||
return self.inputDiv;
|
||||
};
|
||||
this.addButton = function(id, buttonText) {
|
||||
self.inputDiv.append($('<div>').append($('<input>').attr('type', 'button').attr('id', id).val(buttonText)));
|
||||
};
|
||||
this.createWidget = function() {
|
||||
self.widget = $('<div>').addClass('property').append(self.createLabel()).append(self.inputDiv);
|
||||
return self.widget;
|
||||
};
|
||||
this.createLabel = function() {
|
||||
self.label = $('<div>').addClass('label').text(label);
|
||||
return self.label;
|
||||
};
|
||||
this.setValue = function(value) {
|
||||
self.input.val(value);
|
||||
};
|
||||
this.construct();
|
||||
};
|
||||
|
||||
var valueChangeHandler = function() {
|
||||
|
||||
sendWebEvent({
|
||||
action: 'value-change',
|
||||
option: $(this).data('var-name'),
|
||||
value: properties[$(this).data('var-name')].getValue()
|
||||
});
|
||||
};
|
||||
|
||||
NumberInput = function(key, label, value, attributes) {
|
||||
PropertyInput.call(this, key, label, value, attributes);
|
||||
};
|
||||
NumberInput.prototype = Object.create(PropertyInput.prototype);
|
||||
NumberInput.prototype.constructor = NumberInput;
|
||||
NumberInput.prototype.createValue = function() {
|
||||
this.input = $('<input>').data('var-name', this.key).attr('name', this.key).attr('type', 'number').val(this.value).on('change', valueChangeHandler);
|
||||
if (this.attributes !== undefined) {
|
||||
this.input.attr(this.attributes);
|
||||
}
|
||||
return this.input;
|
||||
};
|
||||
NumberInput.prototype.getValue = function() {
|
||||
return parseFloat(this.input.val());
|
||||
};
|
||||
|
||||
CoordinateInput = function(key, label, value, attributes) {
|
||||
PropertyInput.call(this, key, label, value, attributes);
|
||||
};
|
||||
CoordinateInput.prototype = Object.create(PropertyInput.prototype);
|
||||
CoordinateInput.prototype.constructor = CoordinateInput;
|
||||
CoordinateInput.prototype.createValue = function() {
|
||||
this.inputX = $('<input>').data('var-name', this.key).attr('name', this.key + '-x').attr('type', 'number').addClass('coord').val(this.value.x).on('change', valueChangeHandler);
|
||||
this.inputY = $('<input>').data('var-name', this.key).attr('name', this.key + '-y').attr('type', 'number').addClass('coord').val(this.value.y).on('change', valueChangeHandler);
|
||||
this.inputZ = $('<input>').data('var-name', this.key).attr('name', this.key + '-z').attr('type', 'number').addClass('coord').val(this.value.z).on('change', valueChangeHandler);
|
||||
if (this.attributes !== undefined) {
|
||||
this.inputX.attr(this.attributes);
|
||||
this.inputY.attr(this.attributes);
|
||||
this.inputZ.attr(this.attributes);
|
||||
}
|
||||
return [encapsulateInput(this.inputX, 'X'), encapsulateInput(this.inputY, 'Y'), encapsulateInput(this.inputZ, 'Z')];
|
||||
};
|
||||
CoordinateInput.prototype.getValue = function() {
|
||||
return {x: parseFloat(this.inputX.val()), y: parseFloat(this.inputY.val()), z: parseFloat(this.inputZ.val())};
|
||||
};
|
||||
function encapsulateInput(input, label) {
|
||||
return $('<div>').addClass('input-area').append(label + ' ').append(input);
|
||||
}
|
||||
|
||||
function addHeader(label) {
|
||||
$('#properties-list').append($('<div>').addClass('section-header').append($('<label>').text(label)));
|
||||
}
|
||||
|
||||
$(function() {
|
||||
addHeader('Stack Settings');
|
||||
properties['numLayers'] = new NumberInput('numLayers', 'Layers', 17, {'min': 0, 'max': 300, 'step': 1});
|
||||
properties['blocksPerLayer'] = new NumberInput('blocksPerLayer', 'Blocks per layer', 4, {'min': 1, 'max': 100, 'step': 1});
|
||||
properties['blockSize'] = new CoordinateInput('blockSize', 'Block size', {x: 0.2, y: 0.1, z: 0.8}, {'min': 0.05, 'max': 20, 'step': 0.1});
|
||||
properties['blockSpacing'] = new NumberInput('blockSpacing', 'Block spacing', properties['blockSize'].getValue().x / properties['blocksPerLayer'].getValue(), {'min': 0, 'max': 20, 'step': 0.01});
|
||||
properties['blockSpacing'].addButton('btn-recalculate-spacing', 'Recalculate spacing');
|
||||
$('#btn-recalculate-spacing').on('click', function() {
|
||||
properties['blockSpacing'].setValue(properties['blockSize'].getValue().x / properties['blocksPerLayer'].getValue());
|
||||
});
|
||||
properties['blockHeightVariation'] = new NumberInput('blockHeightVariation', 'Block height variation (%)', 0.1, {'min': 0, 'max': 1, 'step': 0.01});
|
||||
addHeader('Physics Settings');
|
||||
properties['gravity'] = new CoordinateInput('gravity', 'Gravity', {x: 0, y: -2.8, z: 0}, {'step': 0.01});
|
||||
properties['density'] = new NumberInput('density', 'Density', 4000, {'min': 0, 'max': 4000, 'step': 1});
|
||||
properties['dampingFactor'] = new NumberInput('dampingFactor', 'Damping factor', 0.98, {'min': 0, 'max': 1, 'step': 0.01});
|
||||
properties['angularDampingFactor'] = new NumberInput('angularDampingFactor', 'Angular damping factor', 0.8, {'min': 0, 'max': 1, 'step': 0.01});
|
||||
properties['friction'] = new NumberInput('friction', 'Friction', 0.99, {'min': 0, 'max': 1, 'step': 0.01});
|
||||
properties['restitution'] = new NumberInput('restitution', 'Restitution', 0.0, {'min': 0, 'max': 1, 'step': 0.01});
|
||||
addHeader('Spawn Settings');
|
||||
properties['spawnDistance'] = new NumberInput('spawnDistance', 'Spawn distance (meters)', 3);
|
||||
properties['blockYawOffset'] = new NumberInput('blockYawOffset', 'Block yaw offset (degrees)', 45, {'min': 0, 'max': 360, 'step': 1});
|
||||
properties['baseDimension'] = new CoordinateInput('baseDimension', 'Base dimension', {x: 7, y: 2, z: 7}, {'min': 0.5, 'max': 200, 'step': 0.1});
|
||||
addHeader('Actions');
|
||||
$('#properties-list')
|
||||
.append($('<input>').val('factory reset').attr('type', 'button').on('click', function() { sendWebEvent({action: 'factory-reset'}); }))
|
||||
.append($('<input>').val('save as default').attr('type', 'button').on('click', function() { sendWebEvent({action: 'save-default'}); }))
|
||||
.append($('<input>').val('cleanup planky').attr('type', 'button').on('click', function() { sendWebEvent({action: 'cleanup'}); }));
|
||||
if (window.EventBridge !== undefined) {
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
if (data.action == 'load') {
|
||||
$.each(data.options, function(option, value) {
|
||||
properties[option].setValue(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
sendWebEvent({action: 'loaded'});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body class="properties">
|
||||
<div id="properties-list"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -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
331
examples/leaves.js
Executable file
|
@ -0,0 +1,331 @@
|
|||
//
|
||||
// Leaves.js
|
||||
// examples
|
||||
//
|
||||
// Created by Bing Shearer on 14 Jul 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
var leafName = "scriptLeaf";
|
||||
var leafSquall = function (properties) {
|
||||
var // Properties
|
||||
squallOrigin,
|
||||
squallRadius,
|
||||
leavesPerMinute = 60,
|
||||
leafSize = {
|
||||
x: 0.1,
|
||||
y: 0.1,
|
||||
z: 0.1
|
||||
},
|
||||
leafFallSpeed = 1, // m/s
|
||||
leafLifetime = 60, // Seconds
|
||||
leafSpinMax = 0, // Maximum angular velocity per axis; deg/s
|
||||
debug = false, // Display origin circle; don't use running on Stack Manager
|
||||
// Other
|
||||
squallCircle,
|
||||
SQUALL_CIRCLE_COLOR = {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
SQUALL_CIRCLE_ALPHA = 0.5,
|
||||
SQUALL_CIRCLE_ROTATION = Quat.fromPitchYawRollDegrees(90, 0, 0),
|
||||
leafProperties,
|
||||
leaf_MODEL_URL = "https://hifi-public.s3.amazonaws.com/ozan/support/forBing/palmLeaf.fbx",
|
||||
leafTimer,
|
||||
leaves = [], // HACK: Work around leaves not always getting velocities
|
||||
leafVelocities = [], // HACK: Work around leaves not always getting velocities
|
||||
DEGREES_TO_RADIANS = Math.PI / 180,
|
||||
leafDeleteOnTearDown = true,
|
||||
maxLeaves,
|
||||
leafCount,
|
||||
nearbyEntities,
|
||||
complexMovement = false,
|
||||
movementTime = 0,
|
||||
maxSpinRadians = properties.leafSpinMax * DEGREES_TO_RADIANS,
|
||||
windFactor,
|
||||
leafDeleteOnGround = false,
|
||||
floorHeight = null;
|
||||
|
||||
|
||||
function processProperties() {
|
||||
if (!properties.hasOwnProperty("origin")) {
|
||||
print("ERROR: Leaf squall origin must be specified");
|
||||
return;
|
||||
}
|
||||
squallOrigin = properties.origin;
|
||||
|
||||
if (!properties.hasOwnProperty("radius")) {
|
||||
print("ERROR: Leaf squall radius must be specified");
|
||||
return;
|
||||
}
|
||||
squallRadius = properties.radius;
|
||||
|
||||
if (properties.hasOwnProperty("leavesPerMinute")) {
|
||||
leavesPerMinute = properties.leavesPerMinute;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("leafSize")) {
|
||||
leafSize = properties.leafSize;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("leafFallSpeed")) {
|
||||
leafFallSpeed = properties.leafFallSpeed;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("leafLifetime")) {
|
||||
leafLifetime = properties.leafLifetime;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("leafSpinMax")) {
|
||||
leafSpinMax = properties.leafSpinMax;
|
||||
}
|
||||
|
||||
if (properties.hasOwnProperty("debug")) {
|
||||
debug = properties.debug;
|
||||
}
|
||||
if (properties.hasOwnProperty("floorHeight")) {
|
||||
floorHeight = properties.floorHeight;
|
||||
}
|
||||
if (properties.hasOwnProperty("maxLeaves")) {
|
||||
maxLeaves = properties.maxLeaves;
|
||||
}
|
||||
if (properties.hasOwnProperty("complexMovement")) {
|
||||
complexMovement = properties.complexMovement;
|
||||
}
|
||||
if (properties.hasOwnProperty("leafDeleteOnGround")) {
|
||||
leafDeleteOnGround = properties.leafDeleteOnGround;
|
||||
}
|
||||
if (properties.hasOwnProperty("windFactor")) {
|
||||
windFactor = properties.windFactor;
|
||||
} else if (complexMovement == true){
|
||||
print("ERROR: Wind Factor must be defined for complex movement")
|
||||
}
|
||||
|
||||
leafProperties = {
|
||||
type: "Model",
|
||||
name: leafName,
|
||||
modelURL: leaf_MODEL_URL,
|
||||
lifetime: leafLifetime,
|
||||
dimensions: leafSize,
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -leafFallSpeed,
|
||||
z: 0
|
||||
},
|
||||
damping: 0,
|
||||
angularDamping: 0,
|
||||
ignoreForCollisions: true
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function createleaf() {
|
||||
var angle,
|
||||
radius,
|
||||
offset,
|
||||
leaf,
|
||||
spin = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
i;
|
||||
|
||||
// HACK: Work around leaves not always getting velocities set at creation
|
||||
for (i = 0; i < leaves.length; i++) {
|
||||
Entities.editEntity(leaves[i], leafVelocities[i]);
|
||||
}
|
||||
|
||||
angle = Math.random() * leafSpinMax;
|
||||
radius = Math.random() * squallRadius;
|
||||
offset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, angle, 0), {
|
||||
x: 0,
|
||||
y: -0.1,
|
||||
z: radius
|
||||
});
|
||||
leafProperties.position = Vec3.sum(squallOrigin, offset);
|
||||
if (properties.leafSpinMax > 0 && !complexMovement) {
|
||||
spin = {
|
||||
x: Math.random() * maxSpinRadians,
|
||||
y: Math.random() * maxSpinRadians,
|
||||
z: Math.random() * maxSpinRadians
|
||||
};
|
||||
leafProperties.angularVelocity = spin;
|
||||
} else if (complexMovement) {
|
||||
spin = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
leafProperties.angularVelocity = spin
|
||||
}
|
||||
leaf = Entities.addEntity(leafProperties);
|
||||
|
||||
// HACK: Work around leaves not always getting velocities set at creation
|
||||
leaves.push(leaf);
|
||||
leafVelocities.push({
|
||||
velocity: leafProperties.velocity,
|
||||
angularVelocity: spin
|
||||
});
|
||||
if (leaves.length > 5) {
|
||||
leaves.shift();
|
||||
leafVelocities.shift();
|
||||
}
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
if (debug) {
|
||||
squallCircle = Overlays.addOverlay("circle3d", {
|
||||
size: {
|
||||
x: 2 * squallRadius,
|
||||
y: 2 * squallRadius
|
||||
},
|
||||
color: SQUALL_CIRCLE_COLOR,
|
||||
alpha: SQUALL_CIRCLE_ALPHA,
|
||||
solid: true,
|
||||
visible: debug,
|
||||
position: squallOrigin,
|
||||
rotation: SQUALL_CIRCLE_ROTATION
|
||||
});
|
||||
}
|
||||
|
||||
leafTimer = Script.setInterval(function () {
|
||||
if (leafCount <= maxLeaves - 1) {
|
||||
createleaf()
|
||||
}
|
||||
}, 60000 / leavesPerMinute);
|
||||
}
|
||||
Script.setInterval(function () {
|
||||
nearbyEntities = Entities.findEntities(squallOrigin, squallRadius);
|
||||
newLeafMovement()
|
||||
}, 100);
|
||||
|
||||
function newLeafMovement() { //new additions to leaf code. Operates at 10 Hz or every 100 ms
|
||||
movementTime += 0.1;
|
||||
var currentLeaf,
|
||||
randomRotationSpeed = {
|
||||
x: maxSpinRadians * Math.sin(movementTime),
|
||||
y: maxSpinRadians * Math.random(),
|
||||
z: maxSpinRadians * Math.sin(movementTime / 7)
|
||||
};
|
||||
for (var i = 0; i < nearbyEntities.length; i++) {
|
||||
var entityProperties = Entities.getEntityProperties(nearbyEntities[i]);
|
||||
var entityName = entityProperties.name;
|
||||
if (leafName === entityName) {
|
||||
currentLeaf = nearbyEntities[i];
|
||||
var leafHeight = entityProperties.position.y;
|
||||
if (complexMovement && leafHeight > floorHeight || complexMovement && floorHeight == null) { //actual new movement code;
|
||||
var leafCurrentVel = entityProperties.velocity,
|
||||
leafCurrentRot = entityProperties.rotation,
|
||||
yVec = {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
},
|
||||
leafYinWFVec = Vec3.multiplyQbyV(leafCurrentRot, yVec),
|
||||
leafLocalHorVec = Vec3.cross(leafYinWFVec, yVec),
|
||||
leafMostDownVec = Vec3.cross(leafYinWFVec, leafLocalHorVec),
|
||||
leafDesiredVel = Vec3.multiply(leafMostDownVec, windFactor),
|
||||
leafVelDelt = Vec3.subtract(leafDesiredVel, leafCurrentVel),
|
||||
leafNewVel = Vec3.sum(leafCurrentVel, Vec3.multiply(leafVelDelt, windFactor));
|
||||
Entities.editEntity(currentLeaf, {
|
||||
angularVelocity: randomRotationSpeed,
|
||||
velocity: leafNewVel
|
||||
})
|
||||
} else if (leafHeight <= floorHeight) {
|
||||
if (!leafDeleteOnGround) {
|
||||
Entities.editEntity(nearbyEntities[i], {
|
||||
locked: false,
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
angularVelocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Entity.deleteEntity(currentLeaf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
getLeafCount = Script.setInterval(function () {
|
||||
leafCount = 0
|
||||
for (var i = 0; i < nearbyEntities.length; i++) {
|
||||
var entityName = Entities.getEntityProperties(nearbyEntities[i]).name;
|
||||
//Stop Leaves at floorHeight
|
||||
if (leafName === entityName) {
|
||||
leafCount++;
|
||||
if (i == nearbyEntities.length - 1) {
|
||||
//print(leafCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
|
||||
|
||||
function tearDown() {
|
||||
Script.clearInterval(leafTimer);
|
||||
Overlays.deleteOverlay(squallCircle);
|
||||
if (leafDeleteOnTearDown) {
|
||||
for (var i = 0; i < nearbyEntities.length; i++) {
|
||||
var entityName = Entities.getEntityProperties(nearbyEntities[i]).name;
|
||||
if (leafName === entityName) {
|
||||
//We have a match - delete this entity
|
||||
Entities.editEntity(nearbyEntities[i], {
|
||||
locked: false
|
||||
});
|
||||
Entities.deleteEntity(nearbyEntities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
processProperties();
|
||||
setUp();
|
||||
Script.scriptEnding.connect(tearDown);
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
var leafSquall1 = new leafSquall({
|
||||
origin: {
|
||||
x: 3071.5,
|
||||
y: 2170,
|
||||
z: 6765.3
|
||||
},
|
||||
radius: 100,
|
||||
leavesPerMinute: 30,
|
||||
leafSize: {
|
||||
x: 0.3,
|
||||
y: 0.00,
|
||||
z: 0.3
|
||||
},
|
||||
leafFallSpeed: 0.4,
|
||||
leafLifetime: 100,
|
||||
leafSpinMax: 30,
|
||||
debug: false,
|
||||
maxLeaves: 100,
|
||||
leafDeleteOnTearDown: true,
|
||||
complexMovement: true,
|
||||
floorHeight: 2143.5,
|
||||
windFactor: 0.5,
|
||||
leafDeleteOnGround: false
|
||||
});
|
||||
|
||||
// todo
|
||||
//deal with depth issue
|
|
@ -141,7 +141,7 @@ CameraManager = function() {
|
|||
|
||||
// Pick a point INITIAL_ZOOM_DISTANCE in front of the camera to use as a focal point
|
||||
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;
|
||||
|
|
455
examples/libraries/overlayManager.js
Normal file
455
examples/libraries/overlayManager.js
Normal 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);
|
||||
})();
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
186
examples/utilities/tools/vector.js
Normal file
186
examples/utilities/tools/vector.js
Normal file
|
@ -0,0 +1,186 @@
|
|||
//
|
||||
// vector.js
|
||||
// examples
|
||||
//
|
||||
// Created by Bridget Went on 7/1/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// A template for creating vector arrows using line entities. A VectorArrow object creates a
|
||||
// draggable vector arrow where the user clicked at a specified distance from the viewer.
|
||||
// The relative magnitude and direction of the vector may be displayed.
|
||||
//
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
//
|
||||
|
||||
var LINE_DIMENSIONS = 100;
|
||||
var LIFETIME = 6000;
|
||||
var RAD_TO_DEG = 180.0 / Math.PI;
|
||||
|
||||
var LINE_WIDTH = 4;
|
||||
var ARROW_WIDTH = 6;
|
||||
var line, linePosition;
|
||||
var arrow1, arrow2;
|
||||
|
||||
var SCALE = 0.15;
|
||||
var ANGLE = 150.0;
|
||||
|
||||
|
||||
VectorArrow = function(distance, showStats, statsTitle, statsPosition) {
|
||||
this.magnitude = 0;
|
||||
this.direction = {x: 0, y: 0, z: 0};
|
||||
|
||||
this.showStats = showStats;
|
||||
this.isDragging = false;
|
||||
|
||||
this.newLine = function(position) {
|
||||
linePosition = position;
|
||||
var points = [];
|
||||
|
||||
line = Entities.addEntity({
|
||||
position: linePosition,
|
||||
type: "Line",
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
dimensions: {
|
||||
x: LINE_DIMENSIONS,
|
||||
y: LINE_DIMENSIONS,
|
||||
z: LINE_DIMENSIONS
|
||||
},
|
||||
lineWidth: LINE_WIDTH,
|
||||
lifetime: LIFETIME,
|
||||
linePoints: []
|
||||
});
|
||||
|
||||
arrow1 = Entities.addEntity({
|
||||
position: {x: 0, y: 0, z: 0},
|
||||
type: "Line",
|
||||
dimensions: {
|
||||
x: LINE_DIMENSIONS,
|
||||
y: LINE_DIMENSIONS,
|
||||
z: LINE_DIMENSIONS
|
||||
},
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
lineWidth: ARROW_WIDTH,
|
||||
linePoints: [],
|
||||
});
|
||||
|
||||
arrow2 = Entities.addEntity({
|
||||
position: {x: 0, y: 0, z: 0},
|
||||
type: "Line",
|
||||
dimensions: {
|
||||
x: LINE_DIMENSIONS,
|
||||
y: LINE_DIMENSIONS,
|
||||
z: LINE_DIMENSIONS
|
||||
},
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
lineWidth: ARROW_WIDTH,
|
||||
linePoints: [],
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
var STATS_DIMENSIONS = {
|
||||
x: 4.0,
|
||||
y: 1.5,
|
||||
z: 0.1
|
||||
};
|
||||
var TEXT_HEIGHT = 0.3;
|
||||
|
||||
this.onMousePressEvent = function(event) {
|
||||
|
||||
this.newLine(computeWorldPoint(event));
|
||||
|
||||
if (this.showStats) {
|
||||
this.label = Entities.addEntity({
|
||||
type: "Text",
|
||||
position: statsPosition,
|
||||
dimensions: STATS_DIMENSIONS,
|
||||
lineHeight: TEXT_HEIGHT,
|
||||
faceCamera: true
|
||||
});
|
||||
}
|
||||
|
||||
this.isDragging = true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.onMouseMoveEvent = function(event) {
|
||||
|
||||
if (!this.isDragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
var worldPoint = computeWorldPoint(event);
|
||||
var localPoint = computeLocalPoint(event, linePosition);
|
||||
points = [{x: 0, y: 0, z: 0}, localPoint];
|
||||
Entities.editEntity(line, { linePoints: points });
|
||||
|
||||
var nextOffset = Vec3.multiply(SCALE, localPoint);
|
||||
var normOffset = Vec3.normalize(localPoint);
|
||||
var axis = Vec3.cross(normOffset, Quat.getFront(Camera.getOrientation()) );
|
||||
axis = Vec3.cross(axis, normOffset);
|
||||
var rotate1 = Quat.angleAxis(ANGLE, axis);
|
||||
var rotate2 = Quat.angleAxis(-ANGLE, axis);
|
||||
|
||||
// Rotate arrow head to follow direction of the line
|
||||
Entities.editEntity(arrow1, {
|
||||
visible: true,
|
||||
position: worldPoint,
|
||||
linePoints: [{x: 0, y: 0, z: 0}, nextOffset],
|
||||
rotation: rotate1
|
||||
});
|
||||
Entities.editEntity(arrow2, {
|
||||
visible: true,
|
||||
position: worldPoint,
|
||||
linePoints: [{x: 0, y: 0, z: 0}, nextOffset],
|
||||
rotation: rotate2
|
||||
});
|
||||
|
||||
this.magnitude = Vec3.length(localPoint) * 0.1;
|
||||
this.direction = Vec3.normalize(Vec3.subtract(worldPoint, linePosition));
|
||||
|
||||
if (this.showStats) {
|
||||
this.editLabel(statsTitle + " Magnitude " + this.magnitude.toFixed(2) + ", Direction: " +
|
||||
this.direction.x.toFixed(2) + ", " + this.direction.y.toFixed(2) + ", " + this.direction.z.toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
this.onMouseReleaseEvent = function() {
|
||||
this.isDragging = false;
|
||||
}
|
||||
|
||||
this.cleanup = function() {
|
||||
Entities.deleteEntity(line);
|
||||
Entities.deleteEntity(arrow1);
|
||||
Entities.deleteEntity(arrow2);
|
||||
}
|
||||
|
||||
this.deleteLabel = function() {
|
||||
Entities.deleteEntity(this.label);
|
||||
}
|
||||
|
||||
this.editLabel = function(str) {
|
||||
if(!this.showStats) {
|
||||
return;
|
||||
}
|
||||
Entities.editEntity(this.label, {
|
||||
text: str
|
||||
});
|
||||
}
|
||||
|
||||
function computeWorldPoint(event) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var addVector = Vec3.multiply(pickRay.direction, distance);
|
||||
return Vec3.sum(Camera.getPosition(), addVector);
|
||||
}
|
||||
|
||||
function computeLocalPoint(event, linePosition) {
|
||||
var localPoint = Vec3.subtract(computeWorldPoint(event), linePosition);
|
||||
return localPoint;
|
||||
}
|
||||
|
||||
}
|
33
examples/zones/jsstreamplayerdomain-zone-entity.js
Normal file
33
examples/zones/jsstreamplayerdomain-zone-entity.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// #20628: JS Stream Player Domain-Zone-Entity
|
||||
// ********************************************
|
||||
//
|
||||
// Created by Kevin M. Thomas and Thoys 07/20/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// kevintown.net
|
||||
//
|
||||
// JavaScript for the High Fidelity interface that is an entity script to be placed in a chosen entity inside a domain-zone.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
// Function which exists inside of an entity which triggers as a user approches it.
|
||||
(function() {
|
||||
const SCRIPT_NAME = "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.js";
|
||||
function isScriptRunning(script) {
|
||||
script = script.toLowerCase().trim();
|
||||
var runningScripts = ScriptDiscoveryService.getRunning();
|
||||
for (i in runningScripts) {
|
||||
if (runningScripts[i].url.toLowerCase().trim() == script) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!isScriptRunning(SCRIPT_NAME)) {
|
||||
Script.load(SCRIPT_NAME);
|
||||
}
|
||||
})
|
42
examples/zones/jsstreamplayerdomain-zone.html
Normal file
42
examples/zones/jsstreamplayerdomain-zone.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<!-- -->
|
||||
<!-- #20628: JS Stream Player Domain-Zone -->
|
||||
<!-- ************************************* -->
|
||||
<!-- -->
|
||||
<!-- Created by Kevin M. Thomas and Thoys 07/20/15. -->
|
||||
<!-- Copyright 2015 High Fidelity, Inc. -->
|
||||
<!-- kevintown.net -->
|
||||
<!-- -->
|
||||
<!-- JavaScript for the High Fidelity interface that creates a stream player with a UI for playing a domain-zone specificed stream URL in addition to play, stop and volume functionality which is resident only in the domain-zone. -->
|
||||
<!-- -->
|
||||
<!-- Distributed under the Apache License, Version 2.0. -->
|
||||
<!-- See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -->
|
||||
<!-- -->
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
if (window.EventBridge !== undefined) {
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
var myData = JSON.parse(data);
|
||||
if (myData.action == "changeStream") {
|
||||
$('body > audio').attr("src", myData.stream);
|
||||
}
|
||||
if (myData.action == "changeVolume") {
|
||||
$('body > audio').prop("volume", myData.volume);
|
||||
}
|
||||
});
|
||||
}
|
||||
EventBridge.emitWebEvent("loaded");
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<audio controls src="" controls autoplay></audio>
|
||||
</body>
|
||||
</html>
|
176
examples/zones/jsstreamplayerdomain-zone.js
Normal file
176
examples/zones/jsstreamplayerdomain-zone.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
//
|
||||
// #20628: JS Stream Player Domain-Zone
|
||||
// *************************************
|
||||
//
|
||||
// Created by Kevin M. Thomas, Thoys and Konstantin 07/24/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// kevintown.net
|
||||
//
|
||||
// JavaScript for the High Fidelity interface that creates a stream player with a UI for playing a domain-zone specificed stream URL in addition to play, stop and volume functionality which is resident only in the domain-zone.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
// Declare variables and set up new WebWindow.
|
||||
var lastZone = "";
|
||||
var volume = 0.5;
|
||||
var stream = "";
|
||||
var streamWindow = new WebWindow('Stream', "https://dl.dropboxusercontent.com/u/17344741/jsstreamplayer/jsstreamplayerdomain-zone.html", 0, 0, false);
|
||||
var visible = false;
|
||||
|
||||
// Set up toggleStreamPlayButton overlay.
|
||||
var toggleStreamPlayButton = Overlays.addOverlay("text", {
|
||||
x: 122,
|
||||
y: 310,
|
||||
width: 38,
|
||||
height: 28,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8,
|
||||
visible: false,
|
||||
text: " Play"
|
||||
});
|
||||
|
||||
// Set up toggleStreamStopButton overlay.
|
||||
var toggleStreamStopButton = Overlays.addOverlay("text", {
|
||||
x: 166,
|
||||
y: 310,
|
||||
width: 40,
|
||||
height: 28,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8,
|
||||
visible: false,
|
||||
text: " Stop"
|
||||
});
|
||||
|
||||
// Set up increaseVolumeButton overlay.
|
||||
var toggleIncreaseVolumeButton = Overlays.addOverlay("text", {
|
||||
x: 211,
|
||||
y: 310,
|
||||
width: 18,
|
||||
height: 28,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8,
|
||||
visible: false,
|
||||
text: " +"
|
||||
});
|
||||
|
||||
// Set up decreaseVolumeButton overlay.
|
||||
var toggleDecreaseVolumeButton = Overlays.addOverlay("text", {
|
||||
x: 234,
|
||||
y: 310,
|
||||
width: 15,
|
||||
height: 28,
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
font: {size: 15},
|
||||
topMargin: 8,
|
||||
visible: false,
|
||||
text: " -"
|
||||
});
|
||||
|
||||
// Function to change JSON object stream.
|
||||
function changeStream(stream) {
|
||||
var streamJSON = {
|
||||
action: "changeStream",
|
||||
stream: stream
|
||||
}
|
||||
streamWindow.eventBridge.emitScriptEvent(JSON.stringify(streamJSON));
|
||||
}
|
||||
|
||||
// Function to change JSON object volume.
|
||||
function changeVolume(volume) {
|
||||
var volumeJSON = {
|
||||
action: "changeVolume",
|
||||
volume: volume
|
||||
}
|
||||
streamWindow.eventBridge.emitScriptEvent(JSON.stringify(volumeJSON));
|
||||
}
|
||||
|
||||
// Function that adds mousePressEvent functionality to connect UI to enter stream URL, play and stop stream.
|
||||
function mousePressEvent(event) {
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamPlayButton) {
|
||||
changeStream(stream);
|
||||
volume = 0.25;
|
||||
changeVolume(volume);
|
||||
}
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleStreamStopButton) {
|
||||
changeStream("");
|
||||
}
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleIncreaseVolumeButton) {
|
||||
volume += 0.25;
|
||||
changeVolume(volume);
|
||||
}
|
||||
if (Overlays.getOverlayAtPoint({x: event.x, y: event.y}) == toggleDecreaseVolumeButton) {
|
||||
volume -= 0.25;
|
||||
changeVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
// Function checking bool if in proper zone.
|
||||
function isOurZone(properties) {
|
||||
return stream != "" && properties.type == "Zone";
|
||||
}
|
||||
|
||||
// Function to toggle visibile the overlay.
|
||||
function toggleVisible(newVisibility) {
|
||||
if (newVisibility != visible) {
|
||||
visible = newVisibility;
|
||||
Overlays.editOverlay(toggleStreamPlayButton, {visible: visible});
|
||||
Overlays.editOverlay(toggleStreamStopButton, {visible: visible});
|
||||
Overlays.editOverlay(toggleIncreaseVolumeButton, {visible: visible});
|
||||
Overlays.editOverlay(toggleDecreaseVolumeButton, {visible: visible});
|
||||
}
|
||||
}
|
||||
|
||||
// Function to check if avatar is in proper domain.
|
||||
Window.domainChanged.connect(function() {
|
||||
Script.stop();
|
||||
});
|
||||
|
||||
// Function to check if avatar is within zone.
|
||||
Entities.enterEntity.connect(function(entityID) {
|
||||
print("Entered..." + JSON.stringify(entityID));
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
stream = properties.userData;
|
||||
if(isOurZone(properties))
|
||||
{
|
||||
lastZone = properties.name;
|
||||
toggleVisible(true);
|
||||
}
|
||||
})
|
||||
|
||||
// Function to check if avatar is leaving zone.
|
||||
Entities.leaveEntity.connect(function(entityID) {
|
||||
print("Left..." + JSON.stringify(entityID));
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
if (properties.name == lastZone && properties.type == "Zone") {
|
||||
print("Leaving Zone!");
|
||||
toggleVisible(false);
|
||||
changeStream("");
|
||||
}
|
||||
})
|
||||
|
||||
// Function to delete overlays upon exit.
|
||||
function onScriptEnding() {
|
||||
Overlays.deleteOverlay(toggleStreamPlayButton);
|
||||
Overlays.deleteOverlay(toggleStreamStopButton);
|
||||
Overlays.deleteOverlay(toggleIncreaseVolumeButton);
|
||||
Overlays.deleteOverlay(toggleDecreaseVolumeButton);
|
||||
changeStream("");
|
||||
streamWindow.deleteLater();
|
||||
}
|
||||
|
||||
// Connect mouse and hide WebWindow.
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
streamWindow.setVisible(false);
|
||||
|
||||
// Call function upon ending script.
|
||||
Script.scriptEnding.connect(onScriptEnding);
|
|
@ -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)
|
||||
|
||||
|
|
79
interface/external/connexionclient/Inc/I3dMouseParams.h
vendored
Normal file
79
interface/external/connexionclient/Inc/I3dMouseParams.h
vendored
Normal 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
|
4
interface/external/connexionclient/readme.txt
vendored
Normal file
4
interface/external/connexionclient/readme.txt
vendored
Normal 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
|
64
interface/resources/meshes/defaultAvatar_full.fst
Normal file
64
interface/resources/meshes/defaultAvatar_full.fst
Normal 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.
BIN
interface/resources/meshes/defaultAvatar_full/textures/visor.png
Normal file
BIN
interface/resources/meshes/defaultAvatar_full/textures/visor.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 4.6 KiB |
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#define hifi_GLCanvas_h
|
||||
|
||||
#include <QDebug>
|
||||
#include <gpu/GPUConfig.h>
|
||||
#include <QGLWidget>
|
||||
#include <QTimer>
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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&);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
1013
interface/src/devices/3Dconnexion.cpp
Executable file
1013
interface/src/devices/3Dconnexion.cpp
Executable file
File diff suppressed because it is too large
Load diff
244
interface/src/devices/3Dconnexion.h
Executable file
244
interface/src/devices/3Dconnexion.h
Executable 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)
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <glm/glm.hpp>
|
||||
|
||||
class Camera;
|
||||
class RenderArgs;
|
||||
|
||||
struct eyeFrustum {
|
||||
double left;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
ControllerScriptingInterface::ControllerScriptingInterface() :
|
||||
_mouseCaptured(false),
|
||||
_touchCaptured(false),
|
||||
_wheelCaptured(false)
|
||||
_wheelCaptured(false),
|
||||
_actionsCaptured(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
26
interface/src/scripting/DialogsManagerScriptingInterface.cpp
Normal file
26
interface/src/scripting/DialogsManagerScriptingInterface.cpp
Normal 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);
|
||||
}
|
29
interface/src/scripting/DialogsManagerScriptingInterface.h
Normal file
29
interface/src/scripting/DialogsManagerScriptingInterface.h
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
void DialogsManager::toggleAddressBar() {
|
||||
AddressBarDialog::toggle();
|
||||
emit addressBarToggled();
|
||||
}
|
||||
|
||||
void DialogsManager::toggleDiskCacheEditor() {
|
||||
|
|
|
@ -72,6 +72,9 @@ public slots:
|
|||
// Application Update
|
||||
void showUpdateDialog();
|
||||
|
||||
signals:
|
||||
void addressBarToggled();
|
||||
|
||||
private slots:
|
||||
void toggleToolWindow();
|
||||
void hmdToolsClosed();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
197
interface/src/ui/overlays/FloatingUIPanel.cpp
Normal file
197
interface/src/ui/overlays/FloatingUIPanel.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
63
interface/src/ui/overlays/FloatingUIPanel.h
Normal file
63
interface/src/ui/overlays/FloatingUIPanel.h
Normal 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
|
|
@ -18,6 +18,9 @@
|
|||
#include <PathUtils.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
|
||||
QString const Grid3DOverlay::TYPE = "grid";
|
||||
|
||||
Grid3DOverlay::Grid3DOverlay() :
|
||||
_minorGridWidth(1.0),
|
||||
_majorGridEvery(5) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
#include <gpu/StandardShaderLib.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
|
||||
QString const ImageOverlay::TYPE = "image";
|
||||
|
||||
ImageOverlay::ImageOverlay() :
|
||||
_imageURL(),
|
||||
_renderImage(false),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
#include <GeometryCache.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
|
||||
QString const Line3DOverlay::TYPE = "line3d";
|
||||
|
||||
Line3DOverlay::Line3DOverlay() :
|
||||
_geometryCacheID(DependencyManager::get<GeometryCache>()->allocateID())
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
#include <EntityTreeRenderer.h>
|
||||
#include <gpu/Batch.h>
|
||||
|
||||
|
||||
QString const LocalModelsOverlay::TYPE = "localmodels";
|
||||
|
||||
LocalModelsOverlay::LocalModelsOverlay(EntityTreeRenderer* entityTreeRenderer) :
|
||||
Volume3DOverlay(),
|
||||
_entityTreeRenderer(entityTreeRenderer) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
80
interface/src/ui/overlays/PanelAttachable.cpp
Normal file
80
interface/src/ui/overlays/PanelAttachable.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
45
interface/src/ui/overlays/PanelAttachable.h
Normal file
45
interface/src/ui/overlays/PanelAttachable.h
Normal 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
Loading…
Reference in a new issue