This commit is contained in:
samcake 2015-09-29 10:53:56 -07:00
commit a9f6c93a2c
144 changed files with 3785 additions and 5825 deletions

View file

@ -13,7 +13,6 @@
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3
* [glm](http://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.4
* [gverb](https://github.com/highfidelity/gverb)
* [Soxr](http://sourceforge.net/projects/soxr/) ~> 0.1.1
The following external projects are optional dependencies. You can indicate to CMake that you would like to include them by passing -DGET_$NAME=1 when running a clean CMake build. For example, to get CMake to download and compile SDL2 you would pass -DGET_SDL2=1.

View file

@ -112,7 +112,11 @@ set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_CMAKE_PREFIX_PATH})
if (APPLE)
SET(OSX_SDK "10.9" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH")
exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE OSX_VERSION)
string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION})
message(STATUS "Detected OS X version = ${OSX_VERSION}")
set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH")
# set our OS X deployment target
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8)
@ -127,13 +131,14 @@ if (APPLE)
)
if (NOT _OSX_DESIRED_SDK_PATH)
message(FATAL_ERROR "Could not find OS X ${OSX_SDK} SDK. Please pass OSX_SDK_PATH to CMake to point us to your SDKs directory.")
message(STATUS "Could not find OS X ${OSX_SDK} SDK. Will fall back to default. If you want a specific SDK, please pass OSX_SDK and optionally OSX_SDK_PATH to CMake.")
else ()
message(STATUS "Found OS X ${OSX_SDK} SDK at ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk")
# set that as the SDK to use
set(CMAKE_OSX_SYSROOT ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk)
endif ()
# set that as the SDK to use
set(CMAKE_OSX_SYSROOT ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk)
endif ()
# Hide automoc folders (for IDEs)
@ -181,7 +186,6 @@ setup_externals_binary_dir()
option(GET_BULLET "Get Bullet library automatically as external project" 1)
option(GET_GLM "Get GLM library automatically as external project" 1)
option(GET_GVERB "Get Gverb library automatically as external project" 1)
option(GET_SOXR "Get Soxr library automatically as external project" 1)
option(GET_TBB "Get Threading Building Blocks library automatically as external project" 1)
option(GET_LIBOVR "Get LibOVR library automatically as external project" 1)
option(GET_VHACD "Get V-HACD library automatically as external project" 1)

View file

@ -43,7 +43,7 @@ if (WIN32)
endif()
elseif(APPLE)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://static.oculus.com/sdk-downloads/ovr_sdk_macos_0.5.0.1.tar.gz

View file

@ -19,19 +19,20 @@ ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
if (APPLE)
set(INSTALL_NAME_LIBRARY_DIR ${INSTALL_DIR}/lib)
message(STATUS "in polyvox INSTALL_NAME_LIBRARY_DIR ${INSTALL_NAME_LIBRARY_DIR}")
ExternalProject_Add_Step(
${EXTERNAL_NAME}
change-install-name
change-install-name-debug
COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking"
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${INSTALL_NAME_LIBRARY_DIR}/Debug -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
DEPENDEES install
WORKING_DIRECTORY <SOURCE_DIR>
LOG 1
)
ExternalProject_Add_Step(
${EXTERNAL_NAME}
change-install-name
change-install-name-release
COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking"
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${INSTALL_NAME_LIBRARY_DIR}/Release -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
DEPENDEES install

View file

@ -1,34 +0,0 @@
set(EXTERNAL_NAME soxr)
if (ANDROID)
set(PLATFORM_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19" "-DHAVE_WORDS_BIGENDIAN_EXITCODE=1")
endif ()
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/soxr-0.1.1.zip
URL_MD5 349b5b2f323a7380bc12186d98c77d1d
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DBUILD_SHARED_LIBS=1 -DBUILD_TESTS=0 -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
)
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of soxr include directories")
if (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${INSTALL_DIR}/lib/soxr.lib CACHE FILEPATH "List of soxr libraries")
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/bin CACHE PATH "Path to soxr dll")
elseif (APPLE)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${INSTALL_DIR}/lib/libsoxr.dylib CACHE FILEPATH "List of soxr libraries")
else ()
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${INSTALL_DIR}/lib/libsoxr.so CACHE FILEPATH "List of soxr libraries")
endif ()

View file

@ -1,43 +0,0 @@
#
# FindSoxr.cmake
#
# Try to find the libsoxr resampling library
#
# You can provide a LIBSOXR_ROOT_DIR which contains lib and include directories
#
# Once done this will define
#
# SOXR_FOUND - system found libsoxr
# SOXR_INCLUDE_DIRS - the libsoxr include directory
# SOXR_LIBRARIES - link to this to use libsoxr
#
# Created on 1/22/2015 by Stephen Birarda
# 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("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("soxr")
find_path(SOXR_INCLUDE_DIRS soxr.h PATH_SUFFIXES include HINTS ${SOXR_SEARCH_DIRS})
find_library(SOXR_LIBRARIES NAMES soxr PATH_SUFFIXES lib HINTS ${SOXR_SEARCH_DIRS})
if (WIN32)
find_path(SOXR_DLL_PATH soxr.dll PATH_SUFFIXES bin HINTS ${SOXR_SEARCH_DIRS})
endif()
set(SOXR_REQUIREMENTS SOXR_INCLUDE_DIRS SOXR_LIBRARIES)
if (WIN32)
list(APPEND SOXR_REQUIREMENTS SOXR_DLL_PATH)
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Soxr DEFAULT_MSG ${SOXR_REQUIREMENTS})
if (WIN32)
add_paths_to_fixup_libs(${SOXR_DLL_PATH})
endif ()
mark_as_advanced(SOXR_INCLUDE_DIRS SOXR_LIBRARIES SOXR_SEARCH_DIRS)

View file

@ -2,17 +2,17 @@
// examples
//
// Created by Eric Levin on 9/2/15
// Additions by James B. Pollack @imgntn on 9/24/2015
// Copyright 2015 High Fidelity, Inc.
//
// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */
Script.include("../libraries/utils.js");
/////////////////////////////////////////////////////////////////
//
// these tune time-averaging and "on" value for analog trigger
@ -29,9 +29,9 @@ var TRIGGER_ON_VALUE = 0.2;
var DISTANCE_HOLDING_RADIUS_FACTOR = 5; // multiplied by distance between hand and object
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did
var NO_INTERSECT_COLOR = {red: 10, green: 10, blue: 255}; // line color when pick misses
var INTERSECT_COLOR = {red: 250, green: 10, blue: 10}; // line color when pick hits
var LINE_ENTITY_DIMENSIONS = {x: 1000, y: 1000, z: 1000};
var NO_INTERSECT_COLOR = { red: 10, green: 10, blue: 255}; // line color when pick misses
var INTERSECT_COLOR = { red: 250, green: 10, blue: 10}; // line color when pick hits
var LINE_ENTITY_DIMENSIONS = { x: 1000, y: 1000, z: 1000};
var LINE_LENGTH = 500;
@ -54,7 +54,7 @@ var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things
var RIGHT_HAND = 1;
var LEFT_HAND = 0;
var ZERO_VEC = {x: 0, y: 0, z: 0};
var ZERO_VEC = { x: 0, y: 0, z: 0};
var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}";
var MSEC_PER_SEC = 1000.0;
@ -68,11 +68,14 @@ var STATE_DISTANCE_HOLDING = 1;
var STATE_CONTINUE_DISTANCE_HOLDING = 2;
var STATE_NEAR_GRABBING = 3;
var STATE_CONTINUE_NEAR_GRABBING = 4;
var STATE_RELEASE = 5;
var STATE_NEAR_GRABBING_NON_COLLIDING = 5;
var STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING = 6;
var STATE_RELEASE = 7;
var GRAB_USER_DATA_KEY = "grabKey";
var GRABBABLE_DATA_KEY = "grabbableKey";
function controller(hand, triggerAction) {
function MyController(hand, triggerAction) {
this.hand = hand;
if (this.hand === RIGHT_HAND) {
this.getHandPosition = MyAvatar.getRightPalmPosition;
@ -81,6 +84,7 @@ function controller(hand, triggerAction) {
this.getHandPosition = MyAvatar.getLeftPalmPosition;
this.getHandRotation = MyAvatar.getLeftPalmRotation;
}
this.triggerAction = triggerAction;
this.palm = 2 * hand;
// this.tip = 2 * hand + 1; // unused, but I'm leaving this here for fear it will be needed
@ -91,11 +95,13 @@ function controller(hand, triggerAction) {
this.state = 0;
this.pointer = null; // entity-id of line object
this.triggerValue = 0; // rolling average of trigger value
var _this = this;
this.update = function() {
switch(this.state) {
switch (this.state) {
case STATE_SEARCHING:
this.search();
this.touchTest();
break;
case STATE_DISTANCE_HOLDING:
this.distanceHolding();
@ -109,44 +115,48 @@ function controller(hand, triggerAction) {
case STATE_CONTINUE_NEAR_GRABBING:
this.continueNearGrabbing();
break;
case STATE_NEAR_GRABBING_NON_COLLIDING:
this.nearGrabbingNonColliding();
break;
case STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING:
this.continueNearGrabbingNonColliding();
break;
case STATE_RELEASE:
this.release();
break;
}
}
};
this.lineOn = function(closePoint, farPoint, color) {
// draw a line
if (this.pointer == null) {
if (this.pointer === null) {
this.pointer = Entities.addEntity({
type: "Line",
name: "pointer",
dimensions: LINE_ENTITY_DIMENSIONS,
visible: true,
position: closePoint,
linePoints: [ ZERO_VEC, farPoint ],
linePoints: [ZERO_VEC, farPoint],
color: color,
lifetime: LIFETIME
});
} else {
Entities.editEntity(this.pointer, {
position: closePoint,
linePoints: [ ZERO_VEC, farPoint ],
linePoints: [ZERO_VEC, farPoint],
color: color,
lifetime: (Date.now() - startTime) / MSEC_PER_SEC + LIFETIME
});
}
}
};
this.lineOff = function() {
if (this.pointer != null) {
if (this.pointer !== null) {
Entities.deleteEntity(this.pointer);
}
this.pointer = null;
}
};
this.triggerSmoothedSqueezed = function() {
var triggerValue = Controller.getActionValue(this.triggerAction);
@ -154,14 +164,12 @@ function controller(hand, triggerAction) {
this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) +
(triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO));
return this.triggerValue > TRIGGER_ON_VALUE;
}
};
this.triggerSqueezed = function() {
var triggerValue = Controller.getActionValue(this.triggerAction);
return triggerValue > TRIGGER_ON_VALUE;
}
};
this.search = function() {
if (!this.triggerSmoothedSqueezed()) {
@ -171,7 +179,15 @@ function controller(hand, triggerAction) {
// the trigger is being pressed, do a ray test
var handPosition = this.getHandPosition();
var pickRay = {origin: handPosition, direction: Quat.getUp(this.getHandRotation())};
var pickRay = {
origin: handPosition,
direction: Quat.getUp(this.getHandRotation())
};
var defaultGrabbableData = {
grabbable: true
};
var intersection = Entities.findRayIntersection(pickRay, true);
if (intersection.intersects &&
intersection.properties.collisionsWillMove === 1 &&
@ -180,9 +196,15 @@ function controller(hand, triggerAction) {
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
var intersectionDistance = Vec3.distance(handControllerPosition, intersection.intersection);
this.grabbedEntity = intersection.entityID;
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, defaultGrabbableData);
if (grabbableData.grabbable === false) {
return;
}
if (intersectionDistance < NEAR_PICK_MAX_DISTANCE) {
// the hand is very close to the intersected object. go into close-grabbing mode.
this.state = STATE_NEAR_GRABBING;
} else {
// the hand is far from the intersected object. go into distance-holding mode
this.state = STATE_DISTANCE_HOLDING;
@ -192,30 +214,39 @@ function controller(hand, triggerAction) {
// forward ray test failed, try sphere test.
var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS);
var minDistance = GRAB_RADIUS;
var grabbedEntity = null;
for (var i = 0; i < nearbyEntities.length; i++) {
var props = Entities.getEntityProperties(nearbyEntities[i]);
var distance = Vec3.distance(props.position, handPosition);
if (distance < minDistance && props.name !== "pointer" &&
props.collisionsWillMove === 1 &&
props.locked === 0) {
var i, props, distance;
for (i = 0; i < nearbyEntities.length; i++) {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], defaultGrabbableData);
if (grabbableData.grabbable === false) {
return;
}
props = Entities.getEntityProperties(nearbyEntities[i], ["position", "name", "collisionsWillMove", "locked"]);
distance = Vec3.distance(props.position, handPosition);
if (distance < minDistance && props.name !== "pointer") {
this.grabbedEntity = nearbyEntities[i];
minDistance = distance;
}
}
if (this.grabbedEntity === null) {
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
} else {
} else if (props.locked === 0 && props.collisionsWillMove === 1) {
this.state = STATE_NEAR_GRABBING;
} else if (props.collisionsWillMove === 0) {
// We have grabbed a non-physical object, so we want to trigger a non-colliding event as opposed to a grab event
this.state = STATE_NEAR_GRABBING_NON_COLLIDING;
}
}
}
};
this.distanceHolding = function() {
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position","rotation"]);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]);
// add the action and initialize some variables
this.currentObjectPosition = grabbedProperties.position;
@ -230,23 +261,22 @@ function controller(hand, triggerAction) {
targetRotation: this.currentObjectRotation,
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME
});
if (this.actionID == NULL_ACTION_ID) {
if (this.actionID === NULL_ACTION_ID) {
this.actionID = null;
}
if (this.actionID != null) {
if (this.actionID !== null) {
this.state = STATE_CONTINUE_DISTANCE_HOLDING;
this.activateEntity(this.grabbedEntity);
Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab");
if (this.hand === RIGHT_HAND) {
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
} else {
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
}
Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab");
}
}
};
this.continueDistanceHolding = function() {
if (!this.triggerSmoothedSqueezed()) {
@ -262,9 +292,7 @@ function controller(hand, triggerAction) {
this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR);
// the action was set up on a previous call. update the targets.
var radius = Math.max(Vec3.distance(this.currentObjectPosition,
handControllerPosition) * DISTANCE_HOLDING_RADIUS_FACTOR,
DISTANCE_HOLDING_RADIUS_FACTOR);
var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) * DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR);
var handMoved = Vec3.subtract(handControllerPosition, this.handPreviousPosition);
this.handPreviousPosition = handControllerPosition;
@ -280,20 +308,19 @@ function controller(hand, triggerAction) {
this.currentObjectTime = now;
// this doubles hand rotation
var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, handRotation,
DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR),
Quat.inverse(this.handPreviousRotation));
var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, handRotation, DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), Quat.inverse(this.handPreviousRotation));
this.handPreviousRotation = handRotation;
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab");
Entities.updateAction(this.grabbedEntity, this.actionID, {
targetPosition: this.currentObjectPosition, linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
targetRotation: this.currentObjectRotation, angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME
targetPosition: this.currentObjectPosition,
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
targetRotation: this.currentObjectRotation,
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME
});
}
};
this.nearGrabbing = function() {
if (!this.triggerSmoothedSqueezed()) {
@ -313,32 +340,32 @@ function controller(hand, triggerAction) {
var objectRotation = grabbedProperties.rotation;
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
currentObjectPosition = grabbedProperties.position;
var currentObjectPosition = grabbedProperties.position;
var offset = Vec3.subtract(currentObjectPosition, handPosition);
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
hand: this.hand == RIGHT_HAND ? "right" : "left",
hand: this.hand === RIGHT_HAND ? "right" : "left",
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
relativePosition: offsetPosition,
relativeRotation: offsetRotation
});
if (this.actionID == NULL_ACTION_ID) {
if (this.actionID === NULL_ACTION_ID) {
this.actionID = null;
} else {
this.state = STATE_CONTINUE_NEAR_GRABBING;
Entities.callEntityMethod(this.grabbedEntity, "startNearGrab");
if (this.hand === RIGHT_HAND) {
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
} else {
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
}
Entities.callEntityMethod(this.grabbedEntity, "startNearGrab");
}
this.currentHandControllerPosition = Controller.getSpatialControlPosition(this.palm);
this.currentObjectTime = Date.now();
}
};
this.continueNearGrabbing = function() {
if (!this.triggerSmoothedSqueezed()) {
@ -357,8 +384,94 @@ function controller(hand, triggerAction) {
this.currentHandControllerPosition = handControllerPosition;
this.currentObjectTime = now;
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab");
}
};
this.nearGrabbingNonColliding = function() {
if (!this.triggerSmoothedSqueezed()) {
this.state = STATE_RELEASE;
return;
}
Entities.callEntityMethod(this.grabbedEntity, "startNearGrabNonColliding");
this.state = STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING;
};
this.continueNearGrabbingNonColliding = function() {
if (!this.triggerSmoothedSqueezed()) {
this.state = STATE_RELEASE;
return;
}
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrabbingNonColliding");
};
_this.allTouchedIDs = {};
this.touchTest = function() {
var maxDistance = 0.05;
var leftHandPosition = MyAvatar.getLeftPalmPosition();
var rightHandPosition = MyAvatar.getRightPalmPosition();
var leftEntities = Entities.findEntities(leftHandPosition, maxDistance);
var rightEntities = Entities.findEntities(rightHandPosition, maxDistance);
var ids = [];
if (leftEntities.length !== 0) {
leftEntities.forEach(function(entity) {
ids.push(entity);
});
}
if (rightEntities.length !== 0) {
rightEntities.forEach(function(entity) {
ids.push(entity);
});
}
ids.forEach(function(id) {
var props = Entities.getEntityProperties(id, ["boundingBox", "name"]);
if (props.name === 'pointer') {
return;
} else {
var entityMinPoint = props.boundingBox.brn;
var entityMaxPoint = props.boundingBox.tfl;
var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint);
var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint);
if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) {
// we haven't been touched before, but either right or left is touching us now
_this.allTouchedIDs[id] = true;
_this.startTouch(id);
} else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === true) {
// we have been touched before and are still being touched
// continue touch
_this.continueTouch(id);
} else if (_this.allTouchedIDs[id] === true) {
delete _this.allTouchedIDs[id];
_this.stopTouch(id);
} else {
//we are in another state
return;
}
}
});
};
this.startTouch = function(entityID) {
// print('START TOUCH' + entityID);
Entities.callEntityMethod(entityID, "startTouch");
};
this.continueTouch = function(entityID) {
// print('CONTINUE TOUCH' + entityID);
Entities.callEntityMethod(entityID, "continueTouch");
};
this.stopTouch = function(entityID) {
// print('STOP TOUCH' + entityID);
Entities.callEntityMethod(entityID, "stopTouch");
};
this.computeReleaseVelocity = function(deltaPosition, deltaTime, useMultiplier) {
if (deltaTime > 0.0 && !vec3equal(deltaPosition, ZERO_VEC)) {
@ -367,75 +480,70 @@ function controller(hand, triggerAction) {
// value would otherwise give the held object time to slow down.
if (this.triggerSqueezed()) {
this.grabbedVelocity =
Vec3.sum(Vec3.multiply(this.grabbedVelocity,
(1.0 - NEAR_GRABBING_VELOCITY_SMOOTH_RATIO)),
Vec3.multiply(grabbedVelocity, NEAR_GRABBING_VELOCITY_SMOOTH_RATIO));
Vec3.sum(Vec3.multiply(this.grabbedVelocity, (1.0 - NEAR_GRABBING_VELOCITY_SMOOTH_RATIO)),
Vec3.multiply(grabbedVelocity, NEAR_GRABBING_VELOCITY_SMOOTH_RATIO));
}
if (useMultiplier) {
this.grabbedVelocity = Vec3.multiply(this.grabbedVelocity, RELEASE_VELOCITY_MULTIPLIER);
}
}
}
};
this.release = function() {
this.lineOff();
if (this.grabbedEntity != null && this.actionID != null) {
if (this.grabbedEntity !== null && this.actionID !== null) {
Entities.deleteAction(this.grabbedEntity, this.actionID);
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
}
// the action will tend to quickly bring an object's velocity to zero. now that
// the action is gone, set the objects velocity to something the holder might expect.
Entities.editEntity(this.grabbedEntity, {velocity: this.grabbedVelocity});
Entities.editEntity(this.grabbedEntity, {
velocity: this.grabbedVelocity
});
this.deactivateEntity(this.grabbedEntity);
this.grabbedVelocity = ZERO_VEC;
this.grabbedEntity = null;
this.actionID = null;
this.state = STATE_SEARCHING;
}
};
this.cleanup = function() {
release();
}
this.release();
};
this.activateEntity = function(entity) {
this.activateEntity = function() {
var data = {
activated: true,
avatarId: MyAvatar.sessionUUID
};
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
}
};
this.deactivateEntity = function(entity) {
this.deactivateEntity = function() {
var data = {
activated: false,
avatarId: null
};
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
}
};
}
var rightController = new controller(RIGHT_HAND, Controller.findAction("RIGHT_HAND_CLICK"));
var leftController = new controller(LEFT_HAND, Controller.findAction("LEFT_HAND_CLICK"));
var rightController = new MyController(RIGHT_HAND, Controller.findAction("RIGHT_HAND_CLICK"));
var leftController = new MyController(LEFT_HAND, Controller.findAction("LEFT_HAND_CLICK"));
function update() {
rightController.update();
leftController.update();
}
function cleanup() {
rightController.cleanup();
leftController.cleanup();
}
Script.scriptEnding.connect(cleanup);
Script.update.connect(update)
Script.update.connect(update);

View file

@ -1035,7 +1035,7 @@ function handeMenuEvent(menuItem) {
var importURL;
if (menuItem == "Import Entities") {
importURL = Window.browse("Select models to import", "", "*.json");
importURL = "file:///" + Window.browse("Select models to import", "", "*.json");
} else {
importURL = Window.prompt("URL of SVO to import", "");
}

View file

@ -0,0 +1,71 @@
//
// changeColorOnTouch.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/1/14.
// Additions by James B. Pollack @imgntn on 9/23/2015
// Copyright 2014 High Fidelity, Inc.
//
// ATTENTION: Requires you to run handControllerGrab.js
// This is an example of an entity script which when assigned to a non-model entity like a box or sphere, will
// change the color of the entity when you touch it.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */
(function() {
function ChangeColorOnTouch () {
this.oldColor = {};
this.oldColorKnown = false;
}
ChangeColorOnTouch.prototype = {
storeOldColor: function(entityID) {
var oldProperties = Entities.getEntityProperties(entityID);
this.oldColor = oldProperties.color;
this.oldColorKnown = true;
print("storing old color... this.oldColor=" + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
},
preload: function(entityID) {
print("preload");
this.entityID = entityID;
this.storeOldColor(entityID);
},
startTouch: function() {
print("startTouch");
if (!this.oldColorKnown) {
this.storeOldColor(this.entityID);
}
Entities.editEntity(this.entityID, {color: { red: 0, green: 255, blue: 255 }});
},
continueTouch: function() {
//unused here
return;
},
stopTouch: function() {
print("stopTouch");
if (this.oldColorKnown) {
print("leave restoring old color... this.oldColor=" + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
Entities.editEntity(this.entityID, {color: this.oldColor});
}
}
};
return new ChangeColorOnTouch();
});

View file

@ -79,19 +79,18 @@
lastFrame: 10000,
running: true
});
var PI = 3.141593;
var DEG_TO_RAD = PI / 180.0;
this.paintStream = Entities.addEntity({
type: "ParticleEffect",
animationSettings: animationSettings,
position: this.properties.position,
textures: "https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png",
emitVelocity: ZERO_VEC,
emitSpeed: 0,
speedSpread: 0.02,
polarFinish: 2 * DEG_TO_RAD,
emitAcceleration: ZERO_VEC,
velocitySpread: {
x: .1,
y: .1,
z: 0.1
},
emitRate: 100,
particleRadius: 0.01,
color: {
@ -127,7 +126,8 @@
position = Vec3.sum(position, Vec3.multiply(upVec, TIP_OFFSET_Y))
Entities.editEntity(self.paintStream, {
position: position,
emitVelocity: Vec3.multiply(5, forwardVec)
emitOrientation: forwardVec,
emitSpeed: 5
});
//Now check for an intersection with an entity

View file

@ -13,35 +13,49 @@
(function () {
var box,
sphere,
sphereDimensions = { x: 0.4, y: 0.8, z: 0.2 },
pointDimensions = { x: 0.0, y: 0.0, z: 0.0 },
sphereOrientation = Quat.fromPitchYawRollDegrees(-60.0, 30.0, 0.0),
verticalOrientation = Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0),
particles,
particleExample = -1,
NUM_PARTICLE_EXAMPLES = 11,
PARTICLE_RADIUS = 0.04;
PARTICLE_RADIUS = 0.04,
SLOW_EMIT_RATE = 2.0,
HALF_EMIT_RATE = 50.0,
FAST_EMIT_RATE = 100.0,
SLOW_EMIT_SPEED = 0.025,
FAST_EMIT_SPEED = 1.0,
GRAVITY_EMIT_ACCELERATON = { x: 0.0, y: -0.3, z: 0.0 },
ZERO_EMIT_ACCELERATON = { x: 0.0, y: 0.0, z: 0.0 },
PI = 3.141593,
DEG_TO_RAD = PI / 180.0,
NUM_PARTICLE_EXAMPLES = 18;
function onClickDownOnEntity(entityID) {
if (entityID === box || entityID === particles) {
if (entityID === box || entityID === sphere || entityID === particles) {
particleExample = (particleExample + 1) % NUM_PARTICLE_EXAMPLES;
switch (particleExample) {
case 0:
print("Simple emitter");
Entities.editEntity(particles, {
velocitySpread: { x: 0.0, y: 0.0, z: 0.0 },
speedSpread: 0.0,
accelerationSpread: { x: 0.0, y: 0.0, z: 0.0 },
radiusSpread: 0.0,
animationIsPlaying: true
});
break;
case 1:
print("Velocity spread");
print("Speed spread");
Entities.editEntity(particles, {
velocitySpread: { x: 0.1, y: 0.0, z: 0.1 }
speedSpread: 0.1
});
break;
case 2:
print("Acceleration spread");
Entities.editEntity(particles, {
velocitySpread: { x: 0.0, y: 0.0, z: 0.0 },
speedSpread: 0.0,
accelerationSpread: { x: 0.0, y: 0.1, z: 0.0 }
});
break;
@ -104,19 +118,99 @@
});
break;
case 10:
print("Stop emitting");
print("Emit in a spread beam");
Entities.editEntity(particles, {
colorStart: { red: 255, green: 255, blue: 255 },
colorFinish: { red: 255, green: 255, blue: 255 },
alphaFinish: 0.0,
emitRate: FAST_EMIT_RATE,
polarFinish: 2.0 * DEG_TO_RAD
});
break;
case 11:
print("Emit in all directions from point");
Entities.editEntity(particles, {
emitSpeed: SLOW_EMIT_SPEED,
emitAcceleration: ZERO_EMIT_ACCELERATON,
polarFinish: PI
});
break;
case 12:
print("Emit from sphere surface");
Entities.editEntity(particles, {
colorStart: { red: 255, green: 255, blue: 255 },
colorFinish: { red: 255, green: 255, blue: 255 },
emitDimensions: sphereDimensions,
emitOrientation: sphereOrientation
});
Entities.editEntity(box, {
visible: false
});
Entities.editEntity(sphere, {
visible: true
});
break;
case 13:
print("Emit from hemisphere of sphere surface");
Entities.editEntity(particles, {
polarFinish: PI / 2.0
});
break;
case 14:
print("Emit from equator of sphere surface");
Entities.editEntity(particles, {
polarStart: PI / 2.0,
emitRate: HALF_EMIT_RATE
});
break;
case 15:
print("Emit from half equator of sphere surface");
Entities.editEntity(particles, {
azimuthStart: -PI / 2.0,
azimuthFinish: PI / 2.0
});
break;
case 16:
print("Emit within eighth of sphere volume");
Entities.editEntity(particles, {
polarStart: 0.0,
polarFinish: PI / 2.0,
azimuthStart: 0,
azimuthFinish: PI / 2.0,
emitRadiusStart: 0.0,
alphaFinish: 1.0,
emitSpeed: 0.0
});
break;
case 17:
print("Stop emitting");
Entities.editEntity(particles, {
emitDimensions: pointDimensions,
emitOrientation: verticalOrientation,
alphaFinish: 1.0,
emitRate: SLOW_EMIT_RATE,
emitSpeed: FAST_EMIT_SPEED,
emitAcceleration: GRAVITY_EMIT_ACCELERATON,
polarStart: 0.0,
polarFinish: 0.0,
azimuthStart: -PI,
azimuthFinish: PI,
animationIsPlaying: false
});
Entities.editEntity(box, {
visible: true
});
Entities.editEntity(sphere, {
visible: false
});
break;
}
}
}
function setUp() {
var spawnPoint = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation()))),
var boxPoint,
spawnPoint,
animation = {
fps: 30,
frameIndex: 0,
@ -126,28 +220,50 @@
loop: true
};
boxPoint = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation())));
boxPoint = Vec3.sum(boxPoint, { x: 0.0, y: -0.5, z: 0.0 });
spawnPoint = Vec3.sum(boxPoint, { x: 0.0, y: 1.0, z: 0.0 });
box = Entities.addEntity({
type: "Box",
position: spawnPoint,
name: "ParticlesTest Box",
position: boxPoint,
rotation: verticalOrientation,
dimensions: { x: 0.3, y: 0.3, z: 0.3 },
color: { red: 128, green: 128, blue: 128 },
lifetime: 3600 // 1 hour; just in case
lifetime: 3600, // 1 hour; just in case
visible: true
});
// Same size and orientation as emitter when ellipsoid.
sphere = Entities.addEntity({
type: "Sphere",
name: "ParticlesTest Sphere",
position: boxPoint,
rotation: sphereOrientation,
dimensions: sphereDimensions,
color: { red: 128, green: 128, blue: 128 },
lifetime: 3600, // 1 hour; just in case
visible: false
});
// 1.0m above the box or ellipsoid.
particles = Entities.addEntity({
type: "ParticleEffect",
name: "ParticlesTest Emitter",
position: spawnPoint,
emitOrientation: verticalOrientation,
particleRadius: PARTICLE_RADIUS,
radiusSpread: 0.0,
emitRate: 2.0,
emitVelocity: { x: 0.0, y: 1.0, z: 0.0 },
velocitySpread: { x: 0.0, y: 0.0, z: 0.0 },
emitAcceleration: { x: 0.0, y: -0.3, z: 0.0 },
emitRate: SLOW_EMIT_RATE,
emitSpeed: FAST_EMIT_SPEED,
speedSpread: 0.0,
emitAcceleration: GRAVITY_EMIT_ACCELERATON,
accelerationSpread: { x: 0.0, y: 0.0, z: 0.0 },
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
color: { red: 255, green: 255, blue: 255 },
lifespan: 5.0,
visible: true,
visible: false,
locked: false,
animationSettings: animation,
animationIsPlaying: false,
@ -163,6 +279,7 @@
Entities.clickDownOnEntity.disconnect(onClickDownOnEntity);
Entities.deleteEntity(particles);
Entities.deleteEntity(box);
Entities.deleteEntity(sphere);
}
setUp();

View file

@ -0,0 +1,94 @@
//
// collectHifiStats.js
//
// Created by Thijs Wenker on 24 Sept 2015
// Additions by James B. Pollack @imgntn on 25 Sept 2015
// Copyright 2015 High Fidelity, Inc.
//
// Collects stats for analysis to a REST endpoint. Defaults to batching stats, but can be customized.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// The url where the data will be posted.
var ENDPOINT_URL = "";
var BATCH_STATS = true;
var BATCH_SIZE = 5;
var batch = [];
if (BATCH_STATS) {
var RECORD_EVERY = 1000; // 1 seconds
var batchCount = 0;
Script.setInterval(function() {
if (batchCount === BATCH_SIZE) {
sendBatchToEndpoint(batch);
batchCount = 0;
}
Stats.forceUpdateStats();
batch.push(getBatchStats());
batchCount++;
}, RECORD_EVERY);
} else {
// Send the data every:
var SEND_EVERY = 30000; // 30 seconds
Script.setInterval(function() {
Stats.forceUpdateStats();
var req = new XMLHttpRequest();
req.open("POST", ENDPOINT_URL, false);
req.send(getStats());
}, SEND_EVERY);
}
function getStats() {
return JSON.stringify({
username: GlobalServices.username,
location: Window.location.hostname,
framerate: Stats.framerate,
simrate: Stats.simrate,
ping: {
audio: Stats.audioPing,
avatar: Stats.avatarPing,
entities: Stats.entitiesPing,
asset: Stats.assetPing
},
position: Camera.position,
yaw: Stats.yaw,
rotation: Camera.orientation.x + ',' + Camera.orientation.y + ',' + Camera.orientation.z + ',' + Camera.orientation.w
})
}
function getBatchStats() {
// print('GET BATCH STATS');
return {
username: GlobalServices.username,
location: Window.location.hostname,
framerate: Stats.framerate,
simrate: Stats.simrate,
ping: {
audio: Stats.audioPing,
avatar: Stats.avatarPing,
entities: Stats.entitiesPing,
asset: Stats.assetPing
},
position: Camera.position,
yaw: Stats.yaw,
rotation: Camera.orientation.x + ',' + Camera.orientation.y + ',' + Camera.orientation.z + ',' + Camera.orientation.w
}
}
function sendBatchToEndpoint(batch) {
// print('SEND BATCH TO ENDPOINT');
var req = new XMLHttpRequest();
req.open("POST", ENDPOINT_URL, false);
req.send(JSON.stringify(batch));
batch = [];
}

View file

@ -0,0 +1,67 @@
//
// statsExample.js
// examples/example/misc
//
// Created by Thijs Wenker on 24 Sept 2015
// Copyright 2015 High Fidelity, Inc.
//
// Prints the stats to the console.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// The stats to be displayed
var stats = [
'serverCount',
'framerate', // a.k.a. FPS
'simrate',
'avatarSimrate',
'avatarCount',
'packetInCount',
'packetOutCount',
'mbpsIn',
'mbpsOut',
'audioPing',
'avatarPing',
'entitiesPing',
'assetPing',
'velocity',
'yaw',
'avatarMixerKbps',
'avatarMixerPps',
'audioMixerKbps',
'audioMixerPps',
'downloads',
'downloadsPending',
'triangles',
'quads',
'materialSwitches',
'meshOpaque',
'meshTranslucent',
'opaqueConsidered',
'opaqueOutOfView',
'opaqueTooSmall',
'translucentConsidered',
'translucentOutOfView',
'translucentTooSmall',
'sendingMode',
'packetStats',
'lodStatus',
'timingStats',
'serverElements',
'serverInternal',
'serverLeaves',
'localElements',
'localInternal',
'localLeaves'
];
// Force update the stats, in case the stats panel is invisible
Stats.forceUpdateStats();
// Loop through the stats and display them
for (var i in stats) {
var stat = stats[i];
print(stat + " = " + Stats[stat]);
}

View file

@ -11,6 +11,17 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var normalDisplay = Entities.addEntity({
type: "Line",
name: "normalDisplay",
visible: false,
color: { red: 255, green: 0, blue: 0 },
dimensions: { x: 100, y: 100, z: 100 }
});
var wasVisible = false;
function mouseMoveEvent(event) {
print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
var pickRay = Camera.computePickRay(event.x, event.y);
@ -18,26 +29,45 @@ function mouseMoveEvent(event) {
print("computePickRay origin=" + pickRay.origin.x + ", " + pickRay.origin.y + ", " + pickRay.origin.z);
print("computePickRay direction=" + pickRay.direction.x + ", " + pickRay.direction.y + ", " + pickRay.direction.z);
var pickRay = Camera.computePickRay(event.x, event.y);
intersection = Entities.findRayIntersection(pickRay);
intersection = Entities.findRayIntersection(pickRay, true); // to get precise picking
if (!intersection.accurate) {
print(">>> NOTE: intersection not accurate. will try calling Entities.findRayIntersectionBlocking()");
intersection = Entities.findRayIntersectionBlocking(pickRay);
intersection = Entities.findRayIntersectionBlocking(pickRay, true); // to get precise picking
print(">>> AFTER BLOCKING CALL intersection.accurate=" + intersection.accurate);
}
if (intersection.intersects) {
print("intersection entityID.id=" + intersection.entityID.id);
print("intersection entityID=" + intersection.entityID);
print("intersection properties.modelURL=" + intersection.properties.modelURL);
print("intersection face=" + intersection.face);
print("intersection distance=" + intersection.distance);
print("intersection intersection.x/y/z=" + intersection.intersection.x + ", "
print("intersection intersection.x/y/z=" + intersection.intersection.x + ", "
+ intersection.intersection.y + ", " + intersection.intersection.z);
print("intersection surfaceNormal.x/y/z=" + intersection.surfaceNormal.x + ", "
+ intersection.surfaceNormal.y + ", " + intersection.surfaceNormal.z);
// Note: line entity takes points in "entity relative frame"
var lineStart = { x: 0, y: 0, z: 0 };
var lineEnd = intersection.surfaceNormal;
Entities.editEntity(normalDisplay, {
visible: true,
position: intersection.intersection,
linePoints: [lineStart, lineEnd],
});
wasVisible = true;
} else {
if (wasVisible) {
Entities.editEntity(normalDisplay, { visible: false });
wasVisible = false;
}
}
}
Controller.mouseMoveEvent.connect(mouseMoveEvent);
function scriptEnding() {
}
Script.scriptEnding.connect(scriptEnding);
Script.scriptEnding.connect(function() {
Entities.deleteEntity(normalDisplay);
});

View file

@ -101,7 +101,6 @@ Rocket = function(point, colorPalette) {
this.burst = false;
this.emitRate = randInt(80, 120);
this.emitStrength = randInt(5.0, 7.0);
this.rocket = Entities.addEntity({
type: "Sphere",
@ -163,6 +162,9 @@ Rocket.prototype.explode = function(position) {
});
var colorIndex = 0;
var PI = 3.141593;
var DEG_TO_RAD = PI / 180.0;
for (var i = 0; i < NUM_BURSTS; ++i) {
var color = this.colors[colorIndex];
print(JSON.stringify(color));
@ -172,12 +174,7 @@ Rocket.prototype.explode = function(position) {
position: position,
textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png',
emitRate: this.emitRate,
emitStrength: this.emitStrength,
emitDirection: {
x: Math.pow(-1, i) * randFloat(0.0, 1.4),
y: 1.0,
z: 0.0
},
polarFinish: 25 * DEG_TO_RAD,
color: color,
lifespan: 1.0,
visible: true,

View file

@ -6,6 +6,9 @@
var AUDIO_RANGE = 0.5 * RANGE;
var DIST_BETWEEN_BURSTS = 1.0;
var PI = 3.141593;
var DEG_TO_RAD = PI / 180.0;
var LOUDNESS_RADIUS_RATIO = 10;
var TEXTURE_PATH = 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png';
@ -86,12 +89,7 @@
position: this.point,
textures: TEXTURE_PATH,
emitRate: this.emitRate,
emitStrength: this.emitStrength,
emitDirection: {
x: Math.pow(-1, i) * randFloat(0.0, 0.4),
y: 1.0,
z: 0.0
},
polarFinish: 25 * DEG_TO_RAD,
color: color,
lifespan: 1.0,
visible: true,

View file

@ -35,13 +35,16 @@
firstFrame: 0,
lastFrame: 30,
loop: true });
var PI = 3.141593;
var DEG_TO_RAD = PI / 180.0;
this.entity = Entities.addEntity({ type: "ParticleEffect",
animationSettings: animationSettings,
position: spawnPoint,
dimensions: {x: 2, y: 2, z: 2},
emitVelocity: {x: 0, y: 5, z: 0},
velocitySpread: {x: 2, y: 0, z: 2},
emitSpeed: 5,
speedSpread: 2,
polarFinish: 30 * DEG_TO_RAD,
emitAcceleration: {x: 0, y: -9.8, z: 0},
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
color: color,

View file

@ -0,0 +1,47 @@
// createDoll.js
//
// Script Type: Entity
// Created by James B. Pollack @imgntn 9/23/2015
// Copyright 2015 High Fidelity, Inc.
//
// Creates a doll entity in front of you.
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
function createDoll() {
var modelURL = "http://hifi-public.s3.amazonaws.com/models/Bboys/bboy2/bboy2.fbx";
var scriptURL = Script.resolvePath("doll.js");
var center = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
var naturalDimensions = { x: 1.63, y: 1.67, z: 0.26};
var desiredDimensions = Vec3.multiply(naturalDimensions, 0.15);
var doll = Entities.addEntity({
type: "Model",
name: "doll",
modelURL: modelURL,
script: scriptURL,
position: center,
shapeType: 'box',
dimensions: desiredDimensions,
gravity: {
x: 0,
y: 0,
z: 0
},
velocity: {
x: 0,
y: 0,
z: 0
},
collisionsWillMove: true
});
return doll;
}
createDoll();

View file

@ -0,0 +1,92 @@
// doll.js
//
// Script Type: Entity
// Created by Eric Levin on 9/21/15.
// Additions by James B. Pollack @imgntn on 9/24/15
// Copyright 2015 High Fidelity, Inc.
//
// This entity script plays an animation and a sound while you hold it.
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Known issues: If you pass the doll between hands, animation can get into a weird state. We want to prevent the animation from starting again when you switch hands, but when you switch mid-animation your hand at release is still the first hand and not the current hand.
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
(function() {
Script.include("../../utilities.js");
Script.include("../../libraries/utils.js");
// this is the "constructor" for the entity as a JS object we don't do much here
var Doll = function() {
this.screamSounds = [SoundCache.getSound("https://hifi-public.s3.amazonaws.com/sounds/KenDoll_1%2303.wav")];
};
Doll.prototype = {
startAnimationSetting: JSON.stringify({
running: true,
fps: 30,
startFrame: 0,
lastFrame: 128,
startAutomatically: true
}),
stopAnimationSetting: JSON.stringify({running: false}),
audioInjector: null,
isGrabbed: false,
setLeftHand: function() {
this.hand = 'left';
},
setRightHand: function() {
this.hand = 'right';
},
startNearGrab: function() {
if (this.isGrabbed === false) {
Entities.editEntity(this.entityID, {
animationURL: "https://hifi-public.s3.amazonaws.com/models/Bboys/zombie_scream.fbx",
animationSettings: this.startAnimationSetting
});
var position = Entities.getEntityProperties(this.entityID, "position").position;
this.audioInjector = Audio.playSound(this.screamSounds[randInt(0, this.screamSounds.length)], {
position: position,
volume: 0.1
});
this.isGrabbed = true;
this.initialHand = this.hand;
}
},
continueNearGrab: function() {
var props = Entities.getEntityProperties(this.entityID, "position");
var audioOptions = {
position: props.position
};
this.audioInjector.options = audioOptions;
},
releaseGrab: function() {
if (this.isGrabbed === true && this.hand === this.initialHand) {
this.audioInjector.stop();
Entities.editEntity(this.entityID, {
animationSettings: this.stopAnimationSetting,
animationURL: "http://hifi-public.s3.amazonaws.com/models/Bboys/bboy2/bboy2.fbx",
});
this.isGrabbed = false;
}
},
preload: function(entityID) {
// preload() will be called when the entity has become visible (or known) to the interface
// it gives us a chance to set our local JavaScript object up. In this case it means:
// * remembering our entityID, so we can access it in cases where we're called without an entityID
this.entityID = entityID;
},
};
// entity scripts always need to return a newly constructed object of our type
return new Doll();
});

View file

@ -1,5 +1,5 @@
//
// createFlashligh.js
// createFlashlight.js
// examples/entityScripts
//
// Created by Sam Gateau on 9/9/15.
@ -11,7 +11,7 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js");
@ -19,31 +19,14 @@ var scriptURL = Script.resolvePath('flashlight.js?123123');
var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx";
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
x: 0,
y: 0.5,
z: 0
}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
var flashlight = Entities.addEntity({
type: "Model",
modelURL: modelURL,
position: center,
dimensions: {
x: 0.08,
y: 0.30,
z: 0.08
},
collisionsWillMove: true,
shapeType: 'box',
script: scriptURL
type: "Model",
modelURL: modelURL,
position: center,
dimensions: { x: 0.08, y: 0.30, z: 0.08},
collisionsWillMove: true,
shapeType: 'box',
script: scriptURL
});
function cleanup() {
//commenting out the line below makes this persistent. to delete at cleanup, uncomment
//Entities.deleteEntity(flashlight);
}
Script.scriptEnding.connect(cleanup);

View file

@ -14,39 +14,28 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
(function() {
Script.include("../../libraries/utils.js");
var _this;
var ON_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/flashlight_on.wav';
var OFF_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/flashlight_off.wav';
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
// our this object, so we can access it in cases where we're called without a this (like in the case of various global signals)
Flashlight = function() {
_this = this;
};
function Flashlight() {
return;
}
//if the trigger value goes below this while held, the flashlight will turn off. if it goes above, it will
var DISABLE_LIGHT_THRESHOLD = 0.7;
// These constants define the Spotlight position and orientation relative to the model
var MODEL_LIGHT_POSITION = {
x: 0,
y: -0.3,
z: 0
};
var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, {
x: 1,
y: 0,
z: 0
});
var MODEL_LIGHT_POSITION = { x: 0, y: -0.3, z: 0 };
var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { x: 1, y: 0, z: 0 });
var GLOW_LIGHT_POSITION = {
x: 0,
y: -0.1,
z: 0
}
var GLOW_LIGHT_POSITION = { x: 0, y: -0.1, z: 0};
// Evaluate the world light entity positions and orientations from the model ones
function evalLightWorldTransform(modelPos, modelRot) {
@ -55,15 +44,14 @@
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
};
};
}
function glowLightWorldTransform(modelPos, modelRot) {
return {
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, GLOW_LIGHT_POSITION)),
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
};
};
}
Flashlight.prototype = {
lightOn: false,
@ -74,11 +62,13 @@
setRightHand: function() {
this.hand = 'RIGHT';
},
setLeftHand: function() {
this.hand = 'LEFT';
},
startNearGrab: function() {
if (!_this.hasSpotlight) {
if (!this.hasSpotlight) {
//this light casts the beam
this.spotlight = Entities.addEntity({
@ -117,29 +107,16 @@
cutoff: 90, // in degrees
});
this.debugBox = Entities.addEntity({
type: 'Box',
color: {
red: 255,
blue: 0,
green: 0
},
dimensions: {
x: 0.25,
y: 0.25,
z: 0.25
},
ignoreForCollisions:true
})
this.hasSpotlight = true;
}
},
setWhichHand: function() {
this.whichHand = this.hand;
},
continueNearGrab: function() {
if (this.whichHand === null) {
//only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten
@ -149,6 +126,7 @@
this.changeLightWithTriggerPressure(this.whichHand);
}
},
releaseGrab: function() {
//delete the lights and reset state
if (this.hasSpotlight) {
@ -158,9 +136,10 @@
this.glowLight = null;
this.spotlight = null;
this.whichHand = null;
this.lightOn = false;
}
},
updateLightPositions: function() {
var modelProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
@ -168,27 +147,20 @@
var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation);
var glowLightTransform = glowLightWorldTransform(modelProperties.position, modelProperties.rotation);
//move them with the entity model
Entities.editEntity(this.spotlight, {
position: lightTransform.p,
rotation: lightTransform.q,
})
});
Entities.editEntity(this.glowLight, {
position: glowLightTransform.p,
rotation: glowLightTransform.q,
})
// Entities.editEntity(this.debugBox, {
// position: lightTransform.p,
// rotation: lightTransform.q,
// })
});
},
changeLightWithTriggerPressure: function(flashLightHand) {
changeLightWithTriggerPressure: function(flashLightHand) {
var handClickString = flashLightHand + "_HAND_CLICK";
var handClick = Controller.findAction(handClickString);
@ -200,37 +172,62 @@
} else if (this.triggerValue >= DISABLE_LIGHT_THRESHOLD && this.lightOn === false) {
this.turnLightOn();
}
return
return;
},
turnLightOff: function() {
this.playSoundAtCurrentPosition(false);
Entities.editEntity(this.spotlight, {
intensity: 0
});
Entities.editEntity(this.glowLight, {
intensity: 0
});
this.lightOn = false
this.lightOn = false;
},
turnLightOn: function() {
this.playSoundAtCurrentPosition(true);
Entities.editEntity(this.glowLight, {
intensity: 2
});
Entities.editEntity(this.spotlight, {
intensity: 2
});
this.lightOn = true
this.lightOn = true;
},
// preload() will be called when the entity has become visible (or known) to the interface
// it gives us a chance to set our local JavaScript object up. In this case it means:
// * remembering our entityID, so we can access it in cases where we're called without an entityID
playSoundAtCurrentPosition: function(playOnSound) {
var position = Entities.getEntityProperties(this.entityID, "position").position;
var audioProperties = {
volume: 0.25,
position: position
};
if (playOnSound) {
Audio.playSound(this.ON_SOUND, audioProperties);
} else {
Audio.playSound(this.OFF_SOUND, audioProperties);
}
},
preload: function(entityID) {
// preload() will be called when the entity has become visible (or known) to the interface
// it gives us a chance to set our local JavaScript object up. In this case it means:
// * remembering our entityID, so we can access it in cases where we're called without an entityID
// * preloading sounds
this.entityID = entityID;
this.ON_SOUND = SoundCache.getSound(ON_SOUND_URL);
this.OFF_SOUND = SoundCache.getSound(OFF_SOUND_URL);
},
// unload() will be called when our entity is no longer available. It may be because we were deleted,
// or because we've left the domain or quit the application.
unload: function(entityID) {
unload: function() {
// unload() will be called when our entity is no longer available. It may be because we were deleted,
// or because we've left the domain or quit the application.
if (this.hasSpotlight) {
Entities.deleteEntity(this.spotlight);
Entities.deleteEntity(this.glowLight);
@ -238,12 +235,13 @@
this.glowLight = null;
this.spotlight = null;
this.whichHand = null;
this.lightOn = false;
}
},
};
// entity scripts always need to return a newly constructed object of our type
return new Flashlight();
})
});

View file

@ -45,6 +45,8 @@ var explodeAnimationSettings = JSON.stringify({
var GRAVITY = -9.8;
var TIME_TO_EXPLODE = 2500;
var DISTANCE_IN_FRONT_OF_ME = 1.0;
var PI = 3.141593;
var DEG_TO_RAD = PI / 180.0;
function makeGrenade() {
var position = Vec3.sum(MyAvatar.position,
@ -87,8 +89,7 @@ function update() {
position: newProperties.position,
textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png',
emitRate: 100,
emitStrength: 2.0,
emitDirection: { x: 0.0, y: 1.0, z: 0.0 },
polarFinish: 25 * DEG_TO_RAD,
color: { red: 200, green: 0, blue: 0 },
lifespan: 10.0,
visible: true,
@ -145,8 +146,7 @@ function boom() {
position: properties.position,
textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png',
emitRate: 200,
emitStrength: 3.0,
emitDirection: { x: 0.0, y: 1.0, z: 0.0 },
polarFinish: 25 * DEG_TO_RAD,
color: { red: 255, green: 255, blue: 0 },
lifespan: 2.0,
visible: true,

View file

@ -13,6 +13,7 @@
var createdRenderMenu = false;
var createdGeneratedAudioMenu = false;
var createdAudioListenerModeMenu = false;
var createdStereoInputMenuItem = false;
var DEVELOPER_MENU = "Developer";
@ -29,6 +30,20 @@ var AUDIO_SOURCE_INJECT = "Generated Audio";
var AUDIO_SOURCE_MENU = AUDIO_MENU + " > Generated Audio Source";
var AUDIO_SOURCE_PINK_NOISE = "Pink Noise";
var AUDIO_SOURCE_SINE_440 = "Sine 440hz";
var AUDIO_LISTENER_MODE_MENU = AUDIO_MENU + " > Audio Listener Mode"
var AUDIO_LISTENER_MODE_FROM_HEAD = "Audio from head";
var AUDIO_LISTENER_MODE_FROM_CAMERA = "Audio from camera";
var AUDIO_LISTENER_MODE_CUSTOM = "Audio from custom position";
// be sure that the audio listener options are in the right order (same as the enumerator)
var AUDIO_LISTENER_OPTIONS = [
// MyAvatar.FROM_HEAD (0)
AUDIO_LISTENER_MODE_FROM_HEAD,
// MyAvatar.FROM_CAMERA (1)
AUDIO_LISTENER_MODE_FROM_CAMERA,
// MyAvatar.CUSTOM (2)
AUDIO_LISTENER_MODE_CUSTOM
];
var AUDIO_STEREO_INPUT = "Stereo Input";
@ -67,7 +82,6 @@ function setupMenus() {
Menu.addMenuItem({ menuName: RENDER_MENU, menuItemName: AVATARS_ITEM, isCheckable: true, isChecked: Scene.shouldRenderAvatars })
}
if (!Menu.menuExists(AUDIO_MENU)) {
Menu.addMenu(AUDIO_MENU);
}
@ -80,6 +94,15 @@ function setupMenus() {
Audio.selectPinkNoise();
createdGeneratedAudioMenu = true;
}
if (!Menu.menuExists(AUDIO_LISTENER_MODE_MENU)) {
Menu.addMenu(AUDIO_LISTENER_MODE_MENU);
for (var i = 0; i < AUDIO_LISTENER_OPTIONS.length; i++) {
Menu.addMenuItem({ menuName: AUDIO_LISTENER_MODE_MENU, menuItemName: AUDIO_LISTENER_OPTIONS[i], isCheckable: true, isChecked: (MyAvatar.audioListenerMode === i) });
}
createdAudioListenerModeMenu = true;
}
if (!Menu.menuItemExists(AUDIO_MENU, AUDIO_STEREO_INPUT)) {
Menu.addMenuItem({ menuName: AUDIO_MENU, menuItemName: AUDIO_STEREO_INPUT, isCheckable: true, isChecked: false });
createdStereoInputMenuItem = true;
@ -99,15 +122,23 @@ Menu.menuItemEvent.connect(function (menuItem) {
Scene.shouldRenderAvatars = Menu.isOptionChecked(AVATARS_ITEM);
} else if (menuItem == AUDIO_SOURCE_INJECT && !createdGeneratedAudioMenu) {
Audio.injectGeneratedNoise(Menu.isOptionChecked(AUDIO_SOURCE_INJECT));
} else if (menuItem == AUDIO_SOURCE_PINK_NOISE && !createdGeneratedAudioMenu) {
Audio.selectPinkNoise();
Menu.setIsOptionChecked(AUDIO_SOURCE_SINE_440, false);
} else if (menuItem == AUDIO_SOURCE_SINE_440 && !createdGeneratedAudioMenu) {
Audio.selectSine440();
Menu.setIsOptionChecked(AUDIO_SOURCE_PINK_NOISE, false);
} else if (menuItem == AUDIO_STEREO_INPUT) {
Audio.setStereoInput(Menu.isOptionChecked(AUDIO_STEREO_INPUT))
}
} else if (menuItem == AUDIO_SOURCE_PINK_NOISE && !createdGeneratedAudioMenu) {
Audio.selectPinkNoise();
Menu.setIsOptionChecked(AUDIO_SOURCE_SINE_440, false);
} else if (menuItem == AUDIO_SOURCE_SINE_440 && !createdGeneratedAudioMenu) {
Audio.selectSine440();
Menu.setIsOptionChecked(AUDIO_SOURCE_PINK_NOISE, false);
} else if (menuItem == AUDIO_STEREO_INPUT) {
Audio.setStereoInput(Menu.isOptionChecked(AUDIO_STEREO_INPUT))
} else if (AUDIO_LISTENER_OPTIONS.indexOf(menuItem) !== -1) {
MyAvatar.audioListenerMode = AUDIO_LISTENER_OPTIONS.indexOf(menuItem);
}
});
MyAvatar.audioListenerModeChanged.connect(function() {
for (var i = 0; i < AUDIO_LISTENER_OPTIONS.length; i++) {
Menu.setIsOptionChecked(AUDIO_LISTENER_OPTIONS[i], (MyAvatar.audioListenerMode === i));
}
});
Scene.shouldRenderAvatarsChanged.connect(function(shouldRenderAvatars) {
@ -134,6 +165,10 @@ function scriptEnding() {
Menu.removeMenu(AUDIO_SOURCE_MENU);
}
if (createdAudioListenerModeMenu) {
Menu.removeMenu(AUDIO_LISTENER_MODE_MENU);
}
if (createdStereoInputMenuItem) {
Menu.removeMenuItem(AUDIO_MENU, AUDIO_STEREO_INPUT);
}

View file

@ -23,10 +23,16 @@
"positionVar": "leftHandPosition",
"rotationVar": "leftHandRotation"
},
{
"jointName": "Neck",
"positionVar": "neckPosition",
"rotationVar": "neckRotation"
},
{
"jointName": "Head",
"positionVar": "headPosition",
"rotationVar": "headRotation"
"rotationVar": "headRotation",
"typeVar": "headType"
}
]
},
@ -554,7 +560,7 @@
"id": "strafeLeft",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx",
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_left.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,
@ -566,7 +572,7 @@
"id": "strafeRight",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx",
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_right.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,

View file

@ -708,7 +708,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// Now that menu is initalized we can sync myAvatar with it's state.
_myAvatar->updateMotionBehaviorFromMenu();
_myAvatar->updateStandingHMDModeFromMenu();
// the 3Dconnexion device wants to be initiliazed after a window is displayed.
ConnexionClient::getInstance().init();
@ -794,6 +793,8 @@ void Application::cleanupBeforeQuit() {
DependencyManager::get<EyeTracker>()->setEnabled(false, true);
#endif
AnimDebugDraw::getInstance().shutdown();
if (_keyboardFocusHighlightID > 0) {
getOverlays().deleteOverlay(_keyboardFocusHighlightID);
_keyboardFocusHighlightID = -1;
@ -1033,15 +1034,10 @@ void Application::initializeUi() {
updateInputModes();
}
template<typename F>
void doInBatch(RenderArgs* args, F f) {
gpu::Batch batch;
f(batch);
args->_context->render(batch);
}
void Application::paintGL() {
PROFILE_RANGE(__FUNCTION__);
PerformanceTimer perfTimer("paintGL");
if (nullptr == _displayPlugin) {
return;
}
@ -1058,6 +1054,7 @@ void Application::paintGL() {
auto displayPlugin = getActiveDisplayPlugin();
displayPlugin->preRender();
_offscreenContext->makeCurrent();
// update the avatar with a fresh HMD pose
_myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose());
@ -1068,18 +1065,19 @@ void Application::paintGL() {
lodManager->getBoundaryLevelAdjust(), RenderArgs::DEFAULT_RENDER_MODE,
RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE);
PerformanceTimer perfTimer("paintGL");
PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings));
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::paintGL()");
resizeGL();
// Before anything else, let's sync up the gpuContext with the true glcontext used in case anything happened
renderArgs._context->syncCache();
{
PerformanceTimer perfTimer("syncCache");
renderArgs._context->syncCache();
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
PerformanceTimer perfTimer("Mirror");
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebufferDepthColor();
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
@ -1093,12 +1091,12 @@ void Application::paintGL() {
auto mirrorRectDest = glm::ivec4(mirrorRect.z, mirrorRect.y, mirrorRect.x, mirrorRect.w);
auto selfieFbo = DependencyManager::get<FramebufferCache>()->getSelfieFramebuffer();
gpu::Batch batch;
batch.setFramebuffer(selfieFbo);
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f));
batch.blit(primaryFbo, mirrorRect, selfieFbo, mirrorRectDest);
batch.setFramebuffer(nullptr);
renderArgs._context->render(batch);
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
batch.setFramebuffer(selfieFbo);
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f));
batch.blit(primaryFbo, mirrorRect, selfieFbo, mirrorRectDest);
batch.setFramebuffer(nullptr);
});
}
}
@ -1111,81 +1109,86 @@ void Application::paintGL() {
_applicationOverlay.renderOverlay(&renderArgs);
}
_myAvatar->startCapture();
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, _myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !(_myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN));
Application::getInstance()->cameraMenuChanged();
}
{
PerformanceTimer perfTimer("CameraUpdates");
// The render mode is default or mirror if the camera is in mirror mode, assigned further below
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
// Always use the default eye position, not the actual head eye position.
// Using the latter will cause the camera to wobble with idle animations,
// or with changes from the face tracker
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
if (isHMDMode()) {
mat4 camMat = _myAvatar->getSensorToWorldMatrix() * _myAvatar->getHMDSensorMatrix();
_myCamera.setPosition(extractTranslation(camMat));
_myCamera.setRotation(glm::quat_cast(camMat));
} else {
_myCamera.setPosition(_myAvatar->getDefaultEyePosition());
_myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation());
_myAvatar->startCapture();
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, _myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !(_myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN));
Application::getInstance()->cameraMenuChanged();
}
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
if (isHMDMode()) {
glm::quat hmdRotation = extractRotation(_myAvatar->getHMDSensorMatrix());
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * hmdRotation);
// Ignore MenuOption::CenterPlayerInView in HMD view
glm::vec3 hmdOffset = extractTranslation(_myAvatar->getHMDSensorMatrix());
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
+ _myAvatar->getOrientation()
* (_myAvatar->getScale() * _myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f) + hmdOffset));
} else {
_myCamera.setRotation(_myAvatar->getHead()->getOrientation());
if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) {
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
+ _myCamera.getRotation()
* (_myAvatar->getScale() * _myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
// The render mode is default or mirror if the camera is in mirror mode, assigned further below
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
// Always use the default eye position, not the actual head eye position.
// Using the latter will cause the camera to wobble with idle animations,
// or with changes from the face tracker
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
if (isHMDMode()) {
mat4 camMat = _myAvatar->getSensorToWorldMatrix() * _myAvatar->getHMDSensorMatrix();
_myCamera.setPosition(extractTranslation(camMat));
_myCamera.setRotation(glm::quat_cast(camMat));
} else {
_myCamera.setPosition(_myAvatar->getDefaultEyePosition());
_myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation());
}
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
if (isHMDMode()) {
glm::quat hmdRotation = extractRotation(_myAvatar->getHMDSensorMatrix());
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * hmdRotation);
// Ignore MenuOption::CenterPlayerInView in HMD view
glm::vec3 hmdOffset = extractTranslation(_myAvatar->getHMDSensorMatrix());
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
+ _myAvatar->getOrientation()
* (_myAvatar->getScale() * _myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
* (_myAvatar->getScale() * _myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f) + hmdOffset));
} else {
_myCamera.setRotation(_myAvatar->getHead()->getOrientation());
if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) {
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
+ _myCamera.getRotation()
* (_myAvatar->getScale() * _myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
} else {
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
+ _myAvatar->getOrientation()
* (_myAvatar->getScale() * _myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
}
}
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
if (isHMDMode()) {
glm::quat hmdRotation = extractRotation(_myAvatar->getHMDSensorMatrix());
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)) * hmdRotation);
glm::vec3 hmdOffset = extractTranslation(_myAvatar->getHMDSensorMatrix());
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0)
+ (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
+ (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))) * hmdOffset);
} else {
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0)
+ (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
}
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
}
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
if (isHMDMode()) {
glm::quat hmdRotation = extractRotation(_myAvatar->getHMDSensorMatrix());
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)) * hmdRotation);
glm::vec3 hmdOffset = extractTranslation(_myAvatar->getHMDSensorMatrix());
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0)
+ (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
+ (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))) * hmdOffset);
} else {
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0)
+ (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
// Update camera position
if (!isHMDMode()) {
_myCamera.update(1.0f / _fps);
}
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
_myAvatar->endCapture();
}
// Update camera position
if (!isHMDMode()) {
_myCamera.update(1.0f / _fps);
}
_myAvatar->endCapture();
// Primary rendering pass
auto framebufferCache = DependencyManager::get<FramebufferCache>();
const QSize size = framebufferCache->getFrameBufferSize();
{
PROFILE_RANGE(__FUNCTION__ "/mainRender");
PerformanceTimer perfTimer("mainRender");
// Viewport is assigned to the size of the framebuffer
renderArgs._viewport = ivec4(0, 0, size.width(), size.height());
if (displayPlugin->isStereo()) {
@ -1222,7 +1225,7 @@ void Application::paintGL() {
}
displaySide(&renderArgs, _myCamera);
renderArgs._context->enableStereo(false);
doInBatch(&renderArgs, [](gpu::Batch& batch) {
gpu::doInBatch(renderArgs._context, [](gpu::Batch& batch) {
batch.setFramebuffer(nullptr);
});
}
@ -1230,6 +1233,7 @@ void Application::paintGL() {
// Overlay Composition, needs to occur after screen space effects have completed
{
PROFILE_RANGE(__FUNCTION__ "/compositor");
PerformanceTimer perfTimer("compositor");
auto primaryFbo = framebufferCache->getPrimaryFramebuffer();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFbo));
if (displayPlugin->isStereo()) {
@ -1254,6 +1258,7 @@ void Application::paintGL() {
// deliver final composited scene to the display plugin
{
PROFILE_RANGE(__FUNCTION__ "/pluginOutput");
PerformanceTimer perfTimer("pluginOutput");
auto primaryFbo = framebufferCache->getPrimaryFramebuffer();
GLuint finalTexture = gpu::GLBackend::getTextureID(primaryFbo->getRenderBuffer(0));
// Ensure the rendering context commands are completed when rendering
@ -1271,24 +1276,29 @@ void Application::paintGL() {
{
PROFILE_RANGE(__FUNCTION__ "/pluginDisplay");
PerformanceTimer perfTimer("pluginDisplay");
displayPlugin->display(finalTexture, toGlm(size));
}
{
PROFILE_RANGE(__FUNCTION__ "/bufferSwap");
PerformanceTimer perfTimer("bufferSwap");
displayPlugin->finishFrame();
}
}
_offscreenContext->makeCurrent();
_frameCount++;
Stats::getInstance()->setRenderDetails(renderArgs._details);
{
PerformanceTimer perfTimer("makeCurrent");
_offscreenContext->makeCurrent();
_frameCount++;
Stats::getInstance()->setRenderDetails(renderArgs._details);
// Reset the gpu::Context Stages
// Back to the default framebuffer;
gpu::Batch batch;
batch.resetStages();
renderArgs._context->render(batch);
// Reset the gpu::Context Stages
// Back to the default framebuffer;
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
batch.resetStages();
});
}
}
void Application::runTests() {
@ -2330,7 +2340,8 @@ bool Application::exportEntities(const QString& filename, const QVector<EntityIt
QVector<EntityItemPointer> entities;
auto entityTree = _entities.getTree();
EntityTree exportTree;
auto exportTree = std::make_shared<EntityTree>();
exportTree->createRootElement();
glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE);
for (auto entityID : entityIDs) {
@ -2357,10 +2368,10 @@ bool Application::exportEntities(const QString& filename, const QVector<EntityIt
auto properties = entityItem->getProperties();
properties.setPosition(properties.getPosition() - root);
exportTree.addEntity(entityItem->getEntityItemID(), properties);
exportTree->addEntity(entityItem->getEntityItemID(), properties);
}
exportTree.writeToJSONFile(filename.toLocal8Bit().constData());
exportTree->writeToJSONFile(filename.toLocal8Bit().constData());
// restore the main window's active state
_window->activateWindow();
@ -2373,15 +2384,16 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa
if (entities.size() > 0) {
glm::vec3 root(x, y, z);
EntityTree exportTree;
auto exportTree = std::make_shared<EntityTree>();
exportTree->createRootElement();
for (int i = 0; i < entities.size(); i++) {
EntityItemProperties properties = entities.at(i)->getProperties();
EntityItemID id = entities.at(i)->getEntityItemID();
properties.setPosition(properties.getPosition() - root);
exportTree.addEntity(id, properties);
exportTree->addEntity(id, properties);
}
exportTree.writeToSVOFile(filename.toLocal8Bit().constData());
exportTree->writeToSVOFile(filename.toLocal8Bit().constData());
} else {
qCDebug(interfaceapp) << "No models were selected";
return false;
@ -4029,6 +4041,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
// hook our avatar and avatar hash map object into this script engine
scriptEngine->registerGlobalObject("MyAvatar", _myAvatar);
qScriptRegisterMetaType(scriptEngine, audioListenModeToScriptValue, audioListenModeFromScriptValue);
scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get<AvatarManager>().data());
scriptEngine->registerGlobalObject("Camera", &_myCamera);
@ -4063,6 +4077,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Stats", Stats::getInstance());
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());

View file

@ -142,8 +142,8 @@ public:
static Application* getInstance() { return qApp; } // TODO: replace fully by qApp
static const glm::vec3& getPositionForPath() { return getInstance()->_myAvatar->getPosition(); }
static glm::quat getOrientationForPath() { return getInstance()->_myAvatar->getOrientation(); }
static glm::vec3 getPositionForAudio() { return getInstance()->_myAvatar->getHead()->getPosition(); }
static glm::quat getOrientationForAudio() { return getInstance()->_myAvatar->getHead()->getFinalOrientationInWorldFrame(); }
static glm::vec3 getPositionForAudio() { return getInstance()->_myAvatar->getPositionForAudio(); }
static glm::quat getOrientationForAudio() { return getInstance()->_myAvatar->getOrientationForAudio(); }
static void initPlugins();
static void shutdownPlugins();

View file

@ -294,9 +294,6 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::StandingHMDSensorMode, 0, false,
avatar, SLOT(updateStandingHMDModeFromMenu()));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::WorldAxes);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats);

View file

@ -124,14 +124,12 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
_isEyeTrackerConnected = eyeTracker->isTracking();
}
if (!myAvatar->getStandingHMDSensorMode()) {
// Twist the upper body to follow the rotation of the head, but only do this with my avatar,
// since everyone else will see the full joint rotations for other people.
const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f;
const float BODY_FOLLOW_HEAD_FACTOR = 0.66f;
float currentTwist = getTorsoTwist();
setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE);
}
// Twist the upper body to follow the rotation of the head, but only do this with my avatar,
// since everyone else will see the full joint rotations for other people.
const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f;
const float BODY_FOLLOW_HEAD_FACTOR = 0.66f;
float currentTwist = getTorsoTwist();
setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE);
}
if (!(_isFaceTrackerConnected || billboard)) {
@ -392,7 +390,7 @@ glm::quat Head::getCameraOrientation() const {
// always the same.
if (qApp->getAvatarUpdater()->isHMDMode()) {
MyAvatar* myAvatar = dynamic_cast<MyAvatar*>(_owningAvatar);
if (myAvatar && myAvatar->getStandingHMDSensorMode()) {
if (myAvatar) {
return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation();
} else {
return getOrientation();

View file

@ -47,6 +47,7 @@
#include "Recorder.h"
#include "Util.h"
#include "InterfaceLogging.h"
#include "DebugDraw.h"
using namespace std;
@ -103,12 +104,12 @@ MyAvatar::MyAvatar(RigPointer rig) :
_hmdSensorPosition(),
_bodySensorMatrix(),
_sensorToWorldMatrix(),
_standingHMDSensorMode(false),
_goToPending(false),
_goToPosition(),
_goToOrientation(),
_rig(rig),
_prevShouldDrawHead(true)
_prevShouldDrawHead(true),
_audioListenerMode(FROM_HEAD)
{
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
@ -149,6 +150,25 @@ void MyAvatar::reset() {
eulers.x = 0.0f;
eulers.z = 0.0f;
setOrientation(glm::quat(eulers));
// This should be simpler when we have only graph animations always on.
bool isRig = _rig->getEnableRig();
bool isGraph = _rig->getEnableAnimGraph();
qApp->setRawAvatarUpdateThreading(false);
_rig->disableHands = true;
setEnableRigAnimations(true);
_skeletonModel.simulate(0.1f); // non-zero
setEnableRigAnimations(false);
_skeletonModel.simulate(0.1f);
if (isRig) {
setEnableRigAnimations(true);
Menu::getInstance()->setIsOptionChecked(MenuOption::EnableRigAnimations, true);
} else if (isGraph) {
setEnableAnimGraph(true);
Menu::getInstance()->setIsOptionChecked(MenuOption::EnableAnimGraph, true);
}
_rig->disableHands = false;
qApp->setRawAvatarUpdateThreading();
}
void MyAvatar::update(float deltaTime) {
@ -247,29 +267,68 @@ void MyAvatar::simulate(float deltaTime) {
}
glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
if (getStandingHMDSensorMode()) {
return _sensorToWorldMatrix;
} else {
return createMatFromQuatAndPos(getWorldAlignedOrientation(), getDefaultEyePosition());
}
return _sensorToWorldMatrix;
}
// best called at start of main loop just after we have a fresh hmd pose.
// update internal body position from new hmd pose.
// Pass a recent sample of the HMD to the avatar.
// This can also update the avatar's position to follow the HMD
// as it moves through the world.
void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
auto now = usecTimestampNow();
auto deltaUsecs = now - _lastUpdateFromHMDTime;
_lastUpdateFromHMDTime = now;
double actualDeltaTime = (double)deltaUsecs / (double)USECS_PER_SECOND;
const float BIGGEST_DELTA_TIME_SECS = 0.25f;
float deltaTime = glm::clamp((float)actualDeltaTime, 0.0f, BIGGEST_DELTA_TIME_SECS);
// update the sensorMatrices based on the new hmd pose
_hmdSensorMatrix = hmdSensorMatrix;
_hmdSensorPosition = extractTranslation(hmdSensorMatrix);
_hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix);
_bodySensorMatrix = deriveBodyFromHMDSensor();
if (getStandingHMDSensorMode()) {
// set the body position/orientation to reflect motion due to the head.
auto worldMat = _sensorToWorldMatrix * _bodySensorMatrix;
nextAttitude(extractTranslation(worldMat), glm::quat_cast(worldMat));
const float STRAIGHTING_LEAN_DURATION = 0.5f; // seconds
const float STRAIGHTING_LEAN_THRESHOLD = 0.2f; // meters
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
glm::vec3 diff = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix);
if (!_straightingLean && glm::length(diff) > STRAIGHTING_LEAN_THRESHOLD) {
// begin homing toward derived body position.
_straightingLean = true;
_straightingLeanAlpha = 0.0f;
} else if (_straightingLean) {
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix;
glm::vec3 worldBodyPos = extractTranslation(worldBodyMatrix);
glm::quat worldBodyRot = glm::normalize(glm::quat_cast(worldBodyMatrix));
_straightingLeanAlpha += (1.0f / STRAIGHTING_LEAN_DURATION) * deltaTime;
if (_straightingLeanAlpha >= 1.0f) {
_straightingLean = false;
nextAttitude(worldBodyPos, worldBodyRot);
_bodySensorMatrix = newBodySensorMatrix;
} else {
// interp position toward the desired pos
glm::vec3 pos = lerp(getPosition(), worldBodyPos, _straightingLeanAlpha);
glm::quat rot = glm::normalize(safeMix(getOrientation(), worldBodyRot, _straightingLeanAlpha));
nextAttitude(pos, rot);
// interp sensor matrix toward desired
glm::vec3 nextBodyPos = extractTranslation(newBodySensorMatrix);
glm::quat nextBodyRot = glm::normalize(glm::quat_cast(newBodySensorMatrix));
glm::vec3 prevBodyPos = extractTranslation(_bodySensorMatrix);
glm::quat prevBodyRot = glm::normalize(glm::quat_cast(_bodySensorMatrix));
pos = lerp(prevBodyPos, nextBodyPos, _straightingLeanAlpha);
rot = glm::normalize(safeMix(prevBodyRot, nextBodyRot, _straightingLeanAlpha));
_bodySensorMatrix = createMatFromQuatAndPos(rot, pos);
}
}
}
//
// best called at end of main loop, just before rendering.
// update sensor to world matrix from current body position and hmd sensor.
// This is so the correct camera can be used for rendering.
@ -338,11 +397,9 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
Head* head = getHead();
if (inHmd || isPlaying()) {
if (!getStandingHMDSensorMode()) {
head->setDeltaPitch(estimatedRotation.x);
head->setDeltaYaw(estimatedRotation.y);
head->setDeltaRoll(estimatedRotation.z);
}
head->setDeltaPitch(estimatedRotation.x);
head->setDeltaYaw(estimatedRotation.y);
head->setDeltaRoll(estimatedRotation.z);
} else {
float magnifyFieldOfView = qApp->getFieldOfView() /
_realWorldFieldOfView.get();
@ -364,12 +421,10 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
relativePosition.x = -relativePosition.x;
}
if (!(inHmd && getStandingHMDSensorMode())) {
head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
}
head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
}
@ -1333,7 +1388,7 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
// bones are aligned such that z is forward, not -z.
glm::quat rotY180 = glm::angleAxis((float)M_PI, glm::vec3(0.0f, 1.0f, 0.0f));
AnimPose xform(glm::vec3(1), rotY180 * getOrientation(), getPosition());
AnimPose xform(glm::vec3(1), getOrientation() * rotY180, getPosition());
if (_enableDebugDrawBindPose && _debugDrawSkeleton) {
glm::vec4 gray(0.2f, 0.2f, 0.2f, 0.2f);
@ -1358,6 +1413,9 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
}
}
DebugDraw::getInstance().updateMyAvatarPos(getPosition());
DebugDraw::getInstance().updateMyAvatarRot(getOrientation());
if (shouldDrawHead != _prevShouldDrawHead) {
_skeletonModel.setCauterizeBones(!shouldDrawHead);
}
@ -1720,11 +1778,6 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
_characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
}
void MyAvatar::updateStandingHMDModeFromMenu() {
Menu* menu = Menu::getInstance();
_standingHMDSensorMode = menu->isOptionChecked(MenuOption::StandingHMDSensorMode);
}
//Renders sixense laser pointers for UI selection with controllers
void MyAvatar::renderLaserPointers(gpu::Batch& batch) {
const float PALM_TIP_ROD_RADIUS = 0.002f;
@ -1810,3 +1863,42 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
// avatar facing is determined solely by hmd orientation.
return createMatFromQuatAndPos(hmdOrientationYawOnly, bodyPos);
}
glm::vec3 MyAvatar::getPositionForAudio() {
switch (_audioListenerMode) {
case AudioListenerMode::FROM_HEAD:
return getHead()->getPosition();
case AudioListenerMode::FROM_CAMERA:
return Application::getInstance()->getCamera()->getPosition();
case AudioListenerMode::CUSTOM:
return _customListenPosition;
}
return vec3();
}
glm::quat MyAvatar::getOrientationForAudio() {
switch (_audioListenerMode) {
case AudioListenerMode::FROM_HEAD:
return getHead()->getFinalOrientationInWorldFrame();
case AudioListenerMode::FROM_CAMERA:
return Application::getInstance()->getCamera()->getOrientation();
case AudioListenerMode::CUSTOM:
return _customListenOrientation;
}
return quat();
}
void MyAvatar::setAudioListenerMode(AudioListenerMode audioListenerMode) {
if (_audioListenerMode != audioListenerMode) {
_audioListenerMode = audioListenerMode;
emit audioListenerModeChanged();
}
}
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode) {
return audioListenerMode;
}
void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMode& audioListenerMode) {
audioListenerMode = (AudioListenerMode)object.toUInt16();
}

View file

@ -26,6 +26,14 @@ enum eyeContactTarget {
MOUTH
};
enum AudioListenerMode {
FROM_HEAD = 0,
FROM_CAMERA,
CUSTOM
};
Q_DECLARE_METATYPE(AudioListenerMode);
class MyAvatar : public Avatar {
Q_OBJECT
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
@ -33,12 +41,21 @@ class MyAvatar : public Avatar {
Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale)
Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame)
Q_PROPERTY(QString collisionSoundURL READ getCollisionSoundURL WRITE setCollisionSoundURL)
Q_PROPERTY(AudioListenerMode audioListenerMode READ getAudioListenerMode WRITE setAudioListenerMode)
Q_PROPERTY(glm::vec3 customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition)
Q_PROPERTY(glm::quat customListenOrientation READ getCustomListenOrientation WRITE setCustomListenOrientation)
Q_PROPERTY(AudioListenerMode FROM_HEAD READ getAudioListenerModeHead)
Q_PROPERTY(AudioListenerMode FROM_CAMERA READ getAudioListenerModeCamera)
Q_PROPERTY(AudioListenerMode CUSTOM READ getAudioListenerModeCustom)
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
public:
MyAvatar(RigPointer rig);
~MyAvatar();
AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; }
AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; }
AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; }
void reset();
void update(float deltaTime);
@ -49,8 +66,9 @@ public:
const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
glm::mat4 getSensorToWorldMatrix() const;
// best called at start of main loop just after we have a fresh hmd pose.
// update internal body position from new hmd pose.
// Pass a recent sample of the HMD to the avatar.
// This can also update the avatar's position to follow the HMD
// as it moves through the world.
void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix);
// best called at end of main loop, just before rendering.
@ -151,10 +169,16 @@ public:
static const float ZOOM_MAX;
static const float ZOOM_DEFAULT;
bool getStandingHMDSensorMode() const { return _standingHMDSensorMode; }
void doUpdateBillboard();
void destroyAnimGraph();
AudioListenerMode getAudioListenerMode() { return _audioListenerMode; }
void setAudioListenerMode(AudioListenerMode audioListenerMode);
glm::vec3 getCustomListenPosition() { return _customListenPosition; }
void setCustomListenPosition(glm::vec3 customListenPosition) { _customListenPosition = customListenPosition; }
glm::quat getCustomListenOrientation() { return _customListenOrientation; }
void setCustomListenOrientation(glm::quat customListenOrientation) { _customListenOrientation = customListenOrientation; }
public slots:
void increaseSize();
void decreaseSize();
@ -170,7 +194,6 @@ public slots:
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
void updateMotionBehaviorFromMenu();
void updateStandingHMDModeFromMenu();
glm::vec3 getLeftPalmPosition();
glm::vec3 getLeftPalmVelocity();
@ -204,7 +227,11 @@ public slots:
void setEnableMeshVisible(bool isEnabled);
void setAnimGraphUrl(const QString& url) { _animGraphUrl = url; }
glm::vec3 getPositionForAudio();
glm::quat getOrientationForAudio();
signals:
void audioListenerModeChanged();
void transformChanged();
void newCollisionSoundURL(const QUrl& url);
void collisionWithEntity(const Collision& collision);
@ -317,8 +344,6 @@ private:
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
glm::mat4 _sensorToWorldMatrix;
bool _standingHMDSensorMode;
bool _goToPending;
glm::vec3 _goToPosition;
glm::quat _goToOrientation;
@ -330,6 +355,18 @@ private:
bool _enableDebugDrawBindPose = false;
bool _enableDebugDrawAnimPose = false;
AnimSkeleton::ConstPointer _debugDrawSkeleton = nullptr;
AudioListenerMode _audioListenerMode;
glm::vec3 _customListenPosition;
glm::quat _customListenOrientation;
bool _straightingLean = false;
float _straightingLeanAlpha = 0.0f;
quint64 _lastUpdateFromHMDTime = usecTimestampNow();
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMode& audioListenerMode);
#endif // hifi_MyAvatar_h

View file

@ -21,6 +21,7 @@
#include "SkeletonModel.h"
#include "Util.h"
#include "InterfaceLogging.h"
#include "AnimDebugDraw.h"
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) :
Model(rig, parent),
@ -122,23 +123,39 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
Rig::HeadParameters headParams;
headParams.modelRotation = getRotation();
headParams.modelTranslation = getTranslation();
headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode();
headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode();
headParams.leanSideways = head->getFinalLeanSideways();
headParams.leanForward = head->getFinalLeanForward();
headParams.torsoTwist = head->getTorsoTwist();
headParams.localHeadOrientation = head->getFinalOrientationInLocalFrame();
headParams.localHeadPitch = head->getFinalPitch();
headParams.localHeadYaw = head->getFinalYaw();
headParams.localHeadRoll = head->getFinalRoll();
headParams.isInHMD = qApp->getAvatarUpdater()->isHMDMode();
// get HMD position from sensor space into world space, and back into model space
glm::mat4 worldToModel = glm::inverse(createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()));
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 hmdPosition = glm::angleAxis((float)M_PI, yAxis) * transformPoint(worldToModel * myAvatar->getSensorToWorldMatrix(), myAvatar->getHMDSensorPosition());
headParams.localHeadPosition = hmdPosition;
if (qApp->getAvatarUpdater()->isHMDMode()) {
headParams.isInHMD = true;
// get HMD position from sensor space into world space, and back into model space
AnimPose avatarToWorld(glm::vec3(1), myAvatar->getOrientation(), myAvatar->getPosition());
glm::mat4 worldToAvatar = glm::inverse((glm::mat4)avatarToWorld);
glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
glm::mat4 hmdMat = worldToAvatar * worldHMDMat;
// in local avatar space (i.e. relative to avatar pos/orientation.)
glm::vec3 hmdPosition = extractTranslation(hmdMat);
glm::quat hmdOrientation = extractRotation(hmdMat);
headParams.localHeadPosition = hmdPosition;
headParams.localHeadOrientation = hmdOrientation;
headParams.worldHeadOrientation = extractRotation(worldHMDMat);
} else {
headParams.isInHMD = false;
// We don't have a valid localHeadPosition.
headParams.localHeadOrientation = head->getFinalOrientationInLocalFrame();
headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
}
headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
headParams.eyeLookAt = head->getLookAtPosition();
headParams.eyeSaccade = head->getSaccade();
headParams.leanJointIndex = geometry.leanJointIndex;
@ -229,33 +246,36 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
Hand* hand = _owningAvatar->getHand();
hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
const float HAND_RESTORATION_RATE = 0.25f;
if (leftPalmIndex == -1 && rightPalmIndex == -1) {
// palms are not yet set, use mouse
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
} else {
// transform into model-frame
glm::vec3 handPosition = glm::inverse(_rotation) * (_owningAvatar->getHandPosition() - _translation);
applyHandPosition(geometry.rightHandJointIndex, handPosition);
}
restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
} else if (leftPalmIndex == rightPalmIndex) {
// right hand only
applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[leftPalmIndex]);
restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
} else {
if (leftPalmIndex != -1) {
applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]);
} else {
// Don't Relax toward hand positions when in animGraph mode.
if (!_rig->getEnableAnimGraph()) {
const float HAND_RESTORATION_RATE = 0.25f;
if (leftPalmIndex == -1 && rightPalmIndex == -1) {
// palms are not yet set, use mouse
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
} else {
// transform into model-frame
glm::vec3 handPosition = glm::inverse(_rotation) * (_owningAvatar->getHandPosition() - _translation);
applyHandPosition(geometry.rightHandJointIndex, handPosition);
}
restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
}
if (rightPalmIndex != -1) {
applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]);
} else if (leftPalmIndex == rightPalmIndex) {
// right hand only
applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[leftPalmIndex]);
restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
} else {
restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
if (leftPalmIndex != -1) {
applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]);
} else {
restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
}
if (rightPalmIndex != -1) {
applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]);
} else {
restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
}
}
}
}

View file

@ -160,6 +160,8 @@ ConnexionClient& ConnexionClient::getInstance() {
#ifdef Q_OS_WIN
#include <VersionHelpers.h>
void ConnexionClient::toggleConnexion(bool shouldEnable) {
ConnexionData& connexiondata = ConnexionData::getInstance();
if (shouldEnable && connexiondata.getDeviceID() == 0) {
@ -425,18 +427,13 @@ bool ConnexionClient::InitializeRawInput(HWND hwndTarget) {
return false;
}
// FIXME - http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe
// Get OS version.
OSVERSIONINFO osvi = { sizeof(OSVERSIONINFO), 0 };
::GetVersionEx(&osvi);
unsigned int cbSize = sizeof(devicesToRegister[0]);
for (size_t i = 0; i < numDevices; i++) {
// Set the target window to use
//devicesToRegister[i].hwndTarget = hwndTarget;
// If Vista or newer, enable receiving the WM_INPUT_DEVICE_CHANGE message.
if (osvi.dwMajorVersion >= 6) {
if (IsWindowsVistaOrGreater()) {
devicesToRegister[i].dwFlags |= RIDEV_DEVNOTIFY;
}
}

View file

@ -19,17 +19,40 @@ float ClipboardScriptingInterface::getClipboardContentsLargestDimension() {
}
bool ClipboardScriptingInterface::exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs) {
return Application::getInstance()->exportEntities(filename, entityIDs);
bool retVal;
QMetaObject::invokeMethod(Application::getInstance(), "exportEntities", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, retVal),
Q_ARG(const QString&, filename),
Q_ARG(const QVector<EntityItemID>&, entityIDs));
return retVal;
}
bool ClipboardScriptingInterface::exportEntities(const QString& filename, float x, float y, float z, float s) {
return Application::getInstance()->exportEntities(filename, x, y, z, s);
bool retVal;
QMetaObject::invokeMethod(Application::getInstance(), "exportEntities", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, retVal),
Q_ARG(const QString&, filename),
Q_ARG(float, x),
Q_ARG(float, y),
Q_ARG(float, z),
Q_ARG(float, s));
return retVal;
}
bool ClipboardScriptingInterface::importEntities(const QString& filename) {
return Application::getInstance()->importEntities(filename);
bool retVal;
QMetaObject::invokeMethod(Application::getInstance(), "importEntities", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, retVal),
Q_ARG(const QString&, filename));
return retVal;
}
QVector<EntityItemID> ClipboardScriptingInterface::pasteEntities(glm::vec3 position) {
return Application::getInstance()->pasteEntities(position.x, position.y, position.z);
QVector<EntityItemID> retVal;
QMetaObject::invokeMethod(Application::getInstance(), "pasteEntities", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVector<EntityItemID>, retVal),
Q_ARG(float, position.x),
Q_ARG(float, position.y),
Q_ARG(float, position.z));
return retVal;
}

View file

@ -207,36 +207,36 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) {
updateTooltips();
//Handle fading and deactivation/activation of UI
gpu::Batch batch;
gpu::doInBatch(renderArgs->_context, [=](gpu::Batch& batch) {
auto geometryCache = DependencyManager::get<GeometryCache>();
auto geometryCache = DependencyManager::get<GeometryCache>();
geometryCache->useSimpleDrawPipeline(batch);
batch.setViewportTransform(renderArgs->_viewport);
batch.setModelTransform(Transform());
batch.setViewTransform(Transform());
batch.setProjectionTransform(mat4());
batch.setResourceTexture(0, overlayFramebuffer->getRenderBuffer(0));
geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha));
geometryCache->useSimpleDrawPipeline(batch);
batch.setViewportTransform(renderArgs->_viewport);
batch.setModelTransform(Transform());
batch.setViewTransform(Transform());
batch.setProjectionTransform(mat4());
batch.setResourceTexture(0, overlayFramebuffer->getRenderBuffer(0));
geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha));
// Doesn't actually render
renderPointers(batch);
// Doesn't actually render
renderPointers(batch);
//draw the mouse pointer
// Get the mouse coordinates and convert to NDC [-1, 1]
vec2 canvasSize = qApp->getCanvasSize();
vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize);
// Invert the Y axis
mousePosition.y *= -1.0f;
//draw the mouse pointer
// Get the mouse coordinates and convert to NDC [-1, 1]
vec2 canvasSize = qApp->getCanvasSize();
vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize);
// Invert the Y axis
mousePosition.y *= -1.0f;
Transform model;
model.setTranslation(vec3(mousePosition, 0));
vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize;
model.setScale(vec3(mouseSize, 1.0f));
batch.setModelTransform(model);
bindCursorTexture(batch);
geometryCache->renderUnitQuad(batch, vec4(1));
renderArgs->_context->render(batch);
Transform model;
model.setTranslation(vec3(mousePosition, 0));
vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize;
model.setScale(vec3(mouseSize, 1.0f));
batch.setModelTransform(model);
bindCursorTexture(batch);
geometryCache->renderUnitQuad(batch, vec4(1));
});
}
@ -278,75 +278,67 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int
auto geometryCache = DependencyManager::get<GeometryCache>();
gpu::Batch batch;
geometryCache->useSimpleDrawPipeline(batch);
//batch._glDisable(GL_DEPTH_TEST);
//batch._glDisable(GL_CULL_FACE);
//batch._glBindTexture(GL_TEXTURE_2D, texture);
//batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gpu::doInBatch(renderArgs->_context, [=](gpu::Batch& batch) {
geometryCache->useSimpleDrawPipeline(batch);
batch.setResourceTexture(0, overlayFramebuffer->getRenderBuffer(0));
batch.setResourceTexture(0, overlayFramebuffer->getRenderBuffer(0));
mat4 camMat;
_cameraBaseTransform.getMatrix(camMat);
camMat = camMat * qApp->getEyePose(eye);
batch.setViewportTransform(renderArgs->_viewport);
batch.setViewTransform(camMat);
mat4 camMat;
_cameraBaseTransform.getMatrix(camMat);
camMat = camMat * qApp->getEyePose(eye);
batch.setViewportTransform(renderArgs->_viewport);
batch.setViewTransform(camMat);
batch.setProjectionTransform(qApp->getEyeProjection(eye));
batch.setProjectionTransform(qApp->getEyeProjection(eye));
#ifdef DEBUG_OVERLAY
{
batch.setModelTransform(glm::translate(mat4(), vec3(0, 0, -2)));
geometryCache->renderUnitQuad(batch, glm::vec4(1));
}
#else
{
//batch.setModelTransform(overlayXfm);
#ifdef DEBUG_OVERLAY
{
batch.setModelTransform(glm::translate(mat4(), vec3(0, 0, -2)));
geometryCache->renderUnitQuad(batch, glm::vec4(1));
}
#else
{
batch.setModelTransform(_modelTransform);
drawSphereSection(batch);
}
#endif
batch.setModelTransform(_modelTransform);
drawSphereSection(batch);
}
#endif
// Doesn't actually render
renderPointers(batch);
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize);
// Doesn't actually render
renderPointers(batch);
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize);
bindCursorTexture(batch);
bindCursorTexture(batch);
//Controller Pointers
glm::mat4 overlayXfm;
_modelTransform.getMatrix(overlayXfm);
//Controller Pointers
glm::mat4 overlayXfm;
_modelTransform.getMatrix(overlayXfm);
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) {
PalmData& palm = myAvatar->getHand()->getPalms()[i];
if (palm.isActive()) {
glm::vec2 polar = getPolarCoordinates(palm);
// Convert to quaternion
mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
mat4 reticleXfm = overlayXfm * pointerXfm;
reticleXfm = glm::scale(reticleXfm, reticleScale);
batch.setModelTransform(reticleXfm);
// Render reticle at location
geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad);
}
}
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) {
PalmData& palm = myAvatar->getHand()->getPalms()[i];
if (palm.isActive()) {
glm::vec2 polar = getPolarCoordinates(palm);
// Convert to quaternion
mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
//Mouse Pointer
if (_reticleActive[MOUSE]) {
glm::vec2 projection = screenToSpherical(glm::vec2(_reticlePosition[MOUSE].x(),
_reticlePosition[MOUSE].y()));
mat4 pointerXfm = glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
mat4 reticleXfm = overlayXfm * pointerXfm;
reticleXfm = glm::scale(reticleXfm, reticleScale);
batch.setModelTransform(reticleXfm);
// Render reticle at location
geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad);
}
}
//Mouse Pointer
if (_reticleActive[MOUSE]) {
glm::vec2 projection = screenToSpherical(glm::vec2(_reticlePosition[MOUSE].x(),
_reticlePosition[MOUSE].y()));
mat4 pointerXfm = glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
mat4 reticleXfm = overlayXfm * pointerXfm;
reticleXfm = glm::scale(reticleXfm, reticleScale);
batch.setModelTransform(reticleXfm);
geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad);
}
renderArgs->_context->render(batch);
});
}

View file

@ -70,29 +70,28 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
}
// Execute the batch into our framebuffer
gpu::Batch batch;
renderArgs->_batch = &batch;
doInBatch(renderArgs->_context, [=](gpu::Batch& batch) {
renderArgs->_batch = &batch;
int width = _overlayFramebuffer->getWidth();
int height = _overlayFramebuffer->getHeight();
int width = _overlayFramebuffer->getWidth();
int height = _overlayFramebuffer->getHeight();
batch.setViewportTransform(glm::ivec4(0, 0, width, height));
batch.setFramebuffer(_overlayFramebuffer);
batch.setViewportTransform(glm::ivec4(0, 0, width, height));
batch.setFramebuffer(_overlayFramebuffer);
glm::vec4 color { 0.0f, 0.0f, 0.0f, 0.0f };
float depth = 1.0f;
int stencil = 0;
batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH, color, depth, stencil);
glm::vec4 color { 0.0f, 0.0f, 0.0f, 0.0f };
float depth = 1.0f;
int stencil = 0;
batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH, color, depth, stencil);
// Now render the overlay components together into a single texture
renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line
renderAudioScope(renderArgs); // audio scope in the very back
renderRearView(renderArgs); // renders the mirror view selfie
renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts
renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope
renderStatsAndLogs(renderArgs); // currently renders nothing
renderArgs->_context->render(batch);
// Now render the overlay components together into a single texture
renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line
renderAudioScope(renderArgs); // audio scope in the very back
renderRearView(renderArgs); // renders the mirror view selfie
renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts
renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope
renderStatsAndLogs(renderArgs); // currently renders nothing
});
renderArgs->_batch = nullptr; // so future users of renderArgs don't try to use our batch

View file

@ -68,12 +68,7 @@ void OverlayConductor::updateMode() {
Mode newMode;
if (qApp->isHMDMode()) {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (myAvatar->getStandingHMDSensorMode()) {
newMode = STANDING;
} else {
newMode = SITTING;
}
newMode = SITTING;
} else {
newMode = FLAT;
}

View file

@ -46,6 +46,7 @@ Stats::Stats(QQuickItem* parent) : QQuickItem(parent) {
INSTANCE = this;
const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
_monospaceFont = font.family();
_audioStats = &DependencyManager::get<AudioClient>()->getStats();
}
bool Stats::includeTimingRecord(const QString& name) {
@ -89,14 +90,14 @@ bool Stats::includeTimingRecord(const QString& name) {
}
void Stats::updateStats() {
if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
if (isVisible()) {
setVisible(false);
}
return;
} else {
if (!isVisible()) {
void Stats::updateStats(bool force) {
if (!force) {
if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
if (isVisible()) {
setVisible(false);
}
return;
} else if (!isVisible()) {
setVisible(true);
}
}
@ -161,7 +162,7 @@ void Stats::updateStats() {
STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z));
STAT_UPDATE_FLOAT(velocity, glm::length(myAvatar->getVelocity()), 0.1f);
STAT_UPDATE_FLOAT(yaw, myAvatar->getBodyYaw(), 0.1f);
if (_expanded) {
if (_expanded || force) {
SharedNodePointer avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer);
if (avatarMixer) {
STAT_UPDATE(avatarMixerKbps, roundf(
@ -175,7 +176,7 @@ void Stats::updateStats() {
STAT_UPDATE(avatarMixerPps, -1);
}
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixerNode) {
if (audioMixerNode || force) {
STAT_UPDATE(audioMixerKbps, roundf(
bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) +
bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer)));
@ -230,7 +231,7 @@ void Stats::updateStats() {
totalLeaves += stats.getTotalLeaves();
}
}
if (_expanded) {
if (_expanded || force) {
if (serverCount == 0) {
sendingModeStream << "---";
}
@ -272,7 +273,7 @@ void Stats::updateStats() {
STAT_UPDATE(serverElements, (int)totalNodes);
STAT_UPDATE(localElements, (int)OctreeElement::getNodeCount());
if (_expanded) {
if (_expanded || force) {
STAT_UPDATE(serverInternal, (int)totalInternal);
STAT_UPDATE(serverLeaves, (int)totalLeaves);
// Local Voxels

View file

@ -12,6 +12,7 @@
#include <OffscreenQmlElement.h>
#include <RenderArgs.h>
#include <QVector3D>
#include <AudioIOStats.h>
#define STATS_PROPERTY(type, name, initialValue) \
Q_PROPERTY(type name READ name NOTIFY name##Changed) \
@ -27,6 +28,8 @@ class Stats : public QQuickItem {
Q_PROPERTY(bool expanded READ isExpanded WRITE setExpanded NOTIFY expandedChanged)
Q_PROPERTY(bool timingExpanded READ isTimingExpanded NOTIFY timingExpandedChanged)
Q_PROPERTY(QString monospaceFont READ monospaceFont CONSTANT)
Q_PROPERTY(float audioPacketlossUpstream READ getAudioPacketLossUpstream)
Q_PROPERTY(float audioPacketlossDownstream READ getAudioPacketLossDownstream)
STATS_PROPERTY(int, serverCount, 0)
STATS_PROPERTY(int, framerate, 0)
@ -81,7 +84,11 @@ public:
const QString& monospaceFont() {
return _monospaceFont;
}
void updateStats();
float getAudioPacketLossUpstream() { return _audioStats->getMixerAvatarStreamStats()._packetStreamStats.getLostRate(); }
float getAudioPacketLossDownstream() { return _audioStats->getMixerDownstreamStats()._packetStreamStats.getLostRate(); }
void updateStats(bool force = false);
bool isExpanded() { return _expanded; }
bool isTimingExpanded() { return _timingExpanded; }
@ -93,6 +100,9 @@ public:
}
}
public slots:
void forceUpdateStats() { updateStats(true); }
signals:
void expandedChanged();
void timingExpandedChanged();
@ -146,6 +156,8 @@ private:
bool _expanded{ false };
bool _timingExpanded{ false };
QString _monospaceFont;
const AudioIOStats* _audioStats;
};
#endif // hifi_Stats_h

View file

@ -171,6 +171,6 @@ QScriptValue Base3DOverlay::getProperty(const QString& property) {
}
bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) {
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
return false;
}

View file

@ -57,11 +57,12 @@ public:
virtual void setProperties(const QScriptValue& properties);
virtual QScriptValue getProperty(const QString& property);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, QString& extraInfo) {
return findRayIntersection(origin, direction, distance, face);
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
return findRayIntersection(origin, direction, distance, face, surfaceNormal);
}
protected:

View file

@ -391,10 +391,10 @@ QScriptValue Circle3DOverlay::getProperty(const QString& property) {
}
bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin,
const glm::vec3& direction, float& distance, BoxFace& face) {
bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) {
bool intersects = Planar3DOverlay::findRayIntersection(origin, direction, distance, face);
bool intersects = Planar3DOverlay::findRayIntersection(origin, direction, distance, face, surfaceNormal);
if (intersects) {
glm::vec3 hitPosition = origin + (distance * direction);

View file

@ -52,7 +52,8 @@ public:
void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; }
void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; }
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);
virtual Circle3DOverlay* createClone() const;

View file

@ -166,7 +166,7 @@ void Image3DOverlay::setURL(const QString& url) {
}
bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) {
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
if (_texture && _texture->isLoaded()) {
// Make sure position and rotation is updated.
applyTransformTo(_transform, true);
@ -178,6 +178,7 @@ bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec
float maxSize = glm::max(width, height);
glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize);
// FIXME - face and surfaceNormal not being set
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), dimensions, distance);
}

View file

@ -38,7 +38,8 @@ public:
virtual void setProperties(const QScriptValue& properties);
virtual QScriptValue getProperty(const QString& property);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);
virtual Image3DOverlay* createClone() const;

View file

@ -159,16 +159,16 @@ QScriptValue ModelOverlay::getProperty(const QString& property) {
}
bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) {
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
QString subMeshNameTemp;
return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, subMeshNameTemp);
return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp);
}
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, QString& extraInfo) {
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo);
return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
}
ModelOverlay* ModelOverlay::createClone() const {

View file

@ -29,9 +29,10 @@ public:
virtual void render(RenderArgs* args);
virtual void setProperties(const QScriptValue& properties);
virtual QScriptValue getProperty(const QString& property);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, QString& extraInfo);
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo);
virtual ModelOverlay* createClone() const;

View file

@ -346,6 +346,7 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
glm::vec3 origin(pointCopy.x, pointCopy.y, LARGE_NEGATIVE_FLOAT);
glm::vec3 direction(0, 0, 1);
BoxFace thisFace;
glm::vec3 thisSurfaceNormal;
float distance;
while (i.hasPrevious()) {
@ -354,7 +355,7 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
if (i.value()->is3D()) {
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
if (thisOverlay && !thisOverlay->getIgnoreRayIntersection()) {
if (thisOverlay->findRayIntersection(origin, direction, distance, thisFace)) {
if (thisOverlay->findRayIntersection(origin, direction, distance, thisFace, thisSurfaceNormal)) {
return thisID;
}
}
@ -423,8 +424,10 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray)
if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnoreRayIntersection() && thisOverlay->isLoaded()) {
float thisDistance;
BoxFace thisFace;
glm::vec3 thisSurfaceNormal;
QString thisExtraInfo;
if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance, thisFace, thisExtraInfo)) {
if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance,
thisFace, thisSurfaceNormal, thisExtraInfo)) {
bool isDrawInFront = thisOverlay->getDrawInFront();
if (thisDistance < bestDistance && (!bestIsFront || isDrawInFront)) {
bestIsFront = isDrawInFront;
@ -432,6 +435,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray)
result.intersects = true;
result.distance = thisDistance;
result.face = thisFace;
result.surfaceNormal = thisSurfaceNormal;
result.overlayID = thisID;
result.intersection = ray.origin + (ray.direction * thisDistance);
result.extraInfo = thisExtraInfo;

View file

@ -46,6 +46,7 @@ public:
int overlayID;
float distance;
BoxFace face;
glm::vec3 surfaceNormal;
glm::vec3 intersection;
QString extraInfo;
};

View file

@ -92,6 +92,7 @@ QScriptValue Planar3DOverlay::getProperty(const QString& property) {
}
bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) {
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
// FIXME - face and surfaceNormal not being returned
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), getDimensions(), distance);
}

View file

@ -29,7 +29,8 @@ public:
virtual void setProperties(const QScriptValue& properties);
virtual QScriptValue getProperty(const QString& property);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);
protected:
glm::vec2 _dimensions;

View file

@ -211,7 +211,8 @@ QSizeF Text3DOverlay::textSize(const QString& text) const {
return QSizeF(extents.x, extents.y) * pointToWorldScale;
}
bool Text3DOverlay::findRayIntersection(const glm::vec3 &origin, const glm::vec3 &direction, float &distance, BoxFace &face) {
bool Text3DOverlay::findRayIntersection(const glm::vec3 &origin, const glm::vec3 &direction, float &distance,
BoxFace &face, glm::vec3& surfaceNormal) {
applyTransformTo(_transform, true);
return Billboard3DOverlay::findRayIntersection(origin, direction, distance, face);
return Billboard3DOverlay::findRayIntersection(origin, direction, distance, face, surfaceNormal);
}

View file

@ -54,7 +54,8 @@ public:
QSizeF textSize(const QString& test) const; // Meters
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);
virtual Text3DOverlay* createClone() const;

View file

@ -93,7 +93,7 @@ QScriptValue Volume3DOverlay::getProperty(const QString& property) {
}
bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) {
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
// extents is the entity relative, scaled, centered extents of the entity
glm::mat4 worldToEntityMatrix;
_transform.getInverseMatrix(worldToEntityMatrix);
@ -103,5 +103,5 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve
// we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame
// and testing intersection there.
return _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face);
return _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal);
}

View file

@ -29,7 +29,8 @@ public:
virtual void setProperties(const QScriptValue& properties);
virtual QScriptValue getProperty(const QString& property);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);
protected:
// Centered local bounding box

View file

@ -151,8 +151,10 @@ void Web3DOverlay::setURL(const QString& url) {
}
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) {
//// Make sure position and rotation is updated.
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
// FIXME - face and surfaceNormal not being returned
// Make sure position and rotation is updated.
applyTransformTo(_transform, true);
vec2 size = _resolution / _dpi * INCHES_TO_METERS * vec2(getDimensions());
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.

View file

@ -34,7 +34,8 @@ public:
virtual void setProperties(const QScriptValue& properties);
virtual QScriptValue getProperty(const QString& property);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);
virtual Web3DOverlay* createClone() const;

View file

@ -9,6 +9,7 @@
#include "AnimInverseKinematics.h"
#include <GeometryUtil.h>
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <SharedUtil.h>
@ -42,9 +43,8 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) {
void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) const {
int numJoints = (int)_relativePoses.size();
absolutePoses.clear();
absolutePoses.resize(numJoints);
assert(numJoints <= _skeleton->getNumJoints());
assert(numJoints == (int)absolutePoses.size());
for (int i = 0; i < numJoints; ++i) {
int parentIndex = _skeleton->getParentIndex(i);
if (parentIndex < 0) {
@ -55,7 +55,11 @@ void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) con
}
}
void AnimInverseKinematics::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar) {
void AnimInverseKinematics::setTargetVars(
const QString& jointName,
const QString& positionVar,
const QString& rotationVar,
const QString& typeVar) {
// if there are dups, last one wins.
bool found = false;
for (auto& targetVar: _targetVarVec) {
@ -63,13 +67,14 @@ void AnimInverseKinematics::setTargetVars(const QString& jointName, const QStrin
// update existing targetVar
targetVar.positionVar = positionVar;
targetVar.rotationVar = rotationVar;
targetVar.typeVar = typeVar;
found = true;
break;
}
}
if (!found) {
// create a new entry
_targetVarVec.push_back(IKTargetVar(jointName, positionVar, rotationVar));
_targetVarVec.push_back(IKTargetVar(jointName, positionVar, rotationVar, typeVar));
}
}
@ -86,6 +91,7 @@ static int findRootJointInSkeleton(AnimSkeleton::ConstPointer skeleton, int inde
void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets) {
// build a list of valid targets from _targetVarVec and animVars
_maxTargetIndex = -1;
bool removeUnfoundJoints = false;
for (auto& targetVar : _targetVarVec) {
if (targetVar.jointIndex == -1) {
@ -100,14 +106,17 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
removeUnfoundJoints = true;
}
} else {
// TODO: get this done without a double-lookup of each var in animVars
IKTarget target;
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses);
target.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans);
target.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot);
target.setType(animVars.lookup(targetVar.typeVar, QString("")));
target.rootIndex = targetVar.rootIndex;
target.index = targetVar.jointIndex;
targets.push_back(target);
if (target.index > _maxTargetIndex) {
_maxTargetIndex = target.index;
}
}
}
@ -129,9 +138,10 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
}
}
void AnimInverseKinematics::solveWithCyclicCoordinateDescent(std::vector<IKTarget>& targets) {
void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets) {
// compute absolute poses that correspond to relative target poses
AnimPoseVec absolutePoses;
absolutePoses.resize(_relativePoses.size());
computeAbsolutePoses(absolutePoses);
// clear the accumulators before we start the IK solver
@ -139,86 +149,124 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(std::vector<IKTarge
accumulator.clearAndClean();
}
float largestError = 0.0f;
const float ACCEPTABLE_RELATIVE_ERROR = 1.0e-3f;
int numLoops = 0;
const int MAX_IK_LOOPS = 16;
const quint64 MAX_IK_TIME = 10 * USECS_PER_MSEC;
quint64 expiry = usecTimestampNow() + MAX_IK_TIME;
const int MAX_IK_LOOPS = 4;
do {
largestError = 0.0f;
int lowestMovedIndex = _relativePoses.size();
for (auto& target: targets) {
int tipIndex = target.index;
if (target.type == IKTarget::Type::RotationOnly) {
// the final rotation will be enforced after the iterations
continue;
}
AnimPose targetPose = target.pose;
glm::vec3 tip = absolutePoses[tipIndex].trans;
float error = glm::length(targetPose.trans - tip);
// cache tip absolute transform
glm::vec3 tipPosition = absolutePoses[tipIndex].trans;
glm::quat tipRotation = absolutePoses[tipIndex].rot;
// cache tip's parent's absolute rotation so we can recompute the tip's parent-relative
// as we proceed walking down the joint chain
int pivotIndex = _skeleton->getParentIndex(tipIndex);
glm::quat tipParentRotation;
if (pivotIndex != -1) {
tipParentRotation = absolutePoses[pivotIndex].rot;
}
// descend toward root, pivoting each joint to get tip closer to target
int pivotIndex = _skeleton->getParentIndex(tipIndex);
while (pivotIndex != -1 && error > ACCEPTABLE_RELATIVE_ERROR) {
int ancestorCount = 1;
while (pivotIndex != -1) {
// compute the two lines that should be aligned
glm::vec3 jointPosition = absolutePoses[pivotIndex].trans;
glm::vec3 leverArm = tip - jointPosition;
glm::vec3 leverArm = tipPosition - jointPosition;
glm::vec3 targetLine = targetPose.trans - jointPosition;
// compute the axis of the rotation that would align them
// compute the swing that would get get tip closer
glm::vec3 axis = glm::cross(leverArm, targetLine);
float axisLength = glm::length(axis);
if (axisLength > EPSILON) {
// compute deltaRotation for alignment (brings tip closer to target)
glm::quat deltaRotation;
const float MIN_AXIS_LENGTH = 1.0e-4f;
if (axisLength > MIN_AXIS_LENGTH) {
// compute deltaRotation for alignment (swings tip closer to target)
axis /= axisLength;
float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)));
// NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is
// still possible for the angle to be zero so we also check that to avoid unnecessary calculations.
if (angle > EPSILON) {
// reduce angle by half: slows convergence but adds stability to IK solution
angle = 0.5f * angle;
glm::quat deltaRotation = glm::angleAxis(angle, axis);
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
if (angle > MIN_ADJUSTMENT_ANGLE) {
// reduce angle by a fraction (reduces IK swing contribution of this joint)
angle /= (float)ancestorCount;
deltaRotation = glm::angleAxis(angle, axis);
}
int parentIndex = _skeleton->getParentIndex(pivotIndex);
if (parentIndex == -1) {
// TODO? apply constraints to root?
// TODO? harvest the root's transform as movement of entire skeleton?
} else {
// compute joint's new parent-relative rotation
// Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q
glm::quat newRot = glm::normalize(glm::inverse(
absolutePoses[parentIndex].rot) *
deltaRotation *
absolutePoses[pivotIndex].rot);
RotationConstraint* constraint = getConstraint(pivotIndex);
if (constraint) {
bool constrained = constraint->apply(newRot);
if (constrained) {
// the constraint will modify the movement of the tip so we have to compute the modified
// model-frame deltaRotation
// Q' = Qp^ * dQ * Q --> dQ = Qp * Q' * Q^
deltaRotation = absolutePoses[parentIndex].rot *
newRot *
glm::inverse(absolutePoses[pivotIndex].rot);
}
}
// store the rotation change in the accumulator
_accumulators[pivotIndex].add(newRot);
}
// this joint has been changed so we check to see if it has the lowest index
if (pivotIndex < lowestMovedIndex) {
lowestMovedIndex = pivotIndex;
}
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
// new rotation and its target. We compute that delta here and rotate the tipJoint accordingly.
glm::quat tipRelativeRotation = glm::inverse(deltaRotation * tipParentRotation) * targetPose.rot;
// keep track of tip's new position as we descend towards root
tip = jointPosition + deltaRotation * leverArm;
error = glm::length(targetPose.trans - tip);
// enforce tip's constraint
RotationConstraint* constraint = getConstraint(tipIndex);
if (constraint) {
bool constrained = constraint->apply(tipRelativeRotation);
if (constrained) {
// The tip's final parent-relative rotation violates its constraint
// so we try to twist this pivot to compensate.
glm::quat constrainedTipRotation = deltaRotation * tipParentRotation * tipRelativeRotation;
glm::quat missingRotation = targetPose.rot * glm::inverse(constrainedTipRotation);
glm::quat swingPart;
glm::quat twistPart;
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
deltaRotation = twistPart * deltaRotation;
}
// we update the tip rotation here to rotate it as close to its target orientation as possible
// before moving on to next pivot
tipRotation = tipParentRotation * tipRelativeRotation;
}
}
++ancestorCount;
int parentIndex = _skeleton->getParentIndex(pivotIndex);
if (parentIndex == -1) {
// TODO? apply constraints to root?
// TODO? harvest the root's transform as movement of entire skeleton?
} else {
// compute joint's new parent-relative rotation after swing
// Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q
glm::quat newRot = glm::normalize(glm::inverse(
absolutePoses[parentIndex].rot) *
deltaRotation *
absolutePoses[pivotIndex].rot);
// enforce pivot's constraint
RotationConstraint* constraint = getConstraint(pivotIndex);
if (constraint) {
bool constrained = constraint->apply(newRot);
if (constrained) {
// the constraint will modify the movement of the tip so we have to compute the modified
// model-frame deltaRotation
// Q' = Qp^ * dQ * Q --> dQ = Qp * Q' * Q^
deltaRotation = absolutePoses[parentIndex].rot *
newRot *
glm::inverse(absolutePoses[pivotIndex].rot);
}
}
// store the rotation change in the accumulator
_accumulators[pivotIndex].add(newRot);
}
// this joint has been changed so we check to see if it has the lowest index
if (pivotIndex < lowestMovedIndex) {
lowestMovedIndex = pivotIndex;
}
// keep track of tip's new transform as we descend towards root
tipPosition = jointPosition + deltaRotation * leverArm;
tipRotation = glm::normalize(deltaRotation * tipRotation);
tipParentRotation = glm::normalize(deltaRotation * tipParentRotation);
pivotIndex = _skeleton->getParentIndex(pivotIndex);
}
if (largestError < error) {
largestError = error;
}
}
++numLoops;
@ -238,7 +286,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(std::vector<IKTarge
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
}
}
} while (largestError > ACCEPTABLE_RELATIVE_ERROR && numLoops < MAX_IK_LOOPS && usecTimestampNow() < expiry);
} while (numLoops < MAX_IK_LOOPS);
// finally set the relative rotation of each tip to agree with absolute target rotation
for (auto& target: targets) {
@ -380,7 +428,7 @@ void AnimInverseKinematics::initConstraints() {
}
}
_constraints.clear();
clearConstraints();
for (int i = 0; i < numJoints; ++i) {
// compute the joint's baseName and remember whether its prefix was "Left" or not
QString baseName = _skeleton->getJointName(i);
@ -456,7 +504,7 @@ void AnimInverseKinematics::initConstraints() {
} else if (0 == baseName.compare("Hand", Qt::CaseInsensitive)) {
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot);
const float MAX_HAND_TWIST = PI;
const float MAX_HAND_TWIST = 3.0f * PI / 5.0f;
const float MIN_HAND_TWIST = -PI / 2.0f;
if (isLeft) {
stConstraint->setTwistLimits(-MAX_HAND_TWIST, -MIN_HAND_TWIST);
@ -639,9 +687,12 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
targetVar.jointIndex = -1;
}
_maxTargetIndex = 0;
_maxTargetIndex = -1;
for (auto& accumulator: _accumulators) {
accumulator.clearAndClean();
}
_accumulators.clear();
if (skeleton) {
initConstraints();
} else {

View file

@ -31,20 +31,27 @@ public:
void loadPoses(const AnimPoseVec& poses);
void computeAbsolutePoses(AnimPoseVec& absolutePoses) const;
void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar);
void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, const QString& typeVar);
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) override;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
protected:
struct IKTarget {
enum class Type {
RotationAndPosition,
RotationOnly
};
AnimPose pose;
int index;
int rootIndex;
Type type = Type::RotationAndPosition;
void setType(const QString& typeVar) { type = ((typeVar == "RotationOnly") ? Type::RotationOnly : Type::RotationAndPosition); }
};
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets);
void solveWithCyclicCoordinateDescent(std::vector<IKTarget>& targets);
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton);
// for AnimDebugDraw rendering
@ -55,15 +62,20 @@ protected:
void initConstraints();
struct IKTargetVar {
IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn) :
IKTargetVar(const QString& jointNameIn,
const QString& positionVarIn,
const QString& rotationVarIn,
const QString& typeVarIn) :
positionVar(positionVarIn),
rotationVar(rotationVarIn),
typeVar(typeVarIn),
jointName(jointNameIn),
jointIndex(-1),
rootIndex(-1) {}
QString positionVar;
QString rotationVar;
QString typeVar;
QString jointName;
int jointIndex; // cached joint index
int rootIndex; // cached root index

View file

@ -29,6 +29,12 @@ const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, flo
const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
_alpha = animVars.lookup(_alphaVar, _alpha);
_poses = underPoses;
if (underPoses.size() == 0) {
return _poses;
}
for (auto& jointVar : _jointVars) {
if (!jointVar.hasPerformedJointLookup) {
jointVar.jointIndex = _skeleton->nameToJointIndex(jointVar.jointName);

View file

@ -61,7 +61,7 @@ public:
// hierarchy accessors
void addChild(Pointer child) { _children.push_back(child); }
void removeChild(Pointer child);
Pointer getChild(int i) const;
int getChildCount() const { return (int)_children.size(); }

View file

@ -342,8 +342,9 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS
READ_STRING(jointName, targetObj, id, jsonUrl, nullptr);
READ_STRING(positionVar, targetObj, id, jsonUrl, nullptr);
READ_STRING(rotationVar, targetObj, id, jsonUrl, nullptr);
READ_OPTIONAL_STRING(typeVar, targetObj);
node->setTargetVars(jointName, positionVar, rotationVar);
node->setTargetVars(jointName, positionVar, rotationVar, typeVar);
};
return node;

View file

@ -51,8 +51,8 @@ const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, float d
_alpha = animVars.lookup(_alphaVar, _alpha);
if (_children.size() >= 2) {
auto underPoses = _children[1]->evaluate(animVars, dt, triggersOut);
auto overPoses = _children[0]->overlay(animVars, dt, triggersOut, underPoses);
auto& underPoses = _children[1]->evaluate(animVars, dt, triggersOut);
auto& overPoses = _children[0]->overlay(animVars, dt, triggersOut, underPoses);
if (underPoses.size() > 0 && underPoses.size() == overPoses.size()) {
_poses.resize(underPoses.size());

View file

@ -22,7 +22,7 @@ const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
AnimPose::AnimPose(const glm::mat4& mat) {
scale = extractScale(mat);
rot = extractRotation(mat);
rot = glm::normalize(glm::quat_cast(mat));
trans = extractTranslation(mat);
}
@ -173,6 +173,7 @@ void AnimSkeleton::dump() const {
qCDebug(animation) << "[";
for (int i = 0; i < getNumJoints(); i++) {
qCDebug(animation) << " {";
qCDebug(animation) << " index =" << i;
qCDebug(animation) << " name =" << getJointName(i);
qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);
@ -188,6 +189,7 @@ void AnimSkeleton::dump(const AnimPoseVec& poses) const {
qCDebug(animation) << "[";
for (int i = 0; i < getNumJoints(); i++) {
qCDebug(animation) << " {";
qCDebug(animation) << " index =" << i;
qCDebug(animation) << " name =" << getJointName(i);
qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);

View file

@ -52,7 +52,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl
if (_duringInterp) {
_alpha += _alphaVel * dt;
if (_alpha < 1.0f) {
if (_poses.size() > 0) {
if (_poses.size() > 0 && _nextPoses.size() > 0 && _prevPoses.size() > 0) {
::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]);
}
} else {
@ -77,8 +77,6 @@ void AnimStateMachine::addState(State::Pointer state) {
void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointer desiredState) {
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID();
const float FRAMES_PER_SECOND = 30.0f;
auto prevStateNode = _currentState->getNode();
@ -96,6 +94,8 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe
Triggers triggers;
_nextPoses = nextStateNode->evaluate(animVars, dt, triggers);
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget;
_currentState = desiredState;
}

View file

@ -16,6 +16,7 @@
#include <glm/gtx/quaternion.hpp>
#include <map>
#include <set>
#include "AnimationLogging.h"
class AnimVariant {
public:
@ -46,6 +47,7 @@ public:
bool isQuat() const { return _type == Type::Quat; }
bool isMat4() const { return _type == Type::Mat4; }
bool isString() const { return _type == Type::String; }
Type getType() const { return _type; }
void setBool(bool value) { assert(_type == Type::Bool); _val.boolVal = value; }
void setInt(int value) { assert(_type == Type::Int); _val.intVal = value; }
@ -156,6 +158,37 @@ public:
bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); }
#ifdef NDEBUG
void dump() const {
qCDebug(animation) << "AnimVariantMap =";
for (auto& pair : _map) {
switch (pair.second.getType()) {
case AnimVariant::Type::Bool:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getBool();
break;
case AnimVariant::Type::Int:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getInt();
break;
case AnimVariant::Type::Float:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getFloat();
break;
case AnimVariant::Type::Vec3:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getVec3();
break;
case AnimVariant::Type::Quat:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getQuat();
break;
case AnimVariant::Type::Mat4:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getMat4();
break;
case AnimVariant::Type::String:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
break;
}
}
}
#endif
protected:
std::map<QString, AnimVariant> _map;
std::set<QString> _triggers;

View file

@ -14,9 +14,11 @@
#include <glm/gtx/vector_angle.hpp>
#include <queue>
#include "NumericalConstants.h"
#include "AnimationHandle.h"
#include "AnimationLogging.h"
#include "AnimSkeleton.h"
#include "DebugDraw.h"
#include "Rig.h"
@ -143,16 +145,20 @@ AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QStrin
}
return handle;
}
const float FADE_FRAMES = 30.0f;
const float FRAMES_PER_SECOND = 30.0f;
void Rig::startAnimationByRole(const QString& role, const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
AnimationHandlePointer handle = addAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints, true);
handle->setFadePerSecond(1.0f); // For now. Could be individualized later.
handle->setFadePerSecond(FRAMES_PER_SECOND / FADE_FRAMES); // For now. Could be individualized later.
}
void Rig::stopAnimationByRole(const QString& role) {
foreach (const AnimationHandlePointer& handle, getRunningAnimations()) {
if (handle->getRole() == role) {
handle->setFadePerSecond(-1.0f); // For now. Could be individualized later.
handle->setFadePerSecond(-(FRAMES_PER_SECOND / FADE_FRAMES)); // For now. Could be individualized later.
}
}
}
@ -161,7 +167,7 @@ void Rig::stopAnimation(const QString& url) {
foreach (const AnimationHandlePointer& handle, getRunningAnimations()) {
if (handle->getURL() == url) {
handle->setFade(0.0f); // right away. Will be remove during updateAnimations, without locking
handle->setFadePerSecond(-1.0f); // so that the updateAnimation code notices
handle->setFadePerSecond(-(FRAMES_PER_SECOND / FADE_FRAMES)); // so that the updateAnimation code notices
}
}
}
@ -585,10 +591,12 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
_animVars.setTrigger(trigger);
}
clearJointStatePriorities();
// copy poses into jointStates
const float PRIORITY = 1.0f;
for (size_t i = 0; i < poses.size(); i++) {
setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, false);
setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, false, 1.0f);
}
} else {
@ -736,7 +744,7 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
return;
}
if (_enableAnimGraph && _animSkeleton) {
if (disableHands || (_enableAnimGraph && _animSkeleton)) {
// the hand data goes through a different path: Rig::updateFromHandParameters() --> early-exit
return;
}
@ -924,6 +932,12 @@ void Rig::updateVisibleJointStates() {
}
}
void Rig::clearJointStatePriorities() {
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].setAnimationPriority(0.0f);
}
}
void Rig::setJointVisibleTransform(int jointIndex, glm::mat4 newTransform) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return;
@ -948,6 +962,8 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
if (params.enableLean) {
updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist);
} else {
_animVars.unset("lean");
}
updateNeckJoint(params.neckJointIndex, params);
updateEyeJoints(params.leftEyeJointIndex, params.rightEyeJointIndex, params.modelTranslation, params.modelRotation,
@ -984,21 +1000,101 @@ void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, floa
}
}
static AnimPose avatarToBonePose(AnimPose pose, AnimSkeleton::ConstPointer skeleton) {
AnimPose rootPose = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Hips"));
AnimPose rotY180(glm::vec3(1), glm::angleAxis((float)PI, glm::vec3(0.0f, 1.0f, 0.0f)), glm::vec3(0));
return rootPose * rotY180 * pose;
}
#ifdef DEBUG_RENDERING
static AnimPose boneToAvatarPose(AnimPose pose, AnimSkeleton::ConstPointer skeleton) {
AnimPose rootPose = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Hips"));
AnimPose rotY180(glm::vec3(1), glm::angleAxis((float)PI, glm::vec3(0.0f, 1.0f, 0.0f)), glm::vec3(0));
return (rootPose * rotY180).inverse() * pose;
}
#endif
static void computeHeadNeckAnimVars(AnimSkeleton::ConstPointer skeleton, const AnimPose& avatarHMDPose,
glm::vec3& headPositionOut, glm::quat& headOrientationOut,
glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) {
// the input hmd values are in avatar space
// we need to transform them into bone space.
AnimPose hmdPose = avatarToBonePose(avatarHMDPose, skeleton);
const glm::vec3 hmdPosition = hmdPose.trans;
const glm::quat rotY180 = glm::angleAxis((float)PI, glm::vec3(0.0f, 1.0f, 0.0f));
const glm::quat hmdOrientation = hmdPose.rot * rotY180; // rotY180 will make z forward not -z
// TODO: cache jointIndices
// Use absolute bindPose positions just in case the relBindPose have rotations we don't expect.
glm::vec3 absRightEyePos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("RightEye")).trans;
glm::vec3 absLeftEyePos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("LeftEye")).trans;
glm::vec3 absHeadPos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Head")).trans;
glm::vec3 absNeckPos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Neck")).trans;
glm::vec3 absCenterEyePos = (absRightEyePos + absLeftEyePos) / 2.0f;
glm::vec3 eyeOffset = absCenterEyePos - absHeadPos;
glm::vec3 headOffset = absHeadPos - absNeckPos;
// apply simplistic head/neck model
// head
headPositionOut = hmdPosition - hmdOrientation * eyeOffset;
headOrientationOut = hmdOrientation;
// neck
neckPositionOut = hmdPosition - hmdOrientation * (headOffset + eyeOffset);
// slerp between bind orientation and hmdOrientation
neckOrientationOut = safeMix(hmdOrientation, skeleton->getRelativeBindPose(skeleton->nameToJointIndex("Neck")).rot, 0.5f);
}
void Rig::updateNeckJoint(int index, const HeadParameters& params) {
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
if (_enableAnimGraph && _animSkeleton && _animNode) {
// the params.localHeadOrientation is composed incorrectly, so re-compose it correctly from pitch, yaw and roll.
glm::quat realLocalHeadOrientation = (glm::angleAxis(glm::radians(-params.localHeadRoll), Z_AXIS) *
glm::angleAxis(glm::radians(params.localHeadYaw), Y_AXIS) *
glm::angleAxis(glm::radians(-params.localHeadPitch), X_AXIS));
_animVars.set("headRotation", realLocalHeadOrientation);
if (_enableAnimGraph && _animSkeleton) {
if (params.isInHMD) {
int headIndex = _animSkeleton->nameToJointIndex("Head");
AnimPose rootPose = _animNode->getRootPose(headIndex);
_animVars.set("headPosition", rootPose.trans + params.localHeadPosition); // rootPose.rot is handled in params?d
glm::vec3 headPos, neckPos;
glm::quat headRot, neckRot;
AnimPose avatarHMDPose(glm::vec3(1), params.localHeadOrientation, params.localHeadPosition);
computeHeadNeckAnimVars(_animSkeleton, avatarHMDPose, headPos, headRot, neckPos, neckRot);
// debug rendering
#ifdef DEBUG_RENDERING
const glm::vec4 red(1.0f, 0.0f, 0.0f, 1.0f);
const glm::vec4 green(0.0f, 1.0f, 0.0f, 1.0f);
// transform from bone into avatar space
AnimPose headPose(glm::vec3(1), headRot, headPos);
headPose = boneToAvatarPose(headPose, _animSkeleton);
DebugDraw::getInstance().addMyAvatarMarker("headTarget", headPose.rot, headPose.trans, red);
// transform from bone into avatar space
AnimPose neckPose(glm::vec3(1), neckRot, neckPos);
neckPose = boneToAvatarPose(neckPose, _animSkeleton);
DebugDraw::getInstance().addMyAvatarMarker("neckTarget", neckPose.rot, neckPose.trans, green);
#endif
_animVars.set("headPosition", headPos);
_animVars.set("headRotation", headRot);
_animVars.set("headType", QString("RotationAndPosition"));
_animVars.set("neckPosition", neckPos);
_animVars.set("neckRotation", neckRot);
} else {
// the params.localHeadOrientation is composed incorrectly, so re-compose it correctly from pitch, yaw and roll.
glm::quat realLocalHeadOrientation = (glm::angleAxis(glm::radians(-params.localHeadRoll), Z_AXIS) *
glm::angleAxis(glm::radians(params.localHeadYaw), Y_AXIS) *
glm::angleAxis(glm::radians(-params.localHeadPitch), X_AXIS));
_animVars.unset("headPosition");
_animVars.set("headRotation", realLocalHeadOrientation);
_animVars.set("headType", QString("RotationOnly"));
_animVars.unset("neckPosition");
_animVars.unset("neckRotation");
}
} else if (!_enableAnimGraph) {

View file

@ -172,6 +172,7 @@ public:
bool getJointRotationInConstrainedFrame(int jointIndex, glm::quat& rotOut) const;
glm::quat getJointDefaultRotationInParentFrame(int jointIndex);
void updateVisibleJointStates();
void clearJointStatePriorities();
virtual void updateJointState(int index, glm::mat4 rootTransform) = 0;
@ -193,6 +194,7 @@ public:
AnimNode::ConstPointer getAnimNode() const { return _animNode; }
AnimSkeleton::ConstPointer getAnimSkeleton() const { return _animSkeleton; }
bool disableHands {false}; // should go away with rig animation (and Rig::inverseKinematics)
protected:

View file

@ -9,18 +9,13 @@ link_hifi_libraries(audio)
target_include_directories(${TARGET_NAME} PUBLIC "${HIFI_LIBRARY_DIR}/audio/src")
# have CMake grab externals for us
add_dependency_external_projects(gverb soxr)
add_dependency_external_projects(gverb)
find_package(Gverb REQUIRED)
target_link_libraries(${TARGET_NAME} ${GVERB_LIBRARIES})
target_include_directories(${TARGET_NAME} PRIVATE ${GVERB_INCLUDE_DIRS})
# we use libsoxr for resampling
find_package(Soxr REQUIRED)
target_link_libraries(${TARGET_NAME} ${SOXR_LIBRARIES})
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${SOXR_INCLUDE_DIRS})
if (APPLE)
find_library(CoreAudio CoreAudio)
find_library(CoreFoundation CoreFoundation)

View file

@ -38,11 +38,20 @@
#pragma GCC diagnostic ignored "-Wdouble-promotion"
#endif
#ifdef WIN32
#pragma warning (push)
#pragma warning (disable: 4273 4305)
#endif
extern "C" {
#include <gverb/gverb.h>
#include <gverb/gverbdsp.h>
}
#ifdef WIN32
#pragma warning (pop)
#endif
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif

View file

@ -7,10 +7,4 @@ add_dependency_external_projects(glm)
find_package(GLM REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
# we use libsoxr for resampling
add_dependency_external_projects(soxr)
find_package(Soxr REQUIRED)
target_link_libraries(${TARGET_NAME} ${SOXR_LIBRARIES})
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${SOXR_INCLUDE_DIRS})
link_hifi_libraries(networking shared)

View file

@ -330,6 +330,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol
reinterpret_cast<int16_t*>(resampled.data()),
nInputFrames);
Q_UNUSED(nOutputFrames);
return playSoundAndDelete(resampled, options, NULL);
}

File diff suppressed because it is too large Load diff

View file

@ -31,6 +31,8 @@ public:
private:
float* _polyphaseFilter;
int* _stepTable;
float* _history[MAX_CHANNELS];
float* _inputs[MAX_CHANNELS];
float* _outputs[MAX_CHANNELS];
@ -45,10 +47,13 @@ private:
int _numTaps;
int _numHistory;
int _phase;
int64_t _offset;
int64_t _step;
int createPolyphaseFilter(int upFactor, int downFactor, float gain);
int createRationalFilter(int upFactor, int downFactor, float gain);
int createIrrationalFilter(int upFactor, int downFactor, float gain);
int multirateFilter1(const float* input0, float* output0, int inputFrames);
int multirateFilter2(const float* input0, const float* input1, float* output0, float* output1, int inputFrames);

View file

@ -1,6 +1,6 @@
set(TARGET_NAME entities-renderer)
AUTOSCRIBE_SHADER_LIB(gpu model render)
AUTOSCRIBE_SHADER_LIB(gpu model render render-utils)
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Widgets Network Script)

View file

@ -1,78 +0,0 @@
<!
// DeferredBufferWrite.slh
// libraries/render-utils/src
//
// Created by Sam Gateau on 1/12/15.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
!>
<@if not DEFERRED_BUFFER_WRITE_SLH@>
<@def DEFERRED_BUFFER_WRITE_SLH@>
layout(location = 0) out vec4 _fragColor0;
layout(location = 1) out vec4 _fragColor1;
layout(location = 2) out vec4 _fragColor2;
// the glow intensity
uniform float glowIntensity;
// the alpha threshold
uniform float alphaThreshold;
uniform sampler2D normalFittingMap;
vec3 bestFitNormal(vec3 normal) {
vec3 absNorm = abs(normal);
float maxNAbs = max(absNorm.z, max(absNorm.x, absNorm.y));
vec2 texcoord = (absNorm.z < maxNAbs ?
(absNorm.y < maxNAbs ? absNorm.yz : absNorm.xz) :
absNorm.xy);
texcoord = (texcoord.x < texcoord.y ? texcoord.yx : texcoord.xy);
texcoord.y /= texcoord.x;
vec3 cN = normal / maxNAbs;
float fittingScale = texture(normalFittingMap, texcoord).a;
cN *= fittingScale;
return (cN * 0.5 + 0.5);
}
float evalOpaqueFinalAlpha(float alpha, float mapAlpha) {
return mix(alpha * glowIntensity, 1.0 - alpha * glowIntensity, step(mapAlpha, alphaThreshold));
}
const vec3 DEFAULT_SPECULAR = vec3(0.1);
const float DEFAULT_SHININESS = 10;
void packDeferredFragment(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) {
if (alpha != glowIntensity) {
discard;
}
_fragColor0 = vec4(diffuse.rgb, alpha);
_fragColor1 = vec4(bestFitNormal(normal), 1.0);
_fragColor2 = vec4(specular, shininess / 128.0);
}
void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess, vec3 emissive) {
if (alpha != glowIntensity) {
discard;
}
_fragColor0 = vec4(diffuse.rgb, alpha);
//_fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0);
_fragColor1 = vec4(bestFitNormal(normal), 0.5);
_fragColor2 = vec4(emissive, shininess / 128.0);
}
void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) {
if (alpha <= alphaThreshold) {
discard;
}
_fragColor0 = vec4(diffuse.rgb, alpha);
// _fragColor1 = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0);
// _fragColor2 = vec4(specular, shininess / 128.0);
}
<@endif@>

View file

@ -470,9 +470,10 @@ RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(cons
OctreeElementPointer element;
EntityItemPointer intersectedEntity = NULL;
result.intersects = entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face,
(void**)&intersectedEntity, lockType, &result.accurate,
precisionPicking);
result.intersects = entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance,
result.face, result.surfaceNormal,
(void**)&intersectedEntity, lockType, &result.accurate,
precisionPicking);
if (result.intersects && intersectedEntity) {
result.entityID = intersectedEntity->getEntityItemID();
result.properties = intersectedEntity->getProperties();

View file

@ -87,10 +87,10 @@ public:
QList<EntityItemID>& getEntitiesLastInScene() { return _entityIDsLastInScene; }
signals:
void mousePressOnEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId);
void mousePressOffEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId);
void mouseMoveOnEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId);
void mouseReleaseOnEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId);
void mousePressOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId);
void mousePressOffEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId);
void mouseMoveOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId);
void mouseReleaseOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId);
void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);

View file

@ -54,8 +54,9 @@ void RenderableLightEntityItem::render(RenderArgs* args) {
};
bool RenderableLightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const {
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const {
// TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state
// this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could

View file

@ -26,7 +26,8 @@ public:
virtual void render(RenderArgs* args);
virtual bool supportsDetailedRayIntersection() const { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const;
SIMPLE_RENDERABLE();

View file

@ -366,9 +366,9 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag
return properties;
}
bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const {
glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const {
if (!_model) {
return true;
}
@ -376,7 +376,8 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
// << precisionPicking;
QString extraInfo;
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo, precisionPicking);
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance,
face, surfaceNormal, extraInfo, precisionPicking);
}
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {

View file

@ -55,8 +55,9 @@ public:
virtual void render(RenderArgs* args);
virtual bool supportsDetailedRayIntersection() const { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const;
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const;
Model* getModel(EntityTreeRenderer* renderer);

View file

@ -211,9 +211,12 @@ void RenderableParticleEffectEntityItem::updateRenderItem() {
_vertices.clear();
// build vertices from particle positions and radiuses
const glm::vec3 up = frustum->getUp();
const glm::vec3 right = frustum->getRight();
glm::vec3 frustumPosition = frustum->getPosition();
for (auto&& particle : particleDetails) {
glm::vec3 particleDirection = particle.position - frustumPosition;
glm::vec3 right = glm::normalize(glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), particleDirection));
glm::vec3 up = glm::normalize(glm::cross(right, particleDirection));
glm::vec3 upOffset = up * particle.radius;
glm::vec3 rightOffset = right * particle.radius;
// generate corners of quad aligned to face the camera.

View file

@ -348,13 +348,10 @@ public:
const PolyVox::SimpleVolume<uint8_t>* _vol = nullptr;
};
bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin,
const glm::vec3& direction,
bool& keepSearching,
OctreeElementPointer& element,
float& distance, BoxFace& face,
void** intersectedObject,
bool precisionPicking) const
bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element,
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const
{
// TODO -- correctly pick against marching-cube generated meshes
if (!precisionPicking) {
@ -392,7 +389,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
float voxelDistance;
bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), voxelDistance, face);
bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), voxelDistance, face, surfaceNormal);
glm::vec4 voxelIntersectionPoint = glm::vec4(glm::vec3(originInVoxel) + glm::vec3(directionInVoxel) * voxelDistance, 1.0);
glm::vec4 intersectionPoint = vtwMatrix * voxelIntersectionPoint;

View file

@ -64,8 +64,9 @@ public:
void render(RenderArgs* args);
virtual bool supportsDetailedRayIntersection() const { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const;
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const;
virtual void setVoxelData(QByteArray voxelData);
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);

View file

@ -206,7 +206,8 @@ public:
virtual bool supportsDetailedRayIntersection() const { return false; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const { return true; }
// attributes applicable to all entity types

View file

@ -91,8 +91,15 @@ CONSTRUCT_PROPERTY(shapeType, SHAPE_TYPE_NONE),
CONSTRUCT_PROPERTY(maxParticles, ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES),
CONSTRUCT_PROPERTY(lifespan, ParticleEffectEntityItem::DEFAULT_LIFESPAN),
CONSTRUCT_PROPERTY(emitRate, ParticleEffectEntityItem::DEFAULT_EMIT_RATE),
CONSTRUCT_PROPERTY(emitVelocity, ParticleEffectEntityItem::DEFAULT_EMIT_VELOCITY),
CONSTRUCT_PROPERTY(velocitySpread, ParticleEffectEntityItem::DEFAULT_VELOCITY_SPREAD),
CONSTRUCT_PROPERTY(emitSpeed, ParticleEffectEntityItem::DEFAULT_EMIT_SPEED),
CONSTRUCT_PROPERTY(speedSpread, ParticleEffectEntityItem::DEFAULT_SPEED_SPREAD),
CONSTRUCT_PROPERTY(emitOrientation, ParticleEffectEntityItem::DEFAULT_EMIT_ORIENTATION),
CONSTRUCT_PROPERTY(emitDimensions, ParticleEffectEntityItem::DEFAULT_EMIT_DIMENSIONS),
CONSTRUCT_PROPERTY(emitRadiusStart, ParticleEffectEntityItem::DEFAULT_EMIT_RADIUS_START),
CONSTRUCT_PROPERTY(polarStart, ParticleEffectEntityItem::DEFAULT_POLAR_START),
CONSTRUCT_PROPERTY(polarFinish, ParticleEffectEntityItem::DEFAULT_POLAR_FINISH),
CONSTRUCT_PROPERTY(azimuthStart, ParticleEffectEntityItem::DEFAULT_AZIMUTH_START),
CONSTRUCT_PROPERTY(azimuthFinish, ParticleEffectEntityItem::DEFAULT_AZIMUTH_FINISH),
CONSTRUCT_PROPERTY(emitAcceleration, ParticleEffectEntityItem::DEFAULT_EMIT_ACCELERATION),
CONSTRUCT_PROPERTY(accelerationSpread, ParticleEffectEntityItem::DEFAULT_ACCELERATION_SPREAD),
CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS),
@ -375,8 +382,15 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_MAX_PARTICLES, maxParticles);
CHECK_PROPERTY_CHANGE(PROP_LIFESPAN, lifespan);
CHECK_PROPERTY_CHANGE(PROP_EMIT_RATE, emitRate);
CHECK_PROPERTY_CHANGE(PROP_EMIT_VELOCITY, emitVelocity);
CHECK_PROPERTY_CHANGE(PROP_VELOCITY_SPREAD, velocitySpread);
CHECK_PROPERTY_CHANGE(PROP_EMIT_SPEED, emitSpeed);
CHECK_PROPERTY_CHANGE(PROP_SPEED_SPREAD, speedSpread);
CHECK_PROPERTY_CHANGE(PROP_EMIT_ORIENTATION, emitOrientation);
CHECK_PROPERTY_CHANGE(PROP_EMIT_DIMENSIONS, emitDimensions);
CHECK_PROPERTY_CHANGE(PROP_EMIT_RADIUS_START, emitRadiusStart);
CHECK_PROPERTY_CHANGE(PROP_POLAR_START, polarStart);
CHECK_PROPERTY_CHANGE(PROP_POLAR_FINISH, polarFinish);
CHECK_PROPERTY_CHANGE(PROP_AZIMUTH_START, azimuthStart);
CHECK_PROPERTY_CHANGE(PROP_AZIMUTH_FINISH, azimuthFinish);
CHECK_PROPERTY_CHANGE(PROP_EMIT_ACCELERATION, emitAcceleration);
CHECK_PROPERTY_CHANGE(PROP_ACCELERATION_SPREAD, accelerationSpread);
CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius);
@ -478,8 +492,15 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_PARTICLES, maxParticles);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LIFESPAN, lifespan);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_RATE, emitRate);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_VELOCITY, emitVelocity);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VELOCITY_SPREAD, velocitySpread);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_SPEED, emitSpeed);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPEED_SPREAD, speedSpread);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_ORIENTATION, emitOrientation);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_DIMENSIONS, emitDimensions);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_RADIUS_START, emitRadiusStart);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POLAR_START, polarStart);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POLAR_FINISH, polarFinish);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_AZIMUTH_START, azimuthStart);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_AZIMUTH_FINISH, azimuthFinish);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_ACCELERATION, emitAcceleration);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACCELERATION_SPREAD, accelerationSpread);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_RADIUS, particleRadius);
@ -579,8 +600,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_WIDTHS, strokeWidths);
}
//COPY_PROPERTY_TO_QSCRIPTVALUE(simulationOwner); // TODO: expose this for JSON saves?
// Sitting properties support
if (!skipDefaults) {
QScriptValue sittingPoints = engine->newObject();
@ -678,8 +697,15 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(maxParticles, float, setMaxParticles);
COPY_PROPERTY_FROM_QSCRIPTVALUE(lifespan, float, setLifespan);
COPY_PROPERTY_FROM_QSCRIPTVALUE(emitRate, float, setEmitRate);
COPY_PROPERTY_FROM_QSCRIPTVALUE(emitVelocity, glmVec3, setEmitVelocity);
COPY_PROPERTY_FROM_QSCRIPTVALUE(velocitySpread, glmVec3, setVelocitySpread);
COPY_PROPERTY_FROM_QSCRIPTVALUE(emitSpeed, float, setEmitSpeed);
COPY_PROPERTY_FROM_QSCRIPTVALUE(speedSpread, float, setSpeedSpread);
COPY_PROPERTY_FROM_QSCRIPTVALUE(emitOrientation, glmQuat, setEmitOrientation);
COPY_PROPERTY_FROM_QSCRIPTVALUE(emitDimensions, glmVec3, setEmitDimensions);
COPY_PROPERTY_FROM_QSCRIPTVALUE(emitRadiusStart, float, setEmitRadiusStart);
COPY_PROPERTY_FROM_QSCRIPTVALUE(polarStart, float, setPolarStart);
COPY_PROPERTY_FROM_QSCRIPTVALUE(polarFinish, float, setPolarFinish);
COPY_PROPERTY_FROM_QSCRIPTVALUE(azimuthStart, float, setAzimuthStart);
COPY_PROPERTY_FROM_QSCRIPTVALUE(azimuthFinish, float, setAzimuthFinish);
COPY_PROPERTY_FROM_QSCRIPTVALUE(emitAcceleration, glmVec3, setEmitAcceleration);
COPY_PROPERTY_FROM_QSCRIPTVALUE(accelerationSpread, glmVec3, setAccelerationSpread);
COPY_PROPERTY_FROM_QSCRIPTVALUE(particleRadius, float, setParticleRadius);
@ -828,8 +854,15 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32);
ADD_PROPERTY_TO_MAP(PROP_LIFESPAN, Lifespan, lifespan, float);
ADD_PROPERTY_TO_MAP(PROP_EMIT_RATE, EmitRate, emitRate, float);
ADD_PROPERTY_TO_MAP(PROP_EMIT_VELOCITY, EmitVelocity, emitVelocity, glm::vec3);
ADD_PROPERTY_TO_MAP(PROP_VELOCITY_SPREAD, VelocitySpread, velocitySpread, glm::vec3);
ADD_PROPERTY_TO_MAP(PROP_EMIT_SPEED, EmitSpeed, emitSpeed, glm::vec3);
ADD_PROPERTY_TO_MAP(PROP_SPEED_SPREAD, SpeedSpread, speedSpread, glm::vec3);
ADD_PROPERTY_TO_MAP(PROP_EMIT_ORIENTATION, EmitOrientation, emitOrientation, glm::quat);
ADD_PROPERTY_TO_MAP(PROP_EMIT_DIMENSIONS, EmitDimensions, emitDimensions, glm::vec3);
ADD_PROPERTY_TO_MAP(PROP_EMIT_RADIUS_START, EmitRadiusStart, emitRadiusStart, float);
ADD_PROPERTY_TO_MAP(PROP_POLAR_START, EmitPolarStart, polarStart, float);
ADD_PROPERTY_TO_MAP(PROP_POLAR_FINISH, EmitPolarFinish, polarFinish, float);
ADD_PROPERTY_TO_MAP(PROP_AZIMUTH_START, EmitAzimuthStart, azimuthStart, float);
ADD_PROPERTY_TO_MAP(PROP_AZIMUTH_FINISH, EmitAzimuthFinish, azimuthFinish, float);
ADD_PROPERTY_TO_MAP(PROP_EMIT_ACCELERATION, EmitAcceleration, emitAcceleration, glm::vec3);
ADD_PROPERTY_TO_MAP(PROP_ACCELERATION_SPREAD, AccelerationSpread, accelerationSpread, glm::vec3);
ADD_PROPERTY_TO_MAP(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float);
@ -1080,8 +1113,15 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, properties.getMaxParticles());
APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, properties.getLifespan());
APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, properties.getEmitRate());
APPEND_ENTITY_PROPERTY(PROP_EMIT_VELOCITY, properties.getEmitVelocity());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY_SPREAD, properties.getVelocitySpread());
APPEND_ENTITY_PROPERTY(PROP_EMIT_SPEED, properties.getEmitSpeed());
APPEND_ENTITY_PROPERTY(PROP_SPEED_SPREAD, properties.getSpeedSpread());
APPEND_ENTITY_PROPERTY(PROP_EMIT_ORIENTATION, properties.getEmitOrientation());
APPEND_ENTITY_PROPERTY(PROP_EMIT_DIMENSIONS, properties.getEmitDimensions());
APPEND_ENTITY_PROPERTY(PROP_EMIT_RADIUS_START, properties.getEmitRadiusStart());
APPEND_ENTITY_PROPERTY(PROP_POLAR_START, properties.getPolarStart());
APPEND_ENTITY_PROPERTY(PROP_POLAR_FINISH, properties.getPolarFinish());
APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, properties.getAzimuthStart());
APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, properties.getAzimuthFinish());
APPEND_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, properties.getEmitAcceleration());
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, properties.getAccelerationSpread());
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, properties.getParticleRadius());
@ -1364,8 +1404,15 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_PARTICLES, float, setMaxParticles);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFESPAN, float, setLifespan);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_RATE, float, setEmitRate);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_VELOCITY, glm::vec3, setEmitVelocity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY_SPREAD, glm::vec3, setVelocitySpread);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_SPEED, float, setEmitSpeed);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPEED_SPREAD, float, setSpeedSpread);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_ORIENTATION, glm::quat, setEmitOrientation);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_DIMENSIONS, glm::vec3, setEmitDimensions);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_RADIUS_START, float, setEmitRadiusStart);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POLAR_START, float, setPolarStart);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POLAR_FINISH, float, setPolarFinish);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AZIMUTH_START, float, setAzimuthStart);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AZIMUTH_FINISH, float, setAzimuthFinish);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_ACCELERATION, glm::vec3, setEmitAcceleration);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACCELERATION_SPREAD, glm::vec3, setAccelerationSpread);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius);
@ -1513,8 +1560,15 @@ void EntityItemProperties::markAllChanged() {
_maxParticlesChanged = true;
_lifespanChanged = true;
_emitRateChanged = true;
_emitVelocityChanged = true;
_velocitySpreadChanged = true;
_emitSpeedChanged = true;
_speedSpreadChanged = true;
_emitOrientationChanged = true;
_emitDimensionsChanged = true;
_emitRadiusStartChanged = true;
_polarStartChanged = true;
_polarFinishChanged = true;
_azimuthStartChanged = true;
_azimuthFinishChanged = true;
_emitAccelerationChanged = true;
_accelerationSpreadChanged = true;
_particleRadiusChanged = true;

View file

@ -141,10 +141,17 @@ public:
DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32);
DEFINE_PROPERTY(PROP_LIFESPAN, Lifespan, lifespan, float);
DEFINE_PROPERTY(PROP_EMIT_RATE, EmitRate, emitRate, float);
DEFINE_PROPERTY_REF(PROP_EMIT_VELOCITY, EmitVelocity, emitVelocity, glm::vec3);
DEFINE_PROPERTY_REF(PROP_VELOCITY_SPREAD, VelocitySpread, velocitySpread, glm::vec3);
DEFINE_PROPERTY(PROP_EMIT_ACCELERATION, EmitAcceleration, emitAcceleration, glm::vec3);
DEFINE_PROPERTY(PROP_ACCELERATION_SPREAD, AccelerationSpread, accelerationSpread, glm::vec3);
DEFINE_PROPERTY(PROP_EMIT_SPEED, EmitSpeed, emitSpeed, float);
DEFINE_PROPERTY(PROP_SPEED_SPREAD, SpeedSpread, speedSpread, float);
DEFINE_PROPERTY_REF(PROP_EMIT_ORIENTATION, EmitOrientation, emitOrientation, glm::quat);
DEFINE_PROPERTY_REF(PROP_EMIT_DIMENSIONS, EmitDimensions, emitDimensions, glm::vec3);
DEFINE_PROPERTY(PROP_EMIT_RADIUS_START, EmitRadiusStart, emitRadiusStart, float);
DEFINE_PROPERTY(PROP_POLAR_START, PolarStart, polarStart, float);
DEFINE_PROPERTY(PROP_POLAR_FINISH, PolarFinish, polarFinish, float);
DEFINE_PROPERTY(PROP_AZIMUTH_START, AzimuthStart, azimuthStart, float);
DEFINE_PROPERTY(PROP_AZIMUTH_FINISH, AzimuthFinish, azimuthFinish, float);
DEFINE_PROPERTY_REF(PROP_EMIT_ACCELERATION, EmitAcceleration, emitAcceleration, glm::vec3);
DEFINE_PROPERTY_REF(PROP_ACCELERATION_SPREAD, AccelerationSpread, accelerationSpread, glm::vec3);
DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float);
DEFINE_PROPERTY(PROP_RADIUS_SPREAD, RadiusSpread, radiusSpread, float);
DEFINE_PROPERTY(PROP_RADIUS_START, RadiusStart, radiusStart, float);
@ -344,7 +351,15 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, MaxParticles, maxParticles, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Lifespan, lifespan, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitRate, emitRate, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitVelocity, emitVelocity, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitSpeed, emitSpeed, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, SpeedSpread, speedSpread, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitOrientation, emitOrientation, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitDimensions, emitDimensions, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitRadiusStart, emitRadiusStart, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, PolarStart, polarStart, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, PolarFinish, polarFinish, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AzimuthStart, azimuthStart, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AzimuthFinish, azimuthFinish, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitAcceleration, emitAcceleration, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AccelerationSpread, accelerationSpread, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, "");

View file

@ -44,6 +44,14 @@
} \
}
#define SKIP_ENTITY_PROPERTY(P,T) \
if (propertyFlags.getHasProperty(P)) { \
T fromBuffer; \
int bytes = OctreePacketData::unpackDataFromBytes(dataAt, fromBuffer); \
dataAt += bytes; \
bytesRead += bytes; \
}
#define DECODE_GROUP_PROPERTY_HAS_CHANGED(P,N) \
if (propertyFlags.getHasProperty(P)) { \
set##N##Changed(true); \

View file

@ -71,7 +71,7 @@ enum EntityPropertyList {
PROP_MAX_PARTICLES,
PROP_LIFESPAN,
PROP_EMIT_RATE,
PROP_EMIT_VELOCITY,
PROP_EMIT_SPEED,
PROP_EMIT_STRENGTH,
PROP_EMIT_ACCELERATION,
PROP_PARTICLE_RADIUS,
@ -111,7 +111,7 @@ enum EntityPropertyList {
PROP_STROKE_WIDTHS,
// used by particles
PROP_VELOCITY_SPREAD,
PROP_SPEED_SPREAD,
PROP_ACCELERATION_SPREAD,
PROP_X_N_NEIGHBOR_ID, // used by PolyVox
@ -135,6 +135,13 @@ enum EntityPropertyList {
PROP_ALPHA_SPREAD,
PROP_ALPHA_START,
PROP_ALPHA_FINISH,
PROP_EMIT_ORIENTATION,
PROP_EMIT_DIMENSIONS,
PROP_EMIT_RADIUS_START,
PROP_POLAR_START,
PROP_POLAR_FINISH,
PROP_AZIMUTH_START,
PROP_AZIMUTH_FINISH,
////////////////////////////////////////////////////////////////////////////////////////////////////
// ATTENTION: add new properties to end of list just ABOVE this line
@ -169,7 +176,7 @@ enum EntityPropertyList {
PROP_ATMOSPHERE_CENTER = PROP_MAX_PARTICLES,
PROP_ATMOSPHERE_INNER_RADIUS = PROP_LIFESPAN,
PROP_ATMOSPHERE_OUTER_RADIUS = PROP_EMIT_RATE,
PROP_ATMOSPHERE_MIE_SCATTERING = PROP_EMIT_VELOCITY,
PROP_ATMOSPHERE_MIE_SCATTERING = PROP_EMIT_SPEED,
PROP_ATMOSPHERE_RAYLEIGH_SCATTERING = PROP_EMIT_STRENGTH,
PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS = PROP_EMIT_ACCELERATION,
PROP_ATMOSPHERE_HAS_STARS = PROP_PARTICLE_RADIUS,

View file

@ -297,7 +297,7 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke
OctreeElementPointer element;
EntityItemPointer intersectedEntity = NULL;
result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face,
(void**)&intersectedEntity, lockType, &result.accurate,
result.surfaceNormal, (void**)&intersectedEntity, lockType, &result.accurate,
precisionPicking);
if (result.intersects && intersectedEntity) {
result.entityID = intersectedEntity->getEntityItemID();
@ -393,6 +393,9 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
obj.setProperty("intersection", intersection);
QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal);
obj.setProperty("surfaceNormal", surfaceNormal);
return obj;
}
@ -426,6 +429,10 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
if (intersection.isValid()) {
vec3FromScriptValue(intersection, value.intersection);
}
QScriptValue surfaceNormal = object.property("surfaceNormal");
if (surfaceNormal.isValid()) {
vec3FromScriptValue(surfaceNormal, value.surfaceNormal);
}
}
bool EntityScriptingInterface::setVoxels(QUuid entityID,

View file

@ -43,6 +43,7 @@ public:
float distance;
BoxFace face;
glm::vec3 intersection;
glm::vec3 surfaceNormal;
EntityItemPointer entity;
};

View file

@ -492,9 +492,9 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
return false;
}
bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking, float distanceToElementCube) {
bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching,
OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking, float distanceToElementCube) {
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
int entityNumber = 0;
@ -503,9 +503,10 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
AABox entityBox = entity->getAABox();
float localDistance;
BoxFace localFace;
glm::vec3 localSurfaceNormal;
// if the ray doesn't intersect with our cube, we can stop searching!
if (!entityBox.findRayIntersection(origin, direction, localDistance, localFace)) {
if (!entityBox.findRayIntersection(origin, direction, localDistance, localFace, localSurfaceNormal)) {
return;
}
@ -526,16 +527,18 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
// and testing intersection there.
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, localFace)) {
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
localFace, localSurfaceNormal)) {
if (localDistance < distance) {
// now ask the entity if we actually intersect
if (entity->supportsDetailedRayIntersection()) {
if (entity->findDetailedRayIntersection(origin, direction, keepSearching, element, localDistance,
localFace, intersectedObject, precisionPicking)) {
localFace, localSurfaceNormal, intersectedObject, precisionPicking)) {
if (localDistance < distance) {
distance = localDistance;
face = localFace;
surfaceNormal = localSurfaceNormal;
*intersectedObject = (void*)entity.get();
somethingIntersected = true;
}
@ -545,6 +548,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
if (localDistance < distance) {
distance = localDistance;
face = localFace;
surfaceNormal = localSurfaceNormal;
*intersectedObject = (void*)entity.get();
somethingIntersected = true;
}

View file

@ -143,7 +143,8 @@ public:
virtual bool canRayIntersect() const { return hasEntities(); }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking, float distanceToElementCube);
virtual bool findSpherePenetration(const glm::vec3& center, float radius,

View file

@ -64,8 +64,9 @@ class LineEntityItem : public EntityItem {
// never have a ray intersection pick a LineEntityItem.
virtual bool supportsDetailedRayIntersection() const { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const { return false; }
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const { return false; }
virtual void debugDump() const;
static const float DEFAULT_LINE_WIDTH;

View file

@ -43,6 +43,9 @@
#include "EntityScriptingInterface.h"
#include "ParticleEffectEntityItem.h"
const glm::vec3 X_AXIS = glm::vec3(1.0f, 0.0f, 0.0f);
const glm::vec3 Z_AXIS = glm::vec3(0.0f, 0.0f, 1.0f);
const xColor ParticleEffectEntityItem::DEFAULT_COLOR = { 255, 255, 255 };
const xColor ParticleEffectEntityItem::DEFAULT_COLOR_SPREAD = { 0, 0, 0 };
const float ParticleEffectEntityItem::DEFAULT_ALPHA = 1.0f;
@ -55,8 +58,15 @@ const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FPS = 30.0f;
const quint32 ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES = 1000;
const float ParticleEffectEntityItem::DEFAULT_LIFESPAN = 3.0f;
const float ParticleEffectEntityItem::DEFAULT_EMIT_RATE = 15.0f;
const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_VELOCITY(0.0f, 5.0f, 0.0f);
const glm::vec3 ParticleEffectEntityItem::DEFAULT_VELOCITY_SPREAD(3.0f, 0.0f, 3.0f);
const float ParticleEffectEntityItem::DEFAULT_EMIT_SPEED = 5.0f;
const float ParticleEffectEntityItem::DEFAULT_SPEED_SPREAD = 1.0f;
const glm::quat ParticleEffectEntityItem::DEFAULT_EMIT_ORIENTATION = glm::angleAxis(-PI_OVER_TWO, X_AXIS); // Vertical
const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIMENSIONS = glm::vec3(0.0f, 0.0f, 0.0f); // Emit from point
const float ParticleEffectEntityItem::DEFAULT_EMIT_RADIUS_START = 1.0f; // Emit from surface (when emitDimensions > 0)
const float ParticleEffectEntityItem::DEFAULT_POLAR_START = 0.0f; // Emit along z-axis
const float ParticleEffectEntityItem::DEFAULT_POLAR_FINISH = 0.0f; // ""
const float ParticleEffectEntityItem::DEFAULT_AZIMUTH_START = -PI; // Emit full circumference (when polarFinish > 0)
const float ParticleEffectEntityItem::DEFAULT_AZIMUTH_FINISH = PI; // ""
const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_ACCELERATION(0.0f, -9.8f, 0.0f);
const glm::vec3 ParticleEffectEntityItem::DEFAULT_ACCELERATION_SPREAD(0.0f, 0.0f, 0.0f);
const float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f;
@ -104,13 +114,24 @@ ParticleEffectEntityItem::~ParticleEffectEntityItem() {
}
void ParticleEffectEntityItem::setEmitVelocity(const glm::vec3& emitVelocity) {
_emitVelocity = emitVelocity;
void ParticleEffectEntityItem::setEmitSpeed(float emitSpeed) {
_emitSpeed = emitSpeed;
computeAndUpdateDimensions();
}
void ParticleEffectEntityItem::setVelocitySpread(const glm::vec3& velocitySpread) {
_velocitySpread = velocitySpread;
void ParticleEffectEntityItem::setSpeedSpread(float speedSpread) {
_speedSpread = speedSpread;
computeAndUpdateDimensions();
}
void ParticleEffectEntityItem::setEmitOrientation(const glm::quat& emitOrientation) {
_emitOrientation = emitOrientation;
computeAndUpdateDimensions();
}
void ParticleEffectEntityItem::setEmitDimensions(const glm::vec3& emitDimensions) {
_emitDimensions = emitDimensions;
computeAndUpdateDimensions();
}
@ -126,23 +147,18 @@ void ParticleEffectEntityItem::setAccelerationSpread(const glm::vec3& accelerati
void ParticleEffectEntityItem::computeAndUpdateDimensions() {
const float time = _lifespan * 1.1f; // add 10% extra time to account for incremental timer accumulation error
float maxVelocityX = fabsf(_velocity.x) + _velocitySpread.x;
float maxAccelerationX = fabsf(_acceleration.x) + _accelerationSpread.x;
float maxXDistance = (maxVelocityX * time) + (0.5f * maxAccelerationX * time * time);
float maxVelocityY = fabsf(_velocity.y) + _velocitySpread.y;
float maxAccelerationY = fabsf(_acceleration.y) + _accelerationSpread.y;
float maxYDistance = (maxVelocityY * time) + (0.5f * maxAccelerationY * time * time);
float maxVelocityZ = fabsf(_velocity.z) + _velocitySpread.z;
float maxAccelerationZ = fabsf(_acceleration.z) + _accelerationSpread.z;
float maxZDistance = (maxVelocityZ * time) + (0.5f * maxAccelerationZ * time * time);
float maxDistance = std::max(maxXDistance, std::max(maxYDistance, maxZDistance));
glm::vec3 velocity = _emitSpeed * (_emitOrientation * Z_AXIS);
glm::vec3 velocitySpread = _speedSpread * (_emitOrientation * Z_AXIS);
glm::vec3 maxVelocity = glm::abs(velocity) + velocitySpread;
glm::vec3 maxAccleration = glm::abs(_acceleration) + _accelerationSpread;
glm::vec3 maxDistance = 0.5f * _emitDimensions + time * maxVelocity + (0.5f * time * time) * maxAccleration;
float maxDistanceValue = std::max(maxDistance.x, std::max(maxDistance.y, maxDistance.z));
//times 2 because dimensions are diameters not radii
glm::vec3 dims(2.0f * maxDistance);
glm::vec3 dims(2.0f * maxDistanceValue);
EntityItem::setDimensions(dims);
}
@ -161,8 +177,15 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(EntityPropertyFlags
COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifespan, getLifespan);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitRate, getEmitRate);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitVelocity, getEmitVelocity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocitySpread, getVelocitySpread);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitSpeed, getEmitSpeed);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(speedSpread, getSpeedSpread);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitOrientation, getEmitOrientation);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitDimensions, getEmitDimensions);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitRadiusStart, getEmitRadiusStart);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(polarStart, getPolarStart);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(polarFinish, getPolarFinish);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(azimuthStart, getAzimuthStart);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(azimuthFinish, getAzimuthFinish);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitAcceleration, getEmitAcceleration);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(accelerationSpread, getAccelerationSpread);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleRadius, getParticleRadius);
@ -194,8 +217,15 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitRate, setEmitRate);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitVelocity, setEmitVelocity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocitySpread, setVelocitySpread);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitSpeed, setEmitSpeed);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(speedSpread, setSpeedSpread);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitOrientation, setEmitOrientation);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitDimensions, setEmitDimensions);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitRadiusStart, setEmitRadiusStart);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(polarStart, setPolarStart);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(polarFinish, setPolarFinish);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(azimuthStart, setAzimuthStart);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(azimuthFinish, setAzimuthFinish);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitAcceleration, setEmitAcceleration);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(accelerationSpread, setAccelerationSpread);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleRadius, setParticleRadius);
@ -258,20 +288,25 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles);
READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan);
READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate);
READ_ENTITY_PROPERTY(PROP_EMIT_VELOCITY, glm::vec3, setEmitVelocity);
if (args.bitstreamVersion < VERSION_ENTITIES_PARTICLE_ELLIPSOID_EMITTER) {
// OLD PROP_EMIT_VELOCITY FAKEOUT
SKIP_ENTITY_PROPERTY(PROP_EMIT_SPEED, glm::vec3);
}
if (args.bitstreamVersion >= VERSION_ENTITIES_PARTICLE_MODIFICATIONS) {
READ_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, glm::vec3, setEmitAcceleration);
READ_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, glm::vec3, setAccelerationSpread);
READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius);
READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures);
READ_ENTITY_PROPERTY(PROP_VELOCITY_SPREAD, glm::vec3, setVelocitySpread);
if (args.bitstreamVersion < VERSION_ENTITIES_PARTICLE_ELLIPSOID_EMITTER) {
// OLD PROP_VELOCITY_SPREAD FAKEOUT
SKIP_ENTITY_PROPERTY(PROP_SPEED_SPREAD, glm::vec3);
}
} else {
// EMIT_STRENGTH FAKEOUT
READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius);
// LOCAL_GRAVITY FAKEOUT
READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius);
// ACTUALLY PARTICLE RADIUS
// OLD PROP_EMIT_ACCELERATION FAKEOUT
SKIP_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float);
// OLD PROP_ACCELERATION_SPREAD FAKEOUT
SKIP_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float);
READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius);
READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures);
}
@ -292,6 +327,18 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
READ_ENTITY_PROPERTY(PROP_ALPHA_FINISH, float, setAlphaFinish);
}
if (args.bitstreamVersion >= VERSION_ENTITIES_PARTICLE_ELLIPSOID_EMITTER) {
READ_ENTITY_PROPERTY(PROP_EMIT_SPEED, float, setEmitSpeed);
READ_ENTITY_PROPERTY(PROP_SPEED_SPREAD, float, setSpeedSpread);
READ_ENTITY_PROPERTY(PROP_EMIT_ORIENTATION, glm::quat, setEmitOrientation);
READ_ENTITY_PROPERTY(PROP_EMIT_DIMENSIONS, glm::vec3, setEmitDimensions);
READ_ENTITY_PROPERTY(PROP_EMIT_RADIUS_START, float, setEmitRadiusStart);
READ_ENTITY_PROPERTY(PROP_POLAR_START, float, setPolarStart);
READ_ENTITY_PROPERTY(PROP_POLAR_FINISH, float, setPolarFinish);
READ_ENTITY_PROPERTY(PROP_AZIMUTH_START, float, setAzimuthStart);
READ_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, float, setAzimuthFinish);
}
return bytesRead;
}
@ -309,12 +356,10 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
requestedProperties += PROP_MAX_PARTICLES;
requestedProperties += PROP_LIFESPAN;
requestedProperties += PROP_EMIT_RATE;
requestedProperties += PROP_EMIT_VELOCITY;
requestedProperties += PROP_EMIT_ACCELERATION;
requestedProperties += PROP_ACCELERATION_SPREAD;
requestedProperties += PROP_PARTICLE_RADIUS;
requestedProperties += PROP_TEXTURES;
requestedProperties += PROP_VELOCITY_SPREAD;
requestedProperties += PROP_RADIUS_SPREAD;
requestedProperties += PROP_RADIUS_START;
requestedProperties += PROP_RADIUS_FINISH;
@ -325,6 +370,15 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
requestedProperties += PROP_ALPHA_SPREAD;
requestedProperties += PROP_ALPHA_START;
requestedProperties += PROP_ALPHA_FINISH;
requestedProperties += PROP_EMIT_SPEED;
requestedProperties += PROP_SPEED_SPREAD;
requestedProperties += PROP_EMIT_ORIENTATION;
requestedProperties += PROP_EMIT_DIMENSIONS;
requestedProperties += PROP_EMIT_RADIUS_START;
requestedProperties += PROP_POLAR_START;
requestedProperties += PROP_POLAR_FINISH;
requestedProperties += PROP_AZIMUTH_START;
requestedProperties += PROP_AZIMUTH_FINISH;
return requestedProperties;
}
@ -347,12 +401,10 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, getMaxParticles());
APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, getLifespan());
APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, getEmitRate());
APPEND_ENTITY_PROPERTY(PROP_EMIT_VELOCITY, getEmitVelocity());
APPEND_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, getEmitAcceleration());
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, getAccelerationSpread());
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, getParticleRadius());
APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY_SPREAD, getVelocitySpread());
APPEND_ENTITY_PROPERTY(PROP_RADIUS_SPREAD, getRadiusSpread());
APPEND_ENTITY_PROPERTY(PROP_RADIUS_START, getRadiusStart());
APPEND_ENTITY_PROPERTY(PROP_RADIUS_FINISH, getRadiusFinish());
@ -363,6 +415,15 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
APPEND_ENTITY_PROPERTY(PROP_ALPHA_SPREAD, getAlphaSpread());
APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, getAlphaStart());
APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, getAlphaFinish());
APPEND_ENTITY_PROPERTY(PROP_EMIT_SPEED, getEmitSpeed());
APPEND_ENTITY_PROPERTY(PROP_SPEED_SPREAD, getSpeedSpread());
APPEND_ENTITY_PROPERTY(PROP_EMIT_ORIENTATION, getEmitOrientation());
APPEND_ENTITY_PROPERTY(PROP_EMIT_DIMENSIONS, getEmitDimensions());
APPEND_ENTITY_PROPERTY(PROP_EMIT_RADIUS_START, getEmitRadiusStart());
APPEND_ENTITY_PROPERTY(PROP_POLAR_START, getPolarStart());
APPEND_ENTITY_PROPERTY(PROP_POLAR_FINISH, getPolarFinish());
APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, getAzimuthStart());
APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, getAzimuthFinish());
}
bool ParticleEffectEntityItem::isAnimatingSomething() const {
@ -614,29 +675,73 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) {
_radiusMiddles[i] =_particleRadius;
_radiusFinishes[i] = getRadiusFinish();
} else {
float spreadMultiplier = 1.0f + (2.0f * randFloat() - 1) * _radiusSpread / _particleRadius;
float spreadMultiplier = 1.0f + (2.0f * randFloat() - 1.0f) * _radiusSpread / _particleRadius;
_radiusStarts[i] = spreadMultiplier * getRadiusStart();
_radiusMiddles[i] = spreadMultiplier * _particleRadius;
_radiusFinishes[i] = spreadMultiplier * getRadiusFinish();
}
updateRadius(i, 0.0f);
// Velocity and acceleration
glm::vec3 spreadOffset;
spreadOffset.x = -_velocitySpread.x + randFloat() * (_velocitySpread.x * 2.0f);
spreadOffset.y = -_velocitySpread.y + randFloat() * (_velocitySpread.y * 2.0f);
spreadOffset.z = -_velocitySpread.z + randFloat() * (_velocitySpread.z * 2.0f);
// Position, velocity, and acceleration
if (_polarStart == 0.0f && _polarFinish == 0.0f && _emitDimensions.z == 0.0f) {
// Emit along z-axis from position
_particlePositions[i] = getPosition();
_particleVelocities[i] =
(_emitSpeed + (2.0f * randFloat() - 1.0f) * _speedSpread) * (_emitOrientation * Z_AXIS);
_particleAccelerations[i] = _emitAcceleration + (2.0f * randFloat() - 1.0f) * _accelerationSpread;
// set initial conditions
_particlePositions[i] = getPosition();
_particleVelocities[i] = _emitVelocity + spreadOffset;
spreadOffset.x = -_accelerationSpread.x + randFloat() * (_accelerationSpread.x * 2.0f);
spreadOffset.y = -_accelerationSpread.y + randFloat() * (_accelerationSpread.y * 2.0f);
spreadOffset.z = -_accelerationSpread.z + randFloat() * (_accelerationSpread.z * 2.0f);
_particleAccelerations[i] = _emitAcceleration + spreadOffset;
} else {
// Emit around point or from ellipsoid
// - Distribute directions evenly around point
// - Distribute points relatively evenly over ellipsoid surface
// - Distribute points relatively evenly within ellipsoid volume
float elevationMinZ = sin(PI_OVER_TWO - _polarFinish);
float elevationMaxZ = sin(PI_OVER_TWO - _polarStart);
float elevation = asin(elevationMinZ + (elevationMaxZ - elevationMinZ) * randFloat());
float azimuth;
if (_azimuthFinish >= _azimuthStart) {
azimuth = _azimuthStart + (_azimuthFinish - _azimuthStart) * randFloat();
} else {
azimuth = _azimuthStart + (2.0f * PI + _azimuthFinish - _azimuthStart) * randFloat();
}
glm::vec3 emitDirection;
if (_emitDimensions == glm::vec3()) {
// Point
emitDirection = glm::angleAxis(PI_OVER_TWO - elevation, X_AXIS) * Z_AXIS;
emitDirection = glm::angleAxis(azimuth, Z_AXIS) * emitDirection;
_particlePositions[i] = getPosition();
} else {
// Ellipsoid
float radiusScale = 1.0f;
if (_emitRadiusStart < 1.0f) {
float emitRadiusStart = glm::max(_emitRadiusStart, EPSILON); // Avoid math complications at center
float randRadius = emitRadiusStart + (1.0f - emitRadiusStart) * randFloat();
radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
}
glm::vec3 radiuses = radiusScale * 0.5f * _emitDimensions;
float x = radiuses.x * glm::cos(elevation) * glm::cos(azimuth);
float y = radiuses.y * glm::cos(elevation) * glm::sin(azimuth);
float z = radiuses.z * glm::sin(elevation);
glm::vec3 emitPosition = glm::vec3(x, y, z);
emitDirection = glm::normalize(glm::vec3(
radiuses.x > 0.0f ? x / (radiuses.x * radiuses.x) : 0.0f,
radiuses.y > 0.0f ? y / (radiuses.y * radiuses.y) : 0.0f,
radiuses.z > 0.0f ? z / (radiuses.z * radiuses.z) : 0.0f
));
_particlePositions[i] = getPosition() + _emitOrientation * emitPosition;
}
_particleVelocities[i] =
(_emitSpeed + (2.0f * randFloat() - 1.0f) * _speedSpread) * (_emitOrientation * emitDirection);
_particleAccelerations[i] = _emitAcceleration + (2.0f * randFloat() - 1.0f) * _accelerationSpread;
}
integrateParticle(i, timeLeftInFrame);
extendBounds(_particlePositions[i]);

View file

@ -128,13 +128,41 @@ public:
void setEmitRate(float emitRate) { _emitRate = emitRate; }
float getEmitRate() const { return _emitRate; }
static const glm::vec3 DEFAULT_EMIT_VELOCITY;
void setEmitVelocity(const glm::vec3& emitVelocity);
const glm::vec3& getEmitVelocity() const { return _emitVelocity; }
static const glm::vec3 DEFAULT_VELOCITY_SPREAD;
void setVelocitySpread(const glm::vec3& velocitySpread);
const glm::vec3& getVelocitySpread() const { return _velocitySpread; }
static const float DEFAULT_EMIT_SPEED;
void setEmitSpeed(float emitSpeed);
float getEmitSpeed() const { return _emitSpeed; }
static const float DEFAULT_SPEED_SPREAD;
void setSpeedSpread(float speedSpread);
float getSpeedSpread() const { return _speedSpread; }
static const glm::quat DEFAULT_EMIT_ORIENTATION;
void setEmitOrientation(const glm::quat& emitOrientation);
const glm::quat& getEmitOrientation() const { return _emitOrientation; }
static const glm::vec3 DEFAULT_EMIT_DIMENSIONS;
void setEmitDimensions(const glm::vec3& emitDimensions);
const glm::vec3& getEmitDimensions() const { return _emitDimensions; }
static const float DEFAULT_EMIT_RADIUS_START;
void setEmitRadiusStart(float emitRadiusStart) { _emitRadiusStart = emitRadiusStart; }
float getEmitRadiusStart() const { return _emitRadiusStart; }
static const float DEFAULT_POLAR_START;
void setPolarStart(float polarStart) { _polarStart = polarStart; }
float getPolarStart() const { return _polarStart; }
static const float DEFAULT_POLAR_FINISH;
void setPolarFinish(float polarFinish) { _polarFinish = polarFinish; }
float getPolarFinish() const { return _polarFinish; }
static const float DEFAULT_AZIMUTH_START;
void setAzimuthStart(float azimuthStart) { _azimuthStart = azimuthStart; }
float getAzimuthStart() const { return _azimuthStart; }
static const float DEFAULT_AZIMUTH_FINISH;
void setAzimuthFinish(float azimuthFinish) { _azimuthFinish = azimuthFinish; }
float getAzimuthFinish() const { return _azimuthFinish; }
static const glm::vec3 DEFAULT_EMIT_ACCELERATION;
void setEmitAcceleration(const glm::vec3& emitAcceleration);
@ -202,8 +230,15 @@ protected:
quint32 _maxParticles = DEFAULT_MAX_PARTICLES;
float _lifespan = DEFAULT_LIFESPAN;
float _emitRate = DEFAULT_EMIT_RATE;
glm::vec3 _emitVelocity = DEFAULT_EMIT_VELOCITY;
glm::vec3 _velocitySpread = DEFAULT_VELOCITY_SPREAD;
float _emitSpeed = DEFAULT_EMIT_SPEED;
float _speedSpread = DEFAULT_SPEED_SPREAD;
glm::quat _emitOrientation = DEFAULT_EMIT_ORIENTATION;
glm::vec3 _emitDimensions = DEFAULT_EMIT_DIMENSIONS;
float _emitRadiusStart = DEFAULT_EMIT_RADIUS_START;
float _polarStart = DEFAULT_POLAR_START;
float _polarFinish = DEFAULT_POLAR_FINISH;
float _azimuthStart = DEFAULT_AZIMUTH_START;
float _azimuthFinish = DEFAULT_AZIMUTH_FINISH;
glm::vec3 _emitAcceleration = DEFAULT_EMIT_ACCELERATION;
glm::vec3 _accelerationSpread = DEFAULT_ACCELERATION_SPREAD;
float _particleRadius = DEFAULT_PARTICLE_RADIUS;

View file

@ -73,8 +73,9 @@ class PolyLineEntityItem : public EntityItem {
// never have a ray intersection pick a PolyLineEntityItem.
virtual bool supportsDetailedRayIntersection() const { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const { return false; }
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const { return false;}
virtual void debugDump() const;
static const float DEFAULT_LINE_WIDTH;

View file

@ -44,8 +44,9 @@ class PolyVoxEntityItem : public EntityItem {
// never have a ray intersection pick a PolyVoxEntityItem.
virtual bool supportsDetailedRayIntersection() const { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const { return false; }
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const { return false; }
virtual void debugDump() const;

View file

@ -95,7 +95,7 @@ void SphereEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBi
bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element,
float& distance, BoxFace& face,
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const {
// determine the ray in the frame of the entity transformed from a unit sphere
glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix();
@ -111,6 +111,7 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons
// then translate back to work coordinates
glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f));
distance = glm::distance(origin, hitAt);
surfaceNormal = glm::normalize(hitAt - getCenterPosition());
return true;
}
return false;

View file

@ -54,8 +54,9 @@ public:
virtual bool supportsDetailedRayIntersection() const { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const;
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const;
virtual void debugDump() const;

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