mirror of
https://github.com/overte-org/overte.git
synced 2025-04-14 20:08:56 +02:00
Merge branch 'master' of github.com:highfidelity/hifi
This commit is contained in:
commit
379cdf1d5d
43 changed files with 6398 additions and 1258 deletions
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()
|
||||||
|
|
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)
|
||||||
|
|
|
@ -23,8 +23,9 @@ var WANT_DEBUG = false;
|
||||||
// these tune time-averaging and "on" value for analog trigger
|
// these tune time-averaging and "on" value for analog trigger
|
||||||
//
|
//
|
||||||
|
|
||||||
var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value
|
var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing
|
||||||
var TRIGGER_ON_VALUE = 0.4;
|
var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab
|
||||||
|
var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab
|
||||||
var TRIGGER_OFF_VALUE = 0.15;
|
var TRIGGER_OFF_VALUE = 0.15;
|
||||||
|
|
||||||
var BUMPER_ON_VALUE = 0.5;
|
var BUMPER_ON_VALUE = 0.5;
|
||||||
|
@ -96,7 +97,7 @@ var MSEC_PER_SEC = 1000.0;
|
||||||
var LIFETIME = 10;
|
var LIFETIME = 10;
|
||||||
var ACTION_TTL = 15; // seconds
|
var ACTION_TTL = 15; // seconds
|
||||||
var ACTION_TTL_REFRESH = 5;
|
var ACTION_TTL_REFRESH = 5;
|
||||||
var PICKS_PER_SECOND_PER_HAND = 5;
|
var PICKS_PER_SECOND_PER_HAND = 60;
|
||||||
var MSECS_PER_SEC = 1000.0;
|
var MSECS_PER_SEC = 1000.0;
|
||||||
var GRABBABLE_PROPERTIES = [
|
var GRABBABLE_PROPERTIES = [
|
||||||
"position",
|
"position",
|
||||||
|
@ -123,8 +124,8 @@ var blacklist = [];
|
||||||
|
|
||||||
//we've created various ways of visualizing looking for and moving distant objects
|
//we've created various ways of visualizing looking for and moving distant objects
|
||||||
var USE_ENTITY_LINES_FOR_SEARCHING = false;
|
var USE_ENTITY_LINES_FOR_SEARCHING = false;
|
||||||
var USE_OVERLAY_LINES_FOR_SEARCHING = false;
|
var USE_OVERLAY_LINES_FOR_SEARCHING = true;
|
||||||
var USE_PARTICLE_BEAM_FOR_SEARCHING = true;
|
var USE_PARTICLE_BEAM_FOR_SEARCHING = false;
|
||||||
|
|
||||||
var USE_ENTITY_LINES_FOR_MOVING = false;
|
var USE_ENTITY_LINES_FOR_MOVING = false;
|
||||||
var USE_OVERLAY_LINES_FOR_MOVING = false;
|
var USE_OVERLAY_LINES_FOR_MOVING = false;
|
||||||
|
@ -285,12 +286,17 @@ function MyController(hand) {
|
||||||
//for visualizations
|
//for visualizations
|
||||||
this.overlayLine = null;
|
this.overlayLine = null;
|
||||||
this.particleBeam = null;
|
this.particleBeam = null;
|
||||||
|
|
||||||
//for lights
|
//for lights
|
||||||
this.spotlight = null;
|
this.spotlight = null;
|
||||||
this.pointlight = null;
|
this.pointlight = null;
|
||||||
this.overlayLine = null;
|
this.overlayLine = null;
|
||||||
|
this.searchSphere = null;
|
||||||
|
|
||||||
|
// how far from camera to search intersection?
|
||||||
|
this.intersectionDistance = 0.0;
|
||||||
|
this.searchSphereDistance = 0.0;
|
||||||
|
|
||||||
this.ignoreIK = false;
|
this.ignoreIK = false;
|
||||||
this.offsetPosition = Vec3.ZERO;
|
this.offsetPosition = Vec3.ZERO;
|
||||||
this.offsetRotation = Quat.IDENTITY;
|
this.offsetRotation = Quat.IDENTITY;
|
||||||
|
@ -395,7 +401,7 @@ function MyController(hand) {
|
||||||
userData: JSON.stringify({
|
userData: JSON.stringify({
|
||||||
grabbableKey: {
|
grabbableKey: {
|
||||||
grabbable: false
|
grabbable: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -409,6 +415,23 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var SEARCH_SPHERE_ALPHA = 0.5;
|
||||||
|
this.searchSphereOn = function(location, size, color) {
|
||||||
|
if (this.searchSphere === null) {
|
||||||
|
var sphereProperties = {
|
||||||
|
position: location,
|
||||||
|
size: size,
|
||||||
|
color: color,
|
||||||
|
alpha: SEARCH_SPHERE_ALPHA,
|
||||||
|
solid: true,
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
this.searchSphere = Overlays.addOverlay("sphere", sphereProperties);
|
||||||
|
} else {
|
||||||
|
Overlays.editOverlay(this.searchSphere, { position: location, size: size, color: color, visible: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.overlayLineOn = function(closePoint, farPoint, color) {
|
this.overlayLineOn = function(closePoint, farPoint, color) {
|
||||||
if (this.overlayLine === null) {
|
if (this.overlayLine === null) {
|
||||||
var lineProperties = {
|
var lineProperties = {
|
||||||
|
@ -452,7 +475,7 @@ function MyController(hand) {
|
||||||
this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan);
|
this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan);
|
||||||
} else {
|
} else {
|
||||||
this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan);
|
this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleDistantParticleBeam = function(handPosition, objectPosition, color) {
|
this.handleDistantParticleBeam = function(handPosition, objectPosition, color) {
|
||||||
|
@ -540,12 +563,12 @@ function MyController(hand) {
|
||||||
Entities.editEntity(this.particleBeam, {
|
Entities.editEntity(this.particleBeam, {
|
||||||
rotation: orientation,
|
rotation: orientation,
|
||||||
position: position,
|
position: position,
|
||||||
visible: true,
|
visible: true,
|
||||||
color: color,
|
color: color,
|
||||||
emitSpeed: speed,
|
emitSpeed: speed,
|
||||||
speedSpread: spread,
|
speedSpread: spread,
|
||||||
lifespan: lifespan
|
lifespan: lifespan
|
||||||
})
|
})
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -561,7 +584,7 @@ function MyController(hand) {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 0,
|
y: 0,
|
||||||
z: 0
|
z: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
|
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
|
||||||
|
@ -654,6 +677,17 @@ function MyController(hand) {
|
||||||
this.overlayLine = null;
|
this.overlayLine = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.searchSphereOff = function() {
|
||||||
|
if (this.searchSphere !== null) {
|
||||||
|
//Overlays.editOverlay(this.searchSphere, { visible: false });
|
||||||
|
Overlays.deleteOverlay(this.searchSphere);
|
||||||
|
this.searchSphere = null;
|
||||||
|
this.searchSphereDistance = 0.0;
|
||||||
|
this.intersectionDistance = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
this.particleBeamOff = function() {
|
this.particleBeamOff = function() {
|
||||||
if (this.particleBeam !== null) {
|
if (this.particleBeam !== null) {
|
||||||
Entities.editEntity(this.particleBeam, {
|
Entities.editEntity(this.particleBeam, {
|
||||||
|
@ -687,6 +721,7 @@ function MyController(hand) {
|
||||||
if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) {
|
if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) {
|
||||||
this.particleBeamOff();
|
this.particleBeamOff();
|
||||||
}
|
}
|
||||||
|
this.searchSphereOff();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.triggerPress = function(value) {
|
this.triggerPress = function(value) {
|
||||||
|
@ -704,6 +739,10 @@ function MyController(hand) {
|
||||||
(triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO));
|
(triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.triggerSmoothedGrab = function() {
|
||||||
|
return this.triggerValue > TRIGGER_GRAB_VALUE;
|
||||||
|
};
|
||||||
|
|
||||||
this.triggerSmoothedSqueezed = function() {
|
this.triggerSmoothedSqueezed = function() {
|
||||||
return this.triggerValue > TRIGGER_ON_VALUE;
|
return this.triggerValue > TRIGGER_ON_VALUE;
|
||||||
};
|
};
|
||||||
|
@ -712,11 +751,6 @@ function MyController(hand) {
|
||||||
return this.triggerValue < TRIGGER_OFF_VALUE;
|
return this.triggerValue < TRIGGER_OFF_VALUE;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.triggerSqueezed = function() {
|
|
||||||
var triggerValue = this.rawTriggerValue;
|
|
||||||
return triggerValue > TRIGGER_ON_VALUE;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.bumperSqueezed = function() {
|
this.bumperSqueezed = function() {
|
||||||
return _this.rawBumperValue > BUMPER_ON_VALUE;
|
return _this.rawBumperValue > BUMPER_ON_VALUE;
|
||||||
};
|
};
|
||||||
|
@ -726,15 +760,15 @@ function MyController(hand) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.off = function() {
|
this.off = function() {
|
||||||
if (this.triggerSmoothedSqueezed()) {
|
if (this.triggerSmoothedSqueezed() || this.bumperSqueezed()) {
|
||||||
this.lastPickTime = 0;
|
this.lastPickTime = 0;
|
||||||
this.setState(STATE_SEARCHING);
|
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||||
return;
|
this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation;
|
||||||
}
|
if (this.triggerSmoothedSqueezed()) {
|
||||||
if (this.bumperSqueezed()) {
|
this.setState(STATE_SEARCHING);
|
||||||
this.lastPickTime = 0;
|
} else {
|
||||||
this.setState(STATE_EQUIP_SEARCHING);
|
this.setState(STATE_EQUIP_SEARCHING);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -746,15 +780,20 @@ function MyController(hand) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the trigger is being pressed, do a ray test
|
// the trigger is being pressed, so do a ray test to see what we are hitting
|
||||||
var handPosition = this.getHandPosition();
|
var handPosition = this.getHandPosition();
|
||||||
|
|
||||||
|
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||||
|
var currentHandRotation = Controller.getPoseValue(controllerHandInput).rotation;
|
||||||
|
var handDeltaRotation = Quat.multiply(currentHandRotation, Quat.inverse(this.startingHandRotation));
|
||||||
|
|
||||||
var distantPickRay = {
|
var distantPickRay = {
|
||||||
origin: handPosition,
|
origin: Camera.position,
|
||||||
direction: Quat.getUp(this.getHandRotation()),
|
direction: Quat.getFront(Quat.multiply(Camera.orientation, handDeltaRotation)),
|
||||||
length: PICK_MAX_DISTANCE
|
length: PICK_MAX_DISTANCE
|
||||||
};
|
};
|
||||||
|
|
||||||
// don't pick 60x per second.
|
// Pick at some maximum rate, not always
|
||||||
var pickRays = [];
|
var pickRays = [];
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) {
|
if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) {
|
||||||
|
@ -779,19 +818,28 @@ function MyController(hand) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Messages.sendMessage('Hifi-Light-Overlay-Ray-Check', JSON.stringify(pickRayBacked));
|
||||||
|
|
||||||
var intersection;
|
var intersection;
|
||||||
|
|
||||||
if (USE_BLACKLIST === true && blacklist.length !== 0) {
|
if (USE_BLACKLIST === true && blacklist.length !== 0) {
|
||||||
intersection = Entities.findRayIntersection(pickRay, true, [], blacklist);
|
intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist);
|
||||||
} else {
|
} else {
|
||||||
intersection = Entities.findRayIntersection(pickRayBacked, true);
|
intersection = Entities.findRayIntersection(pickRayBacked, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (intersection.intersects) {
|
if (intersection.intersects) {
|
||||||
|
|
||||||
// the ray is intersecting something we can move.
|
// the ray is intersecting something we can move.
|
||||||
var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection);
|
this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection);
|
||||||
|
|
||||||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA);
|
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA);
|
||||||
|
var defaultDisableNearGrabData = {
|
||||||
|
disableNearGrab: false
|
||||||
|
};
|
||||||
|
//sometimes we want things to stay right where they are when we let go.
|
||||||
|
var disableNearGrabData = getEntityCustomData('handControllerKey', intersection.entityID, defaultDisableNearGrabData);
|
||||||
|
|
||||||
if (intersection.properties.name == "Grab Debug Entity") {
|
if (intersection.properties.name == "Grab Debug Entity") {
|
||||||
continue;
|
continue;
|
||||||
|
@ -800,11 +848,11 @@ function MyController(hand) {
|
||||||
if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) {
|
if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (intersectionDistance > pickRay.length) {
|
if (this.intersectionDistance > pickRay.length) {
|
||||||
// too far away for this ray.
|
// too far away for this ray.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) {
|
if (this.intersectionDistance <= NEAR_PICK_MAX_DISTANCE) {
|
||||||
// the hand is very close to the intersected object. go into close-grabbing mode.
|
// the hand is very close to the intersected object. go into close-grabbing mode.
|
||||||
if (grabbableData.wantsTrigger) {
|
if (grabbableData.wantsTrigger) {
|
||||||
this.grabbedEntity = intersection.entityID;
|
this.grabbedEntity = intersection.entityID;
|
||||||
|
@ -813,7 +861,11 @@ function MyController(hand) {
|
||||||
} else if (!intersection.properties.locked) {
|
} else if (!intersection.properties.locked) {
|
||||||
this.grabbedEntity = intersection.entityID;
|
this.grabbedEntity = intersection.entityID;
|
||||||
if (this.state == STATE_SEARCHING) {
|
if (this.state == STATE_SEARCHING) {
|
||||||
this.setState(STATE_NEAR_GRABBING);
|
if (disableNearGrabData.disableNearGrab !== true) {
|
||||||
|
this.setState(STATE_NEAR_GRABBING);
|
||||||
|
} else {
|
||||||
|
//disable near grab on this thing
|
||||||
|
}
|
||||||
} else { // equipping
|
} else { // equipping
|
||||||
if (typeof grabbableData.spatialKey !== 'undefined') {
|
if (typeof grabbableData.spatialKey !== 'undefined') {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -838,11 +890,11 @@ function MyController(hand) {
|
||||||
// this.setState(STATE_EQUIP_SPRING);
|
// this.setState(STATE_EQUIP_SPRING);
|
||||||
this.setState(STATE_EQUIP);
|
this.setState(STATE_EQUIP);
|
||||||
return;
|
return;
|
||||||
} else if (this.state == STATE_SEARCHING) {
|
} else if ((this.state == STATE_SEARCHING) && this.triggerSmoothedGrab()) {
|
||||||
this.setState(STATE_DISTANCE_HOLDING);
|
this.setState(STATE_DISTANCE_HOLDING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (grabbableData.wantsTrigger) {
|
} else if (grabbableData.wantsTrigger && this.triggerSmoothedGrab()) {
|
||||||
this.grabbedEntity = intersection.entityID;
|
this.grabbedEntity = intersection.entityID;
|
||||||
this.setState(STATE_FAR_TRIGGER);
|
this.setState(STATE_FAR_TRIGGER);
|
||||||
return;
|
return;
|
||||||
|
@ -851,6 +903,7 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// forward ray test failed, try sphere test.
|
// forward ray test failed, try sphere test.
|
||||||
if (WANT_DEBUG) {
|
if (WANT_DEBUG) {
|
||||||
Entities.addEntity({
|
Entities.addEntity({
|
||||||
|
@ -936,7 +989,18 @@ function MyController(hand) {
|
||||||
this.setState(STATE_NEAR_TRIGGER);
|
this.setState(STATE_NEAR_TRIGGER);
|
||||||
return;
|
return;
|
||||||
} else if (!props.locked && props.collisionsWillMove) {
|
} else if (!props.locked && props.collisionsWillMove) {
|
||||||
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP)
|
var defaultDisableNearGrabData = {
|
||||||
|
disableNearGrab: false
|
||||||
|
};
|
||||||
|
//sometimes we want things to stay right where they are when we let go.
|
||||||
|
var disableNearGrabData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultDisableNearGrabData);
|
||||||
|
if (disableNearGrabData.disableNearGrab === true) {
|
||||||
|
//do nothing because near grab is disabled for this object
|
||||||
|
} else {
|
||||||
|
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -946,14 +1010,23 @@ function MyController(hand) {
|
||||||
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
|
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (USE_OVERLAY_LINES_FOR_SEARCHING === true) {
|
|
||||||
this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) {
|
if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) {
|
||||||
this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR);
|
this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR);
|
||||||
}
|
}
|
||||||
|
if (this.intersectionDistance > 0) {
|
||||||
|
var SPHERE_INTERSECTION_SIZE = 0.011;
|
||||||
|
var SEARCH_SPHERE_FOLLOW_RATE = 0.50;
|
||||||
|
var SEARCH_SPHERE_CHASE_DROP = 0.2;
|
||||||
|
this.searchSphereDistance = this.searchSphereDistance * SEARCH_SPHERE_FOLLOW_RATE + this.intersectionDistance * (1.0 - SEARCH_SPHERE_FOLLOW_RATE);
|
||||||
|
var searchSphereLocation = Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, this.searchSphereDistance));
|
||||||
|
searchSphereLocation.y -= ((this.intersectionDistance - this.searchSphereDistance) / this.intersectionDistance) * SEARCH_SPHERE_CHASE_DROP;
|
||||||
|
this.searchSphereOn(searchSphereLocation, SPHERE_INTERSECTION_SIZE * this.intersectionDistance, this.triggerSmoothedGrab() ? INTERSECT_COLOR : NO_INTERSECT_COLOR);
|
||||||
|
if (USE_OVERLAY_LINES_FOR_SEARCHING === true) {
|
||||||
|
var OVERLAY_BEAM_SETBACK = 0.9;
|
||||||
|
var startBeam = Vec3.sum(handPosition, Vec3.multiply(Vec3.subtract(searchSphereLocation, handPosition), OVERLAY_BEAM_SETBACK));
|
||||||
|
this.overlayLineOn(startBeam, searchSphereLocation, this.triggerSmoothedGrab() ? INTERSECT_COLOR : NO_INTERSECT_COLOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.distanceHolding = function() {
|
this.distanceHolding = function() {
|
||||||
|
@ -1095,22 +1168,54 @@ function MyController(hand) {
|
||||||
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
||||||
|
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab");
|
Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab");
|
||||||
// mix in head motion
|
|
||||||
if (MOVE_WITH_HEAD) {
|
var defaultMoveWithHeadData = {
|
||||||
var objDistance = Vec3.length(objectToAvatar);
|
disableMoveWithHead: false
|
||||||
var before = Vec3.multiplyQbyV(this.currentCameraOrientation, {
|
};
|
||||||
x: 0.0,
|
|
||||||
y: 0.0,
|
var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData);
|
||||||
z: objDistance
|
|
||||||
});
|
if (handControllerData.disableMoveWithHead !== true) {
|
||||||
var after = Vec3.multiplyQbyV(Camera.orientation, {
|
// mix in head motion
|
||||||
x: 0.0,
|
if (MOVE_WITH_HEAD) {
|
||||||
y: 0.0,
|
var objDistance = Vec3.length(objectToAvatar);
|
||||||
z: objDistance
|
var before = Vec3.multiplyQbyV(this.currentCameraOrientation, {
|
||||||
});
|
x: 0.0,
|
||||||
var change = Vec3.subtract(before, after);
|
y: 0.0,
|
||||||
this.currentCameraOrientation = Camera.orientation;
|
z: objDistance
|
||||||
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change);
|
});
|
||||||
|
var after = Vec3.multiplyQbyV(Camera.orientation, {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: objDistance
|
||||||
|
});
|
||||||
|
var change = Vec3.subtract(before, after);
|
||||||
|
this.currentCameraOrientation = Camera.orientation;
|
||||||
|
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// print('should not head move!');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var defaultConstraintData = {
|
||||||
|
axisStart: false,
|
||||||
|
axisEnd: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
var constraintData = getEntityCustomData('lightModifierKey', this.grabbedEntity, defaultConstraintData);
|
||||||
|
var clampedVector;
|
||||||
|
var targetPosition;
|
||||||
|
if (constraintData.axisStart !== false) {
|
||||||
|
clampedVector = this.projectVectorAlongAxis(this.currentObjectPosition, constraintData.axisStart, constraintData.axisEnd);
|
||||||
|
targetPosition = clampedVector;
|
||||||
|
} else {
|
||||||
|
targetPosition = {
|
||||||
|
x: this.currentObjectPosition.x,
|
||||||
|
y: this.currentObjectPosition.y,
|
||||||
|
z: this.currentObjectPosition.z
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1133,7 +1238,7 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||||
targetPosition: this.currentObjectPosition,
|
targetPosition: targetPosition,
|
||||||
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||||
targetRotation: this.currentObjectRotation,
|
targetRotation: this.currentObjectRotation,
|
||||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||||
|
@ -1141,98 +1246,122 @@ function MyController(hand) {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC);
|
this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.nearGrabbing = function() {
|
this.projectVectorAlongAxis = function(position, axisStart, axisEnd) {
|
||||||
var now = Date.now();
|
|
||||||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
|
||||||
|
|
||||||
if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
|
var aPrime = Vec3.subtract(position, axisStart);
|
||||||
this.setState(STATE_RELEASE);
|
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.turnOffVisualizations();
|
|
||||||
|
|
||||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
var bPrime = Vec3.subtract(axisEnd, axisStart);
|
||||||
this.activateEntity(this.grabbedEntity, grabbedProperties);
|
|
||||||
if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) {
|
|
||||||
Entities.editEntity(this.grabbedEntity, {
|
|
||||||
collisionsWillMove: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var handRotation = this.getHandRotation();
|
|
||||||
var handPosition = this.getHandPosition();
|
|
||||||
|
|
||||||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
var bPrimeMagnitude = Vec3.length(bPrime);
|
||||||
var objectRotation = grabbedProperties.rotation;
|
|
||||||
var currentObjectPosition = grabbedProperties.position;
|
var dotProduct = Vec3.dot(aPrime, bPrime);
|
||||||
var offset = Vec3.subtract(currentObjectPosition, handPosition);
|
|
||||||
if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) {
|
|
||||||
// if an object is "equipped" and has a spatialKey, use it.
|
var scalar = dotProduct / bPrimeMagnitude;
|
||||||
this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false;
|
|
||||||
if (grabbableData.spatialKey.relativePosition || grabbableData.spatialKey.rightRelativePosition
|
if (scalar < 0) {
|
||||||
|| grabbableData.spatialKey.leftRelativePosition) {
|
scalar = 0;
|
||||||
this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
|
|
||||||
} else {
|
|
||||||
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
|
|
||||||
}
|
}
|
||||||
if (grabbableData.spatialKey.relativeRotation || grabbableData.spatialKey.rightRelativeRotation
|
|
||||||
|| grabbableData.spatialKey.leftRelativeRotation) {
|
if (scalar > 1) {
|
||||||
|
scalar = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var projection = Vec3.sum(axisStart, Vec3.multiply(scalar, Vec3.normalize(bPrime)));
|
||||||
|
|
||||||
|
return projection
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
this.nearGrabbing = function() {
|
||||||
|
var now = Date.now();
|
||||||
|
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
||||||
|
|
||||||
|
if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
|
||||||
|
this.setState(STATE_RELEASE);
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lineOff();
|
||||||
|
this.overlayLineOff();
|
||||||
|
|
||||||
|
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
||||||
|
this.activateEntity(this.grabbedEntity, grabbedProperties);
|
||||||
|
if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) {
|
||||||
|
Entities.editEntity(this.grabbedEntity, {
|
||||||
|
collisionsWillMove: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var handRotation = this.getHandRotation();
|
||||||
|
var handPosition = this.getHandPosition();
|
||||||
|
|
||||||
|
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
||||||
|
|
||||||
|
if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) {
|
||||||
|
// if an object is "equipped" and has a spatialKey, use it.
|
||||||
|
this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false;
|
||||||
|
this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
|
||||||
this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
|
this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
|
||||||
} else {
|
} else {
|
||||||
|
this.ignoreIK = false;
|
||||||
|
|
||||||
|
var objectRotation = grabbedProperties.rotation;
|
||||||
this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.ignoreIK = false;
|
|
||||||
this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
|
||||||
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.actionID = NULL_ACTION_ID;
|
var currentObjectPosition = grabbedProperties.position;
|
||||||
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
|
var offset = Vec3.subtract(currentObjectPosition, handPosition);
|
||||||
hand: this.hand === RIGHT_HAND ? "right" : "left",
|
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
|
||||||
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
|
}
|
||||||
relativePosition: this.offsetPosition,
|
|
||||||
relativeRotation: this.offsetRotation,
|
this.actionID = NULL_ACTION_ID;
|
||||||
ttl: ACTION_TTL,
|
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
|
||||||
kinematic: NEAR_GRABBING_KINEMATIC,
|
hand: this.hand === RIGHT_HAND ? "right" : "left",
|
||||||
kinematicSetVelocity: true,
|
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
|
||||||
ignoreIK: this.ignoreIK
|
relativePosition: this.offsetPosition,
|
||||||
});
|
relativeRotation: this.offsetRotation,
|
||||||
if (this.actionID === NULL_ACTION_ID) {
|
ttl: ACTION_TTL,
|
||||||
this.actionID = null;
|
kinematic: NEAR_GRABBING_KINEMATIC,
|
||||||
} else {
|
kinematicSetVelocity: true,
|
||||||
this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC);
|
ignoreIK: this.ignoreIK
|
||||||
if (this.state == STATE_NEAR_GRABBING) {
|
});
|
||||||
this.setState(STATE_CONTINUE_NEAR_GRABBING);
|
if (this.actionID === NULL_ACTION_ID) {
|
||||||
|
this.actionID = null;
|
||||||
} else {
|
} else {
|
||||||
// equipping
|
this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC);
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]);
|
if (this.state == STATE_NEAR_GRABBING) {
|
||||||
this.startHandGrasp();
|
this.setState(STATE_CONTINUE_NEAR_GRABBING);
|
||||||
|
} else {
|
||||||
|
// equipping
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]);
|
||||||
|
this.startHandGrasp();
|
||||||
|
|
||||||
|
this.setState(STATE_CONTINUE_EQUIP_BD);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hand === RIGHT_HAND) {
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
|
||||||
|
} else {
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
||||||
|
}
|
||||||
|
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]);
|
||||||
|
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "startNearGrab");
|
||||||
|
|
||||||
this.setState(STATE_CONTINUE_EQUIP_BD);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hand === RIGHT_HAND) {
|
this.currentHandControllerTipPosition =
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
|
(this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;
|
||||||
} else {
|
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
|
||||||
}
|
|
||||||
|
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]);
|
this.currentObjectTime = Date.now();
|
||||||
|
};
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "startNearGrab");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentHandControllerTipPosition =
|
|
||||||
(this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;
|
|
||||||
|
|
||||||
this.currentObjectTime = Date.now();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.continueNearGrabbing = function() {
|
this.continueNearGrabbing = function() {
|
||||||
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
|
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
|
||||||
|
@ -1421,7 +1550,7 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (USE_ENTITY_LINES_FOR_MOVING === true) {
|
if (USE_ENTITY_LINES_FOR_MOVING === true) {
|
||||||
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
|
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger");
|
Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger");
|
||||||
|
@ -1502,7 +1631,34 @@ function MyController(hand) {
|
||||||
|
|
||||||
if (this.grabbedEntity !== null) {
|
if (this.grabbedEntity !== null) {
|
||||||
if (this.actionID !== null) {
|
if (this.actionID !== null) {
|
||||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
//add velocity whatnot
|
||||||
|
var defaultReleaseVelocityData = {
|
||||||
|
disableReleaseVelocity: false
|
||||||
|
};
|
||||||
|
//sometimes we want things to stay right where they are when we let go.
|
||||||
|
var releaseVelocityData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultReleaseVelocityData);
|
||||||
|
if (releaseVelocityData.disableReleaseVelocity === true) {
|
||||||
|
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||||
|
|
||||||
|
Entities.editEntity(this.grabbedEntity, {
|
||||||
|
velocity: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
angularVelocity: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//don't make adjustments
|
||||||
|
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1632,7 +1788,7 @@ Controller.enableMapping(MAPPING_NAME);
|
||||||
var handToDisable = 'none';
|
var handToDisable = 'none';
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
if (handToDisable !== LEFT_HAND && handToDisable!=='both') {
|
if (handToDisable !== LEFT_HAND && handToDisable !== 'both') {
|
||||||
leftController.update();
|
leftController.update();
|
||||||
}
|
}
|
||||||
if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') {
|
if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') {
|
||||||
|
@ -1658,22 +1814,22 @@ handleHandMessages = function(channel, message, sender) {
|
||||||
}
|
}
|
||||||
} else if (channel === 'Hifi-Hand-Grab') {
|
} else if (channel === 'Hifi-Hand-Grab') {
|
||||||
try {
|
try {
|
||||||
var data = JSON.parse(message);
|
var data = JSON.parse(message);
|
||||||
var selectedController = (data.hand === 'left') ? leftController : rightController;
|
var selectedController = (data.hand === 'left') ? leftController : rightController;
|
||||||
selectedController.release();
|
selectedController.release();
|
||||||
selectedController.setState(STATE_EQUIP);
|
selectedController.setState(STATE_EQUIP);
|
||||||
selectedController.grabbedEntity = data.entityID;
|
selectedController.grabbedEntity = data.entityID;
|
||||||
|
|
||||||
} catch (e) { }
|
} catch (e) {}
|
||||||
}
|
|
||||||
else if (channel === 'Hifi-Hand-RayPick-Blacklist' && sender === MyAvatar.sessionUUID) {
|
} else if (channel === 'Hifi-Hand-RayPick-Blacklist') {
|
||||||
try {
|
try {
|
||||||
var data = JSON.parse(message);
|
var data = JSON.parse(message);
|
||||||
var action = data.action;
|
var action = data.action;
|
||||||
var id = data.id;
|
var id = data.id;
|
||||||
var index = blacklist.indexOf(id);
|
var index = blacklist.indexOf(id);
|
||||||
|
|
||||||
if (action === 'add' && index ===-1) {
|
if (action === 'add' && index === -1) {
|
||||||
blacklist.push(id);
|
blacklist.push(id);
|
||||||
}
|
}
|
||||||
if (action === 'remove') {
|
if (action === 'remove') {
|
||||||
|
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -22,33 +22,13 @@ function length(posA, posB) {
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
var EXPECTED_CHANGE = 50;
|
|
||||||
var lastPos = Controller.getReticlePosition();
|
|
||||||
function moveReticleAbsolute(x, y) {
|
function moveReticleAbsolute(x, y) {
|
||||||
var globalPos = Controller.getReticlePosition();
|
var globalPos = Controller.getReticlePosition();
|
||||||
var dX = x - globalPos.x;
|
|
||||||
var dY = y - globalPos.y;
|
|
||||||
|
|
||||||
// some debugging to see if position is jumping around on us...
|
|
||||||
var distanceSinceLastMove = length(lastPos, globalPos);
|
|
||||||
if (distanceSinceLastMove > EXPECTED_CHANGE) {
|
|
||||||
debugPrint("------------------ distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(dX) > EXPECTED_CHANGE) {
|
|
||||||
debugPrint("surpressing unexpectedly large change dX:" + dX + "----------------------------");
|
|
||||||
}
|
|
||||||
if (Math.abs(dY) > EXPECTED_CHANGE) {
|
|
||||||
debugPrint("surpressing unexpectedly large change dY:" + dY + "----------------------------");
|
|
||||||
}
|
|
||||||
|
|
||||||
globalPos.x = x;
|
globalPos.x = x;
|
||||||
globalPos.y = y;
|
globalPos.y = y;
|
||||||
Controller.setReticlePosition(globalPos);
|
Controller.setReticlePosition(globalPos);
|
||||||
lastPos = globalPos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation";
|
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation";
|
||||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
var mapping = Controller.newMapping(MAPPING_NAME);
|
||||||
mapping.from(Controller.Standard.LT).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
|
mapping.from(Controller.Standard.LT).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
|
||||||
|
@ -56,8 +36,6 @@ mapping.from(Controller.Standard.RT).peek().constrainToInteger().to(Controller.A
|
||||||
mapping.enable();
|
mapping.enable();
|
||||||
|
|
||||||
|
|
||||||
var lastRotatedLeft = Vec3.UNIT_NEG_Y;
|
|
||||||
var lastRotatedRight = Vec3.UNIT_NEG_Y;
|
|
||||||
|
|
||||||
function debugPrint(message) {
|
function debugPrint(message) {
|
||||||
if (DEBUGGING) {
|
if (DEBUGGING) {
|
||||||
|
@ -65,24 +43,9 @@ function debugPrint(message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var MAX_WAKE_UP_DISTANCE = 0.005;
|
var leftRightBias = 0.0;
|
||||||
var MIN_WAKE_UP_DISTANCE = 0.001;
|
var filteredRotatedLeft = Vec3.UNIT_NEG_Y;
|
||||||
var INITIAL_WAKE_UP_DISTANCE = MIN_WAKE_UP_DISTANCE;
|
var filteredRotatedRight = Vec3.UNIT_NEG_Y;
|
||||||
var INCREMENTAL_WAKE_UP_DISTANCE = 0.001;
|
|
||||||
|
|
||||||
var MAX_SLEEP_DISTANCE = 0.0004;
|
|
||||||
var MIN_SLEEP_DISTANCE = 0.00001; //0.00002;
|
|
||||||
var INITIAL_SLEEP_DISTANCE = MIN_SLEEP_DISTANCE;
|
|
||||||
var INCREMENTAL_SLEEP_DISTANCE = 0.000002; // 0.00002;
|
|
||||||
|
|
||||||
var leftAsleep = true;
|
|
||||||
var rightAsleep = true;
|
|
||||||
|
|
||||||
var leftWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
|
|
||||||
var rightWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
|
|
||||||
|
|
||||||
var leftSleepDistance = INITIAL_SLEEP_DISTANCE;
|
|
||||||
var rightSleepDistance = INITIAL_SLEEP_DISTANCE;
|
|
||||||
|
|
||||||
Script.update.connect(function(deltaTime) {
|
Script.update.connect(function(deltaTime) {
|
||||||
|
|
||||||
|
@ -96,153 +59,46 @@ Script.update.connect(function(deltaTime) {
|
||||||
var rotatedRight = Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y);
|
var rotatedRight = Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y);
|
||||||
var rotatedLeft = Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y);
|
var rotatedLeft = Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y);
|
||||||
|
|
||||||
var suppressRight = false;
|
|
||||||
var suppressLeft = false;
|
|
||||||
|
|
||||||
// What I really want to do is to slowly increase the epsilon you have to move it
|
|
||||||
// to wake up, the longer you go without moving it
|
|
||||||
var leftDistance = Vec3.distance(rotatedLeft, lastRotatedLeft);
|
|
||||||
var rightDistance = Vec3.distance(rotatedRight, lastRotatedRight);
|
|
||||||
|
|
||||||
// check to see if hand should wakeup or sleep
|
|
||||||
if (leftAsleep) {
|
|
||||||
if (leftDistance > leftWakeUpDistance) {
|
|
||||||
leftAsleep = false;
|
|
||||||
leftSleepDistance = INITIAL_SLEEP_DISTANCE;
|
|
||||||
leftWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
|
|
||||||
} else {
|
|
||||||
// grow the wake up distance to make it harder to wake up
|
|
||||||
leftWakeUpDistance = Math.min(leftWakeUpDistance + INCREMENTAL_WAKE_UP_DISTANCE, MAX_WAKE_UP_DISTANCE);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// we are awake, determine if we should fall asleep, if we haven't moved
|
|
||||||
// at least as much as our sleep distance then we sleep
|
|
||||||
if (leftDistance < leftSleepDistance) {
|
|
||||||
leftAsleep = true;
|
|
||||||
leftSleepDistance = INITIAL_SLEEP_DISTANCE;
|
|
||||||
leftWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
|
|
||||||
} else {
|
|
||||||
// if we moved more than the sleep amount, but we moved less than the max sleep
|
|
||||||
// amount, then increase our liklihood of sleep.
|
|
||||||
if (leftDistance < MAX_SLEEP_DISTANCE) {
|
|
||||||
print("growing sleep....");
|
|
||||||
leftSleepDistance = Math.max(leftSleepDistance + INCREMENTAL_SLEEP_DISTANCE, MAX_SLEEP_DISTANCE);
|
|
||||||
} else {
|
|
||||||
// otherwise reset it to initial
|
|
||||||
leftSleepDistance = INITIAL_SLEEP_DISTANCE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (leftAsleep) {
|
|
||||||
suppressLeft = true;
|
|
||||||
debugPrint("suppressing left not moving enough");
|
|
||||||
}
|
|
||||||
|
|
||||||
// check to see if hand should wakeup or sleep
|
|
||||||
if (rightAsleep) {
|
|
||||||
if (rightDistance > rightWakeUpDistance) {
|
|
||||||
rightAsleep = false;
|
|
||||||
rightSleepDistance = INITIAL_SLEEP_DISTANCE;
|
|
||||||
rightWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
|
|
||||||
} else {
|
|
||||||
// grow the wake up distance to make it harder to wake up
|
|
||||||
rightWakeUpDistance = Math.min(rightWakeUpDistance + INCREMENTAL_WAKE_UP_DISTANCE, MAX_WAKE_UP_DISTANCE);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// we are awake, determine if we should fall asleep, if we haven't moved
|
|
||||||
// at least as much as our sleep distance then we sleep
|
|
||||||
if (rightDistance < rightSleepDistance) {
|
|
||||||
rightAsleep = true;
|
|
||||||
rightSleepDistance = INITIAL_SLEEP_DISTANCE;
|
|
||||||
rightWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
|
|
||||||
} else {
|
|
||||||
// if we moved more than the sleep amount, but we moved less than the max sleep
|
|
||||||
// amount, then increase our liklihood of sleep.
|
|
||||||
if (rightDistance < MAX_SLEEP_DISTANCE) {
|
|
||||||
print("growing sleep....");
|
|
||||||
rightSleepDistance = Math.max(rightSleepDistance + INCREMENTAL_SLEEP_DISTANCE, MAX_SLEEP_DISTANCE);
|
|
||||||
} else {
|
|
||||||
// otherwise reset it to initial
|
|
||||||
rightSleepDistance = INITIAL_SLEEP_DISTANCE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rightAsleep) {
|
|
||||||
suppressRight = true;
|
|
||||||
debugPrint("suppressing right not moving enough");
|
|
||||||
}
|
|
||||||
|
|
||||||
// check to see if hand is on base station
|
|
||||||
if (Vec3.equal(rotatedLeft, Vec3.UNIT_NEG_Y)) {
|
|
||||||
suppressLeft = true;
|
|
||||||
debugPrint("suppressing left on base station");
|
|
||||||
}
|
|
||||||
if (Vec3.equal(rotatedRight, Vec3.UNIT_NEG_Y)) {
|
|
||||||
suppressRight = true;
|
|
||||||
debugPrint("suppressing right on base station");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of last rotations, to detect resting (but not on base station hands) in the future
|
|
||||||
lastRotatedLeft = rotatedLeft;
|
|
||||||
lastRotatedRight = rotatedRight;
|
lastRotatedRight = rotatedRight;
|
||||||
|
|
||||||
if (suppressLeft && suppressRight) {
|
|
||||||
debugPrint("both hands suppressed bail out early");
|
// Decide which hand should be controlling the pointer
|
||||||
return;
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suppressLeft) {
|
// Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers
|
||||||
debugPrint("right only");
|
var VELOCITY_FILTER_GAIN = 1.0;
|
||||||
rotatedLeft = rotatedRight;
|
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));
|
||||||
if (suppressRight) {
|
var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, leftRightBias);
|
||||||
debugPrint("left only");
|
|
||||||
rotatedRight = rotatedLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Average the two hand positions, if either hand is on base station, the
|
|
||||||
// other hand becomes the only used hand and the average is the hand in use
|
|
||||||
var rotated = Vec3.multiply(Vec3.sum(rotatedRight,rotatedLeft), 0.5);
|
|
||||||
|
|
||||||
if (DEBUGGING) {
|
|
||||||
Vec3.print("rotatedRight:", rotatedRight);
|
|
||||||
Vec3.print("rotatedLeft:", rotatedLeft);
|
|
||||||
Vec3.print("rotated:", rotated);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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 absoluteYaw = -rotated.x; // from -1 left to 1 right
|
||||||
|
|
||||||
if (DEBUGGING) {
|
|
||||||
print("absolutePitch:" + absolutePitch);
|
|
||||||
print("absoluteYaw:" + absoluteYaw);
|
|
||||||
Vec3.print("rotated:", rotated);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ROTATION_BOUND = 0.6;
|
var ROTATION_BOUND = 0.6;
|
||||||
var clampYaw = Math.clamp(absoluteYaw, -ROTATION_BOUND, ROTATION_BOUND);
|
var clampYaw = Math.clamp(absoluteYaw, -ROTATION_BOUND, ROTATION_BOUND);
|
||||||
var clampPitch = Math.clamp(absolutePitch, -ROTATION_BOUND, ROTATION_BOUND);
|
var clampPitch = Math.clamp(absolutePitch, -ROTATION_BOUND, ROTATION_BOUND);
|
||||||
if (DEBUGGING) {
|
|
||||||
print("clampYaw:" + clampYaw);
|
|
||||||
print("clampPitch:" + clampPitch);
|
|
||||||
}
|
|
||||||
|
|
||||||
// using only from -ROTATION_BOUND to ROTATION_BOUND
|
// using only from -ROTATION_BOUND to ROTATION_BOUND
|
||||||
var xRatio = (clampYaw + ROTATION_BOUND) / (2 * ROTATION_BOUND);
|
var xRatio = (clampYaw + ROTATION_BOUND) / (2 * ROTATION_BOUND);
|
||||||
var yRatio = (clampPitch + ROTATION_BOUND) / (2 * ROTATION_BOUND);
|
var yRatio = (clampPitch + ROTATION_BOUND) / (2 * ROTATION_BOUND);
|
||||||
|
|
||||||
if (DEBUGGING) {
|
|
||||||
print("xRatio:" + xRatio);
|
|
||||||
print("yRatio:" + yRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
var x = screenSizeX * xRatio;
|
var x = screenSizeX * xRatio;
|
||||||
var y = screenSizeY * yRatio;
|
var y = screenSizeY * yRatio;
|
||||||
|
|
||||||
if (DEBUGGING) {
|
// don't move the reticle with the hand controllers unless the controllers are actually being moved
|
||||||
print("position x:" + x + " y:" + y);
|
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)) {
|
|
||||||
|
if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityMagnitude > MINIMUM_CONTROLLER_ANGULAR_VELOCITY)) {
|
||||||
moveReticleAbsolute(x, y);
|
moveReticleAbsolute(x, y);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -53,7 +53,9 @@ LightOverlayManager = function() {
|
||||||
if (visible != isVisible) {
|
if (visible != isVisible) {
|
||||||
visible = isVisible;
|
visible = isVisible;
|
||||||
for (var id in entityOverlays) {
|
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
|
// Allocate or get an unused overlay
|
||||||
function getOverlay() {
|
function getOverlay() {
|
||||||
if (unusedOverlays.length == 0) {
|
if (unusedOverlays.length == 0) {
|
||||||
var overlay = Overlays.addOverlay("image3d", {
|
var overlay = Overlays.addOverlay("image3d", {});
|
||||||
});
|
|
||||||
allOverlays.push(overlay);
|
allOverlays.push(overlay);
|
||||||
} else {
|
} else {
|
||||||
var overlay = unusedOverlays.pop();
|
var overlay = unusedOverlays.pop();
|
||||||
|
@ -72,7 +73,9 @@ LightOverlayManager = function() {
|
||||||
|
|
||||||
function releaseOverlay(overlay) {
|
function releaseOverlay(overlay) {
|
||||||
unusedOverlays.push(overlay);
|
unusedOverlays.push(overlay);
|
||||||
Overlays.editOverlay(overlay, { visible: false });
|
Overlays.editOverlay(overlay, {
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addEntity(entityID) {
|
function addEntity(entityID) {
|
||||||
|
@ -88,7 +91,11 @@ LightOverlayManager = function() {
|
||||||
visible: visible,
|
visible: visible,
|
||||||
alpha: 0.9,
|
alpha: 0.9,
|
||||||
scale: 0.5,
|
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]);
|
Overlays.deleteOverlay(allOverlays[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
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();
|
||||||
|
});
|
|
@ -29,21 +29,14 @@
|
||||||
this.equipped = false;
|
this.equipped = false;
|
||||||
this.forceMultiplier = 1;
|
this.forceMultiplier = 1;
|
||||||
this.laserLength = 100;
|
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.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.ricochetSound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Guns/Ricochet.L.wav");
|
||||||
this.playRichochetSoundChance = 0.1;
|
this.playRichochetSoundChance = 0.1;
|
||||||
this.fireVolume = 0.2;
|
this.fireVolume = 0.2;
|
||||||
this.bulletForce = 10;
|
this.bulletForce = 10;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.showLaser = false;
|
this.showLaser = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Pistol.prototype = {
|
Pistol.prototype = {
|
||||||
|
@ -58,20 +51,36 @@
|
||||||
if (!this.equipped) {
|
if (!this.equipped) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.toggleWithTriggerPressure();
|
this.updateProps();
|
||||||
if (this.showLaser) {
|
if (this.showLaser) {
|
||||||
this.updateLaser();
|
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() {
|
toggleWithTriggerPressure: function() {
|
||||||
this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[this.hand]);
|
this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[this.hand]);
|
||||||
|
|
||||||
if (this.triggerValue < RELOAD_THRESHOLD) {
|
if (this.triggerValue < RELOAD_THRESHOLD) {
|
||||||
// print('RELOAD');
|
|
||||||
this.canShoot = true;
|
this.canShoot = true;
|
||||||
}
|
}
|
||||||
if (this.canShoot === true && this.triggerValue === 1) {
|
if (this.canShoot === true && this.triggerValue === 1) {
|
||||||
// print('SHOOT');
|
|
||||||
this.fire();
|
this.fire();
|
||||||
this.canShoot = false;
|
this.canShoot = false;
|
||||||
}
|
}
|
||||||
|
@ -91,17 +100,10 @@
|
||||||
|
|
||||||
},
|
},
|
||||||
updateLaser: function() {
|
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, {
|
Overlays.editOverlay(this.laser, {
|
||||||
start: this.barrelPoint,
|
start: this.barrelPoint,
|
||||||
end: laserTip,
|
end: this.laserTip,
|
||||||
alpha: 1
|
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) {
|
triggerPress: function(hand, value) {
|
||||||
if (this.hand === hand && value === 1) {
|
if (this.hand === hand && value === 1) {
|
||||||
//We are pulling trigger on the hand we have the gun in, so fire
|
//We are pulling trigger on the hand we have the gun in, so fire
|
||||||
|
@ -135,15 +124,16 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
fire: function() {
|
fire: function() {
|
||||||
var pickRay = {
|
|
||||||
origin: this.barrelPoint,
|
|
||||||
direction: this.firingDirection
|
|
||||||
};
|
|
||||||
Audio.playSound(this.fireSound, {
|
Audio.playSound(this.fireSound, {
|
||||||
position: this.barrelPoint,
|
position: this.barrelPoint,
|
||||||
volume: this.fireVolume
|
volume: this.fireVolume
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var pickRay = {
|
||||||
|
origin: this.barrelPoint,
|
||||||
|
direction: this.firingDirection
|
||||||
|
};
|
||||||
this.createGunFireEffect(this.barrelPoint)
|
this.createGunFireEffect(this.barrelPoint)
|
||||||
var intersection = Entities.findRayIntersectionBlocking(pickRay, true);
|
var intersection = Entities.findRayIntersectionBlocking(pickRay, true);
|
||||||
if (intersection.intersects) {
|
if (intersection.intersects) {
|
||||||
|
@ -170,11 +160,11 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
createEntityHitEffect: function(position) {
|
createEntityHitEffect: function(position) {
|
||||||
var flash = Entities.addEntity({
|
var sparks = Entities.addEntity({
|
||||||
type: "ParticleEffect",
|
type: "ParticleEffect",
|
||||||
position: position,
|
position: position,
|
||||||
lifetime: 4,
|
lifetime: 4,
|
||||||
"name": "Flash Emitter",
|
"name": "Sparks Emitter",
|
||||||
"color": {
|
"color": {
|
||||||
red: 228,
|
red: 228,
|
||||||
green: 128,
|
green: 128,
|
||||||
|
@ -228,7 +218,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
Script.setTimeout(function() {
|
Script.setTimeout(function() {
|
||||||
Entities.editEntity(flash, {
|
Entities.editEntity(sparks, {
|
||||||
isEmitting: false
|
isEmitting: false
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
|
@ -261,11 +251,11 @@
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"accelerationSpread": {
|
"accelerationSpread": {
|
||||||
"x": .2,
|
"x": 0.2,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"z": .2
|
"z": 0.2
|
||||||
},
|
},
|
||||||
"radiusSpread": .04,
|
"radiusSpread": 0.04,
|
||||||
"particleRadius": 0.07,
|
"particleRadius": 0.07,
|
||||||
"radiusStart": 0.07,
|
"radiusStart": 0.07,
|
||||||
"radiusFinish": 0.07,
|
"radiusFinish": 0.07,
|
||||||
|
@ -282,11 +272,46 @@
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 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",
|
type: "ParticleEffect",
|
||||||
position: position,
|
position: this.barrelPoint,
|
||||||
lifetime: 4,
|
|
||||||
"name": "Muzzle Flash",
|
"name": "Muzzle Flash",
|
||||||
|
isEmitting: false,
|
||||||
"color": {
|
"color": {
|
||||||
red: 228,
|
red: 228,
|
||||||
green: 128,
|
green: 128,
|
||||||
|
@ -339,16 +364,13 @@
|
||||||
"textures": "http://ericrius1.github.io/PartiArt/assets/star.png"
|
"textures": "http://ericrius1.github.io/PartiArt/assets/star.png"
|
||||||
});
|
});
|
||||||
|
|
||||||
Script.setTimeout(function() {
|
Script.setTimeout(function() {
|
||||||
Entities.editEntity(flash, {
|
Entities.editEntity(_this.flash, {parentID: _this.entityID});
|
||||||
isEmitting: false
|
}, 500)
|
||||||
});
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// entity scripts always need to return a newly constructed object of our type
|
// entity scripts always need to return a newly constructed object of our type
|
||||||
return new Pistol();
|
return new Pistol();
|
||||||
});
|
});
|
||||||
|
|
|
@ -109,7 +109,9 @@ add_dependency_external_projects(sdl2)
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
add_dependency_external_projects(OpenVR)
|
add_dependency_external_projects(OpenVR)
|
||||||
endif()
|
endif()
|
||||||
|
if(WIN32 OR APPLE)
|
||||||
|
add_dependency_external_projects(neuron)
|
||||||
|
endif()
|
||||||
|
|
||||||
# disable /OPT:REF and /OPT:ICF for the Debug builds
|
# disable /OPT:REF and /OPT:ICF for the Debug builds
|
||||||
# This will prevent the following linker warnings
|
# This will prevent the following linker warnings
|
||||||
|
|
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" }
|
||||||
|
]
|
||||||
|
}
|
|
@ -3486,78 +3486,86 @@ namespace render {
|
||||||
|
|
||||||
// Background rendering decision
|
// Background rendering decision
|
||||||
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
|
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
|
// this line intentionally left blank
|
||||||
} else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_DOME) {
|
} else {
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
|
if (backgroundMode == model::SunSkyStage::SKY_BOX) {
|
||||||
PerformanceTimer perfTimer("stars");
|
auto skybox = skyStage->getSkybox();
|
||||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
if (skybox && skybox->getCubemap() && skybox->getCubemap()->isDefined()) {
|
||||||
"Application::payloadRender<BackgroundRenderData>() ... stars...");
|
PerformanceTimer perfTimer("skybox");
|
||||||
// should be the first rendering pass - w/o depth buffer / lighting
|
skybox->render(batch, *(args->_viewFrustum));
|
||||||
|
} else {
|
||||||
// compute starfield alpha based on distance from atmosphere
|
// If no skybox texture is available, render the SKY_DOME while it loads
|
||||||
float alpha = 1.0f;
|
backgroundMode = model::SunSkyStage::SKY_DOME;
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_BOX) {
|
if (backgroundMode == model::SunSkyStage::SKY_DOME) {
|
||||||
PerformanceTimer perfTimer("skybox");
|
if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
|
||||||
auto skybox = skyStage->getSkybox();
|
PerformanceTimer perfTimer("stars");
|
||||||
if (skybox) {
|
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||||
skybox->render(batch, *(args->_viewFrustum));
|
"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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -310,8 +310,8 @@ QVector<QUuid> AvatarManager::getAvatarIdentifiers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarData* AvatarManager::getAvatar(QUuid avatarID) {
|
AvatarData* AvatarManager::getAvatar(QUuid avatarID) {
|
||||||
QReadLocker locker(&_hashLock);
|
// Null/Default-constructed QUuids will return MyAvatar
|
||||||
return _avatarHash[avatarID].get(); // Non-obvious: A bogus avatarID answers your own avatar.
|
return getAvatarBySessionID(avatarID).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
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 <RegisteredMetaTypes.h>
|
||||||
#include "AnimVariant.h" // which has AnimVariant/AnimVariantMap
|
#include "AnimVariant.h" // which has AnimVariant/AnimVariantMap
|
||||||
|
|
||||||
|
const AnimVariant AnimVariant::False = AnimVariant();
|
||||||
|
|
||||||
QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const {
|
QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const {
|
||||||
if (QThread::currentThread() != engine->thread()) {
|
if (QThread::currentThread() != engine->thread()) {
|
||||||
qCWarning(animation) << "Cannot create Javacript object from non-script thread" << QThread::currentThread();
|
qCWarning(animation) << "Cannot create Javacript object from non-script thread" << QThread::currentThread();
|
||||||
|
|
|
@ -34,6 +34,8 @@ public:
|
||||||
NumTypes
|
NumTypes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const AnimVariant False;
|
||||||
|
|
||||||
AnimVariant() : _type(Type::Bool) { memset(&_val, 0, sizeof(_val)); }
|
AnimVariant() : _type(Type::Bool) { memset(&_val, 0, sizeof(_val)); }
|
||||||
AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; }
|
AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; }
|
||||||
AnimVariant(int value) : _type(Type::Int) { _val.intVal = 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 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; }
|
void setString(const QString& value) { assert(_type == Type::String); _stringVal = value; }
|
||||||
|
|
||||||
bool getBool() const { assert(_type == Type::Bool); return _val.boolVal; }
|
bool getBool() const {
|
||||||
int getInt() const { assert(_type == Type::Int || _type == Type::Float); return _type == Type::Float ? (int)_val.floats[0] : _val.intVal; }
|
if (_type == Type::Bool) {
|
||||||
float getFloat() const { assert(_type == Type::Float || _type == Type::Int); return _type == Type::Int ? (float)_val.intVal : _val.floats[0]; }
|
return _val.boolVal;
|
||||||
|
} else if (_type == Type::Int) {
|
||||||
const glm::vec3& getVec3() const { assert(_type == Type::Vec3); return *reinterpret_cast<const glm::vec3*>(&_val); }
|
return _val.intVal != 0;
|
||||||
const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast<const glm::quat*>(&_val); }
|
} else {
|
||||||
const QString& getString() const { assert(_type == Type::String); return _stringVal; }
|
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:
|
protected:
|
||||||
Type _type;
|
Type _type;
|
||||||
|
@ -71,7 +110,7 @@ protected:
|
||||||
union {
|
union {
|
||||||
bool boolVal;
|
bool boolVal;
|
||||||
int intVal;
|
int intVal;
|
||||||
float floats[16];
|
float floats[4];
|
||||||
} _val;
|
} _val;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -172,6 +211,15 @@ public:
|
||||||
void clearMap() { _map.clear(); }
|
void clearMap() { _map.clear(); }
|
||||||
bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); }
|
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.
|
// 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;
|
QScriptValue animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const;
|
||||||
// Side-effect us with the value of object's own properties. (No inherited properties.)
|
// Side-effect us with the value of object's own properties. (No inherited properties.)
|
||||||
|
|
|
@ -289,8 +289,10 @@ void Rig::clearJointState(int index) {
|
||||||
|
|
||||||
void Rig::clearJointStates() {
|
void Rig::clearJointStates() {
|
||||||
_internalPoseSet._overrideFlags.clear();
|
_internalPoseSet._overrideFlags.clear();
|
||||||
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints());
|
if (_animSkeleton) {
|
||||||
_internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints());
|
||||||
|
_internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::clearJointAnimationPriority(int index) {
|
void Rig::clearJointAnimationPriority(int index) {
|
||||||
|
|
|
@ -88,15 +88,73 @@ namespace controller {
|
||||||
|
|
||||||
// No correlation to SDL
|
// No correlation to SDL
|
||||||
enum StandardPoseChannel {
|
enum StandardPoseChannel {
|
||||||
LEFT_HAND = 0,
|
HIPS = 0,
|
||||||
RIGHT_HAND,
|
RIGHT_UP_LEG,
|
||||||
|
RIGHT_LEG,
|
||||||
|
RIGHT_FOOT,
|
||||||
|
LEFT_UP_LEG,
|
||||||
|
LEFT_LEG,
|
||||||
|
LEFT_FOOT,
|
||||||
|
SPINE,
|
||||||
|
SPINE1,
|
||||||
|
SPINE2,
|
||||||
|
SPINE3,
|
||||||
|
NECK,
|
||||||
HEAD,
|
HEAD,
|
||||||
|
RIGHT_SHOULDER,
|
||||||
|
RIGHT_ARM,
|
||||||
|
RIGHT_FORE_ARM,
|
||||||
|
RIGHT_HAND,
|
||||||
|
RIGHT_HAND_THUMB1,
|
||||||
|
RIGHT_HAND_THUMB2,
|
||||||
|
RIGHT_HAND_THUMB3,
|
||||||
|
RIGHT_HAND_THUMB4,
|
||||||
|
RIGHT_HAND_INDEX1,
|
||||||
|
RIGHT_HAND_INDEX2,
|
||||||
|
RIGHT_HAND_INDEX3,
|
||||||
|
RIGHT_HAND_INDEX4,
|
||||||
|
RIGHT_HAND_MIDDLE1,
|
||||||
|
RIGHT_HAND_MIDDLE2,
|
||||||
|
RIGHT_HAND_MIDDLE3,
|
||||||
|
RIGHT_HAND_MIDDLE4,
|
||||||
|
RIGHT_HAND_RING1,
|
||||||
|
RIGHT_HAND_RING2,
|
||||||
|
RIGHT_HAND_RING3,
|
||||||
|
RIGHT_HAND_RING4,
|
||||||
|
RIGHT_HAND_PINKY1,
|
||||||
|
RIGHT_HAND_PINKY2,
|
||||||
|
RIGHT_HAND_PINKY3,
|
||||||
|
RIGHT_HAND_PINKY4,
|
||||||
|
LEFT_SHOULDER,
|
||||||
|
LEFT_ARM,
|
||||||
|
LEFT_FORE_ARM,
|
||||||
|
LEFT_HAND,
|
||||||
|
LEFT_HAND_THUMB1,
|
||||||
|
LEFT_HAND_THUMB2,
|
||||||
|
LEFT_HAND_THUMB3,
|
||||||
|
LEFT_HAND_THUMB4,
|
||||||
|
LEFT_HAND_INDEX1,
|
||||||
|
LEFT_HAND_INDEX2,
|
||||||
|
LEFT_HAND_INDEX3,
|
||||||
|
LEFT_HAND_INDEX4,
|
||||||
|
LEFT_HAND_MIDDLE1,
|
||||||
|
LEFT_HAND_MIDDLE2,
|
||||||
|
LEFT_HAND_MIDDLE3,
|
||||||
|
LEFT_HAND_MIDDLE4,
|
||||||
|
LEFT_HAND_RING1,
|
||||||
|
LEFT_HAND_RING2,
|
||||||
|
LEFT_HAND_RING3,
|
||||||
|
LEFT_HAND_RING4,
|
||||||
|
LEFT_HAND_PINKY1,
|
||||||
|
LEFT_HAND_PINKY2,
|
||||||
|
LEFT_HAND_PINKY3,
|
||||||
|
LEFT_HAND_PINKY4,
|
||||||
NUM_STANDARD_POSES
|
NUM_STANDARD_POSES
|
||||||
};
|
};
|
||||||
|
|
||||||
enum StandardCounts {
|
enum StandardCounts {
|
||||||
TRIGGERS = 2,
|
TRIGGERS = 2,
|
||||||
ANALOG_STICKS = 2,
|
ANALOG_STICKS = 2,
|
||||||
POSES = 2, // FIXME 3? if we want to expose the head?
|
POSES = NUM_STANDARD_POSES
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,12 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
|
gpu::TexturePointer skymap = skybox.getCubemap();
|
||||||
|
// FIXME: skymap->isDefined may not be threadsafe
|
||||||
|
assert(skymap && skymap->isDefined());
|
||||||
|
|
||||||
glm::mat4 projMat;
|
glm::mat4 projMat;
|
||||||
viewFrustum.evalProjectionMatrix(projMat);
|
viewFrustum.evalProjectionMatrix(projMat);
|
||||||
|
|
||||||
|
@ -106,11 +111,6 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky
|
||||||
batch.setViewTransform(viewTransform);
|
batch.setViewTransform(viewTransform);
|
||||||
batch.setModelTransform(Transform()); // only for Mac
|
batch.setModelTransform(Transform()); // only for Mac
|
||||||
|
|
||||||
gpu::TexturePointer skymap;
|
|
||||||
if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) {
|
|
||||||
skymap = skybox.getCubemap();
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.setPipeline(thePipeline);
|
batch.setPipeline(thePipeline);
|
||||||
batch.setUniformBuffer(SKYBOX_CONSTANTS_SLOT, skybox._dataBuffer);
|
batch.setUniformBuffer(SKYBOX_CONSTANTS_SLOT, skybox._dataBuffer);
|
||||||
batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, skymap);
|
batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, skymap);
|
||||||
|
@ -118,6 +118,5 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky
|
||||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||||
|
|
||||||
batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, nullptr);
|
batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, nullptr);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -234,6 +234,7 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes)
|
||||||
void NodeList::sendDomainServerCheckIn() {
|
void NodeList::sendDomainServerCheckIn() {
|
||||||
if (_isShuttingDown) {
|
if (_isShuttingDown) {
|
||||||
qCDebug(networking) << "Refusing to send a domain-server check in while shutting down.";
|
qCDebug(networking) << "Refusing to send a domain-server check in while shutting down.";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_publicSockAddr.isNull()) {
|
if (_publicSockAddr.isNull()) {
|
||||||
|
|
|
@ -48,6 +48,10 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skybox._procedural && skybox._procedural->_enabled && skybox._procedural->ready()) {
|
if (skybox._procedural && skybox._procedural->_enabled && skybox._procedural->ready()) {
|
||||||
|
gpu::TexturePointer skymap = skybox.getCubemap();
|
||||||
|
// FIXME: skymap->isDefined may not be threadsafe
|
||||||
|
assert(skymap && skymap->isDefined());
|
||||||
|
|
||||||
glm::mat4 projMat;
|
glm::mat4 projMat;
|
||||||
viewFrustum.evalProjectionMatrix(projMat);
|
viewFrustum.evalProjectionMatrix(projMat);
|
||||||
|
|
||||||
|
@ -56,10 +60,7 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum,
|
||||||
batch.setProjectionTransform(projMat);
|
batch.setProjectionTransform(projMat);
|
||||||
batch.setViewTransform(viewTransform);
|
batch.setViewTransform(viewTransform);
|
||||||
batch.setModelTransform(Transform()); // only for Mac
|
batch.setModelTransform(Transform()); // only for Mac
|
||||||
|
batch.setResourceTexture(0, skybox.getCubemap());
|
||||||
if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) {
|
|
||||||
batch.setResourceTexture(0, skybox.getCubemap());
|
|
||||||
}
|
|
||||||
|
|
||||||
skybox._procedural->prepare(batch, glm::vec3(0), glm::vec3(1));
|
skybox._procedural->prepare(batch, glm::vec3(0), glm::vec3(1));
|
||||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||||
|
|
|
@ -82,6 +82,7 @@ public:
|
||||||
static const vec3& RIGHT;
|
static const vec3& RIGHT;
|
||||||
static const vec3& UP;
|
static const vec3& UP;
|
||||||
static const vec3& FRONT;
|
static const vec3& FRONT;
|
||||||
|
static const vec3 ZERO4;
|
||||||
};
|
};
|
||||||
|
|
||||||
// These pack/unpack functions are designed to start specific known types in as efficient a manner
|
// These pack/unpack functions are designed to start specific known types in as efficient a manner
|
||||||
|
|
13
plugins/hifiNeuron/CMakeLists.txt
Normal file
13
plugins/hifiNeuron/CMakeLists.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#
|
||||||
|
# Created by Anthony Thibault on 2015/12/18
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
|
||||||
|
set(TARGET_NAME hifiNeuron)
|
||||||
|
setup_hifi_plugin(Script Qml Widgets)
|
||||||
|
link_hifi_libraries(shared controllers plugins input-plugins)
|
||||||
|
target_neuron()
|
||||||
|
|
557
plugins/hifiNeuron/src/NeuronPlugin.cpp
Normal file
557
plugins/hifiNeuron/src/NeuronPlugin.cpp
Normal file
|
@ -0,0 +1,557 @@
|
||||||
|
//
|
||||||
|
// NeuronPlugin.cpp
|
||||||
|
// input-plugins/src/input-plugins
|
||||||
|
//
|
||||||
|
// Created by Anthony Thibault on 12/18/2015.
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "NeuronPlugin.h"
|
||||||
|
|
||||||
|
#include <controllers/UserInputMapper.h>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
#include <PathUtils.h>
|
||||||
|
#include <DebugDraw.h>
|
||||||
|
#include <cassert>
|
||||||
|
#include <NumericalConstants.h>
|
||||||
|
#include <StreamUtils.h>
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(inputplugins)
|
||||||
|
Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins")
|
||||||
|
|
||||||
|
#define __OS_XUN__ 1
|
||||||
|
#define BOOL int
|
||||||
|
|
||||||
|
#ifdef HAVE_NEURON
|
||||||
|
#include <NeuronDataReader.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const QString NeuronPlugin::NAME = "Neuron";
|
||||||
|
const QString NeuronPlugin::NEURON_ID_STRING = "Perception Neuron";
|
||||||
|
|
||||||
|
// indices of joints of the Neuron standard skeleton.
|
||||||
|
// This is 'almost' the same as the High Fidelity standard skeleton.
|
||||||
|
// It is missing a thumb joint.
|
||||||
|
enum NeuronJointIndex {
|
||||||
|
Hips = 0,
|
||||||
|
RightUpLeg,
|
||||||
|
RightLeg,
|
||||||
|
RightFoot,
|
||||||
|
LeftUpLeg,
|
||||||
|
LeftLeg,
|
||||||
|
LeftFoot,
|
||||||
|
Spine,
|
||||||
|
Spine1,
|
||||||
|
Spine2,
|
||||||
|
Spine3,
|
||||||
|
Neck,
|
||||||
|
Head,
|
||||||
|
RightShoulder,
|
||||||
|
RightArm,
|
||||||
|
RightForeArm,
|
||||||
|
RightHand,
|
||||||
|
RightHandThumb1,
|
||||||
|
RightHandThumb2,
|
||||||
|
RightHandThumb3,
|
||||||
|
RightInHandIndex,
|
||||||
|
RightHandIndex1,
|
||||||
|
RightHandIndex2,
|
||||||
|
RightHandIndex3,
|
||||||
|
RightInHandMiddle,
|
||||||
|
RightHandMiddle1,
|
||||||
|
RightHandMiddle2,
|
||||||
|
RightHandMiddle3,
|
||||||
|
RightInHandRing,
|
||||||
|
RightHandRing1,
|
||||||
|
RightHandRing2,
|
||||||
|
RightHandRing3,
|
||||||
|
RightInHandPinky,
|
||||||
|
RightHandPinky1,
|
||||||
|
RightHandPinky2,
|
||||||
|
RightHandPinky3,
|
||||||
|
LeftShoulder,
|
||||||
|
LeftArm,
|
||||||
|
LeftForeArm,
|
||||||
|
LeftHand,
|
||||||
|
LeftHandThumb1,
|
||||||
|
LeftHandThumb2,
|
||||||
|
LeftHandThumb3,
|
||||||
|
LeftInHandIndex,
|
||||||
|
LeftHandIndex1,
|
||||||
|
LeftHandIndex2,
|
||||||
|
LeftHandIndex3,
|
||||||
|
LeftInHandMiddle,
|
||||||
|
LeftHandMiddle1,
|
||||||
|
LeftHandMiddle2,
|
||||||
|
LeftHandMiddle3,
|
||||||
|
LeftInHandRing,
|
||||||
|
LeftHandRing1,
|
||||||
|
LeftHandRing2,
|
||||||
|
LeftHandRing3,
|
||||||
|
LeftInHandPinky,
|
||||||
|
LeftHandPinky1,
|
||||||
|
LeftHandPinky2,
|
||||||
|
LeftHandPinky3,
|
||||||
|
Size
|
||||||
|
};
|
||||||
|
|
||||||
|
// Almost a direct mapping except for LEFT_HAND_THUMB1 and RIGHT_HAND_THUMB1,
|
||||||
|
// which are not present in the Neuron standard skeleton.
|
||||||
|
static controller::StandardPoseChannel neuronJointIndexToPoseIndexMap[NeuronJointIndex::Size] = {
|
||||||
|
controller::HIPS,
|
||||||
|
controller::RIGHT_UP_LEG,
|
||||||
|
controller::RIGHT_LEG,
|
||||||
|
controller::RIGHT_FOOT,
|
||||||
|
controller::LEFT_UP_LEG,
|
||||||
|
controller::LEFT_LEG,
|
||||||
|
controller::LEFT_FOOT,
|
||||||
|
controller::SPINE,
|
||||||
|
controller::SPINE1,
|
||||||
|
controller::SPINE2,
|
||||||
|
controller::SPINE3,
|
||||||
|
controller::NECK,
|
||||||
|
controller::HEAD,
|
||||||
|
controller::RIGHT_SHOULDER,
|
||||||
|
controller::RIGHT_ARM,
|
||||||
|
controller::RIGHT_FORE_ARM,
|
||||||
|
controller::RIGHT_HAND,
|
||||||
|
controller::RIGHT_HAND_THUMB2,
|
||||||
|
controller::RIGHT_HAND_THUMB3,
|
||||||
|
controller::RIGHT_HAND_THUMB4,
|
||||||
|
controller::RIGHT_HAND_INDEX1,
|
||||||
|
controller::RIGHT_HAND_INDEX2,
|
||||||
|
controller::RIGHT_HAND_INDEX3,
|
||||||
|
controller::RIGHT_HAND_INDEX4,
|
||||||
|
controller::RIGHT_HAND_MIDDLE1,
|
||||||
|
controller::RIGHT_HAND_MIDDLE2,
|
||||||
|
controller::RIGHT_HAND_MIDDLE3,
|
||||||
|
controller::RIGHT_HAND_MIDDLE4,
|
||||||
|
controller::RIGHT_HAND_RING1,
|
||||||
|
controller::RIGHT_HAND_RING2,
|
||||||
|
controller::RIGHT_HAND_RING3,
|
||||||
|
controller::RIGHT_HAND_RING4,
|
||||||
|
controller::RIGHT_HAND_PINKY1,
|
||||||
|
controller::RIGHT_HAND_PINKY2,
|
||||||
|
controller::RIGHT_HAND_PINKY3,
|
||||||
|
controller::RIGHT_HAND_PINKY4,
|
||||||
|
controller::LEFT_SHOULDER,
|
||||||
|
controller::LEFT_ARM,
|
||||||
|
controller::LEFT_FORE_ARM,
|
||||||
|
controller::LEFT_HAND,
|
||||||
|
controller::LEFT_HAND_THUMB2,
|
||||||
|
controller::LEFT_HAND_THUMB3,
|
||||||
|
controller::LEFT_HAND_THUMB4,
|
||||||
|
controller::LEFT_HAND_INDEX1,
|
||||||
|
controller::LEFT_HAND_INDEX2,
|
||||||
|
controller::LEFT_HAND_INDEX3,
|
||||||
|
controller::LEFT_HAND_INDEX4,
|
||||||
|
controller::LEFT_HAND_MIDDLE1,
|
||||||
|
controller::LEFT_HAND_MIDDLE2,
|
||||||
|
controller::LEFT_HAND_MIDDLE3,
|
||||||
|
controller::LEFT_HAND_MIDDLE4,
|
||||||
|
controller::LEFT_HAND_RING1,
|
||||||
|
controller::LEFT_HAND_RING2,
|
||||||
|
controller::LEFT_HAND_RING3,
|
||||||
|
controller::LEFT_HAND_RING4,
|
||||||
|
controller::LEFT_HAND_PINKY1,
|
||||||
|
controller::LEFT_HAND_PINKY2,
|
||||||
|
controller::LEFT_HAND_PINKY3,
|
||||||
|
controller::LEFT_HAND_PINKY4
|
||||||
|
};
|
||||||
|
|
||||||
|
// in rig frame
|
||||||
|
static glm::vec3 rightHandThumb1DefaultAbsTranslation(-2.155500650405884, -0.7610001564025879, 2.685631036758423);
|
||||||
|
static glm::vec3 leftHandThumb1DefaultAbsTranslation(2.1555817127227783, -0.7603635787963867, 2.6856393814086914);
|
||||||
|
|
||||||
|
// default translations (cm)
|
||||||
|
static glm::vec3 neuronJointTranslations[NeuronJointIndex::Size] = {
|
||||||
|
{131.901, 95.6602, -27.9815},
|
||||||
|
{-9.55907, -1.58772, 0.0760284},
|
||||||
|
{0.0144232, -41.4683, -0.105322},
|
||||||
|
{1.59348, -41.5875, -0.557237},
|
||||||
|
{9.72077, -1.68926, -0.280643},
|
||||||
|
{0.0886684, -43.1586, -0.0111596},
|
||||||
|
{-2.98473, -44.0517, 0.0694456},
|
||||||
|
{0.110967, 16.3959, 0.140463},
|
||||||
|
{0.0500451, 10.0238, 0.0731921},
|
||||||
|
{0.061568, 10.4352, 0.0583075},
|
||||||
|
{0.0500606, 10.0217, 0.0711083},
|
||||||
|
{0.0317731, 10.7176, 0.0779325},
|
||||||
|
{-0.0204253, 9.71067, 0.131734},
|
||||||
|
{-3.24245, 7.13584, 0.185638},
|
||||||
|
{-13.0885, -0.0877601, 0.176065},
|
||||||
|
{-27.2674, 0.0688724, 0.0272146},
|
||||||
|
{-26.7673, 0.0301916, 0.0102847},
|
||||||
|
{-2.56017, 0.195537, 3.20968},
|
||||||
|
{-3.78796, 0, 0},
|
||||||
|
{-2.63141, 0, 0},
|
||||||
|
{-3.31579, 0.522947, 2.03495},
|
||||||
|
{-5.36589, -0.0939789, 1.02771},
|
||||||
|
{-3.72278, 0, 0},
|
||||||
|
{-2.11074, 0, 0},
|
||||||
|
{-3.47874, 0.532042, 0.778358},
|
||||||
|
{-5.32194, -0.0864, 0.322863},
|
||||||
|
{-4.06232, 0, 0},
|
||||||
|
{-2.54653, 0, 0},
|
||||||
|
{-3.46131, 0.553263, -0.132632},
|
||||||
|
{-4.76716, -0.0227368, -0.492632},
|
||||||
|
{-3.54073, 0, 0},
|
||||||
|
{-2.45634, 0, 0},
|
||||||
|
{-3.25137, 0.482779, -1.23613},
|
||||||
|
{-4.25937, -0.0227368, -1.12168},
|
||||||
|
{-2.83528, 0, 0},
|
||||||
|
{-1.79166, 0, 0},
|
||||||
|
{3.25624, 7.13148, -0.131575},
|
||||||
|
{13.149, -0.052598, -0.125076},
|
||||||
|
{27.2903, 0.00282644, -0.0181535},
|
||||||
|
{26.6602, 0.000969969, -0.0487599},
|
||||||
|
{2.56017, 0.195537, 3.20968},
|
||||||
|
{3.78796, 0, 0},
|
||||||
|
{2.63141, 0, 0},
|
||||||
|
{3.31579, 0.522947, 2.03495},
|
||||||
|
{5.36589, -0.0939789, 1.02771},
|
||||||
|
{3.72278, 0, 0},
|
||||||
|
{2.11074, 0, 0},
|
||||||
|
{3.47874, 0.532042, 0.778358},
|
||||||
|
{5.32194, -0.0864, 0.322863},
|
||||||
|
{4.06232, 0, 0},
|
||||||
|
{2.54653, 0, 0},
|
||||||
|
{3.46131, 0.553263, -0.132632},
|
||||||
|
{4.76716, -0.0227368, -0.492632},
|
||||||
|
{3.54073, 0, 0},
|
||||||
|
{2.45634, 0, 0},
|
||||||
|
{3.25137, 0.482779, -1.23613},
|
||||||
|
{4.25937, -0.0227368, -1.12168},
|
||||||
|
{2.83528, 0, 0},
|
||||||
|
{1.79166, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
static controller::StandardPoseChannel neuronJointIndexToPoseIndex(NeuronJointIndex i) {
|
||||||
|
assert(i >= 0 && i < NeuronJointIndex::Size);
|
||||||
|
if (i >= 0 && i < NeuronJointIndex::Size) {
|
||||||
|
return neuronJointIndexToPoseIndexMap[i];
|
||||||
|
} else {
|
||||||
|
return (controller::StandardPoseChannel)0; // not sure what to do here, but don't crash!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* controllerJointName(controller::StandardPoseChannel i) {
|
||||||
|
switch (i) {
|
||||||
|
case controller::HIPS: return "Hips";
|
||||||
|
case controller::RIGHT_UP_LEG: return "RightUpLeg";
|
||||||
|
case controller::RIGHT_LEG: return "RightLeg";
|
||||||
|
case controller::RIGHT_FOOT: return "RightFoot";
|
||||||
|
case controller::LEFT_UP_LEG: return "LeftUpLeg";
|
||||||
|
case controller::LEFT_LEG: return "LeftLeg";
|
||||||
|
case controller::LEFT_FOOT: return "LeftFoot";
|
||||||
|
case controller::SPINE: return "Spine";
|
||||||
|
case controller::SPINE1: return "Spine1";
|
||||||
|
case controller::SPINE2: return "Spine2";
|
||||||
|
case controller::SPINE3: return "Spine3";
|
||||||
|
case controller::NECK: return "Neck";
|
||||||
|
case controller::HEAD: return "Head";
|
||||||
|
case controller::RIGHT_SHOULDER: return "RightShoulder";
|
||||||
|
case controller::RIGHT_ARM: return "RightArm";
|
||||||
|
case controller::RIGHT_FORE_ARM: return "RightForeArm";
|
||||||
|
case controller::RIGHT_HAND: return "RightHand";
|
||||||
|
case controller::RIGHT_HAND_THUMB1: return "RightHandThumb1";
|
||||||
|
case controller::RIGHT_HAND_THUMB2: return "RightHandThumb2";
|
||||||
|
case controller::RIGHT_HAND_THUMB3: return "RightHandThumb3";
|
||||||
|
case controller::RIGHT_HAND_THUMB4: return "RightHandThumb4";
|
||||||
|
case controller::RIGHT_HAND_INDEX1: return "RightHandIndex1";
|
||||||
|
case controller::RIGHT_HAND_INDEX2: return "RightHandIndex2";
|
||||||
|
case controller::RIGHT_HAND_INDEX3: return "RightHandIndex3";
|
||||||
|
case controller::RIGHT_HAND_INDEX4: return "RightHandIndex4";
|
||||||
|
case controller::RIGHT_HAND_MIDDLE1: return "RightHandMiddle1";
|
||||||
|
case controller::RIGHT_HAND_MIDDLE2: return "RightHandMiddle2";
|
||||||
|
case controller::RIGHT_HAND_MIDDLE3: return "RightHandMiddle3";
|
||||||
|
case controller::RIGHT_HAND_MIDDLE4: return "RightHandMiddle4";
|
||||||
|
case controller::RIGHT_HAND_RING1: return "RightHandRing1";
|
||||||
|
case controller::RIGHT_HAND_RING2: return "RightHandRing2";
|
||||||
|
case controller::RIGHT_HAND_RING3: return "RightHandRing3";
|
||||||
|
case controller::RIGHT_HAND_RING4: return "RightHandRing4";
|
||||||
|
case controller::RIGHT_HAND_PINKY1: return "RightHandPinky1";
|
||||||
|
case controller::RIGHT_HAND_PINKY2: return "RightHandPinky2";
|
||||||
|
case controller::RIGHT_HAND_PINKY3: return "RightHandPinky3";
|
||||||
|
case controller::RIGHT_HAND_PINKY4: return "RightHandPinky4";
|
||||||
|
case controller::LEFT_SHOULDER: return "LeftShoulder";
|
||||||
|
case controller::LEFT_ARM: return "LeftArm";
|
||||||
|
case controller::LEFT_FORE_ARM: return "LeftForeArm";
|
||||||
|
case controller::LEFT_HAND: return "LeftHand";
|
||||||
|
case controller::LEFT_HAND_THUMB1: return "LeftHandThumb1";
|
||||||
|
case controller::LEFT_HAND_THUMB2: return "LeftHandThumb2";
|
||||||
|
case controller::LEFT_HAND_THUMB3: return "LeftHandThumb3";
|
||||||
|
case controller::LEFT_HAND_THUMB4: return "LeftHandThumb4";
|
||||||
|
case controller::LEFT_HAND_INDEX1: return "LeftHandIndex1";
|
||||||
|
case controller::LEFT_HAND_INDEX2: return "LeftHandIndex2";
|
||||||
|
case controller::LEFT_HAND_INDEX3: return "LeftHandIndex3";
|
||||||
|
case controller::LEFT_HAND_INDEX4: return "LeftHandIndex4";
|
||||||
|
case controller::LEFT_HAND_MIDDLE1: return "LeftHandMiddle1";
|
||||||
|
case controller::LEFT_HAND_MIDDLE2: return "LeftHandMiddle2";
|
||||||
|
case controller::LEFT_HAND_MIDDLE3: return "LeftHandMiddle3";
|
||||||
|
case controller::LEFT_HAND_MIDDLE4: return "LeftHandMiddle4";
|
||||||
|
case controller::LEFT_HAND_RING1: return "LeftHandRing1";
|
||||||
|
case controller::LEFT_HAND_RING2: return "LeftHandRing2";
|
||||||
|
case controller::LEFT_HAND_RING3: return "LeftHandRing3";
|
||||||
|
case controller::LEFT_HAND_RING4: return "LeftHandRing4";
|
||||||
|
case controller::LEFT_HAND_PINKY1: return "LeftHandPinky1";
|
||||||
|
case controller::LEFT_HAND_PINKY2: return "LeftHandPinky2";
|
||||||
|
case controller::LEFT_HAND_PINKY3: return "LeftHandPinky3";
|
||||||
|
case controller::LEFT_HAND_PINKY4: return "LeftHandPinky4";
|
||||||
|
default: return "???";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert between YXZ neuron euler angles in degrees to quaternion
|
||||||
|
// this is the default setting in the Axis Neuron server.
|
||||||
|
static quat eulerToQuat(vec3 euler) {
|
||||||
|
// euler.x and euler.y are swaped, WTF.
|
||||||
|
glm::vec3 e = glm::vec3(euler.y, euler.x, euler.z) * RADIANS_PER_DEGREE;
|
||||||
|
return (glm::angleAxis(e.y, Vectors::UNIT_Y) *
|
||||||
|
glm::angleAxis(e.x, Vectors::UNIT_X) *
|
||||||
|
glm::angleAxis(e.z, Vectors::UNIT_Z));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_NEURON
|
||||||
|
|
||||||
|
//
|
||||||
|
// neuronDataReader SDK callback functions
|
||||||
|
//
|
||||||
|
|
||||||
|
// NOTE: must be thread-safe
|
||||||
|
void FrameDataReceivedCallback(void* context, SOCKET_REF sender, BvhDataHeaderEx* header, float* data) {
|
||||||
|
|
||||||
|
auto neuronPlugin = reinterpret_cast<NeuronPlugin*>(context);
|
||||||
|
|
||||||
|
// version 1.0
|
||||||
|
if (header->DataVersion.Major == 1 && header->DataVersion.Minor == 0) {
|
||||||
|
|
||||||
|
// skip reference joint if present
|
||||||
|
if (header->WithReference && header->WithDisp) {
|
||||||
|
data += 6;
|
||||||
|
} else if (header->WithReference && !header->WithDisp) {
|
||||||
|
data += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->WithDisp) {
|
||||||
|
// enter mutex
|
||||||
|
std::lock_guard<std::mutex> guard(neuronPlugin->_jointsMutex);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Data is 6 floats per joint: 3 position values, 3 rotation euler angles (degrees)
|
||||||
|
//
|
||||||
|
|
||||||
|
// resize vector if necessary
|
||||||
|
const size_t NUM_FLOATS_PER_JOINT = 6;
|
||||||
|
const size_t NUM_JOINTS = header->DataCount / NUM_FLOATS_PER_JOINT;
|
||||||
|
if (neuronPlugin->_joints.size() != NUM_JOINTS) {
|
||||||
|
neuronPlugin->_joints.resize(NUM_JOINTS, { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(sizeof(NeuronPlugin::NeuronJoint) == (NUM_FLOATS_PER_JOINT * sizeof(float)));
|
||||||
|
|
||||||
|
// copy the data
|
||||||
|
memcpy(&(neuronPlugin->_joints[0]), data, sizeof(NeuronPlugin::NeuronJoint) * NUM_JOINTS);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qCWarning(inputplugins) << "NeuronPlugin: unsuported binary format, please enable displacements";
|
||||||
|
|
||||||
|
// enter mutex
|
||||||
|
std::lock_guard<std::mutex> guard(neuronPlugin->_jointsMutex);
|
||||||
|
|
||||||
|
if (neuronPlugin->_joints.size() != NeuronJointIndex::Size) {
|
||||||
|
neuronPlugin->_joints.resize(NeuronJointIndex::Size, { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < NeuronJointIndex::Size; i++) {
|
||||||
|
neuronPlugin->_joints[i].euler = glm::vec3();
|
||||||
|
neuronPlugin->_joints[i].pos = neuronJointTranslations[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
static bool ONCE = false;
|
||||||
|
if (!ONCE) {
|
||||||
|
qCCritical(inputplugins) << "NeuronPlugin: bad frame version number, expected 1.0";
|
||||||
|
ONCE = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// I can't even get the SDK to send me a callback.
|
||||||
|
// BRCommandFetchAvatarDataFromServer & BRRegisterAutoSyncParmeter [sic] don't seem to work.
|
||||||
|
// So this is totally untested.
|
||||||
|
// NOTE: must be thread-safe
|
||||||
|
static void CommandDataReceivedCallback(void* context, SOCKET_REF sender, CommandPack* pack, void* data) {
|
||||||
|
|
||||||
|
DATA_VER version;
|
||||||
|
version._VersionMask = pack->DataVersion;
|
||||||
|
if (version.Major == 1 && version.Minor == 0) {
|
||||||
|
const char* str = "Unknown";
|
||||||
|
switch (pack->CommandId) {
|
||||||
|
case Cmd_BoneSize: // Id can be used to request bone size from server or register avatar name command.
|
||||||
|
str = "BoneSize";
|
||||||
|
break;
|
||||||
|
case Cmd_AvatarName: // Id can be used to request avatar name from server or register avatar name command.
|
||||||
|
str = "AvatarName";
|
||||||
|
break;
|
||||||
|
case Cmd_FaceDirection: // Id used to request face direction from server
|
||||||
|
str = "FaceDirection";
|
||||||
|
break;
|
||||||
|
case Cmd_DataFrequency: // Id can be used to request data frequency from server or register data frequency command.
|
||||||
|
str = "DataFrequency";
|
||||||
|
break;
|
||||||
|
case Cmd_BvhInheritanceTxt: // Id can be used to request bvh header txt from server or register bvh header txt command.
|
||||||
|
str = "BvhInheritanceTxt";
|
||||||
|
break;
|
||||||
|
case Cmd_AvatarCount: // Id can be used to request avatar count from server or register avatar count command.
|
||||||
|
str = "AvatarCount";
|
||||||
|
break;
|
||||||
|
case Cmd_CombinationMode: // Id can be used to request combination mode from server or register combination mode command.
|
||||||
|
str = "CombinationMode";
|
||||||
|
break;
|
||||||
|
case Cmd_RegisterEvent: // Id can be used to register event.
|
||||||
|
str = "RegisterEvent";
|
||||||
|
break;
|
||||||
|
case Cmd_UnRegisterEvent: // Id can be used to unregister event.
|
||||||
|
str = "UnRegisterEvent";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
qCDebug(inputplugins) << "NeuronPlugin: command data received CommandID = " << str;
|
||||||
|
} else {
|
||||||
|
static bool ONCE = false;
|
||||||
|
if (!ONCE) {
|
||||||
|
qCCritical(inputplugins) << "NeuronPlugin: bad command version number, expected 1.0";
|
||||||
|
ONCE = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: must be thread-safe
|
||||||
|
static void SocketStatusChangedCallback(void* context, SOCKET_REF sender, SocketStatus status, char* message) {
|
||||||
|
// just dump to log, later we might want to pop up a connection lost dialog or attempt to reconnect.
|
||||||
|
qCDebug(inputplugins) << "NeuronPlugin: socket status = " << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #ifdef HAVE_NEURON
|
||||||
|
|
||||||
|
//
|
||||||
|
// NeuronPlugin
|
||||||
|
//
|
||||||
|
|
||||||
|
bool NeuronPlugin::isSupported() const {
|
||||||
|
#ifdef HAVE_NEURON
|
||||||
|
// Because it's a client/server network architecture, we can't tell
|
||||||
|
// if the neuron is actually connected until we connect to the server.
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuronPlugin::activate() {
|
||||||
|
#ifdef HAVE_NEURON
|
||||||
|
InputPlugin::activate();
|
||||||
|
|
||||||
|
// register with userInputMapper
|
||||||
|
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
|
||||||
|
userInputMapper->registerDevice(_inputDevice);
|
||||||
|
|
||||||
|
// register c-style callbacks
|
||||||
|
BRRegisterFrameDataCallback((void*)this, FrameDataReceivedCallback);
|
||||||
|
BRRegisterCommandDataCallback((void*)this, CommandDataReceivedCallback);
|
||||||
|
BRRegisterSocketStatusCallback((void*)this, SocketStatusChangedCallback);
|
||||||
|
|
||||||
|
// TODO: Pull these from prefs dialog?
|
||||||
|
// localhost is fine for now.
|
||||||
|
_serverAddress = "localhost";
|
||||||
|
_serverPort = 7001; // default port for TCP Axis Neuron server.
|
||||||
|
|
||||||
|
_socketRef = BRConnectTo((char*)_serverAddress.c_str(), _serverPort);
|
||||||
|
if (!_socketRef) {
|
||||||
|
// error
|
||||||
|
qCCritical(inputplugins) << "NeuronPlugin: error connecting to " << _serverAddress.c_str() << ":" << _serverPort << ", error = " << BRGetLastErrorMessage();
|
||||||
|
} else {
|
||||||
|
qCDebug(inputplugins) << "NeuronPlugin: success connecting to " << _serverAddress.c_str() << ":" << _serverPort;
|
||||||
|
|
||||||
|
BRRegisterAutoSyncParmeter(_socketRef, Cmd_CombinationMode);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuronPlugin::deactivate() {
|
||||||
|
#ifdef HAVE_NEURON
|
||||||
|
// unregister from userInputMapper
|
||||||
|
if (_inputDevice->_deviceID != controller::Input::INVALID_DEVICE) {
|
||||||
|
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
|
||||||
|
userInputMapper->removeDevice(_inputDevice->_deviceID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_socketRef) {
|
||||||
|
BRUnregisterAutoSyncParmeter(_socketRef, Cmd_CombinationMode);
|
||||||
|
BRCloseSocket(_socketRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputPlugin::deactivate();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuronPlugin::pluginUpdate(float deltaTime, bool jointsCaptured) {
|
||||||
|
std::vector<NeuronJoint> joints;
|
||||||
|
{
|
||||||
|
// lock and copy
|
||||||
|
std::lock_guard<std::mutex> guard(_jointsMutex);
|
||||||
|
joints = _joints;
|
||||||
|
}
|
||||||
|
_inputDevice->update(deltaTime, joints, _prevJoints);
|
||||||
|
_prevJoints = joints;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuronPlugin::saveSettings() const {
|
||||||
|
InputPlugin::saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuronPlugin::loadSettings() {
|
||||||
|
InputPlugin::loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// InputDevice
|
||||||
|
//
|
||||||
|
|
||||||
|
controller::Input::NamedVector NeuronPlugin::InputDevice::getAvailableInputs() const {
|
||||||
|
static controller::Input::NamedVector availableInputs;
|
||||||
|
if (availableInputs.size() == 0) {
|
||||||
|
for (int i = 0; i < controller::NUM_STANDARD_POSES; i++) {
|
||||||
|
auto channel = static_cast<controller::StandardPoseChannel>(i);
|
||||||
|
availableInputs.push_back(makePair(channel, controllerJointName(channel)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return availableInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NeuronPlugin::InputDevice::getDefaultMappingConfig() const {
|
||||||
|
static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/neuron.json";
|
||||||
|
return MAPPING_JSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuronPlugin::InputDevice::update(float deltaTime, const std::vector<NeuronPlugin::NeuronJoint>& joints, const std::vector<NeuronPlugin::NeuronJoint>& prevJoints) {
|
||||||
|
for (size_t i = 0; i < joints.size(); i++) {
|
||||||
|
glm::vec3 linearVel, angularVel;
|
||||||
|
glm::vec3 pos = joints[i].pos;
|
||||||
|
glm::quat rot = eulerToQuat(joints[i].euler);
|
||||||
|
if (i < prevJoints.size()) {
|
||||||
|
linearVel = (pos - (prevJoints[i].pos * METERS_PER_CENTIMETER)) / deltaTime; // m/s
|
||||||
|
// quat log imaginary part points along the axis of rotation, with length of one half the angle of rotation.
|
||||||
|
glm::quat d = glm::log(rot * glm::inverse(eulerToQuat(prevJoints[i].euler)));
|
||||||
|
angularVel = glm::vec3(d.x, d.y, d.z) / (0.5f * deltaTime); // radians/s
|
||||||
|
}
|
||||||
|
int poseIndex = neuronJointIndexToPoseIndex((NeuronJointIndex)i);
|
||||||
|
_poseStateMap[poseIndex] = controller::Pose(pos, rot, linearVel, angularVel);
|
||||||
|
}
|
||||||
|
|
||||||
|
_poseStateMap[controller::RIGHT_HAND_THUMB1] = controller::Pose(rightHandThumb1DefaultAbsTranslation, glm::quat(), glm::vec3(), glm::vec3());
|
||||||
|
_poseStateMap[controller::LEFT_HAND_THUMB1] = controller::Pose(leftHandThumb1DefaultAbsTranslation, glm::quat(), glm::vec3(), glm::vec3());
|
||||||
|
}
|
85
plugins/hifiNeuron/src/NeuronPlugin.h
Normal file
85
plugins/hifiNeuron/src/NeuronPlugin.h
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
//
|
||||||
|
// NeuronPlugin.h
|
||||||
|
// input-plugins/src/input-plugins
|
||||||
|
//
|
||||||
|
// Created by Anthony Thibault on 12/18/2015.
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_NeuronPlugin_h
|
||||||
|
#define hifi_NeuronPlugin_h
|
||||||
|
|
||||||
|
#include <controllers/InputDevice.h>
|
||||||
|
#include <controllers/StandardControls.h>
|
||||||
|
#include <plugins/InputPlugin.h>
|
||||||
|
|
||||||
|
struct _BvhDataHeaderEx;
|
||||||
|
void FrameDataReceivedCallback(void* context, void* sender, _BvhDataHeaderEx* header, float* data);
|
||||||
|
|
||||||
|
// Handles interaction with the Neuron SDK
|
||||||
|
class NeuronPlugin : public InputPlugin {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
friend void FrameDataReceivedCallback(void* context, void* sender, _BvhDataHeaderEx* header, float* data);
|
||||||
|
|
||||||
|
// Plugin functions
|
||||||
|
virtual bool isSupported() const override;
|
||||||
|
virtual bool isJointController() const override { return true; }
|
||||||
|
const QString& getName() const override { return NAME; }
|
||||||
|
const QString& getID() const override { return NEURON_ID_STRING; }
|
||||||
|
|
||||||
|
virtual void activate() override;
|
||||||
|
virtual void deactivate() override;
|
||||||
|
|
||||||
|
virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
|
||||||
|
virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override;
|
||||||
|
|
||||||
|
virtual void saveSettings() const override;
|
||||||
|
virtual void loadSettings() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
struct NeuronJoint {
|
||||||
|
glm::vec3 pos;
|
||||||
|
glm::vec3 euler;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputDevice : public controller::InputDevice {
|
||||||
|
public:
|
||||||
|
friend class NeuronPlugin;
|
||||||
|
|
||||||
|
InputDevice() : controller::InputDevice("Neuron") {}
|
||||||
|
|
||||||
|
// Device functions
|
||||||
|
virtual controller::Input::NamedVector getAvailableInputs() const override;
|
||||||
|
virtual QString getDefaultMappingConfig() const override;
|
||||||
|
virtual void update(float deltaTime, bool jointsCaptured) override {};
|
||||||
|
virtual void focusOutEvent() override {};
|
||||||
|
|
||||||
|
void update(float deltaTime, const std::vector<NeuronPlugin::NeuronJoint>& joints, const std::vector<NeuronPlugin::NeuronJoint>& prevJoints);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<InputDevice> _inputDevice { std::make_shared<InputDevice>() };
|
||||||
|
|
||||||
|
static const QString NAME;
|
||||||
|
static const QString NEURON_ID_STRING;
|
||||||
|
|
||||||
|
std::string _serverAddress;
|
||||||
|
int _serverPort;
|
||||||
|
void* _socketRef;
|
||||||
|
|
||||||
|
// used to guard multi-threaded access to _joints
|
||||||
|
std::mutex _jointsMutex;
|
||||||
|
|
||||||
|
// copy of data directly from the NeuronDataReader SDK
|
||||||
|
std::vector<NeuronJoint> _joints;
|
||||||
|
|
||||||
|
// one frame old copy of _joints, used to caluclate angular and linear velocity.
|
||||||
|
std::vector<NeuronJoint> _prevJoints;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_NeuronPlugin_h
|
||||||
|
|
45
plugins/hifiNeuron/src/NeuronProvider.cpp
Normal file
45
plugins/hifiNeuron/src/NeuronProvider.cpp
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// Created by Anthony Thibault on 2015/12/18
|
||||||
|
// 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 <mutex>
|
||||||
|
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
#include <QtCore/QtPlugin>
|
||||||
|
#include <QtCore/QStringList>
|
||||||
|
|
||||||
|
#include <plugins/RuntimePlugin.h>
|
||||||
|
#include <plugins/InputPlugin.h>
|
||||||
|
|
||||||
|
#include "NeuronPlugin.h"
|
||||||
|
|
||||||
|
class NeuronProvider : public QObject, public InputProvider
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID InputProvider_iid FILE "plugin.json")
|
||||||
|
Q_INTERFACES(InputProvider)
|
||||||
|
|
||||||
|
public:
|
||||||
|
NeuronProvider(QObject* parent = nullptr) : QObject(parent) {}
|
||||||
|
virtual ~NeuronProvider() {}
|
||||||
|
|
||||||
|
virtual InputPluginList getInputPlugins() override {
|
||||||
|
static std::once_flag once;
|
||||||
|
std::call_once(once, [&] {
|
||||||
|
InputPluginPointer plugin(new NeuronPlugin());
|
||||||
|
if (plugin->isSupported()) {
|
||||||
|
_inputPlugins.push_back(plugin);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return _inputPlugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
InputPluginList _inputPlugins;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "NeuronProvider.moc"
|
1
plugins/hifiNeuron/src/plugin.json
Normal file
1
plugins/hifiNeuron/src/plugin.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -274,7 +274,7 @@ struct ByteData {
|
||||||
|
|
||||||
QTextStream & operator << (QTextStream& stream, const ByteData & wrapper) {
|
QTextStream & operator << (QTextStream& stream, const ByteData & wrapper) {
|
||||||
// Print bytes as hex
|
// Print bytes as hex
|
||||||
stream << QByteArray::fromRawData(wrapper.data, wrapper.length).toHex();
|
stream << QByteArray::fromRawData(wrapper.data, (int)wrapper.length).toHex();
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,13 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "AnimTests.h"
|
#include "AnimTests.h"
|
||||||
#include "AnimNodeLoader.h"
|
#include <AnimNodeLoader.h>
|
||||||
#include "AnimClip.h"
|
#include <AnimClip.h>
|
||||||
#include "AnimBlendLinear.h"
|
#include <AnimBlendLinear.h>
|
||||||
#include "AnimationLogging.h"
|
#include <AnimationLogging.h>
|
||||||
#include "AnimVariant.h"
|
#include <AnimVariant.h>
|
||||||
#include "AnimUtil.h"
|
#include <AnimExpression.h>
|
||||||
|
#include <AnimUtil.h>
|
||||||
|
|
||||||
#include <../QTestExtensions.h>
|
#include <../QTestExtensions.h>
|
||||||
|
|
||||||
|
@ -315,7 +316,6 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram
|
||||||
triggers.clear();
|
triggers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AnimTests::testAnimPose() {
|
void AnimTests::testAnimPose() {
|
||||||
const float PI = (float)M_PI;
|
const float PI = (float)M_PI;
|
||||||
const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
@ -394,3 +394,234 @@ void AnimTests::testAnimPose() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimTests::testExpressionTokenizer() {
|
||||||
|
QString str = "(10 + x) >= 20.1 && (y != !z)";
|
||||||
|
AnimExpression e("x");
|
||||||
|
auto iter = str.cbegin();
|
||||||
|
AnimExpression::Token token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::LeftParen);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::Int);
|
||||||
|
QVERIFY(token.intVal == 10);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::Plus);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::Identifier);
|
||||||
|
QVERIFY(token.strVal == "x");
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::RightParen);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::GreaterThanEqual);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::Float);
|
||||||
|
QVERIFY(fabsf(token.floatVal - 20.1f) < 0.0001f);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::And);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::LeftParen);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::Identifier);
|
||||||
|
QVERIFY(token.strVal == "y");
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::NotEqual);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::Not);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::Identifier);
|
||||||
|
QVERIFY(token.strVal == "z");
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::RightParen);
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
|
||||||
|
str = "true";
|
||||||
|
iter = str.cbegin();
|
||||||
|
token = e.consumeToken(str, iter);
|
||||||
|
QVERIFY(token.type == AnimExpression::Token::Bool);
|
||||||
|
QVERIFY(token.intVal == (int)true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimTests::testExpressionParser() {
|
||||||
|
|
||||||
|
auto vars = AnimVariantMap();
|
||||||
|
vars.set("f", false);
|
||||||
|
vars.set("t", true);
|
||||||
|
vars.set("ten", (int)10);
|
||||||
|
vars.set("twenty", (int)20);
|
||||||
|
vars.set("five", (float)5.0f);
|
||||||
|
vars.set("forty", (float)40.0f);
|
||||||
|
|
||||||
|
AnimExpression e("10");
|
||||||
|
QVERIFY(e._opCodes.size() == 1);
|
||||||
|
if (e._opCodes.size() == 1) {
|
||||||
|
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int);
|
||||||
|
QVERIFY(e._opCodes[0].intVal == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = AnimExpression("(10)");
|
||||||
|
QVERIFY(e._opCodes.size() == 1);
|
||||||
|
if (e._opCodes.size() == 1) {
|
||||||
|
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int);
|
||||||
|
QVERIFY(e._opCodes[0].intVal == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = AnimExpression("((10))");
|
||||||
|
QVERIFY(e._opCodes.size() == 1);
|
||||||
|
if (e._opCodes.size() == 1) {
|
||||||
|
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int);
|
||||||
|
QVERIFY(e._opCodes[0].intVal == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = AnimExpression("12.5");
|
||||||
|
QVERIFY(e._opCodes.size() == 1);
|
||||||
|
if (e._opCodes.size() == 1) {
|
||||||
|
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Float);
|
||||||
|
QVERIFY(e._opCodes[0].floatVal == 12.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = AnimExpression("twenty");
|
||||||
|
QVERIFY(e._opCodes.size() == 1);
|
||||||
|
if (e._opCodes.size() == 1) {
|
||||||
|
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier);
|
||||||
|
QVERIFY(e._opCodes[0].strVal == "twenty");
|
||||||
|
}
|
||||||
|
|
||||||
|
e = AnimExpression("true || false");
|
||||||
|
QVERIFY(e._opCodes.size() == 3);
|
||||||
|
if (e._opCodes.size() == 3) {
|
||||||
|
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[0].intVal == (int)true);
|
||||||
|
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[1].intVal == (int)false);
|
||||||
|
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Or);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = AnimExpression("true || false && true");
|
||||||
|
QVERIFY(e._opCodes.size() == 5);
|
||||||
|
if (e._opCodes.size() == 5) {
|
||||||
|
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[0].intVal == (int)true);
|
||||||
|
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[1].intVal == (int)false);
|
||||||
|
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[2].intVal == (int)true);
|
||||||
|
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::And);
|
||||||
|
QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::Or);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = AnimExpression("(true || false) && true");
|
||||||
|
QVERIFY(e._opCodes.size() == 5);
|
||||||
|
if (e._opCodes.size() == 5) {
|
||||||
|
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[0].intVal == (int)true);
|
||||||
|
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[1].intVal == (int)false);
|
||||||
|
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Or);
|
||||||
|
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[3].intVal == (int)true);
|
||||||
|
QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::And);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = AnimExpression("!(true || false) && true");
|
||||||
|
QVERIFY(e._opCodes.size() == 6);
|
||||||
|
if (e._opCodes.size() == 6) {
|
||||||
|
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[0].intVal == (int)true);
|
||||||
|
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[1].intVal == (int)false);
|
||||||
|
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Or);
|
||||||
|
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Not);
|
||||||
|
QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::Bool);
|
||||||
|
QVERIFY(e._opCodes[4].intVal == (int)true);
|
||||||
|
QVERIFY(e._opCodes[5].type == AnimExpression::OpCode::And);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TEST_BOOL_EXPR(EXPR) \
|
||||||
|
result = AnimExpression( #EXPR ).evaluate(vars); \
|
||||||
|
QVERIFY(result.type == AnimExpression::OpCode::Bool); \
|
||||||
|
QVERIFY(result.intVal == (int)(EXPR))
|
||||||
|
|
||||||
|
void AnimTests::testExpressionEvaluator() {
|
||||||
|
auto vars = AnimVariantMap();
|
||||||
|
|
||||||
|
bool f = false;
|
||||||
|
bool t = true;
|
||||||
|
int ten = 10;
|
||||||
|
int twenty = 20;
|
||||||
|
float five = 5.0f;
|
||||||
|
float fourty = 40.0f;
|
||||||
|
vars.set("f", f);
|
||||||
|
vars.set("t", t);
|
||||||
|
vars.set("ten", ten);
|
||||||
|
vars.set("twenty", twenty);
|
||||||
|
vars.set("five", five);
|
||||||
|
vars.set("forty", fourty);
|
||||||
|
|
||||||
|
AnimExpression::OpCode result(AnimExpression::OpCode::Int);
|
||||||
|
|
||||||
|
result = AnimExpression("10").evaluate(vars);
|
||||||
|
QVERIFY(result.type == AnimExpression::OpCode::Int);
|
||||||
|
QVERIFY(result.intVal == 10);
|
||||||
|
|
||||||
|
result = AnimExpression("(10)").evaluate(vars);
|
||||||
|
QVERIFY(result.type == AnimExpression::OpCode::Int);
|
||||||
|
QVERIFY(result.intVal == 10);
|
||||||
|
|
||||||
|
TEST_BOOL_EXPR(true);
|
||||||
|
TEST_BOOL_EXPR(false);
|
||||||
|
TEST_BOOL_EXPR(t);
|
||||||
|
TEST_BOOL_EXPR(f);
|
||||||
|
|
||||||
|
TEST_BOOL_EXPR(true || false);
|
||||||
|
TEST_BOOL_EXPR(true || true);
|
||||||
|
TEST_BOOL_EXPR(false || false);
|
||||||
|
TEST_BOOL_EXPR(false || true);
|
||||||
|
|
||||||
|
TEST_BOOL_EXPR(true && false);
|
||||||
|
TEST_BOOL_EXPR(true && true);
|
||||||
|
TEST_BOOL_EXPR(false && false);
|
||||||
|
TEST_BOOL_EXPR(false && true);
|
||||||
|
|
||||||
|
TEST_BOOL_EXPR(true || false && true);
|
||||||
|
TEST_BOOL_EXPR(true || false && false);
|
||||||
|
TEST_BOOL_EXPR(true || true && true);
|
||||||
|
TEST_BOOL_EXPR(true || true && false);
|
||||||
|
TEST_BOOL_EXPR(false || false && true);
|
||||||
|
TEST_BOOL_EXPR(false || false && false);
|
||||||
|
TEST_BOOL_EXPR(false || true && true);
|
||||||
|
TEST_BOOL_EXPR(false || true && false);
|
||||||
|
|
||||||
|
TEST_BOOL_EXPR(true && false || true);
|
||||||
|
TEST_BOOL_EXPR(true && false || false);
|
||||||
|
TEST_BOOL_EXPR(true && true || true);
|
||||||
|
TEST_BOOL_EXPR(true && true || false);
|
||||||
|
TEST_BOOL_EXPR(false && false || true);
|
||||||
|
TEST_BOOL_EXPR(false && false || false);
|
||||||
|
TEST_BOOL_EXPR(false && true || true);
|
||||||
|
TEST_BOOL_EXPR(false && true || false);
|
||||||
|
|
||||||
|
TEST_BOOL_EXPR(t || false);
|
||||||
|
TEST_BOOL_EXPR(t || true);
|
||||||
|
TEST_BOOL_EXPR(f || false);
|
||||||
|
TEST_BOOL_EXPR(f || true);
|
||||||
|
|
||||||
|
TEST_BOOL_EXPR(!true);
|
||||||
|
TEST_BOOL_EXPR(!false);
|
||||||
|
TEST_BOOL_EXPR(!true || true);
|
||||||
|
|
||||||
|
TEST_BOOL_EXPR(!true && !false || !true);
|
||||||
|
TEST_BOOL_EXPR(!true && !false || true);
|
||||||
|
TEST_BOOL_EXPR(!true && false || !true);
|
||||||
|
TEST_BOOL_EXPR(!true && false || true);
|
||||||
|
TEST_BOOL_EXPR(true && !false || !true);
|
||||||
|
TEST_BOOL_EXPR(true && !false || true);
|
||||||
|
TEST_BOOL_EXPR(true && false || !true);
|
||||||
|
TEST_BOOL_EXPR(true && false || true);
|
||||||
|
|
||||||
|
TEST_BOOL_EXPR(!(true && f) || !t);
|
||||||
|
TEST_BOOL_EXPR(!!!(t) && (!!f || true));
|
||||||
|
TEST_BOOL_EXPR(!(true && f) && true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,9 @@ private slots:
|
||||||
void testVariant();
|
void testVariant();
|
||||||
void testAccumulateTime();
|
void testAccumulateTime();
|
||||||
void testAnimPose();
|
void testAnimPose();
|
||||||
|
void testExpressionTokenizer();
|
||||||
|
void testExpressionParser();
|
||||||
|
void testExpressionEvaluator();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AnimTests_h
|
#endif // hifi_AnimTests_h
|
||||||
|
|
57
tests/shared/src/GLMHelpersTests.cpp
Normal file
57
tests/shared/src/GLMHelpersTests.cpp
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
//
|
||||||
|
// GLMHelpersTests.cpp
|
||||||
|
// tests/shared/src
|
||||||
|
//
|
||||||
|
// Created by Anthony Thibault on 2015.12.29
|
||||||
|
// 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 "GLMHelpersTests.h"
|
||||||
|
|
||||||
|
#include <NumericalConstants.h>
|
||||||
|
#include <StreamUtils.h>
|
||||||
|
|
||||||
|
#include <../QTestExtensions.h>
|
||||||
|
|
||||||
|
|
||||||
|
QTEST_MAIN(GLMHelpersTests)
|
||||||
|
|
||||||
|
void GLMHelpersTests::testEulerDecomposition() {
|
||||||
|
// quat to euler and back again....
|
||||||
|
|
||||||
|
const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f));
|
||||||
|
const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
|
||||||
|
const float EPSILON = 0.00001f;
|
||||||
|
|
||||||
|
std::vector<glm::quat> quatVec = {
|
||||||
|
glm::quat(),
|
||||||
|
ROT_X_90,
|
||||||
|
ROT_Y_180,
|
||||||
|
ROT_Z_30,
|
||||||
|
ROT_X_90 * ROT_Y_180 * ROT_Z_30,
|
||||||
|
ROT_X_90 * ROT_Z_30 * ROT_Y_180,
|
||||||
|
ROT_Y_180 * ROT_Z_30 * ROT_X_90,
|
||||||
|
ROT_Y_180 * ROT_X_90 * ROT_Z_30,
|
||||||
|
ROT_Z_30 * ROT_X_90 * ROT_Y_180,
|
||||||
|
ROT_Z_30 * ROT_Y_180 * ROT_X_90,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& q : quatVec) {
|
||||||
|
glm::vec3 euler = safeEulerAngles(q);
|
||||||
|
glm::quat r(euler);
|
||||||
|
|
||||||
|
// when the axis and angle are flipped.
|
||||||
|
if (glm::dot(q, r) < 0.0f) {
|
||||||
|
r = -r;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(q, r, EPSILON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
27
tests/shared/src/GLMHelpersTests.h
Normal file
27
tests/shared/src/GLMHelpersTests.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// GLMHelpersTests.h
|
||||||
|
// tests/shared/src
|
||||||
|
//
|
||||||
|
// Created by Anthony thibault on 2015.12.29
|
||||||
|
// 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_GLMHelpersTests_h
|
||||||
|
#define hifi_GLMHelpersTests_h
|
||||||
|
|
||||||
|
#include <QtTest/QtTest>
|
||||||
|
#include <GLMHelpers.h>
|
||||||
|
|
||||||
|
class GLMHelpersTests : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
private slots:
|
||||||
|
void testEulerDecomposition();
|
||||||
|
};
|
||||||
|
|
||||||
|
float getErrorDifference(const float& a, const float& b);
|
||||||
|
float getErrorDifference(const glm::vec3& a, const glm::vec3& b);
|
||||||
|
|
||||||
|
#endif // hifi_GLMHelpersTest_h
|
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
// GeometryUtilTests.cpp
|
// GeometryUtilTests.cpp
|
||||||
// tests/physics/src
|
// tests/shared/src
|
||||||
//
|
//
|
||||||
// Created by Andrew Meadows on 2015.07.27
|
// Created by Andrew Meadows on 2015.07.27
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
// GeometryUtilTests.h
|
// GeometryUtilTests.h
|
||||||
// tests/physics/src
|
// tests/shared/src
|
||||||
//
|
//
|
||||||
// Created by Andrew Meadows on 2014.05.30
|
// Created by Andrew Meadows on 2014.05.30
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
@ -9,8 +9,8 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef hifi_AngularConstraintTests_h
|
#ifndef hifi_GeometryUtilTests_h
|
||||||
#define hifi_AngularConstraintTests_h
|
#define hifi_GeometryUtilTests_h
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
@ -26,4 +26,4 @@ private slots:
|
||||||
float getErrorDifference(const float& a, const float& b);
|
float getErrorDifference(const float& a, const float& b);
|
||||||
float getErrorDifference(const glm::vec3& a, const glm::vec3& b);
|
float getErrorDifference(const glm::vec3& a, const glm::vec3& b);
|
||||||
|
|
||||||
#endif // hifi_AngularConstraintTests_h
|
#endif // hifi_GeometryUtilTests_h
|
||||||
|
|
Loading…
Reference in a new issue