This commit is contained in:
Philip Rosedale 2014-09-26 11:29:42 -07:00
commit b3f15966fc
37 changed files with 763 additions and 129 deletions

View file

@ -75,7 +75,7 @@ InboundAudioStream::Settings AudioMixer::_streamSettings;
bool AudioMixer::_printStreamStats = false; bool AudioMixer::_printStreamStats = false;
bool AudioMixer::_enableFilter = false; bool AudioMixer::_enableFilter = true;
AudioMixer::AudioMixer(const QByteArray& packet) : AudioMixer::AudioMixer(const QByteArray& packet) :
ThreadedAssignment(packet), ThreadedAssignment(packet),
@ -710,7 +710,9 @@ void AudioMixer::run() {
} }
const QString FILTER_KEY = "J-enable-filter"; const QString FILTER_KEY = "J-enable-filter";
_enableFilter = audioGroupObject[FILTER_KEY].toBool(); if (audioGroupObject[FILTER_KEY].isBool()) {
_enableFilter = audioGroupObject[FILTER_KEY].toBool();
}
if (_enableFilter) { if (_enableFilter) {
qDebug() << "Filter enabled"; qDebug() << "Filter enabled";
} }

View file

@ -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": false "default": true
} }
} }
} }

View file

@ -40,6 +40,7 @@ var timerOffset;
setupToolBar(); setupToolBar();
var timer = null; var timer = null;
var slider = null;
setupTimer(); setupTimer();
var watchStop = false; var watchStop = false;
@ -115,6 +116,30 @@ function setupTimer() {
alpha: 1.0, alpha: 1.0,
visible: true visible: true
}); });
slider = { x: 0, y: 0,
w: 200, h: 20,
pos: 0.0, // 0.0 <= pos <= 1.0
};
slider.background = Overlays.addOverlay("text", {
text: "",
backgroundColor: { red: 128, green: 128, blue: 128 },
x: slider.x, y: slider.y,
width: slider.w,
height: slider.h,
alpha: 1.0,
visible: true
});
slider.foreground = Overlays.addOverlay("text", {
text: "",
backgroundColor: { red: 200, green: 200, blue: 200 },
x: slider.x, y: slider.y,
width: slider.pos * slider.w,
height: slider.h,
alpha: 1.0,
visible: true
});
} }
function updateTimer() { function updateTimer() {
@ -131,6 +156,16 @@ function updateTimer() {
text: text text: text
}) })
toolBar.changeSpacing(text.length * 8 + ((MyAvatar.isRecording()) ? 15 : 0), spacing); toolBar.changeSpacing(text.length * 8 + ((MyAvatar.isRecording()) ? 15 : 0), spacing);
if (MyAvatar.isRecording()) {
slider.pos = 1.0;
} else if (MyAvatar.playerLength() > 0) {
slider.pos = MyAvatar.playerElapsed() / MyAvatar.playerLength();
}
Overlays.editOverlay(slider.foreground, {
width: slider.pos * slider.w
});
} }
function formatTime(time) { function formatTime(time) {
@ -163,7 +198,19 @@ function moveUI() {
Overlays.editOverlay(timer, { Overlays.editOverlay(timer, {
x: relative.x + timerOffset - ToolBar.SPACING, x: relative.x + timerOffset - ToolBar.SPACING,
y: windowDimensions.y - relative.y - ToolBar.SPACING y: windowDimensions.y - relative.y - ToolBar.SPACING
}); });
slider.x = relative.x - ToolBar.SPACING;
slider.y = windowDimensions.y - relative.y - slider.h - ToolBar.SPACING;
Overlays.editOverlay(slider.background, {
x: slider.x,
y: slider.y,
});
Overlays.editOverlay(slider.foreground, {
x: slider.x,
y: slider.y,
});
} }
function mousePressEvent(event) { function mousePressEvent(event) {
@ -188,7 +235,7 @@ function mousePressEvent(event) {
} }
} else if (playIcon === toolBar.clicked(clickedOverlay) && !MyAvatar.isRecording()) { } else if (playIcon === toolBar.clicked(clickedOverlay) && !MyAvatar.isRecording()) {
if (MyAvatar.isPlaying()) { if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying(); MyAvatar.pausePlayer();
toolBar.setAlpha(ALPHA_ON, recordIcon); toolBar.setAlpha(ALPHA_ON, recordIcon);
toolBar.setAlpha(ALPHA_ON, saveIcon); toolBar.setAlpha(ALPHA_ON, saveIcon);
toolBar.setAlpha(ALPHA_ON, loadIcon); toolBar.setAlpha(ALPHA_ON, loadIcon);
@ -203,7 +250,7 @@ function mousePressEvent(event) {
} }
} else if (playLoopIcon === toolBar.clicked(clickedOverlay) && !MyAvatar.isRecording()) { } else if (playLoopIcon === toolBar.clicked(clickedOverlay) && !MyAvatar.isRecording()) {
if (MyAvatar.isPlaying()) { if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying(); MyAvatar.pausePlayer();
toolBar.setAlpha(ALPHA_ON, recordIcon); toolBar.setAlpha(ALPHA_ON, recordIcon);
toolBar.setAlpha(ALPHA_ON, saveIcon); toolBar.setAlpha(ALPHA_ON, saveIcon);
toolBar.setAlpha(ALPHA_ON, loadIcon); toolBar.setAlpha(ALPHA_ON, loadIcon);
@ -234,10 +281,30 @@ function mousePressEvent(event) {
toolBar.setAlpha(ALPHA_ON, saveIcon); toolBar.setAlpha(ALPHA_ON, saveIcon);
} }
} }
} else { } else if (MyAvatar.playerLength() > 0 &&
slider.x < event.x && event.x < slider.x + slider.w &&
slider.y < event.y && event.y < slider.y + slider.h) {
isSliding = true;
slider.pos = (event.x - slider.x) / slider.w;
MyAvatar.setPlayerTime(slider.pos * MyAvatar.playerLength());
} }
} }
var isSliding = false;
function mouseMoveEvent(event) {
if (isSliding) {
slider.pos = (event.x - slider.x) / slider.w;
if (slider.pos < 0.0 || slider.pos > 1.0) {
MyAvatar.stopPlaying();
slider.pos = 0.0;
}
MyAvatar.setPlayerTime(slider.pos * MyAvatar.playerLength());
}
}
function mouseReleaseEvent(event) {
isSliding = false;
}
function update() { function update() {
var newDimensions = Controller.getViewportDimensions(); var newDimensions = Controller.getViewportDimensions();
@ -264,11 +331,15 @@ function scriptEnding() {
if (MyAvatar.isPlaying()) { if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying(); MyAvatar.stopPlaying();
} }
toolBar.cleanup(); toolBar.cleanup();
Overlays.deleteOverlay(timer); Overlays.deleteOverlay(timer);
Overlays.deleteOverlay(slider.background);
Overlays.deleteOverlay(slider.foreground);
} }
Controller.mousePressEvent.connect(mousePressEvent); Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Script.update.connect(update); Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding); Script.scriptEnding.connect(scriptEnding);

View file

@ -2449,6 +2449,7 @@ function Tooltip() {
text += "Lifetime: " + properties.lifetime + "\n" text += "Lifetime: " + properties.lifetime + "\n"
} }
text += "Age: " + properties.ageAsText + "\n" text += "Age: " + properties.ageAsText + "\n"
text += "Mass: " + properties.mass + "\n"
text += "Script: " + properties.script + "\n" text += "Script: " + properties.script + "\n"
@ -2906,6 +2907,8 @@ function handeMenuEvent(menuItem) {
index++; index++;
array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) }); array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
index++; index++;
array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) });
index++;
array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) }); array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) });
index++; index++;
array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) }); array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) });
@ -3052,6 +3055,7 @@ Window.nonBlockingFormClosed.connect(function() {
properties.velocity.y = array[index++].value; properties.velocity.y = array[index++].value;
properties.velocity.z = array[index++].value; properties.velocity.z = array[index++].value;
properties.damping = array[index++].value; properties.damping = array[index++].value;
properties.mass = array[index++].value;
properties.angularVelocity.x = array[index++].value; properties.angularVelocity.x = array[index++].value;
properties.angularVelocity.y = array[index++].value; properties.angularVelocity.y = array[index++].value;

View file

@ -1127,8 +1127,6 @@ function keyPressEvent(event) {
} else if (event.text == "z") { } else if (event.text == "z") {
undoSound.playRandom(); undoSound.playRandom();
} }
} }
trackKeyPressEvent(event); // used by preview support trackKeyPressEvent(event); // used by preview support

View file

@ -10,7 +10,7 @@
// //
var modelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/entities/radio/Speakers2Finished.fbx"; var modelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/entities/radio/Speakers.fbx";
var soundURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/FamilyStereo.raw"; var soundURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/FamilyStereo.raw";
var AudioRotationOffset = Quat.fromPitchYawRollDegrees(0, -90, 0); var AudioRotationOffset = Quat.fromPitchYawRollDegrees(0, -90, 0);
@ -20,7 +20,7 @@ audioOptions.loop = true;
audioOptions.isStereo = true; audioOptions.isStereo = true;
var injector = null; var injector = null;
var sound = new Sound(soundURL); var sound = new Sound(soundURL, audioOptions.isStereo);
var entity = null; var entity = null;
var properties = null; var properties = null;
@ -31,14 +31,16 @@ function update() {
print("Sound file downloaded"); print("Sound file downloaded");
var position = Vec3.sum(MyAvatar.position, var position = Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(MyAvatar.orientation,
{ x: 0, y: 0.3, z: -1 })); { x: 0, y: -0.3, z: -2 }));
var rotation = Quat.multiply(MyAvatar.orientation, var rotation = Quat.multiply(MyAvatar.orientation,
Quat.fromPitchYawRollDegrees(0, -90, 0)); Quat.fromPitchYawRollDegrees(0, -90, 0));
entity = Entities.addEntity({ entity = Entities.addEntity({
type: "Model", type: "Model",
position: position, position: position,
rotation: rotation, rotation: rotation,
dimensions: { x: 0.5, y: 0.5, z: 0.5 }, dimensions: { x: 0.391,
y: 1.000,
z: 1.701 },
modelURL: modelURL modelURL: modelURL
}); });
properties = Entities.getEntityProperties(entity); properties = Entities.getEntityProperties(entity);

View file

@ -17,13 +17,20 @@ const int SPLAT_COUNT = 4;
// the splat textures // the splat textures
uniform sampler2D diffuseMaps[SPLAT_COUNT]; uniform sampler2D diffuseMaps[SPLAT_COUNT];
// the model space normal
varying vec3 normal;
// alpha values for the four splat textures // alpha values for the four splat textures
varying vec4 alphaValues; varying vec4 alphaValues;
void main(void) { void main(void) {
// determine the cube face to use for texture coordinate generation
vec3 absNormal = abs(normal);
vec2 parameters = step(absNormal.yy, absNormal.xz) * step(absNormal.zx, absNormal.xz);
// blend the splat textures // blend the splat textures
gl_FragColor = (texture2D(diffuseMaps[0], gl_TexCoord[0].st) * alphaValues.x + gl_FragColor = (texture2D(diffuseMaps[0], mix(gl_TexCoord[0].xw, gl_TexCoord[0].zy, parameters)) * alphaValues.x +
texture2D(diffuseMaps[1], gl_TexCoord[1].st) * alphaValues.y + texture2D(diffuseMaps[1], mix(gl_TexCoord[1].xw, gl_TexCoord[1].zy, parameters)) * alphaValues.y +
texture2D(diffuseMaps[2], gl_TexCoord[2].st) * alphaValues.z + texture2D(diffuseMaps[2], mix(gl_TexCoord[2].xw, gl_TexCoord[2].zy, parameters)) * alphaValues.z +
texture2D(diffuseMaps[3], gl_TexCoord[3].st) * alphaValues.w); texture2D(diffuseMaps[3], mix(gl_TexCoord[3].xw, gl_TexCoord[3].zy, parameters)) * alphaValues.w);
} }

View file

@ -29,6 +29,9 @@ attribute vec4 materials;
// the weights of each material // the weights of each material
attribute vec4 materialWeights; attribute vec4 materialWeights;
// the model space normal
varying vec3 normal;
// alpha values for the four splat textures // alpha values for the four splat textures
varying vec4 alphaValues; varying vec4 alphaValues;
@ -36,12 +39,19 @@ void main(void) {
// use the fixed-function position // use the fixed-function position
gl_Position = ftransform(); gl_Position = ftransform();
// pass along the normal
normal = gl_Normal;
// pass along the scaled/offset texture coordinates // pass along the scaled/offset texture coordinates
vec4 textureSpacePosition = vec4(gl_Vertex.xz, 0.0, 1.0); vec4 textureSpacePosition = gl_Vertex.xyyz;
gl_TexCoord[0] = textureSpacePosition * vec4(splatTextureScalesS[0], splatTextureScalesT[0], 0.0, 1.0); gl_TexCoord[0] = textureSpacePosition * vec4(splatTextureScalesS[0], splatTextureScalesT[0],
gl_TexCoord[1] = textureSpacePosition * vec4(splatTextureScalesS[1], splatTextureScalesT[1], 0.0, 1.0); splatTextureScalesS[0], splatTextureScalesT[0]);
gl_TexCoord[2] = textureSpacePosition * vec4(splatTextureScalesS[2], splatTextureScalesT[2], 0.0, 1.0); gl_TexCoord[1] = textureSpacePosition * vec4(splatTextureScalesS[1], splatTextureScalesT[1],
gl_TexCoord[3] = textureSpacePosition * vec4(splatTextureScalesS[3], splatTextureScalesT[3], 0.0, 1.0); splatTextureScalesS[1], splatTextureScalesT[1]);
gl_TexCoord[2] = textureSpacePosition * vec4(splatTextureScalesS[2], splatTextureScalesT[2],
splatTextureScalesS[2], splatTextureScalesT[2]);
gl_TexCoord[3] = textureSpacePosition * vec4(splatTextureScalesS[3], splatTextureScalesT[3],
splatTextureScalesS[3], splatTextureScalesT[3]);
// compute the alpha values for each texture // compute the alpha values for each texture
float value = materials[0]; float value = materials[0];

View file

@ -25,7 +25,8 @@ AudioInjector::AudioInjector(QObject* parent) :
QObject(parent), QObject(parent),
_sound(NULL), _sound(NULL),
_options(), _options(),
_shouldStop(false) _shouldStop(false),
_currentSendPosition(0)
{ {
} }
@ -77,11 +78,13 @@ void AudioInjector::injectAudio() {
// pack the position for injected audio // pack the position for injected audio
int positionOptionOffset = injectAudioPacket.size(); int positionOptionOffset = injectAudioPacket.size();
packetStream.writeRawData(reinterpret_cast<const char*>(&_options.getPosition()), sizeof(_options.getPosition())); packetStream.writeRawData(reinterpret_cast<const char*>(&_options.getPosition()),
sizeof(_options.getPosition()));
// pack our orientation for injected audio // pack our orientation for injected audio
int orientationOptionOffset = injectAudioPacket.size(); int orientationOptionOffset = injectAudioPacket.size();
packetStream.writeRawData(reinterpret_cast<const char*>(&_options.getOrientation()), sizeof(_options.getOrientation())); packetStream.writeRawData(reinterpret_cast<const char*>(&_options.getOrientation()),
sizeof(_options.getOrientation()));
// pack zero for radius // pack zero for radius
float radius = 0; float radius = 0;
@ -95,17 +98,15 @@ void AudioInjector::injectAudio() {
timer.start(); timer.start();
int nextFrame = 0; int nextFrame = 0;
int currentSendPosition = 0;
int numPreAudioDataBytes = injectAudioPacket.size(); int numPreAudioDataBytes = injectAudioPacket.size();
bool shouldLoop = _options.getLoop(); bool shouldLoop = _options.getLoop();
// loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
quint16 outgoingInjectedAudioSequenceNumber = 0; quint16 outgoingInjectedAudioSequenceNumber = 0;
while (currentSendPosition < soundByteArray.size() && !_shouldStop) { while (_currentSendPosition < soundByteArray.size() && !_shouldStop) {
int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, int bytesToCopy = std::min(((_options.isStereo()) ? 2 : 1) * NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
soundByteArray.size() - currentSendPosition); soundByteArray.size() - _currentSendPosition);
memcpy(injectAudioPacket.data() + positionOptionOffset, memcpy(injectAudioPacket.data() + positionOptionOffset,
&_options.getPosition(), &_options.getPosition(),
sizeof(_options.getPosition())); sizeof(_options.getPosition()));
@ -117,10 +118,12 @@ void AudioInjector::injectAudio() {
injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy); injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy);
// pack the sequence number // pack the sequence number
memcpy(injectAudioPacket.data() + numPreSequenceNumberBytes, &outgoingInjectedAudioSequenceNumber, sizeof(quint16)); memcpy(injectAudioPacket.data() + numPreSequenceNumberBytes,
&outgoingInjectedAudioSequenceNumber, sizeof(quint16));
// copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet // copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet
memcpy(injectAudioPacket.data() + numPreAudioDataBytes, soundByteArray.data() + currentSendPosition, bytesToCopy); memcpy(injectAudioPacket.data() + numPreAudioDataBytes,
soundByteArray.data() + _currentSendPosition, bytesToCopy);
// grab our audio mixer from the NodeList, if it exists // grab our audio mixer from the NodeList, if it exists
NodeList* nodeList = NodeList::getInstance(); NodeList* nodeList = NodeList::getInstance();
@ -130,22 +133,22 @@ void AudioInjector::injectAudio() {
nodeList->writeDatagram(injectAudioPacket, audioMixer); nodeList->writeDatagram(injectAudioPacket, audioMixer);
outgoingInjectedAudioSequenceNumber++; outgoingInjectedAudioSequenceNumber++;
currentSendPosition += bytesToCopy; _currentSendPosition += bytesToCopy;
// send two packets before the first sleep so the mixer can start playback right away // send two packets before the first sleep so the mixer can start playback right away
if (currentSendPosition != bytesToCopy && currentSendPosition < soundByteArray.size()) { if (_currentSendPosition != bytesToCopy && _currentSendPosition < soundByteArray.size()) {
// not the first packet and not done // not the first packet and not done
// sleep for the appropriate time // sleep for the appropriate time
int usecToSleep = (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - timer.nsecsElapsed() / 1000; int usecToSleep = (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - timer.nsecsElapsed() / 1000;
if (usecToSleep > 0) { if (usecToSleep > 0) {
usleep(usecToSleep); usleep(usecToSleep);
} }
} }
if (shouldLoop && currentSendPosition == soundByteArray.size()) { if (shouldLoop && _currentSendPosition >= soundByteArray.size()) {
currentSendPosition = 0; _currentSendPosition = 0;
} }
} }
} }

View file

@ -26,16 +26,20 @@ class AudioInjector : public QObject {
public: public:
AudioInjector(QObject* parent); AudioInjector(QObject* parent);
AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions); AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions);
int getCurrentSendPosition() const { return _currentSendPosition; }
public slots: public slots:
void injectAudio(); void injectAudio();
void stop() { _shouldStop = true; } void stop() { _shouldStop = true; }
void setOptions(AudioInjectorOptions& options); void setOptions(AudioInjectorOptions& options);
void setCurrentSendPosition(int currentSendPosition) { _currentSendPosition = currentSendPosition; }
signals: signals:
void finished(); void finished();
private: private:
Sound* _sound; Sound* _sound;
AudioInjectorOptions _options; AudioInjectorOptions _options;
bool _shouldStop; bool _shouldStop;
int _currentSendPosition;
}; };
Q_DECLARE_METATYPE(AudioInjector*) Q_DECLARE_METATYPE(AudioInjector*)

View file

@ -13,6 +13,9 @@
AudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions* injectorOptions) { AudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions* injectorOptions) {
if (sound->isStereo()) {
const_cast<AudioInjectorOptions*>(injectorOptions)->setIsStereo(true);
}
AudioInjector* injector = new AudioInjector(sound, *injectorOptions); AudioInjector* injector = new AudioInjector(sound, *injectorOptions);
QThread* injectorThread = new QThread(); QThread* injectorThread = new QThread();

View file

@ -30,7 +30,9 @@ InjectedAudioStream::InjectedAudioStream(const QUuid& streamIdentifier, const In
const uchar MAX_INJECTOR_VOLUME = 255; const uchar MAX_INJECTOR_VOLUME = 255;
int InjectedAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { int InjectedAudioStream::parseStreamProperties(PacketType type,
const QByteArray& packetAfterSeqNum,
int& numAudioSamples) {
// setup a data stream to read from this packet // setup a data stream to read from this packet
QDataStream packetStream(packetAfterSeqNum); QDataStream packetStream(packetAfterSeqNum);
@ -38,6 +40,9 @@ int InjectedAudioStream::parseStreamProperties(PacketType type, const QByteArray
packetStream.skipRawData(NUM_BYTES_RFC4122_UUID); packetStream.skipRawData(NUM_BYTES_RFC4122_UUID);
packetStream >> _isStereo; packetStream >> _isStereo;
if (isStereo()) {
_ringBuffer.resizeForFrameSize(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO);
}
// pull the loopback flag and set our boolean // pull the loopback flag and set our boolean
uchar shouldLoopback; uchar shouldLoopback;

View file

@ -31,7 +31,8 @@
// procedural audio version of Sound // procedural audio version of Sound
Sound::Sound(float volume, float frequency, float duration, float decay, QObject* parent) : Sound::Sound(float volume, float frequency, float duration, float decay, QObject* parent) :
QObject(parent) QObject(parent),
_isStereo(false)
{ {
static char monoAudioData[MAX_PACKET_SIZE]; static char monoAudioData[MAX_PACKET_SIZE];
static int16_t* monoAudioSamples = (int16_t*)(monoAudioData); static int16_t* monoAudioSamples = (int16_t*)(monoAudioData);
@ -69,8 +70,9 @@ Sound::Sound(float volume, float frequency, float duration, float decay, QObject
} }
} }
Sound::Sound(const QUrl& sampleURL, QObject* parent) : Sound::Sound(const QUrl& sampleURL, bool isStereo, QObject* parent) :
QObject(parent), QObject(parent),
_isStereo(isStereo),
_hasDownloaded(false) _hasDownloaded(false)
{ {
// assume we have a QApplication or QCoreApplication instance and use the // assume we have a QApplication or QCoreApplication instance and use the
@ -82,12 +84,14 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) :
QNetworkReply* soundDownload = networkAccessManager.get(QNetworkRequest(sampleURL)); QNetworkReply* soundDownload = networkAccessManager.get(QNetworkRequest(sampleURL));
connect(soundDownload, &QNetworkReply::finished, this, &Sound::replyFinished); connect(soundDownload, &QNetworkReply::finished, this, &Sound::replyFinished);
connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(replyError(QNetworkReply::NetworkError))); connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(replyError(QNetworkReply::NetworkError)));
} }
Sound::Sound(const QByteArray byteArray, QObject* parent) : Sound::Sound(const QByteArray byteArray, QObject* parent) :
QObject(parent), QObject(parent),
_byteArray(byteArray), _byteArray(byteArray),
_isStereo(false),
_hasDownloaded(true) _hasDownloaded(true)
{ {
} }
@ -149,11 +153,20 @@ void Sound::downSample(const QByteArray& rawAudioByteArray) {
int16_t* sourceSamples = (int16_t*) rawAudioByteArray.data(); int16_t* sourceSamples = (int16_t*) rawAudioByteArray.data();
int16_t* destinationSamples = (int16_t*) _byteArray.data(); int16_t* destinationSamples = (int16_t*) _byteArray.data();
for (int i = 1; i < numSourceSamples; i += 2) {
if (i + 1 >= numSourceSamples) { if (_isStereo) {
destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 2) + (sourceSamples[i] / 2); for (int i = 0; i < numSourceSamples; i += 4) {
} else { destinationSamples[i / 2] = (sourceSamples[i] / 2) + (sourceSamples[i + 2] / 2);
destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 4) + (sourceSamples[i] / 2) + (sourceSamples[i + 1] / 4); destinationSamples[(i / 2) + 1] = (sourceSamples[i + 1] / 2) + (sourceSamples[i + 3] / 2);
}
} else {
for (int i = 1; i < numSourceSamples; i += 2) {
if (i + 1 >= numSourceSamples) {
destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 2) + (sourceSamples[i] / 2);
} else {
destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 4) + (sourceSamples[i] / 2)
+ (sourceSamples[i + 1] / 4);
}
} }
} }
} }

View file

@ -20,17 +20,19 @@ class Sound : public QObject {
Q_PROPERTY(bool downloaded READ hasDownloaded) Q_PROPERTY(bool downloaded READ hasDownloaded)
public: public:
Sound(const QUrl& sampleURL, QObject* parent = NULL); Sound(const QUrl& sampleURL, bool isStereo = false, QObject* parent = NULL);
Sound(float volume, float frequency, float duration, float decay, QObject* parent = NULL); Sound(float volume, float frequency, float duration, float decay, QObject* parent = NULL);
Sound(const QByteArray byteArray, QObject* parent = NULL); Sound(const QByteArray byteArray, QObject* parent = NULL);
void append(const QByteArray byteArray); void append(const QByteArray byteArray);
bool isStereo() const { return _isStereo; }
bool hasDownloaded() const { return _hasDownloaded; } bool hasDownloaded() const { return _hasDownloaded; }
const QByteArray& getByteArray() { return _byteArray; } const QByteArray& getByteArray() { return _byteArray; }
private: private:
QByteArray _byteArray; QByteArray _byteArray;
bool _isStereo;
bool _hasDownloaded; bool _hasDownloaded;
void trimFrames(); void trimFrames();

View file

@ -587,18 +587,13 @@ bool AvatarData::hasReferential() {
} }
bool AvatarData::isPlaying() { bool AvatarData::isPlaying() {
if (!_player) {
return false;
}
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(this, "isPlaying", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, result));
return result;
}
return _player && _player->isPlaying(); return _player && _player->isPlaying();
} }
bool AvatarData::isPaused() {
return _player && _player->isPaused();
}
qint64 AvatarData::playerElapsed() { qint64 AvatarData::playerElapsed() {
if (!_player) { if (!_player) {
return 0; return 0;
@ -625,6 +620,14 @@ qint64 AvatarData::playerLength() {
return _player->getRecording()->getLength(); return _player->getRecording()->getLength();
} }
int AvatarData::playerCurrentFrame() {
return (_player) ? _player->getCurrentFrame() : 0;
}
int AvatarData::playerFrameNumber() {
return (_player && _player->getRecording()) ? _player->getRecording()->getFrameNumber() : 0;
}
void AvatarData::loadRecording(QString filename) { void AvatarData::loadRecording(QString filename) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection,
@ -649,6 +652,18 @@ void AvatarData::startPlaying() {
_player->startPlaying(); _player->startPlaying();
} }
void AvatarData::setPlayerFrame(int frame) {
if (_player) {
_player->setCurrentFrame(frame);
}
}
void AvatarData::setPlayerTime(qint64 time) {
if (_player) {
_player->setCurrentTime(time);
}
}
void AvatarData::setPlayFromCurrentLocation(bool playFromCurrentLocation) { void AvatarData::setPlayFromCurrentLocation(bool playFromCurrentLocation) {
if (_player) { if (_player) {
_player->setPlayFromCurrentLocation(playFromCurrentLocation); _player->setPlayFromCurrentLocation(playFromCurrentLocation);
@ -696,6 +711,19 @@ void AvatarData::play() {
} }
} }
void AvatarData::pausePlayer() {
if (!_player) {
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "pausePlayer", Qt::BlockingQueuedConnection);
return;
}
if (_player) {
_player->pausePlayer();
}
}
void AvatarData::stopPlaying() { void AvatarData::stopPlaying() {
if (!_player) { if (!_player) {
return; return;

View file

@ -296,10 +296,16 @@ public slots:
bool hasReferential(); bool hasReferential();
bool isPlaying(); bool isPlaying();
bool isPaused();
qint64 playerElapsed(); qint64 playerElapsed();
qint64 playerLength(); qint64 playerLength();
int playerCurrentFrame();
int playerFrameNumber();
void loadRecording(QString filename); void loadRecording(QString filename);
void startPlaying(); void startPlaying();
void setPlayerFrame(int frame);
void setPlayerTime(qint64 time);
void setPlayFromCurrentLocation(bool playFromCurrentLocation); void setPlayFromCurrentLocation(bool playFromCurrentLocation);
void setPlayerLoop(bool loop); void setPlayerLoop(bool loop);
void setPlayerUseDisplayName(bool useDisplayName); void setPlayerUseDisplayName(bool useDisplayName);
@ -307,6 +313,7 @@ public slots:
void setPlayerUseHeadModel(bool useHeadModel); void setPlayerUseHeadModel(bool useHeadModel);
void setPlayerUseSkeletonModel(bool useSkeletonModel); void setPlayerUseSkeletonModel(bool useSkeletonModel);
void play(); void play();
void pausePlayer();
void stopPlaying(); void stopPlaying();
protected: protected:

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <AudioRingBuffer.h>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <NodeList.h> #include <NodeList.h>
#include <StreamUtils.h> #include <StreamUtils.h>
@ -18,6 +19,8 @@
Player::Player(AvatarData* avatar) : Player::Player(AvatarData* avatar) :
_recording(new Recording()), _recording(new Recording()),
_pausedFrame(-1),
_timerOffset(0),
_avatar(avatar), _avatar(avatar),
_audioThread(NULL), _audioThread(NULL),
_playFromCurrentPosition(true), _playFromCurrentPosition(true),
@ -36,16 +39,26 @@ bool Player::isPlaying() const {
return _timer.isValid(); return _timer.isValid();
} }
bool Player::isPaused() const {
return (_pausedFrame != -1);
}
qint64 Player::elapsed() const { qint64 Player::elapsed() const {
if (isPlaying()) { if (isPlaying()) {
return _timer.elapsed(); return _timerOffset + _timer.elapsed();
} else if (isPaused()) {
return _timerOffset;
} else { } else {
return 0; return 0;
} }
} }
void Player::startPlaying() { void Player::startPlaying() {
if (_recording && _recording->getFrameNumber() > 0) { if (!_recording || _recording->getFrameNumber() <= 1) {
return;
}
if (!isPaused()) {
_currentContext.globalTimestamp = usecTimestampNow(); _currentContext.globalTimestamp = usecTimestampNow();
_currentContext.domain = NodeList::getInstance()->getDomainHandler().getHostname(); _currentContext.domain = NodeList::getInstance()->getDomainHandler().getHostname();
_currentContext.position = _avatar->getPosition(); _currentContext.position = _avatar->getPosition();
@ -97,9 +110,17 @@ void Player::startPlaying() {
_avatar->setForceFaceshiftConnected(true); _avatar->setForceFaceshiftConnected(true);
qDebug() << "Recorder::startPlaying()"; qDebug() << "Recorder::startPlaying()";
setupAudioThread();
_currentFrame = 0; _currentFrame = 0;
_timerOffset = 0;
_timer.start();
} else {
qDebug() << "Recorder::startPlaying(): Unpause";
setupAudioThread(); setupAudioThread();
_timer.start(); _timer.start();
setCurrentFrame(_pausedFrame);
_pausedFrame = -1;
} }
} }
@ -107,6 +128,7 @@ void Player::stopPlaying() {
if (!isPlaying()) { if (!isPlaying()) {
return; return;
} }
_pausedFrame = -1;
_timer.invalidate(); _timer.invalidate();
cleanupAudioThread(); cleanupAudioThread();
_avatar->clearJointsData(); _avatar->clearJointsData();
@ -130,6 +152,15 @@ void Player::stopPlaying() {
qDebug() << "Recorder::stopPlaying()"; qDebug() << "Recorder::stopPlaying()";
} }
void Player::pausePlayer() {
_timerOffset = elapsed();
_timer.invalidate();
cleanupAudioThread();
_pausedFrame = _currentFrame;
qDebug() << "Recorder::pausePlayer()";
}
void Player::setupAudioThread() { void Player::setupAudioThread() {
_audioThread = new QThread(); _audioThread = new QThread();
_options.setPosition(_avatar->getPosition()); _options.setPosition(_avatar->getPosition());
@ -156,6 +187,7 @@ void Player::loopRecording() {
cleanupAudioThread(); cleanupAudioThread();
setupAudioThread(); setupAudioThread();
_currentFrame = 0; _currentFrame = 0;
_timerOffset = 0;
_timer.restart(); _timer.restart();
} }
@ -166,10 +198,13 @@ void Player::loadFromFile(const QString& file) {
_recording = RecordingPointer(new Recording()); _recording = RecordingPointer(new Recording());
} }
readRecordingFromFile(_recording, file); readRecordingFromFile(_recording, file);
_pausedFrame = -1;
} }
void Player::loadRecording(RecordingPointer recording) { void Player::loadRecording(RecordingPointer recording) {
_recording = recording; _recording = recording;
_pausedFrame = -1;
} }
void Player::play() { void Player::play() {
@ -213,6 +248,77 @@ void Player::play() {
_injector->setOptions(_options); _injector->setOptions(_options);
} }
void Player::setCurrentFrame(int currentFrame) {
if (_recording && (currentFrame < 0 || currentFrame >= _recording->getFrameNumber())) {
stopPlaying();
return;
}
_currentFrame = currentFrame;
_timerOffset = _recording->getFrameTimestamp(_currentFrame);
if (isPlaying()) {
_timer.start();
setAudionInjectorPosition();
} else {
_pausedFrame = currentFrame;
}
}
void Player::setCurrentTime(qint64 currentTime) {
if (currentTime < 0 || currentTime >= _recording->getLength()) {
stopPlaying();
return;
}
// Find correct frame
int lowestBound = 0;
int highestBound = _recording->getFrameNumber() - 1;
while (lowestBound + 1 != highestBound) {
assert(lowestBound < highestBound);
int bestGuess = lowestBound +
(highestBound - lowestBound) *
(float)(currentTime - _recording->getFrameTimestamp(lowestBound)) /
(float)(_recording->getFrameTimestamp(highestBound) - _recording->getFrameTimestamp(lowestBound));
if (_recording->getFrameTimestamp(bestGuess) <= currentTime) {
if (currentTime < _recording->getFrameTimestamp(bestGuess + 1)) {
lowestBound = bestGuess;
highestBound = bestGuess + 1;
} else {
lowestBound = bestGuess + 1;
}
} else {
if (_recording->getFrameTimestamp(bestGuess - 1) <= currentTime) {
lowestBound = bestGuess - 1;
highestBound = bestGuess;
} else {
highestBound = bestGuess - 1;
}
}
}
_currentFrame = lowestBound;
_timerOffset = _recording->getFrameTimestamp(lowestBound);
if (isPlaying()) {
_timer.start();
setAudionInjectorPosition();
} else {
_pausedFrame = lowestBound;
}
}
void Player::setAudionInjectorPosition() {
int MSEC_PER_SEC = 1000;
int SAMPLE_SIZE = 2; // 16 bits
int CHANNEL_COUNT = 1;
int FRAME_SIZE = SAMPLE_SIZE * CHANNEL_COUNT;
int currentAudioFrame = elapsed() * FRAME_SIZE * (SAMPLE_RATE / MSEC_PER_SEC);
_injector->setCurrentSendPosition(currentAudioFrame);
}
void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) { void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) {
_playFromCurrentPosition = playFromCurrentLocation; _playFromCurrentPosition = playFromCurrentLocation;
} }
@ -227,7 +333,7 @@ bool Player::computeCurrentFrame() {
} }
while (_currentFrame < _recording->getFrameNumber() - 1 && while (_currentFrame < _recording->getFrameNumber() - 1 &&
_recording->getFrameTimestamp(_currentFrame) < _timer.elapsed()) { _recording->getFrameTimestamp(_currentFrame) < elapsed()) {
++_currentFrame; ++_currentFrame;
} }

View file

@ -30,17 +30,23 @@ public:
Player(AvatarData* avatar); Player(AvatarData* avatar);
bool isPlaying() const; bool isPlaying() const;
bool isPaused() const;
qint64 elapsed() const; qint64 elapsed() const;
RecordingPointer getRecording() const { return _recording; } RecordingPointer getRecording() const { return _recording; }
int getCurrentFrame() const { return _currentFrame; }
public slots: public slots:
void startPlaying(); void startPlaying();
void stopPlaying(); void stopPlaying();
void pausePlayer();
void loadFromFile(const QString& file); void loadFromFile(const QString& file);
void loadRecording(RecordingPointer recording); void loadRecording(RecordingPointer recording);
void play(); void play();
void setCurrentFrame(int currentFrame);
void setCurrentTime(qint64 currentTime);
void setPlayFromCurrentLocation(bool playFromCurrentPosition); void setPlayFromCurrentLocation(bool playFromCurrentPosition);
void setLoop(bool loop) { _loop = loop; } void setLoop(bool loop) { _loop = loop; }
void useAttachements(bool useAttachments) { _useAttachments = useAttachments; } void useAttachements(bool useAttachments) { _useAttachments = useAttachments; }
@ -52,11 +58,14 @@ private:
void setupAudioThread(); void setupAudioThread();
void cleanupAudioThread(); void cleanupAudioThread();
void loopRecording(); void loopRecording();
void setAudionInjectorPosition();
bool computeCurrentFrame(); bool computeCurrentFrame();
QElapsedTimer _timer; QElapsedTimer _timer;
RecordingPointer _recording; RecordingPointer _recording;
int _currentFrame; int _currentFrame;
int _pausedFrame;
qint64 _timerOffset;
QSharedPointer<AudioInjector> _injector; QSharedPointer<AudioInjector> _injector;
AudioInjectorOptions _options; AudioInjectorOptions _options;
@ -64,7 +73,6 @@ private:
AvatarData* _avatar; AvatarData* _avatar;
QThread* _audioThread; QThread* _audioThread;
RecordingContext _currentContext; RecordingContext _currentContext;
bool _playFromCurrentPosition; bool _playFromCurrentPosition;
bool _loop; bool _loop;
@ -72,7 +80,6 @@ private:
bool _useDisplayName; bool _useDisplayName;
bool _useHeadURL; bool _useHeadURL;
bool _useSkeletonURL; bool _useSkeletonURL;
}; };
#endif // hifi_Player_h #endif // hifi_Player_h

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <AudioRingBuffer.h>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NodeList.h> #include <NodeList.h>
@ -60,6 +61,9 @@ qint32 Recording::getFrameTimestamp(int i) const {
if (i >= _timestamps.size()) { if (i >= _timestamps.size()) {
return getLength(); return getLength();
} }
if (i < 0) {
return 0;
}
return _timestamps[i]; return _timestamps[i];
} }
@ -781,7 +785,6 @@ RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QStr
fileStream >> audioArray; fileStream >> audioArray;
// Cut down audio if necessary // Cut down audio if necessary
int SAMPLE_RATE = 48000; // 48 kHz
int SAMPLE_SIZE = 2; // 16 bits int SAMPLE_SIZE = 2; // 16 bits
int MSEC_PER_SEC = 1000; int MSEC_PER_SEC = 1000;
int audioLength = recording->getLength() * SAMPLE_SIZE * (SAMPLE_RATE / MSEC_PER_SEC); int audioLength = recording->getLength() * SAMPLE_SIZE * (SAMPLE_RATE / MSEC_PER_SEC);

View file

@ -13,8 +13,10 @@
#include <AbstractAudioInterface.h> #include <AbstractAudioInterface.h>
#include <VoxelTree.h> #include <VoxelTree.h>
#include <AvatarData.h> #include <AvatarData.h>
#include <CollisionInfo.h>
#include <HeadData.h> #include <HeadData.h>
#include <HandData.h> #include <HandData.h>
#include <SphereShape.h>
#include "EntityItem.h" #include "EntityItem.h"
#include "EntityCollisionSystem.h" #include "EntityCollisionSystem.h"
@ -103,11 +105,28 @@ void EntityCollisionSystem::updateCollisionWithVoxels(EntityItem* entity) {
} }
void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) { void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
glm::vec3 center = entityA->getPosition() * (float)(TREE_SCALE);
float radius = entityA->getRadius() * (float)(TREE_SCALE);
glm::vec3 penetration; glm::vec3 penetration;
EntityItem* entityB; EntityItem* entityB = NULL;
if (_entities->findSpherePenetration(center, radius, penetration, (void**)&entityB, Octree::NoLock)) {
const float MAX_COLLISIONS_PER_ENTITY = 32;
CollisionList collisions(MAX_COLLISIONS_PER_ENTITY);
bool shapeCollisionsAccurate = false;
bool shapeCollisions = _entities->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
collisions, Octree::NoLock, &shapeCollisionsAccurate);
if (shapeCollisions) {
for(int i = 0; i < collisions.size(); i++) {
CollisionInfo* collision = collisions[i];
penetration = collision->_penetration;
entityB = static_cast<EntityItem*>(collision->_extraData);
// TODO: how to handle multiple collisions?
break;
}
}
if (shapeCollisions) {
// NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B. // NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B.
glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE); glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE);

View file

@ -70,6 +70,8 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_angularVelocity = DEFAULT_ANGULAR_VELOCITY; _angularVelocity = DEFAULT_ANGULAR_VELOCITY;
_angularDamping = DEFAULT_ANGULAR_DAMPING; _angularDamping = DEFAULT_ANGULAR_DAMPING;
_visible = DEFAULT_VISIBLE; _visible = DEFAULT_VISIBLE;
recalculateCollisionShape();
} }
EntityItem::EntityItem(const EntityItemID& entityItemID) { EntityItem::EntityItem(const EntityItemID& entityItemID) {
@ -490,6 +492,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
recalculateCollisionShape();
} }
return bytesRead; return bytesRead;
} }
@ -675,7 +678,7 @@ void EntityItem::update(const quint64& updateTime) {
velocity = NO_VELOCITY; velocity = NO_VELOCITY;
} }
setPosition(position); setPosition(position); // this will automatically recalculate our collision shape
setVelocity(velocity); setVelocity(velocity);
if (wantDebug) { if (wantDebug) {
@ -749,7 +752,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc
} }
} }
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPositionInMeters); SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPositionInMeters); // this will call recalculate collision shape if needed
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setDimensionsInMeters); // NOTE: radius is obsolete SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setDimensionsInMeters); // NOTE: radius is obsolete
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation); SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(mass, setMass); SET_ENTITY_PROPERTY_FROM_PROPERTIES(mass, setMass);
@ -903,3 +906,11 @@ float EntityItem::getRadius() const {
return radius; return radius;
} }
void EntityItem::recalculateCollisionShape() {
AACube entityAACube = getMinimumAACube();
entityAACube.scale(TREE_SCALE); // scale to meters
_collisionShape.setTranslation(entityAACube.calcCenter());
_collisionShape.setScale(entityAACube.getScale());
}

View file

@ -16,6 +16,7 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <AACubeShape.h>
#include <AnimationCache.h> // for Animation, AnimationCache, and AnimationPointer classes #include <AnimationCache.h> // for Animation, AnimationCache, and AnimationPointer classes
#include <Octree.h> // for EncodeBitstreamParams class #include <Octree.h> // for EncodeBitstreamParams class
#include <OctreeElement.h> // for OctreeElement::AppendState #include <OctreeElement.h> // for OctreeElement::AppendState
@ -123,7 +124,9 @@ public:
EntityTypes::EntityType getType() const { return _type; } EntityTypes::EntityType getType() const { return _type; }
const glm::vec3& getPosition() const { return _position; } /// get position in domain scale units (0.0 - 1.0) const glm::vec3& getPosition() const { return _position; } /// get position in domain scale units (0.0 - 1.0)
glm::vec3 getPositionInMeters() const { return _position * (float) TREE_SCALE; } /// get position in meters glm::vec3 getPositionInMeters() const { return _position * (float) TREE_SCALE; } /// get position in meters
void setPosition(const glm::vec3& value) { _position = value; } /// set position in domain scale units (0.0 - 1.0)
/// set position in domain scale units (0.0 - 1.0)
void setPosition(const glm::vec3& value) { _position = value; recalculateCollisionShape(); }
void setPositionInMeters(const glm::vec3& value) /// set position in meter units (0.0 - TREE_SCALE) void setPositionInMeters(const glm::vec3& value) /// set position in meter units (0.0 - TREE_SCALE)
{ setPosition(glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f)); } { setPosition(glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f)); }
@ -137,14 +140,14 @@ public:
float getLargestDimension() const { return glm::length(_dimensions); } /// get the largest possible dimension float getLargestDimension() const { return glm::length(_dimensions); } /// get the largest possible dimension
/// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately
void setDimensions(const glm::vec3& value) { _dimensions = value; } void setDimensions(const glm::vec3& value) { _dimensions = value; ; recalculateCollisionShape(); }
/// set dimensions in meter units (0.0 - TREE_SCALE) this will also reset radius appropriately /// set dimensions in meter units (0.0 - TREE_SCALE) this will also reset radius appropriately
void setDimensionsInMeters(const glm::vec3& value) { setDimensions(value / (float) TREE_SCALE); } void setDimensionsInMeters(const glm::vec3& value) { setDimensions(value / (float) TREE_SCALE); }
static const glm::quat DEFAULT_ROTATION; static const glm::quat DEFAULT_ROTATION;
const glm::quat& getRotation() const { return _rotation; } const glm::quat& getRotation() const { return _rotation; }
void setRotation(const glm::quat& rotation) { _rotation = rotation; } void setRotation(const glm::quat& rotation) { _rotation = rotation; ; recalculateCollisionShape(); }
static const float DEFAULT_GLOW_LEVEL; static const float DEFAULT_GLOW_LEVEL;
float getGlowLevel() const { return _glowLevel; } float getGlowLevel() const { return _glowLevel; }
@ -207,7 +210,10 @@ public:
static const glm::vec3 DEFAULT_REGISTRATION_POINT; static const glm::vec3 DEFAULT_REGISTRATION_POINT;
const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity
void setRegistrationPoint(const glm::vec3& value) { _registrationPoint = glm::clamp(value, 0.0f, 1.0f); } /// registration point as ratio of entity
/// registration point as ratio of entity
void setRegistrationPoint(const glm::vec3& value)
{ _registrationPoint = glm::clamp(value, 0.0f, 1.0f); recalculateCollisionShape(); }
static const glm::vec3 NO_ANGULAR_VELOCITY; static const glm::vec3 NO_ANGULAR_VELOCITY;
static const glm::vec3 DEFAULT_ANGULAR_VELOCITY; static const glm::vec3 DEFAULT_ANGULAR_VELOCITY;
@ -229,9 +235,11 @@ public:
float getRadius() const; float getRadius() const;
void applyHardCollision(const CollisionInfo& collisionInfo); void applyHardCollision(const CollisionInfo& collisionInfo);
virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; }
protected: protected:
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init
virtual void recalculateCollisionShape();
EntityTypes::EntityType _type; EntityTypes::EntityType _type;
QUuid _id; QUuid _id;
@ -264,6 +272,7 @@ protected:
/// set radius in domain scale units (0.0 - 1.0) this will also reset dimensions to be equal for each axis /// set radius in domain scale units (0.0 - 1.0) this will also reset dimensions to be equal for each axis
void setRadius(float value); void setRadius(float value);
AACubeShape _collisionShape;
}; };

View file

@ -134,6 +134,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(velocity); COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(velocity);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(gravity); COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(gravity);
COPY_PROPERTY_TO_QSCRIPTVALUE(damping); COPY_PROPERTY_TO_QSCRIPTVALUE(damping);
COPY_PROPERTY_TO_QSCRIPTVALUE(mass);
COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime); COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(age, getAge()); // gettable, but not settable COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(age, getAge()); // gettable, but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable

View file

@ -11,6 +11,9 @@
#include <glm/gtx/transform.hpp> #include <glm/gtx/transform.hpp>
#include <AACubeShape.h>
#include <ShapeCollider.h>
#include <FBXReader.h> #include <FBXReader.h>
#include <GeometryUtil.h> #include <GeometryUtil.h>
@ -546,6 +549,25 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad
return false; return false;
} }
bool EntityTreeElement::findShapeCollisions(const Shape* shape, CollisionList& collisions) const {
bool atLeastOneCollision = false;
QList<EntityItem*>::iterator entityItr = _entityItems->begin();
QList<EntityItem*>::const_iterator entityEnd = _entityItems->end();
while(entityItr != entityEnd) {
EntityItem* entity = (*entityItr);
const Shape* otherCollisionShape = &entity->getCollisionShapeInMeters();
if (shape != otherCollisionShape) {
if (ShapeCollider::collideShapes(shape, otherCollisionShape, collisions)) {
CollisionInfo* lastCollision = collisions.getLastCollision();
lastCollision->_extraData = entity;
atLeastOneCollision = true;
}
}
++entityItr;
}
return atLeastOneCollision;
}
void EntityTreeElement::updateEntityItemID(const EntityItemID& creatorTokenEntityID, const EntityItemID& knownIDEntityID) { void EntityTreeElement::updateEntityItemID(const EntityItemID& creatorTokenEntityID, const EntityItemID& knownIDEntityID) {
uint16_t numberOfEntities = _entityItems->size(); uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) { for (uint16_t i = 0; i < numberOfEntities; i++) {

View file

@ -142,6 +142,8 @@ public:
virtual bool findSpherePenetration(const glm::vec3& center, float radius, virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const; glm::vec3& penetration, void** penetratedObject) const;
virtual bool findShapeCollisions(const Shape* shape, CollisionList& collisions) const;
const QList<EntityItem*>& getEntities() const { return *_entityItems; } const QList<EntityItem*>& getEntities() const { return *_entityItems; }
QList<EntityItem*>& getEntities() { return *_entityItems; } QList<EntityItem*>& getEntities() { return *_entityItems; }
bool hasEntities() const { return _entityItems ? _entityItems->size() > 0 : false; } bool hasEntities() const { return _entityItems ? _entityItems->size() > 0 : false; }

View file

@ -88,4 +88,11 @@ void SphereEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBi
bool successPropertyFits = true; bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor()); APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
} }
void SphereEntityItem::recalculateCollisionShape() {
_sphereShape.setTranslation(getCenterInMeters());
glm::vec3 dimensionsInMeters = getDimensionsInMeters();
float largestDiameter = glm::max(dimensionsInMeters.x, dimensionsInMeters.y, dimensionsInMeters.z);
_sphereShape.setRadius(largestDiameter / 2.0f);
}

View file

@ -12,7 +12,8 @@
#ifndef hifi_SphereEntityItem_h #ifndef hifi_SphereEntityItem_h
#define hifi_SphereEntityItem_h #define hifi_SphereEntityItem_h
#include "EntityItem.h" #include <SphereShape.h>
#include "EntityItem.h"
class SphereEntityItem : public EntityItem { class SphereEntityItem : public EntityItem {
public: public:
@ -49,9 +50,14 @@ public:
_color[GREEN_INDEX] = value.green; _color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue; _color[BLUE_INDEX] = value.blue;
} }
virtual const Shape& getCollisionShapeInMeters() const { return _sphereShape; }
protected: protected:
virtual void recalculateCollisionShape();
rgbColor _color; rgbColor _color;
SphereShape _sphereShape;
}; };
#endif // hifi_SphereEntityItem_h #endif // hifi_SphereEntityItem_h

View file

@ -667,8 +667,9 @@ int VoxelMaterialBoxEditVisitor::visit(MetavoxelInfo& info) {
return DEFAULT_ORDER; return DEFAULT_ORDER;
} }
VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue<VoxelColorDataPointer>(); VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue<VoxelColorDataPointer>();
QVector<QRgb> colorContents = (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) ? QVector<QRgb> oldColorContents = (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) ?
colorPointer->getContents() : QVector<QRgb>(VOXEL_BLOCK_VOLUME); colorPointer->getContents() : QVector<QRgb>(VOXEL_BLOCK_VOLUME);
QVector<QRgb> colorContents = oldColorContents;
Box overlap = info.getBounds().getIntersection(_region); Box overlap = info.getBounds().getIntersection(_region);
float scale = VOXEL_BLOCK_SIZE / info.size; float scale = VOXEL_BLOCK_SIZE / info.size;
@ -725,32 +726,92 @@ int VoxelMaterialBoxEditVisitor::visit(MetavoxelInfo& info) {
for (int x = hermiteMinX, hermiteMaxX = x + hermiteSizeX - 1; x <= hermiteMaxX; x++, for (int x = hermiteMinX, hermiteMaxX = x + hermiteSizeX - 1; x <= hermiteMaxX; x++,
hermiteDestX += VoxelHermiteData::EDGE_COUNT) { hermiteDestX += VoxelHermiteData::EDGE_COUNT) {
// internal edges are set to zero; border edges (when non-terminal) are set to the intersection values // internal edges are set to zero; border edges (when non-terminal) are set to the intersection values
hermiteDestX[0] = 0x0;
if ((x == hermiteMinX || x == hermiteMaxX) && x != VOXEL_BLOCK_SIZE) { if ((x == hermiteMinX || x == hermiteMaxX) && x != VOXEL_BLOCK_SIZE) {
const QRgb* color = colorContents.constData() + z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]); int alpha0 = qAlpha(color[0]);
if (alpha0 != qAlpha(color[1])) { int alpha1 = qAlpha(color[1]);
hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0, if (alpha0 != alpha1) {
((x == hermiteMinX ? overlap.minimum.x : overlap.maximum.x) - x) * EIGHT_BIT_MAXIMUM); const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[1]) == alpha1) {
if (x == hermiteMinX) {
int alpha = (overlap.minimum.x - x) * EIGHT_BIT_MAXIMUM;
if (alpha <= qAlpha(hermiteDestX[0])) {
hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0, alpha);
}
} else {
int alpha = (overlap.maximum.x - x) * EIGHT_BIT_MAXIMUM;
if (alpha >= qAlpha(hermiteDestX[0])) {
hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0, alpha);
}
}
} else {
hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0,
((x == hermiteMinX ? overlap.minimum.x : overlap.maximum.x) - x) * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[0] = 0x0;
} }
} else {
hermiteDestX[0] = 0x0;
} }
hermiteDestX[1] = 0x0;
if ((y == hermiteMinY || y == hermiteMaxY) && y != VOXEL_BLOCK_SIZE) { if ((y == hermiteMinY || y == hermiteMaxY) && y != VOXEL_BLOCK_SIZE) {
const QRgb* color = colorContents.constData() + z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]); int alpha0 = qAlpha(color[0]);
if (alpha0 != qAlpha(color[VOXEL_BLOCK_SAMPLES])) { int alpha2 = qAlpha(color[VOXEL_BLOCK_SAMPLES]);
hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, if (alpha0 != alpha2) {
((y == hermiteMinY ? overlap.minimum.y : overlap.maximum.y) - y) * EIGHT_BIT_MAXIMUM); const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_SAMPLES]) == alpha2) {
if (y == hermiteMinY) {
int alpha = (overlap.minimum.y - y) * EIGHT_BIT_MAXIMUM;
if (alpha <= qAlpha(hermiteDestX[1])) {
hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, alpha);
}
} else {
int alpha = (overlap.maximum.y - y) * EIGHT_BIT_MAXIMUM;
if (alpha >= qAlpha(hermiteDestX[1])) {
hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, alpha);
}
}
} else {
hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0,
((y == hermiteMinY ? overlap.minimum.y : overlap.maximum.y) - y) * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[1] = 0x0;
} }
} else {
hermiteDestX[1] = 0x0;
} }
hermiteDestX[2] = 0x0;
if ((z == hermiteMinZ || z == hermiteMaxZ) && z != VOXEL_BLOCK_SIZE) { if ((z == hermiteMinZ || z == hermiteMaxZ) && z != VOXEL_BLOCK_SIZE) {
const QRgb* color = colorContents.constData() + z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]); int alpha0 = qAlpha(color[0]);
if (alpha0 != qAlpha(color[VOXEL_BLOCK_AREA])) { int alpha4 = qAlpha(color[VOXEL_BLOCK_AREA]);
hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, if (alpha0 != alpha4) {
((z == hermiteMinZ ? overlap.minimum.z : overlap.maximum.z) - z) * EIGHT_BIT_MAXIMUM); const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_AREA]) == alpha4) {
if (z == hermiteMinZ) {
int alpha = (overlap.minimum.z - z) * EIGHT_BIT_MAXIMUM;
if (alpha <= qAlpha(hermiteDestX[2])) {
hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, alpha);
}
} else {
int alpha = (overlap.maximum.z - z) * EIGHT_BIT_MAXIMUM;
if (alpha >= qAlpha(hermiteDestX[2])) {
hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, alpha);
}
}
} else {
hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX,
((z == hermiteMinZ ? overlap.minimum.z : overlap.maximum.z) - z) * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[2] = 0x0;
} }
} else {
hermiteDestX[2] = 0x0;
} }
} }
} }
@ -864,8 +925,9 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) {
return DEFAULT_ORDER; return DEFAULT_ORDER;
} }
VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue<VoxelColorDataPointer>(); VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue<VoxelColorDataPointer>();
QVector<QRgb> colorContents = (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) ? QVector<QRgb> oldColorContents = (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) ?
colorPointer->getContents() : QVector<QRgb>(VOXEL_BLOCK_VOLUME); colorPointer->getContents() : QVector<QRgb>(VOXEL_BLOCK_VOLUME);
QVector<QRgb> colorContents = oldColorContents;
Box overlap = info.getBounds().getIntersection(_bounds); Box overlap = info.getBounds().getIntersection(_bounds);
float scale = VOXEL_BLOCK_SIZE / info.size; float scale = VOXEL_BLOCK_SIZE / info.size;
@ -883,6 +945,7 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) {
float relativeRadiusSquared = relativeRadius * relativeRadius; float relativeRadiusSquared = relativeRadius * relativeRadius;
QRgb rgb = _color.rgba(); QRgb rgb = _color.rgba();
bool flipped = (qAlpha(rgb) == 0);
glm::vec3 position(0.0f, 0.0f, minZ); glm::vec3 position(0.0f, 0.0f, minZ);
for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z++) { *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z++) {
@ -932,78 +995,117 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) {
hermiteDestX += VoxelHermiteData::EDGE_COUNT) { hermiteDestX += VoxelHermiteData::EDGE_COUNT) {
// at each intersected non-terminal edge, we check for a transition and, if one is detected, we assign the // at each intersected non-terminal edge, we check for a transition and, if one is detected, we assign the
// crossing and normal values based on intersection with the sphere // crossing and normal values based on intersection with the sphere
hermiteDestX[0] = 0x0; glm::vec3 vector(x - relativeCenter.x, y - relativeCenter.y, z - relativeCenter.z);
glm::vec3 offset(x - relativeCenter.x, y - relativeCenter.y, z - relativeCenter.z);
if (x != VOXEL_BLOCK_SIZE) { if (x != VOXEL_BLOCK_SIZE) {
const QRgb* color = colorContents.constData() + z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]); int alpha0 = qAlpha(color[0]);
if (alpha0 != qAlpha(color[1])) { int alpha1 = qAlpha(color[1]);
float radicand = relativeRadiusSquared - offset.y * offset.y - offset.z * offset.z; if (alpha0 != alpha1) {
float radicand = relativeRadiusSquared - vector.y * vector.y - vector.z * vector.z;
float parameter = 0.5f; float parameter = 0.5f;
if (radicand >= 0.0f) { if (radicand >= 0.0f) {
float root = glm::sqrt(radicand); float root = glm::sqrt(radicand);
parameter = -offset.x - root; parameter = -vector.x - root;
if (parameter < 0.0f || parameter > 1.0f) { if (parameter < 0.0f || parameter > 1.0f) {
parameter = glm::clamp(-offset.x + root, 0.0f, 1.0f); parameter = glm::clamp(-vector.x + root, 0.0f, 1.0f);
} }
} }
glm::vec3 normal = offset + glm::vec3(parameter, 0.0f, 0.0f); glm::vec3 normal = vector + glm::vec3(parameter, 0.0f, 0.0f);
float length = glm::length(normal); float length = glm::length(normal);
if (length > EPSILON) { if (length > EPSILON) {
normal /= length; normal /= length;
} else { } else {
normal = glm::vec3(0.0f, 1.0f, 0.0f); normal = glm::vec3(0.0f, 1.0f, 0.0f);
} }
hermiteDestX[0] = packNormal(normal, parameter * EIGHT_BIT_MAXIMUM); const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[1]) == alpha1) {
int alpha = parameter * EIGHT_BIT_MAXIMUM;
if (normal.x < 0.0f ? alpha <= qAlpha(hermiteDestX[0]) : alpha >= qAlpha(hermiteDestX[0])) {
hermiteDestX[0] = packNormal(flipped ? -normal : normal, alpha);
}
} else {
hermiteDestX[0] = packNormal(flipped ? -normal : normal, parameter * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[0] = 0x0;
} }
} else {
hermiteDestX[0] = 0x0;
} }
hermiteDestX[1] = 0x0;
if (y != VOXEL_BLOCK_SIZE) { if (y != VOXEL_BLOCK_SIZE) {
const QRgb* color = colorContents.constData() + z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]); int alpha0 = qAlpha(color[0]);
if (alpha0 != qAlpha(color[VOXEL_BLOCK_SAMPLES])) { int alpha2 = qAlpha(color[VOXEL_BLOCK_SAMPLES]);
float radicand = relativeRadiusSquared - offset.x * offset.x - offset.z * offset.z; if (alpha0 != alpha2) {
float radicand = relativeRadiusSquared - vector.x * vector.x - vector.z * vector.z;
float parameter = 0.5f; float parameter = 0.5f;
if (radicand >= 0.0f) { if (radicand >= 0.0f) {
float root = glm::sqrt(radicand); float root = glm::sqrt(radicand);
parameter = -offset.y - root; parameter = -vector.y - root;
if (parameter < 0.0f || parameter > 1.0f) { if (parameter < 0.0f || parameter > 1.0f) {
parameter = glm::clamp(-offset.y + root, 0.0f, 1.0f); parameter = glm::clamp(-vector.y + root, 0.0f, 1.0f);
} }
} }
glm::vec3 normal = offset + glm::vec3(parameter, 0.0f, 0.0f); glm::vec3 normal = vector + glm::vec3(parameter, 0.0f, 0.0f);
float length = glm::length(normal); float length = glm::length(normal);
if (length > EPSILON) { if (length > EPSILON) {
normal /= length; normal /= length;
} else { } else {
normal = glm::vec3(1.0f, 0.0f, 0.0f); normal = glm::vec3(1.0f, 0.0f, 0.0f);
} }
hermiteDestX[1] = packNormal(normal, parameter * EIGHT_BIT_MAXIMUM); const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_SAMPLES]) == alpha2) {
int alpha = parameter * EIGHT_BIT_MAXIMUM;
if (normal.y < 0.0f ? alpha <= qAlpha(hermiteDestX[1]) : alpha >= qAlpha(hermiteDestX[1])) {
hermiteDestX[1] = packNormal(flipped ? -normal : normal, alpha);
}
} else {
hermiteDestX[1] = packNormal(flipped ? -normal : normal, parameter * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[1] = 0x0;
} }
} else {
hermiteDestX[1] = 0x0;
} }
hermiteDestX[2] = 0x0;
if (z != VOXEL_BLOCK_SIZE) { if (z != VOXEL_BLOCK_SIZE) {
const QRgb* color = colorContents.constData() + z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]); int alpha0 = qAlpha(color[0]);
if (alpha0 != qAlpha(color[VOXEL_BLOCK_AREA])) { int alpha4 = qAlpha(color[VOXEL_BLOCK_AREA]);
float radicand = relativeRadiusSquared - offset.x * offset.x - offset.y * offset.y; if (alpha0 != alpha4) {
float radicand = relativeRadiusSquared - vector.x * vector.x - vector.y * vector.y;
float parameter = 0.5f; float parameter = 0.5f;
if (radicand >= 0.0f) { if (radicand >= 0.0f) {
float root = glm::sqrt(radicand); float root = glm::sqrt(radicand);
parameter = -offset.z - root; parameter = -vector.z - root;
if (parameter < 0.0f || parameter > 1.0f) { if (parameter < 0.0f || parameter > 1.0f) {
parameter = glm::clamp(-offset.z + root, 0.0f, 1.0f); parameter = glm::clamp(-vector.z + root, 0.0f, 1.0f);
} }
} }
glm::vec3 normal = offset + glm::vec3(parameter, 0.0f, 0.0f); glm::vec3 normal = vector + glm::vec3(parameter, 0.0f, 0.0f);
float length = glm::length(normal); float length = glm::length(normal);
if (length > EPSILON) { if (length > EPSILON) {
normal /= length; normal /= length;
} else { } else {
normal = glm::vec3(1.0f, 0.0f, 0.0f); normal = glm::vec3(1.0f, 0.0f, 0.0f);
} }
hermiteDestX[2] = packNormal(normal, parameter * EIGHT_BIT_MAXIMUM); const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_AREA]) == alpha4) {
int alpha = parameter * EIGHT_BIT_MAXIMUM;
if (normal.z < 0.0f ? alpha <= qAlpha(hermiteDestX[2]) : alpha >= qAlpha(hermiteDestX[2])) {
hermiteDestX[2] = packNormal(flipped ? -normal : normal, alpha);
}
} else {
hermiteDestX[2] = packNormal(flipped ? -normal : normal, parameter * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[2] = 0x0;
} }
} else {
hermiteDestX[2] = 0x0;
} }
} }
} }

View file

@ -817,9 +817,6 @@ bool findCapsulePenetrationOp(OctreeElement* element, void* extraData) {
if (!box.expandedIntersectsSegment(args->start, args->end, args->radius)) { if (!box.expandedIntersectsSegment(args->start, args->end, args->radius)) {
return false; return false;
} }
if (!element->isLeaf()) {
return true; // recurse on children
}
if (element->hasContent()) { if (element->hasContent()) {
glm::vec3 nodePenetration; glm::vec3 nodePenetration;
if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) { if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) {
@ -827,27 +824,29 @@ bool findCapsulePenetrationOp(OctreeElement* element, void* extraData) {
args->found = true; args->found = true;
} }
} }
if (!element->isLeaf()) {
return true; // recurse on children
}
return false; return false;
} }
bool findShapeCollisionsOp(OctreeElement* element, void* extraData) { bool findShapeCollisionsOp(OctreeElement* element, void* extraData) {
ShapeArgs* args = static_cast<ShapeArgs*>(extraData); ShapeArgs* args = static_cast<ShapeArgs*>(extraData);
// coarse check against bounds // coarse check against bounds
AACube cube = element->getAACube(); AACube cube = element->getAACube();
cube.scale(TREE_SCALE); cube.scale(TREE_SCALE);
if (!cube.expandedContains(args->shape->getTranslation(), args->shape->getBoundingRadius())) { if (!cube.expandedContains(args->shape->getTranslation(), args->shape->getBoundingRadius())) {
return false; return false;
} }
if (!element->isLeaf()) {
return true; // recurse on children
}
if (element->hasContent()) { if (element->hasContent()) {
if (ShapeCollider::collideShapeWithAACubeLegacy(args->shape, cube.calcCenter(), cube.getScale(), args->collisions)) { if (element->findShapeCollisions(args->shape, args->collisions)) {
args->found = true; args->found = true;
return true; return true;
} }
} }
if (!element->isLeaf()) {
return true; // recurse on children
}
return false; return false;
} }

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <assert.h>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <stdio.h> #include <stdio.h>
@ -17,7 +18,8 @@
#include <NodeList.h> #include <NodeList.h>
#include <PerfStat.h> #include <PerfStat.h>
#include <assert.h> #include <AACubeShape.h>
#include <ShapeCollider.h>
#include "AACube.h" #include "AACube.h"
#include "OctalCode.h" #include "OctalCode.h"
@ -1369,6 +1371,11 @@ bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius,
return _cube.findSpherePenetration(center, radius, penetration); return _cube.findSpherePenetration(center, radius, penetration);
} }
bool OctreeElement::findShapeCollisions(const Shape* shape, CollisionList& collisions) const {
AACube cube = getAACube();
cube.scale(TREE_SCALE);
return ShapeCollider::collideShapeWithAACubeLegacy(shape, cube.calcCenter(), cube.getScale(), collisions);
}
// TODO: consider removing this, or switching to using getOrCreateChildElementContaining(const AACube& box)... // TODO: consider removing this, or switching to using getOrCreateChildElementContaining(const AACube& box)...
OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float z, float s) { OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float z, float s) {

View file

@ -25,6 +25,7 @@
#include "ViewFrustum.h" #include "ViewFrustum.h"
#include "OctreeConstants.h" #include "OctreeConstants.h"
class CollisionList;
class EncodeBitstreamParams; class EncodeBitstreamParams;
class Octree; class Octree;
class OctreeElement; class OctreeElement;
@ -32,6 +33,7 @@ class OctreeElementBag;
class OctreeElementDeleteHook; class OctreeElementDeleteHook;
class OctreePacketData; class OctreePacketData;
class ReadBitstreamToTreeParams; class ReadBitstreamToTreeParams;
class Shape;
class VoxelSystem; class VoxelSystem;
const float SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE = (1.0f / TREE_SCALE) / 10000.0f; // 1/10,000th of a meter const float SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE = (1.0f / TREE_SCALE) / 10000.0f; // 1/10,000th of a meter
@ -128,6 +130,8 @@ public:
virtual bool findSpherePenetration(const glm::vec3& center, float radius, virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const; glm::vec3& penetration, void** penetratedObject) const;
virtual bool findShapeCollisions(const Shape* shape, CollisionList& collisions) const;
// Base class methods you don't need to implement // Base class methods you don't need to implement
const unsigned char* getOctalCode() const { return (_octcodePointer) ? _octalCode.pointer : &_octalCode.buffer[0]; } const unsigned char* getOctalCode() const { return (_octcodePointer) ? _octalCode.pointer : &_octalCode.buffer[0]; }
OctreeElement* getChildAtIndex(int childIndex) const; OctreeElement* getChildAtIndex(int childIndex) const;

View file

@ -48,7 +48,8 @@ EntityScriptingInterface ScriptEngine::_entityScriptingInterface;
static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* engine) { static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* engine) {
QUrl soundURL = QUrl(context->argument(0).toString()); QUrl soundURL = QUrl(context->argument(0).toString());
QScriptValue soundScriptValue = engine->newQObject(new Sound(soundURL), QScriptEngine::ScriptOwnership); bool isStereo = context->argument(1).toBool();
QScriptValue soundScriptValue = engine->newQObject(new Sound(soundURL, isStereo), QScriptEngine::ScriptOwnership);
return soundScriptValue; return soundScriptValue;
} }

View file

@ -12,6 +12,7 @@
#ifndef hifi_AACubeShape_h #ifndef hifi_AACubeShape_h
#define hifi_AACubeShape_h #define hifi_AACubeShape_h
#include <QDebug>
#include "Shape.h" #include "Shape.h"
class AACubeShape : public Shape { class AACubeShape : public Shape {
@ -28,9 +29,22 @@ public:
bool findRayIntersection(RayIntersectionInfo& intersection) const; bool findRayIntersection(RayIntersectionInfo& intersection) const;
float getVolume() const { return _scale * _scale * _scale; } float getVolume() const { return _scale * _scale * _scale; }
virtual QDebug& dumpToDebug(QDebug& debugConext) const;
protected: protected:
float _scale; float _scale;
}; };
inline QDebug& AACubeShape::dumpToDebug(QDebug& debugConext) const {
debugConext << "AACubeShape[ ("
<< "type: " << getType()
<< "position: "
<< getTranslation().x << ", " << getTranslation().y << ", " << getTranslation().z
<< "scale: "
<< getScale()
<< "]";
return debugConext;
}
#endif // hifi_AACubeShape_h #endif // hifi_AACubeShape_h

View file

@ -54,6 +54,8 @@ public:
const Shape* _shapeA; // pointer to shapeA in this collision const Shape* _shapeA; // pointer to shapeA in this collision
const Shape* _shapeB; // pointer to shapeB in this collision const Shape* _shapeB; // pointer to shapeB in this collision
void* _extraData; // pointer to extraData for this collision, opaque to the collision info, useful for external data
float _damping; // range [0,1] of friction coeficient float _damping; // range [0,1] of friction coeficient
float _elasticity; // range [0,1] of energy conservation float _elasticity; // range [0,1] of energy conservation
glm::vec3 _contactPoint; // world-frame point on BodyA that is deepest into BodyB glm::vec3 _contactPoint; // world-frame point on BodyA that is deepest into BodyB

View file

@ -14,6 +14,7 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include <QDebug>
#include <QtGlobal> #include <QtGlobal>
#include <QVector> #include <QVector>
@ -80,6 +81,8 @@ public:
virtual float getVolume() const { return 1.0; } virtual float getVolume() const { return 1.0; }
virtual void getVerletPoints(QVector<VerletPoint*>& points) {} virtual void getVerletPoints(QVector<VerletPoint*>& points) {}
virtual QDebug& dumpToDebug(QDebug& debugConext) const;
protected: protected:
// these ctors are protected (used by derived classes only) // these ctors are protected (used by derived classes only)
@ -113,4 +116,25 @@ protected:
float _mass; float _mass;
}; };
inline QDebug& Shape::dumpToDebug(QDebug& debugConext) const {
debugConext << "Shape[ ("
<< "type: " << getType()
<< "position: "
<< getTranslation().x << ", " << getTranslation().y << ", " << getTranslation().z
<< "radius: "
<< getBoundingRadius()
<< "]";
return debugConext;
}
inline QDebug operator<<(QDebug debug, const Shape& shape) {
return shape.dumpToDebug(debug);
}
inline QDebug operator<<(QDebug debug, const Shape* shape) {
return shape->dumpToDebug(debug);
}
#endif // hifi_Shape_h #endif // hifi_Shape_h

View file

@ -20,6 +20,8 @@
#include "PlaneShape.h" #include "PlaneShape.h"
#include "SphereShape.h" #include "SphereShape.h"
#include "StreamUtils.h"
// NOTE: // NOTE:
// //
// * Large ListShape's are inefficient keep the lists short. // * Large ListShape's are inefficient keep the lists short.
@ -978,7 +980,112 @@ bool aaCubeVsCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& co
return capsuleVsAACube(shapeB, shapeA, collisions); return capsuleVsAACube(shapeB, shapeA, collisions);
} }
// helper function
CollisionInfo* aaCubeVsAACubeHelper(const glm::vec3& cubeCenterA, float cubeSideA, const glm::vec3& cubeCenterB,
float cubeSideB, CollisionList& collisions) {
// cube is A
// cube is B
// BA = B - A = from center of A to center of B
float halfCubeSideA = 0.5f * cubeSideA;
float halfCubeSideB = 0.5f * cubeSideB;
glm::vec3 BA = cubeCenterB - cubeCenterA;
float distance = glm::length(BA);
if (distance > EPSILON) {
float maxBA = glm::max(glm::max(glm::abs(BA.x), glm::abs(BA.y)), glm::abs(BA.z));
if (maxBA > halfCubeSideB + halfCubeSideA) {
// cube misses cube entirely
return NULL;
}
CollisionInfo* collision = collisions.getNewCollision();
if (!collision) {
return NULL; // no more room for collisions
}
if (maxBA > halfCubeSideB) {
// cube hits cube but its center is outside cube
// compute contact anti-pole on cube (in cube frame)
glm::vec3 cubeContact = glm::abs(BA);
if (cubeContact.x > halfCubeSideB) {
cubeContact.x = halfCubeSideB;
}
if (cubeContact.y > halfCubeSideB) {
cubeContact.y = halfCubeSideB;
}
if (cubeContact.z > halfCubeSideB) {
cubeContact.z = halfCubeSideB;
}
glm::vec3 signs = glm::sign(BA);
cubeContact.x *= signs.x;
cubeContact.y *= signs.y;
cubeContact.z *= signs.z;
// compute penetration direction
glm::vec3 direction = BA - cubeContact;
float lengthDirection = glm::length(direction);
if (lengthDirection < EPSILON) {
// cubeCenterA is touching cube B surface, so we can't use the difference between those two
// points to compute the penetration direction. Instead we use the unitary components of
// cubeContact.
glm::modf(cubeContact / halfCubeSideB, direction);
lengthDirection = glm::length(direction);
} else if (lengthDirection > halfCubeSideA) {
collisions.deleteLastCollision();
return NULL;
}
direction /= lengthDirection;
// compute collision details
collision->_contactPoint = cubeCenterA + halfCubeSideA * direction;
collision->_penetration = halfCubeSideA * direction - (BA - cubeContact);
} else {
// cube center is inside cube
// --> push out nearest face
glm::vec3 direction;
BA /= maxBA;
glm::modf(BA, direction);
float lengthDirection = glm::length(direction);
direction /= lengthDirection;
// compute collision details
collision->_floatData = cubeSideB;
collision->_vecData = cubeCenterB;
collision->_penetration = (halfCubeSideB * lengthDirection + halfCubeSideA - maxBA * glm::dot(BA, direction)) * direction;
collision->_contactPoint = cubeCenterA + halfCubeSideA * direction;
}
collision->_shapeA = NULL;
collision->_shapeB = NULL;
return collision;
} else if (halfCubeSideA + halfCubeSideB > distance) {
// NOTE: for cocentric approximation we collide sphere and cube as two spheres which means
// this algorithm will probably be wrong when both sphere and cube are very small (both ~EPSILON)
CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
// the penetration and contactPoint are undefined, so we pick a penetration direction (-yAxis)
collision->_penetration = (halfCubeSideA + halfCubeSideB) * glm::vec3(0.0f, -1.0f, 0.0f);
// contactPoint is on surface of A
collision->_contactPoint = cubeCenterA + collision->_penetration;
collision->_shapeA = NULL;
collision->_shapeB = NULL;
return collision;
}
}
return NULL;
}
bool aaCubeVsAACube(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool aaCubeVsAACube(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) {
// BA = B - A = from center of A to center of B
const AACubeShape* cubeA = static_cast<const AACubeShape*>(shapeA);
const AACubeShape* cubeB = static_cast<const AACubeShape*>(shapeB);
CollisionInfo* collision = aaCubeVsAACubeHelper( cubeA->getTranslation(), cubeA->getScale(),
cubeB->getTranslation(), cubeB->getScale(), collisions);
if (collision) {
collision->_shapeA = shapeA;
collision->_shapeB = shapeB;
return true;
}
return false; return false;
} }

View file

@ -39,4 +39,26 @@ public:
float getVolume() const { return 1.3333333333f * PI * _boundingRadius * _boundingRadius * _boundingRadius; } float getVolume() const { return 1.3333333333f * PI * _boundingRadius * _boundingRadius * _boundingRadius; }
}; };
inline QDebug operator<<(QDebug debug, const SphereShape& shape) {
debug << "SphereShape[ ("
<< "position: "
<< shape.getTranslation().x << ", " << shape.getTranslation().y << ", " << shape.getTranslation().z
<< "radius: "
<< shape.getRadius()
<< "]";
return debug;
}
inline QDebug operator<<(QDebug debug, const SphereShape* shape) {
debug << "SphereShape[ ("
<< "center: "
<< shape->getTranslation().x << ", " << shape->getTranslation().y << ", " << shape->getTranslation().z
<< "radius: "
<< shape->getRadius()
<< "]";
return debug;
}
#endif // hifi_SphereShape_h #endif // hifi_SphereShape_h