mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 17:35:45 +02:00
Merge branch 'master' into move-locations
This commit is contained in:
commit
25c86af40f
23 changed files with 367 additions and 355 deletions
|
@ -35,8 +35,7 @@ endif ()
|
||||||
|
|
||||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_CMAKE_PREFIX_PATH})
|
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_CMAKE_PREFIX_PATH})
|
||||||
|
|
||||||
# set our Base SDK to 10.8
|
# set our OS X deployment target to
|
||||||
set(CMAKE_OSX_SYSROOT /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk)
|
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8)
|
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8)
|
||||||
|
|
||||||
# Find includes in corresponding build directories
|
# Find includes in corresponding build directories
|
||||||
|
|
|
@ -96,7 +96,6 @@ AudioMixer::AudioMixer(const QByteArray& packet) :
|
||||||
{
|
{
|
||||||
// constant defined in AudioMixer.h. However, we don't want to include this here
|
// constant defined in AudioMixer.h. However, we don't want to include this here
|
||||||
// we will soon find a better common home for these audio-related constants
|
// we will soon find a better common home for these audio-related constants
|
||||||
_penumbraFilter.initialize(SAMPLE_RATE, (NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)) / 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioMixer::~AudioMixer() {
|
AudioMixer::~AudioMixer() {
|
||||||
|
@ -108,8 +107,10 @@ const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
|
||||||
const float ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE = 0.18f;
|
const float ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE = 0.18f;
|
||||||
const float RADIUS_OF_HEAD = 0.076f;
|
const float RADIUS_OF_HEAD = 0.076f;
|
||||||
|
|
||||||
int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd,
|
int AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData* listenerNodeData,
|
||||||
AvatarAudioStream* listeningNodeStream) {
|
const QUuid& streamUUID,
|
||||||
|
PositionalAudioStream* streamToAdd,
|
||||||
|
AvatarAudioStream* listeningNodeStream) {
|
||||||
// If repetition with fade is enabled:
|
// If repetition with fade is enabled:
|
||||||
// If streamToAdd could not provide a frame (it was starved), then we'll mix its previously-mixed frame
|
// If streamToAdd could not provide a frame (it was starved), then we'll mix its previously-mixed frame
|
||||||
// This is preferable to not mixing it at all since that's equivalent to inserting silence.
|
// This is preferable to not mixing it at all since that's equivalent to inserting silence.
|
||||||
|
@ -413,11 +414,13 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream*
|
||||||
<< -bearingRelativeAngleToSource;
|
<< -bearingRelativeAngleToSource;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Get our per listener/source data so we can get our filter
|
||||||
|
AudioFilterHSF1s& penumbraFilter = listenerNodeData->getListenerSourcePairData(streamUUID)->getPenumbraFilter();
|
||||||
|
|
||||||
// set the gain on both filter channels
|
// set the gain on both filter channels
|
||||||
_penumbraFilter.reset();
|
penumbraFilter.setParameters(0, 0, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainL, penumbraFilterSlope);
|
||||||
_penumbraFilter.setParameters(0, 0, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainL, penumbraFilterSlope);
|
penumbraFilter.setParameters(0, 1, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainR, penumbraFilterSlope);
|
||||||
_penumbraFilter.setParameters(0, 1, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainR, penumbraFilterSlope);
|
penumbraFilter.render(_preMixSamples, _preMixSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2);
|
||||||
_penumbraFilter.render(_preMixSamples, _preMixSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actually mix the _preMixSamples into the _mixSamples here.
|
// Actually mix the _preMixSamples into the _mixSamples here.
|
||||||
|
@ -430,6 +433,7 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream*
|
||||||
|
|
||||||
int AudioMixer::prepareMixForListeningNode(Node* node) {
|
int AudioMixer::prepareMixForListeningNode(Node* node) {
|
||||||
AvatarAudioStream* nodeAudioStream = ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioStream();
|
AvatarAudioStream* nodeAudioStream = ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioStream();
|
||||||
|
AudioMixerClientData* listenerNodeData = (AudioMixerClientData*)node->getLinkedData();
|
||||||
|
|
||||||
// zero out the client mix for this node
|
// zero out the client mix for this node
|
||||||
memset(_preMixSamples, 0, sizeof(_preMixSamples));
|
memset(_preMixSamples, 0, sizeof(_preMixSamples));
|
||||||
|
@ -447,9 +451,15 @@ int AudioMixer::prepareMixForListeningNode(Node* node) {
|
||||||
QHash<QUuid, PositionalAudioStream*>::ConstIterator i;
|
QHash<QUuid, PositionalAudioStream*>::ConstIterator i;
|
||||||
for (i = otherNodeAudioStreams.constBegin(); i != otherNodeAudioStreams.constEnd(); i++) {
|
for (i = otherNodeAudioStreams.constBegin(); i != otherNodeAudioStreams.constEnd(); i++) {
|
||||||
PositionalAudioStream* otherNodeStream = i.value();
|
PositionalAudioStream* otherNodeStream = i.value();
|
||||||
|
QUuid streamUUID = i.key();
|
||||||
|
|
||||||
|
if (otherNodeStream->getType() == PositionalAudioStream::Microphone) {
|
||||||
|
streamUUID = otherNode->getUUID();
|
||||||
|
}
|
||||||
|
|
||||||
if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) {
|
if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) {
|
||||||
streamsMixed += addStreamToMixForListeningNodeWithStream(otherNodeStream, nodeAudioStream);
|
streamsMixed += addStreamToMixForListeningNodeWithStream(listenerNodeData, streamUUID,
|
||||||
|
otherNodeStream, nodeAudioStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,12 @@
|
||||||
#define hifi_AudioMixer_h
|
#define hifi_AudioMixer_h
|
||||||
|
|
||||||
#include <AABox.h>
|
#include <AABox.h>
|
||||||
#include <AudioFormat.h> // For AudioFilterHSF1s and _penumbraFilter
|
|
||||||
#include <AudioBuffer.h> // For AudioFilterHSF1s and _penumbraFilter
|
|
||||||
#include <AudioFilter.h> // For AudioFilterHSF1s and _penumbraFilter
|
|
||||||
#include <AudioFilterBank.h> // For AudioFilterHSF1s and _penumbraFilter
|
|
||||||
#include <AudioRingBuffer.h>
|
#include <AudioRingBuffer.h>
|
||||||
#include <ThreadedAssignment.h>
|
#include <ThreadedAssignment.h>
|
||||||
|
|
||||||
class PositionalAudioStream;
|
class PositionalAudioStream;
|
||||||
class AvatarAudioStream;
|
class AvatarAudioStream;
|
||||||
|
class AudioMixerClientData;
|
||||||
|
|
||||||
const int SAMPLE_PHASE_DELAY_AT_90 = 20;
|
const int SAMPLE_PHASE_DELAY_AT_90 = 20;
|
||||||
|
|
||||||
|
@ -46,8 +43,10 @@ public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// adds one stream to the mix for a listening node
|
/// adds one stream to the mix for a listening node
|
||||||
int addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd,
|
int addStreamToMixForListeningNodeWithStream(AudioMixerClientData* listenerNodeData,
|
||||||
AvatarAudioStream* listeningNodeStream);
|
const QUuid& streamUUID,
|
||||||
|
PositionalAudioStream* streamToAdd,
|
||||||
|
AvatarAudioStream* listeningNodeStream);
|
||||||
|
|
||||||
/// prepares and sends a mix to one Node
|
/// prepares and sends a mix to one Node
|
||||||
int prepareMixForListeningNode(Node* node);
|
int prepareMixForListeningNode(Node* node);
|
||||||
|
@ -60,8 +59,6 @@ private:
|
||||||
// we are MMX adding 4 samples at a time so we need client samples to have an extra 4
|
// we are MMX adding 4 samples at a time so we need client samples to have an extra 4
|
||||||
int16_t _mixSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
|
int16_t _mixSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
|
||||||
|
|
||||||
AudioFilterHSF1s _penumbraFilter;
|
|
||||||
|
|
||||||
void perSecondActions();
|
void perSecondActions();
|
||||||
|
|
||||||
QString getReadPendingDatagramsCallsPerSecondsStatsString() const;
|
QString getReadPendingDatagramsCallsPerSecondsStatsString() const;
|
||||||
|
|
|
@ -33,6 +33,11 @@ AudioMixerClientData::~AudioMixerClientData() {
|
||||||
// delete this attached InboundAudioStream
|
// delete this attached InboundAudioStream
|
||||||
delete i.value();
|
delete i.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clean up our pair data...
|
||||||
|
foreach(PerListenerSourcePairData* pairData, _listenerSourcePairData) {
|
||||||
|
delete pairData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() const {
|
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() const {
|
||||||
|
@ -302,3 +307,12 @@ void AudioMixerClientData::printAudioStreamStats(const AudioStreamStats& streamS
|
||||||
formatUsecTime(streamStats._timeGapWindowMax).toLatin1().data(),
|
formatUsecTime(streamStats._timeGapWindowMax).toLatin1().data(),
|
||||||
formatUsecTime(streamStats._timeGapWindowAverage).toLatin1().data());
|
formatUsecTime(streamStats._timeGapWindowAverage).toLatin1().data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PerListenerSourcePairData* AudioMixerClientData::getListenerSourcePairData(const QUuid& sourceUUID) {
|
||||||
|
if (!_listenerSourcePairData.contains(sourceUUID)) {
|
||||||
|
PerListenerSourcePairData* newData = new PerListenerSourcePairData();
|
||||||
|
_listenerSourcePairData[sourceUUID] = newData;
|
||||||
|
}
|
||||||
|
return _listenerSourcePairData[sourceUUID];
|
||||||
|
}
|
||||||
|
|
|
@ -13,10 +13,25 @@
|
||||||
#define hifi_AudioMixerClientData_h
|
#define hifi_AudioMixerClientData_h
|
||||||
|
|
||||||
#include <AABox.h>
|
#include <AABox.h>
|
||||||
|
#include <AudioFormat.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||||
|
#include <AudioBuffer.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||||
|
#include <AudioFilter.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||||
|
#include <AudioFilterBank.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||||
|
|
||||||
#include "PositionalAudioStream.h"
|
#include "PositionalAudioStream.h"
|
||||||
#include "AvatarAudioStream.h"
|
#include "AvatarAudioStream.h"
|
||||||
|
|
||||||
|
class PerListenerSourcePairData {
|
||||||
|
public:
|
||||||
|
PerListenerSourcePairData() {
|
||||||
|
_penumbraFilter.initialize(SAMPLE_RATE, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2);
|
||||||
|
};
|
||||||
|
AudioFilterHSF1s& getPenumbraFilter() { return _penumbraFilter; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
AudioFilterHSF1s _penumbraFilter;
|
||||||
|
};
|
||||||
|
|
||||||
class AudioMixerClientData : public NodeData {
|
class AudioMixerClientData : public NodeData {
|
||||||
public:
|
public:
|
||||||
AudioMixerClientData();
|
AudioMixerClientData();
|
||||||
|
@ -40,12 +55,16 @@ public:
|
||||||
|
|
||||||
void printUpstreamDownstreamStats() const;
|
void printUpstreamDownstreamStats() const;
|
||||||
|
|
||||||
|
PerListenerSourcePairData* getListenerSourcePairData(const QUuid& sourceUUID);
|
||||||
private:
|
private:
|
||||||
void printAudioStreamStats(const AudioStreamStats& streamStats) const;
|
void printAudioStreamStats(const AudioStreamStats& streamStats) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHash<QUuid, PositionalAudioStream*> _audioStreams; // mic stream stored under key of null UUID
|
QHash<QUuid, PositionalAudioStream*> _audioStreams; // mic stream stored under key of null UUID
|
||||||
|
|
||||||
|
// TODO: how can we prune this hash when a stream is no longer present?
|
||||||
|
QHash<QUuid, PerListenerSourcePairData*> _listenerSourcePairData;
|
||||||
|
|
||||||
quint16 _outgoingMixedAudioSequenceNumber;
|
quint16 _outgoingMixedAudioSequenceNumber;
|
||||||
|
|
||||||
AudioStreamStats _downstreamAudioStreamStats;
|
AudioStreamStats _downstreamAudioStreamStats;
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
"type": "checkbox",
|
"type": "checkbox",
|
||||||
"label": "Enable Positional Filter",
|
"label": "Enable Positional Filter",
|
||||||
"help": "If enabled, positional audio stream uses lowpass filter",
|
"help": "If enabled, positional audio stream uses lowpass filter",
|
||||||
"default": true
|
"default": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
// Set the following variables to the values needed
|
// Set the following variables to the values needed
|
||||||
var filename = "http://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/bartender.rec";
|
var filename = "http://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/bartender.rec";
|
||||||
var playFromCurrentLocation = true;
|
var playFromCurrentLocation = true;
|
||||||
|
var useDisplayName = true;
|
||||||
|
var useAttachments = true;
|
||||||
|
var useHeadModel = true;
|
||||||
|
var useSkeletonModel = true;
|
||||||
|
|
||||||
// ID of the agent. Two agents can't have the same ID.
|
// ID of the agent. Two agents can't have the same ID.
|
||||||
var id = 0;
|
var id = 0;
|
||||||
|
@ -44,11 +48,15 @@ COLORS[SHOW] = { red: SHOW, green: 0, blue: 0 };
|
||||||
COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 };
|
COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 };
|
||||||
|
|
||||||
controlVoxelPosition.x += id * controlVoxelSize;
|
controlVoxelPosition.x += id * controlVoxelSize;
|
||||||
|
|
||||||
Avatar.setPlayFromCurrentLocation(playFromCurrentLocation);
|
|
||||||
|
|
||||||
Avatar.loadRecording(filename);
|
Avatar.loadRecording(filename);
|
||||||
|
|
||||||
|
Avatar.setPlayFromCurrentLocation(playFromCurrentLocation);
|
||||||
|
Avatar.setPlayerUseDisplayName(useDisplayName);
|
||||||
|
Avatar.setPlayerUseAttachments(useAttachments);
|
||||||
|
Avatar.setPlayerUseHeadModel(useHeadModel);
|
||||||
|
Avatar.setPlayerUseSkeletonModel(useSkeletonModel);
|
||||||
|
|
||||||
function setupVoxelViewer() {
|
function setupVoxelViewer() {
|
||||||
var voxelViewerOffset = 10;
|
var voxelViewerOffset = 10;
|
||||||
var voxelViewerPosition = JSON.parse(JSON.stringify(controlVoxelPosition));
|
var voxelViewerPosition = JSON.parse(JSON.stringify(controlVoxelPosition));
|
||||||
|
|
|
@ -12,7 +12,14 @@
|
||||||
Script.include("toolBars.js");
|
Script.include("toolBars.js");
|
||||||
|
|
||||||
var recordingFile = "recording.rec";
|
var recordingFile = "recording.rec";
|
||||||
var playFromCurrentLocation = true;
|
|
||||||
|
function setPlayerOptions() {
|
||||||
|
MyAvatar.setPlayFromCurrentLocation(true);
|
||||||
|
MyAvatar.setPlayerUseDisplayName(false);
|
||||||
|
MyAvatar.setPlayerUseAttachments(false);
|
||||||
|
MyAvatar.setPlayerUseHeadModel(false);
|
||||||
|
MyAvatar.setPlayerUseSkeletonModel(false);
|
||||||
|
}
|
||||||
|
|
||||||
var windowDimensions = Controller.getViewportDimensions();
|
var windowDimensions = Controller.getViewportDimensions();
|
||||||
var TOOL_ICON_URL = "http://s3-us-west-1.amazonaws.com/highfidelity-public/images/tools/";
|
var TOOL_ICON_URL = "http://s3-us-west-1.amazonaws.com/highfidelity-public/images/tools/";
|
||||||
|
@ -186,7 +193,7 @@ function mousePressEvent(event) {
|
||||||
toolBar.setAlpha(ALPHA_ON, saveIcon);
|
toolBar.setAlpha(ALPHA_ON, saveIcon);
|
||||||
toolBar.setAlpha(ALPHA_ON, loadIcon);
|
toolBar.setAlpha(ALPHA_ON, loadIcon);
|
||||||
} else if (MyAvatar.playerLength() > 0) {
|
} else if (MyAvatar.playerLength() > 0) {
|
||||||
MyAvatar.setPlayFromCurrentLocation(playFromCurrentLocation);
|
setPlayerOptions();
|
||||||
MyAvatar.setPlayerLoop(false);
|
MyAvatar.setPlayerLoop(false);
|
||||||
MyAvatar.startPlaying();
|
MyAvatar.startPlaying();
|
||||||
toolBar.setAlpha(ALPHA_OFF, recordIcon);
|
toolBar.setAlpha(ALPHA_OFF, recordIcon);
|
||||||
|
@ -201,7 +208,7 @@ function mousePressEvent(event) {
|
||||||
toolBar.setAlpha(ALPHA_ON, saveIcon);
|
toolBar.setAlpha(ALPHA_ON, saveIcon);
|
||||||
toolBar.setAlpha(ALPHA_ON, loadIcon);
|
toolBar.setAlpha(ALPHA_ON, loadIcon);
|
||||||
} else if (MyAvatar.playerLength() > 0) {
|
} else if (MyAvatar.playerLength() > 0) {
|
||||||
MyAvatar.setPlayFromCurrentLocation(playFromCurrentLocation);
|
setPlayerOptions();
|
||||||
MyAvatar.setPlayerLoop(true);
|
MyAvatar.setPlayerLoop(true);
|
||||||
MyAvatar.startPlaying();
|
MyAvatar.startPlaying();
|
||||||
toolBar.setAlpha(ALPHA_OFF, recordIcon);
|
toolBar.setAlpha(ALPHA_OFF, recordIcon);
|
||||||
|
|
|
@ -16,24 +16,21 @@ var debug = false;
|
||||||
var movingWithHead = false;
|
var movingWithHead = false;
|
||||||
var headStartPosition, headStartDeltaPitch, headStartFinalPitch, headStartRoll, headStartYaw;
|
var headStartPosition, headStartDeltaPitch, headStartFinalPitch, headStartRoll, headStartYaw;
|
||||||
|
|
||||||
var HEAD_MOVE_DEAD_ZONE = 0.10;
|
var HEAD_MOVE_DEAD_ZONE = 0.05;
|
||||||
var HEAD_STRAFE_DEAD_ZONE = 0.0;
|
var HEAD_STRAFE_DEAD_ZONE = 0.03;
|
||||||
var HEAD_ROTATE_DEAD_ZONE = 0.0;
|
var HEAD_ROTATE_DEAD_ZONE = 10.0;
|
||||||
//var HEAD_THRUST_FWD_SCALE = 12000.0;
|
var HEAD_YAW_RATE = 1.5;
|
||||||
//var HEAD_THRUST_STRAFE_SCALE = 0.0;
|
|
||||||
var HEAD_YAW_RATE = 1.0;
|
|
||||||
var HEAD_PITCH_RATE = 1.0;
|
var HEAD_PITCH_RATE = 1.0;
|
||||||
//var HEAD_ROLL_THRUST_SCALE = 75.0;
|
var WALL_BOUNCE = 10000.0;
|
||||||
//var HEAD_PITCH_LIFT_THRUST = 3.0;
|
var FIXED_WALK_VELOCITY = 1.5;
|
||||||
var WALL_BOUNCE = 4000.0;
|
|
||||||
|
|
||||||
// Modify these values to tweak the strength of the motion.
|
// Modify these values to tweak the strength of the motion.
|
||||||
// A larger *FACTOR increases the speed.
|
// A larger *FACTOR increases the speed.
|
||||||
// A lower SHORT_TIMESCALE makes the motor achieve full speed faster.
|
// A lower SHORT_TIMESCALE makes the motor achieve full speed faster.
|
||||||
var HEAD_VELOCITY_FWD_FACTOR = 20.0;
|
var HEAD_VELOCITY_FWD_FACTOR = 10.0;
|
||||||
var HEAD_VELOCITY_LEFT_FACTOR = 20.0;
|
var HEAD_VELOCITY_LEFT_FACTOR = 0.0;
|
||||||
var HEAD_VELOCITY_UP_FACTOR = 20.0;
|
var HEAD_VELOCITY_UP_FACTOR = 20.0;
|
||||||
var SHORT_TIMESCALE = 0.125;
|
var SHORT_TIMESCALE = 0.01;
|
||||||
var VERY_LARGE_TIMESCALE = 1000000.0;
|
var VERY_LARGE_TIMESCALE = 1000000.0;
|
||||||
|
|
||||||
var xAxis = {x:1.0, y:0.0, z:0.0 };
|
var xAxis = {x:1.0, y:0.0, z:0.0 };
|
||||||
|
@ -43,9 +40,10 @@ var zAxis = {x:0.0, y:0.0, z:1.0 };
|
||||||
// If these values are set to something
|
// If these values are set to something
|
||||||
var maxVelocity = 1.25;
|
var maxVelocity = 1.25;
|
||||||
var noFly = true;
|
var noFly = true;
|
||||||
|
var fixedWalkVelocity = true;
|
||||||
|
|
||||||
//var roomLimits = { xMin: 618, xMax: 635.5, zMin: 528, zMax: 552.5 };
|
//var roomLimits = { xMin: 618, xMax: 635.5, zMin: 528, zMax: 552.5 };
|
||||||
var roomLimits = { xMin: -1, xMax: 0, zMin: 0, zMax: 0 };
|
var roomLimits = { xMin: 193.0, xMax: 206.5, zMin: 251.4, zMax: 269.5 };
|
||||||
|
|
||||||
function isInRoom(position) {
|
function isInRoom(position) {
|
||||||
var BUFFER = 2.0;
|
var BUFFER = 2.0;
|
||||||
|
@ -71,25 +69,49 @@ function moveWithHead(deltaTime) {
|
||||||
var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch;
|
var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch;
|
||||||
var deltaRoll = MyAvatar.getHeadFinalRoll() - headStartRoll;
|
var deltaRoll = MyAvatar.getHeadFinalRoll() - headStartRoll;
|
||||||
var velocity = MyAvatar.getVelocity();
|
var velocity = MyAvatar.getVelocity();
|
||||||
var bodyLocalCurrentHeadVector = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position);
|
var position = MyAvatar.position;
|
||||||
bodyLocalCurrentHeadVector = Vec3.multiplyQbyV(Quat.angleAxis(-deltaYaw, {x:0, y: 1, z:0}), bodyLocalCurrentHeadVector);
|
var neckPosition = MyAvatar.getNeckPosition();
|
||||||
|
var bodyLocalCurrentHeadVector = Vec3.subtract(neckPosition, position);
|
||||||
|
bodyLocalCurrentHeadVector = Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), bodyLocalCurrentHeadVector);
|
||||||
var headDelta = Vec3.subtract(bodyLocalCurrentHeadVector, headStartPosition);
|
var headDelta = Vec3.subtract(bodyLocalCurrentHeadVector, headStartPosition);
|
||||||
headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta);
|
|
||||||
headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion
|
headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion
|
||||||
|
headDelta = Vec3.multiplyQbyV(MyAvatar.orientation, headDelta);
|
||||||
|
headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta);
|
||||||
|
|
||||||
|
var length = Vec3.length(headDelta);
|
||||||
|
|
||||||
|
if (length > 1.0) {
|
||||||
|
// Needs fixed! Right now sometimes reported neck position jumps to a bad value
|
||||||
|
headDelta.x = headDelta.y = headDelta.z = 0.0;
|
||||||
|
length = 0.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Thrust based on leaning forward and side-to-side
|
// Thrust based on leaning forward and side-to-side
|
||||||
var targetVelocity = {x:0.0, y:0.0, z:0.0};
|
var targetVelocity = {x:0.0, y:0.0, z:0.0};
|
||||||
|
if (length > HEAD_MOVE_DEAD_ZONE) {
|
||||||
|
//headDelta = Vec3.normalize(headDelta);
|
||||||
|
//targetVelocity = Vec3.multiply(headDelta, FIXED_WALK_VELOCITY);
|
||||||
|
targetVelocity = Vec3.multiply(headDelta, HEAD_VELOCITY_FWD_FACTOR);
|
||||||
|
}
|
||||||
|
/*
|
||||||
if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) {
|
if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) {
|
||||||
targetVelocity = Vec3.multiply(zAxis, -headDelta.z * HEAD_VELOCITY_FWD_FACTOR);
|
if (fixedWalkVelocity) {
|
||||||
|
targetVelocity = Vec3.multiply(zAxis, headDelta.z > 0 ? FIXED_WALK_VELOCITY : -FIXED_WALK_VELOCITY);
|
||||||
|
} else {
|
||||||
|
targetVelocity = Vec3.multiply(zAxis, headDelta.z * HEAD_VELOCITY_FWD_FACTOR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) {
|
if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) {
|
||||||
var deltaVelocity = Vec3.multiply(xAxis, -headDelta.x * HEAD_VELOCITY_LEFT_FACTOR);
|
var deltaVelocity = Vec3.multiply(xAxis, headDelta.x * HEAD_VELOCITY_LEFT_FACTOR);
|
||||||
targetVelocity = Vec3.sum(targetVelocity, deltaVelocity);
|
targetVelocity = Vec3.sum(targetVelocity, deltaVelocity);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) {
|
if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) {
|
||||||
var orientation = Quat.multiply(Quat.angleAxis((deltaYaw + deltaRoll) * HEAD_YAW_RATE * deltaTime, yAxis), MyAvatar.orientation);
|
var orientation = Quat.multiply(Quat.angleAxis((deltaYaw + deltaRoll) * HEAD_YAW_RATE * deltaTime, yAxis), MyAvatar.orientation);
|
||||||
MyAvatar.orientation = orientation;
|
MyAvatar.orientation = orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thrust Up/Down based on head pitch
|
// Thrust Up/Down based on head pitch
|
||||||
if (!noFly) {
|
if (!noFly) {
|
||||||
var deltaVelocity = Vec3.multiply(yAxis, headDelta.y * HEAD_VELOCITY_UP_FACTOR);
|
var deltaVelocity = Vec3.multiply(yAxis, headDelta.y * HEAD_VELOCITY_UP_FACTOR);
|
||||||
|
@ -121,7 +143,7 @@ function moveWithHead(deltaTime) {
|
||||||
if (movingWithHead && Vec3.length(thrust) > 0.0) {
|
if (movingWithHead && Vec3.length(thrust) > 0.0) {
|
||||||
// reduce the timescale of the motor so that it won't defeat the thrust code
|
// reduce the timescale of the motor so that it won't defeat the thrust code
|
||||||
Vec3.print("adebug room containment thrust = ", thrust);
|
Vec3.print("adebug room containment thrust = ", thrust);
|
||||||
motorTimescale = 4.0 * SHORT_TIMESCALE;
|
motorTimescale = 1000000.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MyAvatar.motorTimescale = motorTimescale;
|
MyAvatar.motorTimescale = motorTimescale;
|
||||||
|
@ -130,7 +152,8 @@ function moveWithHead(deltaTime) {
|
||||||
Controller.keyPressEvent.connect(function(event) {
|
Controller.keyPressEvent.connect(function(event) {
|
||||||
if (event.text == "SPACE" && !movingWithHead) {
|
if (event.text == "SPACE" && !movingWithHead) {
|
||||||
movingWithHead = true;
|
movingWithHead = true;
|
||||||
headStartPosition = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position);
|
headStartPosition = Vec3.subtract(MyAvatar.getNeckPosition(), MyAvatar.position);
|
||||||
|
headStartPosition = Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), headStartPosition);
|
||||||
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
|
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
|
||||||
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
|
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
|
||||||
headStartRoll = MyAvatar.getHeadFinalRoll();
|
headStartRoll = MyAvatar.getHeadFinalRoll();
|
||||||
|
|
171
examples/move.js
Executable file
171
examples/move.js
Executable file
|
@ -0,0 +1,171 @@
|
||||||
|
//
|
||||||
|
// move.js
|
||||||
|
//
|
||||||
|
// Created by AndrewMeadows, 2014.09.17
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// The avatar can be controlled by setting two motor parameters: motorVelocity and motorTimescale.
|
||||||
|
// Once the motorVelocity is set the avatar will try to move in that direction and speed. The
|
||||||
|
// motorTimescale is the approximate amount of time it takes for the avatar to reach within 1/e of its
|
||||||
|
// motorVelocity, so a short timescale makes it ramp up fast, and a long timescale makes it slow.
|
||||||
|
|
||||||
|
|
||||||
|
// These parameters control the motor's speed and strength.
|
||||||
|
var MAX_MOTOR_TIMESCALE = 0.5;
|
||||||
|
var PUSHING_MOTOR_TIMESCALE = 0.25;
|
||||||
|
var BRAKING_MOTOR_TIMESCALE = 0.125;
|
||||||
|
var VERY_LONG_TIME = 1000000.0;
|
||||||
|
|
||||||
|
var AVATAR_SPEED = 4.0;
|
||||||
|
var MIN_BRAKING_SPEED = 0.2;
|
||||||
|
|
||||||
|
|
||||||
|
var motorAccumulator = {x:0.0, y:0.0, z:0.0};
|
||||||
|
var isBraking = false;
|
||||||
|
|
||||||
|
// There is a bug in Qt-5.3.0 (and below) that prevents QKeyEvent.isAutoRepeat from being correctly set.
|
||||||
|
// This means we can't tell when a held button is actually released -- all we get are the repeated
|
||||||
|
// keyPress- (and keyRelease-) events. So what we have to do is keep a list of last press timestamps
|
||||||
|
// for buttons of interest, and then periodically scan that list for old timestamps and drop any that
|
||||||
|
// have expired (at which point we actually remove that buttons effect on the motor). As long as the
|
||||||
|
// check period is longer than the time between key repeats then things will be smooth, and as long
|
||||||
|
// as the expiry time is short enough then the stop won't feel too laggy.
|
||||||
|
|
||||||
|
var MAX_AUTO_REPEAT_DELAY = 3;
|
||||||
|
var KEY_RELEASE_EXPIRY_MSEC = 100;
|
||||||
|
|
||||||
|
// KeyInfo class contructor:
|
||||||
|
function KeyInfo(contribution) {
|
||||||
|
this.motorContribution = contribution; // Vec3 contribution of this key toward motorVelocity
|
||||||
|
this.releaseTime = new Date(); // time when this button was released
|
||||||
|
this.pressTime = new Date(); // time when this button was pressed
|
||||||
|
this.isPressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: the avatar's default orientation is such that "forward" is along the -zAxis, and "left" is along -xAxis.
|
||||||
|
var controlKeys = {
|
||||||
|
"UP" : new KeyInfo({x: 0.0, y: 0.0, z:-1.0}),
|
||||||
|
"DOWN" : new KeyInfo({x: 0.0, y: 0.0, z: 1.0}),
|
||||||
|
"SHIFT+LEFT" : new KeyInfo({x:-1.0, y: 0.0, z: 0.0}),
|
||||||
|
"SHIFT+RIGHT": new KeyInfo({x: 1.0, y: 0.0, z: 0.0}),
|
||||||
|
"w" : new KeyInfo({x: 0.0, y: 0.0, z:-1.0}),
|
||||||
|
"s" : new KeyInfo({x: 0.0, y: 0.0, z: 1.0}),
|
||||||
|
"e" : new KeyInfo({x: 0.0, y: 1.0, z: 0.0}),
|
||||||
|
"c" : new KeyInfo({x: 0.0, y:-1.0, z: 0.0})
|
||||||
|
};
|
||||||
|
|
||||||
|
// list of last timestamps when various buttons were last pressed
|
||||||
|
var pressTimestamps = {};
|
||||||
|
|
||||||
|
function keyPressEvent(event) {
|
||||||
|
// NOTE: we're harvesting some of the same keyboard controls that are used by the default (scriptless)
|
||||||
|
// avatar control. The scriptless control can be disabled via the Menu, thereby allowing this script
|
||||||
|
// to be the ONLY controller of the avatar position.
|
||||||
|
|
||||||
|
var keyName = event.text;
|
||||||
|
if (event.isShifted) {
|
||||||
|
keyName = "SHIFT+" + keyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = controlKeys[keyName];
|
||||||
|
if (key != undefined) {
|
||||||
|
key.pressTime = new Date();
|
||||||
|
// set the last pressTimestap element to undefined (MUCH faster than removing from the list)
|
||||||
|
pressTimestamps[keyName] = undefined;
|
||||||
|
var msec = key.pressTime.valueOf() - key.releaseTime.valueOf();
|
||||||
|
if (!key.isPressed) {
|
||||||
|
// add this key's effect to the motorAccumulator
|
||||||
|
motorAccumulator = Vec3.sum(motorAccumulator, key.motorContribution);
|
||||||
|
key.isPressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyReleaseEvent(event) {
|
||||||
|
var keyName = event.text;
|
||||||
|
if (event.isShifted) {
|
||||||
|
keyName = "SHIFT+" + keyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = controlKeys[keyName];
|
||||||
|
if (key != undefined) {
|
||||||
|
// add key to pressTimestamps
|
||||||
|
pressTimestamps[keyName] = new Date();
|
||||||
|
key.releaseTime = new Date();
|
||||||
|
var msec = key.releaseTime.valueOf() - key.pressTime.valueOf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMotor(deltaTime) {
|
||||||
|
// remove expired pressTimestamps
|
||||||
|
var now = new Date();
|
||||||
|
for (var keyName in pressTimestamps) {
|
||||||
|
var t = pressTimestamps[keyName];
|
||||||
|
if (t != undefined) {
|
||||||
|
var msec = now.valueOf() - t.valueOf();
|
||||||
|
if (msec > KEY_RELEASE_EXPIRY_MSEC) {
|
||||||
|
// the release of this key is now official, and we remove it from the motorAccumulator
|
||||||
|
motorAccumulator = Vec3.subtract(motorAccumulator, controlKeys[keyName].motorContribution);
|
||||||
|
controlKeys[keyName].isPressed = false;
|
||||||
|
// set the last pressTimestap element to undefined (MUCH faster than removing from the list)
|
||||||
|
pressTimestamps[keyName] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var motorVelocity = {x:0.0, y:0.0, z:0.0};
|
||||||
|
|
||||||
|
// figure out if we're pushing or braking
|
||||||
|
var accumulatorLength = Vec3.length(motorAccumulator);
|
||||||
|
var isPushing = false;
|
||||||
|
if (accumulatorLength == 0.0) {
|
||||||
|
if (!isBraking) {
|
||||||
|
isBraking = true;
|
||||||
|
}
|
||||||
|
isPushing = false;
|
||||||
|
} else {
|
||||||
|
isPushing = true;
|
||||||
|
motorVelocity = Vec3.multiply(AVATAR_SPEED / accumulatorLength, motorAccumulator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the timescale
|
||||||
|
var motorTimescale = MAX_MOTOR_TIMESCALE;
|
||||||
|
if (isBraking) {
|
||||||
|
var speed = Vec3.length(MyAvatar.getVelocity());
|
||||||
|
if (speed < MIN_BRAKING_SPEED) {
|
||||||
|
// we're going slow enough to turn off braking
|
||||||
|
// --> we'll drift to a halt, but not so stiffly that we can't be bumped
|
||||||
|
isBraking = false;
|
||||||
|
motorTimescale = MAX_MOTOR_TIMESCALE;
|
||||||
|
} else {
|
||||||
|
// we're still braking
|
||||||
|
motorTimescale = BRAKING_MOTOR_TIMESCALE;
|
||||||
|
}
|
||||||
|
} else if (isPushing) {
|
||||||
|
motorTimescale = PUSHING_MOTOR_TIMESCALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the motor parameters
|
||||||
|
MyAvatar.motorVelocity = motorVelocity;
|
||||||
|
MyAvatar.motorTimescale = motorTimescale;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scriptEnding() {
|
||||||
|
// disable the motor
|
||||||
|
MyAvatar.motorVelocity = {x:0.0, y:0.0, z:0.0}
|
||||||
|
MyAvatar.motorTimescale = VERY_LONG_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// init stuff
|
||||||
|
MyAvatar.motorReferenceFrame = "camera"; // "camera" is default, other options are "avatar" and "world"
|
||||||
|
MyAvatar.motorTimescale = VERY_LONG_TIME;
|
||||||
|
|
||||||
|
// connect callbacks
|
||||||
|
Controller.keyPressEvent.connect(keyPressEvent);
|
||||||
|
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||||
|
Script.update.connect(updateMotor);
|
||||||
|
Script.scriptEnding.connect(scriptEnding)
|
|
@ -1,294 +0,0 @@
|
||||||
//
|
|
||||||
// swissArmyJetpack.js
|
|
||||||
// examples
|
|
||||||
//
|
|
||||||
// Created by Andrew Meadows 2014.04.24
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// This is a work in progress. It will eventually be able to move the avatar around,
|
|
||||||
// toggle collision groups, modify avatar movement options, and other stuff (maybe trigger animations).
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
// misc global constants
|
|
||||||
var NUMBER_OF_COLLISION_BUTTONS = 3;
|
|
||||||
var NUMBER_OF_BUTTONS = 4;
|
|
||||||
var DOWN = { x: 0.0, y: -1.0, z: 0.0 };
|
|
||||||
var MAX_VOXEL_SCAN_DISTANCE = 30.0;
|
|
||||||
|
|
||||||
// behavior transition thresholds
|
|
||||||
var MIN_FLYING_SPEED = 3.0;
|
|
||||||
var MIN_COLLISIONLESS_SPEED = 5.0;
|
|
||||||
var MAX_WALKING_SPEED = 30.0;
|
|
||||||
var MAX_COLLIDABLE_SPEED = 35.0;
|
|
||||||
|
|
||||||
// button URL and geometry/UI tuning
|
|
||||||
var BUTTON_IMAGE_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/testing-swatches.svg";
|
|
||||||
var DISABLED_OFFSET_Y = 12;
|
|
||||||
var ENABLED_OFFSET_Y = 55 + 12;
|
|
||||||
var UI_BUFFER = 1;
|
|
||||||
var OFFSET_X = UI_BUFFER;
|
|
||||||
var OFFSET_Y = 200;
|
|
||||||
var BUTTON_WIDTH = 30;
|
|
||||||
var BUTTON_HEIGHT = 30;
|
|
||||||
var TEXT_OFFSET_X = OFFSET_X + BUTTON_WIDTH + UI_BUFFER;
|
|
||||||
var TEXT_HEIGHT = BUTTON_HEIGHT;
|
|
||||||
var TEXT_WIDTH = 210;
|
|
||||||
|
|
||||||
var MSEC_PER_SECOND = 1000;
|
|
||||||
var RAYCAST_EXPIRY_PERIOD = MSEC_PER_SECOND / 16;
|
|
||||||
var COLLISION_EXPIRY_PERIOD = 2 * MSEC_PER_SECOND;
|
|
||||||
var GRAVITY_ON_EXPIRY_PERIOD = MSEC_PER_SECOND / 2;
|
|
||||||
var GRAVITY_OFF_EXPIRY_PERIOD = MSEC_PER_SECOND / 8;
|
|
||||||
|
|
||||||
var dater = new Date();
|
|
||||||
var raycastExpiry = dater.getTime() + RAYCAST_EXPIRY_PERIOD;
|
|
||||||
var gravityOnExpiry = dater.getTime() + GRAVITY_ON_EXPIRY_PERIOD;
|
|
||||||
var gravityOffExpiry = dater.getTime() + GRAVITY_OFF_EXPIRY_PERIOD;
|
|
||||||
var collisionOnExpiry = dater.getTime() + COLLISION_EXPIRY_PERIOD;
|
|
||||||
|
|
||||||
// avatar state
|
|
||||||
var velocity = { x: 0.0, y: 0.0, z: 0.0 };
|
|
||||||
var standing = false;
|
|
||||||
|
|
||||||
// speedometer globals
|
|
||||||
var speed = 0.0;
|
|
||||||
var lastPosition = MyAvatar.position;
|
|
||||||
var speedometer = Overlays.addOverlay("text", {
|
|
||||||
x: OFFSET_X,
|
|
||||||
y: OFFSET_Y - BUTTON_HEIGHT,
|
|
||||||
width: BUTTON_WIDTH + UI_BUFFER + TEXT_WIDTH,
|
|
||||||
height: TEXT_HEIGHT,
|
|
||||||
color: { red: 0, green: 0, blue: 0 },
|
|
||||||
textColor: { red: 255, green: 0, blue: 0},
|
|
||||||
topMargin: 4,
|
|
||||||
leftMargin: 4,
|
|
||||||
text: "Speed: 0.0"
|
|
||||||
});
|
|
||||||
|
|
||||||
// collision group buttons
|
|
||||||
var buttons = new Array();
|
|
||||||
var labels = new Array();
|
|
||||||
|
|
||||||
var labelContents = new Array();
|
|
||||||
labelContents[0] = "Collide with Avatars";
|
|
||||||
labelContents[1] = "Collide with Voxels";
|
|
||||||
labelContents[2] = "Collide with Particles";
|
|
||||||
labelContents[3] = "Use local gravity";
|
|
||||||
var groupBits = 0;
|
|
||||||
|
|
||||||
var enabledColors = new Array();
|
|
||||||
enabledColors[0] = { red: 255, green: 0, blue: 0};
|
|
||||||
enabledColors[1] = { red: 0, green: 255, blue: 0};
|
|
||||||
enabledColors[2] = { red: 0, green: 0, blue: 255};
|
|
||||||
enabledColors[3] = { red: 255, green: 255, blue: 0};
|
|
||||||
|
|
||||||
var disabledColors = new Array();
|
|
||||||
disabledColors[0] = { red: 90, green: 75, blue: 75};
|
|
||||||
disabledColors[1] = { red: 75, green: 90, blue: 75};
|
|
||||||
disabledColors[2] = { red: 75, green: 75, blue: 90};
|
|
||||||
disabledColors[3] = { red: 90, green: 90, blue: 75};
|
|
||||||
|
|
||||||
var buttonStates = new Array();
|
|
||||||
|
|
||||||
for (i = 0; i < NUMBER_OF_BUTTONS; i++) {
|
|
||||||
var offsetS = 12
|
|
||||||
var offsetT = DISABLED_OFFSET_Y;
|
|
||||||
|
|
||||||
buttons[i] = Overlays.addOverlay("image", {
|
|
||||||
x: OFFSET_X,
|
|
||||||
y: OFFSET_Y + (BUTTON_HEIGHT * i),
|
|
||||||
width: BUTTON_WIDTH,
|
|
||||||
height: BUTTON_HEIGHT,
|
|
||||||
subImage: { x: offsetS, y: offsetT, width: BUTTON_WIDTH, height: BUTTON_HEIGHT },
|
|
||||||
imageURL: BUTTON_IMAGE_URL,
|
|
||||||
color: disabledColors[i],
|
|
||||||
alpha: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
labels[i] = Overlays.addOverlay("text", {
|
|
||||||
x: TEXT_OFFSET_X,
|
|
||||||
y: OFFSET_Y + (BUTTON_HEIGHT * i),
|
|
||||||
width: TEXT_WIDTH,
|
|
||||||
height: TEXT_HEIGHT,
|
|
||||||
color: { red: 0, green: 0, blue: 0},
|
|
||||||
textColor: { red: 255, green: 0, blue: 0},
|
|
||||||
topMargin: 4,
|
|
||||||
leftMargin: 4,
|
|
||||||
text: labelContents[i]
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonStates[i] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// functions
|
|
||||||
|
|
||||||
function updateButton(i, enabled) {
|
|
||||||
var offsetY = DISABLED_OFFSET_Y;
|
|
||||||
var buttonColor = disabledColors[i];
|
|
||||||
if (enabled) {
|
|
||||||
offsetY = ENABLED_OFFSET_Y;
|
|
||||||
buttonColor = enabledColors[i];
|
|
||||||
if (i == 0) {
|
|
||||||
groupBits |= COLLISION_GROUP_AVATARS;
|
|
||||||
} else if (i == 1) {
|
|
||||||
groupBits |= COLLISION_GROUP_VOXELS;
|
|
||||||
} else if (i == 2) {
|
|
||||||
groupBits |= COLLISION_GROUP_PARTICLES;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (i == 0) {
|
|
||||||
groupBits &= ~COLLISION_GROUP_AVATARS;
|
|
||||||
} else if (i == 1) {
|
|
||||||
groupBits &= ~COLLISION_GROUP_VOXELS;
|
|
||||||
} else if (i == 2) {
|
|
||||||
groupBits &= ~COLLISION_GROUP_PARTICLES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (groupBits != MyAvatar.collisionGroups) {
|
|
||||||
MyAvatar.collisionGroups = groupBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
Overlays.editOverlay(buttons[i], { subImage: { y: offsetY } } );
|
|
||||||
Overlays.editOverlay(buttons[i], { color: buttonColor } );
|
|
||||||
buttonStates[i] = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// When our script shuts down, we should clean up all of our overlays
|
|
||||||
function scriptEnding() {
|
|
||||||
for (i = 0; i < NUMBER_OF_BUTTONS; i++) {
|
|
||||||
Overlays.deleteOverlay(buttons[i]);
|
|
||||||
Overlays.deleteOverlay(labels[i]);
|
|
||||||
}
|
|
||||||
Overlays.deleteOverlay(speedometer);
|
|
||||||
}
|
|
||||||
Script.scriptEnding.connect(scriptEnding);
|
|
||||||
|
|
||||||
|
|
||||||
function updateSpeedometerDisplay() {
|
|
||||||
Overlays.editOverlay(speedometer, { text: "Speed: " + speed.toFixed(2) });
|
|
||||||
}
|
|
||||||
Script.setInterval(updateSpeedometerDisplay, 100);
|
|
||||||
|
|
||||||
function disableArtificialGravity() {
|
|
||||||
MyAvatar.motionBehaviors = MyAvatar.motionBehaviors & ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
|
|
||||||
updateButton(3, false);
|
|
||||||
}
|
|
||||||
// call this immediately so that avatar doesn't fall before voxel data arrives
|
|
||||||
// Ideally we would only do this on LOGIN, not when starting the script
|
|
||||||
// in the middle of a session.
|
|
||||||
disableArtificialGravity();
|
|
||||||
|
|
||||||
function enableArtificialGravity() {
|
|
||||||
// NOTE: setting the gravity automatically sets the AVATAR_MOTION_OBEY_LOCAL_GRAVITY behavior bit.
|
|
||||||
MyAvatar.gravity = DOWN;
|
|
||||||
updateButton(3, true);
|
|
||||||
// also enable collisions with voxels
|
|
||||||
groupBits |= COLLISION_GROUP_VOXELS;
|
|
||||||
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays
|
|
||||||
function update(deltaTime) {
|
|
||||||
if (groupBits != MyAvatar.collisionGroups) {
|
|
||||||
groupBits = MyAvatar.collisionGroups;
|
|
||||||
updateButton(0, groupBits & COLLISION_GROUP_AVATARS);
|
|
||||||
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
|
|
||||||
updateButton(2, groupBits & COLLISION_GROUP_PARTICLES);
|
|
||||||
}
|
|
||||||
|
|
||||||
// measure speed
|
|
||||||
var distance = Vec3.distance(MyAvatar.position, lastPosition);
|
|
||||||
speed = 0.8 * speed + 0.2 * distance / deltaTime;
|
|
||||||
lastPosition = MyAvatar.position;
|
|
||||||
|
|
||||||
dater = new Date();
|
|
||||||
var now = dater.getTime();
|
|
||||||
|
|
||||||
// transition gravity
|
|
||||||
if (raycastExpiry < now) {
|
|
||||||
// scan for landing platform
|
|
||||||
ray = { origin: MyAvatar.position, direction: DOWN };
|
|
||||||
var intersection = Voxels.findRayIntersection(ray);
|
|
||||||
// NOTE: it is possible for intersection.intersects to be false when it should be true
|
|
||||||
// (perhaps the raycast failed to lock the octree thread?). To workaround this problem
|
|
||||||
// we only transition on repeated failures.
|
|
||||||
|
|
||||||
if (intersection.intersects) {
|
|
||||||
// compute distance to voxel
|
|
||||||
var v = intersection.voxel;
|
|
||||||
var maxCorner = Vec3.sum({ x: v.x, y: v.y, z: v.z }, {x: v.s, y: v.s, z: v.s });
|
|
||||||
var distance = lastPosition.y - maxCorner.y;
|
|
||||||
|
|
||||||
if (distance < MAX_VOXEL_SCAN_DISTANCE) {
|
|
||||||
if (speed < MIN_FLYING_SPEED &&
|
|
||||||
gravityOnExpiry < now &&
|
|
||||||
!(MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY)) {
|
|
||||||
enableArtificialGravity();
|
|
||||||
}
|
|
||||||
if (speed < MAX_WALKING_SPEED) {
|
|
||||||
gravityOffExpiry = now + GRAVITY_OFF_EXPIRY_PERIOD;
|
|
||||||
} else if (gravityOffExpiry < now && MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
|
|
||||||
disableArtificialGravity();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// distance too far
|
|
||||||
if (gravityOffExpiry < now && MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
|
|
||||||
disableArtificialGravity();
|
|
||||||
}
|
|
||||||
gravityOnExpiry = now + GRAVITY_ON_EXPIRY_PERIOD;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no intersection
|
|
||||||
if (gravityOffExpiry < now && MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
|
|
||||||
disableArtificialGravity();
|
|
||||||
}
|
|
||||||
gravityOnExpiry = now + GRAVITY_ON_EXPIRY_PERIOD;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (speed > MAX_WALKING_SPEED && gravityOffExpiry < now) {
|
|
||||||
if (MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
|
|
||||||
// turn off gravity
|
|
||||||
MyAvatar.motionBehaviors = MyAvatar.motionBehaviors & ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
|
|
||||||
updateButton(3, false);
|
|
||||||
}
|
|
||||||
gravityOnExpiry = now + GRAVITY_ON_EXPIRY_PERIOD;
|
|
||||||
}
|
|
||||||
|
|
||||||
// transition collidability with voxels
|
|
||||||
if (speed < MIN_COLLISIONLESS_SPEED) {
|
|
||||||
if (collisionOnExpiry < now && !(MyAvatar.collisionGroups & COLLISION_GROUP_VOXELS)) {
|
|
||||||
// TODO: check to make sure not already colliding
|
|
||||||
// enable collision with voxels
|
|
||||||
groupBits |= COLLISION_GROUP_VOXELS;
|
|
||||||
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
collisionOnExpiry = now + COLLISION_EXPIRY_PERIOD;
|
|
||||||
}
|
|
||||||
if (speed > MAX_COLLIDABLE_SPEED) {
|
|
||||||
if (MyAvatar.collisionGroups & COLLISION_GROUP_VOXELS) {
|
|
||||||
// disable collisions with voxels
|
|
||||||
groupBits &= ~COLLISION_GROUP_VOXELS;
|
|
||||||
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Script.update.connect(update);
|
|
||||||
|
|
||||||
// we also handle click detection in our mousePressEvent()
|
|
||||||
function mousePressEvent(event) {
|
|
||||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
|
||||||
for (i = 0; i < NUMBER_OF_COLLISION_BUTTONS; i++) {
|
|
||||||
if (clickedOverlay == buttons[i]) {
|
|
||||||
var enabled = !(buttonStates[i]);
|
|
||||||
updateButton(i, enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Controller.mousePressEvent.connect(mousePressEvent);
|
|
||||||
|
|
|
@ -97,6 +97,12 @@ glm::vec3 Avatar::getChestPosition() const {
|
||||||
return _skeletonModel.getNeckPosition(neckPosition) ? (_position + neckPosition) * 0.5f : _position;
|
return _skeletonModel.getNeckPosition(neckPosition) ? (_position + neckPosition) * 0.5f : _position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 Avatar::getNeckPosition() const {
|
||||||
|
glm::vec3 neckPosition;
|
||||||
|
return _skeletonModel.getNeckPosition(neckPosition) ? neckPosition : _position;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
glm::quat Avatar::getWorldAlignedOrientation () const {
|
glm::quat Avatar::getWorldAlignedOrientation () const {
|
||||||
return computeRotationFromBodyToWorldUp() * getOrientation();
|
return computeRotationFromBodyToWorldUp() * getOrientation();
|
||||||
}
|
}
|
||||||
|
@ -871,7 +877,9 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
|
|
||||||
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||||
AvatarData::setAttachmentData(attachmentData);
|
AvatarData::setAttachmentData(attachmentData);
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection,
|
||||||
|
Q_ARG(const QVector<AttachmentData>, attachmentData));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// make sure we have as many models as attachments
|
// make sure we have as many models as attachments
|
||||||
|
@ -886,9 +894,9 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||||
|
|
||||||
// update the urls
|
// update the urls
|
||||||
for (int i = 0; i < attachmentData.size(); i++) {
|
for (int i = 0; i < attachmentData.size(); i++) {
|
||||||
|
_attachmentModels[i]->setURL(attachmentData.at(i).modelURL);
|
||||||
_attachmentModels[i]->setSnapModelToCenter(true);
|
_attachmentModels[i]->setSnapModelToCenter(true);
|
||||||
_attachmentModels[i]->setScaleToFit(true, _scale * _attachmentData.at(i).scale);
|
_attachmentModels[i]->setScaleToFit(true, _scale * _attachmentData.at(i).scale);
|
||||||
_attachmentModels[i]->setURL(attachmentData.at(i).modelURL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,8 @@ public:
|
||||||
Q_INVOKABLE void setJointModelPositionAndOrientation(int index, const glm::vec3 position, const glm::quat& rotation);
|
Q_INVOKABLE void setJointModelPositionAndOrientation(int index, const glm::vec3 position, const glm::quat& rotation);
|
||||||
Q_INVOKABLE void setJointModelPositionAndOrientation(const QString& name, const glm::vec3 position,
|
Q_INVOKABLE void setJointModelPositionAndOrientation(const QString& name, const glm::vec3 position,
|
||||||
const glm::quat& rotation);
|
const glm::quat& rotation);
|
||||||
|
|
||||||
|
Q_INVOKABLE glm::vec3 getNeckPosition() const;
|
||||||
|
|
||||||
Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; }
|
Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; }
|
||||||
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
|
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
|
||||||
|
|
|
@ -32,6 +32,7 @@ Head::Head(Avatar* owningAvatar) :
|
||||||
_eyePosition(0.0f, 0.0f, 0.0f),
|
_eyePosition(0.0f, 0.0f, 0.0f),
|
||||||
_scale(1.0f),
|
_scale(1.0f),
|
||||||
_lastLoudness(0.0f),
|
_lastLoudness(0.0f),
|
||||||
|
_longTermAverageLoudness(-1.0f),
|
||||||
_audioAttack(0.0f),
|
_audioAttack(0.0f),
|
||||||
_angularVelocity(0,0,0),
|
_angularVelocity(0,0,0),
|
||||||
_renderLookatVectors(false),
|
_renderLookatVectors(false),
|
||||||
|
@ -62,7 +63,7 @@ void Head::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
||||||
// Update audio trailing average for rendering facial animations
|
|
||||||
if (isMine) {
|
if (isMine) {
|
||||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||||
|
|
||||||
|
@ -78,6 +79,18 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Update audio trailing average for rendering facial animations
|
||||||
|
const float AUDIO_AVERAGING_SECS = 0.05f;
|
||||||
|
const float AUDIO_LONG_TERM_AVERAGING_SECS = 30.f;
|
||||||
|
_averageLoudness = glm::mix(_averageLoudness, _audioLoudness, glm::min(deltaTime / AUDIO_AVERAGING_SECS, 1.0f));
|
||||||
|
|
||||||
|
if (_longTermAverageLoudness == -1.0) {
|
||||||
|
_longTermAverageLoudness = _averageLoudness;
|
||||||
|
} else {
|
||||||
|
_longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f));
|
||||||
|
}
|
||||||
|
float deltaLoudness = glm::max(0.0f, _averageLoudness - _longTermAverageLoudness);
|
||||||
|
//qDebug() << "deltaLoudness: " << deltaLoudness;
|
||||||
|
|
||||||
if (!(_isFaceshiftConnected || billboard)) {
|
if (!(_isFaceshiftConnected || billboard)) {
|
||||||
// Update eye saccades
|
// Update eye saccades
|
||||||
|
@ -92,9 +105,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
||||||
_saccadeTarget = SACCADE_MAGNITUDE * randVector();
|
_saccadeTarget = SACCADE_MAGNITUDE * randVector();
|
||||||
}
|
}
|
||||||
_saccade += (_saccadeTarget - _saccade) * 0.50f;
|
_saccade += (_saccadeTarget - _saccade) * 0.50f;
|
||||||
|
|
||||||
const float AUDIO_AVERAGING_SECS = 0.05f;
|
|
||||||
_averageLoudness = glm::mix(_averageLoudness, _audioLoudness, glm::min(deltaTime / AUDIO_AVERAGING_SECS, 1.0f));
|
|
||||||
|
|
||||||
// Detect transition from talking to not; force blink after that and a delay
|
// Detect transition from talking to not; force blink after that and a delay
|
||||||
bool forceBlink = false;
|
bool forceBlink = false;
|
||||||
|
@ -108,8 +118,8 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update audio attack data for facial animation (eyebrows and mouth)
|
// Update audio attack data for facial animation (eyebrows and mouth)
|
||||||
_audioAttack = 0.9f * _audioAttack + 0.1f * fabs(_audioLoudness - _lastLoudness);
|
_audioAttack = 0.9f * _audioAttack + 0.1f * fabs((_audioLoudness - _longTermAverageLoudness) - _lastLoudness);
|
||||||
_lastLoudness = _audioLoudness;
|
_lastLoudness = (_audioLoudness - _longTermAverageLoudness);
|
||||||
|
|
||||||
const float BROW_LIFT_THRESHOLD = 100.0f;
|
const float BROW_LIFT_THRESHOLD = 100.0f;
|
||||||
if (_audioAttack > BROW_LIFT_THRESHOLD) {
|
if (_audioAttack > BROW_LIFT_THRESHOLD) {
|
||||||
|
|
|
@ -121,6 +121,7 @@ private:
|
||||||
|
|
||||||
float _scale;
|
float _scale;
|
||||||
float _lastLoudness;
|
float _lastLoudness;
|
||||||
|
float _longTermAverageLoudness;
|
||||||
float _audioAttack;
|
float _audioAttack;
|
||||||
glm::vec3 _angularVelocity;
|
glm::vec3 _angularVelocity;
|
||||||
bool _renderLookatVectors;
|
bool _renderLookatVectors;
|
||||||
|
|
|
@ -1029,7 +1029,9 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
|
|
||||||
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||||
Avatar::setAttachmentData(attachmentData);
|
Avatar::setAttachmentData(attachmentData);
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection,
|
||||||
|
Q_ARG(const QVector<AttachmentData>, attachmentData));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_billboardValid = false;
|
_billboardValid = false;
|
||||||
|
|
|
@ -75,6 +75,8 @@ public:
|
||||||
- (_b1 * _ym1)
|
- (_b1 * _ym1)
|
||||||
- (_b2 * _ym2);
|
- (_b2 * _ym2);
|
||||||
|
|
||||||
|
y = (y >= -EPSILON && y < EPSILON) ? 0.0f : y; // clamp to 0
|
||||||
|
|
||||||
// update delay line
|
// update delay line
|
||||||
_xm2 = _xm1;
|
_xm2 = _xm1;
|
||||||
_xm1 = x;
|
_xm1 = x;
|
||||||
|
|
|
@ -40,8 +40,8 @@ void AudioSourceTone::updateCoefficients() {
|
||||||
|
|
||||||
void AudioSourceTone::initialize() {
|
void AudioSourceTone::initialize() {
|
||||||
const float32_t FREQUENCY_220_HZ = 220.0f;
|
const float32_t FREQUENCY_220_HZ = 220.0f;
|
||||||
const float32_t GAIN_MINUS_3DB = 0.708f;
|
const float32_t GAIN_MINUS_6DB = 0.501f;
|
||||||
setParameters(SAMPLE_RATE, FREQUENCY_220_HZ, GAIN_MINUS_3DB);
|
setParameters(SAMPLE_RATE, FREQUENCY_220_HZ, GAIN_MINUS_6DB);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSourceTone::setParameters(const float32_t sampleRate, const float32_t frequency, const float32_t amplitude) {
|
void AudioSourceTone::setParameters(const float32_t sampleRate, const float32_t frequency, const float32_t amplitude) {
|
||||||
|
|
|
@ -661,6 +661,30 @@ void AvatarData::setPlayerLoop(bool loop) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarData::setPlayerUseDisplayName(bool useDisplayName) {
|
||||||
|
if(_player) {
|
||||||
|
_player->useDisplayName(useDisplayName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarData::setPlayerUseAttachments(bool useAttachments) {
|
||||||
|
if(_player) {
|
||||||
|
_player->useAttachements(useAttachments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarData::setPlayerUseHeadModel(bool useHeadModel) {
|
||||||
|
if(_player) {
|
||||||
|
_player->useHeadModel(useHeadModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarData::setPlayerUseSkeletonModel(bool useSkeletonModel) {
|
||||||
|
if(_player) {
|
||||||
|
_player->useSkeletonModel(useSkeletonModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AvatarData::play() {
|
void AvatarData::play() {
|
||||||
if (isPlaying()) {
|
if (isPlaying()) {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
|
|
|
@ -302,6 +302,10 @@ public slots:
|
||||||
void startPlaying();
|
void startPlaying();
|
||||||
void setPlayFromCurrentLocation(bool playFromCurrentLocation);
|
void setPlayFromCurrentLocation(bool playFromCurrentLocation);
|
||||||
void setPlayerLoop(bool loop);
|
void setPlayerLoop(bool loop);
|
||||||
|
void setPlayerUseDisplayName(bool useDisplayName);
|
||||||
|
void setPlayerUseAttachments(bool useAttachments);
|
||||||
|
void setPlayerUseHeadModel(bool useHeadModel);
|
||||||
|
void setPlayerUseSkeletonModel(bool useSkeletonModel);
|
||||||
void play();
|
void play();
|
||||||
void stopPlaying();
|
void stopPlaying();
|
||||||
|
|
||||||
|
|
|
@ -605,18 +605,18 @@ void ScriptEngine::stop() {
|
||||||
|
|
||||||
void ScriptEngine::timerFired() {
|
void ScriptEngine::timerFired() {
|
||||||
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
|
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
|
||||||
|
|
||||||
// call the associated JS function, if it exists
|
|
||||||
QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);
|
QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);
|
||||||
if (timerFunction.isValid()) {
|
|
||||||
timerFunction.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!callingTimer->isActive()) {
|
if (!callingTimer->isActive()) {
|
||||||
// this timer is done, we can kill it
|
// this timer is done, we can kill it
|
||||||
_timerFunctionMap.remove(callingTimer);
|
_timerFunctionMap.remove(callingTimer);
|
||||||
delete callingTimer;
|
delete callingTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// call the associated JS function, if it exists
|
||||||
|
if (timerFunction.isValid()) {
|
||||||
|
timerFunction.call();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {
|
QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {
|
||||||
|
|
|
@ -25,6 +25,10 @@ glm::vec3 Vec3::multiply(const glm::vec3& v1, float f) {
|
||||||
return v1 * f;
|
return v1 * f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 Vec3::multiply(float f, const glm::vec3& v1) {
|
||||||
|
return v1 * f;
|
||||||
|
}
|
||||||
|
|
||||||
glm::vec3 Vec3::multiplyQbyV(const glm::quat& q, const glm::vec3& v) {
|
glm::vec3 Vec3::multiplyQbyV(const glm::quat& q, const glm::vec3& v) {
|
||||||
return q * v;
|
return q * v;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ public slots:
|
||||||
glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2);
|
glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2);
|
||||||
float dot(const glm::vec3& v1, const glm::vec3& v2);
|
float dot(const glm::vec3& v1, const glm::vec3& v2);
|
||||||
glm::vec3 multiply(const glm::vec3& v1, float f);
|
glm::vec3 multiply(const glm::vec3& v1, float f);
|
||||||
|
glm::vec3 multiply(float, const glm::vec3& v1);
|
||||||
glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v);
|
glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v);
|
||||||
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);
|
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);
|
||||||
glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2);
|
glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2);
|
||||||
|
|
Loading…
Reference in a new issue