mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-25 17:14:59 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into pid-render-limits
This commit is contained in:
commit
9c8a04ce1e
269 changed files with 13640 additions and 3162 deletions
1
CMakeGraphvizOptions.cmake
Normal file
1
CMakeGraphvizOptions.cmake
Normal file
|
@ -0,0 +1 @@
|
|||
set(GRAPHVIZ_EXTERNAL_LIBS FALSE)
|
|
@ -197,6 +197,10 @@ if (WIN32)
|
|||
add_paths_to_fixup_libs("${QT_DIR}/bin")
|
||||
endif ()
|
||||
|
||||
if (NOT DEFINED SERVER_ONLY)
|
||||
set(SERVER_ONLY 0)
|
||||
endif()
|
||||
|
||||
# add subdirectories for all targets
|
||||
if (NOT ANDROID)
|
||||
add_subdirectory(assignment-client)
|
||||
|
@ -205,14 +209,16 @@ if (NOT ANDROID)
|
|||
set_target_properties(domain-server PROPERTIES FOLDER "Apps")
|
||||
add_subdirectory(ice-server)
|
||||
set_target_properties(ice-server PROPERTIES FOLDER "Apps")
|
||||
add_subdirectory(interface)
|
||||
set_target_properties(interface PROPERTIES FOLDER "Apps")
|
||||
add_subdirectory(stack-manager)
|
||||
set_target_properties(stack-manager PROPERTIES FOLDER "Apps")
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(plugins)
|
||||
if (NOT SERVER_ONLY)
|
||||
add_subdirectory(interface)
|
||||
set_target_properties(interface PROPERTIES FOLDER "Apps")
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(plugins)
|
||||
endif()
|
||||
add_subdirectory(tools)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
if (ANDROID OR DESKTOP_GVR)
|
||||
add_subdirectory(gvr-interface)
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <ShutdownEventListener.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ResourceScriptingInterface.h>
|
||||
|
||||
#include "AssignmentFactory.h"
|
||||
#include "AssignmentActionFactory.h"
|
||||
|
@ -61,6 +62,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
|
||||
DependencyManager::registerInheritance<EntityActionFactoryInterface, AssignmentActionFactory>();
|
||||
auto actionFactory = DependencyManager::set<AssignmentActionFactory>();
|
||||
DependencyManager::set<ResourceScriptingInterface>();
|
||||
|
||||
// setup a thread for the NodeList and its PacketReceiver
|
||||
QThread* nodeThread = new QThread(this);
|
||||
|
|
|
@ -33,7 +33,7 @@ bool OctreeQueryNode::packetIsDuplicate() const {
|
|||
// of the entire packet, we need to compare only the packet content...
|
||||
|
||||
if (_lastOctreePacketLength == _octreePacket->getPayloadSize()) {
|
||||
if (memcmp(&_lastOctreePayload + OCTREE_PACKET_EXTRA_HEADERS_SIZE,
|
||||
if (memcmp(_lastOctreePayload.data() + OCTREE_PACKET_EXTRA_HEADERS_SIZE,
|
||||
_octreePacket->getPayload() + OCTREE_PACKET_EXTRA_HEADERS_SIZE,
|
||||
_octreePacket->getPayloadSize() - OCTREE_PACKET_EXTRA_HEADERS_SIZE) == 0) {
|
||||
return true;
|
||||
|
@ -101,7 +101,7 @@ void OctreeQueryNode::resetOctreePacket() {
|
|||
// scene information, (e.g. the root node packet of a static scene), we can use this as a strategy for reducing
|
||||
// packet send rate.
|
||||
_lastOctreePacketLength = _octreePacket->getPayloadSize();
|
||||
memcpy(&_lastOctreePayload, _octreePacket->getPayload(), _lastOctreePacketLength);
|
||||
memcpy(_lastOctreePayload.data(), _octreePacket->getPayload(), _lastOctreePacketLength);
|
||||
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
|
|
56
cmake/externals/neuron/CMakeLists.txt
vendored
Normal file
56
cmake/externals/neuron/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
include(ExternalProject)
|
||||
|
||||
set(EXTERNAL_NAME neuron)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
set(NEURON_URL "https://s3.amazonaws.com/hifi-public/dependencies/neuron_datareader_b.12.zip")
|
||||
set(NEURON_URL_MD5 "0ab54ca04c9cc8094e0fa046c226e574")
|
||||
|
||||
ExternalProject_Add(${EXTERNAL_NAME}
|
||||
URL ${NEURON_URL}
|
||||
URL_MD5 ${NEURON_URL_MD5}
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
# set include dir
|
||||
if(WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Windows/include" CACHE TYPE INTERNAL)
|
||||
elseif(APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Mac/include" CACHE TYPE INTERNAL)
|
||||
else()
|
||||
# Unsupported
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
|
||||
if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
|
||||
set(ARCH_DIR "x64")
|
||||
else()
|
||||
set(ARCH_DIR "x86")
|
||||
endif()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Windows/lib/${ARCH_DIR}")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL)
|
||||
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}")
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Mac/dylib")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL)
|
||||
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}")
|
||||
|
||||
else()
|
||||
# UNSUPPORTED
|
||||
endif()
|
||||
|
77
cmake/externals/qxmpp/CMakeLists.txt
vendored
77
cmake/externals/qxmpp/CMakeLists.txt
vendored
|
@ -1,77 +0,0 @@
|
|||
set(EXTERNAL_NAME qxmpp)
|
||||
|
||||
# we need to find qmake inside QT_DIR
|
||||
find_program(QMAKE_COMMAND NAME qmake PATHS ${QT_DIR}/bin $ENV{QTTOOLDIR} NO_DEFAULT_PATH)
|
||||
|
||||
if (NOT QMAKE_COMMAND)
|
||||
message(FATAL_ERROR "Could not find qmake. Qxmpp cannot be compiled without qmake.")
|
||||
endif ()
|
||||
|
||||
if (ANDROID)
|
||||
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
find_program(PLATFORM_BUILD_COMMAND nmake PATHS "C:/Program Files (x86)/Microsoft Visual Studio 12.0/VC/bin")
|
||||
|
||||
if (NOT PLATFORM_BUILD_COMMAND)
|
||||
message(FATAL_ERROR "You asked CMake to grap QXmpp and build it, but nmake was not found. Please make sure the folder containing nmake.exe is in your PATH.")
|
||||
endif ()
|
||||
else ()
|
||||
find_program(PLATFORM_BUILD_COMMAND make)
|
||||
endif ()
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://qxmpp.googlecode.com/files/qxmpp-0.7.6.tar.gz
|
||||
URL_MD5 ee45a97313306ded2ff0f6618a3ed1e1
|
||||
BUILD_IN_SOURCE 1
|
||||
PATCH_COMMAND patch -p2 -t -N --verbose < ${CMAKE_CURRENT_SOURCE_DIR}/qxmpp.patch
|
||||
CONFIGURE_COMMAND ${QMAKE_COMMAND} PREFIX=<INSTALL_DIR>
|
||||
BUILD_COMMAND ${PLATFORM_BUILD_COMMAND}
|
||||
INSTALL_COMMAND ${PLATFORM_BUILD_COMMAND} install
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
if (CMAKE_GENERATOR STREQUAL Xcode)
|
||||
find_program(DITTO_COMMAND ditto)
|
||||
|
||||
ExternalProject_Add_Step(
|
||||
${EXTERNAL_NAME}
|
||||
copy-from-xcode-install
|
||||
COMMENT "Copying from /tmp/hifi.dst${INSTALL_DIR} to move install to proper location"
|
||||
COMMAND ${DITTO_COMMAND} /tmp/hifi.dst${INSTALL_DIR} ${INSTALL_DIR}
|
||||
DEPENDEES install
|
||||
LOG 1
|
||||
)
|
||||
endif ()
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to Qxmpp include directory")
|
||||
|
||||
set(_LIB_DIR ${INSTALL_DIR}/lib)
|
||||
|
||||
if (WIN32)
|
||||
set(_LIB_EXT "0.lib")
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${_LIB_DIR} CACHE PATH "Location of QXmpp DLL")
|
||||
else ()
|
||||
if (APPLE)
|
||||
set(_LIB_EXT ".dylib")
|
||||
else ()
|
||||
set(_LIB_EXT ".so")
|
||||
endif ()
|
||||
|
||||
set(_LIB_PREFIX "lib")
|
||||
endif ()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${_LIB_DIR}/${_LIB_PREFIX}qxmpp${_LIB_EXT} CACHE FILEPATH "Path to QXmpp release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "Path to QXmpp debug library")
|
|
@ -40,7 +40,7 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
|
|||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>"
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>"
|
||||
)
|
||||
elseif (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE)
|
||||
find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH)
|
||||
|
|
17
cmake/macros/TargetNeuron.cmake
Normal file
17
cmake/macros/TargetNeuron.cmake
Normal file
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Created by Anthony J. Thibault on 2015/12/21
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_NEURON)
|
||||
# Neuron data reader is only available on these platforms
|
||||
if (WIN32 OR APPLE)
|
||||
add_dependency_external_projects(neuron)
|
||||
find_package(Neuron REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${NEURON_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${NEURON_LIBRARIES})
|
||||
add_definitions(-DHAVE_NEURON)
|
||||
endif(WIN32 OR APPLE)
|
||||
endmacro()
|
28
cmake/modules/FindNeuron.cmake
Normal file
28
cmake/modules/FindNeuron.cmake
Normal file
|
@ -0,0 +1,28 @@
|
|||
#
|
||||
# FindNeuron.cmake
|
||||
#
|
||||
# Try to find the Perception Neuron SDK
|
||||
#
|
||||
# You must provide a NEURON_ROOT_DIR which contains lib and include directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# NEURON_FOUND - system found Neuron SDK
|
||||
# NEURON_INCLUDE_DIRS - the Neuron SDK include directory
|
||||
# NEURON_LIBRARIES - Link this to use Neuron
|
||||
#
|
||||
# Created on 12/21/2015 by Anthony J. Thibault
|
||||
# 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(SelectLibraryConfigurations)
|
||||
select_library_configurations(NEURON)
|
||||
|
||||
set(NEURON_REQUIREMENTS NEURON_INCLUDE_DIRS NEURON_LIBRARIES)
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Neuron DEFAULT_MSG NEURON_INCLUDE_DIRS NEURON_LIBRARIES)
|
||||
mark_as_advanced(NEURON_LIBRARIES NEURON_INCLUDE_DIRS NEURON_SEARCH_DIRS)
|
||||
|
72
examples/acScripts/triggeredRecordingOnAC.js
Normal file
72
examples/acScripts/triggeredRecordingOnAC.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// triggeredRecordingOnAC.js
|
||||
// examples/acScripts
|
||||
//
|
||||
// Created by Thijs Wenker on 12/21/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is the triggered rocording script used in the winterSmashUp target practice game.
|
||||
// Change the CLIP_URL to your asset,
|
||||
// the RECORDING_CHANNEL and RECORDING_CHANNEL_MESSAGE are used to trigger it i.e.:
|
||||
// Messages.sendMessage("PlayBackOnAssignment", "BowShootingGameWelcome");
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
const CLIP_URL = "atp:3fbe82f2153c443f12f9a2b14ce2d7fa2fff81977263746d9e0885ea5aabed62.hfr";
|
||||
|
||||
const RECORDING_CHANNEL = 'PlayBackOnAssignment';
|
||||
const RECORDING_CHANNEL_MESSAGE = 'BowShootingGameWelcome'; // For each different assignment add a different message here.
|
||||
const PLAY_FROM_CURRENT_LOCATION = true;
|
||||
const USE_DISPLAY_NAME = true;
|
||||
const USE_ATTACHMENTS = true;
|
||||
const USE_AVATAR_MODEL = true;
|
||||
const AUDIO_OFFSET = 0.0;
|
||||
const STARTING_TIME = 0.0;
|
||||
const COOLDOWN_PERIOD = 0; // The period in ms that no animations can be played after one has been played already
|
||||
|
||||
var isPlaying = false;
|
||||
var isPlayable = true;
|
||||
|
||||
var playRecording = function() {
|
||||
if (!isPlayable || isPlaying) {
|
||||
return;
|
||||
}
|
||||
Agent.isAvatar = true;
|
||||
Recording.setPlayFromCurrentLocation(PLAY_FROM_CURRENT_LOCATION);
|
||||
Recording.setPlayerUseDisplayName(USE_DISPLAY_NAME);
|
||||
Recording.setPlayerUseAttachments(USE_ATTACHMENTS);
|
||||
Recording.setPlayerUseHeadModel(false);
|
||||
Recording.setPlayerUseSkeletonModel(USE_AVATAR_MODEL);
|
||||
Recording.setPlayerLoop(false);
|
||||
Recording.setPlayerTime(STARTING_TIME);
|
||||
Recording.setPlayerAudioOffset(AUDIO_OFFSET);
|
||||
Recording.loadRecording(CLIP_URL);
|
||||
Recording.startPlaying();
|
||||
isPlaying = true;
|
||||
isPlayable = false; // Set this true again after the cooldown period
|
||||
};
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
if (isPlaying && !Recording.isPlaying()) {
|
||||
print('Reached the end of the recording. Resetting.');
|
||||
isPlaying = false;
|
||||
Agent.isAvatar = false;
|
||||
if (COOLDOWN_PERIOD === 0) {
|
||||
isPlayable = true;
|
||||
return;
|
||||
}
|
||||
Script.setTimeout(function () {
|
||||
isPlayable;
|
||||
}, COOLDOWN_PERIOD);
|
||||
}
|
||||
});
|
||||
|
||||
Messages.subscribe(RECORDING_CHANNEL);
|
||||
|
||||
Messages.messageReceived.connect(function (channel, message, senderID) {
|
||||
if (channel === RECORDING_CHANNEL && message === RECORDING_CHANNEL_MESSAGE) {
|
||||
playRecording();
|
||||
}
|
||||
});
|
54
examples/controllers/getHUDLookAtPositionTest.js
Normal file
54
examples/controllers/getHUDLookAtPositionTest.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// getHUDLookAtPositionTest.js
|
||||
// examples/controllers
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 2015/12/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
|
||||
//
|
||||
|
||||
|
||||
// This script demonstrates the testing of the HMD.getHUDLookAtPosition--() functions.
|
||||
// If these functions are working correctly, we'd expect to see a 3D cube and a 2D square
|
||||
// follow around the center of the HMD view.
|
||||
|
||||
var cubePosition = { x: 0, y: 0, z: 0 };
|
||||
var cubeSize = 0.03;
|
||||
var cube = Overlays.addOverlay("cube", {
|
||||
position: cubePosition,
|
||||
size: cubeSize,
|
||||
color: { red: 255, green: 0, blue: 0},
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
var square = Overlays.addOverlay("text", {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: { red: 255, green: 255, blue: 0},
|
||||
backgroundColor: { red: 255, green: 255, blue: 0},
|
||||
alpha: 1
|
||||
});
|
||||
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
if (!HMD.active) {
|
||||
return;
|
||||
}
|
||||
var lookAt3D = HMD.getHUDLookAtPosition3D();
|
||||
Overlays.editOverlay(cube, { position: lookAt3D });
|
||||
|
||||
var lookAt2D = HMD.getHUDLookAtPosition2D();
|
||||
Overlays.editOverlay(square, { x: lookAt2D.x, y: lookAt2D.y });
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function(){
|
||||
Overlays.deleteOverlay(cube);
|
||||
Overlays.deleteOverlay(square);
|
||||
});
|
||||
|
||||
|
File diff suppressed because it is too large
Load diff
239
examples/controllers/neuron/neuronAvatar.js
Normal file
239
examples/controllers/neuron/neuronAvatar.js
Normal file
|
@ -0,0 +1,239 @@
|
|||
var JOINT_PARENT_MAP = {
|
||||
Hips: "",
|
||||
RightUpLeg: "Hips",
|
||||
RightLeg: "RightUpLeg",
|
||||
RightFoot: "RightLeg",
|
||||
LeftUpLeg: "Hips",
|
||||
LeftLeg: "LeftUpLeg",
|
||||
LeftFoot: "LeftLeg",
|
||||
Spine: "Hips",
|
||||
Spine1: "Spine",
|
||||
Spine2: "Spine1",
|
||||
Spine3: "Spine2",
|
||||
Neck: "Spine3",
|
||||
Head: "Neck",
|
||||
RightShoulder: "Spine3",
|
||||
RightArm: "RightShoulder",
|
||||
RightForeArm: "RightArm",
|
||||
RightHand: "RightForeArm",
|
||||
RightHandThumb1: "RightHand",
|
||||
RightHandThumb2: "RightHandThumb1",
|
||||
RightHandThumb3: "RightHandThumb2",
|
||||
RightHandThumb4: "RightHandThumb3",
|
||||
RightHandIndex1: "RightHand",
|
||||
RightHandIndex2: "RightHandIndex1",
|
||||
RightHandIndex3: "RightHandIndex2",
|
||||
RightHandIndex4: "RightHandIndex3",
|
||||
RightHandMiddle1: "RightHand",
|
||||
RightHandMiddle2: "RightHandMiddle1",
|
||||
RightHandMiddle3: "RightHandMiddle2",
|
||||
RightHandMiddle4: "RightHandMiddle3",
|
||||
RightHandRing1: "RightHand",
|
||||
RightHandRing2: "RightHandRing1",
|
||||
RightHandRing3: "RightHandRing2",
|
||||
RightHandRing4: "RightHandRing3",
|
||||
RightHandPinky1: "RightHand",
|
||||
RightHandPinky2: "RightHandPinky1",
|
||||
RightHandPinky3: "RightHandPinky2",
|
||||
RightHandPinky4: "RightHandPinky3",
|
||||
LeftShoulder: "Spine3",
|
||||
LeftArm: "LeftShoulder",
|
||||
LeftForeArm: "LeftArm",
|
||||
LeftHand: "LeftForeArm",
|
||||
LeftHandThumb1: "LeftHand",
|
||||
LeftHandThumb2: "LeftHandThumb1",
|
||||
LeftHandThumb3: "LeftHandThumb2",
|
||||
LeftHandThumb4: "LeftHandThumb3",
|
||||
LeftHandIndex1: "LeftHand",
|
||||
LeftHandIndex2: "LeftHandIndex1",
|
||||
LeftHandIndex3: "LeftHandIndex2",
|
||||
LeftHandIndex4: "LeftHandIndex3",
|
||||
LeftHandMiddle1: "LeftHand",
|
||||
LeftHandMiddle2: "LeftHandMiddle1",
|
||||
LeftHandMiddle3: "LeftHandMiddle2",
|
||||
LeftHandMiddle4: "LeftHandMiddle3",
|
||||
LeftHandRing1: "LeftHand",
|
||||
LeftHandRing2: "LeftHandRing1",
|
||||
LeftHandRing3: "LeftHandRing2",
|
||||
LeftHandRing4: "LeftHandRing3",
|
||||
LeftHandPinky1: "LeftHand",
|
||||
LeftHandPinky2: "LeftHandPinky1",
|
||||
LeftHandPinky3: "LeftHandPinky2",
|
||||
LeftHandPinky: "LeftHandPinky3",
|
||||
};
|
||||
|
||||
var USE_TRANSLATIONS = false;
|
||||
|
||||
// ctor
|
||||
function Xform(rot, pos) {
|
||||
this.rot = rot;
|
||||
this.pos = pos;
|
||||
};
|
||||
Xform.mul = function (lhs, rhs) {
|
||||
var rot = Quat.multiply(lhs.rot, rhs.rot);
|
||||
var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos));
|
||||
return new Xform(rot, pos);
|
||||
};
|
||||
Xform.prototype.inv = function () {
|
||||
var invRot = Quat.inverse(this.rot);
|
||||
var invPos = Vec3.multiply(-1, this.pos);
|
||||
return new Xform(invRot, Vec3.multiplyQbyV(invRot, invPos));
|
||||
};
|
||||
Xform.prototype.toString = function () {
|
||||
var rot = this.rot;
|
||||
var pos = this.pos;
|
||||
return "Xform rot = (" + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "), pos = (" + pos.x + ", " + pos.y + ", " + pos.z + ")";
|
||||
};
|
||||
|
||||
function dumpHardwareMapping() {
|
||||
Object.keys(Controller.Hardware).forEach(function (deviceName) {
|
||||
Object.keys(Controller.Hardware[deviceName]).forEach(function (input) {
|
||||
print("Controller.Hardware." + deviceName + "." + input + ":" + Controller.Hardware[deviceName][input]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ctor
|
||||
function NeuronAvatar() {
|
||||
var self = this;
|
||||
Script.scriptEnding.connect(function () {
|
||||
self.shutdown();
|
||||
});
|
||||
Controller.hardwareChanged.connect(function () {
|
||||
self.hardwareChanged();
|
||||
});
|
||||
|
||||
if (Controller.Hardware.Neuron) {
|
||||
this.activate();
|
||||
} else {
|
||||
this.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
NeuronAvatar.prototype.shutdown = function () {
|
||||
this.deactivate();
|
||||
};
|
||||
|
||||
NeuronAvatar.prototype.hardwareChanged = function () {
|
||||
if (Controller.Hardware.Neuron) {
|
||||
this.activate();
|
||||
} else {
|
||||
this.deactivate();
|
||||
}
|
||||
};
|
||||
|
||||
NeuronAvatar.prototype.activate = function () {
|
||||
if (!this._active) {
|
||||
Script.update.connect(updateCallback);
|
||||
}
|
||||
this._active = true;
|
||||
|
||||
// build absDefaultPoseMap
|
||||
this._defaultAbsRotMap = {};
|
||||
this._defaultAbsPosMap = {};
|
||||
this._defaultAbsRotMap[""] = {x: 0, y: 0, z: 0, w: 1};
|
||||
this._defaultAbsPosMap[""] = {x: 0, y: 0, z: 0};
|
||||
var keys = Object.keys(JOINT_PARENT_MAP);
|
||||
var i, l = keys.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
var jointName = keys[i];
|
||||
var j = MyAvatar.getJointIndex(jointName);
|
||||
var parentJointName = JOINT_PARENT_MAP[jointName];
|
||||
this._defaultAbsRotMap[jointName] = Quat.multiply(this._defaultAbsRotMap[parentJointName], MyAvatar.getDefaultJointRotation(j));
|
||||
this._defaultAbsPosMap[jointName] = Vec3.sum(this._defaultAbsPosMap[parentJointName],
|
||||
Quat.multiply(this._defaultAbsRotMap[parentJointName], MyAvatar.getDefaultJointTranslation(j)));
|
||||
}
|
||||
};
|
||||
|
||||
NeuronAvatar.prototype.deactivate = function () {
|
||||
if (this._active) {
|
||||
var self = this;
|
||||
Script.update.disconnect(updateCallback);
|
||||
}
|
||||
this._active = false;
|
||||
MyAvatar.clearJointsData();
|
||||
};
|
||||
|
||||
NeuronAvatar.prototype.update = function (deltaTime) {
|
||||
|
||||
var hmdActive = HMD.active;
|
||||
var keys = Object.keys(JOINT_PARENT_MAP);
|
||||
var i, l = keys.length;
|
||||
var absDefaultRot = {};
|
||||
var jointName, channel, pose, parentJointName, j, parentDefaultAbsRot;
|
||||
var localRotations = {};
|
||||
var localTranslations = {};
|
||||
for (i = 0; i < l; i++) {
|
||||
var jointName = keys[i];
|
||||
var channel = Controller.Hardware.Neuron[jointName];
|
||||
if (channel) {
|
||||
pose = Controller.getPoseValue(channel);
|
||||
parentJointName = JOINT_PARENT_MAP[jointName];
|
||||
j = MyAvatar.getJointIndex(jointName);
|
||||
defaultAbsRot = this._defaultAbsRotMap[jointName];
|
||||
parentDefaultAbsRot = this._defaultAbsRotMap[parentJointName];
|
||||
|
||||
// Rotations from the neuron controller are in world orientation but are delta's from the default pose.
|
||||
// So first we build the absolute rotation of the default pose (local into world).
|
||||
// Then apply the rotation from the controller, in world space.
|
||||
// Then we transform back into joint local by multiplying by the inverse of the parents absolute rotation.
|
||||
var localRotation = Quat.multiply(Quat.inverse(parentDefaultAbsRot), Quat.multiply(pose.rotation, defaultAbsRot));
|
||||
if (!hmdActive || jointName !== "Hips") {
|
||||
MyAvatar.setJointRotation(j, localRotation);
|
||||
}
|
||||
localRotations[jointName] = localRotation;
|
||||
|
||||
// translation proportions might be different from the neuron avatar and the user avatar skeleton.
|
||||
// so this is disabled by default
|
||||
if (USE_TRANSLATIONS) {
|
||||
var localTranslation = Vec3.multiplyQbyV(Quat.inverse(parentDefaultAbsRot), pose.translation);
|
||||
MyAvatar.setJointTranslation(j, localTranslation);
|
||||
localTranslations[jointName] = localTranslation;
|
||||
} else {
|
||||
localTranslations[jointName] = MyAvatar.getDefaultJointTranslation(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// it attempts to adjust the hips so that the avatar's head is at the same location & oreintation as the HMD.
|
||||
// however it's fighting with the internal c++ code that also attempts to adjust the hips.
|
||||
if (hmdActive) {
|
||||
var UNIT_SCALE = 1 / 100;
|
||||
var hmdXform = new Xform(HMD.orientation, Vec3.multiply(1 / UNIT_SCALE, HMD.position)); // convert to cm
|
||||
var y180Xform = new Xform({x: 0, y: 1, z: 0, w: 0}, {x: 0, y: 0, z: 0});
|
||||
var avatarXform = new Xform(MyAvatar.orientation, Vec3.multiply(1 / UNIT_SCALE, MyAvatar.position)); // convert to cm
|
||||
var hipsJointIndex = MyAvatar.getJointIndex("Hips");
|
||||
var modelOffsetInvXform = new Xform({x: 0, y: 0, z: 0, w: 1}, MyAvatar.getDefaultJointTranslation(hipsJointIndex));
|
||||
var defaultHipsXform = new Xform(MyAvatar.getDefaultJointRotation(hipsJointIndex), MyAvatar.getDefaultJointTranslation(hipsJointIndex));
|
||||
|
||||
var headXform = new Xform(localRotations["Head"], localTranslations["Head"]);
|
||||
|
||||
// transform eyes down the heirarchy chain into avatar space.
|
||||
var hierarchy = ["Neck", "Spine3", "Spine2", "Spine1", "Spine"];
|
||||
var i, l = hierarchy.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
var xform = new Xform(localRotations[hierarchy[i]], localTranslations[hierarchy[i]]);
|
||||
headXform = Xform.mul(xform, headXform);
|
||||
}
|
||||
headXform = Xform.mul(defaultHipsXform, headXform);
|
||||
|
||||
var preXform = Xform.mul(headXform, y180Xform);
|
||||
var postXform = Xform.mul(avatarXform, Xform.mul(y180Xform, modelOffsetInvXform.inv()));
|
||||
|
||||
// solve for the offset that will put the eyes at the hmd position & orientation.
|
||||
var hipsOffsetXform = Xform.mul(postXform.inv(), Xform.mul(hmdXform, preXform.inv()));
|
||||
|
||||
// now combine it with the default hips transform
|
||||
var hipsXform = Xform.mul(hipsOffsetXform, defaultHipsXform);
|
||||
|
||||
MyAvatar.setJointRotation("Hips", hipsXform.rot);
|
||||
MyAvatar.setJointTranslation("Hips", hipsXform.pos);
|
||||
}
|
||||
};
|
||||
|
||||
var neuronAvatar = new NeuronAvatar();
|
||||
|
||||
function updateCallback(deltaTime) {
|
||||
neuronAvatar.update(deltaTime);
|
||||
}
|
||||
|
87
examples/controllers/philipsVersion.js
Normal file
87
examples/controllers/philipsVersion.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
//
|
||||
// reticleTest.js
|
||||
// examples/controllers
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 2015/12/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
|
||||
//
|
||||
|
||||
function length(posA, posB) {
|
||||
var dx = posA.x - posB.x;
|
||||
var dy = posA.y - posB.y;
|
||||
var length = Math.sqrt((dx*dx) + (dy*dy))
|
||||
return length;
|
||||
}
|
||||
|
||||
var PITCH_DEADZONE = 1.0;
|
||||
var PITCH_MAX = 20.0;
|
||||
var YAW_DEADZONE = 1.0;
|
||||
var YAW_MAX = 20.0;
|
||||
var PITCH_SCALING = 10.0;
|
||||
var YAW_SCALING = 10.0;
|
||||
|
||||
var EXPECTED_CHANGE = 50;
|
||||
var lastPos = Controller.getReticlePosition();
|
||||
function moveReticle(dY, dX) {
|
||||
var globalPos = Controller.getReticlePosition();
|
||||
|
||||
// some debugging to see if position is jumping around on us...
|
||||
var distanceSinceLastMove = length(lastPos, globalPos);
|
||||
if (distanceSinceLastMove > EXPECTED_CHANGE) {
|
||||
print("distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------");
|
||||
}
|
||||
|
||||
if (Math.abs(dX) > EXPECTED_CHANGE) {
|
||||
print("UNEXPECTED dX:" + dX + "----------------------------");
|
||||
dX = 0;
|
||||
}
|
||||
if (Math.abs(dY) > EXPECTED_CHANGE) {
|
||||
print("UNEXPECTED dY:" + dY + "----------------------------");
|
||||
dY = 0;
|
||||
}
|
||||
|
||||
globalPos.x += dX;
|
||||
globalPos.y += dY;
|
||||
Controller.setReticlePosition(globalPos);
|
||||
lastPos = globalPos;
|
||||
}
|
||||
|
||||
|
||||
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHand";
|
||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
||||
|
||||
var lastHandPitch = 0;
|
||||
var lastHandYaw = 0;
|
||||
|
||||
mapping.from(Controller.Standard.LeftHand).peek().to(function(pose) {
|
||||
var handEulers = Quat.safeEulerAngles(pose.rotation);
|
||||
//Vec3.print("handEulers:", handEulers);
|
||||
|
||||
var handPitch = handEulers.y;
|
||||
var handYaw = handEulers.x;
|
||||
var changePitch = (handPitch - lastHandPitch) * PITCH_SCALING;
|
||||
var changeYaw = (handYaw - lastHandYaw) * YAW_SCALING;
|
||||
if (Math.abs(changePitch) > PITCH_MAX) {
|
||||
print("Pitch: " + changePitch);
|
||||
changePitch = 0;
|
||||
}
|
||||
if (Math.abs(changeYaw) > YAW_MAX) {
|
||||
print("Yaw: " + changeYaw);
|
||||
changeYaw = 0;
|
||||
}
|
||||
changePitch = Math.abs(changePitch) < PITCH_DEADZONE ? 0 : changePitch;
|
||||
changeYaw = Math.abs(changeYaw) < YAW_DEADZONE ? 0 : changeYaw;
|
||||
moveReticle(changePitch, changeYaw);
|
||||
lastHandPitch = handPitch;
|
||||
lastHandYaw = handYaw;
|
||||
|
||||
});
|
||||
mapping.enable();
|
||||
|
||||
|
||||
Script.scriptEnding.connect(function(){
|
||||
mapping.disable();
|
||||
});
|
76
examples/controllers/proceduralHandPoseExample.js
Normal file
76
examples/controllers/proceduralHandPoseExample.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// proceduralHandPoseExample.js
|
||||
// examples/controllers
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 2015/12/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
|
||||
//
|
||||
|
||||
|
||||
var MAPPING_NAME = "com.highfidelity.examples.proceduralHandPose";
|
||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
||||
var translation = { x: 0, y: 0.1, z: 0 };
|
||||
var translationDx = 0.01;
|
||||
var translationDy = 0.01;
|
||||
var translationDz = -0.01;
|
||||
var TRANSLATION_LIMIT = 0.5;
|
||||
|
||||
var pitch = 45;
|
||||
var yaw = 0;
|
||||
var roll = 45;
|
||||
var pitchDelta = 1;
|
||||
var yawDelta = -1;
|
||||
var rollDelta = 1;
|
||||
var ROTATION_MIN = -90;
|
||||
var ROTATION_MAX = 90;
|
||||
|
||||
mapping.from(function() {
|
||||
|
||||
// adjust the hand translation in a periodic back and forth motion for each of the 3 axes
|
||||
translation.x = translation.x + translationDx;
|
||||
translation.y = translation.y + translationDy;
|
||||
translation.z = translation.z + translationDz;
|
||||
if ((translation.x > TRANSLATION_LIMIT) || (translation.x < (-1 * TRANSLATION_LIMIT))) {
|
||||
translationDx = translationDx * -1;
|
||||
}
|
||||
if ((translation.y > TRANSLATION_LIMIT) || (translation.y < (-1 * TRANSLATION_LIMIT))) {
|
||||
translationDy = translationDy * -1;
|
||||
}
|
||||
if ((translation.z > TRANSLATION_LIMIT) || (translation.z < (-1 * TRANSLATION_LIMIT))) {
|
||||
translationDz = translationDz * -1;
|
||||
}
|
||||
|
||||
// adjust the hand rotation in a periodic back and forth motion for each of pitch/yaw/roll
|
||||
pitch = pitch + pitchDelta;
|
||||
yaw = yaw + yawDelta;
|
||||
roll = roll + rollDelta;
|
||||
if ((pitch > ROTATION_MAX) || (pitch < ROTATION_MIN)) {
|
||||
pitchDelta = pitchDelta * -1;
|
||||
}
|
||||
if ((yaw > ROTATION_MAX) || (yaw < ROTATION_MIN)) {
|
||||
yawDelta = yawDelta * -1;
|
||||
}
|
||||
if ((roll > ROTATION_MAX) || (roll < ROTATION_MIN)) {
|
||||
rollDelta = rollDelta * -1;
|
||||
}
|
||||
|
||||
var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll);
|
||||
|
||||
var pose = {
|
||||
translation: translation,
|
||||
rotation: rotation,
|
||||
velocity: { x: 0, y: 0, z: 0 },
|
||||
angularVelocity: { x: 0, y: 0, z: 0 }
|
||||
};
|
||||
return pose;
|
||||
}).debug(true).to(Controller.Standard.LeftHand);
|
||||
|
||||
Controller.enableMapping(MAPPING_NAME);
|
||||
|
||||
|
||||
Script.scriptEnding.connect(function(){
|
||||
mapping.disable();
|
||||
});
|
121
examples/controllers/reticleHandAngularVelocityTest.js
Normal file
121
examples/controllers/reticleHandAngularVelocityTest.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// reticleHandAngularVelocityTest.js
|
||||
// examples/controllers
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 2015/12/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
|
||||
//
|
||||
|
||||
// If you set this to true, you will get the raw instantaneous angular velocity.
|
||||
// note: there is a LOT of noise in the hydra rotation, you will probably be very
|
||||
// frustrated with the level of jitter.
|
||||
var USE_INSTANTANEOUS_ANGULAR_VELOCITY = false;
|
||||
var whichHand = Controller.Standard.RightHand;
|
||||
var whichTrigger = Controller.Standard.RT;
|
||||
|
||||
|
||||
|
||||
function msecTimestampNow() {
|
||||
var d = new Date();
|
||||
return d.getTime();
|
||||
}
|
||||
|
||||
function length(posA, posB) {
|
||||
var dx = posA.x - posB.x;
|
||||
var dy = posA.y - posB.y;
|
||||
var length = Math.sqrt((dx*dx) + (dy*dy))
|
||||
return length;
|
||||
}
|
||||
|
||||
var EXPECTED_CHANGE = 50;
|
||||
var lastPos = Controller.getReticlePosition();
|
||||
function moveReticle(dX, dY) {
|
||||
var globalPos = Controller.getReticlePosition();
|
||||
|
||||
// some debugging to see if position is jumping around on us...
|
||||
var distanceSinceLastMove = length(lastPos, globalPos);
|
||||
if (distanceSinceLastMove > EXPECTED_CHANGE) {
|
||||
print("------------------ distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------");
|
||||
}
|
||||
|
||||
if (Math.abs(dX) > EXPECTED_CHANGE) {
|
||||
print("surpressing unexpectedly large change dX:" + dX + "----------------------------");
|
||||
dX = 0;
|
||||
}
|
||||
if (Math.abs(dY) > EXPECTED_CHANGE) {
|
||||
print("surpressing unexpectedly large change dY:" + dY + "----------------------------");
|
||||
dY = 0;
|
||||
}
|
||||
|
||||
globalPos.x += dX;
|
||||
globalPos.y += dY;
|
||||
Controller.setReticlePosition(globalPos);
|
||||
lastPos = globalPos;
|
||||
}
|
||||
|
||||
var firstTime = true;
|
||||
var lastTime = msecTimestampNow();
|
||||
var previousRotation;
|
||||
|
||||
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHand";
|
||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
||||
mapping.from(whichTrigger).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
|
||||
mapping.from(whichHand).peek().to(function(pose) {
|
||||
|
||||
var MSECS_PER_SECOND = 1000;
|
||||
var now = msecTimestampNow();
|
||||
var deltaMsecs = (now - lastTime);
|
||||
var deltaTime = deltaMsecs / MSECS_PER_SECOND;
|
||||
|
||||
if (firstTime) {
|
||||
previousRotation = pose.rotation;
|
||||
lastTime = msecTimestampNow();
|
||||
firstTime = false;
|
||||
}
|
||||
|
||||
// pose.angularVelocity - is the angularVelocity in a "physics" sense, that
|
||||
// means the direction of the vector is the axis of symetry of rotation
|
||||
// and the scale of the vector is the speed in radians/second of rotation
|
||||
// around that axis.
|
||||
//
|
||||
// we want to deconstruct that in the portion of the rotation on the Y axis
|
||||
// and make that portion move our reticle in the horizontal/X direction
|
||||
// and the portion of the rotation on the X axis and make that portion
|
||||
// move our reticle in the veritcle/Y direction
|
||||
var xPart = -pose.angularVelocity.y;
|
||||
var yPart = -pose.angularVelocity.x;
|
||||
|
||||
// pose.angularVelocity is "smoothed", we can calculate our own instantaneous
|
||||
// angular velocity as such:
|
||||
if (USE_INSTANTANEOUS_ANGULAR_VELOCITY) {
|
||||
var previousConjugate = Quat.conjugate(previousRotation);
|
||||
var deltaRotation = Quat.multiply(pose.rotation, previousConjugate);
|
||||
var normalizedDeltaRotation = Quat.normalize(deltaRotation);
|
||||
var axis = Quat.axis(normalizedDeltaRotation);
|
||||
var speed = Quat.angle(normalizedDeltaRotation) / deltaTime;
|
||||
var instantaneousAngularVelocity = Vec3.multiply(speed, axis);
|
||||
|
||||
xPart = -instantaneousAngularVelocity.y;
|
||||
yPart = -instantaneousAngularVelocity.x;
|
||||
|
||||
previousRotation = pose.rotation;
|
||||
}
|
||||
|
||||
var MOVE_SCALE = 1;
|
||||
lastTime = now;
|
||||
|
||||
var dX = (xPart * MOVE_SCALE) / deltaTime;
|
||||
var dY = (yPart * MOVE_SCALE) / deltaTime;
|
||||
|
||||
moveReticle(dX, dY);
|
||||
});
|
||||
mapping.enable();
|
||||
|
||||
Script.scriptEnding.connect(function(){
|
||||
mapping.disable();
|
||||
});
|
||||
|
||||
|
110
examples/controllers/reticleHandRotationTest.js
Normal file
110
examples/controllers/reticleHandRotationTest.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// reticleHandRotationTest.js
|
||||
// examples/controllers
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 2015/12/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
|
||||
//
|
||||
|
||||
var DEBUGGING = false;
|
||||
|
||||
Math.clamp=function(a,b,c) {
|
||||
return Math.max(b,Math.min(c,a));
|
||||
}
|
||||
|
||||
function length(posA, posB) {
|
||||
var dx = posA.x - posB.x;
|
||||
var dy = posA.y - posB.y;
|
||||
var length = Math.sqrt((dx*dx) + (dy*dy))
|
||||
return length;
|
||||
}
|
||||
|
||||
function moveReticleAbsolute(x, y) {
|
||||
var globalPos = Controller.getReticlePosition();
|
||||
globalPos.x = x;
|
||||
globalPos.y = y;
|
||||
Controller.setReticlePosition(globalPos);
|
||||
}
|
||||
|
||||
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation";
|
||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
||||
mapping.from(Controller.Standard.LT).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
|
||||
mapping.from(Controller.Standard.RT).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
|
||||
mapping.enable();
|
||||
|
||||
|
||||
|
||||
function debugPrint(message) {
|
||||
if (DEBUGGING) {
|
||||
print(message);
|
||||
}
|
||||
}
|
||||
|
||||
var leftRightBias = 0.0;
|
||||
var filteredRotatedLeft = Vec3.UNIT_NEG_Y;
|
||||
var filteredRotatedRight = Vec3.UNIT_NEG_Y;
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
|
||||
var poseRight = Controller.getPoseValue(Controller.Standard.RightHand);
|
||||
var poseLeft = Controller.getPoseValue(Controller.Standard.LeftHand);
|
||||
|
||||
// NOTE: hack for now
|
||||
var screenSizeX = 1920;
|
||||
var screenSizeY = 1080;
|
||||
|
||||
var rotatedRight = Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y);
|
||||
var rotatedLeft = Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y);
|
||||
|
||||
lastRotatedRight = rotatedRight;
|
||||
|
||||
|
||||
// Decide which hand should be controlling the pointer
|
||||
// by comparing which one is moving more, and by
|
||||
// tending to stay with the one moving more.
|
||||
var BIAS_ADJUST_RATE = 0.5;
|
||||
var BIAS_ADJUST_DEADZONE = 0.05;
|
||||
leftRightBias += (Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity)) * BIAS_ADJUST_RATE;
|
||||
if (leftRightBias < BIAS_ADJUST_DEADZONE) {
|
||||
leftRightBias = 0.0;
|
||||
} else if (leftRightBias > (1.0 - BIAS_ADJUST_DEADZONE)) {
|
||||
leftRightBias = 1.0;
|
||||
}
|
||||
|
||||
// Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers
|
||||
var VELOCITY_FILTER_GAIN = 1.0;
|
||||
filteredRotatedLeft = Vec3.mix(filteredRotatedLeft, rotatedLeft, Math.clamp(Vec3.length(poseLeft.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
|
||||
filteredRotatedRight = Vec3.mix(filteredRotatedRight, rotatedRight, Math.clamp(Vec3.length(poseRight.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
|
||||
var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, leftRightBias);
|
||||
|
||||
var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again...
|
||||
var absoluteYaw = -rotated.x; // from -1 left to 1 right
|
||||
|
||||
var ROTATION_BOUND = 0.6;
|
||||
var clampYaw = Math.clamp(absoluteYaw, -ROTATION_BOUND, ROTATION_BOUND);
|
||||
var clampPitch = Math.clamp(absolutePitch, -ROTATION_BOUND, ROTATION_BOUND);
|
||||
|
||||
// using only from -ROTATION_BOUND to ROTATION_BOUND
|
||||
var xRatio = (clampYaw + ROTATION_BOUND) / (2 * ROTATION_BOUND);
|
||||
var yRatio = (clampPitch + ROTATION_BOUND) / (2 * ROTATION_BOUND);
|
||||
|
||||
var x = screenSizeX * xRatio;
|
||||
var y = screenSizeY * yRatio;
|
||||
|
||||
// don't move the reticle with the hand controllers unless the controllers are actually being moved
|
||||
var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.0001;
|
||||
var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - leftRightBias) + Vec3.length(poseRight.angularVelocity) * leftRightBias;
|
||||
|
||||
if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityMagnitude > MINIMUM_CONTROLLER_ANGULAR_VELOCITY)) {
|
||||
moveReticleAbsolute(x, y);
|
||||
}
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function(){
|
||||
mapping.disable();
|
||||
});
|
||||
|
||||
|
|
@ -33,7 +33,6 @@ var mappingJSON = {
|
|||
|
||||
mapping = Controller.parseMapping(JSON.stringify(mappingJSON));
|
||||
mapping.enable();
|
||||
|
||||
Script.scriptEnding.connect(function(){
|
||||
mapping.disable();
|
||||
});
|
||||
|
|
|
@ -62,8 +62,13 @@ var directory = (function () {
|
|||
function setUp() {
|
||||
viewport = Controller.getViewportDimensions();
|
||||
|
||||
directoryWindow = new WebWindow('Directory', DIRECTORY_URL, 900, 700, false);
|
||||
directoryWindow.setVisible(false);
|
||||
directoryWindow = new OverlayWebWindow({
|
||||
title: 'Directory',
|
||||
source: DIRECTORY_URL,
|
||||
width: 900,
|
||||
height: 700,
|
||||
visible: false
|
||||
});
|
||||
|
||||
directoryButton = Overlays.addOverlay("image", {
|
||||
imageURL: DIRECTORY_BUTTON_URL,
|
||||
|
|
|
@ -140,8 +140,37 @@ var importingSVOTextOverlay = Overlays.addOverlay("text", {
|
|||
});
|
||||
|
||||
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
|
||||
var marketplaceWindow = new WebWindow('Marketplace', MARKETPLACE_URL, 900, 700, false);
|
||||
marketplaceWindow.setVisible(false);
|
||||
var marketplaceWindow = new OverlayWebWindow({
|
||||
title: 'Marketplace',
|
||||
source: "about:blank",
|
||||
width: 900,
|
||||
height: 700,
|
||||
visible: false
|
||||
});
|
||||
|
||||
function showMarketplace(marketplaceID) {
|
||||
var url = MARKETPLACE_URL;
|
||||
if (marketplaceID) {
|
||||
url = url + "/items/" + marketplaceID;
|
||||
}
|
||||
print("setting marketplace URL to " + url);
|
||||
marketplaceWindow.setURL(url);
|
||||
marketplaceWindow.setVisible(true);
|
||||
marketplaceWindow.raise();
|
||||
}
|
||||
|
||||
function hideMarketplace() {
|
||||
marketplaceWindow.setVisible(false);
|
||||
marketplaceWindow.setURL("about:blank");
|
||||
}
|
||||
|
||||
function toggleMarketplace() {
|
||||
if (marketplaceWindow.visible) {
|
||||
hideMarketplace();
|
||||
} else {
|
||||
showMarketplace();
|
||||
}
|
||||
}
|
||||
|
||||
var toolBar = (function() {
|
||||
var that = {},
|
||||
|
@ -413,12 +442,9 @@ var toolBar = (function() {
|
|||
newModelButtonDown = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (browseMarketplaceButton === toolBar.clicked(clickedOverlay)) {
|
||||
if (marketplaceWindow.url != MARKETPLACE_URL) {
|
||||
marketplaceWindow.setURL(MARKETPLACE_URL);
|
||||
}
|
||||
marketplaceWindow.setVisible(true);
|
||||
marketplaceWindow.raise();
|
||||
toggleMarketplace();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1336,6 +1362,7 @@ function getPositionToCreateEntity() {
|
|||
}
|
||||
|
||||
function importSVO(importURL) {
|
||||
print("Import URL requested: " + importURL)
|
||||
if (!Entities.canAdjustLocks()) {
|
||||
Window.alert(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
|
||||
return;
|
||||
|
@ -1574,11 +1601,7 @@ PropertiesTool = function(opts) {
|
|||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
} else if (data.type == "showMarketplace") {
|
||||
if (marketplaceWindow.url != data.url) {
|
||||
marketplaceWindow.setURL(data.url);
|
||||
}
|
||||
marketplaceWindow.setVisible(true);
|
||||
marketplaceWindow.raise();
|
||||
showMarketplace();
|
||||
} else if (data.type == "action") {
|
||||
if (data.action == "moveSelectionToGrid") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
|
@ -1859,12 +1882,7 @@ var propertyMenu = PopupMenu();
|
|||
|
||||
propertyMenu.onSelectMenuItem = function(name) {
|
||||
if (propertyMenu.marketplaceID) {
|
||||
var url = MARKETPLACE_URL + "/items/" + propertyMenu.marketplaceID;
|
||||
if (marketplaceWindow.url != url) {
|
||||
marketplaceWindow.setURL(url);
|
||||
}
|
||||
marketplaceWindow.setVisible(true);
|
||||
marketplaceWindow.raise();
|
||||
showMarketplace(propertyMenu.marketplaceID);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
145
examples/flowArts/arcBall/arcBall.js
Normal file
145
examples/flowArts/arcBall/arcBall.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
//
|
||||
// arcBall.js
|
||||
// examples/arcBall
|
||||
//
|
||||
// Created by Eric Levin on 12/17/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script creats a particle light ball which makes particle trails as you move it.
|
||||
//
|
||||
//
|
||||
// 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/utils.js");
|
||||
|
||||
|
||||
var scriptURL = Script.resolvePath("arcBallEntityScript.js?v1" + Math.random());
|
||||
ArcBall = function(spawnPosition) {
|
||||
|
||||
var colorPalette = [{
|
||||
red: 25,
|
||||
green: 20,
|
||||
blue: 162
|
||||
}];
|
||||
|
||||
|
||||
var containerBall = Entities.addEntity({
|
||||
type: "Sphere",
|
||||
name: "Arc Ball",
|
||||
script: scriptURL,
|
||||
position: Vec3.sum(spawnPosition, {
|
||||
x: 0,
|
||||
y: .7,
|
||||
z: 0
|
||||
}),
|
||||
dimensions: {
|
||||
x: .05,
|
||||
y: .05,
|
||||
z: .05
|
||||
},
|
||||
color: {
|
||||
red: 100,
|
||||
green: 10,
|
||||
blue: 150
|
||||
},
|
||||
ignoreForCollisions: true,
|
||||
damping: 0.8,
|
||||
collisionsWillMove: true,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
spatialKey: {
|
||||
// relativePosition: {
|
||||
// x: 0,
|
||||
// y: -0.5,
|
||||
// z: 0.0
|
||||
// },
|
||||
},
|
||||
// invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
var light = Entities.addEntity({
|
||||
type: 'Light',
|
||||
name: "ballLight",
|
||||
parentID: containerBall,
|
||||
dimensions: {
|
||||
x: 30,
|
||||
y: 30,
|
||||
z: 30
|
||||
},
|
||||
color: colorPalette[randInt(0, colorPalette.length)],
|
||||
intensity: 5
|
||||
});
|
||||
|
||||
|
||||
var arcBall = Entities.addEntity({
|
||||
type: "ParticleEffect",
|
||||
parentID: containerBall,
|
||||
isEmitting: true,
|
||||
name: "Arc Ball Particle Effect",
|
||||
colorStart: {
|
||||
red: 200,
|
||||
green: 20,
|
||||
blue: 40
|
||||
},
|
||||
color: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 255
|
||||
},
|
||||
colorFinish: {
|
||||
red: 25,
|
||||
green: 20,
|
||||
blue: 255
|
||||
},
|
||||
maxParticles: 100000,
|
||||
lifespan: 2,
|
||||
emitRate: 400,
|
||||
emitSpeed: .1,
|
||||
lifetime: -1,
|
||||
speedSpread: 0.0,
|
||||
emitDimensions: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
polarStart: 0,
|
||||
polarFinish: Math.PI,
|
||||
azimuthStart: -Math.PI,
|
||||
azimuthFinish: Math.PI,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: .00,
|
||||
y: .00,
|
||||
z: .00
|
||||
},
|
||||
particleRadius: 0.02,
|
||||
radiusSpread: 0,
|
||||
radiusStart: 0.03,
|
||||
radiusFinish: 0.0003,
|
||||
alpha: 0,
|
||||
alphaSpread: .5,
|
||||
alphaStart: 0,
|
||||
alphaFinish: 0.5,
|
||||
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
|
||||
emitterShouldTrail: true
|
||||
})
|
||||
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(arcBall);
|
||||
Entities.deleteEntity(containerBall);
|
||||
Entities.deleteEntity(light);
|
||||
}
|
||||
|
||||
this.cleanup = cleanup;
|
||||
}
|
155
examples/flowArts/arcBall/arcBallEntityScript.js
Normal file
155
examples/flowArts/arcBall/arcBallEntityScript.js
Normal file
|
@ -0,0 +1,155 @@
|
|||
// arcBallEntityScript.js
|
||||
//
|
||||
// Script Type: Entity
|
||||
// Created by Eric Levin on 12/17/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This entity script handles the logic for the arcBall rave toy
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
Script.include("../../libraries/utils.js");
|
||||
var _this;
|
||||
var ArcBall = function() {
|
||||
_this = this;
|
||||
this.colorPalette = [{
|
||||
red: 25,
|
||||
green: 20,
|
||||
blue: 162
|
||||
}, {
|
||||
red: 200,
|
||||
green: 10,
|
||||
blue: 10
|
||||
}];
|
||||
|
||||
this.searchRadius = 10;
|
||||
};
|
||||
|
||||
ArcBall.prototype = {
|
||||
isGrabbed: false,
|
||||
startDistantGrab: function() {
|
||||
this.searchForNearbyArcBalls();
|
||||
},
|
||||
|
||||
startNearGrab: function() {
|
||||
this.searchForNearbyArcBalls();
|
||||
},
|
||||
|
||||
searchForNearbyArcBalls: function() {
|
||||
//Search for nearby balls and create an arc to it if one is found
|
||||
var position = Entities.getEntityProperties(this.entityID, "position").position
|
||||
var entities = Entities.findEntities(position, this.searchRadius);
|
||||
entities.forEach(function(entity) {
|
||||
var props = Entities.getEntityProperties(entity, ["position", "name"]);
|
||||
if (props.name === "Arc Ball" && JSON.stringify(_this.entityID) !== JSON.stringify(entity)) {
|
||||
_this.target = entity;
|
||||
_this.createBeam(position, props.position);
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
createBeam: function(startPosition, endPosition) {
|
||||
|
||||
// Creates particle arc from start position to end position
|
||||
var rotation = Entities.getEntityProperties(this.entityID, "rotation").rotation;
|
||||
var sourceToTargetVec = Vec3.subtract(endPosition, startPosition);
|
||||
var emitOrientation = Quat.rotationBetween(Vec3.UNIT_Z, sourceToTargetVec);
|
||||
emitOrientation = Quat.multiply(Quat.inverse(rotation), emitOrientation);
|
||||
|
||||
var color = this.colorPalette[randInt(0, this.colorPalette.length)];
|
||||
var props = {
|
||||
type: "ParticleEffect",
|
||||
name: "Particle Arc",
|
||||
parentID: this.entityID,
|
||||
parentJointIndex: -1,
|
||||
// position: startPosition,
|
||||
isEmitting: true,
|
||||
colorStart: color,
|
||||
color: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 255
|
||||
},
|
||||
colorFinish: color,
|
||||
maxParticles: 100000,
|
||||
lifespan: 1,
|
||||
emitRate: 1000,
|
||||
emitOrientation: emitOrientation,
|
||||
emitSpeed: 1,
|
||||
speedSpread: 0.02,
|
||||
emitDimensions: {
|
||||
x: .01,
|
||||
y: .01,
|
||||
z: .01
|
||||
},
|
||||
polarStart: 0,
|
||||
polarFinish: 0,
|
||||
azimuthStart: 0.02,
|
||||
azimuthFinish: .01,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
radiusStart: 0.01,
|
||||
radiusFinish: 0.005,
|
||||
radiusSpread: 0.005,
|
||||
alpha: 0.5,
|
||||
alphaSpread: 0.1,
|
||||
alphaStart: 0.5,
|
||||
alphaFinish: 0.5,
|
||||
textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png",
|
||||
emitterShouldTrail: true
|
||||
}
|
||||
this.particleArc = Entities.addEntity(props);
|
||||
},
|
||||
|
||||
updateBeam: function() {
|
||||
if(!this.target) {
|
||||
return;
|
||||
}
|
||||
var startPosition = Entities.getEntityProperties(this.entityID, "position").position;
|
||||
var targetPosition = Entities.getEntityProperties(this.target, "position").position;
|
||||
var rotation = Entities.getEntityProperties(this.entityID, "rotation").rotation;
|
||||
var sourceToTargetVec = Vec3.subtract(targetPosition, startPosition);
|
||||
var emitOrientation = Quat.rotationBetween(Vec3.UNIT_Z, sourceToTargetVec);
|
||||
Entities.editEntity(this.particleArc, {
|
||||
emitOrientation: emitOrientation
|
||||
});
|
||||
},
|
||||
|
||||
continueNearGrab: function() {
|
||||
this.updateBeam();
|
||||
},
|
||||
|
||||
continueDistantGrab: function() {
|
||||
this.updateBeam();
|
||||
},
|
||||
|
||||
releaseGrab: function() {
|
||||
Entities.editEntity(this.particleArc, {
|
||||
isEmitting: false
|
||||
});
|
||||
this.target = null;
|
||||
},
|
||||
|
||||
unload: function() {
|
||||
if (this.particleArc) {
|
||||
Entities.deleteEntity(this.particleArc);
|
||||
}
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
},
|
||||
};
|
||||
return new ArcBall();
|
||||
});
|
91
examples/flowArts/flowArtsHutSpawner.js
Normal file
91
examples/flowArts/flowArtsHutSpawner.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// flowArtsHutSpawner.js
|
||||
// examples/flowArts
|
||||
//
|
||||
// Created by Eric Levin on 12/17/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script creates a special flow arts hut with a bunch of flow art toys people can go in and play with
|
||||
//
|
||||
//
|
||||
// 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/utils.js");
|
||||
Script.include("lightBall/lightBall.js");
|
||||
Script.include("raveStick/raveStick.js");
|
||||
Script.include("lightSaber/lightSaber.js");
|
||||
Script.include("arcBall/arcBall.js");
|
||||
|
||||
|
||||
|
||||
var basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(1, Quat.getFront(Camera.getOrientation())));
|
||||
basePosition.y = MyAvatar.position.y + 1;
|
||||
|
||||
// RAVE ITEMS
|
||||
// var lightBall = new LightBall(basePosition);
|
||||
|
||||
var arcBall = new ArcBall(basePosition);
|
||||
var arcBall2 = new ArcBall(Vec3.sum(basePosition, {x: -1, y: 0, z: 0}));
|
||||
var raveStick = new RaveStick(Vec3.sum(basePosition, {x: 1, y: 0.5, z: 1}));
|
||||
var lightSaber = new LightSaber(Vec3.sum(basePosition, {x: 3, y: 0.5, z: 1}));
|
||||
|
||||
|
||||
var modelURL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/RaveRoom.fbx";
|
||||
|
||||
var roomDimensions = {x: 30.58, y: 15.29, z: 30.58};
|
||||
|
||||
var raveRoom = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "Rave Hut Room",
|
||||
modelURL: modelURL,
|
||||
position: basePosition,
|
||||
dimensions:roomDimensions,
|
||||
visible: true
|
||||
});
|
||||
|
||||
var floor = Entities.addEntity({
|
||||
type: "Box",
|
||||
name: "Rave Floor",
|
||||
position: Vec3.sum(basePosition, {x: 0, y: -1.2, z: 0}),
|
||||
dimensions: {x: roomDimensions.x, y: 0.6, z: roomDimensions.z},
|
||||
color: {red: 50, green: 10, blue: 100},
|
||||
shapeType: 'box'
|
||||
});
|
||||
|
||||
|
||||
|
||||
var lightZone = Entities.addEntity({
|
||||
type: "Zone",
|
||||
name: "Rave Hut Zone",
|
||||
shapeType: 'box',
|
||||
keyLightIntensity: 0.4,
|
||||
keyLightColor: {
|
||||
red: 50,
|
||||
green: 0,
|
||||
blue: 50
|
||||
},
|
||||
keyLightAmbientIntensity: .2,
|
||||
position: MyAvatar.position,
|
||||
dimensions: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
z: 100
|
||||
}
|
||||
});
|
||||
|
||||
function cleanup() {
|
||||
|
||||
Entities.deleteEntity(raveRoom);
|
||||
Entities.deleteEntity(lightZone);
|
||||
Entities.deleteEntity(floor);
|
||||
// lightBall.cleanup();
|
||||
arcBall.cleanup();
|
||||
arcBall2.cleanup();
|
||||
raveStick.cleanup();
|
||||
lightSaber.cleanup();
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
145
examples/flowArts/lightBall/lightBall.js
Normal file
145
examples/flowArts/lightBall/lightBall.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
//
|
||||
// LightBall.js
|
||||
// examples/lightBall
|
||||
//
|
||||
// Created by Eric Levin on 12/17/15.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// This script creats a particle light ball which makes particle trails as you move it.
|
||||
//
|
||||
//
|
||||
// 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/utils.js");
|
||||
|
||||
LightBall = function(spawnPosition) {
|
||||
|
||||
var colorPalette = [{
|
||||
red: 25,
|
||||
green: 20,
|
||||
blue: 162
|
||||
}];
|
||||
|
||||
|
||||
var containerBall = Entities.addEntity({
|
||||
type: "Sphere",
|
||||
name: "containerBall",
|
||||
position: Vec3.sum(spawnPosition, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}),
|
||||
dimensions: {
|
||||
x: 0.1,
|
||||
y: 0.1,
|
||||
z: 0.1
|
||||
},
|
||||
color: {
|
||||
red: 15,
|
||||
green: 10,
|
||||
blue: 150
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
// gravity: {
|
||||
// x: 0,
|
||||
// y: -0.5,
|
||||
// z: 0
|
||||
// },
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
spatialKey: {
|
||||
relativePosition: {
|
||||
x: 0,
|
||||
y: 0.1,
|
||||
z: 0
|
||||
}
|
||||
},
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
var light = Entities.addEntity({
|
||||
type: 'Light',
|
||||
name: "ballLight",
|
||||
parentID: containerBall,
|
||||
dimensions: {
|
||||
x: 30,
|
||||
y: 30,
|
||||
z: 30
|
||||
},
|
||||
color: colorPalette[randInt(0, colorPalette.length)],
|
||||
intensity: 5
|
||||
});
|
||||
|
||||
|
||||
var lightBall = Entities.addEntity({
|
||||
type: "ParticleEffect",
|
||||
parentID: containerBall,
|
||||
isEmitting: true,
|
||||
name: "particleBall",
|
||||
colorStart: {
|
||||
red: 200,
|
||||
green: 20,
|
||||
blue: 40
|
||||
},
|
||||
color: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 255
|
||||
},
|
||||
colorFinish: {
|
||||
red: 25,
|
||||
green: 20,
|
||||
blue: 255
|
||||
},
|
||||
maxParticles: 100000,
|
||||
lifespan: 2,
|
||||
emitRate: 10000,
|
||||
emitSpeed: 0.1,
|
||||
lifetime: -1,
|
||||
speedSpread: 0.0,
|
||||
emitDimensions: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
polarStart: 0,
|
||||
polarFinish: Math.PI,
|
||||
azimuthStart: -Math.PI,
|
||||
azimuthFinish: Math.PI,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: 0.00,
|
||||
y: 0.00,
|
||||
z: 0.00
|
||||
},
|
||||
particleRadius: 0.02,
|
||||
radiusSpread: 0,
|
||||
radiusStart: 0.03,
|
||||
radiusFinish: 0.0003,
|
||||
alpha: 0,
|
||||
alphaSpread: 0.5,
|
||||
alphaStart: 0,
|
||||
alphaFinish: 0.5,
|
||||
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
|
||||
emitterShouldTrail: true
|
||||
})
|
||||
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(lightBall);
|
||||
Entities.deleteEntity(containerBall);
|
||||
Entities.deleteEntity(light);
|
||||
}
|
||||
|
||||
this.cleanup = cleanup;
|
||||
}
|
67
examples/flowArts/lightSaber/lightSaber.js
Normal file
67
examples/flowArts/lightSaber/lightSaber.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// LightSaber.js
|
||||
// examples
|
||||
//
|
||||
// Created by Eric Levin on 12/17/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script creates a lightsaber which activates on grab
|
||||
//
|
||||
//
|
||||
// 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/utils.js");
|
||||
var modelURL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/lightSaber.fbx";
|
||||
var scriptURL = Script.resolvePath("lightSaberEntityScript.js");
|
||||
LightSaber = function(spawnPosition) {
|
||||
|
||||
var saberHandle = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "LightSaber Handle",
|
||||
modelURL: modelURL,
|
||||
position: spawnPosition,
|
||||
shapeType: 'box',
|
||||
collisionsWillMove: true,
|
||||
script: scriptURL,
|
||||
dimensions: {
|
||||
x: 0.06,
|
||||
y: 0.06,
|
||||
z: 0.31
|
||||
},
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
spatialKey: {
|
||||
relativePosition: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.1
|
||||
},
|
||||
relativeRotation: Quat.fromPitchYawRollDegrees(180, 90, 0)
|
||||
},
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
var light = Entities.addEntity({
|
||||
type: 'Light',
|
||||
name: "raveLight",
|
||||
parentID: saberHandle,
|
||||
dimensions: {
|
||||
x: 30,
|
||||
y: 30,
|
||||
z: 30
|
||||
},
|
||||
color: {red: 200, green: 10, blue: 200},
|
||||
intensity: 5
|
||||
});
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(saberHandle);
|
||||
}
|
||||
|
||||
this.cleanup = cleanup;
|
||||
}
|
116
examples/flowArts/lightSaber/lightSaberEntityScript.js
Normal file
116
examples/flowArts/lightSaber/lightSaberEntityScript.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
// lightSaberEntityScript.js
|
||||
//
|
||||
// Script Type: Entity
|
||||
// Created by Eric Levin on 12/17/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This entity script creates the logic for displaying the lightsaber beam.
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
Script.include("../../libraries/utils.js");
|
||||
var _this;
|
||||
// this is the "constructor" for the entity as a JS object we don't do much here
|
||||
var LightSaber = function() {
|
||||
_this = this;
|
||||
this.colorPalette = [{
|
||||
red: 0,
|
||||
green: 200,
|
||||
blue: 40
|
||||
}, {
|
||||
red: 200,
|
||||
green: 10,
|
||||
blue: 40
|
||||
}];
|
||||
};
|
||||
|
||||
LightSaber.prototype = {
|
||||
isGrabbed: false,
|
||||
|
||||
startNearGrab: function() {
|
||||
Entities.editEntity(this.beam, {
|
||||
isEmitting: true,
|
||||
visible: true
|
||||
});
|
||||
},
|
||||
|
||||
releaseGrab: function() {
|
||||
Entities.editEntity(this.beam, {
|
||||
visible: false,
|
||||
isEmitting: false
|
||||
});
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
this.createBeam();
|
||||
},
|
||||
|
||||
unload: function() {
|
||||
Entities.deleteEntity(this.beam);
|
||||
},
|
||||
|
||||
createBeam: function() {
|
||||
|
||||
this.props = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
||||
var forwardVec = Quat.getFront(Quat.multiply(this.props.rotation, Quat.fromPitchYawRollDegrees(-90, 0, 0)));
|
||||
var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec);
|
||||
var position = this.props.position;
|
||||
|
||||
var color = this.colorPalette[randInt(0, this.colorPalette.length)];
|
||||
var props = {
|
||||
type: "ParticleEffect",
|
||||
name: "LightSaber Beam",
|
||||
position: position,
|
||||
parentID: this.entityID,
|
||||
isEmitting: false,
|
||||
colorStart: color,
|
||||
color: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 255
|
||||
},
|
||||
colorFinish: color,
|
||||
maxParticles: 100000,
|
||||
lifespan: 2,
|
||||
emitRate: 1000,
|
||||
emitOrientation: forwardQuat,
|
||||
emitSpeed: 0.7,
|
||||
speedSpread: 0.0,
|
||||
emitDimensions: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
polarStart: 0,
|
||||
polarFinish: 0,
|
||||
azimuthStart: 0.1,
|
||||
azimuthFinish: 0.01,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: .00,
|
||||
y: .00,
|
||||
z: .00
|
||||
},
|
||||
radiusStart: 0.03,
|
||||
adiusFinish: 0.025,
|
||||
alpha: 0.7,
|
||||
alphaSpread: 0.1,
|
||||
alphaStart: 0.5,
|
||||
alphaFinish: 0.5,
|
||||
textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png",
|
||||
emitterShouldTrail: false
|
||||
}
|
||||
this.beam = Entities.addEntity(props);
|
||||
|
||||
}
|
||||
};
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new LightSaber();
|
||||
});
|
191
examples/flowArts/lightTrails.js
Normal file
191
examples/flowArts/lightTrails.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
//
|
||||
// lightTrails.js
|
||||
// examples
|
||||
//
|
||||
// Created by Eric Levin on 5/14/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script creates light trails as you move your hydra hands
|
||||
//
|
||||
//
|
||||
// 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/utils.js");
|
||||
|
||||
var eraseTrail = true;
|
||||
var ugLSD = 25;
|
||||
// var eraseTrail = false;
|
||||
var LEFT = 0;
|
||||
var RIGHT = 1;
|
||||
var MAX_POINTS_PER_LINE = 50;
|
||||
|
||||
var LIFETIME = 6000;
|
||||
var DRAWING_DEPTH = 0.8;
|
||||
var LINE_DIMENSIONS = 100;
|
||||
|
||||
|
||||
var MIN_POINT_DISTANCE = 0.02;
|
||||
|
||||
|
||||
var colorPalette = [{
|
||||
red: 250,
|
||||
green: 137,
|
||||
blue: 162
|
||||
}, {
|
||||
red: 204,
|
||||
green: 244,
|
||||
blue: 249
|
||||
}, {
|
||||
red: 146,
|
||||
green: 206,
|
||||
blue: 116
|
||||
}, {
|
||||
red: 240,
|
||||
green: 87,
|
||||
blue: 129
|
||||
}];
|
||||
|
||||
var STROKE_WIDTH = 0.04;
|
||||
|
||||
function controller(side, triggerAction) {
|
||||
this.triggerHeld = false;
|
||||
this.triggerThreshold = 0.9;
|
||||
this.side = side;
|
||||
this.triggerAction = triggerAction;
|
||||
var texture = "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/trails.png";
|
||||
|
||||
this.light = Entities.addEntity({
|
||||
type: 'Light',
|
||||
position: MyAvatar.position,
|
||||
dimensions: {
|
||||
x: 30,
|
||||
y: 30,
|
||||
z: 30
|
||||
},
|
||||
color: colorPalette[randInt(0, colorPalette.length)],
|
||||
intensity: 5
|
||||
});
|
||||
|
||||
this.trail = Entities.addEntity({
|
||||
type: "PolyLine",
|
||||
dimensions: {
|
||||
x: LINE_DIMENSIONS,
|
||||
y: LINE_DIMENSIONS,
|
||||
z: LINE_DIMENSIONS
|
||||
},
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
textures: texture,
|
||||
lifetime: LIFETIME
|
||||
});
|
||||
this.points = [];
|
||||
this.normals = [];
|
||||
this.strokeWidths = [];
|
||||
var self = this;
|
||||
|
||||
this.trailEraseInterval = Script.setInterval(function() {
|
||||
if (self.points.length > 0 && eraseTrail) {
|
||||
self.points.shift();
|
||||
self.normals.shift();
|
||||
self.strokeWidths.shift();
|
||||
Entities.editEntity(self.trail, {
|
||||
linePoints: self.points,
|
||||
strokeWidths: self.strokeWidths,
|
||||
normals: self.normals
|
||||
});
|
||||
}
|
||||
}, ugLSD);
|
||||
|
||||
|
||||
this.setTrailPosition = function(position) {
|
||||
this.trailPosition = position;
|
||||
Entities.editEntity(this.trail, {
|
||||
position: this.trailPosition
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.update = function(deltaTime) {
|
||||
this.updateControllerState();
|
||||
var newTrailPosOffset = Vec3.multiply(Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)), DRAWING_DEPTH);
|
||||
var newTrailPos = Vec3.sum(this.palmPosition, newTrailPosOffset);
|
||||
Entities.editEntity(this.light, {
|
||||
position: newTrailPos
|
||||
});
|
||||
|
||||
|
||||
if (!this.drawing) {
|
||||
this.setTrailPosition(newTrailPos);
|
||||
this.drawing = true;
|
||||
}
|
||||
|
||||
if (this.drawing) {
|
||||
var localPoint = Vec3.subtract(newTrailPos, this.trailPosition);
|
||||
if (Vec3.distance(localPoint, this.points[this.points.length - 1]) < MIN_POINT_DISTANCE) {
|
||||
//Need a minimum distance to avoid binormal NANs
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.points.length === MAX_POINTS_PER_LINE) {
|
||||
this.points.shift();
|
||||
this.normals.shift();
|
||||
this.strokeWidths.shift();
|
||||
}
|
||||
|
||||
this.points.push(localPoint);
|
||||
var normal = computeNormal(newTrailPos, Camera.getPosition());
|
||||
|
||||
this.normals.push(normal);
|
||||
this.strokeWidths.push(STROKE_WIDTH + Math.random() * 0.01);
|
||||
Entities.editEntity(this.trail, {
|
||||
linePoints: this.points,
|
||||
normals: this.normals,
|
||||
strokeWidths: this.strokeWidths,
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.updateControllerState = function() {
|
||||
this.palmPosition = this.side == RIGHT ? MyAvatar.rightHandPose.translation : MyAvatar.leftHandPose.translation;
|
||||
this.tipPosition = this.side == RIGHT ? MyAvatar.rightHandTipPose.translation : MyAvatar.leftHandTipPose.translation;
|
||||
this.triggerValue = Controller.getActionValue(this.triggerAction);
|
||||
|
||||
}
|
||||
|
||||
this.cleanup = function() {
|
||||
Entities.deleteEntity(this.trail);
|
||||
Entities.deleteEntity(this.light);
|
||||
Script.clearInterval(this.trailEraseInterval);
|
||||
}
|
||||
}
|
||||
|
||||
function computeNormal(p1, p2) {
|
||||
return Vec3.normalize(Vec3.subtract(p2, p1));
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
leftController.update(deltaTime);
|
||||
rightController.update(deltaTime);
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
leftController.cleanup();
|
||||
rightController.cleanup();
|
||||
}
|
||||
|
||||
function vectorIsZero(v) {
|
||||
return v.x === 0 && v.y === 0 && v.z === 0;
|
||||
}
|
||||
|
||||
|
||||
var rightController = new controller(RIGHT, Controller.findAction("RIGHT_HAND_CLICK"));
|
||||
var leftController = new controller(LEFT, Controller.findAction("LEFT_HAND_CLICK"));
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
function map(value, min1, max1, min2, max2) {
|
||||
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
|
||||
}
|
95
examples/flowArts/raveStick/raveStick.js
Normal file
95
examples/flowArts/raveStick/raveStick.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// RaveStick.js
|
||||
// examples/flowArats/raveStick
|
||||
//
|
||||
// Created by Eric Levin on 12/17/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script creates a rave stick which makes pretty light trails as you paint
|
||||
//
|
||||
// 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/utils.js");
|
||||
var modelURL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/raveStick.fbx";
|
||||
var scriptURL = Script.resolvePath("raveStickEntityScript.js");
|
||||
RaveStick = function(spawnPosition) {
|
||||
var colorPalette = [{
|
||||
red: 0,
|
||||
green: 200,
|
||||
blue: 40
|
||||
}, {
|
||||
red: 200,
|
||||
green: 10,
|
||||
blue: 40
|
||||
}];
|
||||
var stick = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "raveStick",
|
||||
modelURL: modelURL,
|
||||
position: spawnPosition,
|
||||
shapeType: 'box',
|
||||
collisionsWillMove: true,
|
||||
script: scriptURL,
|
||||
dimensions: {
|
||||
x: 0.06,
|
||||
y: 0.06,
|
||||
z: 0.31
|
||||
},
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
spatialKey: {
|
||||
rightRelativePosition: {
|
||||
x: 0.02,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
leftRelativePosition: {
|
||||
x: -0.02,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
relativeRotation: Quat.fromPitchYawRollDegrees(90, 90, 0)
|
||||
},
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
var light = Entities.addEntity({
|
||||
type: 'Light',
|
||||
name: "raveLight",
|
||||
parentID: stick,
|
||||
dimensions: {
|
||||
x: 30,
|
||||
y: 30,
|
||||
z: 30
|
||||
},
|
||||
color: colorPalette[randInt(0, colorPalette.length)],
|
||||
intensity: 5
|
||||
});
|
||||
|
||||
var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0)
|
||||
var forwardVec = Quat.getFront(Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(-90, 0, 0)));
|
||||
forwardVec = Vec3.normalize(forwardVec);
|
||||
var forwardQuat = orientationOf(forwardVec);
|
||||
var position = Vec3.sum(spawnPosition, Vec3.multiply(Quat.getFront(rotation), 0.1));
|
||||
position.z += 0.1;
|
||||
position.x += -0.035;
|
||||
var color = {
|
||||
red: 0,
|
||||
green: 200,
|
||||
blue: 40
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(stick);
|
||||
Entities.deleteEntity(light);
|
||||
}
|
||||
|
||||
this.cleanup = cleanup;
|
||||
}
|
141
examples/flowArts/raveStick/raveStickEntityScript.js
Normal file
141
examples/flowArts/raveStick/raveStickEntityScript.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
// raveStickEntityScript.js
|
||||
//
|
||||
// Script Type: Entity
|
||||
// Created by Eric Levin on 12/16/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This entity script create light trails on a given object as it moves.
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
Script.include("../../libraries/utils.js");
|
||||
var _this;
|
||||
var LIFETIME = 6000;
|
||||
var DRAWING_DEPTH = 0.8;
|
||||
var LINE_DIMENSIONS = 100;
|
||||
var MAX_POINTS_PER_LINE = 50;
|
||||
var MIN_POINT_DISTANCE = 0.02;
|
||||
var STROKE_WIDTH = 0.05
|
||||
var ugLSD = 35;
|
||||
var RaveStick = function() {
|
||||
_this = this;
|
||||
this.colorPalette = [{
|
||||
red: 0,
|
||||
green: 200,
|
||||
blue: 40
|
||||
}, {
|
||||
red: 200,
|
||||
green: 10,
|
||||
blue: 40
|
||||
}];
|
||||
var texture = "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/trails.png";
|
||||
this.trail = Entities.addEntity({
|
||||
type: "PolyLine",
|
||||
dimensions: {
|
||||
x: LINE_DIMENSIONS,
|
||||
y: LINE_DIMENSIONS,
|
||||
z: LINE_DIMENSIONS
|
||||
},
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
textures: texture,
|
||||
lifetime: LIFETIME
|
||||
});
|
||||
|
||||
this.points = [];
|
||||
this.normals = [];
|
||||
this.strokeWidths = [];
|
||||
};
|
||||
|
||||
RaveStick.prototype = {
|
||||
isGrabbed: false,
|
||||
|
||||
startNearGrab: function() {
|
||||
this.trailBasePosition = Entities.getEntityProperties(this.entityID, "position").position;
|
||||
Entities.editEntity(this.trail, {
|
||||
position: this.trailBasePosition
|
||||
});
|
||||
this.points = [];
|
||||
this.normals = [];
|
||||
this.strokeWidths = [];
|
||||
this.setupEraseInterval();
|
||||
},
|
||||
|
||||
continueNearGrab: function() {
|
||||
var props = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
||||
var forwardVec = Quat.getFront(Quat.multiply(props.rotation, Quat.fromPitchYawRollDegrees(-90, 0, 0)));
|
||||
forwardVec = Vec3.normalize(forwardVec);
|
||||
var forwardQuat = orientationOf(forwardVec);
|
||||
var position = Vec3.sum(props.position, Vec3.multiply(Quat.getFront(props.rotation), 0.04));
|
||||
var localPoint = Vec3.subtract(position, this.trailBasePosition);
|
||||
if (this.points.length >= 1 && Vec3.distance(localPoint, this.points[this.points.length - 1]) < MIN_POINT_DISTANCE) {
|
||||
//Need a minimum distance to avoid binormal NANs
|
||||
return;
|
||||
}
|
||||
if (this.points.length === MAX_POINTS_PER_LINE) {
|
||||
this.points.shift();
|
||||
this.normals.shift();
|
||||
this.strokeWidths.shift();
|
||||
}
|
||||
|
||||
this.points.push(localPoint);
|
||||
var normal = Quat.getUp(props.rotation);
|
||||
this.normals.push(normal);
|
||||
this.strokeWidths.push(STROKE_WIDTH);
|
||||
Entities.editEntity(this.trail, {
|
||||
linePoints: this.points,
|
||||
normals: this.normals,
|
||||
strokeWidths: this.strokeWidths
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
|
||||
setupEraseInterval: function() {
|
||||
this.trailEraseInterval = Script.setInterval(function() {
|
||||
if (_this.points.length > 0) {
|
||||
_this.points.shift();
|
||||
_this.normals.shift();
|
||||
_this.strokeWidths.shift();
|
||||
Entities.editEntity(_this.trail, {
|
||||
linePoints: _this.points,
|
||||
strokeWidths: _this.strokeWidths,
|
||||
normals: _this.normals
|
||||
});
|
||||
}
|
||||
}, ugLSD);
|
||||
},
|
||||
|
||||
releaseGrab: function() {
|
||||
if(!this.trailEraseInterval) {
|
||||
return;
|
||||
}
|
||||
Script.setTimeout(function() {
|
||||
Script.clearInterval(_this.trailEraseInterval);
|
||||
_this.trailEraseInterval = null;
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
},
|
||||
|
||||
unload: function() {
|
||||
Entities.deleteEntity(this.beam);
|
||||
Entities.deleteEntity(this.trail);
|
||||
if (this.trailEraseInterval) {
|
||||
Script.clearInterval(this.trailEraseInterval);
|
||||
}
|
||||
}
|
||||
};
|
||||
return new RaveStick();
|
||||
|
||||
function computeNormal(p1, p2) {
|
||||
return Vec3.normalize(Vec3.subtract(p2, p1));
|
||||
}
|
||||
});
|
59
examples/html/eventBridgeLoader.js
Normal file
59
examples/html/eventBridgeLoader.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
|
||||
//public slots:
|
||||
// void emitWebEvent(const QString& data);
|
||||
// void emitScriptEvent(const QString& data);
|
||||
//
|
||||
//signals:
|
||||
// void webEventReceived(const QString& data);
|
||||
// void scriptEventReceived(const QString& data);
|
||||
//
|
||||
|
||||
EventBridgeConnectionProxy = function(parent) {
|
||||
this.parent = parent;
|
||||
this.realSignal = this.parent.realBridge.scriptEventReceived
|
||||
this.webWindowId = this.parent.webWindow.windowId;
|
||||
}
|
||||
|
||||
EventBridgeConnectionProxy.prototype.connect = function(callback) {
|
||||
var that = this;
|
||||
this.realSignal.connect(function(id, message) {
|
||||
if (id === that.webWindowId) { callback(message); }
|
||||
});
|
||||
}
|
||||
|
||||
EventBridgeProxy = function(webWindow) {
|
||||
this.webWindow = webWindow;
|
||||
this.realBridge = this.webWindow.eventBridge;
|
||||
this.scriptEventReceived = new EventBridgeConnectionProxy(this);
|
||||
}
|
||||
|
||||
EventBridgeProxy.prototype.emitWebEvent = function(data) {
|
||||
this.realBridge.emitWebEvent(data);
|
||||
}
|
||||
|
||||
openEventBridge = function(callback) {
|
||||
EVENT_BRIDGE_URI = "ws://localhost:51016";
|
||||
socket = new WebSocket(this.EVENT_BRIDGE_URI);
|
||||
|
||||
socket.onclose = function() {
|
||||
console.error("web channel closed");
|
||||
};
|
||||
|
||||
socket.onerror = function(error) {
|
||||
console.error("web channel error: " + error);
|
||||
};
|
||||
|
||||
socket.onopen = function() {
|
||||
channel = new QWebChannel(socket, function(channel) {
|
||||
console.log("Document url is " + document.URL);
|
||||
for(var key in channel.objects){
|
||||
console.log("registered object: " + key);
|
||||
}
|
||||
var webWindow = channel.objects[document.URL.toLowerCase()];
|
||||
console.log("WebWindow is " + webWindow)
|
||||
eventBridgeProxy = new EventBridgeProxy(webWindow);
|
||||
if (callback) { callback(eventBridgeProxy); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
31
examples/html/qmlWebTest.html
Normal file
31
examples/html/qmlWebTest.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Properties</title>
|
||||
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="eventBridgeLoader.js"></script>
|
||||
|
||||
<script>
|
||||
var myBridge;
|
||||
|
||||
window.onload = function() {
|
||||
openEventBridge(function(eventBridge) {
|
||||
myBridge = eventBridge;
|
||||
myBridge.scriptEventReceived.connect(function(message) {
|
||||
console.log("HTML side received message: " + message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
testClick = function() {
|
||||
myBridge.emitWebEvent("HTML side sending message - button click");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="properties">
|
||||
<button name="Test" title="Test" onclick="testClick()">Test</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</body>
|
File diff suppressed because it is too large
Load diff
|
@ -53,7 +53,9 @@ LightOverlayManager = function() {
|
|||
if (visible != isVisible) {
|
||||
visible = isVisible;
|
||||
for (var id in entityOverlays) {
|
||||
Overlays.editOverlay(entityOverlays[id], { visible: visible });
|
||||
Overlays.editOverlay(entityOverlays[id], {
|
||||
visible: visible
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -61,8 +63,7 @@ LightOverlayManager = function() {
|
|||
// Allocate or get an unused overlay
|
||||
function getOverlay() {
|
||||
if (unusedOverlays.length == 0) {
|
||||
var overlay = Overlays.addOverlay("image3d", {
|
||||
});
|
||||
var overlay = Overlays.addOverlay("image3d", {});
|
||||
allOverlays.push(overlay);
|
||||
} else {
|
||||
var overlay = unusedOverlays.pop();
|
||||
|
@ -72,7 +73,9 @@ LightOverlayManager = function() {
|
|||
|
||||
function releaseOverlay(overlay) {
|
||||
unusedOverlays.push(overlay);
|
||||
Overlays.editOverlay(overlay, { visible: false });
|
||||
Overlays.editOverlay(overlay, {
|
||||
visible: false
|
||||
});
|
||||
}
|
||||
|
||||
function addEntity(entityID) {
|
||||
|
@ -88,7 +91,11 @@ LightOverlayManager = function() {
|
|||
visible: visible,
|
||||
alpha: 0.9,
|
||||
scale: 0.5,
|
||||
color: { red: 255, green: 255, blue: 255 }
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -123,4 +130,4 @@ LightOverlayManager = function() {
|
|||
Overlays.deleteOverlay(allOverlays[i]);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
|
@ -271,3 +271,29 @@ hexToRgb = function(hex) {
|
|||
} : null;
|
||||
}
|
||||
|
||||
calculateHandSizeRatio = function() {
|
||||
// Get the ratio of the current avatar's hand to Owen's hand
|
||||
|
||||
var standardCenterHandPoint = 0.11288;
|
||||
var jointNames = MyAvatar.getJointNames();
|
||||
//get distance from handJoint up to leftHandIndex3 as a proxy for center of hand
|
||||
var wristToFingertipDistance = 0;;
|
||||
for (var i = 0; i < jointNames.length; i++) {
|
||||
var jointName = jointNames[i];
|
||||
print(jointName)
|
||||
if (jointName.indexOf("LeftHandIndex") !== -1) {
|
||||
// translations are relative to parent joint, so simply add them together
|
||||
// joints face down the y-axis
|
||||
var translation = MyAvatar.getDefaultJointTranslation(i).y;
|
||||
wristToFingertipDistance += translation;
|
||||
}
|
||||
}
|
||||
// Right now units are in cm, so convert to meters
|
||||
wristToFingertipDistance /= 100;
|
||||
|
||||
var centerHandPoint = wristToFingertipDistance/2;
|
||||
|
||||
// Compare against standard hand (Owen)
|
||||
var handSizeRatio = centerHandPoint/standardCenterHandPoint;
|
||||
return handSizeRatio;
|
||||
}
|
||||
|
|
29
examples/light_modifier/README.md
Normal file
29
examples/light_modifier/README.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
This PR demonstrates one way in-world editing of objects might work.
|
||||
|
||||
Running this script will show light overlay icons in-world. Enter edit mode by running your distance beam through a light overlay. Exit using the red X.
|
||||
|
||||
When you distant grab the sliders, you can move them along their axis to change their values. You may also rotate / move the block to which the spotlight is attached.
|
||||
|
||||
To test: https://rawgit.com/imgntn/hifi/light_mod/examples/lights/lightLoader.js
|
||||
To reset, I recommend stopping all scripts then re-loading lightLoader.js
|
||||
|
||||
When you run the lightLoader.js script, several scripts will be loaded:
|
||||
- handControllerGrab.js (will not impart velocity when you move the parent or a slider, will not move sliders with head movement,will constrain movement for a slider to a given axis start and end, will support blacklisting of entities for raypicking during search for objects)
|
||||
- lightModifier.js (listens for message to create sliders for a given light. will start with slider set to the light's initial properties)
|
||||
- lightModifierTestScene.js (creates a light)
|
||||
- slider.js (attached to each slider entity)
|
||||
- lightParent.js (attached to a 3d model of a light, to which a light is parented, so you can move it around. or keep the current parent if a light already has a parent)
|
||||
- visiblePanel.js (the transparent panel)
|
||||
- closeButton.js (for closing the ui)
|
||||
- ../libraries/lightOverlayManager.js (shows 2d overlays for lights in the world)
|
||||
- ../libraries/entitySelectionTool.js (visualizes volume of the lights)
|
||||
|
||||
Current sliders are (top to bottom):
|
||||
red
|
||||
green
|
||||
blue
|
||||
intensity
|
||||
cutoff
|
||||
exponent
|
||||
|
||||

|
36
examples/light_modifier/closeButton.js
Normal file
36
examples/light_modifier/closeButton.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// closeButton.js
|
||||
//
|
||||
// Created by James Pollack @imgntn on 12/15/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Entity script that closes sliders when interacted with.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
|
||||
function CloseButton() {
|
||||
return this;
|
||||
}
|
||||
|
||||
CloseButton.prototype = {
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
var entityProperties = Entities.getEntityProperties(this.entityID, "userData");
|
||||
this.initialProperties = entityProperties
|
||||
this.userData = JSON.parse(entityProperties.userData);
|
||||
},
|
||||
startNearGrab: function() {
|
||||
|
||||
},
|
||||
startFarTrigger: function() {
|
||||
Messages.sendMessage('Hifi-Light-Modifier-Cleanup', 'callCleanup')
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return new CloseButton();
|
||||
});
|
20
examples/light_modifier/lightLoader.js
Normal file
20
examples/light_modifier/lightLoader.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// lightLoader.js
|
||||
//
|
||||
// Created by James Pollack @imgntn on 12/15/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Loads a test scene showing sliders that you can grab and move to change entity properties.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var grabScript = Script.resolvePath('../controllers/handControllerGrab.js?' + Math.random(0 - 100));
|
||||
Script.load(grabScript);
|
||||
var lightModifier = Script.resolvePath('lightModifier.js?' + Math.random(0 - 100));
|
||||
Script.load(lightModifier);
|
||||
Script.setTimeout(function() {
|
||||
var lightModifierTestScene = Script.resolvePath('lightModifierTestScene.js?' + Math.random(0 - 100));
|
||||
Script.load(lightModifierTestScene);
|
||||
}, 750)
|
876
examples/light_modifier/lightModifier.js
Normal file
876
examples/light_modifier/lightModifier.js
Normal file
|
@ -0,0 +1,876 @@
|
|||
//
|
||||
// lightModifier.js
|
||||
//
|
||||
// Created by James Pollack @imgntn on 12/15/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Given a selected light, instantiate some entities that represent various values you can dynamically adjust by grabbing and moving.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
//some experimental options
|
||||
var ONLY_I_CAN_EDIT = false;
|
||||
var SLIDERS_SHOULD_STAY_WITH_AVATAR = false;
|
||||
var VERTICAL_SLIDERS = false;
|
||||
var SHOW_OVERLAYS = true;
|
||||
var SHOW_LIGHT_VOLUME = true;
|
||||
var USE_PARENTED_PANEL = true;
|
||||
var VISIBLE_PANEL = true;
|
||||
var USE_LABELS = true;
|
||||
var LEFT_LABELS = false;
|
||||
var RIGHT_LABELS = true;
|
||||
var ROTATE_CLOSE_BUTTON = false;
|
||||
|
||||
//variables for managing overlays
|
||||
var selectionDisplay;
|
||||
var selectionManager;
|
||||
var lightOverlayManager;
|
||||
|
||||
//for when we make a 3d model of a light a parent for the light
|
||||
var PARENT_SCRIPT_URL = Script.resolvePath('lightParent.js?' + Math.random(0 - 100));
|
||||
|
||||
if (SHOW_OVERLAYS === true) {
|
||||
|
||||
Script.include('../libraries/gridTool.js');
|
||||
Script.include('../libraries/entitySelectionTool.js?' + Math.random(0 - 100));
|
||||
Script.include('../libraries/lightOverlayManager.js');
|
||||
|
||||
var grid = Grid();
|
||||
gridTool = GridTool({
|
||||
horizontalGrid: grid
|
||||
});
|
||||
gridTool.setVisible(false);
|
||||
|
||||
selectionDisplay = SelectionDisplay;
|
||||
selectionManager = SelectionManager;
|
||||
lightOverlayManager = new LightOverlayManager();
|
||||
selectionManager.addEventListener(function() {
|
||||
selectionDisplay.updateHandles();
|
||||
lightOverlayManager.updatePositions();
|
||||
});
|
||||
lightOverlayManager.setVisible(true);
|
||||
}
|
||||
|
||||
var DEFAULT_PARENT_ID = '{00000000-0000-0000-0000-000000000000}'
|
||||
|
||||
var AXIS_SCALE = 1;
|
||||
var COLOR_MAX = 255;
|
||||
var INTENSITY_MAX = 0.05;
|
||||
var CUTOFF_MAX = 360;
|
||||
var EXPONENT_MAX = 1;
|
||||
|
||||
var SLIDER_SCRIPT_URL = Script.resolvePath('slider.js?' + Math.random(0, 100));
|
||||
var LIGHT_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/light_modifier/source4_very_good.fbx';
|
||||
var CLOSE_BUTTON_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/light_modifier/red_x.fbx';
|
||||
var CLOSE_BUTTON_SCRIPT_URL = Script.resolvePath('closeButton.js?' + Math.random(0, 100));
|
||||
var TRANSPARENT_PANEL_URL = 'http://hifi-content.s3.amazonaws.com/james/light_modifier/transparent_box_alpha_15.fbx';
|
||||
var VISIBLE_PANEL_SCRIPT_URL = Script.resolvePath('visiblePanel.js?' + Math.random(0, 100));
|
||||
|
||||
var RED = {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
};
|
||||
|
||||
var GREEN = {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 0
|
||||
};
|
||||
|
||||
var BLUE = {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 255
|
||||
};
|
||||
|
||||
var PURPLE = {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 255
|
||||
};
|
||||
|
||||
var WHITE = {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
};
|
||||
|
||||
var ORANGE = {
|
||||
red: 255,
|
||||
green: 165,
|
||||
blue: 0
|
||||
}
|
||||
|
||||
var SLIDER_DIMENSIONS = {
|
||||
x: 0.075,
|
||||
y: 0.075,
|
||||
z: 0.075
|
||||
};
|
||||
|
||||
var CLOSE_BUTTON_DIMENSIONS = {
|
||||
x: 0.1,
|
||||
y: 0.025,
|
||||
z: 0.1
|
||||
}
|
||||
|
||||
var LIGHT_MODEL_DIMENSIONS = {
|
||||
x: 0.58,
|
||||
y: 1.21,
|
||||
z: 0.57
|
||||
}
|
||||
|
||||
var PER_ROW_OFFSET = {
|
||||
x: 0,
|
||||
y: -0.2,
|
||||
z: 0
|
||||
};
|
||||
var sliders = [];
|
||||
var slidersRef = {
|
||||
'color_red': null,
|
||||
'color_green': null,
|
||||
'color_blue': null,
|
||||
intensity: null,
|
||||
cutoff: null,
|
||||
exponent: null
|
||||
};
|
||||
var light = null;
|
||||
|
||||
var basePosition;
|
||||
var avatarRotation;
|
||||
|
||||
function entitySlider(light, color, sliderType, displayText, row) {
|
||||
this.light = light;
|
||||
this.lightID = light.id.replace(/[{}]/g, "");
|
||||
this.initialProperties = light.initialProperties;
|
||||
this.color = color;
|
||||
this.sliderType = sliderType;
|
||||
this.displayText = displayText;
|
||||
this.verticalOffset = Vec3.multiply(row, PER_ROW_OFFSET);
|
||||
this.avatarRot = Quat.fromPitchYawRollDegrees(0, MyAvatar.bodyYaw, 0.0);
|
||||
this.basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(1.5, Quat.getFront(this.avatarRot)));
|
||||
this.basePosition.y += 1;
|
||||
basePosition = this.basePosition;
|
||||
avatarRot = this.avatarRot;
|
||||
|
||||
var message = {
|
||||
lightID: this.lightID,
|
||||
sliderType: this.sliderType,
|
||||
sliderValue: null
|
||||
};
|
||||
|
||||
if (this.sliderType === 'color_red') {
|
||||
message.sliderValue = this.initialProperties.color.red
|
||||
this.setValueFromMessage(message);
|
||||
}
|
||||
if (this.sliderType === 'color_green') {
|
||||
message.sliderValue = this.initialProperties.color.green
|
||||
this.setValueFromMessage(message);
|
||||
}
|
||||
if (this.sliderType === 'color_blue') {
|
||||
message.sliderValue = this.initialProperties.color.blue
|
||||
this.setValueFromMessage(message);
|
||||
}
|
||||
|
||||
if (this.sliderType === 'intensity') {
|
||||
message.sliderValue = this.initialProperties.intensity
|
||||
this.setValueFromMessage(message);
|
||||
}
|
||||
|
||||
if (this.sliderType === 'exponent') {
|
||||
message.sliderValue = this.initialProperties.exponent
|
||||
this.setValueFromMessage(message);
|
||||
}
|
||||
|
||||
if (this.sliderType === 'cutoff') {
|
||||
message.sliderValue = this.initialProperties.cutoff
|
||||
this.setValueFromMessage(message);
|
||||
}
|
||||
|
||||
this.setInitialSliderPositions();
|
||||
this.createAxis();
|
||||
this.createSliderIndicator();
|
||||
if (USE_LABELS === true) {
|
||||
this.createLabel()
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
//what's the ux for adjusting values? start with simple entities, try image overlays etc
|
||||
entitySlider.prototype = {
|
||||
createAxis: function() {
|
||||
//start of line
|
||||
var position;
|
||||
var extension;
|
||||
|
||||
if (VERTICAL_SLIDERS == true) {
|
||||
position = Vec3.sum(this.basePosition, Vec3.multiply(row, (Vec3.multiply(0.2, Quat.getRight(this.avatarRot)))));
|
||||
//line starts on bottom and goes up
|
||||
var upVector = Quat.getUp(this.avatarRot);
|
||||
extension = Vec3.multiply(AXIS_SCALE, upVector);
|
||||
} else {
|
||||
position = Vec3.sum(this.basePosition, this.verticalOffset);
|
||||
//line starts on left and goes to right
|
||||
//set the end of the line to the right
|
||||
var rightVector = Quat.getRight(this.avatarRot);
|
||||
extension = Vec3.multiply(AXIS_SCALE, rightVector);
|
||||
}
|
||||
|
||||
|
||||
this.axisStart = position;
|
||||
this.endOfAxis = Vec3.sum(position, extension);
|
||||
this.createEndOfAxisEntity();
|
||||
|
||||
var properties = {
|
||||
type: 'Line',
|
||||
name: 'Hifi-Slider-Axis::' + this.sliderType,
|
||||
color: this.color,
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true,
|
||||
dimensions: {
|
||||
x: 3,
|
||||
y: 3,
|
||||
z: 3
|
||||
},
|
||||
position: position,
|
||||
linePoints: [{
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}, extension],
|
||||
lineWidth: 5,
|
||||
};
|
||||
|
||||
this.axis = Entities.addEntity(properties);
|
||||
},
|
||||
createEndOfAxisEntity: function() {
|
||||
//we use this to track the end of the axis while parented to a panel
|
||||
var properties = {
|
||||
name: 'Hifi-End-Of-Axis',
|
||||
type: 'Box',
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true,
|
||||
dimensions: {
|
||||
x: 0.01,
|
||||
y: 0.01,
|
||||
z: 0.01
|
||||
},
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
position: this.endOfAxis,
|
||||
parentID: this.axis,
|
||||
visible: false
|
||||
}
|
||||
|
||||
this.endOfAxisEntity = Entities.addEntity(this.endOfAxis);
|
||||
},
|
||||
createLabel: function() {
|
||||
|
||||
var LABEL_WIDTH = 0.25
|
||||
var PER_LETTER_SPACING = 0.1;
|
||||
var textWidth = this.displayText.length * PER_LETTER_SPACING;
|
||||
|
||||
var position;
|
||||
if (LEFT_LABELS === true) {
|
||||
var leftVector = Vec3.multiply(-1, Quat.getRight(this.avatarRot));
|
||||
|
||||
var extension = Vec3.multiply(textWidth, leftVector);
|
||||
|
||||
position = Vec3.sum(this.axisStart, extension);
|
||||
}
|
||||
|
||||
if (RIGHT_LABELS === true) {
|
||||
var rightVector = Quat.getRight(this.avatarRot);
|
||||
|
||||
var extension = Vec3.multiply(textWidth / 1.75, rightVector);
|
||||
|
||||
position = Vec3.sum(this.endOfAxis, extension);
|
||||
}
|
||||
|
||||
|
||||
var labelProperties = {
|
||||
name: 'Hifi-Slider-Label-' + this.sliderType,
|
||||
type: 'Text',
|
||||
dimensions: {
|
||||
x: textWidth,
|
||||
y: 0.2,
|
||||
z: 0.1
|
||||
},
|
||||
textColor: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
text: this.displayText,
|
||||
lineHeight: 0.14,
|
||||
backgroundColor: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
position: position,
|
||||
rotation: this.avatarRot,
|
||||
}
|
||||
print('BEFORE CREATE LABEL' + JSON.stringify(labelProperties))
|
||||
this.label = Entities.addEntity(labelProperties);
|
||||
print('AFTER CREATE LABEL')
|
||||
},
|
||||
createSliderIndicator: function() {
|
||||
var extensionVector;
|
||||
var position;
|
||||
if (VERTICAL_SLIDERS == true) {
|
||||
position = Vec3.sum(this.basePosition, Vec3.multiply(row, (Vec3.multiply(0.2, Quat.getRight(this.avatarRot)))));
|
||||
extensionVector = Quat.getUp(this.avatarRot);
|
||||
|
||||
} else {
|
||||
position = Vec3.sum(this.basePosition, this.verticalOffset);
|
||||
extensionVector = Quat.getRight(this.avatarRot);
|
||||
|
||||
}
|
||||
|
||||
var initialDistance;
|
||||
if (this.sliderType === 'color_red') {
|
||||
initialDistance = this.distanceRed;
|
||||
}
|
||||
if (this.sliderType === 'color_green') {
|
||||
initialDistance = this.distanceGreen;
|
||||
}
|
||||
if (this.sliderType === 'color_blue') {
|
||||
initialDistance = this.distanceBlue;
|
||||
}
|
||||
if (this.sliderType === 'intensity') {
|
||||
initialDistance = this.distanceIntensity;
|
||||
}
|
||||
if (this.sliderType === 'cutoff') {
|
||||
initialDistance = this.distanceCutoff;
|
||||
}
|
||||
if (this.sliderType === 'exponent') {
|
||||
initialDistance = this.distanceExponent;
|
||||
}
|
||||
|
||||
var extension = Vec3.multiply(initialDistance, extensionVector);
|
||||
var sliderPosition = Vec3.sum(position, extension);
|
||||
|
||||
var properties = {
|
||||
type: 'Sphere',
|
||||
name: 'Hifi-Slider-' + this.sliderType,
|
||||
dimensions: SLIDER_DIMENSIONS,
|
||||
collisionsWillMove: true,
|
||||
color: this.color,
|
||||
position: sliderPosition,
|
||||
script: SLIDER_SCRIPT_URL,
|
||||
ignoreForCollisions: true,
|
||||
userData: JSON.stringify({
|
||||
lightModifierKey: {
|
||||
lightID: this.lightID,
|
||||
sliderType: this.sliderType,
|
||||
axisStart: position,
|
||||
axisEnd: this.endOfAxis,
|
||||
},
|
||||
handControllerKey: {
|
||||
disableReleaseVelocity: true,
|
||||
disableMoveWithHead: true,
|
||||
disableNearGrab:true
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
this.sliderIndicator = Entities.addEntity(properties);
|
||||
},
|
||||
setValueFromMessage: function(message) {
|
||||
|
||||
//message is not for our light
|
||||
if (message.lightID !== this.lightID) {
|
||||
// print('not our light')
|
||||
return;
|
||||
}
|
||||
|
||||
//message is not our type
|
||||
if (message.sliderType !== this.sliderType) {
|
||||
// print('not our slider type')
|
||||
return
|
||||
}
|
||||
|
||||
var lightProperties = Entities.getEntityProperties(this.lightID);
|
||||
|
||||
if (this.sliderType === 'color_red') {
|
||||
Entities.editEntity(this.lightID, {
|
||||
color: {
|
||||
red: message.sliderValue,
|
||||
green: lightProperties.color.green,
|
||||
blue: lightProperties.color.blue
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.sliderType === 'color_green') {
|
||||
Entities.editEntity(this.lightID, {
|
||||
color: {
|
||||
red: lightProperties.color.red,
|
||||
green: message.sliderValue,
|
||||
blue: lightProperties.color.blue
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.sliderType === 'color_blue') {
|
||||
Entities.editEntity(this.lightID, {
|
||||
color: {
|
||||
red: lightProperties.color.red,
|
||||
green: lightProperties.color.green,
|
||||
blue: message.sliderValue,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.sliderType === 'intensity') {
|
||||
Entities.editEntity(this.lightID, {
|
||||
intensity: message.sliderValue
|
||||
});
|
||||
}
|
||||
|
||||
if (this.sliderType === 'cutoff') {
|
||||
Entities.editEntity(this.lightID, {
|
||||
cutoff: message.sliderValue
|
||||
});
|
||||
}
|
||||
|
||||
if (this.sliderType === 'exponent') {
|
||||
Entities.editEntity(this.lightID, {
|
||||
exponent: message.sliderValue
|
||||
});
|
||||
}
|
||||
},
|
||||
setInitialSliderPositions: function() {
|
||||
this.distanceRed = (this.initialProperties.color.red / COLOR_MAX) * AXIS_SCALE;
|
||||
this.distanceGreen = (this.initialProperties.color.green / COLOR_MAX) * AXIS_SCALE;
|
||||
this.distanceBlue = (this.initialProperties.color.blue / COLOR_MAX) * AXIS_SCALE;
|
||||
this.distanceIntensity = (this.initialProperties.intensity / INTENSITY_MAX) * AXIS_SCALE;
|
||||
this.distanceCutoff = (this.initialProperties.cutoff / CUTOFF_MAX) * AXIS_SCALE;
|
||||
this.distanceExponent = (this.initialProperties.exponent / EXPONENT_MAX) * AXIS_SCALE;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
var panel;
|
||||
var visiblePanel;
|
||||
|
||||
function makeSliders(light) {
|
||||
|
||||
if (USE_PARENTED_PANEL === true) {
|
||||
panel = createPanelEntity(MyAvatar.position);
|
||||
}
|
||||
|
||||
if (light.type === 'spotlight') {
|
||||
var USE_COLOR_SLIDER = true;
|
||||
var USE_INTENSITY_SLIDER = true;
|
||||
var USE_CUTOFF_SLIDER = true;
|
||||
var USE_EXPONENT_SLIDER = true;
|
||||
}
|
||||
if (light.type === 'pointlight') {
|
||||
var USE_COLOR_SLIDER = true;
|
||||
var USE_INTENSITY_SLIDER = true;
|
||||
var USE_CUTOFF_SLIDER = false;
|
||||
var USE_EXPONENT_SLIDER = false;
|
||||
}
|
||||
if (USE_COLOR_SLIDER === true) {
|
||||
slidersRef.color_red = new entitySlider(light, RED, 'color_red', 'Red', 1);
|
||||
slidersRef.color_green = new entitySlider(light, GREEN, 'color_green', 'Green', 2);
|
||||
slidersRef.color_blue = new entitySlider(light, BLUE, 'color_blue', 'Blue', 3);
|
||||
|
||||
sliders.push(slidersRef.color_red);
|
||||
sliders.push(slidersRef.color_green);
|
||||
sliders.push(slidersRef.color_blue);
|
||||
|
||||
}
|
||||
if (USE_INTENSITY_SLIDER === true) {
|
||||
slidersRef.intensity = new entitySlider(light, WHITE, 'intensity', 'Intensity', 4);
|
||||
sliders.push(slidersRef.intensity);
|
||||
}
|
||||
if (USE_CUTOFF_SLIDER === true) {
|
||||
slidersRef.cutoff = new entitySlider(light, PURPLE, 'cutoff', 'Cutoff', 5);
|
||||
sliders.push(slidersRef.cutoff);
|
||||
}
|
||||
if (USE_EXPONENT_SLIDER === true) {
|
||||
slidersRef.exponent = new entitySlider(light, ORANGE, 'exponent', 'Exponent', 6);
|
||||
sliders.push(slidersRef.exponent);
|
||||
}
|
||||
|
||||
createCloseButton(slidersRef.color_red.axisStart);
|
||||
|
||||
subscribeToSliderMessages();
|
||||
|
||||
if (USE_PARENTED_PANEL === true) {
|
||||
parentEntitiesToPanel(panel);
|
||||
}
|
||||
|
||||
if (SLIDERS_SHOULD_STAY_WITH_AVATAR === true) {
|
||||
parentPanelToAvatar(panel);
|
||||
}
|
||||
|
||||
if (VISIBLE_PANEL === true) {
|
||||
visiblePanel = createVisiblePanel();
|
||||
}
|
||||
};
|
||||
|
||||
function parentPanelToAvatar(panel) {
|
||||
//this is going to need some more work re: the sliders actually being grabbable. probably something to do with updating axis movement
|
||||
Entities.editEntity(panel, {
|
||||
parentID: MyAvatar.sessionUUID,
|
||||
//actually figure out which one to parent it to -- probably a spine or something.
|
||||
parentJointIndex: 1,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function parentEntitiesToPanel(panel) {
|
||||
|
||||
sliders.forEach(function(slider) {
|
||||
Entities.editEntity(slider.axis, {
|
||||
parentID: panel
|
||||
})
|
||||
Entities.editEntity(slider.sliderIndicator, {
|
||||
parentID: panel
|
||||
})
|
||||
})
|
||||
|
||||
closeButtons.forEach(function(button) {
|
||||
Entities.editEntity(button, {
|
||||
parentID: panel
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function createPanelEntity(position) {
|
||||
print('CREATING PANEL at ' + JSON.stringify(position));
|
||||
var panelProperties = {
|
||||
name: 'Hifi-Slider-Panel',
|
||||
type: 'Box',
|
||||
dimensions: {
|
||||
x: 0.1,
|
||||
y: 0.1,
|
||||
z: 0.1
|
||||
},
|
||||
visible: false,
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true
|
||||
}
|
||||
|
||||
var panel = Entities.addEntity(panelProperties);
|
||||
return panel
|
||||
}
|
||||
|
||||
function createVisiblePanel() {
|
||||
var totalOffset = -PER_ROW_OFFSET.y * sliders.length;
|
||||
|
||||
var moveRight = Vec3.sum(basePosition, Vec3.multiply(AXIS_SCALE / 2, Quat.getRight(avatarRot)));
|
||||
|
||||
var moveDown = Vec3.sum(moveRight, Vec3.multiply((sliders.length + 1) / 2, PER_ROW_OFFSET))
|
||||
var panelProperties = {
|
||||
name: 'Hifi-Visible-Transparent-Panel',
|
||||
type: 'Model',
|
||||
modelURL: TRANSPARENT_PANEL_URL,
|
||||
dimensions: {
|
||||
x: AXIS_SCALE + 0.1,
|
||||
y: totalOffset,
|
||||
z: SLIDER_DIMENSIONS.z / 4
|
||||
},
|
||||
visible: true,
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true,
|
||||
position: moveDown,
|
||||
rotation: avatarRot,
|
||||
script: VISIBLE_PANEL_SCRIPT_URL
|
||||
}
|
||||
|
||||
var panel = Entities.addEntity(panelProperties);
|
||||
|
||||
return panel
|
||||
}
|
||||
|
||||
|
||||
function createLightModel(position, rotation) {
|
||||
var blockProperties = {
|
||||
name: 'Hifi-Spotlight-Model',
|
||||
type: 'Model',
|
||||
shapeType: 'box',
|
||||
modelURL: LIGHT_MODEL_URL,
|
||||
dimensions: LIGHT_MODEL_DIMENSIONS,
|
||||
collisionsWillMove: true,
|
||||
position: position,
|
||||
rotation: rotation,
|
||||
script: PARENT_SCRIPT_URL,
|
||||
userData: JSON.stringify({
|
||||
handControllerKey: {
|
||||
disableReleaseVelocity: true
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
var block = Entities.addEntity(blockProperties);
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
var closeButtons = [];
|
||||
|
||||
function createCloseButton(axisStart) {
|
||||
var MARGIN = 0.10;
|
||||
var VERTICAL_OFFFSET = {
|
||||
x: 0,
|
||||
y: 0.15,
|
||||
z: 0
|
||||
};
|
||||
var leftVector = Vec3.multiply(-1, Quat.getRight(avatarRot));
|
||||
var extension = Vec3.multiply(MARGIN, leftVector);
|
||||
var position = Vec3.sum(axisStart, extension);
|
||||
|
||||
var buttonProperties = {
|
||||
name: 'Hifi-Close-Button',
|
||||
type: 'Model',
|
||||
modelURL: CLOSE_BUTTON_MODEL_URL,
|
||||
dimensions: CLOSE_BUTTON_DIMENSIONS,
|
||||
position: Vec3.sum(position, VERTICAL_OFFFSET),
|
||||
rotation: Quat.multiply(avatarRot, Quat.fromPitchYawRollDegrees(90, 0, 45)),
|
||||
//rotation: Quat.fromPitchYawRollDegrees(0, 0, 90),
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true,
|
||||
script: CLOSE_BUTTON_SCRIPT_URL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
wantsTrigger: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var button = Entities.addEntity(buttonProperties);
|
||||
|
||||
closeButtons.push(button);
|
||||
|
||||
if (ROTATE_CLOSE_BUTTON === true) {
|
||||
Script.update.connect(rotateCloseButtons);
|
||||
}
|
||||
}
|
||||
|
||||
function rotateCloseButtons() {
|
||||
closeButtons.forEach(function(button) {
|
||||
Entities.editEntity(button, {
|
||||
angularVelocity: {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function subScribeToNewLights() {
|
||||
Messages.subscribe('Hifi-Light-Mod-Receiver');
|
||||
Messages.messageReceived.connect(handleLightModMessages);
|
||||
}
|
||||
|
||||
function subscribeToSliderMessages() {
|
||||
Messages.subscribe('Hifi-Slider-Value-Reciever');
|
||||
Messages.messageReceived.connect(handleValueMessages);
|
||||
}
|
||||
|
||||
function subscribeToLightOverlayRayCheckMessages() {
|
||||
Messages.subscribe('Hifi-Light-Overlay-Ray-Check');
|
||||
Messages.messageReceived.connect(handleLightOverlayRayCheckMessages);
|
||||
}
|
||||
|
||||
function subscribeToCleanupMessages() {
|
||||
Messages.subscribe('Hifi-Light-Modifier-Cleanup');
|
||||
Messages.messageReceived.connect(handleCleanupMessages);
|
||||
}
|
||||
|
||||
|
||||
function handleLightModMessages(channel, message, sender) {
|
||||
if (channel !== 'Hifi-Light-Mod-Receiver') {
|
||||
return;
|
||||
}
|
||||
if (sender !== MyAvatar.sessionUUID) {
|
||||
return;
|
||||
}
|
||||
var parsedMessage = JSON.parse(message);
|
||||
|
||||
makeSliders(parsedMessage.light);
|
||||
light = parsedMessage.light.id
|
||||
if (SHOW_LIGHT_VOLUME === true) {
|
||||
selectionManager.setSelections([parsedMessage.light.id]);
|
||||
}
|
||||
}
|
||||
|
||||
function handleValueMessages(channel, message, sender) {
|
||||
|
||||
if (channel !== 'Hifi-Slider-Value-Reciever') {
|
||||
return;
|
||||
}
|
||||
if (ONLY_I_CAN_EDIT === true && sender !== MyAvatar.sessionUUID) {
|
||||
return;
|
||||
}
|
||||
var parsedMessage = JSON.parse(message);
|
||||
|
||||
slidersRef[parsedMessage.sliderType].setValueFromMessage(parsedMessage);
|
||||
}
|
||||
|
||||
var currentLight;
|
||||
var block;
|
||||
var oldParent = null;
|
||||
var hasParent = false;
|
||||
|
||||
function handleLightOverlayRayCheckMessages(channel, message, sender) {
|
||||
if (channel !== 'Hifi-Light-Overlay-Ray-Check') {
|
||||
return;
|
||||
}
|
||||
if (ONLY_I_CAN_EDIT === true && sender !== MyAvatar.sessionUUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pickRay = JSON.parse(message);
|
||||
|
||||
var doesIntersect = lightOverlayManager.findRayIntersection(pickRay);
|
||||
// print('DOES INTERSECT A LIGHT WE HAVE???' + doesIntersect.intersects);
|
||||
if (doesIntersect.intersects === true) {
|
||||
// print('FULL MESSAGE:::' + JSON.stringify(doesIntersect))
|
||||
|
||||
var lightID = doesIntersect.entityID;
|
||||
if (currentLight === lightID) {
|
||||
// print('ALREADY HAVE A BLOCK, EXIT')
|
||||
return;
|
||||
}
|
||||
|
||||
currentLight = lightID;
|
||||
var lightProperties = Entities.getEntityProperties(lightID);
|
||||
if (lightProperties.parentID !== DEFAULT_PARENT_ID) {
|
||||
//this light has a parent already. so lets call our block the parent and then make sure not to delete it at the end;
|
||||
oldParent = lightProperties.parentID;
|
||||
hasParent = true;
|
||||
block = lightProperties.parentID;
|
||||
if (lightProperties.parentJointIndex !== -1) {
|
||||
//should make sure to retain the parent too. but i don't actually know what the
|
||||
}
|
||||
} else {
|
||||
block = createLightModel(lightProperties.position, lightProperties.rotation);
|
||||
}
|
||||
|
||||
var light = {
|
||||
id: lightID,
|
||||
type: 'spotlight',
|
||||
initialProperties: lightProperties
|
||||
}
|
||||
|
||||
makeSliders(light);
|
||||
|
||||
if (SHOW_LIGHT_VOLUME === true) {
|
||||
selectionManager.setSelections([lightID]);
|
||||
}
|
||||
|
||||
Entities.editEntity(lightID, {
|
||||
parentID: block,
|
||||
parentJointIndex: -1
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function handleCleanupMessages(channel, message, sender) {
|
||||
|
||||
if (channel !== 'Hifi-Light-Modifier-Cleanup') {
|
||||
return;
|
||||
}
|
||||
if (ONLY_I_CAN_EDIT === true && sender !== MyAvatar.sessionUUID) {
|
||||
return;
|
||||
}
|
||||
if (message === 'callCleanup') {
|
||||
cleanup(true);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSliderAxis() {
|
||||
sliders.forEach(function(slider) {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function cleanup(fromMessage) {
|
||||
var i;
|
||||
for (i = 0; i < sliders.length; i++) {
|
||||
Entities.deleteEntity(sliders[i].axis);
|
||||
Entities.deleteEntity(sliders[i].sliderIndicator);
|
||||
Entities.deleteEntity(sliders[i].label);
|
||||
}
|
||||
|
||||
while (closeButtons.length > 0) {
|
||||
Entities.deleteEntity(closeButtons.pop());
|
||||
}
|
||||
|
||||
//if the light was already parented to something we will want to restore that. or come up with groups or something clever.
|
||||
if (oldParent !== null) {
|
||||
Entities.editEntity(currentLight, {
|
||||
parentID: oldParent,
|
||||
});
|
||||
} else {
|
||||
Entities.editEntity(currentLight, {
|
||||
parentID: null,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (fromMessage !== true) {
|
||||
Messages.messageReceived.disconnect(handleLightModMessages);
|
||||
Messages.messageReceived.disconnect(handleValueMessages);
|
||||
Messages.messageReceived.disconnect(handleLightOverlayRayCheckMessages);
|
||||
lightOverlayManager.setVisible(false);
|
||||
}
|
||||
|
||||
|
||||
Entities.deleteEntity(panel);
|
||||
Entities.deleteEntity(visiblePanel);
|
||||
|
||||
selectionManager.clearSelections();
|
||||
|
||||
if (ROTATE_CLOSE_BUTTON === true) {
|
||||
Script.update.disconnect(rotateCloseButtons);
|
||||
}
|
||||
|
||||
if (hasParent === false) {
|
||||
Entities.deleteEntity(block);
|
||||
}
|
||||
|
||||
oldParent = null;
|
||||
hasParent = false;
|
||||
currentLight = null;
|
||||
sliders = [];
|
||||
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
lightOverlayManager.setVisible(false);
|
||||
})
|
||||
|
||||
|
||||
subscribeToLightOverlayRayCheckMessages();
|
||||
subScribeToNewLights();
|
||||
subscribeToCleanupMessages();
|
||||
|
||||
|
||||
|
||||
//other light properties
|
||||
// diffuseColor: { red: 255, green: 255, blue: 255 },
|
||||
// ambientColor: { red: 255, green: 255, blue: 255 },
|
||||
// specularColor: { red: 255, green: 255, blue: 255 },
|
||||
// constantAttenuation: 1,
|
||||
// linearAttenuation: 0,
|
||||
// quadraticAttenuation: 0,
|
||||
// exponent: 0,
|
||||
// cutoff: 180, // in degrees
|
73
examples/light_modifier/lightModifierTestScene.js
Normal file
73
examples/light_modifier/lightModifierTestScene.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// lightModifierTestScene.js
|
||||
//
|
||||
// Created by James Pollack @imgntn on 12/15/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Given a selected light, instantiate some entities that represent various values you can dynamically adjust.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var PARENT_SCRIPT_URL = Script.resolvePath('lightParent.js?' + Math.random(0 - 100));
|
||||
var basePosition, avatarRot;
|
||||
avatarRot = Quat.fromPitchYawRollDegrees(0, MyAvatar.bodyYaw, 0.0);
|
||||
basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(0, Quat.getUp(avatarRot)));
|
||||
|
||||
var light;
|
||||
|
||||
function createLight() {
|
||||
var position = basePosition;
|
||||
position.y += 2;
|
||||
var lightTransform = evalLightWorldTransform(position, avatarRot);
|
||||
var lightProperties = {
|
||||
name: 'Hifi-Spotlight',
|
||||
type: "Light",
|
||||
isSpotlight: true,
|
||||
dimensions: {
|
||||
x: 2,
|
||||
y: 2,
|
||||
z: 8
|
||||
},
|
||||
color: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 255
|
||||
},
|
||||
intensity: 0.035,
|
||||
exponent: 1,
|
||||
cutoff: 30,
|
||||
lifetime: -1,
|
||||
position: lightTransform.p,
|
||||
rotation: lightTransform.q
|
||||
};
|
||||
|
||||
light = Entities.addEntity(lightProperties);
|
||||
|
||||
}
|
||||
|
||||
function evalLightWorldTransform(modelPos, modelRot) {
|
||||
var MODEL_LIGHT_POSITION = {
|
||||
x: 0,
|
||||
y: -0.3,
|
||||
z: 0
|
||||
};
|
||||
var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, {
|
||||
x: 1,
|
||||
y: 0,
|
||||
z: 0
|
||||
});
|
||||
return {
|
||||
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
|
||||
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
|
||||
};
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(light);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
createLight();
|
40
examples/light_modifier/lightParent.js
Normal file
40
examples/light_modifier/lightParent.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// lightParent.js
|
||||
//
|
||||
// Created by James Pollack @imgntn on 12/15/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Entity script that tells the light parent to update the selection tool when we move it.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
|
||||
function LightParent() {
|
||||
return this;
|
||||
}
|
||||
|
||||
LightParent.prototype = {
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
var entityProperties = Entities.getEntityProperties(this.entityID, "userData");
|
||||
this.initialProperties = entityProperties
|
||||
this.userData = JSON.parse(entityProperties.userData);
|
||||
},
|
||||
startNearGrab: function() {},
|
||||
startDistantGrab: function() {
|
||||
|
||||
},
|
||||
continueNearGrab: function() {
|
||||
this.continueDistantGrab();
|
||||
},
|
||||
continueDistantGrab: function() {
|
||||
Messages.sendMessage('entityToolUpdates', 'callUpdate');
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
return new LightParent();
|
||||
});
|
105
examples/light_modifier/slider.js
Normal file
105
examples/light_modifier/slider.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// slider.js
|
||||
//
|
||||
// Created by James Pollack @imgntn on 12/15/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Entity script that sends a scaled value to a light based on its distance from the start of its constraint axis.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
|
||||
var AXIS_SCALE = 1;
|
||||
var COLOR_MAX = 255;
|
||||
var INTENSITY_MAX = 0.05;
|
||||
var CUTOFF_MAX = 360;
|
||||
var EXPONENT_MAX = 1;
|
||||
|
||||
function Slider() {
|
||||
return this;
|
||||
}
|
||||
|
||||
Slider.prototype = {
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
var entityProperties = Entities.getEntityProperties(this.entityID, "userData");
|
||||
var parsedUserData = JSON.parse(entityProperties.userData);
|
||||
this.userData = parsedUserData.lightModifierKey;
|
||||
},
|
||||
startNearGrab: function() {
|
||||
this.setInitialProperties();
|
||||
},
|
||||
startDistantGrab: function() {
|
||||
this.setInitialProperties();
|
||||
},
|
||||
setInitialProperties: function() {
|
||||
this.initialProperties = Entities.getEntityProperties(this.entityID);
|
||||
},
|
||||
continueNearGrab: function() {
|
||||
// this.continueDistantGrab();
|
||||
},
|
||||
continueDistantGrab: function() {
|
||||
this.setSliderValueBasedOnDistance();
|
||||
},
|
||||
setSliderValueBasedOnDistance: function() {
|
||||
var currentPosition = Entities.getEntityProperties(this.entityID, "position").position;
|
||||
|
||||
var distance = Vec3.distance(this.userData.axisStart, currentPosition);
|
||||
|
||||
if (this.userData.sliderType === 'color_red' || this.userData.sliderType === 'color_green' || this.userData.sliderType === 'color_blue') {
|
||||
this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, COLOR_MAX);
|
||||
}
|
||||
if (this.userData.sliderType === 'intensity') {
|
||||
this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, INTENSITY_MAX);
|
||||
}
|
||||
if (this.userData.sliderType === 'cutoff') {
|
||||
this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, CUTOFF_MAX);
|
||||
}
|
||||
if (this.userData.sliderType === 'exponent') {
|
||||
this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, EXPONENT_MAX);
|
||||
};
|
||||
|
||||
this.sendValueToSlider();
|
||||
},
|
||||
releaseGrab: function() {
|
||||
Entities.editEntity(this.entityID, {
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
angularVelocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}
|
||||
})
|
||||
|
||||
this.sendValueToSlider();
|
||||
},
|
||||
scaleValueBasedOnDistanceFromStart: function(value, min2, max2) {
|
||||
var min1 = 0;
|
||||
var max1 = AXIS_SCALE;
|
||||
var min2 = min2;
|
||||
var max2 = max2;
|
||||
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
|
||||
},
|
||||
sendValueToSlider: function() {
|
||||
var _t = this;
|
||||
var message = {
|
||||
lightID: _t.userData.lightID,
|
||||
sliderType: _t.userData.sliderType,
|
||||
sliderValue: _t.sliderValue
|
||||
}
|
||||
Messages.sendMessage('Hifi-Slider-Value-Reciever', JSON.stringify(message));
|
||||
if (_t.userData.sliderType === 'cutoff') {
|
||||
Messages.sendMessage('entityToolUpdates', 'callUpdate');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return new Slider();
|
||||
});
|
40
examples/light_modifier/visiblePanel.js
Normal file
40
examples/light_modifier/visiblePanel.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// visiblePanel.js
|
||||
//
|
||||
// Created by James Pollack @imgntn on 12/15/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Entity script that disables picking on this panel.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
|
||||
function VisiblePanel() {
|
||||
return this;
|
||||
}
|
||||
|
||||
VisiblePanel.prototype = {
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
|
||||
var data = {
|
||||
action: 'add',
|
||||
id: this.entityID
|
||||
};
|
||||
Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data))
|
||||
},
|
||||
unload: function() {
|
||||
var data = {
|
||||
action: 'remove',
|
||||
id: this.entityID
|
||||
};
|
||||
Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data))
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return new VisiblePanel();
|
||||
});
|
|
@ -43,6 +43,8 @@ var MAX_STROKE_WIDTH = 0.04;
|
|||
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var textureURL = "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/paintStroke.png";
|
||||
|
||||
|
||||
|
||||
function MyController(hand, triggerAction) {
|
||||
|
@ -148,7 +150,8 @@ function MyController(hand, triggerAction) {
|
|||
y: 50,
|
||||
z: 50
|
||||
},
|
||||
lifetime: 200
|
||||
lifetime: 200,
|
||||
textures: textureURL
|
||||
});
|
||||
this.strokePoints = [];
|
||||
this.strokeNormals = [];
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
var MIN_STROKE_WIDTH = 0.0005;
|
||||
var MAX_STROKE_WIDTH = 0.03;
|
||||
|
||||
var textureURL = "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/paintStroke.png";
|
||||
|
||||
var TRIGGER_CONTROLS = [
|
||||
Controller.Standard.LT,
|
||||
Controller.Standard.RT,
|
||||
|
@ -168,6 +170,7 @@
|
|||
type: "PolyLine",
|
||||
name: "paintStroke",
|
||||
color: this.strokeColor,
|
||||
textures: "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/paintStroke.png",
|
||||
dimensions: {
|
||||
x: 50,
|
||||
y: 50,
|
||||
|
|
|
@ -247,4 +247,4 @@ function cleanup() {
|
|||
|
||||
|
||||
// Uncomment this line to delete whiteboard and all associated entity on script close
|
||||
//Script.scriptEnding.connect(cleanup);
|
||||
// Script.scriptEnding.connect(cleanup);
|
||||
|
|
73
examples/rayPickingFilterExample.js
Normal file
73
examples/rayPickingFilterExample.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// rayPickingFilterExample.js
|
||||
// examples
|
||||
//
|
||||
// Created by Eric Levin on 12/24/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that demonstrates the use of filtering entities for ray picking
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var whiteListBox = Entities.addEntity({
|
||||
type: "Box",
|
||||
color: {
|
||||
red: 10,
|
||||
green: 200,
|
||||
blue: 10
|
||||
},
|
||||
dimensions: {
|
||||
x: 0.2,
|
||||
y: 0.2,
|
||||
z: 0.2
|
||||
},
|
||||
position: center
|
||||
});
|
||||
|
||||
var blackListBox = Entities.addEntity({
|
||||
type: "Box",
|
||||
color: {
|
||||
red: 100,
|
||||
green: 10,
|
||||
blue: 10
|
||||
},
|
||||
dimensions: {
|
||||
x: 0.2,
|
||||
y: 0.2,
|
||||
z: 0.2
|
||||
},
|
||||
position: Vec3.sum(center, {
|
||||
x: 0,
|
||||
y: 0.3,
|
||||
z: 0
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
function castRay(event) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
// In this example every entity will be pickable except the entities in the blacklist array
|
||||
// the third argument is the whitelist array,and the fourth and final is the blacklist array
|
||||
var pickResults = Entities.findRayIntersection(pickRay, true, [], [blackListBox]);
|
||||
|
||||
// With below example, only entities added to whitelist will be pickable
|
||||
// var pickResults = Entities.findRayIntersection(pickRay, true, [whiteListBox], []);
|
||||
|
||||
if (pickResults.intersects) {
|
||||
print("INTERSECTION!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(whiteListBox);
|
||||
Entities.deleteEntity(blackListBox);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Controller.mousePressEvent.connect(castRay);
|
40
examples/tests/qmlTest.js
Normal file
40
examples/tests/qmlTest.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
print("Launching web window");
|
||||
qmlWindow = new OverlayWindow({
|
||||
title: 'Test Qml',
|
||||
source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml",
|
||||
height: 240,
|
||||
width: 320,
|
||||
toolWindow: false,
|
||||
visible: true
|
||||
});
|
||||
|
||||
//qmlWindow.eventBridge.webEventReceived.connect(function(data) {
|
||||
// print("JS Side event received: " + data);
|
||||
//});
|
||||
//
|
||||
//var titles = ["A", "B", "C"];
|
||||
//var titleIndex = 0;
|
||||
//
|
||||
//Script.setInterval(function() {
|
||||
// qmlWindow.eventBridge.emitScriptEvent("JS Event sent");
|
||||
// var size = qmlWindow.size;
|
||||
// var position = qmlWindow.position;
|
||||
// print("Window visible: " + qmlWindow.visible)
|
||||
// if (qmlWindow.visible) {
|
||||
// print("Window size: " + size.x + "x" + size.y)
|
||||
// print("Window pos: " + position.x + "x" + position.y)
|
||||
// qmlWindow.setVisible(false);
|
||||
// } else {
|
||||
// qmlWindow.setVisible(true);
|
||||
// qmlWindow.setTitle(titles[titleIndex]);
|
||||
// qmlWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
|
||||
// titleIndex += 1;
|
||||
// titleIndex %= titles.length;
|
||||
// }
|
||||
//}, 2 * 1000);
|
||||
//
|
||||
//Script.setTimeout(function() {
|
||||
// print("Closing script");
|
||||
// qmlWindow.close();
|
||||
// Script.stop();
|
||||
//}, 15 * 1000)
|
33
examples/tests/qmlWebTest.js
Normal file
33
examples/tests/qmlWebTest.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
print("Launching web window");
|
||||
|
||||
var htmlUrl = Script.resolvePath("..//html/qmlWebTest.html")
|
||||
webWindow = new OverlayWebWindow('Test Event Bridge', htmlUrl, 320, 240, false);
|
||||
print("JS Side window: " + webWindow);
|
||||
print("JS Side bridge: " + webWindow.eventBridge);
|
||||
webWindow.eventBridge.webEventReceived.connect(function(data) {
|
||||
print("JS Side event received: " + data);
|
||||
});
|
||||
|
||||
var titles = ["A", "B", "C"];
|
||||
var titleIndex = 0;
|
||||
|
||||
Script.setInterval(function() {
|
||||
webWindow.eventBridge.emitScriptEvent("JS Event sent");
|
||||
var size = webWindow.size;
|
||||
var position = webWindow.position;
|
||||
print("Window url: " + webWindow.url)
|
||||
print("Window visible: " + webWindow.visible)
|
||||
print("Window size: " + size.x + "x" + size.y)
|
||||
print("Window pos: " + position.x + "x" + position.y)
|
||||
webWindow.setVisible(!webWindow.visible);
|
||||
webWindow.setTitle(titles[titleIndex]);
|
||||
webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
|
||||
titleIndex += 1;
|
||||
titleIndex %= titles.length;
|
||||
}, 2 * 1000);
|
||||
|
||||
Script.setTimeout(function() {
|
||||
print("Closing script");
|
||||
webWindow.close();
|
||||
Script.stop();
|
||||
}, 15 * 1000)
|
|
@ -241,9 +241,9 @@
|
|||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
grabbable: false
|
||||
}
|
||||
},
|
||||
creatorSessionUUID: MyAvatar.sessionUUID
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
var makeArrowStick = function(entityA, entityB, collision) {
|
||||
|
|
|
@ -38,6 +38,14 @@ var pingPongGun = Entities.addEntity({
|
|||
collisionSoundURL: COLLISION_SOUND_URL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
spatialKey: {
|
||||
relativePosition: {
|
||||
x: -0.05,
|
||||
y: 0,
|
||||
z: 0.0
|
||||
},
|
||||
relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, -90)
|
||||
},
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
|
|
|
@ -29,21 +29,14 @@
|
|||
this.equipped = false;
|
||||
this.forceMultiplier = 1;
|
||||
this.laserLength = 100;
|
||||
this.laserOffsets = {
|
||||
y: .095
|
||||
};
|
||||
this.firingOffsets = {
|
||||
z: 0.16
|
||||
}
|
||||
|
||||
this.fireSound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Guns/GUN-SHOT2.raw");
|
||||
this.ricochetSound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Guns/Ricochet.L.wav");
|
||||
this.playRichochetSoundChance = 0.1;
|
||||
this.fireVolume = 0.2;
|
||||
this.bulletForce = 10;
|
||||
|
||||
|
||||
|
||||
this.showLaser = false;
|
||||
|
||||
};
|
||||
|
||||
Pistol.prototype = {
|
||||
|
@ -58,20 +51,36 @@
|
|||
if (!this.equipped) {
|
||||
return;
|
||||
}
|
||||
this.toggleWithTriggerPressure();
|
||||
this.updateProps();
|
||||
if (this.showLaser) {
|
||||
this.updateLaser();
|
||||
}
|
||||
this.toggleWithTriggerPressure();
|
||||
|
||||
|
||||
},
|
||||
|
||||
updateProps: function() {
|
||||
var gunProps = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
|
||||
this.position = gunProps.position;
|
||||
this.rotation = gunProps.rotation;
|
||||
this.firingDirection = Quat.getFront(this.rotation);
|
||||
var upVec = Quat.getUp(this.rotation);
|
||||
this.barrelPoint = Vec3.sum(this.position, Vec3.multiply(upVec, this.laserOffsets.y));
|
||||
this.laserTip = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.laserLength));
|
||||
this.barrelPoint = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.firingOffsets.z))
|
||||
var pickRay = {
|
||||
origin: this.barrelPoint,
|
||||
direction: this.firingDirection
|
||||
};
|
||||
},
|
||||
toggleWithTriggerPressure: function() {
|
||||
this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[this.hand]);
|
||||
|
||||
if (this.triggerValue < RELOAD_THRESHOLD) {
|
||||
// print('RELOAD');
|
||||
this.canShoot = true;
|
||||
}
|
||||
if (this.canShoot === true && this.triggerValue === 1) {
|
||||
// print('SHOOT');
|
||||
this.fire();
|
||||
this.canShoot = false;
|
||||
}
|
||||
|
@ -91,17 +100,10 @@
|
|||
|
||||
},
|
||||
updateLaser: function() {
|
||||
var gunProps = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
|
||||
var position = gunProps.position;
|
||||
var rotation = gunProps.rotation;
|
||||
this.firingDirection = Quat.getFront(rotation);
|
||||
var upVec = Quat.getUp(rotation);
|
||||
this.barrelPoint = Vec3.sum(position, Vec3.multiply(upVec, this.laserOffsets.y));
|
||||
var laserTip = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.laserLength));
|
||||
this.barrelPoint = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.firingOffsets.z))
|
||||
|
||||
Overlays.editOverlay(this.laser, {
|
||||
start: this.barrelPoint,
|
||||
end: laserTip,
|
||||
end: this.laserTip,
|
||||
alpha: 1
|
||||
});
|
||||
},
|
||||
|
@ -114,19 +116,6 @@
|
|||
});
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
// this.initControllerMapping();
|
||||
this.laser = Overlays.addOverlay("line3d", {
|
||||
start: ZERO_VECTOR,
|
||||
end: ZERO_VECTOR,
|
||||
color: COLORS.RED,
|
||||
alpha: 1,
|
||||
visible: true,
|
||||
lineWidth: 2
|
||||
});
|
||||
},
|
||||
|
||||
triggerPress: function(hand, value) {
|
||||
if (this.hand === hand && value === 1) {
|
||||
//We are pulling trigger on the hand we have the gun in, so fire
|
||||
|
@ -135,15 +124,16 @@
|
|||
},
|
||||
|
||||
fire: function() {
|
||||
var pickRay = {
|
||||
origin: this.barrelPoint,
|
||||
direction: this.firingDirection
|
||||
};
|
||||
|
||||
Audio.playSound(this.fireSound, {
|
||||
position: this.barrelPoint,
|
||||
volume: this.fireVolume
|
||||
});
|
||||
|
||||
var pickRay = {
|
||||
origin: this.barrelPoint,
|
||||
direction: this.firingDirection
|
||||
};
|
||||
this.createGunFireEffect(this.barrelPoint)
|
||||
var intersection = Entities.findRayIntersectionBlocking(pickRay, true);
|
||||
if (intersection.intersects) {
|
||||
|
@ -170,11 +160,11 @@
|
|||
},
|
||||
|
||||
createEntityHitEffect: function(position) {
|
||||
var flash = Entities.addEntity({
|
||||
var sparks = Entities.addEntity({
|
||||
type: "ParticleEffect",
|
||||
position: position,
|
||||
lifetime: 4,
|
||||
"name": "Flash Emitter",
|
||||
"name": "Sparks Emitter",
|
||||
"color": {
|
||||
red: 228,
|
||||
green: 128,
|
||||
|
@ -228,7 +218,7 @@
|
|||
});
|
||||
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(flash, {
|
||||
Entities.editEntity(sparks, {
|
||||
isEmitting: false
|
||||
});
|
||||
}, 100);
|
||||
|
@ -261,11 +251,11 @@
|
|||
"z": 0
|
||||
},
|
||||
"accelerationSpread": {
|
||||
"x": .2,
|
||||
"x": 0.2,
|
||||
"y": 0,
|
||||
"z": .2
|
||||
"z": 0.2
|
||||
},
|
||||
"radiusSpread": .04,
|
||||
"radiusSpread": 0.04,
|
||||
"particleRadius": 0.07,
|
||||
"radiusStart": 0.07,
|
||||
"radiusFinish": 0.07,
|
||||
|
@ -282,11 +272,46 @@
|
|||
});
|
||||
}, 100);
|
||||
|
||||
var flash = Entities.addEntity({
|
||||
Entities.editEntity(this.flash, {
|
||||
isEmitting: true
|
||||
});
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(_this.flash, {
|
||||
isEmitting: false
|
||||
});
|
||||
}, 100)
|
||||
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
this.laser = Overlays.addOverlay("line3d", {
|
||||
start: ZERO_VECTOR,
|
||||
end: ZERO_VECTOR,
|
||||
color: COLORS.RED,
|
||||
alpha: 1,
|
||||
visible: true,
|
||||
lineWidth: 2
|
||||
});
|
||||
this.laserOffsets = {
|
||||
y: 0.095
|
||||
};
|
||||
this.firingOffsets = {
|
||||
z: 0.16
|
||||
}
|
||||
var gunProps = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
|
||||
var position = gunProps.position;
|
||||
var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0);
|
||||
this.firingDirection = Quat.getFront(rotation);
|
||||
var upVec = Quat.getUp(rotation);
|
||||
this.barrelPoint = Vec3.sum(position, Vec3.multiply(upVec, this.laserOffsets.y));
|
||||
this.barrelPoint = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.firingOffsets.z))
|
||||
|
||||
this.flash = Entities.addEntity({
|
||||
type: "ParticleEffect",
|
||||
position: position,
|
||||
lifetime: 4,
|
||||
position: this.barrelPoint,
|
||||
"name": "Muzzle Flash",
|
||||
isEmitting: false,
|
||||
"color": {
|
||||
red: 228,
|
||||
green: 128,
|
||||
|
@ -339,16 +364,13 @@
|
|||
"textures": "http://ericrius1.github.io/PartiArt/assets/star.png"
|
||||
});
|
||||
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(flash, {
|
||||
isEmitting: false
|
||||
});
|
||||
}, 100)
|
||||
|
||||
}
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(_this.flash, {parentID: _this.entityID});
|
||||
}, 500)
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new Pistol();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -384,10 +384,10 @@ var CHECK_MARK_COLOR = {
|
|||
y: newY
|
||||
});
|
||||
Overlays.editOverlay(this.checkMark, {
|
||||
y: newY
|
||||
y: newY + (0.25 * this.thumbSize)
|
||||
});
|
||||
Overlays.editOverlay(this.unCheckMark, {
|
||||
y: newY
|
||||
y: newY + (0.25 * this.thumbSize)
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -399,10 +399,10 @@ var CHECK_MARK_COLOR = {
|
|||
y: this.y
|
||||
});
|
||||
Overlays.editOverlay(this.checkMark, {
|
||||
y: this.y
|
||||
y: this.y + (0.25 * this.thumbSize)
|
||||
});
|
||||
Overlays.editOverlay(this.unCheckMark, {
|
||||
y: this.y
|
||||
y: this.y+ (0.25 * this.thumbSize)
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -814,12 +814,14 @@ var CHECK_MARK_COLOR = {
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
CollapsablePanelItem.prototype.destroy = function() {
|
||||
Overlays.deleteOverlay(this.title);
|
||||
Overlays.deleteOverlay(this.thumb);
|
||||
};
|
||||
|
||||
CollapsablePanelItem.prototype.editTitle = function(opts) {
|
||||
Overlays.editOverlay(this.title, opts);
|
||||
};
|
||||
|
||||
CollapsablePanelItem.prototype.hide = function() {
|
||||
Overlays.editOverlay(this.title, {
|
||||
|
@ -1531,4 +1533,4 @@ Controller.captureKeyEvents({
|
|||
});
|
||||
Controller.captureKeyEvents({
|
||||
text: "right"
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//
|
||||
// SunLightExample.js
|
||||
// examples
|
||||
// renderEngineDebug.js
|
||||
// examples/utilities/tools
|
||||
//
|
||||
// Sam Gateau
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
|
@ -10,78 +11,93 @@
|
|||
|
||||
Script.include("cookies.js");
|
||||
|
||||
var MENU = "Developer>Render>Debug Deferred Buffer";
|
||||
var ACTIONS = ["Off", "Diffuse", "Alpha", "Specular", "Roughness", "Normal", "Depth", "Lighting", "Custom"];
|
||||
var SETTINGS_KEY = "EngineDebugScript.DebugMode";
|
||||
|
||||
Number.prototype.clamp = function(min, max) {
|
||||
return Math.min(Math.max(this, min), max);
|
||||
};
|
||||
|
||||
var panel = new Panel(10, 100);
|
||||
|
||||
function CounterWidget(parentPanel, name, feedGetter, drawGetter, capSetter, capGetter) {
|
||||
this.subPanel = panel.newSubPanel(name);
|
||||
function CounterWidget(parentPanel, name, counter) {
|
||||
var subPanel = parentPanel.newSubPanel(name);
|
||||
var widget = parentPanel.items[name];
|
||||
widget.editTitle({ width: 270 });
|
||||
|
||||
this.subPanel.newSlider("Num Feed", 0, 1,
|
||||
function(value) { },
|
||||
feedGetter,
|
||||
function(value) { return (value); });
|
||||
this.subPanel.newSlider("Num Drawn", 0, 1,
|
||||
function(value) { },
|
||||
drawGetter,
|
||||
function(value) { return (value); });
|
||||
this.subPanel.newSlider("Max Drawn", -1, 1,
|
||||
capSetter,
|
||||
capGetter,
|
||||
function(value) { return (value); });
|
||||
subPanel.newSlider('Max Drawn', -1, 1,
|
||||
function(value) { counter.maxDrawn = value; }, // setter
|
||||
function() { return counter.maxDrawn; }, // getter
|
||||
function(value) { return value; });
|
||||
|
||||
this.update = function () {
|
||||
var numFeed = this.subPanel.get("Num Feed");
|
||||
this.subPanel.set("Num Feed", numFeed);
|
||||
this.subPanel.set("Num Drawn", this.subPanel.get("Num Drawn"));
|
||||
var slider = subPanel.getWidget('Max Drawn');
|
||||
|
||||
var numMax = Math.max(numFeed, 1);
|
||||
this.subPanel.getWidget("Num Feed").setMaxValue(numMax);
|
||||
this.subPanel.getWidget("Num Drawn").setMaxValue(numMax);
|
||||
this.subPanel.getWidget("Max Drawn").setMaxValue(numMax);
|
||||
this.update = function () {
|
||||
var numDrawn = counter.numDrawn; // avoid double polling
|
||||
var numMax = Math.max(numDrawn, 1);
|
||||
var title = [
|
||||
' ' + name,
|
||||
numDrawn + ' / ' + counter.numFeed
|
||||
].join('\t');
|
||||
|
||||
widget.editTitle({ text: title });
|
||||
slider.setMaxValue(numMax);
|
||||
};
|
||||
};
|
||||
|
||||
var opaquesCounter = new CounterWidget(panel, "Opaques",
|
||||
function () { return Scene.getEngineNumFeedOpaqueItems(); },
|
||||
function () { return Scene.getEngineNumDrawnOpaqueItems(); },
|
||||
function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); },
|
||||
function () { return Scene.getEngineMaxDrawnOpaqueItems(); }
|
||||
);
|
||||
var opaquesCounter = new CounterWidget(panel, "Opaques", Render.opaque);
|
||||
var transparentsCounter = new CounterWidget(panel, "Transparents", Render.transparent);
|
||||
var overlaysCounter = new CounterWidget(panel, "Overlays", Render.overlay3D);
|
||||
|
||||
var transparentsCounter = new CounterWidget(panel, "Transparents",
|
||||
function () { return Scene.getEngineNumFeedTransparentItems(); },
|
||||
function () { return Scene.getEngineNumDrawnTransparentItems(); },
|
||||
function(value) { Scene.setEngineMaxDrawnTransparentItems(value); },
|
||||
function () { return Scene.getEngineMaxDrawnTransparentItems(); }
|
||||
);
|
||||
var resizing = false;
|
||||
var previousMode = Settings.getValue(SETTINGS_KEY, -1);
|
||||
Menu.addActionGroup(MENU, ACTIONS, ACTIONS[previousMode + 1]);
|
||||
Render.deferredDebugMode = previousMode;
|
||||
Render.deferredDebugSize = { x: 0.0, y: -1.0, z: 1.0, w: 1.0 }; // Reset to default size
|
||||
|
||||
var overlaysCounter = new CounterWidget(panel, "Overlays",
|
||||
function () { return Scene.getEngineNumFeedOverlay3DItems(); },
|
||||
function () { return Scene.getEngineNumDrawnOverlay3DItems(); },
|
||||
function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); },
|
||||
function () { return Scene.getEngineMaxDrawnOverlay3DItems(); }
|
||||
);
|
||||
function setEngineDeferredDebugSize(eventX) {
|
||||
var scaledX = (2.0 * (eventX / Window.innerWidth) - 1.0).clamp(-1.0, 1.0);
|
||||
Render.deferredDebugSize = { x: scaledX, y: -1.0, z: 1.0, w: 1.0 };
|
||||
}
|
||||
function shouldStartResizing(eventX) {
|
||||
var x = Math.abs(eventX - Window.innerWidth * (1.0 + Render.deferredDebugSize.x) / 2.0);
|
||||
var mode = Render.deferredDebugMode;
|
||||
return mode !== -1 && x < 20;
|
||||
}
|
||||
|
||||
function menuItemEvent(menuItem) {
|
||||
var index = ACTIONS.indexOf(menuItem);
|
||||
if (index >= 0) {
|
||||
Render.deferredDebugMode = (index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// see libraries/render/src/render/Engine.h
|
||||
var showDisplayStatusFlag = 1;
|
||||
var showNetworkStatusFlag = 2;
|
||||
|
||||
panel.newCheckbox("Display status",
|
||||
function(value) { Scene.setEngineDisplayItemStatus(value ?
|
||||
Scene.doEngineDisplayItemStatus() | showDisplayStatusFlag :
|
||||
Scene.doEngineDisplayItemStatus() & ~showDisplayStatusFlag); },
|
||||
function() { return (Scene.doEngineDisplayItemStatus() & showDisplayStatusFlag) > 0; },
|
||||
function(value) { Render.displayItemStatus = (value ?
|
||||
Render.displayItemStatus | showDisplayStatusFlag :
|
||||
Render.displayItemStatus & ~showDisplayStatusFlag); },
|
||||
function() { return (Render.displayItemStatus & showDisplayStatusFlag) > 0; },
|
||||
function(value) { return (value & showDisplayStatusFlag) > 0; }
|
||||
);
|
||||
|
||||
panel.newCheckbox("Network/Physics status",
|
||||
function(value) { Scene.setEngineDisplayItemStatus(value ?
|
||||
Scene.doEngineDisplayItemStatus() | showNetworkStatusFlag :
|
||||
Scene.doEngineDisplayItemStatus() & ~showNetworkStatusFlag); },
|
||||
function() { return (Scene.doEngineDisplayItemStatus() & showNetworkStatusFlag) > 0; },
|
||||
function(value) { Render.displayItemStatus = (value ?
|
||||
Render.displayItemStatus | showNetworkStatusFlag :
|
||||
Render.displayItemStatus & ~showNetworkStatusFlag); },
|
||||
function() { return (Render.displayItemStatus & showNetworkStatusFlag) > 0; },
|
||||
function(value) { return (value & showNetworkStatusFlag) > 0; }
|
||||
);
|
||||
|
||||
panel.newSlider("Tone Mapping Exposure", -10, 10,
|
||||
function (value) { Render.tone.exposure = value; },
|
||||
function() { return Render.tone.exposure; },
|
||||
function (value) { return (value); });
|
||||
|
||||
var tickTackPeriod = 500;
|
||||
|
||||
function updateCounters() {
|
||||
|
@ -91,11 +107,51 @@ function updateCounters() {
|
|||
}
|
||||
Script.setInterval(updateCounters, tickTackPeriod);
|
||||
|
||||
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); });
|
||||
Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); });
|
||||
Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); });
|
||||
function mouseMoveEvent(event) {
|
||||
if (resizing) {
|
||||
setEngineDeferredDebugSize(event.x);
|
||||
} else {
|
||||
panel.mouseMoveEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
function mousePressEvent(event) {
|
||||
if (shouldStartResizing(event.x)) {
|
||||
resizing = true;
|
||||
} else {
|
||||
panel.mousePressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
if (resizing) {
|
||||
resizing = false;
|
||||
} else {
|
||||
panel.mouseReleaseEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
|
||||
Menu.menuItemEvent.connect(menuItemEvent);
|
||||
|
||||
function scriptEnding() {
|
||||
panel.destroy();
|
||||
Menu.removeActionGroup(MENU);
|
||||
// Reset
|
||||
Settings.setValue(SETTINGS_KEY, Render.deferredDebugMode);
|
||||
Render.deferredDebugMode = -1;
|
||||
Render.deferredDebugSize = { x: 0.0, y: -1.0, z: 1.0, w: 1.0 };
|
||||
Render.opaque.maxDrawn = -1;
|
||||
Render.transparent.maxDrawn = -1;
|
||||
Render.overlay3D.maxDrawn = -1;
|
||||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
|
||||
// Collapse items
|
||||
panel.mousePressEvent({ x: panel.x, y: panel.items["Overlays"].y});
|
||||
panel.mousePressEvent({ x: panel.x, y: panel.items["Transparents"].y});
|
||||
panel.mousePressEvent({ x: panel.x, y: panel.items["Opaques"].y});
|
||||
|
|
73
examples/winterSmashUp/targetPractice/shooterPlatform.js
Normal file
73
examples/winterSmashUp/targetPractice/shooterPlatform.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// shooterPlatform.js
|
||||
// examples/winterSmashUp/targetPractice
|
||||
//
|
||||
// Created by Thijs Wenker on 12/21/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// The Winter Smash Up Target Practice Game using a bow.
|
||||
// This is the platform that spawns the bow and attaches it to the avatars hand,
|
||||
// then de-rez the bow on leaving to prevent walking up to the targets with the bow.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
var _this = this;
|
||||
|
||||
const GAME_CHANNEL = 'winterSmashUpGame';
|
||||
const SCRIPT_URL = Script.resolvePath('../../toybox/bow/bow.js');
|
||||
const MODEL_URL = "https://hifi-public.s3.amazonaws.com/models/bow/new/bow-deadly.fbx";
|
||||
const COLLISION_HULL_URL = "https://hifi-public.s3.amazonaws.com/models/bow/new/bow_collision_hull.obj";
|
||||
const RIGHT_HAND = 1;
|
||||
const LEFT_HAND = 0;
|
||||
|
||||
var bowEntity = undefined;
|
||||
|
||||
_this.enterEntity = function(entityID) {
|
||||
print('entered bow game entity');
|
||||
|
||||
// Triggers a recording on an assignment client:
|
||||
Messages.sendMessage('PlayBackOnAssignment', 'BowShootingGameExplaination');
|
||||
|
||||
bowEntity = Entities.addEntity({
|
||||
name: 'Hifi-Bow-For-Game',
|
||||
type: 'Model',
|
||||
modelURL: MODEL_URL,
|
||||
position: MyAvatar.position,
|
||||
dimensions: {x: 0.04, y: 1.3, z: 0.21},
|
||||
collisionsWillMove: true,
|
||||
gravity: {x: 0, y: 0, z: 0},
|
||||
shapeType: 'compound',
|
||||
compoundShapeURL: COLLISION_HULL_URL,
|
||||
script: SCRIPT_URL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true,
|
||||
spatialKey: {
|
||||
leftRelativePosition: {
|
||||
x: 0.05,
|
||||
y: 0.06,
|
||||
z: -0.05
|
||||
},
|
||||
rightRelativePosition: {
|
||||
x: -0.05,
|
||||
y: 0.06,
|
||||
z: -0.05
|
||||
},
|
||||
relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90)
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
Messages.sendMessage('Hifi-Hand-Grab', JSON.stringify({hand: 'left', entityID: bowEntity}));
|
||||
};
|
||||
|
||||
_this.leaveEntity = function(entityID) {
|
||||
if (bowEntity !== undefined) {
|
||||
Entities.deleteEntity(bowEntity);
|
||||
bowEntity = undefined;
|
||||
}
|
||||
};
|
||||
});
|
93
examples/winterSmashUp/targetPractice/startTargetPractice.js
Normal file
93
examples/winterSmashUp/targetPractice/startTargetPractice.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// startTargetPractice.js
|
||||
// examples/winterSmashUp/targetPractice
|
||||
//
|
||||
// Created by Thijs Wenker on 12/21/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// The Winter Smash Up Target Practice Game using a bow.
|
||||
// This script starts the game, when the entity that contains the script gets shot.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
var _this = this;
|
||||
var waitForScriptLoad = false;
|
||||
|
||||
const MAX_GAME_TIME = 60; //seconds
|
||||
const SCRIPT_URL = 'http://s3.amazonaws.com/hifi-public/scripts/winterSmashUp/targetPractice/targetPracticeGame.js';
|
||||
const GAME_CHANNEL = 'winterSmashUpGame';
|
||||
|
||||
var isScriptRunning = function(script) {
|
||||
script = script.toLowerCase().trim();
|
||||
var runningScripts = ScriptDiscoveryService.getRunning();
|
||||
for (i in runningScripts) {
|
||||
if (runningScripts[i].url.toLowerCase().trim() == script) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var sendStartSignal = function() {
|
||||
Messages.sendMessage(GAME_CHANNEL, JSON.stringify({
|
||||
action: 'start',
|
||||
gameEntityID: _this.entityID,
|
||||
playerSessionUUID: MyAvatar.sessionUUID
|
||||
}));
|
||||
}
|
||||
|
||||
var startGame = function() {
|
||||
//TODO: check here if someone is already playing this game instance by verifying the userData for playerSessionID
|
||||
// and startTime with a maximum timeout of X seconds (30?)
|
||||
|
||||
|
||||
if (!isScriptRunning(SCRIPT_URL)) {
|
||||
// Loads the script for the player if this isn't yet loaded
|
||||
Script.load(SCRIPT_URL);
|
||||
waitForScriptLoad = true;
|
||||
return;
|
||||
}
|
||||
sendStartSignal();
|
||||
};
|
||||
|
||||
Messages.messageReceived.connect(function (channel, message, senderID) {
|
||||
if (channel == GAME_CHANNEL) {
|
||||
var data = JSON.parse(message);
|
||||
switch (data.action) {
|
||||
case 'scriptLoaded':
|
||||
if (waitForPing) {
|
||||
sendStartSignal();
|
||||
waitForPing = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_this.preload = function(entityID) {
|
||||
_this.entityID = entityID;
|
||||
};
|
||||
|
||||
_this.collisionWithEntity = function(entityA, entityB, collisionInfo) {
|
||||
if (entityA == _this.entityID) {
|
||||
try {
|
||||
var data = JSON.parse(Entities.getEntityProperties(entityB).userData);
|
||||
if (data.creatorSessionUUID === MyAvatar.sessionUUID) {
|
||||
print('attempting to startGame by collisionWithEntity.');
|
||||
startGame();
|
||||
}
|
||||
} catch(e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_this.onShot = function(forceDirection) {
|
||||
print('attempting to startGame by onShot.');
|
||||
startGame();
|
||||
};
|
||||
|
||||
Messages.subscribe(GAME_CHANNEL);
|
||||
});
|
227
examples/winterSmashUp/targetPractice/targetPracticeGame.js
Normal file
227
examples/winterSmashUp/targetPractice/targetPracticeGame.js
Normal file
|
@ -0,0 +1,227 @@
|
|||
//
|
||||
// targetPracticeGame.js
|
||||
// examples/winterSmashUp/targetPractice
|
||||
//
|
||||
// Created by Thijs Wenker on 12/21/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// The Winter Smash Up Target Practice Game using a bow.
|
||||
// This script runs on the client side (it is loaded through the platform trigger entity)
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
const GAME_CHANNEL = 'winterSmashUpGame';
|
||||
const SCORE_POST_URL = 'https://script.google.com/macros/s/AKfycbwZAMx6cMBx6-8NGEhR8ELUA-dldtpa_4P55z38Q4vYHW6kneg/exec';
|
||||
const MODEL_URL = 'http://cdn.highfidelity.com/chris/production/winter/game/';
|
||||
const MAX_GAME_TIME = 120; //seconds
|
||||
const TARGET_CLOSE_OFFSET = 0.5;
|
||||
const MILLISECS_IN_SEC = 1000;
|
||||
const HIT_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Clay_Pigeon_02.L.wav';
|
||||
const GRAVITY = -9.8;
|
||||
const MESSAGE_WIDTH = 100;
|
||||
const MESSAGE_HEIGHT = 50;
|
||||
|
||||
const TARGETS = [
|
||||
{pitch: -1, yaw: -20, maxDistance: 17},
|
||||
{pitch: -1, yaw: -15, maxDistance: 17},
|
||||
{pitch: -1, yaw: -10, maxDistance: 17},
|
||||
{pitch: -2, yaw: -5, maxDistance: 17},
|
||||
{pitch: -1, yaw: 0, maxDistance: 17},
|
||||
{pitch: 3, yaw: 10, maxDistance: 17},
|
||||
{pitch: -1, yaw: 15, maxDistance: 17},
|
||||
{pitch: -1, yaw: 20, maxDistance: 17},
|
||||
{pitch: 2, yaw: 25, maxDistance: 17},
|
||||
{pitch: 0, yaw: 30, maxDistance: 17}
|
||||
];
|
||||
|
||||
var gameRunning = false;
|
||||
var gameStartTime;
|
||||
var gameEntityID;
|
||||
var gameTimeoutTimer;
|
||||
var targetEntities = [];
|
||||
var hitSound = SoundCache.getSound(HIT_SOUND_URL);
|
||||
var messageOverlay = undefined;
|
||||
var messageExpire = 0;
|
||||
|
||||
var clearMessage = function() {
|
||||
if (messageOverlay !== undefined) {
|
||||
Overlays.deleteOverlay(messageOverlay);
|
||||
messageOverlay = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
var displayMessage = function(message, timeout) {
|
||||
clearMessage();
|
||||
messageExpire = Date.now() + timeout;
|
||||
messageOverlay = Overlays.addOverlay('text', {
|
||||
text: message,
|
||||
x: (Window.innerWidth / 2) - (MESSAGE_WIDTH / 2),
|
||||
y: (Window.innerHeight / 2) - (MESSAGE_HEIGHT / 2),
|
||||
width: MESSAGE_WIDTH,
|
||||
height: MESSAGE_HEIGHT,
|
||||
alpha: 1,
|
||||
backgroundAlpha: 0.0,
|
||||
font: {size: 20}
|
||||
});
|
||||
};
|
||||
|
||||
var getStatsText = function() {
|
||||
var timeLeft = MAX_GAME_TIME - ((Date.now() - gameStartTime) / MILLISECS_IN_SEC);
|
||||
return 'Time remaining: ' + timeLeft.toFixed(1) + 's\nTargets hit: ' + (TARGETS.length - targetEntities.length) + '/' + TARGETS.length;
|
||||
};
|
||||
|
||||
const timerOverlayWidth = 50;
|
||||
var timerOverlay = Overlays.addOverlay('text', {
|
||||
text: '',
|
||||
x: Window.innerWidth / 2 - (timerOverlayWidth / 2),
|
||||
y: 100,
|
||||
width: timerOverlayWidth,
|
||||
alpha: 1,
|
||||
backgroundAlpha: 0.0,
|
||||
visible: false,
|
||||
font: {size: 20}
|
||||
});
|
||||
|
||||
var cleanRemainingTargets = function() {
|
||||
while (targetEntities.length > 0) {
|
||||
Entities.deleteEntity(targetEntities.pop());
|
||||
}
|
||||
};
|
||||
|
||||
var createTarget = function(position, rotation, scale) {
|
||||
var initialDimensions = {x: 1.8437, y: 0.1614, z: 1.8438};
|
||||
var dimensions = Vec3.multiply(initialDimensions, scale);
|
||||
return Entities.addEntity({
|
||||
type: 'Model',
|
||||
rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)),
|
||||
lifetime: MAX_GAME_TIME,
|
||||
modelURL: MODEL_URL + 'target.fbx',
|
||||
shapeType: 'compound',
|
||||
compoundShapeURL: MODEL_URL + 'targetCollision.obj',
|
||||
dimensions: dimensions,
|
||||
position: position
|
||||
});
|
||||
};
|
||||
|
||||
var createTargetInDirection = function(startPosition, startRotation, pitch, yaw, maxDistance, scale) {
|
||||
var directionQuat = Quat.multiply(startRotation, Quat.fromPitchYawRollDegrees(pitch, yaw, 0.0));
|
||||
var directionVec = Vec3.multiplyQbyV(directionQuat, Vec3.FRONT);
|
||||
var intersection = Entities.findRayIntersection({direction: directionVec, origin: startPosition}, true);
|
||||
var distance = maxDistance;
|
||||
if (intersection.intersects && intersection.distance <= maxDistance) {
|
||||
distance = intersection.distance - TARGET_CLOSE_OFFSET;
|
||||
}
|
||||
return createTarget(Vec3.sum(startPosition, Vec3.multiplyQbyV(directionQuat, Vec3.multiply(Vec3.FRONT, distance))), startRotation, scale);
|
||||
};
|
||||
|
||||
var killTimer = function() {
|
||||
if (gameTimeoutTimer !== undefined) {
|
||||
Script.clearTimeout(gameTimeoutTimer);
|
||||
gameTimeoutTimer = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
var submitScore = function() {
|
||||
gameRunning = false;
|
||||
killTimer();
|
||||
Overlays.editOverlay(timerOverlay, {visible: false});
|
||||
if (!GlobalServices.username) {
|
||||
displayMessage('Could not submit score, you are not logged in.', 5000);
|
||||
return;
|
||||
}
|
||||
var timeItTook = Date.now() - gameStartTime;
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('POST', SCORE_POST_URL, false);
|
||||
req.send(JSON.stringify({
|
||||
username: GlobalServices.username,
|
||||
time: timeItTook / MILLISECS_IN_SEC
|
||||
}));
|
||||
displayMessage('Your score has been submitted!', 5000);
|
||||
};
|
||||
|
||||
var onTargetHit = function(targetEntity, projectileEntity, collision) {
|
||||
var targetIndex = targetEntities.indexOf(targetEntity);
|
||||
if (targetIndex !== -1) {
|
||||
try {
|
||||
var data = JSON.parse(Entities.getEntityProperties(projectileEntity).userData);
|
||||
if (data.creatorSessionUUID === MyAvatar.sessionUUID) {
|
||||
this.audioInjector = Audio.playSound(hitSound, {
|
||||
position: collision.contactPoint,
|
||||
volume: 0.5
|
||||
});
|
||||
// Attach arrow to target for the nice effect
|
||||
Entities.editEntity(projectileEntity, {
|
||||
ignoreForCollisions: true,
|
||||
parentID: targetEntity
|
||||
});
|
||||
Entities.editEntity(targetEntity, {
|
||||
collisionsWillMove: true,
|
||||
gravity: {x: 0, y: GRAVITY, z: 0},
|
||||
velocity: {x: 0, y: -0.01, z: 0}
|
||||
});
|
||||
targetEntities.splice(targetIndex, 1);
|
||||
if (targetEntities.length === 0) {
|
||||
submitScore();
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var startGame = function(entityID) {
|
||||
cleanRemainingTargets();
|
||||
killTimer();
|
||||
gameEntityID = entityID;
|
||||
targetEntities = [];
|
||||
var parentEntity = Entities.getEntityProperties(gameEntityID);
|
||||
for (var i in TARGETS) {
|
||||
var target = TARGETS[i];
|
||||
var targetEntity = createTargetInDirection(parentEntity.position, parentEntity.rotation, target.pitch, target.yaw, target.maxDistance, 0.67);
|
||||
Script.addEventHandler(targetEntity, 'collisionWithEntity', onTargetHit);
|
||||
targetEntities.push(targetEntity);
|
||||
}
|
||||
gameStartTime = Date.now();
|
||||
gameTimeoutTimer = Script.setTimeout(function() {
|
||||
cleanRemainingTargets();
|
||||
Overlays.editOverlay(timerOverlay, {visible: false});
|
||||
gameRunning = false;
|
||||
displayMessage('Game timed out.', 5000);
|
||||
}, MAX_GAME_TIME * MILLISECS_IN_SEC);
|
||||
gameRunning = true;
|
||||
Overlays.editOverlay(timerOverlay, {visible: true, text: getStatsText()});
|
||||
displayMessage('Game started! GO GO GO!', 3000);
|
||||
};
|
||||
|
||||
Messages.messageReceived.connect(function (channel, message, senderID) {
|
||||
if (channel == GAME_CHANNEL) {
|
||||
var data = JSON.parse(message);
|
||||
switch (data.action) {
|
||||
case 'start':
|
||||
if (data.playerSessionUUID === MyAvatar.sessionUUID) {
|
||||
startGame(data.gameEntityID);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Messages.subscribe(GAME_CHANNEL);
|
||||
Messages.sendMessage(GAME_CHANNEL, JSON.stringify({action: 'scriptLoaded'}));
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
if (gameRunning) {
|
||||
Overlays.editOverlay(timerOverlay, {text: getStatsText()});
|
||||
}
|
||||
if (messageOverlay !== undefined && Date.now() > messageExpire) {
|
||||
clearMessage();
|
||||
}
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
Overlays.deleteOverlay(timerOverlay);
|
||||
cleanRemainingTargets();
|
||||
clearMessage();
|
||||
});
|
|
@ -45,7 +45,9 @@ else ()
|
|||
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
|
||||
endif ()
|
||||
|
||||
find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets WebSockets)
|
||||
find_package(Qt5 COMPONENTS
|
||||
Gui Multimedia Network OpenGL Qml Quick Script Svg
|
||||
WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets)
|
||||
|
||||
# grab the ui files in resources/ui
|
||||
file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
|
||||
|
@ -107,7 +109,9 @@ add_dependency_external_projects(sdl2)
|
|||
if (WIN32)
|
||||
add_dependency_external_projects(OpenVR)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR APPLE)
|
||||
add_dependency_external_projects(neuron)
|
||||
endif()
|
||||
|
||||
# disable /OPT:REF and /OPT:ICF for the Debug builds
|
||||
# This will prevent the following linker warnings
|
||||
|
@ -175,9 +179,17 @@ include_directories("${PROJECT_SOURCE_DIR}/src")
|
|||
|
||||
target_link_libraries(
|
||||
${TARGET_NAME}
|
||||
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKitWidgets
|
||||
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL
|
||||
Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg
|
||||
Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets
|
||||
)
|
||||
|
||||
# Issue causes build failure unless we add this directory.
|
||||
# See https://bugreports.qt.io/browse/QTBUG-43351
|
||||
if (WIN32)
|
||||
add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine)
|
||||
endif()
|
||||
|
||||
# assume we are using a Qt build without bearer management
|
||||
add_definitions(-DQT_NO_BEARERMANAGEMENT)
|
||||
|
||||
|
@ -209,5 +221,9 @@ else (APPLE)
|
|||
endif()
|
||||
endif (APPLE)
|
||||
|
||||
if (WIN32)
|
||||
set(EXTRA_DEPLOY_OPTIONS "--qmldir ${PROJECT_SOURCE_DIR}/resources/qml")
|
||||
endif()
|
||||
|
||||
package_libraries_for_deployment()
|
||||
consolidate_stack_components()
|
||||
|
|
7
interface/resources/controllers/neuron.json
Normal file
7
interface/resources/controllers/neuron.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "Neuron to Standard",
|
||||
"channels": [
|
||||
{ "from": "Hydra.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" }
|
||||
]
|
||||
}
|
61
interface/resources/controllers/standard_navigation.json
Normal file
61
interface/resources/controllers/standard_navigation.json
Normal file
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "Standard to Action",
|
||||
"when": "Application.NavigationFocused",
|
||||
"channels": [
|
||||
{ "disabled_from": { "makeAxis" : [ "Standard.DD", "Standard.DU" ] }, "to": "Actions.UiNavVertical" },
|
||||
{ "disabled_from": { "makeAxis" : [ "Standard.DL", "Standard.DR" ] }, "to": "Actions.UiNavLateral" },
|
||||
{ "disabled_from": { "makeAxis" : [ "Standard.LB", "Standard.RB" ] }, "to": "Actions.UiNavGroup" },
|
||||
{ "from": "Standard.DU", "to": "Actions.UiNavVertical" },
|
||||
{ "from": "Standard.DD", "to": "Actions.UiNavVertical", "filters": "invert" },
|
||||
{ "from": "Standard.DL", "to": "Actions.UiNavLateral", "filters": "invert" },
|
||||
{ "from": "Standard.DR", "to": "Actions.UiNavLateral" },
|
||||
{ "from": "Standard.LB", "to": "Actions.UiNavGroup","filters": "invert" },
|
||||
{ "from": "Standard.RB", "to": "Actions.UiNavGroup" },
|
||||
{ "from": [ "Standard.A", "Standard.X", "Standard.RT", "Standard.LT" ], "to": "Actions.UiNavSelect" },
|
||||
{ "from": [ "Standard.B", "Standard.Y", "Standard.RightPrimaryThumb", "Standard.LeftPrimaryThumb" ], "to": "Actions.UiNavBack" },
|
||||
{
|
||||
"from": [ "Standard.RT", "Standard.LT" ],
|
||||
"to": "Actions.UiNavSelect",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.5 },
|
||||
"constrainToInteger"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "Standard.LX", "to": "Actions.UiNavLateral",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.95 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "Standard.LY", "to": "Actions.UiNavVertical",
|
||||
"filters": [
|
||||
"invert",
|
||||
{ "type": "deadZone", "min": 0.95 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "Standard.RX", "to": "Actions.UiNavLateral",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.95 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "Standard.RY", "to": "Actions.UiNavVertical",
|
||||
"filters": [
|
||||
"invert",
|
||||
{ "type": "deadZone", "min": 0.95 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.4 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebKit 3.0
|
||||
import QtWebEngine 1.1
|
||||
import "controls"
|
||||
import "styles"
|
||||
|
||||
|
@ -39,9 +39,10 @@ VrDialog {
|
|||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: scrollView.top
|
||||
anchors.bottom: webview.top
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttons
|
||||
spacing: 4
|
||||
|
@ -112,26 +113,22 @@ VrDialog {
|
|||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
WebEngineView {
|
||||
id: webview
|
||||
url: "http://highfidelity.com"
|
||||
anchors.top: buttons.bottom
|
||||
anchors.topMargin: 8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
WebView {
|
||||
id: webview
|
||||
url: "http://highfidelity.com"
|
||||
anchors.fill: parent
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.status == WebView.LoadSucceededStarted) {
|
||||
addressBar.text = loadRequest.url
|
||||
}
|
||||
}
|
||||
onIconChanged: {
|
||||
barIcon.source = icon
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
|
||||
addressBar.text = loadRequest.url
|
||||
}
|
||||
}
|
||||
onIconChanged: {
|
||||
console.log("New icon: " + icon)
|
||||
}
|
||||
}
|
||||
} // item
|
||||
|
||||
|
@ -146,5 +143,4 @@ VrDialog {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // dialog
|
||||
|
|
|
@ -2,7 +2,7 @@ import Hifi 1.0 as Hifi
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.3
|
||||
import QtWebKit 3.0
|
||||
import QtWebEngine 1.1
|
||||
import "controls"
|
||||
|
||||
VrDialog {
|
||||
|
@ -18,15 +18,11 @@ VrDialog {
|
|||
anchors.margins: parent.margins
|
||||
anchors.topMargin: parent.topMargin
|
||||
|
||||
ScrollView {
|
||||
WebEngineView {
|
||||
id: webview
|
||||
objectName: "WebView"
|
||||
anchors.fill: parent
|
||||
WebView {
|
||||
objectName: "WebView"
|
||||
id: webview
|
||||
url: infoView.url
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
url: infoView.url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import Hifi 1.0
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Controls.Styles 1.3
|
||||
import QtWebKit 3.0
|
||||
import QtWebEngine 1.1
|
||||
import "controls"
|
||||
|
||||
VrDialog {
|
||||
|
@ -24,27 +24,22 @@ VrDialog {
|
|||
anchors.margins: parent.margins
|
||||
anchors.topMargin: parent.topMargin
|
||||
|
||||
|
||||
ScrollView {
|
||||
WebEngineView {
|
||||
objectName: "WebView"
|
||||
id: webview
|
||||
url: "https://metaverse.highfidelity.com/marketplace"
|
||||
anchors.fill: parent
|
||||
WebView {
|
||||
objectName: "WebView"
|
||||
id: webview
|
||||
url: "https://metaverse.highfidelity.com/marketplace"
|
||||
anchors.fill: parent
|
||||
onNavigationRequested: {
|
||||
console.log(request.url)
|
||||
if (!marketplaceDialog.navigationRequested(request.url)) {
|
||||
console.log("Application absorbed the request")
|
||||
request.action = WebView.IgnoreRequest;
|
||||
return;
|
||||
}
|
||||
console.log("Application passed on the request")
|
||||
request.action = WebView.AcceptRequest;
|
||||
onNavigationRequested: {
|
||||
console.log(request.url)
|
||||
if (!marketplaceDialog.navigationRequested(request.url)) {
|
||||
console.log("Application absorbed the request")
|
||||
request.action = WebView.IgnoreRequest;
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log("Application passed on the request")
|
||||
request.action = WebView.AcceptRequest;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
77
interface/resources/qml/QmlWebWindow.qml
Normal file
77
interface/resources/qml/QmlWebWindow.qml
Normal file
|
@ -0,0 +1,77 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebEngine 1.1
|
||||
|
||||
import "controls"
|
||||
import "styles"
|
||||
|
||||
VrDialog {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
title: "WebWindow"
|
||||
resizable: true
|
||||
enabled: false
|
||||
visible: false
|
||||
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||
destroyOnCloseButton: false
|
||||
contentImplicitWidth: clientArea.implicitWidth
|
||||
contentImplicitHeight: clientArea.implicitHeight
|
||||
backgroundColor: "#7f000000"
|
||||
property url source: "about:blank"
|
||||
|
||||
signal navigating(string url)
|
||||
function stop() {
|
||||
webview.stop();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Ensure the JS from the web-engine makes it to our logging
|
||||
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
|
||||
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: clientArea
|
||||
implicitHeight: 600
|
||||
implicitWidth: 800
|
||||
x: root.clientX
|
||||
y: root.clientY
|
||||
width: root.clientWidth
|
||||
height: root.clientHeight
|
||||
|
||||
WebEngineView {
|
||||
id: webview
|
||||
url: root.source
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
onUrlChanged: {
|
||||
var currentUrl = url.toString();
|
||||
var newUrl = urlHandler.fixupUrl(currentUrl);
|
||||
if (newUrl != currentUrl) {
|
||||
url = newUrl;
|
||||
}
|
||||
}
|
||||
|
||||
onLoadingChanged: {
|
||||
// Required to support clicking on "hifi://" links
|
||||
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
||||
var url = loadRequest.url.toString();
|
||||
if (urlHandler.canHandleUrl(url)) {
|
||||
if (urlHandler.handleUrl(url)) {
|
||||
webview.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
profile: WebEngineProfile {
|
||||
id: webviewProfile
|
||||
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"
|
||||
storageName: "qmlWebEngine"
|
||||
}
|
||||
}
|
||||
} // item
|
||||
} // dialog
|
55
interface/resources/qml/QmlWindow.qml
Normal file
55
interface/resources/qml/QmlWindow.qml
Normal file
|
@ -0,0 +1,55 @@
|
|||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebChannel 1.0
|
||||
import QtWebSockets 1.0
|
||||
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
|
||||
|
||||
import "controls"
|
||||
import "styles"
|
||||
|
||||
VrDialog {
|
||||
id: root
|
||||
objectName: "topLevelWindow"
|
||||
HifiConstants { id: hifi }
|
||||
title: "QmlWindow"
|
||||
resizable: true
|
||||
enabled: false
|
||||
visible: false
|
||||
focus: true
|
||||
property var channel;
|
||||
|
||||
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||
destroyOnCloseButton: false
|
||||
contentImplicitWidth: clientArea.implicitWidth
|
||||
contentImplicitHeight: clientArea.implicitHeight
|
||||
property alias source: pageLoader.source
|
||||
|
||||
Item {
|
||||
id: clientArea
|
||||
implicitHeight: 600
|
||||
implicitWidth: 800
|
||||
x: root.clientX
|
||||
y: root.clientY
|
||||
width: root.clientWidth
|
||||
height: root.clientHeight
|
||||
focus: true
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
id: pageLoader
|
||||
objectName: "Loader"
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
property var dialog: root
|
||||
|
||||
onLoaded: {
|
||||
forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
console.log("QmlWindow pageLoader keypress")
|
||||
}
|
||||
}
|
||||
} // item
|
||||
} // dialog
|
|
@ -4,116 +4,7 @@ import Hifi 1.0
|
|||
|
||||
// Currently for testing a pure QML replacement menu
|
||||
Item {
|
||||
Item {
|
||||
objectName: "AllActions"
|
||||
Action {
|
||||
id: aboutApp
|
||||
objectName: "HifiAction_" + MenuConstants.AboutApp
|
||||
text: qsTr("About Interface")
|
||||
}
|
||||
|
||||
//
|
||||
// File Menu
|
||||
//
|
||||
Action {
|
||||
id: login
|
||||
objectName: "HifiAction_" + MenuConstants.Login
|
||||
text: qsTr("Login")
|
||||
}
|
||||
Action {
|
||||
id: quit
|
||||
objectName: "HifiAction_" + MenuConstants.Quit
|
||||
text: qsTr("Quit")
|
||||
//shortcut: StandardKey.Quit
|
||||
shortcut: "Ctrl+Q"
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Edit menu
|
||||
//
|
||||
Action {
|
||||
id: undo
|
||||
text: "Undo"
|
||||
shortcut: StandardKey.Undo
|
||||
}
|
||||
|
||||
Action {
|
||||
id: redo
|
||||
text: "Redo"
|
||||
shortcut: StandardKey.Redo
|
||||
}
|
||||
|
||||
Action {
|
||||
id: animations
|
||||
objectName: "HifiAction_" + MenuConstants.Animations
|
||||
text: qsTr("Animations...")
|
||||
}
|
||||
Action {
|
||||
id: attachments
|
||||
text: qsTr("Attachments...")
|
||||
}
|
||||
Action {
|
||||
id: explode
|
||||
text: qsTr("Explode on quit")
|
||||
checkable: true
|
||||
checked: true
|
||||
}
|
||||
Action {
|
||||
id: freeze
|
||||
text: qsTr("Freeze on quit")
|
||||
checkable: true
|
||||
checked: false
|
||||
}
|
||||
ExclusiveGroup {
|
||||
Action {
|
||||
id: visibleToEveryone
|
||||
objectName: "HifiAction_" + MenuConstants.VisibleToEveryone
|
||||
text: qsTr("Everyone")
|
||||
checkable: true
|
||||
checked: true
|
||||
}
|
||||
Action {
|
||||
id: visibleToFriends
|
||||
objectName: "HifiAction_" + MenuConstants.VisibleToFriends
|
||||
text: qsTr("Friends")
|
||||
checkable: true
|
||||
}
|
||||
Action {
|
||||
id: visibleToNoOne
|
||||
objectName: "HifiAction_" + MenuConstants.VisibleToNoOne
|
||||
text: qsTr("No one")
|
||||
checkable: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
objectName: "rootMenu";
|
||||
Menu {
|
||||
title: "File"
|
||||
MenuItem { action: login }
|
||||
MenuItem { action: explode }
|
||||
MenuItem { action: freeze }
|
||||
MenuItem { action: quit }
|
||||
}
|
||||
Menu {
|
||||
title: "Tools"
|
||||
Menu {
|
||||
title: "I Am Visible To"
|
||||
MenuItem { action: visibleToEveryone }
|
||||
MenuItem { action: visibleToFriends }
|
||||
MenuItem { action: visibleToNoOne }
|
||||
}
|
||||
MenuItem { action: animations }
|
||||
}
|
||||
Menu {
|
||||
title: "Long menu name top menu"
|
||||
MenuItem { action: aboutApp }
|
||||
}
|
||||
Menu {
|
||||
title: "Help"
|
||||
MenuItem { action: aboutApp }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebKit 3.0
|
||||
import QtWebEngine 1.1
|
||||
|
||||
WebView {
|
||||
WebEngineView {
|
||||
id: root
|
||||
objectName: "webview"
|
||||
anchors.fill: parent
|
||||
objectName: "webview"
|
||||
url: "about:blank"
|
||||
}
|
||||
|
|
|
@ -41,6 +41,11 @@ DialogBase {
|
|||
// modify the visibility
|
||||
onEnabledChanged: {
|
||||
opacity = enabled ? 1.0 : 0.0
|
||||
// If the dialog is initially invisible, setting opacity doesn't
|
||||
// trigger making it visible.
|
||||
if (enabled) {
|
||||
visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The actual animator
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "Application.h"
|
||||
|
||||
#include <gl/Config.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/component_wise.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
@ -28,11 +30,14 @@
|
|||
#include <QtGui/QImage>
|
||||
#include <QtGui/QWheelEvent>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtQml/QQmlContext>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/QMouseEvent>
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
#include <QtQml/QQmlContext>
|
||||
#include <QtQml/QQmlEngine>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
#include <QtWidgets/QActionGroup>
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
@ -47,6 +52,7 @@
|
|||
#include <gl/Config.h>
|
||||
#include <gl/QOpenGLContextWrapper.h>
|
||||
|
||||
#include <ResourceScriptingInterface.h>
|
||||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <ApplicationVersion.h>
|
||||
|
@ -89,6 +95,7 @@
|
|||
#include <RenderableWebEntityItem.h>
|
||||
#include <RenderDeferredTask.h>
|
||||
#include <ResourceCache.h>
|
||||
#include <RenderScriptingInterface.h>
|
||||
#include <SceneScriptingInterface.h>
|
||||
#include <RecordingScriptingInterface.h>
|
||||
#include <ScriptCache.h>
|
||||
|
@ -101,6 +108,7 @@
|
|||
#include <VrMenu.h>
|
||||
#include <recording/Deck.h>
|
||||
#include <recording/Recorder.h>
|
||||
#include <QmlWebWindowClass.h>
|
||||
|
||||
#include "AnimDebugDraw.h"
|
||||
#include "AudioClient.h"
|
||||
|
@ -335,12 +343,15 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<RecordingScriptingInterface>();
|
||||
DependencyManager::set<WindowScriptingInterface>();
|
||||
DependencyManager::set<HMDScriptingInterface>();
|
||||
DependencyManager::set<ResourceScriptingInterface>();
|
||||
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
DependencyManager::set<SpeechRecognizer>();
|
||||
#endif
|
||||
DependencyManager::set<DiscoverabilityManager>();
|
||||
DependencyManager::set<SceneScriptingInterface>();
|
||||
DependencyManager::set<RenderScriptingInterface>();
|
||||
DependencyManager::set<OffscreenUi>();
|
||||
DependencyManager::set<AutoUpdater>();
|
||||
DependencyManager::set<PathUtils>();
|
||||
|
@ -362,6 +373,17 @@ Cube3DOverlay* _keyboardFocusHighlight{ nullptr };
|
|||
int _keyboardFocusHighlightID{ -1 };
|
||||
PluginContainer* _pluginContainer;
|
||||
|
||||
|
||||
// FIXME hack access to the internal share context for the Chromium helper
|
||||
// Normally we'd want to use QWebEngine::initialize(), but we can't because
|
||||
// our primary context is a QGLWidget, which can't easily be initialized to share
|
||||
// from a QOpenGLContext.
|
||||
//
|
||||
// So instead we create a new offscreen context to share with the QGLWidget,
|
||||
// and manually set THAT to be the shared context for the Chromium helper
|
||||
OffscreenGLCanvas* _chromiumShareContext { nullptr };
|
||||
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
|
||||
|
||||
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||
QApplication(argc, argv),
|
||||
_dependencyManagerIsSetup(setupEssentials(argc, argv)),
|
||||
|
@ -623,6 +645,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
_glWidget->makeCurrent();
|
||||
_glWidget->initializeGL();
|
||||
|
||||
_chromiumShareContext = new OffscreenGLCanvas();
|
||||
_chromiumShareContext->create(_glWidget->context()->contextHandle());
|
||||
_chromiumShareContext->makeCurrent();
|
||||
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
|
||||
|
||||
_offscreenContext = new OffscreenGLCanvas();
|
||||
_offscreenContext->create(_glWidget->context()->contextHandle());
|
||||
_offscreenContext->makeCurrent();
|
||||
|
@ -663,6 +690,74 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
// Setup the userInputMapper with the actions
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
|
||||
using namespace controller;
|
||||
static auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
if (offscreenUi->navigationFocused()) {
|
||||
auto actionEnum = static_cast<Action>(action);
|
||||
int key = Qt::Key_unknown;
|
||||
static int lastKey = Qt::Key_unknown;
|
||||
bool navAxis = false;
|
||||
switch (actionEnum) {
|
||||
case Action::UI_NAV_VERTICAL:
|
||||
navAxis = true;
|
||||
if (state > 0.0f) {
|
||||
key = Qt::Key_Up;
|
||||
} else if (state < 0.0f) {
|
||||
key = Qt::Key_Down;
|
||||
}
|
||||
break;
|
||||
|
||||
case Action::UI_NAV_LATERAL:
|
||||
navAxis = true;
|
||||
if (state > 0.0f) {
|
||||
key = Qt::Key_Right;
|
||||
} else if (state < 0.0f) {
|
||||
key = Qt::Key_Left;
|
||||
}
|
||||
break;
|
||||
|
||||
case Action::UI_NAV_GROUP:
|
||||
navAxis = true;
|
||||
if (state > 0.0f) {
|
||||
key = Qt::Key_Tab;
|
||||
} else if (state < 0.0f) {
|
||||
key = Qt::Key_Backtab;
|
||||
}
|
||||
break;
|
||||
|
||||
case Action::UI_NAV_BACK:
|
||||
key = Qt::Key_Escape;
|
||||
break;
|
||||
|
||||
case Action::UI_NAV_SELECT:
|
||||
key = Qt::Key_Return;
|
||||
break;
|
||||
}
|
||||
|
||||
if (navAxis) {
|
||||
if (lastKey != Qt::Key_unknown) {
|
||||
QKeyEvent event(QEvent::KeyRelease, lastKey, Qt::NoModifier);
|
||||
sendEvent(offscreenUi->getWindow(), &event);
|
||||
lastKey = Qt::Key_unknown;
|
||||
}
|
||||
|
||||
if (key != Qt::Key_unknown) {
|
||||
QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier);
|
||||
sendEvent(offscreenUi->getWindow(), &event);
|
||||
lastKey = key;
|
||||
}
|
||||
} else if (key != Qt::Key_unknown) {
|
||||
if (state) {
|
||||
QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier);
|
||||
sendEvent(offscreenUi->getWindow(), &event);
|
||||
} else {
|
||||
QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier);
|
||||
sendEvent(offscreenUi->getWindow(), &event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (action == controller::toInt(controller::Action::RETICLE_CLICK)) {
|
||||
auto globalPos = QCursor::pos();
|
||||
auto localPos = _glWidget->mapFromGlobal(globalPos);
|
||||
|
@ -686,13 +781,37 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
||||
VrMenu::toggle(); // show context menu even on non-stereo displays
|
||||
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
||||
auto globalPos = QCursor::pos();
|
||||
globalPos.setX(globalPos.x() + state);
|
||||
QCursor::setPos(globalPos);
|
||||
auto oldPos = QCursor::pos();
|
||||
auto newPos = oldPos;
|
||||
newPos.setX(oldPos.x() + state);
|
||||
QCursor::setPos(newPos);
|
||||
|
||||
|
||||
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
|
||||
// remove it after we're done
|
||||
const float REASONABLE_CHANGE = 50.0f;
|
||||
glm::vec2 oldPosG = { oldPos.x(), oldPos.y() };
|
||||
glm::vec2 newPosG = { newPos.x(), newPos.y() };
|
||||
auto distance = glm::distance(oldPosG, newPosG);
|
||||
if (distance > REASONABLE_CHANGE) {
|
||||
qDebug() << "Action::RETICLE_X... UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPosG << " newPos:" << newPosG;
|
||||
}
|
||||
|
||||
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
|
||||
auto globalPos = QCursor::pos();
|
||||
globalPos.setY(globalPos.y() + state);
|
||||
QCursor::setPos(globalPos);
|
||||
auto oldPos = QCursor::pos();
|
||||
auto newPos = oldPos;
|
||||
newPos.setY(oldPos.y() + state);
|
||||
QCursor::setPos(newPos);
|
||||
|
||||
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
|
||||
// remove it after we're done
|
||||
const float REASONABLE_CHANGE = 50.0f;
|
||||
glm::vec2 oldPosG = { oldPos.x(), oldPos.y() };
|
||||
glm::vec2 newPosG = { newPos.x(), newPos.y() };
|
||||
auto distance = glm::distance(oldPosG, newPosG);
|
||||
if (distance > REASONABLE_CHANGE) {
|
||||
qDebug() << "Action::RETICLE_Y... UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPosG << " newPos:" << newPosG;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -706,9 +825,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
_applicationStateDevice->addInputVariant(QString("ComfortMode"), controller::StateController::ReadLambda([]() -> float {
|
||||
return (float)Menu::getInstance()->isOptionChecked(MenuOption::ComfortMode);
|
||||
}));
|
||||
_applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float {
|
||||
return (float)qApp->getMyAvatar()->getCharacterController()->onGround();
|
||||
}));
|
||||
_applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float {
|
||||
return (float)qApp->getMyAvatar()->getCharacterController()->onGround();
|
||||
}));
|
||||
_applicationStateDevice->addInputVariant(QString("NavigationFocused"), controller::StateController::ReadLambda([]() -> float {
|
||||
static auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
return offscreenUi->navigationFocused() ? 1.0 : 0.0;
|
||||
}));
|
||||
|
||||
userInputMapper->registerDevice(_applicationStateDevice);
|
||||
|
||||
|
@ -1051,9 +1174,59 @@ void Application::initializeUi() {
|
|||
offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
||||
offscreenUi->load("Root.qml");
|
||||
offscreenUi->load("RootMenu.qml");
|
||||
auto scriptingInterface = DependencyManager::get<controller::ScriptingInterface>();
|
||||
offscreenUi->getRootContext()->setContextProperty("Controller", scriptingInterface.data());
|
||||
offscreenUi->getRootContext()->setContextProperty("MyAvatar", getMyAvatar());
|
||||
// FIXME either expose so that dialogs can set this themselves or
|
||||
// do better detection in the offscreen UI of what has focus
|
||||
offscreenUi->setNavigationFocused(false);
|
||||
|
||||
auto rootContext = offscreenUi->getRootContext();
|
||||
auto engine = rootContext->engine();
|
||||
connect(engine, &QQmlEngine::quit, [] {
|
||||
qApp->quit();
|
||||
});
|
||||
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
|
||||
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
|
||||
rootContext->setContextProperty("MyAvatar", getMyAvatar());
|
||||
rootContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data());
|
||||
rootContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
|
||||
|
||||
rootContext->setContextProperty("TREE_SCALE", TREE_SCALE);
|
||||
rootContext->setContextProperty("Quat", new Quat());
|
||||
rootContext->setContextProperty("Vec3", new Vec3());
|
||||
rootContext->setContextProperty("Uuid", new ScriptUUID());
|
||||
|
||||
rootContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
|
||||
|
||||
rootContext->setContextProperty("Camera", &_myCamera);
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
rootContext->setContextProperty("SpeechRecognizer", DependencyManager::get<SpeechRecognizer>().data());
|
||||
#endif
|
||||
|
||||
rootContext->setContextProperty("Overlays", &_overlays);
|
||||
rootContext->setContextProperty("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
||||
|
||||
rootContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("Stats", Stats::getInstance());
|
||||
rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("AudioDevice", AudioDeviceScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
|
||||
rootContext->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
rootContext->setContextProperty("Account", AccountScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
|
||||
rootContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
|
||||
rootContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data());
|
||||
rootContext->setContextProperty("UndoStack", &_undoStackScriptingInterface);
|
||||
rootContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
|
||||
rootContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data());
|
||||
rootContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Render", DependencyManager::get<RenderScriptingInterface>().data());
|
||||
rootContext->setContextProperty("ScriptDiscoveryService", this->getRunningScriptsWidget());
|
||||
|
||||
_glWidget->installEventFilter(offscreenUi.data());
|
||||
VrMenu::load();
|
||||
VrMenu::executeQueuedLambdas();
|
||||
|
@ -1161,26 +1334,15 @@ void Application::paintGL() {
|
|||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
|
||||
PerformanceTimer perfTimer("Mirror");
|
||||
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebufferDepthColor();
|
||||
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebuffer();
|
||||
|
||||
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
|
||||
renderArgs._blitFramebuffer = DependencyManager::get<FramebufferCache>()->getSelfieFramebuffer();
|
||||
|
||||
renderRearViewMirror(&renderArgs, _mirrorViewRect);
|
||||
|
||||
renderArgs._blitFramebuffer.reset();
|
||||
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
|
||||
|
||||
{
|
||||
float ratio = ((float)QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale());
|
||||
// Flip the src and destination rect horizontally to do the mirror
|
||||
auto mirrorRect = glm::ivec4(0, 0, _mirrorViewRect.width() * ratio, _mirrorViewRect.height() * ratio);
|
||||
auto mirrorRectDest = glm::ivec4(mirrorRect.z, mirrorRect.y, mirrorRect.x, mirrorRect.w);
|
||||
|
||||
auto selfieFbo = DependencyManager::get<FramebufferCache>()->getSelfieFramebuffer();
|
||||
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
|
||||
batch.setFramebuffer(selfieFbo);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
batch.blit(primaryFbo, mirrorRect, selfieFbo, mirrorRectDest);
|
||||
batch.setFramebuffer(nullptr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1351,65 +1513,11 @@ void Application::paintGL() {
|
|||
renderArgs._context->setStereoProjections(eyeProjections);
|
||||
renderArgs._context->setStereoViews(eyeOffsets);
|
||||
}
|
||||
renderArgs._blitFramebuffer = finalFramebuffer;
|
||||
displaySide(&renderArgs, _myCamera);
|
||||
|
||||
renderArgs._blitFramebuffer.reset();
|
||||
renderArgs._context->enableStereo(false);
|
||||
|
||||
// Blit primary to final FBO
|
||||
auto primaryFbo = framebufferCache->getPrimaryFramebuffer();
|
||||
|
||||
if (renderArgs._renderMode == RenderArgs::MIRROR_RENDER_MODE) {
|
||||
if (displayPlugin->isStereo()) {
|
||||
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
|
||||
gpu::Vec4i srcRectLeft;
|
||||
srcRectLeft.z = size.width() / 2;
|
||||
srcRectLeft.w = size.height();
|
||||
|
||||
gpu::Vec4i srcRectRight;
|
||||
srcRectRight.x = size.width() / 2;
|
||||
srcRectRight.z = size.width();
|
||||
srcRectRight.w = size.height();
|
||||
|
||||
gpu::Vec4i destRectLeft;
|
||||
destRectLeft.x = srcRectLeft.z;
|
||||
destRectLeft.z = srcRectLeft.x;
|
||||
destRectLeft.y = srcRectLeft.y;
|
||||
destRectLeft.w = srcRectLeft.w;
|
||||
|
||||
gpu::Vec4i destRectRight;
|
||||
destRectRight.x = srcRectRight.z;
|
||||
destRectRight.z = srcRectRight.x;
|
||||
destRectRight.y = srcRectRight.y;
|
||||
destRectRight.w = srcRectRight.w;
|
||||
|
||||
batch.setFramebuffer(finalFramebuffer);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 1.0f, 0.0f));
|
||||
// BLit left to right and right to left in stereo
|
||||
batch.blit(primaryFbo, srcRectRight, finalFramebuffer, destRectLeft);
|
||||
batch.blit(primaryFbo, srcRectLeft, finalFramebuffer, destRectRight);
|
||||
});
|
||||
} else {
|
||||
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
|
||||
gpu::Vec4i srcRect;
|
||||
srcRect.z = size.width();
|
||||
srcRect.w = size.height();
|
||||
gpu::Vec4i destRect;
|
||||
destRect.x = size.width();
|
||||
destRect.y = 0;
|
||||
destRect.z = 0;
|
||||
destRect.w = size.height();
|
||||
batch.setFramebuffer(finalFramebuffer);
|
||||
batch.blit(primaryFbo, srcRect, finalFramebuffer, destRect);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
|
||||
gpu::Vec4i rect;
|
||||
rect.z = size.width();
|
||||
rect.w = size.height();
|
||||
batch.setFramebuffer(finalFramebuffer);
|
||||
batch.blit(primaryFbo, rect, finalFramebuffer, rect);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay Composition, needs to occur after screen space effects have completed
|
||||
|
@ -1417,7 +1525,9 @@ void Application::paintGL() {
|
|||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/compositor");
|
||||
PerformanceTimer perfTimer("compositor");
|
||||
|
||||
auto primaryFbo = finalFramebuffer;
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFbo));
|
||||
if (displayPlugin->isStereo()) {
|
||||
QRect currentViewport(QPoint(0, 0), QSize(size.width() / 2, size.height()));
|
||||
|
@ -1442,7 +1552,9 @@ void Application::paintGL() {
|
|||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/pluginOutput");
|
||||
PerformanceTimer perfTimer("pluginOutput");
|
||||
|
||||
auto finalTexturePointer = finalFramebuffer->getRenderBuffer(0);
|
||||
|
||||
GLuint finalTexture = gpu::GLBackend::getTextureID(finalTexturePointer);
|
||||
Q_ASSERT(0 != finalTexture);
|
||||
|
||||
|
@ -2570,7 +2682,7 @@ void Application::init() {
|
|||
|
||||
_environment.init();
|
||||
|
||||
DependencyManager::get<DeferredLightingEffect>()->init(this);
|
||||
DependencyManager::get<DeferredLightingEffect>()->init();
|
||||
|
||||
DependencyManager::get<AvatarManager>()->init();
|
||||
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
|
||||
|
@ -3021,8 +3133,7 @@ void Application::update(float deltaTime) {
|
|||
getEntities()->update(); // update the models...
|
||||
}
|
||||
|
||||
myAvatar->harvestResultsFromPhysicsSimulation();
|
||||
myAvatar->simulateAttachments(deltaTime);
|
||||
myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3407,7 +3518,7 @@ QImage Application::renderAvatarBillboard(RenderArgs* renderArgs) {
|
|||
renderArgs->_renderMode = RenderArgs::DEFAULT_RENDER_MODE;
|
||||
renderRearViewMirror(renderArgs, QRect(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE), true);
|
||||
|
||||
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebufferDepthColor();
|
||||
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebuffer();
|
||||
QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32);
|
||||
renderArgs->_context->downloadFramebuffer(primaryFbo, glm::ivec4(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE), image);
|
||||
|
||||
|
@ -3508,78 +3619,86 @@ namespace render {
|
|||
|
||||
// Background rendering decision
|
||||
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
|
||||
if (skyStage->getBackgroundMode() == model::SunSkyStage::NO_BACKGROUND) {
|
||||
auto backgroundMode = skyStage->getBackgroundMode();
|
||||
|
||||
if (backgroundMode == model::SunSkyStage::NO_BACKGROUND) {
|
||||
// this line intentionally left blank
|
||||
} else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_DOME) {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
|
||||
PerformanceTimer perfTimer("stars");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::payloadRender<BackgroundRenderData>() ... stars...");
|
||||
// should be the first rendering pass - w/o depth buffer / lighting
|
||||
|
||||
// compute starfield alpha based on distance from atmosphere
|
||||
float alpha = 1.0f;
|
||||
bool hasStars = true;
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
|
||||
// TODO: handle this correctly for zones
|
||||
const EnvironmentData& closestData = background->_environment->getClosestData(args->_viewFrustum->getPosition()); // was theCamera instead of _viewFrustum
|
||||
|
||||
if (closestData.getHasStars()) {
|
||||
const float APPROXIMATE_DISTANCE_FROM_HORIZON = 0.1f;
|
||||
const float DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON = 0.2f;
|
||||
|
||||
glm::vec3 sunDirection = (args->_viewFrustum->getPosition()/*getAvatarPosition()*/ - closestData.getSunLocation())
|
||||
/ closestData.getAtmosphereOuterRadius();
|
||||
float height = glm::distance(args->_viewFrustum->getPosition()/*theCamera.getPosition()*/, closestData.getAtmosphereCenter());
|
||||
if (height < closestData.getAtmosphereInnerRadius()) {
|
||||
// If we're inside the atmosphere, then determine if our keyLight is below the horizon
|
||||
alpha = 0.0f;
|
||||
|
||||
if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) {
|
||||
float directionY = glm::clamp(sunDirection.y,
|
||||
-APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON)
|
||||
+ APPROXIMATE_DISTANCE_FROM_HORIZON;
|
||||
alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON);
|
||||
}
|
||||
|
||||
|
||||
} else if (height < closestData.getAtmosphereOuterRadius()) {
|
||||
alpha = (height - closestData.getAtmosphereInnerRadius()) /
|
||||
(closestData.getAtmosphereOuterRadius() - closestData.getAtmosphereInnerRadius());
|
||||
|
||||
if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) {
|
||||
float directionY = glm::clamp(sunDirection.y,
|
||||
-APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON)
|
||||
+ APPROXIMATE_DISTANCE_FROM_HORIZON;
|
||||
alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hasStars = false;
|
||||
}
|
||||
} else {
|
||||
if (backgroundMode == model::SunSkyStage::SKY_BOX) {
|
||||
auto skybox = skyStage->getSkybox();
|
||||
if (skybox && skybox->getCubemap() && skybox->getCubemap()->isDefined()) {
|
||||
PerformanceTimer perfTimer("skybox");
|
||||
skybox->render(batch, *(args->_viewFrustum));
|
||||
} else {
|
||||
// If no skybox texture is available, render the SKY_DOME while it loads
|
||||
backgroundMode = model::SunSkyStage::SKY_DOME;
|
||||
}
|
||||
|
||||
// finally render the starfield
|
||||
if (hasStars) {
|
||||
background->_stars.render(args, alpha);
|
||||
}
|
||||
|
||||
// draw the sky dome
|
||||
if (/*!selfAvatarOnly &&*/ Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
|
||||
PerformanceTimer perfTimer("atmosphere");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... atmosphere...");
|
||||
|
||||
background->_environment->renderAtmospheres(batch, *(args->_viewFrustum));
|
||||
}
|
||||
|
||||
}
|
||||
} else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_BOX) {
|
||||
PerformanceTimer perfTimer("skybox");
|
||||
auto skybox = skyStage->getSkybox();
|
||||
if (skybox) {
|
||||
skybox->render(batch, *(args->_viewFrustum));
|
||||
if (backgroundMode == model::SunSkyStage::SKY_DOME) {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
|
||||
PerformanceTimer perfTimer("stars");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::payloadRender<BackgroundRenderData>() ... stars...");
|
||||
// should be the first rendering pass - w/o depth buffer / lighting
|
||||
|
||||
// compute starfield alpha based on distance from atmosphere
|
||||
float alpha = 1.0f;
|
||||
bool hasStars = true;
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
|
||||
// TODO: handle this correctly for zones
|
||||
const EnvironmentData& closestData = background->_environment->getClosestData(args->_viewFrustum->getPosition()); // was theCamera instead of _viewFrustum
|
||||
|
||||
if (closestData.getHasStars()) {
|
||||
const float APPROXIMATE_DISTANCE_FROM_HORIZON = 0.1f;
|
||||
const float DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON = 0.2f;
|
||||
|
||||
glm::vec3 sunDirection = (args->_viewFrustum->getPosition()/*getAvatarPosition()*/ - closestData.getSunLocation())
|
||||
/ closestData.getAtmosphereOuterRadius();
|
||||
float height = glm::distance(args->_viewFrustum->getPosition()/*theCamera.getPosition()*/, closestData.getAtmosphereCenter());
|
||||
if (height < closestData.getAtmosphereInnerRadius()) {
|
||||
// If we're inside the atmosphere, then determine if our keyLight is below the horizon
|
||||
alpha = 0.0f;
|
||||
|
||||
if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) {
|
||||
float directionY = glm::clamp(sunDirection.y,
|
||||
-APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON)
|
||||
+ APPROXIMATE_DISTANCE_FROM_HORIZON;
|
||||
alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON);
|
||||
}
|
||||
|
||||
|
||||
} else if (height < closestData.getAtmosphereOuterRadius()) {
|
||||
alpha = (height - closestData.getAtmosphereInnerRadius()) /
|
||||
(closestData.getAtmosphereOuterRadius() - closestData.getAtmosphereInnerRadius());
|
||||
|
||||
if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) {
|
||||
float directionY = glm::clamp(sunDirection.y,
|
||||
-APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON)
|
||||
+ APPROXIMATE_DISTANCE_FROM_HORIZON;
|
||||
alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hasStars = false;
|
||||
}
|
||||
}
|
||||
|
||||
// finally render the starfield
|
||||
if (hasStars) {
|
||||
background->_stars.render(args, alpha);
|
||||
}
|
||||
|
||||
// draw the sky dome
|
||||
if (/*!selfAvatarOnly &&*/ Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
|
||||
PerformanceTimer perfTimer("atmosphere");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... atmosphere...");
|
||||
|
||||
background->_environment->renderAtmospheres(batch, *(args->_viewFrustum));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3677,34 +3796,19 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
|||
// For now every frame pass the renderContext
|
||||
{
|
||||
PerformanceTimer perfTimer("EngineRun");
|
||||
render::RenderContext renderContext;
|
||||
|
||||
auto sceneInterface = DependencyManager::get<SceneScriptingInterface>();
|
||||
|
||||
renderContext._cullOpaque = sceneInterface->doEngineCullOpaque();
|
||||
renderContext._sortOpaque = sceneInterface->doEngineSortOpaque();
|
||||
renderContext._renderOpaque = sceneInterface->doEngineRenderOpaque();
|
||||
renderContext._cullTransparent = sceneInterface->doEngineCullTransparent();
|
||||
renderContext._sortTransparent = sceneInterface->doEngineSortTransparent();
|
||||
renderContext._renderTransparent = sceneInterface->doEngineRenderTransparent();
|
||||
|
||||
renderContext._maxDrawnOpaqueItems = sceneInterface->getEngineMaxDrawnOpaqueItems();
|
||||
renderContext._maxDrawnTransparentItems = sceneInterface->getEngineMaxDrawnTransparentItems();
|
||||
renderContext._maxDrawnOverlay3DItems = sceneInterface->getEngineMaxDrawnOverlay3DItems();
|
||||
|
||||
renderContext._drawItemStatus = sceneInterface->doEngineDisplayItemStatus();
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowOwned)) {
|
||||
renderContext._drawItemStatus |= render::showNetworkStatusFlag;
|
||||
}
|
||||
renderContext._drawHitEffect = sceneInterface->doEngineDisplayHitEffect();
|
||||
|
||||
renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion);
|
||||
renderContext._fxaaStatus = Menu::getInstance()->isOptionChecked(MenuOption::Antialiasing);
|
||||
auto renderInterface = DependencyManager::get<RenderScriptingInterface>();
|
||||
auto renderContext = renderInterface->getRenderContext();
|
||||
|
||||
renderArgs->_shouldRender = LODManager::shouldRender;
|
||||
|
||||
renderContext.args = renderArgs;
|
||||
renderArgs->_viewFrustum = getDisplayViewFrustum();
|
||||
renderContext.setArgs(renderArgs);
|
||||
|
||||
bool occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion);
|
||||
bool antialiasingStatus = Menu::getInstance()->isOptionChecked(MenuOption::Antialiasing);
|
||||
bool showOwnedStatus = Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowOwned);
|
||||
renderContext.setOptions(occlusionStatus, antialiasingStatus, showOwnedStatus);
|
||||
|
||||
_renderEngine->setRenderContext(renderContext);
|
||||
|
||||
// Before the deferred pass, let's try to use the render engine
|
||||
|
@ -3712,15 +3816,8 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
|||
_renderEngine->run();
|
||||
myAvatar->endRenderRun();
|
||||
|
||||
auto engineRC = _renderEngine->getRenderContext();
|
||||
sceneInterface->setEngineFeedOpaqueItems(engineRC->_numFeedOpaqueItems);
|
||||
sceneInterface->setEngineDrawnOpaqueItems(engineRC->_numDrawnOpaqueItems);
|
||||
|
||||
sceneInterface->setEngineFeedTransparentItems(engineRC->_numFeedTransparentItems);
|
||||
sceneInterface->setEngineDrawnTransparentItems(engineRC->_numDrawnTransparentItems);
|
||||
|
||||
sceneInterface->setEngineFeedOverlay3DItems(engineRC->_numFeedOverlay3DItems);
|
||||
sceneInterface->setEngineDrawnOverlay3DItems(engineRC->_numDrawnOverlay3DItems);
|
||||
auto engineContext = _renderEngine->getRenderContext();
|
||||
renderInterface->setItemCounts(engineContext->getItemsConfig());
|
||||
}
|
||||
|
||||
activeRenderingThread = nullptr;
|
||||
|
@ -4146,6 +4243,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
LocationScriptingInterface::locationSetter);
|
||||
|
||||
scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
|
||||
scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor);
|
||||
scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor);
|
||||
|
||||
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("Stats", Stats::getInstance());
|
||||
|
@ -4174,11 +4273,12 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
|
||||
|
||||
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
||||
scriptEngine->registerGlobalObject("Render", DependencyManager::get<RenderScriptingInterface>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("ScriptDiscoveryService", this->getRunningScriptsWidget());
|
||||
}
|
||||
|
||||
bool Application::canAcceptURL(const QString& urlString) {
|
||||
bool Application::canAcceptURL(const QString& urlString) const {
|
||||
QUrl url(urlString);
|
||||
if (urlString.startsWith(HIFI_URL_SCHEME)) {
|
||||
return true;
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <SimpleMovingAverage.h>
|
||||
#include <StDev.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <AbstractUriHandler.h>
|
||||
|
||||
#include "avatar/AvatarUpdate.h"
|
||||
#include "avatar/MyAvatar.h"
|
||||
|
@ -88,7 +89,7 @@ class Application;
|
|||
#endif
|
||||
#define qApp (static_cast<Application*>(QCoreApplication::instance()))
|
||||
|
||||
class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface {
|
||||
class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface, public AbstractUriHandler {
|
||||
Q_OBJECT
|
||||
|
||||
// TODO? Get rid of those
|
||||
|
@ -219,8 +220,8 @@ public:
|
|||
QString getScriptsLocation();
|
||||
void setScriptsLocation(const QString& scriptsLocation);
|
||||
|
||||
bool canAcceptURL(const QString& url);
|
||||
bool acceptURL(const QString& url, bool defaultUpload = false);
|
||||
virtual bool canAcceptURL(const QString& url) const override;
|
||||
virtual bool acceptURL(const QString& url, bool defaultUpload = false) override;
|
||||
|
||||
void setMaxOctreePacketsPerSecond(int maxOctreePPS);
|
||||
int getMaxOctreePacketsPerSecond();
|
||||
|
|
|
@ -1085,6 +1085,26 @@ void Menu::setGroupingIsVisible(const QString& grouping, bool isVisible) {
|
|||
QMenuBar::repaint();
|
||||
}
|
||||
|
||||
void Menu::addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected) {
|
||||
auto menu = addMenu(groupName);
|
||||
|
||||
QActionGroup* actionGroup = new QActionGroup(menu);
|
||||
actionGroup->setExclusive(true);
|
||||
|
||||
auto menuScriptingInterface = MenuScriptingInterface::getInstance();
|
||||
for (auto action : actionList) {
|
||||
auto item = addCheckableActionToQMenuAndActionHash(menu, action, 0, action == selected,
|
||||
menuScriptingInterface,
|
||||
SLOT(menuItemTriggered()));
|
||||
actionGroup->addAction(item);
|
||||
}
|
||||
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
|
||||
void Menu::removeActionGroup(const QString& groupName) {
|
||||
removeMenu(groupName);
|
||||
}
|
||||
|
||||
MenuWrapper::MenuWrapper(QMenu* menu) : _realMenu(menu) {
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
|
|
|
@ -106,6 +106,8 @@ public slots:
|
|||
void addMenuItem(const MenuItemProperties& properties);
|
||||
void removeMenuItem(const QString& menuName, const QString& menuitem);
|
||||
bool menuItemExists(const QString& menuName, const QString& menuitem);
|
||||
void addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected = QString());
|
||||
void removeActionGroup(const QString& groupName);
|
||||
bool isOptionChecked(const QString& menuOption) const;
|
||||
void setIsOptionChecked(const QString& menuOption, bool isChecked);
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "Util.h"
|
||||
#include "world.h"
|
||||
#include "InterfaceLogging.h"
|
||||
#include "SoftAttachmentModel.h"
|
||||
#include <Rig.h>
|
||||
|
||||
using namespace std;
|
||||
|
@ -108,9 +109,6 @@ Avatar::Avatar(RigPointer rig) :
|
|||
|
||||
Avatar::~Avatar() {
|
||||
assert(_motionState == nullptr);
|
||||
for(auto attachment : _unusedAttachments) {
|
||||
delete attachment;
|
||||
}
|
||||
}
|
||||
|
||||
const float BILLBOARD_LOD_DISTANCE = 40.0f;
|
||||
|
@ -237,6 +235,8 @@ void Avatar::simulate(float deltaTime) {
|
|||
// until velocity is included in AvatarData update message.
|
||||
//_position += _velocity * deltaTime;
|
||||
measureMotionDerivatives(deltaTime);
|
||||
|
||||
simulateAttachments(deltaTime);
|
||||
}
|
||||
|
||||
bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const {
|
||||
|
@ -304,7 +304,7 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene>
|
|||
_skeletonModel.addToScene(scene, pendingChanges);
|
||||
getHead()->getFaceModel().addToScene(scene, pendingChanges);
|
||||
|
||||
for (auto attachmentModel : _attachmentModels) {
|
||||
for (auto& attachmentModel : _attachmentModels) {
|
||||
attachmentModel->addToScene(scene, pendingChanges);
|
||||
}
|
||||
|
||||
|
@ -315,7 +315,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::S
|
|||
pendingChanges.removeItem(_renderItemID);
|
||||
_skeletonModel.removeFromScene(scene, pendingChanges);
|
||||
getHead()->getFaceModel().removeFromScene(scene, pendingChanges);
|
||||
for (auto attachmentModel : _attachmentModels) {
|
||||
for (auto& attachmentModel : _attachmentModels) {
|
||||
attachmentModel->removeFromScene(scene, pendingChanges);
|
||||
}
|
||||
}
|
||||
|
@ -545,15 +545,14 @@ void Avatar::fixupModelsInScene() {
|
|||
faceModel.removeFromScene(scene, pendingChanges);
|
||||
faceModel.addToScene(scene, pendingChanges);
|
||||
}
|
||||
for (auto attachmentModel : _attachmentModels) {
|
||||
for (auto& attachmentModel : _attachmentModels) {
|
||||
if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) {
|
||||
attachmentModel->removeFromScene(scene, pendingChanges);
|
||||
attachmentModel->addToScene(scene, pendingChanges);
|
||||
}
|
||||
}
|
||||
for (auto attachmentModelToRemove : _attachmentsToRemove) {
|
||||
for (auto& attachmentModelToRemove : _attachmentsToRemove) {
|
||||
attachmentModelToRemove->removeFromScene(scene, pendingChanges);
|
||||
_unusedAttachments << attachmentModelToRemove;
|
||||
}
|
||||
_attachmentsToRemove.clear();
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
|
@ -583,21 +582,29 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Avatar::simulateAttachments(float deltaTime) {
|
||||
for (int i = 0; i < _attachmentModels.size(); i++) {
|
||||
const AttachmentData& attachment = _attachmentData.at(i);
|
||||
Model* model = _attachmentModels.at(i);
|
||||
auto& model = _attachmentModels.at(i);
|
||||
int jointIndex = getJointIndex(attachment.jointName);
|
||||
glm::vec3 jointPosition;
|
||||
glm::quat jointRotation;
|
||||
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
|
||||
_skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) {
|
||||
model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale());
|
||||
model->setRotation(jointRotation * attachment.rotation);
|
||||
model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale
|
||||
model->setSnapModelToCenter(false); // hack to force resnap
|
||||
model->setSnapModelToCenter(true);
|
||||
if (attachment.isSoft) {
|
||||
// soft attachments do not have transform offsets
|
||||
model->setTranslation(getPosition());
|
||||
model->setRotation(getOrientation() * Quaternions::Y_180);
|
||||
model->simulate(deltaTime);
|
||||
} else {
|
||||
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
|
||||
_skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) {
|
||||
model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale());
|
||||
model->setRotation(jointRotation * attachment.rotation);
|
||||
model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale
|
||||
model->setSnapModelToCenter(false); // hack to force resnap
|
||||
model->setSnapModelToCenter(true);
|
||||
model->simulate(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -920,13 +927,48 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
_skeletonModel.setURL(_skeletonModelURL);
|
||||
}
|
||||
|
||||
// create new model, can return an instance of a SoftAttachmentModel rather then Model
|
||||
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride) {
|
||||
if (isSoft) {
|
||||
// cast to std::shared_ptr<Model>
|
||||
return std::dynamic_pointer_cast<Model>(std::make_shared<SoftAttachmentModel>(std::make_shared<Rig>(), nullptr, rigOverride));
|
||||
} else {
|
||||
return std::make_shared<Model>(std::make_shared<Rig>());
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
AvatarData::setAttachmentData(attachmentData);
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection,
|
||||
Q_ARG(const QVector<AttachmentData>, attachmentData));
|
||||
return;
|
||||
}
|
||||
|
||||
auto oldAttachmentData = _attachmentData;
|
||||
AvatarData::setAttachmentData(attachmentData);
|
||||
|
||||
// if number of attachments has been reduced, remove excess models.
|
||||
while (_attachmentModels.size() > attachmentData.size()) {
|
||||
auto attachmentModel = _attachmentModels.back();
|
||||
_attachmentModels.pop_back();
|
||||
_attachmentsToRemove.push_back(attachmentModel);
|
||||
}
|
||||
|
||||
for (int i = 0; i < attachmentData.size(); i++) {
|
||||
if (i == _attachmentModels.size()) {
|
||||
// if number of attachments has been increased, we need to allocate a new model
|
||||
_attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig()));
|
||||
}
|
||||
else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) {
|
||||
// if the attachment has changed type, we need to re-allocate a new one.
|
||||
_attachmentsToRemove.push_back(_attachmentModels[i]);
|
||||
_attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig());
|
||||
}
|
||||
_attachmentModels[i]->setURL(attachmentData[i].modelURL);
|
||||
}
|
||||
|
||||
// AJT: TODO REMOVE
|
||||
/*
|
||||
// make sure we have as many models as attachments
|
||||
while (_attachmentModels.size() < attachmentData.size()) {
|
||||
Model* model = nullptr;
|
||||
|
@ -939,16 +981,20 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
|||
_attachmentModels.append(model);
|
||||
}
|
||||
while (_attachmentModels.size() > attachmentData.size()) {
|
||||
auto attachmentModel = _attachmentModels.takeLast();
|
||||
_attachmentsToRemove << attachmentModel;
|
||||
auto attachmentModel = _attachmentModels.back();
|
||||
_attachmentModels.pop_back();
|
||||
_attachmentsToRemove.push_back(attachmentModel);
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// update the urls
|
||||
for (int i = 0; i < attachmentData.size(); i++) {
|
||||
_attachmentModels[i]->setURL(attachmentData.at(i).modelURL);
|
||||
_attachmentModels[i]->setSnapModelToCenter(true);
|
||||
_attachmentModels[i]->setScaleToFit(true, getUniformScale() * _attachmentData.at(i).scale);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void Avatar::setBillboard(const QByteArray& billboard) {
|
||||
|
|
|
@ -68,7 +68,7 @@ public:
|
|||
|
||||
void init();
|
||||
void simulate(float deltaTime);
|
||||
void simulateAttachments(float deltaTime);
|
||||
virtual void simulateAttachments(float deltaTime);
|
||||
|
||||
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition);
|
||||
|
||||
|
@ -177,9 +177,9 @@ protected:
|
|||
|
||||
SkeletonModel _skeletonModel;
|
||||
glm::vec3 _skeletonOffset;
|
||||
QVector<Model*> _attachmentModels;
|
||||
QVector<Model*> _attachmentsToRemove;
|
||||
QVector<Model*> _unusedAttachments;
|
||||
std::vector<std::shared_ptr<Model>> _attachmentModels;
|
||||
std::vector<std::shared_ptr<Model>> _attachmentsToRemove;
|
||||
|
||||
float _bodyYawDelta; // degrees/sec
|
||||
|
||||
// These position histories and derivatives are in the world-frame.
|
||||
|
|
|
@ -269,8 +269,8 @@ QVector<QUuid> AvatarManager::getAvatarIdentifiers() {
|
|||
}
|
||||
|
||||
AvatarData* AvatarManager::getAvatar(QUuid avatarID) {
|
||||
QReadLocker locker(&_hashLock);
|
||||
return _avatarHash[avatarID].get(); // Non-obvious: A bogus avatarID answers your own avatar.
|
||||
// Null/Default-constructed QUuids will return MyAvatar
|
||||
return getAvatarBySessionID(avatarID).get();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -201,6 +201,11 @@ MyAvatar::~MyAvatar() {
|
|||
_lookAtTargetAvatar.reset();
|
||||
}
|
||||
|
||||
// virtual
|
||||
void MyAvatar::simulateAttachments(float deltaTime) {
|
||||
// don't update attachments here, do it in harvestResultsFromPhysicsSimulation()
|
||||
}
|
||||
|
||||
QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
|
||||
CameraMode mode = qApp->getCamera()->getMode();
|
||||
_globalPosition = getPosition();
|
||||
|
@ -621,6 +626,7 @@ void MyAvatar::saveData() {
|
|||
settings.setValue("rotation_y", eulers.y);
|
||||
settings.setValue("rotation_z", eulers.z);
|
||||
settings.setValue("scale", attachment.scale);
|
||||
settings.setValue("isSoft", attachment.isSoft);
|
||||
}
|
||||
settings.endArray();
|
||||
|
||||
|
@ -702,6 +708,7 @@ void MyAvatar::loadData() {
|
|||
eulers.z = loadSetting(settings, "rotation_z", 0.0f);
|
||||
attachment.rotation = glm::quat(eulers);
|
||||
attachment.scale = loadSetting(settings, "scale", 1.0f);
|
||||
attachment.isSoft = settings.value("isSoft").toBool();
|
||||
attachmentData.append(attachment);
|
||||
}
|
||||
settings.endArray();
|
||||
|
@ -1057,7 +1064,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
|||
_characterController.setFollowVelocity(_followVelocity);
|
||||
}
|
||||
|
||||
void MyAvatar::harvestResultsFromPhysicsSimulation() {
|
||||
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaType) {
|
||||
glm::vec3 position = getPosition();
|
||||
glm::quat orientation = getOrientation();
|
||||
_characterController.getPositionAndOrientation(position, orientation);
|
||||
|
@ -1068,6 +1075,9 @@ void MyAvatar::harvestResultsFromPhysicsSimulation() {
|
|||
} else {
|
||||
setVelocity(_characterController.getLinearVelocity());
|
||||
}
|
||||
|
||||
// now that physics has adjusted our position, we can update attachements.
|
||||
Avatar::simulateAttachments(deltaType);
|
||||
}
|
||||
|
||||
void MyAvatar::adjustSensorTransform() {
|
||||
|
@ -1599,7 +1609,7 @@ void MyAvatar::maybeUpdateBillboard() {
|
|||
if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) {
|
||||
return;
|
||||
}
|
||||
foreach (Model* model, _attachmentModels) {
|
||||
for (auto& model : _attachmentModels) {
|
||||
if (!model->isLoadedWithTextures()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -83,6 +83,8 @@ public:
|
|||
MyAvatar(RigPointer rig);
|
||||
~MyAvatar();
|
||||
|
||||
virtual void simulateAttachments(float deltaTime) override;
|
||||
|
||||
AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; }
|
||||
AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; }
|
||||
AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; }
|
||||
|
@ -204,7 +206,7 @@ public:
|
|||
MyCharacterController* getCharacterController() { return &_characterController; }
|
||||
|
||||
void prepareForPhysicsSimulation();
|
||||
void harvestResultsFromPhysicsSimulation();
|
||||
void harvestResultsFromPhysicsSimulation(float deltaTime);
|
||||
void adjustSensorTransform();
|
||||
|
||||
const QString& getCollisionSoundURL() { return _collisionSoundURL; }
|
||||
|
|
84
interface/src/avatar/SoftAttachmentModel.cpp
Normal file
84
interface/src/avatar/SoftAttachmentModel.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// SoftAttachmentModel.cpp
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Created by Anthony J. Thibault on 12/17/15.
|
||||
// Copyright 2013 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 "SoftAttachmentModel.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
||||
SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) :
|
||||
Model(rig, parent),
|
||||
_rigOverride(rigOverride) {
|
||||
assert(_rig);
|
||||
assert(_rigOverride);
|
||||
}
|
||||
|
||||
SoftAttachmentModel::~SoftAttachmentModel() {
|
||||
}
|
||||
|
||||
// virtual
|
||||
void SoftAttachmentModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
_needsUpdateClusterMatrices = true;
|
||||
}
|
||||
|
||||
int SoftAttachmentModel::getJointIndexOverride(int i) const {
|
||||
QString name = _rig->nameOfJoint(i);
|
||||
if (name.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
return _rigOverride->indexOfJoint(name);
|
||||
}
|
||||
|
||||
// virtual
|
||||
// use the _rigOverride matrices instead of the Model::_rig
|
||||
void SoftAttachmentModel::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) {
|
||||
if (!_needsUpdateClusterMatrices) {
|
||||
return;
|
||||
}
|
||||
_needsUpdateClusterMatrices = false;
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
||||
glm::mat4 modelToWorld = glm::mat4_cast(modelOrientation);
|
||||
for (int i = 0; i < _meshStates.size(); i++) {
|
||||
MeshState& state = _meshStates[i];
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||
|
||||
// TODO: cache these look ups as an optimization
|
||||
int jointIndexOverride = getJointIndexOverride(cluster.jointIndex);
|
||||
glm::mat4 jointMatrix(glm::mat4::_null);
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride->getJointStateCount()) {
|
||||
jointMatrix = _rigOverride->getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig->getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
|
||||
}
|
||||
|
||||
// Once computed the cluster matrices, update the buffer(s)
|
||||
if (mesh.clusters.size() > 1) {
|
||||
if (!state.clusterBuffer) {
|
||||
state.clusterBuffer = std::make_shared<gpu::Buffer>(state.clusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) state.clusterMatrices.constData());
|
||||
} else {
|
||||
state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) state.clusterMatrices.constData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// post the blender if we're not currently waiting for one to finish
|
||||
if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
|
||||
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
|
||||
DependencyManager::get<ModelBlender>()->noteRequiresBlend(this);
|
||||
}
|
||||
}
|
42
interface/src/avatar/SoftAttachmentModel.h
Normal file
42
interface/src/avatar/SoftAttachmentModel.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// SoftAttachmentModel.h
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Created by Anthony J. Thibault on 12/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_SoftAttachmentModel_h
|
||||
#define hifi_SoftAttachmentModel_h
|
||||
|
||||
#include <Model.h>
|
||||
|
||||
// A model that allows the creator to specify a secondary rig instance.
|
||||
// When the cluster matrices are created for rendering, the
|
||||
// cluster matrices will use the secondary rig for the joint poses
|
||||
// instead of the primary rig.
|
||||
//
|
||||
// This is used by Avatar instances to wear clothing that follows the same
|
||||
// animated pose as the SkeletonModel.
|
||||
|
||||
class SoftAttachmentModel : public Model {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride);
|
||||
~SoftAttachmentModel();
|
||||
|
||||
virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override;
|
||||
virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) override;
|
||||
|
||||
protected:
|
||||
int getJointIndexOverride(int i) const;
|
||||
|
||||
RigPointer _rigOverride;
|
||||
};
|
||||
|
||||
#endif // hifi_SoftAttachmentModel_h
|
|
@ -17,7 +17,6 @@ AccountScriptingInterface::AccountScriptingInterface() {
|
|||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
connect(&accountManager, &AccountManager::balanceChanged, this,
|
||||
&AccountScriptingInterface::updateBalance);
|
||||
|
||||
}
|
||||
|
||||
AccountScriptingInterface* AccountScriptingInterface::getInstance() {
|
||||
|
@ -39,3 +38,12 @@ void AccountScriptingInterface::updateBalance() {
|
|||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
emit balanceChanged(accountManager.getAccountInfo().getBalanceInSatoshis());
|
||||
}
|
||||
|
||||
QString AccountScriptingInterface::getUsername() {
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
if (accountManager.isLoggedIn()) {
|
||||
return accountManager.getAccountInfo().getUsername();
|
||||
} else {
|
||||
return "Unknown user";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ signals:
|
|||
public slots:
|
||||
static AccountScriptingInterface* getInstance();
|
||||
float getBalance();
|
||||
QString getUsername();
|
||||
bool isLoggedIn();
|
||||
void updateBalance();
|
||||
};
|
||||
|
|
|
@ -84,6 +84,19 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString&
|
|||
return result;
|
||||
}
|
||||
|
||||
void MenuScriptingInterface::addActionGroup(const QString& groupName, const QStringList& actionList,
|
||||
const QString& selected) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "addActionGroup",
|
||||
Q_ARG(const QString&, groupName),
|
||||
Q_ARG(const QStringList&, actionList),
|
||||
Q_ARG(const QString&, selected));
|
||||
}
|
||||
|
||||
void MenuScriptingInterface::removeActionGroup(const QString& groupName) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "removeActionGroup",
|
||||
Q_ARG(const QString&, groupName));
|
||||
}
|
||||
|
||||
bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) {
|
||||
bool result;
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "isOptionChecked", Qt::BlockingQueuedConnection,
|
||||
|
|
|
@ -42,6 +42,10 @@ public slots:
|
|||
void removeMenuItem(const QString& menuName, const QString& menuitem);
|
||||
bool menuItemExists(const QString& menuName, const QString& menuitem);
|
||||
|
||||
void addActionGroup(const QString& groupName, const QStringList& actionList,
|
||||
const QString& selected = QString());
|
||||
void removeActionGroup(const QString& groupName);
|
||||
|
||||
bool isOptionChecked(const QString& menuOption);
|
||||
void setIsOptionChecked(const QString& menuOption, bool isChecked);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QCheckBox>
|
||||
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <avatar/MyAvatar.h>
|
||||
|
@ -27,13 +28,13 @@
|
|||
|
||||
AttachmentsDialog::AttachmentsDialog(QWidget* parent) :
|
||||
QDialog(parent) {
|
||||
|
||||
|
||||
setWindowTitle("Edit Attachments");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
setLayout(layout);
|
||||
|
||||
|
||||
QScrollArea* area = new QScrollArea();
|
||||
layout->addWidget(area);
|
||||
area->setWidgetResizable(true);
|
||||
|
@ -42,26 +43,26 @@ AttachmentsDialog::AttachmentsDialog(QWidget* parent) :
|
|||
container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
||||
area->setWidget(container);
|
||||
_attachments->addStretch(1);
|
||||
|
||||
|
||||
foreach (const AttachmentData& data, DependencyManager::get<AvatarManager>()->getMyAvatar()->getAttachmentData()) {
|
||||
addAttachment(data);
|
||||
}
|
||||
|
||||
|
||||
QPushButton* newAttachment = new QPushButton("New Attachment");
|
||||
connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment()));
|
||||
layout->addWidget(newAttachment);
|
||||
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
layout->addWidget(buttons);
|
||||
connect(buttons, SIGNAL(accepted()), SLOT(deleteLater()));
|
||||
_ok = buttons->button(QDialogButtonBox::Ok);
|
||||
|
||||
|
||||
setMinimumSize(600, 600);
|
||||
}
|
||||
|
||||
void AttachmentsDialog::setVisible(bool visible) {
|
||||
QDialog::setVisible(visible);
|
||||
|
||||
|
||||
// un-default the OK button
|
||||
if (visible) {
|
||||
_ok->setDefault(false);
|
||||
|
@ -104,11 +105,11 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
|
|||
_dialog(dialog),
|
||||
_applying(false) {
|
||||
setFrameStyle(QFrame::StyledPanel);
|
||||
|
||||
|
||||
QFormLayout* layout = new QFormLayout();
|
||||
layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
setLayout(layout);
|
||||
|
||||
|
||||
QHBoxLayout* urlBox = new QHBoxLayout();
|
||||
layout->addRow("Model URL:", urlBox);
|
||||
urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1);
|
||||
|
@ -117,7 +118,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
|
|||
QPushButton* chooseURL = new QPushButton("Choose");
|
||||
urlBox->addWidget(chooseURL);
|
||||
connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL()));
|
||||
|
||||
|
||||
layout->addRow("Joint:", _jointName = new QComboBox());
|
||||
QSharedPointer<NetworkGeometry> geometry = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSkeletonModel().getGeometry();
|
||||
if (geometry && geometry->isLoaded()) {
|
||||
|
@ -127,26 +128,30 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
|
|||
}
|
||||
_jointName->setCurrentText(data.jointName);
|
||||
connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(jointNameChanged()));
|
||||
|
||||
|
||||
QHBoxLayout* translationBox = new QHBoxLayout();
|
||||
translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x));
|
||||
translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y));
|
||||
translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z));
|
||||
layout->addRow("Translation:", translationBox);
|
||||
|
||||
|
||||
QHBoxLayout* rotationBox = new QHBoxLayout();
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation));
|
||||
rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x));
|
||||
rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y));
|
||||
rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z));
|
||||
layout->addRow("Rotation:", rotationBox);
|
||||
|
||||
|
||||
layout->addRow("Scale:", _scale = new QDoubleSpinBox());
|
||||
_scale->setSingleStep(0.01);
|
||||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setValue(data.scale);
|
||||
connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
|
||||
|
||||
layout->addRow("Is Soft:", _isSoft = new QCheckBox());
|
||||
_isSoft->setChecked(data.isSoft);
|
||||
connect(_isSoft, SIGNAL(stateChanged(int)), SLOT(updateAttachmentData()));
|
||||
|
||||
QPushButton* remove = new QPushButton("Delete");
|
||||
layout->addRow(remove);
|
||||
connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater()));
|
||||
|
@ -160,6 +165,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const {
|
|||
data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value());
|
||||
data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value())));
|
||||
data.scale = _scale->value();
|
||||
data.isSoft = _isSoft->isChecked();
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -227,6 +233,7 @@ void AttachmentPanel::applyAttachmentData(const AttachmentData& attachment) {
|
|||
_rotationY->setValue(eulers.y);
|
||||
_rotationZ->setValue(eulers.z);
|
||||
_scale->setValue(attachment.scale);
|
||||
_isSoft->setChecked(attachment.isSoft);
|
||||
_applying = false;
|
||||
_dialog->updateAttachmentData();
|
||||
}
|
||||
|
|
|
@ -36,11 +36,11 @@ public slots:
|
|||
void updateAttachmentData();
|
||||
|
||||
private slots:
|
||||
|
||||
|
||||
void addAttachment(const AttachmentData& data = AttachmentData());
|
||||
|
||||
private:
|
||||
|
||||
|
||||
QVBoxLayout* _attachments;
|
||||
QPushButton* _ok;
|
||||
};
|
||||
|
@ -50,7 +50,7 @@ class AttachmentPanel : public QFrame {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
|
||||
AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData());
|
||||
|
||||
AttachmentData getAttachmentData() const;
|
||||
|
@ -64,9 +64,9 @@ private slots:
|
|||
void updateAttachmentData();
|
||||
|
||||
private:
|
||||
|
||||
|
||||
void applyAttachmentData(const AttachmentData& attachment);
|
||||
|
||||
|
||||
AttachmentsDialog* _dialog;
|
||||
QLineEdit* _modelURL;
|
||||
QComboBox* _jointName;
|
||||
|
@ -77,6 +77,7 @@ private:
|
|||
QDoubleSpinBox* _rotationY;
|
||||
QDoubleSpinBox* _rotationZ;
|
||||
QDoubleSpinBox* _scale;
|
||||
QCheckBox* _isSoft;
|
||||
bool _applying;
|
||||
};
|
||||
|
||||
|
|
676
libraries/animation/src/AnimExpression.cpp
Normal file
676
libraries/animation/src/AnimExpression.cpp
Normal file
|
@ -0,0 +1,676 @@
|
|||
//
|
||||
// AnimExpression.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 11/1/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <StreamUtils.h>
|
||||
#include <QRegExp>
|
||||
|
||||
#include "AnimExpression.h"
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
AnimExpression::AnimExpression(const QString& str) :
|
||||
_expression(str) {
|
||||
auto iter = str.begin();
|
||||
parseExpr(_expression, iter);
|
||||
while(!_tokenStack.empty()) {
|
||||
_tokenStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Tokenizer
|
||||
//
|
||||
|
||||
void AnimExpression::unconsumeToken(const Token& token) {
|
||||
_tokenStack.push(token);
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeToken(const QString& str, QString::const_iterator& iter) const {
|
||||
if (!_tokenStack.empty()) {
|
||||
Token top = _tokenStack.top();
|
||||
_tokenStack.pop();
|
||||
return top;
|
||||
} else {
|
||||
while (iter != str.end()) {
|
||||
if (iter->isSpace()) {
|
||||
++iter;
|
||||
} else if (iter->isLetter()) {
|
||||
return consumeIdentifier(str, iter);
|
||||
} else if (iter->isDigit()) {
|
||||
return consumeNumber(str, iter);
|
||||
} else {
|
||||
switch (iter->unicode()) {
|
||||
case '&': return consumeAnd(str, iter);
|
||||
case '|': return consumeOr(str, iter);
|
||||
case '>': return consumeGreaterThan(str, iter);
|
||||
case '<': return consumeLessThan(str, iter);
|
||||
case '(': ++iter; return Token(Token::LeftParen);
|
||||
case ')': ++iter; return Token(Token::RightParen);
|
||||
case '!': return consumeNot(str, iter);
|
||||
case '-': ++iter; return Token(Token::Minus);
|
||||
case '+': ++iter; return Token(Token::Plus);
|
||||
case '*': ++iter; return Token(Token::Multiply);
|
||||
case '/': ++iter; return Token(Token::Divide);
|
||||
case '%': ++iter; return Token(Token::Modulus);
|
||||
case ',': ++iter; return Token(Token::Comma);
|
||||
default:
|
||||
qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin());
|
||||
return Token(Token::Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Token(Token::End);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeIdentifier(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->isLetter());
|
||||
auto begin = iter;
|
||||
while ((iter->isLetter() || iter->isDigit()) && iter != str.end()) {
|
||||
++iter;
|
||||
}
|
||||
int pos = (int)(begin - str.begin());
|
||||
int len = (int)(iter - begin);
|
||||
|
||||
QStringRef stringRef(const_cast<const QString*>(&str), pos, len);
|
||||
if (stringRef == "true") {
|
||||
return Token(true);
|
||||
} else if (stringRef == "false") {
|
||||
return Token(false);
|
||||
} else {
|
||||
return Token(stringRef);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: not very efficient or accruate, but it's close enough for now.
|
||||
static float computeFractionalPart(int fractionalPart)
|
||||
{
|
||||
float frac = (float)fractionalPart;
|
||||
while (fractionalPart) {
|
||||
fractionalPart /= 10;
|
||||
frac /= 10.0f;
|
||||
}
|
||||
return frac;
|
||||
}
|
||||
|
||||
static float computeFloat(int whole, int fraction) {
|
||||
return (float)whole + computeFractionalPart(fraction);
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeNumber(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->isDigit());
|
||||
auto begin = iter;
|
||||
while (iter->isDigit() && iter != str.end()) {
|
||||
++iter;
|
||||
}
|
||||
|
||||
// parse whole integer part
|
||||
int pos = (int)(begin - str.begin());
|
||||
int len = (int)(iter - begin);
|
||||
QString sub = QStringRef(const_cast<const QString*>(&str), pos, len).toString();
|
||||
int whole = sub.toInt();
|
||||
|
||||
// parse optional fractional part
|
||||
if (iter->unicode() == '.') {
|
||||
iter++;
|
||||
auto begin = iter;
|
||||
while (iter->isDigit() && iter != str.end()) {
|
||||
++iter;
|
||||
}
|
||||
|
||||
int pos = (int)(begin - str.begin());
|
||||
int len = (int)(iter - begin);
|
||||
QString sub = QStringRef(const_cast<const QString*>(&str), pos, len).toString();
|
||||
int fraction = sub.toInt();
|
||||
|
||||
return Token(computeFloat(whole, fraction));
|
||||
|
||||
} else {
|
||||
return Token(whole);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeAnd(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '&');
|
||||
iter++;
|
||||
if (iter->unicode() == '&') {
|
||||
iter++;
|
||||
return Token(Token::And);
|
||||
} else {
|
||||
qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin());
|
||||
return Token(Token::Error);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeOr(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '|');
|
||||
iter++;
|
||||
if (iter->unicode() == '|') {
|
||||
iter++;
|
||||
return Token(Token::Or);
|
||||
} else {
|
||||
qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin());
|
||||
return Token(Token::Error);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeGreaterThan(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '>');
|
||||
iter++;
|
||||
if (iter->unicode() == '=') {
|
||||
iter++;
|
||||
return Token(Token::GreaterThanEqual);
|
||||
} else {
|
||||
return Token(Token::GreaterThan);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeLessThan(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '<');
|
||||
iter++;
|
||||
if (iter->unicode() == '=') {
|
||||
iter++;
|
||||
return Token(Token::LessThanEqual);
|
||||
} else {
|
||||
return Token(Token::LessThan);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeNot(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '!');
|
||||
iter++;
|
||||
if (iter->unicode() == '=') {
|
||||
iter++;
|
||||
return Token(Token::NotEqual);
|
||||
} else {
|
||||
return Token(Token::Not);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Parser
|
||||
//
|
||||
|
||||
/*
|
||||
Expr → Term Expr'
|
||||
Expr' → '||' Term Expr'
|
||||
| ε
|
||||
Term → Unary Term'
|
||||
Term' → '&&' Unary Term'
|
||||
| ε
|
||||
Unary → '!' Unary
|
||||
| Factor
|
||||
Factor → INT
|
||||
| BOOL
|
||||
| FLOAT
|
||||
| IDENTIFIER
|
||||
| '(' Expr ')'
|
||||
*/
|
||||
|
||||
// Expr → Term Expr'
|
||||
bool AnimExpression::parseExpr(const QString& str, QString::const_iterator& iter) {
|
||||
if (!parseTerm(str, iter)) {
|
||||
return false;
|
||||
}
|
||||
if (!parseExprPrime(str, iter)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Expr' → '||' Term Expr' | ε
|
||||
bool AnimExpression::parseExprPrime(const QString& str, QString::const_iterator& iter) {
|
||||
auto token = consumeToken(str, iter);
|
||||
if (token.type == Token::Or) {
|
||||
if (!parseTerm(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
if (!parseExprPrime(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
_opCodes.push_back(OpCode {OpCode::Or});
|
||||
return true;
|
||||
} else {
|
||||
unconsumeToken(token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Term → Unary Term'
|
||||
bool AnimExpression::parseTerm(const QString& str, QString::const_iterator& iter) {
|
||||
if (!parseUnary(str, iter)) {
|
||||
return false;
|
||||
}
|
||||
if (!parseTermPrime(str, iter)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Term' → '&&' Unary Term' | ε
|
||||
bool AnimExpression::parseTermPrime(const QString& str, QString::const_iterator& iter) {
|
||||
auto token = consumeToken(str, iter);
|
||||
if (token.type == Token::And) {
|
||||
if (!parseUnary(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
if (!parseTermPrime(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
_opCodes.push_back(OpCode {OpCode::And});
|
||||
return true;
|
||||
} else {
|
||||
unconsumeToken(token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Unary → '!' Unary | Factor
|
||||
bool AnimExpression::parseUnary(const QString& str, QString::const_iterator& iter) {
|
||||
|
||||
auto token = consumeToken(str, iter);
|
||||
if (token.type == Token::Not) {
|
||||
if (!parseUnary(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
_opCodes.push_back(OpCode {OpCode::Not});
|
||||
return true;
|
||||
}
|
||||
unconsumeToken(token);
|
||||
|
||||
return parseFactor(str, iter);
|
||||
}
|
||||
|
||||
|
||||
// Factor → INT | BOOL | FLOAT | IDENTIFIER | '(' Expr ')'
|
||||
bool AnimExpression::parseFactor(const QString& str, QString::const_iterator& iter) {
|
||||
auto token = consumeToken(str, iter);
|
||||
if (token.type == Token::Int) {
|
||||
_opCodes.push_back(OpCode {token.intVal});
|
||||
return true;
|
||||
} else if (token.type == Token::Bool) {
|
||||
_opCodes.push_back(OpCode {(bool)token.intVal});
|
||||
return true;
|
||||
} else if (token.type == Token::Float) {
|
||||
_opCodes.push_back(OpCode {token.floatVal});
|
||||
return true;
|
||||
} else if (token.type == Token::Identifier) {
|
||||
_opCodes.push_back(OpCode {token.strVal});
|
||||
return true;
|
||||
} else if (token.type == Token::LeftParen) {
|
||||
if (!parseExpr(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
auto nextToken = consumeToken(str, iter);
|
||||
if (nextToken.type != Token::RightParen) {
|
||||
unconsumeToken(nextToken);
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Evaluator
|
||||
//
|
||||
|
||||
AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const {
|
||||
std::stack<OpCode> stack;
|
||||
for (auto& opCode : _opCodes) {
|
||||
switch (opCode.type) {
|
||||
case OpCode::Identifier:
|
||||
case OpCode::Int:
|
||||
case OpCode::Float:
|
||||
case OpCode::Bool:
|
||||
stack.push(opCode);
|
||||
break;
|
||||
case OpCode::And: evalAnd(map, stack); break;
|
||||
case OpCode::Or: evalOr(map, stack); break;
|
||||
case OpCode::GreaterThan: evalGreaterThan(map, stack); break;
|
||||
case OpCode::GreaterThanEqual: evalGreaterThanEqual(map, stack); break;
|
||||
case OpCode::LessThan: evalLessThan(map, stack); break;
|
||||
case OpCode::LessThanEqual: evalLessThanEqual(map, stack); break;
|
||||
case OpCode::Equal: evalEqual(map, stack); break;
|
||||
case OpCode::NotEqual: evalNotEqual(map, stack); break;
|
||||
case OpCode::Not: evalNot(map, stack); break;
|
||||
case OpCode::Subtract: evalSubtract(map, stack); break;
|
||||
case OpCode::Add: evalAdd(map, stack); break;
|
||||
case OpCode::Multiply: evalMultiply(map, stack); break;
|
||||
case OpCode::Divide: evalDivide(map, stack); break;
|
||||
case OpCode::Modulus: evalModulus(map, stack); break;
|
||||
case OpCode::UnaryMinus: evalUnaryMinus(map, stack); break;
|
||||
}
|
||||
}
|
||||
return coerseToValue(map, stack.top());
|
||||
}
|
||||
|
||||
#define POP_BOOL(NAME) \
|
||||
const OpCode& NAME##_temp = stack.top(); \
|
||||
bool NAME = NAME##_temp.coerceBool(map); \
|
||||
stack.pop()
|
||||
|
||||
#define PUSH(EXPR) \
|
||||
stack.push(OpCode {(EXPR)})
|
||||
|
||||
void AnimExpression::evalAnd(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
POP_BOOL(lhs);
|
||||
POP_BOOL(rhs);
|
||||
PUSH(lhs && rhs);
|
||||
}
|
||||
|
||||
void AnimExpression::evalOr(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
POP_BOOL(lhs);
|
||||
POP_BOOL(rhs);
|
||||
PUSH(lhs || rhs);
|
||||
}
|
||||
|
||||
void AnimExpression::evalGreaterThan(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalGreaterThanEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalLessThan(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalLessThanEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalNotEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalNot(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
POP_BOOL(rhs);
|
||||
PUSH(!rhs);
|
||||
}
|
||||
|
||||
void AnimExpression::evalSubtract(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(0.0f);
|
||||
}
|
||||
|
||||
void AnimExpression::add(int lhs, const OpCode& rhs, std::stack<OpCode>& stack) const {
|
||||
switch (rhs.type) {
|
||||
case OpCode::Bool:
|
||||
case OpCode::Int:
|
||||
PUSH(lhs + rhs.intVal);
|
||||
break;
|
||||
case OpCode::Float:
|
||||
PUSH((float)lhs + rhs.floatVal);
|
||||
break;
|
||||
default:
|
||||
PUSH(lhs);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimExpression::add(float lhs, const OpCode& rhs, std::stack<OpCode>& stack) const {
|
||||
switch (rhs.type) {
|
||||
case OpCode::Bool:
|
||||
case OpCode::Int:
|
||||
PUSH(lhs + (float)rhs.intVal);
|
||||
break;
|
||||
case OpCode::Float:
|
||||
PUSH(lhs + rhs.floatVal);
|
||||
break;
|
||||
default:
|
||||
PUSH(lhs);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimExpression::evalAdd(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = coerseToValue(map, stack.top());
|
||||
stack.pop();
|
||||
OpCode rhs = coerseToValue(map, stack.top());
|
||||
stack.pop();
|
||||
|
||||
switch (lhs.type) {
|
||||
case OpCode::Bool:
|
||||
add(lhs.intVal, rhs, stack);
|
||||
break;
|
||||
case OpCode::Int:
|
||||
add(lhs.intVal, rhs, stack);
|
||||
break;
|
||||
case OpCode::Float:
|
||||
add(lhs.floatVal, rhs, stack);
|
||||
break;
|
||||
default:
|
||||
add(0, rhs, stack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimExpression::evalMultiply(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = coerseToValue(map, stack.top());
|
||||
stack.pop();
|
||||
OpCode rhs = coerseToValue(map, stack.top());
|
||||
stack.pop();
|
||||
|
||||
switch(lhs.type) {
|
||||
case OpCode::Bool:
|
||||
mul(lhs.intVal, rhs, stack);
|
||||
break;
|
||||
case OpCode::Int:
|
||||
mul(lhs.intVal, rhs, stack);
|
||||
break;
|
||||
case OpCode::Float:
|
||||
mul(lhs.floatVal, rhs, stack);
|
||||
break;
|
||||
default:
|
||||
mul(0, rhs, stack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimExpression::mul(int lhs, const OpCode& rhs, std::stack<OpCode>& stack) const {
|
||||
switch (rhs.type) {
|
||||
case OpCode::Bool:
|
||||
case OpCode::Int:
|
||||
PUSH(lhs * rhs.intVal);
|
||||
break;
|
||||
case OpCode::Float:
|
||||
PUSH((float)lhs * rhs.floatVal);
|
||||
break;
|
||||
default:
|
||||
PUSH(lhs);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimExpression::mul(float lhs, const OpCode& rhs, std::stack<OpCode>& stack) const {
|
||||
switch (rhs.type) {
|
||||
case OpCode::Bool:
|
||||
case OpCode::Int:
|
||||
PUSH(lhs * (float)rhs.intVal);
|
||||
break;
|
||||
case OpCode::Float:
|
||||
PUSH(lhs * rhs.floatVal);
|
||||
break;
|
||||
default:
|
||||
PUSH(lhs);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimExpression::evalDivide(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(0.0f);
|
||||
}
|
||||
|
||||
void AnimExpression::evalModulus(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH((int)0);
|
||||
}
|
||||
|
||||
void AnimExpression::evalUnaryMinus(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
switch (rhs.type) {
|
||||
case OpCode::Identifier: {
|
||||
const AnimVariant& var = map.get(rhs.strVal);
|
||||
switch (var.getType()) {
|
||||
case AnimVariant::Type::Bool:
|
||||
qCWarning(animation) << "AnimExpression: type missmatch for unary minus, expected a number not a bool";
|
||||
// interpret this as boolean not.
|
||||
PUSH(!var.getBool());
|
||||
break;
|
||||
case AnimVariant::Type::Int:
|
||||
PUSH(-var.getInt());
|
||||
break;
|
||||
case AnimVariant::Type::Float:
|
||||
PUSH(-var.getFloat());
|
||||
break;
|
||||
default:
|
||||
// TODO: Vec3, Quat are unsupported
|
||||
assert(false);
|
||||
PUSH(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
case OpCode::Int:
|
||||
PUSH(-rhs.intVal);
|
||||
break;
|
||||
case OpCode::Float:
|
||||
PUSH(-rhs.floatVal);
|
||||
break;
|
||||
case OpCode::Bool:
|
||||
qCWarning(animation) << "AnimExpression: type missmatch for unary minus, expected a number not a bool";
|
||||
// interpret this as boolean not.
|
||||
PUSH(!rhs.coerceBool(map));
|
||||
break;
|
||||
default:
|
||||
qCCritical(animation) << "AnimExpression: ERRROR for unary minus, expected a number, type = " << rhs.type;
|
||||
assert(false);
|
||||
PUSH(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::OpCode AnimExpression::coerseToValue(const AnimVariantMap& map, const OpCode& opCode) const {
|
||||
switch (opCode.type) {
|
||||
case OpCode::Identifier:
|
||||
{
|
||||
const AnimVariant& var = map.get(opCode.strVal);
|
||||
switch (var.getType()) {
|
||||
case AnimVariant::Type::Bool:
|
||||
return OpCode((bool)var.getBool());
|
||||
break;
|
||||
case AnimVariant::Type::Int:
|
||||
return OpCode(var.getInt());
|
||||
break;
|
||||
case AnimVariant::Type::Float:
|
||||
return OpCode(var.getFloat());
|
||||
break;
|
||||
default:
|
||||
// TODO: Vec3, Quat are unsupported
|
||||
assert(false);
|
||||
return OpCode(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OpCode::Bool:
|
||||
case OpCode::Int:
|
||||
case OpCode::Float:
|
||||
return opCode;
|
||||
default:
|
||||
qCCritical(animation) << "AnimExpression: ERROR expected a number, type = " << opCode.type;
|
||||
assert(false);
|
||||
return OpCode(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void AnimExpression::dumpOpCodes() const {
|
||||
QString tmp;
|
||||
for (auto& op : _opCodes) {
|
||||
switch (op.type) {
|
||||
case OpCode::Identifier: tmp += QString(" %1").arg(op.strVal); break;
|
||||
case OpCode::Bool: tmp += QString(" %1").arg(op.intVal ? "true" : "false"); break;
|
||||
case OpCode::Int: tmp += QString(" %1").arg(op.intVal); break;
|
||||
case OpCode::Float: tmp += QString(" %1").arg(op.floatVal); break;
|
||||
case OpCode::And: tmp += " &&"; break;
|
||||
case OpCode::Or: tmp += " ||"; break;
|
||||
case OpCode::GreaterThan: tmp += " >"; break;
|
||||
case OpCode::GreaterThanEqual: tmp += " >="; break;
|
||||
case OpCode::LessThan: tmp += " <"; break;
|
||||
case OpCode::LessThanEqual: tmp += " <="; break;
|
||||
case OpCode::Equal: tmp += " =="; break;
|
||||
case OpCode::NotEqual: tmp += " !="; break;
|
||||
case OpCode::Not: tmp += " !"; break;
|
||||
case OpCode::Subtract: tmp += " -"; break;
|
||||
case OpCode::Add: tmp += " +"; break;
|
||||
case OpCode::Multiply: tmp += " *"; break;
|
||||
case OpCode::Divide: tmp += " /"; break;
|
||||
case OpCode::Modulus: tmp += " %"; break;
|
||||
case OpCode::UnaryMinus: tmp += " unary-"; break;
|
||||
default: tmp += " ???"; break;
|
||||
}
|
||||
}
|
||||
qCDebug(animation).nospace().noquote() << "opCodes =" << tmp;
|
||||
qCDebug(animation).resetFormat();
|
||||
}
|
||||
#endif
|
158
libraries/animation/src/AnimExpression.h
Normal file
158
libraries/animation/src/AnimExpression.h
Normal file
|
@ -0,0 +1,158 @@
|
|||
//
|
||||
// AnimExpression.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 11/1/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// 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_AnimExpression
|
||||
#define hifi_AnimExpression
|
||||
|
||||
#include <QString>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include "AnimVariant.h"
|
||||
|
||||
class AnimExpression {
|
||||
public:
|
||||
friend class AnimTests;
|
||||
AnimExpression(const QString& str);
|
||||
protected:
|
||||
struct Token {
|
||||
enum Type {
|
||||
End = 0,
|
||||
Identifier,
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
And,
|
||||
Or,
|
||||
GreaterThan,
|
||||
GreaterThanEqual,
|
||||
LessThan,
|
||||
LessThanEqual,
|
||||
Equal,
|
||||
NotEqual,
|
||||
LeftParen,
|
||||
RightParen,
|
||||
Not,
|
||||
Minus,
|
||||
Plus,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulus,
|
||||
Comma,
|
||||
Error
|
||||
};
|
||||
Token(Type type) : type {type} {}
|
||||
Token(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {}
|
||||
explicit Token(int val) : type {Type::Int}, intVal {val} {}
|
||||
explicit Token(bool val) : type {Type::Bool}, intVal {val} {}
|
||||
explicit Token(float val) : type {Type::Float}, floatVal {val} {}
|
||||
Type type {End};
|
||||
QString strVal;
|
||||
int intVal {0};
|
||||
float floatVal {0.0f};
|
||||
};
|
||||
|
||||
struct OpCode {
|
||||
enum Type {
|
||||
Identifier,
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
And,
|
||||
Or,
|
||||
GreaterThan,
|
||||
GreaterThanEqual,
|
||||
LessThan,
|
||||
LessThanEqual,
|
||||
Equal,
|
||||
NotEqual,
|
||||
Not,
|
||||
Subtract,
|
||||
Add,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulus,
|
||||
UnaryMinus
|
||||
};
|
||||
OpCode(Type type) : type {type} {}
|
||||
explicit OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {}
|
||||
explicit OpCode(const QString& str) : type {Type::Identifier}, strVal {str} {}
|
||||
explicit OpCode(int val) : type {Type::Int}, intVal {val} {}
|
||||
explicit OpCode(bool val) : type {Type::Bool}, intVal {(int)val} {}
|
||||
explicit OpCode(float val) : type {Type::Float}, floatVal {val} {}
|
||||
|
||||
bool coerceBool(const AnimVariantMap& map) const {
|
||||
if (type == Int || type == Bool) {
|
||||
return intVal != 0;
|
||||
} else if (type == Identifier) {
|
||||
return map.lookup(strVal, false);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Type type {Int};
|
||||
QString strVal;
|
||||
int intVal {0};
|
||||
float floatVal {0.0f};
|
||||
};
|
||||
|
||||
void unconsumeToken(const Token& token);
|
||||
Token consumeToken(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeIdentifier(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeNumber(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeAnd(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeOr(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeGreaterThan(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeLessThan(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeNot(const QString& str, QString::const_iterator& iter) const;
|
||||
|
||||
bool parseExpr(const QString& str, QString::const_iterator& iter);
|
||||
bool parseExprPrime(const QString& str, QString::const_iterator& iter);
|
||||
bool parseTerm(const QString& str, QString::const_iterator& iter);
|
||||
bool parseTermPrime(const QString& str, QString::const_iterator& iter);
|
||||
bool parseUnary(const QString& str, QString::const_iterator& iter);
|
||||
bool parseFactor(const QString& str, QString::const_iterator& iter);
|
||||
|
||||
OpCode evaluate(const AnimVariantMap& map) const;
|
||||
void evalAnd(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalOr(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalGreaterThan(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalGreaterThanEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalLessThan(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalLessThanEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalNotEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalNot(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalSubtract(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalAdd(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void add(int lhs, const OpCode& rhs, std::stack<OpCode>& stack) const;
|
||||
void add(float lhs, const OpCode& rhs, std::stack<OpCode>& stack) const;
|
||||
void evalMultiply(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void mul(int lhs, const OpCode& rhs, std::stack<OpCode>& stack) const;
|
||||
void mul(float lhs, const OpCode& rhs, std::stack<OpCode>& stack) const;
|
||||
void evalDivide(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalModulus(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalUnaryMinus(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
|
||||
OpCode coerseToValue(const AnimVariantMap& map, const OpCode& opCode) const;
|
||||
|
||||
QString _expression;
|
||||
mutable std::stack<Token> _tokenStack; // TODO: remove, only needed during parsing
|
||||
std::vector<OpCode> _opCodes;
|
||||
|
||||
#ifndef NDEBUG
|
||||
void dumpOpCodes() const;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -15,6 +15,8 @@
|
|||
#include <RegisteredMetaTypes.h>
|
||||
#include "AnimVariant.h" // which has AnimVariant/AnimVariantMap
|
||||
|
||||
const AnimVariant AnimVariant::False = AnimVariant();
|
||||
|
||||
QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const {
|
||||
if (QThread::currentThread() != engine->thread()) {
|
||||
qCWarning(animation) << "Cannot create Javacript object from non-script thread" << QThread::currentThread();
|
||||
|
|
|
@ -34,6 +34,8 @@ public:
|
|||
NumTypes
|
||||
};
|
||||
|
||||
static const AnimVariant False;
|
||||
|
||||
AnimVariant() : _type(Type::Bool) { memset(&_val, 0, sizeof(_val)); }
|
||||
AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; }
|
||||
AnimVariant(int value) : _type(Type::Int) { _val.intVal = value; }
|
||||
|
@ -57,13 +59,50 @@ public:
|
|||
void setQuat(const glm::quat& value) { assert(_type == Type::Quat); *reinterpret_cast<glm::quat*>(&_val) = value; }
|
||||
void setString(const QString& value) { assert(_type == Type::String); _stringVal = value; }
|
||||
|
||||
bool getBool() const { assert(_type == Type::Bool); return _val.boolVal; }
|
||||
int getInt() const { assert(_type == Type::Int || _type == Type::Float); return _type == Type::Float ? (int)_val.floats[0] : _val.intVal; }
|
||||
float getFloat() const { assert(_type == Type::Float || _type == Type::Int); return _type == Type::Int ? (float)_val.intVal : _val.floats[0]; }
|
||||
|
||||
const glm::vec3& getVec3() const { assert(_type == Type::Vec3); return *reinterpret_cast<const glm::vec3*>(&_val); }
|
||||
const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast<const glm::quat*>(&_val); }
|
||||
const QString& getString() const { assert(_type == Type::String); return _stringVal; }
|
||||
bool getBool() const {
|
||||
if (_type == Type::Bool) {
|
||||
return _val.boolVal;
|
||||
} else if (_type == Type::Int) {
|
||||
return _val.intVal != 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int getInt() const {
|
||||
if (_type == Type::Int) {
|
||||
return _val.intVal;
|
||||
} else if (_type == Type::Float) {
|
||||
return (int)_val.floats[0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
float getFloat() const {
|
||||
if (_type == Type::Float) {
|
||||
return _val.floats[0];
|
||||
} else if (_type == Type::Int) {
|
||||
return (float)_val.intVal;
|
||||
} else {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
const glm::vec3& getVec3() const {
|
||||
if (_type == Type::Vec3) {
|
||||
return *reinterpret_cast<const glm::vec3*>(&_val);
|
||||
} else {
|
||||
return Vectors::ZERO;
|
||||
}
|
||||
}
|
||||
const glm::quat& getQuat() const {
|
||||
if (_type == Type::Quat) {
|
||||
return *reinterpret_cast<const glm::quat*>(&_val);
|
||||
} else {
|
||||
return Quaternions::IDENTITY;
|
||||
}
|
||||
}
|
||||
const QString& getString() const {
|
||||
return _stringVal;
|
||||
}
|
||||
|
||||
protected:
|
||||
Type _type;
|
||||
|
@ -71,7 +110,7 @@ protected:
|
|||
union {
|
||||
bool boolVal;
|
||||
int intVal;
|
||||
float floats[16];
|
||||
float floats[4];
|
||||
} _val;
|
||||
};
|
||||
|
||||
|
@ -172,6 +211,15 @@ public:
|
|||
void clearMap() { _map.clear(); }
|
||||
bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); }
|
||||
|
||||
const AnimVariant& get(const QString& key) const {
|
||||
auto iter = _map.find(key);
|
||||
if (iter != _map.end()) {
|
||||
return iter->second;
|
||||
} else {
|
||||
return AnimVariant::False;
|
||||
}
|
||||
}
|
||||
|
||||
// Answer a Plain Old Javascript Object (for the given engine) all of our values set as properties.
|
||||
QScriptValue animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const;
|
||||
// Side-effect us with the value of object's own properties. (No inherited properties.)
|
||||
|
|
|
@ -245,6 +245,14 @@ int Rig::indexOfJoint(const QString& jointName) const {
|
|||
}
|
||||
}
|
||||
|
||||
QString Rig::nameOfJoint(int jointIndex) const {
|
||||
if (_animSkeleton) {
|
||||
return _animSkeleton->getJointName(jointIndex);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::setModelOffset(const glm::mat4& modelOffsetMat) {
|
||||
AnimPose newModelOffset = AnimPose(modelOffsetMat);
|
||||
if (!isEqual(_modelOffset.trans, newModelOffset.trans) ||
|
||||
|
@ -289,8 +297,10 @@ void Rig::clearJointState(int index) {
|
|||
|
||||
void Rig::clearJointStates() {
|
||||
_internalPoseSet._overrideFlags.clear();
|
||||
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints());
|
||||
_internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
if (_animSkeleton) {
|
||||
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints());
|
||||
_internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::clearJointAnimationPriority(int index) {
|
||||
|
|
|
@ -91,6 +91,7 @@ public:
|
|||
bool jointStatesEmpty();
|
||||
int getJointStateCount() const;
|
||||
int indexOfJoint(const QString& jointName) const;
|
||||
QString nameOfJoint(int jointIndex) const;
|
||||
|
||||
void setModelOffset(const glm::mat4& modelOffsetMat);
|
||||
|
||||
|
|
|
@ -289,7 +289,6 @@ uint64_t AudioInjector::injectNextFrame() {
|
|||
_currentSendOffset = 0;
|
||||
} else {
|
||||
// we weren't to loop, say that we're done now
|
||||
qDebug() << "AudioInjector::injectNextFrame has sent all data and was not asked to loop - calling finish().";
|
||||
finish();
|
||||
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
|
||||
}
|
||||
|
|
|
@ -1260,6 +1260,7 @@ void AvatarData::updateJointMappings() {
|
|||
static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl");
|
||||
static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName");
|
||||
static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform");
|
||||
static const QString JSON_ATTACHMENT_IS_SOFT = QStringLiteral("isSoft");
|
||||
|
||||
QJsonObject AttachmentData::toJson() const {
|
||||
QJsonObject result;
|
||||
|
@ -1278,6 +1279,7 @@ QJsonObject AttachmentData::toJson() const {
|
|||
if (!transform.isIdentity()) {
|
||||
result[JSON_ATTACHMENT_TRANSFORM] = Transform::toJson(transform);
|
||||
}
|
||||
result[JSON_ATTACHMENT_IS_SOFT] = isSoft;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1302,21 +1304,25 @@ void AttachmentData::fromJson(const QJsonObject& json) {
|
|||
rotation = transform.getRotation();
|
||||
scale = transform.getScale().x;
|
||||
}
|
||||
|
||||
if (json.contains(JSON_ATTACHMENT_IS_SOFT)) {
|
||||
isSoft = json[JSON_ATTACHMENT_IS_SOFT].toBool();
|
||||
}
|
||||
}
|
||||
|
||||
bool AttachmentData::operator==(const AttachmentData& other) const {
|
||||
return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation &&
|
||||
rotation == other.rotation && scale == other.scale;
|
||||
rotation == other.rotation && scale == other.scale && isSoft == other.isSoft;
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) {
|
||||
return out << attachment.modelURL << attachment.jointName <<
|
||||
attachment.translation << attachment.rotation << attachment.scale;
|
||||
attachment.translation << attachment.rotation << attachment.scale << attachment.isSoft;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) {
|
||||
return in >> attachment.modelURL >> attachment.jointName >>
|
||||
attachment.translation >> attachment.rotation >> attachment.scale;
|
||||
attachment.translation >> attachment.rotation >> attachment.scale >> attachment.isSoft;
|
||||
}
|
||||
|
||||
void AttachmentDataObject::setModelURL(const QString& modelURL) const {
|
||||
|
|
|
@ -437,9 +437,10 @@ public:
|
|||
glm::vec3 translation;
|
||||
glm::quat rotation;
|
||||
float scale { 1.0f };
|
||||
|
||||
bool isSoft { false };
|
||||
|
||||
bool isValid() const { return modelURL.isValid(); }
|
||||
|
||||
|
||||
bool operator==(const AttachmentData& other) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
|
|
@ -70,6 +70,12 @@ namespace controller {
|
|||
makeAxisPair(Action::RETICLE_UP, "ReticleUp"),
|
||||
makeAxisPair(Action::RETICLE_DOWN, "ReticleDown"),
|
||||
|
||||
makeAxisPair(Action::UI_NAV_LATERAL, "UiNavLateral"),
|
||||
makeAxisPair(Action::UI_NAV_VERTICAL, "UiNavVertical"),
|
||||
makeAxisPair(Action::UI_NAV_GROUP, "UiNavGroup"),
|
||||
makeAxisPair(Action::UI_NAV_SELECT, "UiNavSelect"),
|
||||
makeAxisPair(Action::UI_NAV_BACK, "UiNavBack"),
|
||||
|
||||
// Aliases and bisected versions
|
||||
makeAxisPair(Action::LONGITUDINAL_BACKWARD, "Backward"),
|
||||
makeAxisPair(Action::LONGITUDINAL_FORWARD, "Forward"),
|
||||
|
|
|
@ -55,6 +55,12 @@ enum class Action {
|
|||
|
||||
SHIFT,
|
||||
|
||||
UI_NAV_LATERAL,
|
||||
UI_NAV_VERTICAL,
|
||||
UI_NAV_GROUP,
|
||||
UI_NAV_SELECT,
|
||||
UI_NAV_BACK,
|
||||
|
||||
// Pointer/Reticle control
|
||||
RETICLE_CLICK,
|
||||
RETICLE_X,
|
||||
|
@ -90,6 +96,7 @@ enum class Action {
|
|||
BOOM_IN,
|
||||
BOOM_OUT,
|
||||
|
||||
|
||||
NUM_ACTIONS,
|
||||
};
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ protected:
|
|||
friend class UserInputMapper;
|
||||
|
||||
virtual Input::NamedVector getAvailableInputs() const = 0;
|
||||
virtual QStringList getDefaultMappingConfigs() const { return QStringList() << getDefaultMappingConfig(); }
|
||||
virtual QString getDefaultMappingConfig() const { return QString(); }
|
||||
virtual EndpointPointer createEndpoint(const Input& input) const;
|
||||
|
||||
|
|
|
@ -42,7 +42,22 @@ namespace controller {
|
|||
}
|
||||
|
||||
void Pose::fromScriptValue(const QScriptValue& object, Pose& pose) {
|
||||
// nothing for now...
|
||||
auto translation = object.property("translation");
|
||||
auto rotation = object.property("rotation");
|
||||
auto velocity = object.property("velocity");
|
||||
auto angularVelocity = object.property("angularVelocity");
|
||||
if (translation.isValid() &&
|
||||
rotation.isValid() &&
|
||||
velocity.isValid() &&
|
||||
angularVelocity.isValid()) {
|
||||
vec3FromScriptValue(translation, pose.translation);
|
||||
quatFromScriptValue(rotation, pose.rotation);
|
||||
vec3FromScriptValue(velocity, pose.velocity);
|
||||
vec3FromScriptValue(angularVelocity, pose.angularVelocity);
|
||||
pose.valid = true;
|
||||
} else {
|
||||
pose.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QCursor>
|
||||
#include <QThread>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QVariant>
|
||||
|
@ -29,6 +30,7 @@
|
|||
#include <QtScript/QScriptValue>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include "UserInputMapper.h"
|
||||
#include "StandardControls.h"
|
||||
|
@ -87,6 +89,21 @@ namespace controller {
|
|||
Q_INVOKABLE QObject* parseMapping(const QString& json);
|
||||
Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl);
|
||||
|
||||
Q_INVOKABLE glm::vec2 getReticlePosition() {
|
||||
return toGlm(QCursor::pos());
|
||||
}
|
||||
Q_INVOKABLE void setReticlePosition(glm::vec2 position) {
|
||||
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
|
||||
// remove it after we're done
|
||||
const float REASONABLE_CHANGE = 50.0f;
|
||||
glm::vec2 oldPos = toGlm(QCursor::pos());
|
||||
auto distance = glm::distance(oldPos, position);
|
||||
if (distance > REASONABLE_CHANGE) {
|
||||
qDebug() << "Contrller::ScriptingInterface ---- UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPos << " newPos:" << position;
|
||||
}
|
||||
|
||||
QCursor::setPos(position.x, position.y);
|
||||
}
|
||||
|
||||
//Q_INVOKABLE bool isPrimaryButtonPressed() const;
|
||||
//Q_INVOKABLE glm::vec2 getPrimaryJoystickPosition() const;
|
||||
|
|
|
@ -131,10 +131,10 @@ EndpointPointer StandardController::createEndpoint(const Input& input) const {
|
|||
return std::make_shared<StandardEndpoint>(input);
|
||||
}
|
||||
|
||||
QString StandardController::getDefaultMappingConfig() const {
|
||||
QStringList StandardController::getDefaultMappingConfigs() const {
|
||||
static const QString DEFAULT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard.json";
|
||||
return DEFAULT_MAPPING_JSON;
|
||||
static const QString DEFAULT_NAV_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_navigation.json";
|
||||
return QStringList() << DEFAULT_NAV_MAPPING_JSON << DEFAULT_MAPPING_JSON;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class StandardController : public QObject, public InputDevice {
|
|||
public:
|
||||
virtual EndpointPointer createEndpoint(const Input& input) const override;
|
||||
virtual Input::NamedVector getAvailableInputs() const override;
|
||||
virtual QString getDefaultMappingConfig() const override;
|
||||
virtual QStringList getDefaultMappingConfigs() const override;
|
||||
virtual void update(float deltaTime, bool jointsCaptured) override;
|
||||
virtual void focusOutEvent() override;
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue