mirror of
https://github.com/lubosz/overte.git
synced 2025-04-17 12:36:24 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into OculusSDK
This commit is contained in:
commit
4483c7dc6b
67 changed files with 3449 additions and 595 deletions
|
@ -231,22 +231,10 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf
|
|||
delayBufferSample[0] = correctBufferSample[0] * weakChannelAmplitudeRatio;
|
||||
delayBufferSample[1] = correctBufferSample[1] * weakChannelAmplitudeRatio;
|
||||
|
||||
__m64 bufferSamples = _mm_set_pi16(_clientSamples[s + goodChannelOffset],
|
||||
_clientSamples[s + goodChannelOffset + SINGLE_STEREO_OFFSET],
|
||||
_clientSamples[delayedChannelIndex],
|
||||
_clientSamples[delayedChannelIndex + SINGLE_STEREO_OFFSET]);
|
||||
__m64 addedSamples = _mm_set_pi16(correctBufferSample[0], correctBufferSample[1],
|
||||
delayBufferSample[0], delayBufferSample[1]);
|
||||
|
||||
// perform the MMX add (with saturation) of two correct and delayed samples
|
||||
__m64 mmxResult = _mm_adds_pi16(bufferSamples, addedSamples);
|
||||
int16_t* shortResults = reinterpret_cast<int16_t*>(&mmxResult);
|
||||
|
||||
// assign the results from the result of the mmx arithmetic
|
||||
_clientSamples[s + goodChannelOffset] = shortResults[3];
|
||||
_clientSamples[s + goodChannelOffset + SINGLE_STEREO_OFFSET] = shortResults[2];
|
||||
_clientSamples[delayedChannelIndex] = shortResults[1];
|
||||
_clientSamples[delayedChannelIndex + SINGLE_STEREO_OFFSET] = shortResults[0];
|
||||
_clientSamples[s + goodChannelOffset] += correctBufferSample[0];
|
||||
_clientSamples[s + goodChannelOffset + SINGLE_STEREO_OFFSET] += correctBufferSample[1];
|
||||
_clientSamples[delayedChannelIndex] += delayBufferSample[0];
|
||||
_clientSamples[delayedChannelIndex + SINGLE_STEREO_OFFSET] += delayBufferSample[1];
|
||||
}
|
||||
|
||||
// The following code is pretty gross and redundant, but AFAIK it's the best way to avoid
|
||||
|
@ -266,74 +254,9 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf
|
|||
delayNextOutputStart = bufferStart + ringBufferSampleCapacity - numSamplesDelay;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
|
||||
while (i + 3 < numSamplesDelay) {
|
||||
// handle the first cases where we can MMX add four samples at once
|
||||
for (int i = 0; i < numSamplesDelay; i++) {
|
||||
int parentIndex = i * 2;
|
||||
__m64 bufferSamples = _mm_set_pi16(_clientSamples[parentIndex + delayedChannelOffset],
|
||||
_clientSamples[parentIndex + SINGLE_STEREO_OFFSET + delayedChannelOffset],
|
||||
_clientSamples[parentIndex + DOUBLE_STEREO_OFFSET + delayedChannelOffset],
|
||||
_clientSamples[parentIndex + TRIPLE_STEREO_OFFSET + delayedChannelOffset]);
|
||||
__m64 addSamples = _mm_set_pi16(delayNextOutputStart[i] * attenuationAndWeakChannelRatio,
|
||||
delayNextOutputStart[i + 1] * attenuationAndWeakChannelRatio,
|
||||
delayNextOutputStart[i + 2] * attenuationAndWeakChannelRatio,
|
||||
delayNextOutputStart[i + 3] * attenuationAndWeakChannelRatio);
|
||||
__m64 mmxResult = _mm_adds_pi16(bufferSamples, addSamples);
|
||||
int16_t* shortResults = reinterpret_cast<int16_t*>(&mmxResult);
|
||||
|
||||
_clientSamples[parentIndex + delayedChannelOffset] = shortResults[3];
|
||||
_clientSamples[parentIndex + SINGLE_STEREO_OFFSET + delayedChannelOffset] = shortResults[2];
|
||||
_clientSamples[parentIndex + DOUBLE_STEREO_OFFSET + delayedChannelOffset] = shortResults[1];
|
||||
_clientSamples[parentIndex + TRIPLE_STEREO_OFFSET + delayedChannelOffset] = shortResults[0];
|
||||
|
||||
// push the index
|
||||
i += 4;
|
||||
}
|
||||
|
||||
int parentIndex = i * 2;
|
||||
|
||||
if (i + 2 < numSamplesDelay) {
|
||||
// MMX add only three delayed samples
|
||||
|
||||
__m64 bufferSamples = _mm_set_pi16(_clientSamples[parentIndex + delayedChannelOffset],
|
||||
_clientSamples[parentIndex + SINGLE_STEREO_OFFSET + delayedChannelOffset],
|
||||
_clientSamples[parentIndex + DOUBLE_STEREO_OFFSET + delayedChannelOffset],
|
||||
0);
|
||||
__m64 addSamples = _mm_set_pi16(delayNextOutputStart[i] * attenuationAndWeakChannelRatio,
|
||||
delayNextOutputStart[i + 1] * attenuationAndWeakChannelRatio,
|
||||
delayNextOutputStart[i + 2] * attenuationAndWeakChannelRatio,
|
||||
0);
|
||||
__m64 mmxResult = _mm_adds_pi16(bufferSamples, addSamples);
|
||||
int16_t* shortResults = reinterpret_cast<int16_t*>(&mmxResult);
|
||||
|
||||
_clientSamples[parentIndex + delayedChannelOffset] = shortResults[3];
|
||||
_clientSamples[parentIndex + SINGLE_STEREO_OFFSET + delayedChannelOffset] = shortResults[2];
|
||||
_clientSamples[parentIndex + DOUBLE_STEREO_OFFSET + delayedChannelOffset] = shortResults[1];
|
||||
|
||||
} else if (i + 1 < numSamplesDelay) {
|
||||
// MMX add two delayed samples
|
||||
__m64 bufferSamples = _mm_set_pi16(_clientSamples[parentIndex + delayedChannelOffset],
|
||||
_clientSamples[parentIndex + SINGLE_STEREO_OFFSET + delayedChannelOffset],
|
||||
0, 0);
|
||||
__m64 addSamples = _mm_set_pi16(delayNextOutputStart[i] * attenuationAndWeakChannelRatio,
|
||||
delayNextOutputStart[i + 1] * attenuationAndWeakChannelRatio, 0, 0);
|
||||
|
||||
__m64 mmxResult = _mm_adds_pi16(bufferSamples, addSamples);
|
||||
int16_t* shortResults = reinterpret_cast<int16_t*>(&mmxResult);
|
||||
|
||||
_clientSamples[parentIndex + delayedChannelOffset] = shortResults[3];
|
||||
_clientSamples[parentIndex + SINGLE_STEREO_OFFSET + delayedChannelOffset] = shortResults[2];
|
||||
|
||||
} else if (i < numSamplesDelay) {
|
||||
// MMX add a single delayed sample
|
||||
__m64 bufferSamples = _mm_set_pi16(_clientSamples[parentIndex + delayedChannelOffset], 0, 0, 0);
|
||||
__m64 addSamples = _mm_set_pi16(delayNextOutputStart[i] * attenuationAndWeakChannelRatio, 0, 0, 0);
|
||||
|
||||
__m64 mmxResult = _mm_adds_pi16(bufferSamples, addSamples);
|
||||
int16_t* shortResults = reinterpret_cast<int16_t*>(&mmxResult);
|
||||
|
||||
_clientSamples[parentIndex + delayedChannelOffset] = shortResults[3];
|
||||
_clientSamples[parentIndex + delayedChannelOffset] += delayNextOutputStart[i] * attenuationAndWeakChannelRatio;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -380,11 +303,11 @@ void AudioMixer::prepareMixForListeningNode(Node* node) {
|
|||
// enumerate the ARBs attached to the otherNode and add all that should be added to mix
|
||||
for (int i = 0; i < otherNodeClientData->getRingBuffers().size(); i++) {
|
||||
PositionalAudioRingBuffer* otherNodeBuffer = otherNodeClientData->getRingBuffers()[i];
|
||||
|
||||
|
||||
if ((*otherNode != *node
|
||||
|| otherNodeBuffer->shouldLoopbackForNode())
|
||||
&& otherNodeBuffer->willBeAddedToMix()
|
||||
&& otherNodeBuffer->getNextOutputTrailingLoudness() > 0) {
|
||||
&& otherNodeBuffer->getNextOutputTrailingLoudness() > 0.0f) {
|
||||
addBufferToMixForListeningNodeWithBuffer(otherNodeBuffer, nodeRingBuffer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,12 +280,12 @@ QString AudioMixerClientData::getAudioStreamStatsString() const {
|
|||
+ " silents_dropped: ?"
|
||||
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " min_gap:" + QString::number(streamStats._timeGapMin)
|
||||
+ " max_gap:" + QString::number(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2)
|
||||
+ " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2);
|
||||
+ " min_gap:" + formatUsecTime(streamStats._timeGapMin)
|
||||
+ " max_gap:" + formatUsecTime(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + formatUsecTime(streamStats._timeGapAverage)
|
||||
+ " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
|
||||
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
|
||||
if (avatarRingBuffer) {
|
||||
|
@ -299,12 +299,12 @@ QString AudioMixerClientData::getAudioStreamStatsString() const {
|
|||
+ " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped)
|
||||
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " min_gap:" + QString::number(streamStats._timeGapMin)
|
||||
+ " max_gap:" + QString::number(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2)
|
||||
+ " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2);
|
||||
+ " min_gap:" + formatUsecTime(streamStats._timeGapMin)
|
||||
+ " max_gap:" + formatUsecTime(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + formatUsecTime(streamStats._timeGapAverage)
|
||||
+ " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
} else {
|
||||
result = "mic unknown";
|
||||
}
|
||||
|
@ -321,12 +321,12 @@ QString AudioMixerClientData::getAudioStreamStatsString() const {
|
|||
+ " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped)
|
||||
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
|
||||
+ " min_gap:" + QString::number(streamStats._timeGapMin)
|
||||
+ " max_gap:" + QString::number(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2)
|
||||
+ " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2);
|
||||
+ " min_gap:" + formatUsecTime(streamStats._timeGapMin)
|
||||
+ " max_gap:" + formatUsecTime(streamStats._timeGapMax)
|
||||
+ " avg_gap:" + formatUsecTime(streamStats._timeGapAverage)
|
||||
+ " min_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMin)
|
||||
+ " max_gap_30s:" + formatUsecTime(streamStats._timeGapWindowMax)
|
||||
+ " avg_gap_30s:" + formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
//
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QThread>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
|
@ -44,6 +47,19 @@ void MetavoxelServer::run() {
|
|||
|
||||
_lastSend = QDateTime::currentMSecsSinceEpoch();
|
||||
_sendTimer.start(SEND_INTERVAL);
|
||||
|
||||
// initialize Bitstream before using it in multiple threads
|
||||
Bitstream::preThreadingInit();
|
||||
|
||||
// create the persister and start it in its own thread
|
||||
_persister = new MetavoxelPersister(this);
|
||||
QThread* persistenceThread = new QThread(this);
|
||||
_persister->moveToThread(persistenceThread);
|
||||
_persister->connect(persistenceThread, SIGNAL(finished()), SLOT(deleteLater()));
|
||||
persistenceThread->start();
|
||||
|
||||
// queue up the load
|
||||
QMetaObject::invokeMethod(_persister, "load");
|
||||
}
|
||||
|
||||
void MetavoxelServer::readPendingDatagrams() {
|
||||
|
@ -67,6 +83,12 @@ void MetavoxelServer::readPendingDatagrams() {
|
|||
}
|
||||
}
|
||||
|
||||
void MetavoxelServer::aboutToFinish() {
|
||||
QMetaObject::invokeMethod(_persister, "save", Q_ARG(const MetavoxelData&, _data));
|
||||
_persister->thread()->quit();
|
||||
_persister->thread()->wait();
|
||||
}
|
||||
|
||||
void MetavoxelServer::maybeAttachSession(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
|
@ -193,3 +215,44 @@ void MetavoxelSession::sendPacketGroup(int alreadySent) {
|
|||
_sequencer.endPacket();
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelPersister::MetavoxelPersister(MetavoxelServer* server) :
|
||||
_server(server) {
|
||||
}
|
||||
|
||||
const char* SAVE_FILE = "metavoxels.dat";
|
||||
|
||||
void MetavoxelPersister::load() {
|
||||
QFile file(SAVE_FILE);
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
MetavoxelData data;
|
||||
{
|
||||
QDebug debug = qDebug() << "Reading from" << SAVE_FILE << "...";
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QDataStream inStream(&file);
|
||||
Bitstream in(inStream);
|
||||
try {
|
||||
in >> data;
|
||||
} catch (const BitstreamException& e) {
|
||||
debug << "failed, " << e.getDescription();
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(_server, "setData", Q_ARG(const MetavoxelData&, data));
|
||||
debug << "done.";
|
||||
}
|
||||
data.dumpStats();
|
||||
}
|
||||
|
||||
void MetavoxelPersister::save(const MetavoxelData& data) {
|
||||
QDebug debug = qDebug() << "Writing to" << SAVE_FILE << "...";
|
||||
QSaveFile file(SAVE_FILE);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
QDataStream outStream(&file);
|
||||
Bitstream out(outStream);
|
||||
out << data;
|
||||
out.flush();
|
||||
file.commit();
|
||||
debug << "done.";
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <Endpoint.h>
|
||||
|
||||
class MetavoxelEditMessage;
|
||||
class MetavoxelPersister;
|
||||
class MetavoxelSession;
|
||||
|
||||
/// Maintains a shared metavoxel system, accepting change requests and broadcasting updates.
|
||||
|
@ -33,11 +34,15 @@ public:
|
|||
void applyEdit(const MetavoxelEditMessage& edit);
|
||||
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
Q_INVOKABLE void setData(const MetavoxelData& data) { _data = data; }
|
||||
|
||||
virtual void run();
|
||||
|
||||
virtual void readPendingDatagrams();
|
||||
|
||||
virtual void aboutToFinish();
|
||||
|
||||
private slots:
|
||||
|
||||
void maybeAttachSession(const SharedNodePointer& node);
|
||||
|
@ -45,6 +50,8 @@ private slots:
|
|||
|
||||
private:
|
||||
|
||||
MetavoxelPersister* _persister;
|
||||
|
||||
QTimer _sendTimer;
|
||||
qint64 _lastSend;
|
||||
|
||||
|
@ -88,4 +95,20 @@ private:
|
|||
int _reliableDeltaID;
|
||||
};
|
||||
|
||||
/// Handles persistence in a separate thread.
|
||||
class MetavoxelPersister : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelPersister(MetavoxelServer* server);
|
||||
|
||||
Q_INVOKABLE void load();
|
||||
Q_INVOKABLE void save(const MetavoxelData& data);
|
||||
|
||||
private:
|
||||
|
||||
MetavoxelServer* _server;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelServer_h
|
||||
|
|
|
@ -20,7 +20,7 @@ test = function(name, func) {
|
|||
unitTest.run();
|
||||
print(" Success: " + unitTest.numAssertions + " assertions passed");
|
||||
} catch (error) {
|
||||
print(" Failure: " + error.message);
|
||||
print(" Failure: " + error.name + " " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -30,6 +30,12 @@ AssertionException = function(expected, actual, message) {
|
|||
this.name = 'AssertionException';
|
||||
};
|
||||
|
||||
UnthrownException = function(message) {
|
||||
print("Creating exception");
|
||||
this.message = message + "\n";
|
||||
this.name = 'UnthrownException';
|
||||
};
|
||||
|
||||
UnitTest = function(name, func) {
|
||||
this.numAssertions = 0;
|
||||
this.func = func;
|
||||
|
@ -65,4 +71,27 @@ UnitTest.prototype.assertNull = function(value, message) {
|
|||
if (value !== null) {
|
||||
throw new AssertionException(value, null, message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
UnitTest.prototype.arrayEqual = function(array1, array2, message) {
|
||||
this.numAssertions++;
|
||||
if (array1.length !== array2.length) {
|
||||
throw new AssertionException(array1.length , array2.length , message);
|
||||
}
|
||||
for (var i = 0; i < array1.length; ++i) {
|
||||
if (array1[i] !== array2[i]) {
|
||||
throw new AssertionException(array1[i], array2[i], i + " " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnitTest.prototype.raises = function(func, message) {
|
||||
this.numAssertions++;
|
||||
try {
|
||||
func();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new UnthrownException(message);
|
||||
}
|
|
@ -10,12 +10,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var localLightDirections = [ {x: 1.0, y:0.0, z: 0.0}, {x: 0.0, y:1.0, z: 1.0}, {x: 0.0, y:0.0, z: 1.0}, {x: 1.0, y:1.0, z: 1.0} ];
|
||||
var localLightColors = [ {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0} ];
|
||||
var localLightDirections = [ {x: 1.0, y:1.0, z: 0.0}, {x: 0.0, y:1.0, z: 1.0} ];
|
||||
var localLightColors = [ {x: 0.0, y:1.0, z: 0.0}, {x: 1.0, y:0.0, z: 0.0} ];
|
||||
|
||||
var currentSelection = 0;
|
||||
var currentNumLights = 1;
|
||||
var maxNumLights = 2;
|
||||
var currentNumAvatars = 0;
|
||||
var avatarHashIDs = [];
|
||||
|
||||
function keyPressEvent(event) {
|
||||
|
||||
|
@ -43,7 +45,8 @@ function keyPressEvent(event) {
|
|||
localLightColors[currentSelection].x = 0.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection);
|
||||
setAllLightColors();
|
||||
print("CHANGE RED light " + currentSelection + " color (" + localLightColors[currentSelection].x + ", " + localLightColors[currentSelection].y + ", " + localLightColors[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "6" ) {
|
||||
localLightColors[currentSelection].y += 0.01;
|
||||
|
@ -51,7 +54,8 @@ function keyPressEvent(event) {
|
|||
localLightColors[currentSelection].y = 0.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection);
|
||||
setAllLightColors();
|
||||
print("CHANGE GREEN light " + currentSelection + " color (" + localLightColors[currentSelection].x + ", " + localLightColors[currentSelection].y + ", " + localLightColors[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "7" ) {
|
||||
localLightColors[currentSelection].z += 0.01;
|
||||
|
@ -59,7 +63,8 @@ function keyPressEvent(event) {
|
|||
localLightColors[currentSelection].z = 0.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection);
|
||||
setAllLightColors();
|
||||
print("CHANGE BLUE light " + currentSelection + " color (" + localLightColors[currentSelection].x + ", " + localLightColors[currentSelection].y + ", " + localLightColors[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "8" ) {
|
||||
localLightDirections[currentSelection].x += 0.01;
|
||||
|
@ -67,7 +72,8 @@ function keyPressEvent(event) {
|
|||
localLightDirections[currentSelection].x = -1.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection);
|
||||
setAllLightDirections();
|
||||
print("PLUS X light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "9" ) {
|
||||
localLightDirections[currentSelection].x -= 0.01;
|
||||
|
@ -75,52 +81,117 @@ function keyPressEvent(event) {
|
|||
localLightDirections[currentSelection].x = 1.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection);
|
||||
setAllLightDirections();
|
||||
print("MINUS X light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "[" ) {
|
||||
else if (event.text == "0" ) {
|
||||
localLightDirections[currentSelection].y += 0.01;
|
||||
if (localLightDirections[currentSelection].y > 1.0) {
|
||||
localLightDirections[currentSelection].y = -1.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection);
|
||||
setAllLightDirections();
|
||||
print("PLUS Y light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "]" ) {
|
||||
else if (event.text == "-" ) {
|
||||
localLightDirections[currentSelection].y -= 0.01;
|
||||
if (localLightDirections[currentSelection].y < -1.0) {
|
||||
localLightDirections[currentSelection].y = 1.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection);
|
||||
setAllLightDirections();
|
||||
print("MINUS Y light " + currentSelection + " direction (" + localLightDirections[currentSelection].x + ", " + localLightDirections[currentSelection].y + ", " + localLightDirections[currentSelection].z + " )" );
|
||||
}
|
||||
else if (event.text == "," ) {
|
||||
if (currentNumLights + 1 <= maxNumLights) {
|
||||
var darkGrayColor = {x:0.3, y:0.3, z:0.3};
|
||||
|
||||
// default light
|
||||
localLightColors[currentNumLights].x = darkGrayColor.x;
|
||||
localLightColors[currentNumLights].y = darkGrayColor.y;
|
||||
localLightColors[currentNumLights].z = darkGrayColor.z;
|
||||
|
||||
MyAvatar.addLocalLight();
|
||||
MyAvatar.setLocalLightColor(localLightColors[currentNumLights], currentNumLights);
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentNumLights], currentNumLights);
|
||||
|
||||
++currentNumLights;
|
||||
|
||||
for (var i = 0; i < currentNumAvatars; i++) {
|
||||
AvatarManager.addAvatarLocalLight(i);
|
||||
|
||||
for (var j = 0; j < currentNumLights; j++) {
|
||||
AvatarManager.setAvatarLightColor(localLightColors[j], j, i);
|
||||
AvatarManager.setAvatarLightDirection(localLightDirections[j], j, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("ADD LIGHT, number of lights " + currentNumLights);
|
||||
}
|
||||
else if (event.text == "." ) {
|
||||
if (currentNumLights - 1 >= 0 ) {
|
||||
|
||||
// no light contribution
|
||||
localLightColors[currentNumLights - 1].x = 0.0;
|
||||
localLightColors[currentNumLights - 1].y = 0.0;
|
||||
localLightColors[currentNumLights - 1].z = 0.0;
|
||||
|
||||
MyAvatar.removeLocalLight();
|
||||
--currentNumLights;
|
||||
|
||||
for (var i = 0; i < currentNumAvatars; i++) {
|
||||
AvatarManager.removeAvatarLocalLight(i);
|
||||
|
||||
for (var j = 0; j < currentNumLights; j++) {
|
||||
AvatarManager.setAvatarLightColor(localLightColors[j], j, i);
|
||||
AvatarManager.setAvatarLightDirection(localLightDirections[j], j, i);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
print("REMOVE LIGHT, number of lights " + currentNumLights);
|
||||
}
|
||||
}
|
||||
|
||||
function updateLocalLights()
|
||||
{
|
||||
// new avatars, so add lights
|
||||
var numAvatars = AvatarManager.getNumAvatars();
|
||||
if (numAvatars != currentNumAvatars) {
|
||||
|
||||
for (var i = 0; i < numAvatars; i++) {
|
||||
var id = AvatarManager.getAvatarHashKey(i);
|
||||
|
||||
// check if avatar has already been registered
|
||||
var hasRegistered = false;
|
||||
for (var j = 0; j < numAvatars; j++) {
|
||||
if (avatarHashIDs[j] == id) {
|
||||
hasRegistered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// add new id and set light params
|
||||
if (!hasRegistered) {
|
||||
|
||||
avatarHashIDs.push(id);
|
||||
AvatarManager.addAvatarLocalLight(i);
|
||||
|
||||
// set color and direction for new avatar
|
||||
for (var j = 0; j < maxNumLights; j++) {
|
||||
AvatarManager.setAvatarLightColor(localLightColors[j], j, i);
|
||||
AvatarManager.setAvatarLightDirection(localLightDirections[j], j, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentNumAvatars = numAvatars;
|
||||
}
|
||||
}
|
||||
|
||||
function setAllLightColors()
|
||||
{
|
||||
for (var i = 0; i < currentNumAvatars; i++) {
|
||||
for (var j = 0; j < maxNumLights; j++) {
|
||||
AvatarManager.setAvatarLightColor(localLightColors[j], j, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setAllLightDirections()
|
||||
{
|
||||
for (var i = 0; i < currentNumAvatars; i++) {
|
||||
for (var j = 0; j < maxNumLights; j++) {
|
||||
AvatarManager.setAvatarLightDirection(localLightDirections[j], j, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// main
|
||||
Script.update.connect(updateLocalLights);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
|
||||
|
|
|
@ -94,23 +94,21 @@ function playClap(volume, position) {
|
|||
Audio.playSound(claps[clip], options);
|
||||
}
|
||||
|
||||
var FASTEST_CLAP_INTERVAL = 100.0;
|
||||
var SLOWEST_CLAP_INTERVAL = 2000.0;
|
||||
var FASTEST_CLAP_INTERVAL = 150.0;
|
||||
var SLOWEST_CLAP_INTERVAL = 750.0;
|
||||
|
||||
Controller.keyPressEvent.connect(function(event) {
|
||||
if(event.text == "SHIFT") {
|
||||
if (!clickClappingNow) {
|
||||
clickClappingNow = true;
|
||||
clickStartTime = new Date();
|
||||
playClap(1.0, Camera.getPosition());
|
||||
lastClapFrame = 0;
|
||||
MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false);
|
||||
} else {
|
||||
// Adjust animation speed for measured clicking interval
|
||||
// start or adjust clapping speed based on the duration between clicks
|
||||
clickEndTime = new Date();
|
||||
var milliseconds = clickEndTime - clickStartTime;
|
||||
var milliseconds = Math.max(clickEndTime - clickStartTime, FASTEST_CLAP_INTERVAL);
|
||||
clickStartTime = new Date();
|
||||
if ((milliseconds < SLOWEST_CLAP_INTERVAL) && (milliseconds > FASTEST_CLAP_INTERVAL)) {
|
||||
if (milliseconds < SLOWEST_CLAP_INTERVAL) {
|
||||
clapRate = ANIMATION_FRAMES_PER_CLAP * (1000.0 / milliseconds);
|
||||
playClap(1.0, Camera.getPosition());
|
||||
MyAvatar.stopAnimation(clapAnimation);
|
||||
|
|
708
examples/typedArraysUnitTest.js
Normal file
708
examples/typedArraysUnitTest.js
Normal file
|
@ -0,0 +1,708 @@
|
|||
//
|
||||
// typedArraysunitTest.js
|
||||
// examples
|
||||
//
|
||||
// Created by Clément Brisset on 7/7/14
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include("Test.js");
|
||||
|
||||
// e.g. extractbits([0xff, 0x80, 0x00, 0x00], 23, 30); inclusive
|
||||
function extractbits(bytes, lo, hi) {
|
||||
var out = 0;
|
||||
bytes = bytes.slice(); // make a copy
|
||||
var lsb = bytes.pop(), sc = 0, sh = 0;
|
||||
|
||||
for (; lo > 0; lo--, hi--) {
|
||||
lsb >>= 1;
|
||||
if (++sc === 8) { sc = 0; lsb = bytes.pop(); }
|
||||
}
|
||||
|
||||
for (; hi >= 0; hi--) {
|
||||
out = out | (lsb & 0x01) << sh++;
|
||||
lsb >>= 1;
|
||||
if (++sc === 8) { sc = 0; lsb = bytes.pop(); }
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
test('ArrayBuffer', function(finished) {
|
||||
this.assertEquals(new ArrayBuffer(0).byteLength, 0, 'no length');
|
||||
|
||||
this.assertEquals(typeof(new ArrayBuffer(0)), 'object', 'creation');
|
||||
this.assertEquals(typeof(new ArrayBuffer(1)), 'object', 'creation');
|
||||
this.assertEquals(typeof(new ArrayBuffer(123)), 'object', 'creation');
|
||||
|
||||
this.assertEquals(new ArrayBuffer(123).byteLength, 123, 'length');
|
||||
|
||||
this.raises(function () { return new ArrayBuffer(-1); }, 'negative length');
|
||||
this.raises(function () { return new ArrayBuffer(0x80000000); }, 'absurd length');
|
||||
});
|
||||
|
||||
test('DataView constructors', function (finished) {
|
||||
var d = new DataView(new ArrayBuffer(8));
|
||||
|
||||
d.setUint32(0, 0x12345678);
|
||||
this.assertEquals(d.getUint32(0), 0x12345678, 'big endian/big endian');
|
||||
|
||||
d.setUint32(0, 0x12345678, true);
|
||||
this.assertEquals(d.getUint32(0, true), 0x12345678, 'little endian/little endian');
|
||||
|
||||
d.setUint32(0, 0x12345678, true);
|
||||
this.assertEquals(d.getUint32(0), 0x78563412, 'little endian/big endian');
|
||||
|
||||
d.setUint32(0, 0x12345678);
|
||||
this.assertEquals(d.getUint32(0, true), 0x78563412, 'big endian/little endian');
|
||||
|
||||
this.raises(function () { return new DataView({}); }, 'non-ArrayBuffer argument');
|
||||
this.raises(function () { return new DataView("bogus"); }, 'non-ArrayBuffer argument');
|
||||
});
|
||||
|
||||
|
||||
test('ArrayBufferView', function () {
|
||||
var ab = new ArrayBuffer(48);
|
||||
var i32 = new Int32Array(ab, 16);
|
||||
i32.set([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
|
||||
this.assertEquals(i32.buffer, ab, 'ArrayBuffers equal');
|
||||
this.assertEquals(i32.byteOffset, 16, 'byteOffset');
|
||||
this.assertEquals(i32.byteLength, 32, 'byteLength');
|
||||
|
||||
var da = new DataView(i32.buffer, 8);
|
||||
this.assertEquals(da.buffer, ab, 'DataView: ArrayBuffers equal');
|
||||
this.assertEquals(da.byteOffset, 8, 'DataView: byteOffset');
|
||||
this.assertEquals(da.byteLength, 40, 'DataView: byteLength');
|
||||
});
|
||||
|
||||
test('TypedArrays', function () {
|
||||
var a;
|
||||
|
||||
this.assertEquals(Int8Array.BYTES_PER_ELEMENT, 1, 'Int8Array.BYTES_PER_ELEMENT');
|
||||
a = new Int8Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
this.assertEquals(a.BYTES_PER_ELEMENT, 1, 'int8Array.BYTES_PER_ELEMENT');
|
||||
this.assertEquals(a.byteOffset, 0, 'int8Array.byteOffset');
|
||||
this.assertEquals(a.byteLength, 8, 'int8Array.byteLength');
|
||||
|
||||
this.assertEquals(Uint8Array.BYTES_PER_ELEMENT, 1, 'Uint8Array.BYTES_PER_ELEMENT');
|
||||
a = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
this.assertEquals(a.BYTES_PER_ELEMENT, 1, 'uint8Array.BYTES_PER_ELEMENT');
|
||||
this.assertEquals(a.byteOffset, 0, 'uint8Array.byteOffset');
|
||||
this.assertEquals(a.byteLength, 8, 'uint8Array.byteLength');
|
||||
|
||||
this.assertEquals(Int16Array.BYTES_PER_ELEMENT, 2, 'Int16Array.BYTES_PER_ELEMENT');
|
||||
a = new Int16Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
this.assertEquals(a.BYTES_PER_ELEMENT, 2, 'int16Array.BYTES_PER_ELEMENT');
|
||||
this.assertEquals(a.byteOffset, 0, 'int16Array.byteOffset');
|
||||
this.assertEquals(a.byteLength, 16, 'int16Array.byteLength');
|
||||
|
||||
this.assertEquals(Uint16Array.BYTES_PER_ELEMENT, 2, 'Uint16Array.BYTES_PER_ELEMENT');
|
||||
a = new Uint16Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
this.assertEquals(a.BYTES_PER_ELEMENT, 2, 'uint16Array.BYTES_PER_ELEMENT');
|
||||
this.assertEquals(a.byteOffset, 0, 'uint16Array.byteOffset');
|
||||
this.assertEquals(a.byteLength, 16, 'uint16Array.byteLength');
|
||||
|
||||
this.assertEquals(Int32Array.BYTES_PER_ELEMENT, 4, 'Int32Array.BYTES_PER_ELEMENT');
|
||||
a = new Int32Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
this.assertEquals(a.BYTES_PER_ELEMENT, 4, 'int32Array.BYTES_PER_ELEMENT');
|
||||
this.assertEquals(a.byteOffset, 0, 'int32Array.byteOffset');
|
||||
this.assertEquals(a.byteLength, 32, 'int32Array.byteLength');
|
||||
|
||||
this.assertEquals(Uint32Array.BYTES_PER_ELEMENT, 4, 'Uint32Array.BYTES_PER_ELEMENT');
|
||||
a = new Uint32Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
this.assertEquals(a.BYTES_PER_ELEMENT, 4, 'uint32Array.BYTES_PER_ELEMENT');
|
||||
this.assertEquals(a.byteOffset, 0, 'uint32Array.byteOffset');
|
||||
this.assertEquals(a.byteLength, 32, 'uint32Array.byteLength');
|
||||
|
||||
this.assertEquals(Float32Array.BYTES_PER_ELEMENT, 4, 'Float32Array.BYTES_PER_ELEMENT');
|
||||
a = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
this.assertEquals(a.BYTES_PER_ELEMENT, 4, 'float32Array.BYTES_PER_ELEMENT');
|
||||
this.assertEquals(a.byteOffset, 0, 'float32Array.byteOffset');
|
||||
this.assertEquals(a.byteLength, 32, 'float32Array.byteLength');
|
||||
|
||||
this.assertEquals(Float64Array.BYTES_PER_ELEMENT, 8, 'Float64Array.BYTES_PER_ELEMENT');
|
||||
a = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
this.assertEquals(a.BYTES_PER_ELEMENT, 8, 'float64Array.BYTES_PER_ELEMENT');
|
||||
this.assertEquals(a.byteOffset, 0, 'float64Array.byteOffset');
|
||||
this.assertEquals(a.byteLength, 64, 'float64Array.byteLength');
|
||||
});
|
||||
|
||||
|
||||
test('typed array constructors', function () {
|
||||
this.arrayEqual(new Int8Array({ length: 3 }), [0, 0, 0], 'array equal -1');
|
||||
var rawbuf = (new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7])).buffer;
|
||||
|
||||
var int8 = new Int8Array();
|
||||
this.assertEquals(int8.length, 0, 'no args 0');
|
||||
this.raises(function () { return new Int8Array(-1); }, 'bogus length');
|
||||
this.raises(function () { return new Int8Array(0x80000000); }, 'bogus length');
|
||||
|
||||
int8 = new Int8Array(4);
|
||||
this.assertEquals(int8.BYTES_PER_ELEMENT, 1);
|
||||
this.assertEquals(int8.length, 4, 'length 1');
|
||||
this.assertEquals(int8.byteLength, 4, 'length 2');
|
||||
this.assertEquals(int8.byteOffset, 0, 'length 3');
|
||||
this.assertEquals(int8.get(-1), undefined, 'length, out of bounds 4');
|
||||
this.assertEquals(int8.get(4), undefined, 'length, out of bounds 5');
|
||||
|
||||
int8 = new Int8Array([1, 2, 3, 4, 5, 6]);
|
||||
this.assertEquals(int8.length, 6, 'array 6');
|
||||
this.assertEquals(int8.byteLength, 6, 'array 7');
|
||||
this.assertEquals(int8.byteOffset, 0, 'array 8');
|
||||
this.assertEquals(int8.get(3), 4, 'array 9');
|
||||
this.assertEquals(int8.get(-1), undefined, 'array, out of bounds 10');
|
||||
this.assertEquals(int8.get(6), undefined, 'array, out of bounds 11');
|
||||
|
||||
int8 = new Int8Array(rawbuf);
|
||||
this.assertEquals(int8.length, 8, 'buffer 12');
|
||||
this.assertEquals(int8.byteLength, 8, 'buffer 13');
|
||||
this.assertEquals(int8.byteOffset, 0, 'buffer 14');
|
||||
this.assertEquals(int8.get(7), 7, 'buffer 15');
|
||||
int8.set([111]);
|
||||
this.assertEquals(int8.get(0), 111, 'buffer 16');
|
||||
this.assertEquals(int8.get(-1), undefined, 'buffer, out of bounds 17');
|
||||
this.assertEquals(int8.get(8), undefined, 'buffer, out of bounds 18');
|
||||
|
||||
int8 = new Int8Array(rawbuf, 2);
|
||||
this.assertEquals(int8.length, 6, 'buffer, byteOffset 19');
|
||||
this.assertEquals(int8.byteLength, 6, 'buffer, byteOffset 20');
|
||||
this.assertEquals(int8.byteOffset, 2, 'buffer, byteOffset 21');
|
||||
this.assertEquals(int8.get(5), 7, 'buffer, byteOffset 22');
|
||||
int8.set([112]);
|
||||
this.assertEquals(int8.get(0), 112, 'buffer 23');
|
||||
this.assertEquals(int8.get(-1), undefined, 'buffer, byteOffset, out of bounds 24');
|
||||
this.assertEquals(int8.get(6), undefined, 'buffer, byteOffset, out of bounds 25');
|
||||
|
||||
int8 = new Int8Array(rawbuf, 8);
|
||||
this.assertEquals(int8.length, 0, 'buffer, byteOffset 26');
|
||||
|
||||
this.raises(function () { return new Int8Array(rawbuf, -1); }, 'invalid byteOffset 27');
|
||||
this.raises(function () { return new Int8Array(rawbuf, 9); }, 'invalid byteOffset 28');
|
||||
this.raises(function () { return new Int32Array(rawbuf, -1); }, 'invalid byteOffset 29');
|
||||
this.raises(function () { return new Int32Array(rawbuf, 5); }, 'invalid byteOffset 30');
|
||||
|
||||
int8 = new Int8Array(rawbuf, 2, 4);
|
||||
this.assertEquals(int8.length, 4, 'buffer, byteOffset, length 31');
|
||||
this.assertEquals(int8.byteLength, 4, 'buffer, byteOffset, length 32');
|
||||
this.assertEquals(int8.byteOffset, 2, 'buffer, byteOffset, length 33');
|
||||
this.assertEquals(int8.get(3), 5, 'buffer, byteOffset, length 34');
|
||||
int8.set([113]);
|
||||
this.assertEquals(int8.get(0), 113, 'buffer, byteOffset, length 35');
|
||||
this.assertEquals(int8.get(-1), undefined, 'buffer, byteOffset, length, out of bounds 36');
|
||||
this.assertEquals(int8.get(4), undefined, 'buffer, byteOffset, length, out of bounds 37');
|
||||
|
||||
this.raises(function () { return new Int8Array(rawbuf, 0, 9); }, 'invalid byteOffset+length');
|
||||
this.raises(function () { return new Int8Array(rawbuf, 8, 1); }, 'invalid byteOffset+length');
|
||||
this.raises(function () { return new Int8Array(rawbuf, 9, -1); }, 'invalid byteOffset+length');
|
||||
});
|
||||
|
||||
test('TypedArray clone constructor', function () {
|
||||
var src = new Int32Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
var dst = new Int32Array(src);
|
||||
this.arrayEqual(dst, [1, 2, 3, 4, 5, 6, 7, 8], '1');
|
||||
src.set([99]);
|
||||
this.arrayEqual(src, [99, 2, 3, 4, 5, 6, 7, 8], '2');
|
||||
this.arrayEqual(dst, [1, 2, 3, 4, 5, 6, 7, 8], '3');
|
||||
});
|
||||
|
||||
|
||||
test('conversions', function () {
|
||||
var uint8 = new Uint8Array([1, 2, 3, 4]),
|
||||
uint16 = new Uint16Array(uint8.buffer),
|
||||
uint32 = new Uint32Array(uint8.buffer);
|
||||
|
||||
// Note: can't probe individual bytes without endianness awareness
|
||||
this.arrayEqual(uint8, [1, 2, 3, 4]);
|
||||
uint16.set([0xffff]);
|
||||
this.arrayEqual(uint8, [0xff, 0xff, 3, 4]);
|
||||
uint16.set([0xeeee], 1);
|
||||
this.arrayEqual(uint8, [0xff, 0xff, 0xee, 0xee]);
|
||||
uint32.set([0x11111111]);
|
||||
this.assertEquals(uint16.get(0), 0x1111);
|
||||
this.assertEquals(uint16.get(1), 0x1111);
|
||||
this.arrayEqual(uint8, [0x11, 0x11, 0x11, 0x11]);
|
||||
});
|
||||
|
||||
|
||||
test('signed/unsigned conversions', function () {
|
||||
|
||||
var int8 = new Int8Array(1), uint8 = new Uint8Array(int8.buffer);
|
||||
uint8.set([123]);
|
||||
this.assertEquals(int8.get(0), 123, 'int8/uint8');
|
||||
uint8.set([161]);
|
||||
this.assertEquals(int8.get(0), -95, 'int8/uint8');
|
||||
int8.set([-120]);
|
||||
this.assertEquals(uint8.get(0), 136, 'uint8/int8');
|
||||
int8.set([-1]);
|
||||
this.assertEquals(uint8.get(0), 0xff, 'uint8/int8');
|
||||
|
||||
var int16 = new Int16Array(1), uint16 = new Uint16Array(int16.buffer);
|
||||
uint16.set([3210]);
|
||||
this.assertEquals(int16.get(0), 3210, 'int16/uint16');
|
||||
uint16.set([49232]);
|
||||
this.assertEquals(int16.get(0), -16304, 'int16/uint16');
|
||||
int16.set([-16384]);
|
||||
this.assertEquals(uint16.get(0), 49152, 'uint16/int16');
|
||||
int16.set([-1]);
|
||||
this.assertEquals(uint16.get(0), 0xffff, 'uint16/int16');
|
||||
|
||||
var int32 = new Int32Array(1), uint32 = new Uint32Array(int32.buffer);
|
||||
uint32.set([0x80706050]);
|
||||
this.assertEquals(int32.get(0), -2140118960, 'int32/uint32');
|
||||
int32.set([-2023406815]);
|
||||
this.assertEquals(uint32.get(0), 0x87654321, 'uint32/int32');
|
||||
int32.set([-1]);
|
||||
this.assertEquals(uint32.get(0), 0xffffffff, 'uint32/int32');
|
||||
});
|
||||
|
||||
|
||||
test('IEEE754 single precision unpacking', function () {
|
||||
function fromBytes(bytes) {
|
||||
var uint8 = new Uint8Array(bytes),
|
||||
dv = new DataView(uint8.buffer);
|
||||
return dv.getFloat32(0);
|
||||
}
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0xff, 0xff, 0xff])), true, 'Q-NaN');
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0xc0, 0x00, 0x01])), true, 'Q-NaN');
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0xc0, 0x00, 0x00])), true, 'Indeterminate');
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0xbf, 0xff, 0xff])), true, 'S-NaN');
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0x80, 0x00, 0x01])), true, 'S-NaN');
|
||||
|
||||
this.assertEquals(fromBytes([0xff, 0x80, 0x00, 0x00]), -Infinity, '-Infinity');
|
||||
|
||||
this.assertEquals(fromBytes([0xff, 0x7f, 0xff, 0xff]), -3.4028234663852886E+38, '-Normalized');
|
||||
this.assertEquals(fromBytes([0x80, 0x80, 0x00, 0x00]), -1.1754943508222875E-38, '-Normalized');
|
||||
this.assertEquals(fromBytes([0xff, 0x7f, 0xff, 0xff]), -3.4028234663852886E+38, '-Normalized');
|
||||
this.assertEquals(fromBytes([0x80, 0x80, 0x00, 0x00]), -1.1754943508222875E-38, '-Normalized');
|
||||
|
||||
// TODO: Denormalized values fail on Safari on iOS/ARM
|
||||
this.assertEquals(fromBytes([0x80, 0x7f, 0xff, 0xff]), -1.1754942106924411E-38, '-Denormalized');
|
||||
this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x01]), -1.4012984643248170E-45, '-Denormalized');
|
||||
|
||||
this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x00]), -0, '-0');
|
||||
this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x00]), +0, '+0');
|
||||
|
||||
// TODO: Denormalized values fail on Safari on iOS/ARM
|
||||
this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x01]), 1.4012984643248170E-45, '+Denormalized');
|
||||
this.assertEquals(fromBytes([0x00, 0x7f, 0xff, 0xff]), 1.1754942106924411E-38, '+Denormalized');
|
||||
|
||||
this.assertEquals(fromBytes([0x00, 0x80, 0x00, 0x00]), 1.1754943508222875E-38, '+Normalized');
|
||||
this.assertEquals(fromBytes([0x7f, 0x7f, 0xff, 0xff]), 3.4028234663852886E+38, '+Normalized');
|
||||
|
||||
this.assertEquals(fromBytes([0x7f, 0x80, 0x00, 0x00]), +Infinity, '+Infinity');
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0x7f, 0x80, 0x00, 0x01])), true, 'S+NaN');
|
||||
this.assertEquals(isNaN(fromBytes([0x7f, 0xbf, 0xff, 0xff])), true, 'S+NaN');
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0x7f, 0xc0, 0x00, 0x00])), true, 'Q+NaN');
|
||||
this.assertEquals(isNaN(fromBytes([0x7f, 0xff, 0xff, 0xff])), true, 'Q+NaN');
|
||||
});
|
||||
|
||||
test('IEEE754 single precision packing', function () {
|
||||
|
||||
function toBytes(v) {
|
||||
var uint8 = new Uint8Array(4), dv = new DataView(uint8.buffer);
|
||||
dv.setFloat32(0, v);
|
||||
var bytes = [];
|
||||
for (var i = 0; i < 4; i += 1) {
|
||||
bytes.push(uint8.get(i));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
this.arrayEqual(toBytes(-Infinity), [0xff, 0x80, 0x00, 0x00], '-Infinity');
|
||||
|
||||
this.arrayEqual(toBytes(-3.4028235677973366e+38), [0xff, 0x80, 0x00, 0x00], '-Overflow');
|
||||
this.arrayEqual(toBytes(-3.402824E+38), [0xff, 0x80, 0x00, 0x00], '-Overflow');
|
||||
|
||||
this.arrayEqual(toBytes(-3.4028234663852886E+38), [0xff, 0x7f, 0xff, 0xff], '-Normalized');
|
||||
this.arrayEqual(toBytes(-1.1754943508222875E-38), [0x80, 0x80, 0x00, 0x00], '-Normalized');
|
||||
|
||||
// TODO: Denormalized values fail on Safari iOS/ARM
|
||||
this.arrayEqual(toBytes(-1.1754942106924411E-38), [0x80, 0x7f, 0xff, 0xff], '-Denormalized');
|
||||
this.arrayEqual(toBytes(-1.4012984643248170E-45), [0x80, 0x00, 0x00, 0x01], '-Denormalized');
|
||||
|
||||
this.arrayEqual(toBytes(-7.006492321624085e-46), [0x80, 0x00, 0x00, 0x00], '-Underflow');
|
||||
|
||||
this.arrayEqual(toBytes(-0), [0x80, 0x00, 0x00, 0x00], '-0');
|
||||
this.arrayEqual(toBytes(0), [0x00, 0x00, 0x00, 0x00], '+0');
|
||||
|
||||
this.arrayEqual(toBytes(7.006492321624085e-46), [0x00, 0x00, 0x00, 0x00], '+Underflow');
|
||||
|
||||
// TODO: Denormalized values fail on Safari iOS/ARM
|
||||
this.arrayEqual(toBytes(1.4012984643248170E-45), [0x00, 0x00, 0x00, 0x01], '+Denormalized');
|
||||
this.arrayEqual(toBytes(1.1754942106924411E-38), [0x00, 0x7f, 0xff, 0xff], '+Denormalized');
|
||||
|
||||
this.arrayEqual(toBytes(1.1754943508222875E-38), [0x00, 0x80, 0x00, 0x00], '+Normalized');
|
||||
this.arrayEqual(toBytes(3.4028234663852886E+38), [0x7f, 0x7f, 0xff, 0xff], '+Normalized');
|
||||
|
||||
this.arrayEqual(toBytes(+3.402824E+38), [0x7f, 0x80, 0x00, 0x00], '+Overflow');
|
||||
this.arrayEqual(toBytes(+3.402824E+38), [0x7f, 0x80, 0x00, 0x00], '+Overflow');
|
||||
this.arrayEqual(toBytes(+Infinity), [0x7f, 0x80, 0x00, 0x00], '+Infinity');
|
||||
|
||||
// Allow any NaN pattern (exponent all 1's, fraction non-zero)
|
||||
var nanbytes = toBytes(NaN),
|
||||
sign = extractbits(nanbytes, 31, 31),
|
||||
exponent = extractbits(nanbytes, 23, 30),
|
||||
fraction = extractbits(nanbytes, 0, 22);
|
||||
this.assertEquals(exponent === 255 && fraction !== 0, true, 'NaN');
|
||||
});
|
||||
|
||||
|
||||
test('IEEE754 double precision unpacking', function () {
|
||||
|
||||
function fromBytes(bytes) {
|
||||
var uint8 = new Uint8Array(bytes),
|
||||
dv = new DataView(uint8.buffer);
|
||||
return dv.getFloat64(0);
|
||||
}
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'Q-NaN');
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])), true, 'Q-NaN');
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])), true, 'Indeterminate');
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'S-NaN');
|
||||
this.assertEquals(isNaN(fromBytes([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])), true, 'S-NaN');
|
||||
|
||||
this.assertEquals(fromBytes([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), -Infinity, '-Infinity');
|
||||
|
||||
this.assertEquals(fromBytes([0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), -1.7976931348623157E+308, '-Normalized');
|
||||
this.assertEquals(fromBytes([0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), -2.2250738585072014E-308, '-Normalized');
|
||||
|
||||
// TODO: Denormalized values fail on Safari iOS/ARM
|
||||
this.assertEquals(fromBytes([0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), -2.2250738585072010E-308, '-Denormalized');
|
||||
this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]), -4.9406564584124654E-324, '-Denormalized');
|
||||
|
||||
this.assertEquals(fromBytes([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), -0, '-0');
|
||||
this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), +0, '+0');
|
||||
|
||||
// TODO: Denormalized values fail on Safari iOS/ARM
|
||||
this.assertEquals(fromBytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]), 4.9406564584124654E-324, '+Denormalized');
|
||||
this.assertEquals(fromBytes([0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 2.2250738585072010E-308, '+Denormalized');
|
||||
|
||||
this.assertEquals(fromBytes([0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 2.2250738585072014E-308, '+Normalized');
|
||||
this.assertEquals(fromBytes([0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 1.7976931348623157E+308, '+Normalized');
|
||||
|
||||
this.assertEquals(fromBytes([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), +Infinity, '+Infinity');
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])), true, 'S+NaN');
|
||||
this.assertEquals(isNaN(fromBytes([0x7f, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'S+NaN');
|
||||
|
||||
this.assertEquals(isNaN(fromBytes([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])), true, 'Q+NaN');
|
||||
this.assertEquals(isNaN(fromBytes([0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), true, 'Q+NaN');
|
||||
});
|
||||
|
||||
|
||||
test('IEEE754 double precision packing', function () {
|
||||
|
||||
function toBytes(v) {
|
||||
var uint8 = new Uint8Array(8),
|
||||
dv = new DataView(uint8.buffer);
|
||||
dv.setFloat64(0, v);
|
||||
var bytes = [];
|
||||
for (var i = 0; i < 8; i += 1) {
|
||||
bytes.push(uint8.get(i));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
this.arrayEqual(toBytes(-Infinity), [0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '-Infinity');
|
||||
|
||||
this.arrayEqual(toBytes(-1.7976931348623157E+308), [0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '-Normalized');
|
||||
this.arrayEqual(toBytes(-2.2250738585072014E-308), [0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '-Normalized');
|
||||
|
||||
// TODO: Denormalized values fail on Safari iOS/ARM
|
||||
this.arrayEqual(toBytes(-2.2250738585072010E-308), [0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '-Denormalized');
|
||||
this.arrayEqual(toBytes(-4.9406564584124654E-324), [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], '-Denormalized');
|
||||
|
||||
this.arrayEqual(toBytes(-0), [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '-0');
|
||||
this.arrayEqual(toBytes(0), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '+0');
|
||||
|
||||
// TODO: Denormalized values fail on Safari iOS/ARM
|
||||
this.arrayEqual(toBytes(4.9406564584124654E-324), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], '+Denormalized');
|
||||
this.arrayEqual(toBytes(2.2250738585072010E-308), [0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '+Denormalized');
|
||||
|
||||
this.arrayEqual(toBytes(2.2250738585072014E-308), [0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '+Normalized');
|
||||
this.arrayEqual(toBytes(1.7976931348623157E+308), [0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], '+Normalized');
|
||||
|
||||
this.arrayEqual(toBytes(+Infinity), [0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], '+Infinity');
|
||||
|
||||
// Allow any NaN pattern (exponent all 1's, fraction non-zero)
|
||||
var nanbytes = toBytes(NaN),
|
||||
sign = extractbits(nanbytes, 63, 63),
|
||||
exponent = extractbits(nanbytes, 52, 62),
|
||||
fraction = extractbits(nanbytes, 0, 51);
|
||||
this.assertEquals(exponent === 2047 && fraction !== 0, true, 'NaN');
|
||||
});
|
||||
|
||||
test('Int32Array round trips', function () {
|
||||
var i32 = new Int32Array([0]);
|
||||
var data = [
|
||||
0,
|
||||
1,
|
||||
-1,
|
||||
123,
|
||||
-456,
|
||||
0x80000000 >> 0,
|
||||
0x7fffffff >> 0,
|
||||
0x12345678 >> 0,
|
||||
0x87654321 >> 0
|
||||
];
|
||||
|
||||
for (var i = 0; i < data.length; i += 1) {
|
||||
var datum = data[i];
|
||||
i32.set([datum]);
|
||||
this.assertEquals(datum, i32.get(0), String(datum));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('Int16Array round trips', function () {
|
||||
var i16 = new Int16Array([0]);
|
||||
var data = [
|
||||
0,
|
||||
1,
|
||||
-1,
|
||||
123,
|
||||
-456,
|
||||
0xffff8000 >> 0,
|
||||
0x00007fff >> 0,
|
||||
0x00001234 >> 0,
|
||||
0xffff8765 >> 0
|
||||
];
|
||||
|
||||
for (var i = 0; i < data.length; i += 1) {
|
||||
var datum = data[i];
|
||||
i16.set([datum]);
|
||||
this.assertEquals(datum, i16.get(0), String(datum));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('Int8Array round trips', function () {
|
||||
var i8 = new Int8Array([0]);
|
||||
var data = [
|
||||
0,
|
||||
1,
|
||||
-1,
|
||||
123,
|
||||
-45,
|
||||
0xffffff80 >> 0,
|
||||
0x0000007f >> 0,
|
||||
0x00000012 >> 0,
|
||||
0xffffff87 >> 0
|
||||
];
|
||||
|
||||
for (var i = 0; i < data.length; i += 1) {
|
||||
var datum = data[i];
|
||||
i8.set([datum]);
|
||||
this.assertEquals(datum, i8.get(0), String(datum));
|
||||
}
|
||||
});
|
||||
|
||||
test('TypedArray setting', function () {
|
||||
|
||||
var a = new Int32Array([1, 2, 3, 4, 5]);
|
||||
var b = new Int32Array(5);
|
||||
b.set(a);
|
||||
this.arrayEqual(b, [1, 2, 3, 4, 5], '1');
|
||||
this.raises(function () { b.set(a, 1); });
|
||||
|
||||
b.set(new Int32Array([99, 98]), 2);
|
||||
this.arrayEqual(b, [1, 2, 99, 98, 5], '2');
|
||||
|
||||
b.set(new Int32Array([99, 98, 97]), 2);
|
||||
this.arrayEqual(b, [1, 2, 99, 98, 97], '3');
|
||||
|
||||
this.raises(function () { b.set(new Int32Array([99, 98, 97, 96]), 2); });
|
||||
this.raises(function () { b.set([101, 102, 103, 104], 4); });
|
||||
|
||||
// ab = [ 0, 1, 2, 3, 4, 5, 6, 7 ]
|
||||
// a1 = [ ^, ^, ^, ^, ^, ^, ^, ^ ]
|
||||
// a2 = [ ^, ^, ^, ^ ]
|
||||
var ab = new ArrayBuffer(8);
|
||||
var a1 = new Uint8Array(ab);
|
||||
for (var i = 0; i < a1.length; i += 1) { a1.set([i], i); }
|
||||
var a2 = new Uint8Array(ab, 4);
|
||||
a1.set(a2, 2);
|
||||
this.arrayEqual(a1, [0, 1, 4, 5, 6, 7, 6, 7]);
|
||||
this.arrayEqual(a2, [6, 7, 6, 7]);
|
||||
});
|
||||
|
||||
|
||||
test('TypedArray.subarray', function () {
|
||||
|
||||
var a = new Int32Array([1, 2, 3, 4, 5]);
|
||||
this.arrayEqual(a.subarray(3), [4, 5]);
|
||||
this.arrayEqual(a.subarray(1, 3), [2, 3]);
|
||||
this.arrayEqual(a.subarray(-3), [3, 4, 5]);
|
||||
this.arrayEqual(a.subarray(-3, -1), [3, 4]);
|
||||
this.arrayEqual(a.subarray(3, 2), []);
|
||||
this.arrayEqual(a.subarray(-2, -3), []);
|
||||
this.arrayEqual(a.subarray(4, 1), []);
|
||||
this.arrayEqual(a.subarray(-1, -4), []);
|
||||
this.arrayEqual(a.subarray(1).subarray(1), [3, 4, 5]);
|
||||
this.arrayEqual(a.subarray(1, 4).subarray(1, 2), [3]);
|
||||
});
|
||||
|
||||
|
||||
test('DataView constructors', function () {
|
||||
|
||||
var d = new DataView(new ArrayBuffer(8));
|
||||
|
||||
d.setUint32(0, 0x12345678);
|
||||
this.assertEquals(d.getUint32(0), 0x12345678, 'big endian/big endian');
|
||||
|
||||
d.setUint32(0, 0x12345678, true);
|
||||
this.assertEquals(d.getUint32(0, true), 0x12345678, 'little endian/little endian');
|
||||
|
||||
d.setUint32(0, 0x12345678, true);
|
||||
this.assertEquals(d.getUint32(0), 0x78563412, 'little endian/big endian');
|
||||
|
||||
d.setUint32(0, 0x12345678);
|
||||
this.assertEquals(d.getUint32(0, true), 0x78563412, 'big endian/little endian');
|
||||
|
||||
// Chrome allows no arguments, throws if non-ArrayBuffer
|
||||
//stricterEqual(new DataView().buffer.byteLength, 0, 'no arguments');
|
||||
|
||||
// Safari (iOS 5) does not
|
||||
//raises(function () { return new DataView(); }, TypeError, 'no arguments');
|
||||
|
||||
// Chrome raises TypeError, Safari iOS5 raises isDOMException(INDEX_SIZE_ERR)
|
||||
this.raises(function () { return new DataView({}); }, 'non-ArrayBuffer argument');
|
||||
|
||||
this.raises(function () { return new DataView("bogus"); }, TypeError, 'non-ArrayBuffer argument');
|
||||
});
|
||||
|
||||
|
||||
|
||||
test('DataView accessors', function () {
|
||||
var u = new Uint8Array(8), d = new DataView(u.buffer);
|
||||
this.arrayEqual(u, [0, 0, 0, 0, 0, 0, 0, 0], '1');
|
||||
|
||||
d.setUint8(0, 255);
|
||||
this.arrayEqual(u, [0xff, 0, 0, 0, 0, 0, 0, 0], '2');
|
||||
|
||||
d.setInt8(1, -1);
|
||||
this.arrayEqual(u, [0xff, 0xff, 0, 0, 0, 0, 0, 0], '3');
|
||||
|
||||
d.setUint16(2, 0x1234);
|
||||
this.arrayEqual(u, [0xff, 0xff, 0x12, 0x34, 0, 0, 0, 0]), '4';
|
||||
|
||||
d.setInt16(4, -1);
|
||||
this.arrayEqual(u, [0xff, 0xff, 0x12, 0x34, 0xff, 0xff, 0, 0], '5');
|
||||
|
||||
d.setUint32(1, 0x12345678);
|
||||
this.arrayEqual(u, [0xff, 0x12, 0x34, 0x56, 0x78, 0xff, 0, 0], '6');
|
||||
|
||||
d.setInt32(4, -2023406815);
|
||||
this.arrayEqual(u, [0xff, 0x12, 0x34, 0x56, 0x87, 0x65, 0x43, 0x21], '7');
|
||||
|
||||
d.setFloat32(2, 1.2E+38);
|
||||
this.arrayEqual(u, [0xff, 0x12, 0x7e, 0xb4, 0x8e, 0x52, 0x43, 0x21], '8');
|
||||
|
||||
d.setFloat64(0, -1.2345678E+301);
|
||||
this.arrayEqual(u, [0xfe, 0x72, 0x6f, 0x51, 0x5f, 0x61, 0x77, 0xe5], '9');
|
||||
|
||||
u.set([0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87]);
|
||||
this.assertEquals(d.getUint8(0), 128, '10');
|
||||
this.assertEquals(d.getInt8(1), -127, '11');
|
||||
this.assertEquals(d.getUint16(2), 33411, '12');
|
||||
this.assertEquals(d.getInt16(3), -31868, '13');
|
||||
this.assertEquals(d.getUint32(4), 2223343239, '14');
|
||||
this.assertEquals(d.getInt32(2), -2105310075, '15');
|
||||
this.assertEquals(d.getFloat32(2), -1.932478247535851e-37, '16');
|
||||
this.assertEquals(d.getFloat64(0), -3.116851295377095e-306, '17');
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('DataView endian', function () {
|
||||
var rawbuf = (new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7])).buffer;
|
||||
var d;
|
||||
|
||||
d = new DataView(rawbuf);
|
||||
this.assertEquals(d.byteLength, 8, 'buffer');
|
||||
this.assertEquals(d.byteOffset, 0, 'buffer');
|
||||
this.raises(function () { d.getUint8(-2); }); // Chrome bug for index -, DOMException, 'bounds for buffer'?
|
||||
this.raises(function () { d.getUint8(8); }, 'bounds for buffer');
|
||||
this.raises(function () { d.setUint8(-2, 0); }, 'bounds for buffer');
|
||||
this.raises(function () { d.setUint8(8, 0); }, 'bounds for buffer');
|
||||
|
||||
d = new DataView(rawbuf, 2);
|
||||
this.assertEquals(d.byteLength, 6, 'buffer, byteOffset');
|
||||
this.assertEquals(d.byteOffset, 2, 'buffer, byteOffset');
|
||||
this.assertEquals(d.getUint8(5), 7, 'buffer, byteOffset');
|
||||
this.raises(function () { d.getUint8(-2); }, 'bounds for buffer, byteOffset');
|
||||
this.raises(function () { d.getUint8(6); }, 'bounds for buffer, byteOffset');
|
||||
this.raises(function () { d.setUint8(-2, 0); }, 'bounds for buffer, byteOffset');
|
||||
this.raises(function () { d.setUint8(6, 0); }, 'bounds for buffer, byteOffset');
|
||||
|
||||
d = new DataView(rawbuf, 8);
|
||||
this.assertEquals(d.byteLength, 0, 'buffer, byteOffset');
|
||||
|
||||
this.raises(function () { return new DataView(rawbuf, -1); }, 'invalid byteOffset');
|
||||
this.raises(function () { return new DataView(rawbuf, 9); }, 'invalid byteOffset');
|
||||
this.raises(function () { return new DataView(rawbuf, -1); }, 'invalid byteOffset');
|
||||
|
||||
d = new DataView(rawbuf, 2, 4);
|
||||
this.assertEquals(d.byteLength, 4, 'buffer, byteOffset, length');
|
||||
this.assertEquals(d.byteOffset, 2, 'buffer, byteOffset, length');
|
||||
this.assertEquals(d.getUint8(3), 5, 'buffer, byteOffset, length');
|
||||
this.raises(function () { return d.getUint8(-2); }, 'bounds for buffer, byteOffset, length');
|
||||
this.raises(function () { d.getUint8(4); }, 'bounds for buffer, byteOffset, length');
|
||||
this.raises(function () { d.setUint8(-2, 0); }, 'bounds for buffer, byteOffset, length');
|
||||
this.raises(function () { d.setUint8(4, 0); }, 'bounds for buffer, byteOffset, length');
|
||||
|
||||
this.raises(function () { return new DataView(rawbuf, 0, 9); }, 'invalid byteOffset+length');
|
||||
this.raises(function () { return new DataView(rawbuf, 8, 1); }, 'invalid byteOffset+length');
|
||||
this.raises(function () { return new DataView(rawbuf, 9, -1); }, 'invalid byteOffset+length');
|
||||
});
|
||||
|
||||
|
||||
test('Typed Array getters/setters', function () {
|
||||
// Only supported if Object.defineProperty() is fully supported on non-DOM objects.
|
||||
try {
|
||||
var o = {};
|
||||
Object.defineProperty(o, 'x', { get: function() { return 1; } });
|
||||
if (o.x !== 1) throw Error();
|
||||
} catch (_) {
|
||||
ok(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var bytes = new Uint8Array([1, 2, 3, 4]),
|
||||
uint32s = new Uint32Array(bytes.buffer);
|
||||
|
||||
this.assertEquals(bytes[1], 2);
|
||||
uint32s[0] = 0xffffffff;
|
||||
this.assertEquals(bytes[1], 0xff);
|
||||
});
|
||||
|
||||
|
||||
test('Uint8ClampedArray', function () {
|
||||
this.assertEquals(Uint8ClampedArray.BYTES_PER_ELEMENT, 1, 'Uint8ClampedArray.BYTES_PER_ELEMENT');
|
||||
var a = new Uint8ClampedArray([-Infinity, -Number.MAX_VALUE, -1, -Number.MIN_VALUE, -0,
|
||||
0, Number.MIN_VALUE, 1, 1.1, 1.9, 255, 255.1, 255.9, 256, Number.MAX_VALUE, Infinity,
|
||||
NaN]);
|
||||
this.assertEquals(a.BYTES_PER_ELEMENT, 1);
|
||||
this.assertEquals(a.byteOffset, 0);
|
||||
this.assertEquals(a.byteLength, 17);
|
||||
this.arrayEqual(a, [0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0], "array test");
|
||||
});
|
||||
|
||||
test('Regression Tests', function() {
|
||||
// Bug: https://github.com/inexorabletash/polyfill/issues/16
|
||||
var minFloat32 = 1.401298464324817e-45;
|
||||
var truncated = new Float32Array([-minFloat32 / 2 - Math.pow(2, -202)]).get(0);
|
||||
this.assertEquals(truncated, -minFloat32, 'smallest 32 bit float should not truncate to zero');
|
||||
});
|
||||
|
|
@ -3641,6 +3641,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
scriptEngine->registerGlobalObject("AnimationCache", &_animationCache);
|
||||
scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector);
|
||||
scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("Metavoxels", &_metavoxels);
|
||||
|
||||
scriptEngine->registerGlobalObject("AvatarManager", &_avatarManager);
|
||||
|
||||
#ifdef HAVE_RTMIDI
|
||||
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
|
||||
|
|
|
@ -112,12 +112,11 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
|
|||
_scopeInput(0),
|
||||
_scopeOutputLeft(0),
|
||||
_scopeOutputRight(0),
|
||||
_audioMixerAvatarStreamAudioStats(),
|
||||
_starveCount(0),
|
||||
_consecutiveNotMixedCount(0),
|
||||
_outgoingAvatarAudioSequenceNumber(0),
|
||||
_incomingMixedAudioSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH),
|
||||
_interframeTimeGapStats(TIME_GAPS_STATS_INTERVAL_SAMPLES, TIME_GAP_STATS_WINDOW_INTERVALS),
|
||||
_starveCount(0),
|
||||
_consecutiveNotMixedCount(0)
|
||||
_interframeTimeGapStats(TIME_GAPS_STATS_INTERVAL_SAMPLES, TIME_GAP_STATS_WINDOW_INTERVALS)
|
||||
{
|
||||
// clear the array of locally injected samples
|
||||
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||
|
|
|
@ -15,21 +15,189 @@
|
|||
#include "Util.h"
|
||||
#include "world.h"
|
||||
|
||||
const float HAIR_DAMPING = 0.99f;
|
||||
const float CONSTRAINT_RELAXATION = 10.0f;
|
||||
const float HAIR_ACCELERATION_COUPLING = 0.025f;
|
||||
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.10f;
|
||||
const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f;
|
||||
const float HAIR_STIFFNESS = 0.005f;
|
||||
const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f);
|
||||
const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f);
|
||||
|
||||
Hair::Hair() {
|
||||
qDebug() << "Creating Hair";
|
||||
Hair::Hair(int strands,
|
||||
int links,
|
||||
float radius,
|
||||
float linkLength,
|
||||
float hairThickness) :
|
||||
_strands(strands),
|
||||
_links(links),
|
||||
_linkLength(linkLength),
|
||||
_hairThickness(hairThickness),
|
||||
_radius(radius),
|
||||
_acceleration(0.0f),
|
||||
_angularVelocity(0.0f),
|
||||
_gravity(0.0f)
|
||||
{
|
||||
_hairPosition = new glm::vec3[_strands * _links];
|
||||
_hairOriginalPosition = new glm::vec3[_strands * _links];
|
||||
_hairLastPosition = new glm::vec3[_strands * _links];
|
||||
_hairQuadDelta = new glm::vec3[_strands * _links];
|
||||
_hairNormals = new glm::vec3[_strands * _links];
|
||||
_hairColors = new glm::vec3[_strands * _links];
|
||||
_hairIsMoveable = new int[_strands * _links];
|
||||
_hairConstraints = new int[_strands * _links * HAIR_CONSTRAINTS]; // Hair can link to two others
|
||||
const float FACE_WIDTH = PI / 4.0f;
|
||||
glm::vec3 thisVertex;
|
||||
for (int strand = 0; strand < _strands; strand++) {
|
||||
float strandAngle = randFloat() * PI;
|
||||
float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH));
|
||||
float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI);
|
||||
glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation));
|
||||
thisStrand *= _radius;
|
||||
|
||||
for (int link = 0; link < _links; link++) {
|
||||
int vertexIndex = strand * _links + link;
|
||||
// Clear constraints
|
||||
for (int link2 = 0; link2 < HAIR_CONSTRAINTS; link2++) {
|
||||
_hairConstraints[vertexIndex * HAIR_CONSTRAINTS + link2] = -1;
|
||||
}
|
||||
if (vertexIndex % _links == 0) {
|
||||
// start of strand
|
||||
thisVertex = thisStrand;
|
||||
} else {
|
||||
thisVertex+= glm::normalize(thisStrand) * _linkLength;
|
||||
// Set constraints to vertex before and maybe vertex after in strand
|
||||
_hairConstraints[vertexIndex * HAIR_CONSTRAINTS] = vertexIndex - 1;
|
||||
if (link < (_links - 1)) {
|
||||
_hairConstraints[vertexIndex * HAIR_CONSTRAINTS + 1] = vertexIndex + 1;
|
||||
}
|
||||
}
|
||||
_hairOriginalPosition[vertexIndex] = _hairLastPosition[vertexIndex] = _hairPosition[vertexIndex] = thisVertex;
|
||||
|
||||
_hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * _hairThickness, 0.f, sin(strandAngle) * _hairThickness);
|
||||
_hairQuadDelta[vertexIndex] *= 1.f - ((float)link / _links);
|
||||
_hairNormals[vertexIndex] = glm::normalize(randVector());
|
||||
if (randFloat() < elevation / PI_OVER_TWO) {
|
||||
_hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)_links);
|
||||
} else {
|
||||
_hairColors[vertexIndex] = HAIR_COLOR2 * ((float)(link + 1) / (float)_links);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hair::simulate(float deltaTime) {
|
||||
deltaTime = glm::clamp(deltaTime, 0.0f, 1.0f / 30.0f);
|
||||
glm::vec3 acceleration = _acceleration;
|
||||
if (glm::length(acceleration) > HAIR_MAX_LINEAR_ACCELERATION) {
|
||||
acceleration = glm::normalize(acceleration) * HAIR_MAX_LINEAR_ACCELERATION;
|
||||
}
|
||||
|
||||
for (int strand = 0; strand < _strands; strand++) {
|
||||
for (int link = 0; link < _links; link++) {
|
||||
int vertexIndex = strand * _links + link;
|
||||
if (vertexIndex % _links == 0) {
|
||||
// Base Joint - no integration
|
||||
} else {
|
||||
//
|
||||
// Vertlet Integration
|
||||
//
|
||||
// Add velocity from last position, with damping
|
||||
glm::vec3 thisPosition = _hairPosition[vertexIndex];
|
||||
glm::vec3 diff = thisPosition - _hairLastPosition[vertexIndex];
|
||||
_hairPosition[vertexIndex] += diff * HAIR_DAMPING;
|
||||
|
||||
// Resolve collisions with sphere
|
||||
if (glm::length(_hairPosition[vertexIndex]) < _radius) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) *
|
||||
(_radius - glm::length(_hairPosition[vertexIndex]));
|
||||
}
|
||||
|
||||
// Add gravity
|
||||
_hairPosition[vertexIndex] += _gravity * deltaTime;
|
||||
|
||||
// Add linear acceleration
|
||||
_hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime;
|
||||
|
||||
// Add stiffness to return to original position
|
||||
_hairPosition[vertexIndex] += (_hairOriginalPosition[vertexIndex] - _hairPosition[vertexIndex])
|
||||
* powf(1.f - (float)link / _links, 2.f) * HAIR_STIFFNESS;
|
||||
|
||||
// Add angular acceleration
|
||||
const float ANGULAR_VELOCITY_MIN = 0.001f;
|
||||
if (glm::length(_angularVelocity) > ANGULAR_VELOCITY_MIN) {
|
||||
glm::vec3 yawVector = _hairPosition[vertexIndex];
|
||||
yawVector.y = 0.f;
|
||||
if (glm::length(yawVector) > EPSILON) {
|
||||
float radius = glm::length(yawVector);
|
||||
yawVector = glm::normalize(yawVector);
|
||||
float angle = atan2f(yawVector.x, -yawVector.z) + PI;
|
||||
glm::vec3 delta = glm::vec3(-1.f, 0.f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 1, 0));
|
||||
_hairPosition[vertexIndex] -= delta * radius * _angularVelocity.y * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
glm::vec3 pitchVector = _hairPosition[vertexIndex];
|
||||
pitchVector.x = 0.f;
|
||||
if (glm::length(pitchVector) > EPSILON) {
|
||||
float radius = glm::length(pitchVector);
|
||||
pitchVector = glm::normalize(pitchVector);
|
||||
float angle = atan2f(pitchVector.y, -pitchVector.z) + PI;
|
||||
glm::vec3 delta = glm::vec3(0.0f, 1.0f, 0.f) * glm::angleAxis(angle, glm::vec3(1, 0, 0));
|
||||
_hairPosition[vertexIndex] -= delta * radius * _angularVelocity.x * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
glm::vec3 rollVector = _hairPosition[vertexIndex];
|
||||
rollVector.z = 0.f;
|
||||
if (glm::length(rollVector) > EPSILON) {
|
||||
float radius = glm::length(rollVector);
|
||||
pitchVector = glm::normalize(rollVector);
|
||||
float angle = atan2f(rollVector.x, rollVector.y) + PI;
|
||||
glm::vec3 delta = glm::vec3(-1.0f, 0.0f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 0, 1));
|
||||
_hairPosition[vertexIndex] -= delta * radius * _angularVelocity.z * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Impose link constraints
|
||||
for (int link = 0; link < HAIR_CONSTRAINTS; link++) {
|
||||
if (_hairConstraints[vertexIndex * HAIR_CONSTRAINTS + link] > -1) {
|
||||
// If there is a constraint, try to enforce it
|
||||
glm::vec3 vectorBetween = _hairPosition[_hairConstraints[vertexIndex * HAIR_CONSTRAINTS + link]] - _hairPosition[vertexIndex];
|
||||
_hairPosition[vertexIndex] += glm::normalize(vectorBetween) * (glm::length(vectorBetween) - _linkLength) * CONSTRAINT_RELAXATION * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Store start position for next vertlet pass
|
||||
_hairLastPosition[vertexIndex] = thisPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hair::render() {
|
||||
//
|
||||
// Before calling this function, translate/rotate to the origin of the owning object
|
||||
glPushMatrix();
|
||||
glColor3f(1.0f, 1.0f, 0.0f);
|
||||
glutSolidSphere(1.0f, 15, 15);
|
||||
glPopMatrix();
|
||||
//
|
||||
glBegin(GL_QUADS);
|
||||
for (int strand = 0; strand < _strands; strand++) {
|
||||
for (int link = 0; link < _links - 1; link++) {
|
||||
int vertexIndex = strand * _links + link;
|
||||
glColor3fv(&_hairColors[vertexIndex].x);
|
||||
glNormal3fv(&_hairNormals[vertexIndex].x);
|
||||
glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex].z - _hairQuadDelta[vertexIndex].z);
|
||||
glVertex3f(_hairPosition[vertexIndex].x + _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex].y + _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex].z + _hairQuadDelta[vertexIndex].z);
|
||||
|
||||
glVertex3f(_hairPosition[vertexIndex + 1].x + _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex + 1].y + _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex + 1].z + _hairQuadDelta[vertexIndex].z);
|
||||
glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z);
|
||||
}
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -21,14 +21,45 @@
|
|||
#include "InterfaceConfig.h"
|
||||
#include "Util.h"
|
||||
|
||||
const int HAIR_CONSTRAINTS = 2;
|
||||
|
||||
const int DEFAULT_HAIR_STRANDS = 50;
|
||||
const int DEFAULT_HAIR_LINKS = 10;
|
||||
const float DEFAULT_HAIR_RADIUS = 0.15;
|
||||
const float DEFAULT_HAIR_LINK_LENGTH = 0.03;
|
||||
const float DEFAULT_HAIR_THICKNESS = 0.015;
|
||||
|
||||
class Hair {
|
||||
public:
|
||||
Hair();
|
||||
Hair(int strands = DEFAULT_HAIR_STRANDS,
|
||||
int links = DEFAULT_HAIR_LINKS,
|
||||
float radius = DEFAULT_HAIR_RADIUS,
|
||||
float linkLength = DEFAULT_HAIR_LINK_LENGTH,
|
||||
float hairThickness = DEFAULT_HAIR_THICKNESS);
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
void setAcceleration(const glm::vec3& acceleration) { _acceleration = acceleration; }
|
||||
void setAngularVelocity(const glm::vec3& angularVelocity) { _angularVelocity = angularVelocity; }
|
||||
void setGravity(const glm::vec3& gravity) { _gravity = gravity; }
|
||||
|
||||
private:
|
||||
int _strands;
|
||||
int _links;
|
||||
float _linkLength;
|
||||
float _hairThickness;
|
||||
float _radius;
|
||||
glm::vec3* _hairPosition;
|
||||
glm::vec3* _hairOriginalPosition;
|
||||
glm::vec3* _hairLastPosition;
|
||||
glm::vec3* _hairQuadDelta;
|
||||
glm::vec3* _hairNormals;
|
||||
glm::vec3* _hairColors;
|
||||
int* _hairIsMoveable;
|
||||
int* _hairConstraints;
|
||||
glm::vec3 _acceleration;
|
||||
glm::vec3 _angularVelocity;
|
||||
glm::vec3 _gravity;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -441,13 +441,11 @@ Menu::Menu() :
|
|||
QMenu* timingMenu = developerMenu->addMenu("Timing and Statistics Tools");
|
||||
QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer");
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayTimingDetails, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandDisplaySideTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandAvatarSimulateTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandAvatarUpdateTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMiscAvatarTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandIdleTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarSimulateTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandOtherAvatarTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false);
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::TestPing, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer);
|
||||
|
|
|
@ -357,13 +357,11 @@ namespace MenuOption {
|
|||
const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)";
|
||||
const QString Enable3DTVMode = "Enable 3DTV Mode";
|
||||
const QString EnableVRMode = "Enable VR Mode";
|
||||
const QString ExpandMiscAvatarTiming = "Expand Misc MyAvatar Timing";
|
||||
const QString ExpandAvatarUpdateTiming = "Expand MyAvatar update Timing";
|
||||
const QString ExpandAvatarSimulateTiming = "Expand MyAvatar simulate Timing";
|
||||
const QString ExpandDisplaySideTiming = "Expand Display Side Timing";
|
||||
const QString ExpandIdleTiming = "Expand Idle Timing";
|
||||
const QString ExpandPaintGLTiming = "Expand PaintGL Timing";
|
||||
const QString ExpandUpdateTiming = "Expand Update Timing";
|
||||
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
|
||||
const QString ExpandMyAvatarTiming = "Expand /myAvatar";
|
||||
const QString ExpandOtherAvatarTiming = "Expand /otherAvatar";
|
||||
const QString ExpandPaintGLTiming = "Expand /paintGL";
|
||||
const QString ExpandUpdateTiming = "Expand /update";
|
||||
const QString Faceplus = "Faceplus";
|
||||
const QString Faceshift = "Faceshift";
|
||||
const QString FilterSixense = "Smooth Sixense Movement";
|
||||
|
|
|
@ -48,8 +48,10 @@ void MetavoxelSystem::init() {
|
|||
}
|
||||
|
||||
MetavoxelLOD MetavoxelSystem::getLOD() const {
|
||||
const float FIXED_LOD_THRESHOLD = 0.01f;
|
||||
return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(), FIXED_LOD_THRESHOLD);
|
||||
// the LOD threshold is temporarily tied to the avatar LOD parameter
|
||||
const float BASE_LOD_THRESHOLD = 0.01f;
|
||||
return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(),
|
||||
BASE_LOD_THRESHOLD * Menu::getInstance()->getAvatarLODDistanceMultiplier());
|
||||
}
|
||||
|
||||
void MetavoxelSystem::simulate(float deltaTime) {
|
||||
|
|
|
@ -60,7 +60,7 @@ Avatar::Avatar() :
|
|||
_mouseRayDirection(0.0f, 0.0f, 0.0f),
|
||||
_moving(false),
|
||||
_collisionGroups(0),
|
||||
_numLocalLights(2),
|
||||
_numLocalLights(0),
|
||||
_initialized(false),
|
||||
_shouldRenderBillboard(true)
|
||||
{
|
||||
|
@ -82,24 +82,6 @@ void Avatar::init() {
|
|||
_skeletonModel.init();
|
||||
_initialized = true;
|
||||
_shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
|
||||
initializeHair();
|
||||
|
||||
for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) {
|
||||
_localLightColors[i] = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_localLightDirections[i] = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 darkGrayColor(0.4f, 0.4f, 0.4f);
|
||||
glm::vec3 greenColor(0.0f, 1.0f, 0.0f);
|
||||
glm::vec3 directionX(1.0f, 0.0f, 0.0f);
|
||||
glm::vec3 directionY(0.0f, 1.0f, 0.0f);
|
||||
|
||||
// initialize local lights
|
||||
_localLightColors[0] = darkGrayColor;
|
||||
_localLightColors[1] = darkGrayColor;
|
||||
|
||||
_localLightDirections[0] = directionX;
|
||||
_localLightDirections[1] = directionY;
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getChestPosition() const {
|
||||
|
@ -168,13 +150,11 @@ void Avatar::simulate(float deltaTime) {
|
|||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
PerformanceTimer perfTimer("hair");
|
||||
simulateHair(deltaTime);
|
||||
_hair.setAcceleration(getAcceleration() * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.setAngularVelocity(getAngularVelocity() + getHead()->getAngularVelocity() * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()) * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.simulate(deltaTime);
|
||||
}
|
||||
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->simulate(deltaTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// update position by velocity, and subtract the change added earlier for gravity
|
||||
|
@ -426,233 +406,17 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
getHand()->render(false, modelRenderMode);
|
||||
}
|
||||
getHead()->render(1.0f, modelRenderMode);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
renderHair();
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Constants for the Hair Simulation
|
||||
//
|
||||
|
||||
const float HAIR_LENGTH = 0.2f;
|
||||
const float HAIR_LINK_LENGTH = HAIR_LENGTH / HAIR_LINKS;
|
||||
const float HAIR_DAMPING = 0.99f;
|
||||
const float HEAD_RADIUS = 0.21f;
|
||||
const float CONSTRAINT_RELAXATION = 10.0f;
|
||||
const glm::vec3 HAIR_GRAVITY(0.0f, -0.007f, 0.0f);
|
||||
const float HAIR_ACCELERATION_COUPLING = 0.025f;
|
||||
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.10f;
|
||||
const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f;
|
||||
const float HAIR_THICKNESS = 0.015f;
|
||||
const float HAIR_STIFFNESS = 0.0000f;
|
||||
const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f);
|
||||
const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f);
|
||||
const glm::vec3 WIND_DIRECTION(0.5f, -1.0f, 0.0f);
|
||||
const float MAX_WIND_STRENGTH = 0.02f;
|
||||
const float FINGER_LENGTH = 0.25f;
|
||||
const float FINGER_RADIUS = 0.10f;
|
||||
|
||||
void Avatar::renderHair() {
|
||||
//
|
||||
// Render the avatar's moveable hair
|
||||
//
|
||||
|
||||
glm::vec3 headPosition = getHead()->getPosition();
|
||||
glPushMatrix();
|
||||
glTranslatef(headPosition.x, headPosition.y, headPosition.z);
|
||||
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
for (int strand = 0; strand < HAIR_STRANDS; strand++) {
|
||||
for (int link = 0; link < HAIR_LINKS - 1; link++) {
|
||||
int vertexIndex = strand * HAIR_LINKS + link;
|
||||
glColor3fv(&_hairColors[vertexIndex].x);
|
||||
glNormal3fv(&_hairNormals[vertexIndex].x);
|
||||
glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex].z - _hairQuadDelta[vertexIndex].z);
|
||||
glVertex3f(_hairPosition[vertexIndex].x + _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex].y + _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex].z + _hairQuadDelta[vertexIndex].z);
|
||||
|
||||
glVertex3f(_hairPosition[vertexIndex + 1].x + _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex + 1].y + _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex + 1].z + _hairQuadDelta[vertexIndex].z);
|
||||
glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z);
|
||||
}
|
||||
}
|
||||
glEnd();
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
}
|
||||
|
||||
void Avatar::simulateHair(float deltaTime) {
|
||||
|
||||
deltaTime = glm::clamp(deltaTime, 0.0f, 1.0f / 30.0f);
|
||||
glm::vec3 acceleration = getAcceleration();
|
||||
if (glm::length(acceleration) > HAIR_MAX_LINEAR_ACCELERATION) {
|
||||
acceleration = glm::normalize(acceleration) * HAIR_MAX_LINEAR_ACCELERATION;
|
||||
}
|
||||
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
|
||||
acceleration = acceleration * rotation;
|
||||
glm::vec3 angularVelocity = getAngularVelocity() + getHead()->getAngularVelocity();
|
||||
|
||||
// Get hand positions to allow touching hair
|
||||
glm::vec3 leftHandPosition, rightHandPosition;
|
||||
getSkeletonModel().getLeftHandPosition(leftHandPosition);
|
||||
getSkeletonModel().getRightHandPosition(rightHandPosition);
|
||||
leftHandPosition -= getHead()->getPosition();
|
||||
rightHandPosition -= getHead()->getPosition();
|
||||
glm::quat leftRotation, rightRotation;
|
||||
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation);
|
||||
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation);
|
||||
leftHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(leftRotation);
|
||||
rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(rightRotation);
|
||||
leftHandPosition = leftHandPosition * rotation;
|
||||
rightHandPosition = rightHandPosition * rotation;
|
||||
|
||||
float windIntensity = randFloat() * MAX_WIND_STRENGTH;
|
||||
|
||||
for (int strand = 0; strand < HAIR_STRANDS; strand++) {
|
||||
for (int link = 0; link < HAIR_LINKS; link++) {
|
||||
int vertexIndex = strand * HAIR_LINKS + link;
|
||||
if (vertexIndex % HAIR_LINKS == 0) {
|
||||
// Base Joint - no integration
|
||||
} else {
|
||||
//
|
||||
// Vertlet Integration
|
||||
//
|
||||
// Add velocity from last position, with damping
|
||||
glm::vec3 thisPosition = _hairPosition[vertexIndex];
|
||||
glm::vec3 diff = thisPosition - _hairLastPosition[vertexIndex];
|
||||
_hairPosition[vertexIndex] += diff * HAIR_DAMPING;
|
||||
// Resolve collision with head sphere
|
||||
if (glm::length(_hairPosition[vertexIndex]) < HEAD_RADIUS) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) *
|
||||
(HEAD_RADIUS - glm::length(_hairPosition[vertexIndex]));
|
||||
}
|
||||
// Resolve collision with hands
|
||||
if (glm::length(_hairPosition[vertexIndex] - leftHandPosition) < FINGER_RADIUS) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - leftHandPosition) *
|
||||
(FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - leftHandPosition));
|
||||
}
|
||||
if (glm::length(_hairPosition[vertexIndex] - rightHandPosition) < FINGER_RADIUS) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - rightHandPosition) *
|
||||
(FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - rightHandPosition));
|
||||
}
|
||||
|
||||
|
||||
// Add a little gravity
|
||||
_hairPosition[vertexIndex] += HAIR_GRAVITY * rotation * deltaTime;
|
||||
|
||||
// Add linear acceleration of the avatar body
|
||||
_hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime;
|
||||
|
||||
// Add stiffness (like hair care products do)
|
||||
_hairPosition[vertexIndex] += (_hairOriginalPosition[vertexIndex] - _hairPosition[vertexIndex])
|
||||
* powf(1.f - link / HAIR_LINKS, 2.f) * HAIR_STIFFNESS;
|
||||
|
||||
// Add some wind
|
||||
glm::vec3 wind = WIND_DIRECTION * windIntensity;
|
||||
_hairPosition[vertexIndex] += wind * deltaTime;
|
||||
|
||||
const float ANGULAR_VELOCITY_MIN = 0.001f;
|
||||
// Add angular acceleration of the avatar body
|
||||
if (glm::length(angularVelocity) > ANGULAR_VELOCITY_MIN) {
|
||||
glm::vec3 yawVector = _hairPosition[vertexIndex];
|
||||
yawVector.y = 0.f;
|
||||
if (glm::length(yawVector) > EPSILON) {
|
||||
float radius = glm::length(yawVector);
|
||||
yawVector = glm::normalize(yawVector);
|
||||
float angle = atan2f(yawVector.x, -yawVector.z) + PI;
|
||||
glm::vec3 delta = glm::vec3(-1.f, 0.f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 1, 0));
|
||||
_hairPosition[vertexIndex] -= delta * radius * angularVelocity.y * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
glm::vec3 pitchVector = _hairPosition[vertexIndex];
|
||||
pitchVector.x = 0.f;
|
||||
if (glm::length(pitchVector) > EPSILON) {
|
||||
float radius = glm::length(pitchVector);
|
||||
pitchVector = glm::normalize(pitchVector);
|
||||
float angle = atan2f(pitchVector.y, -pitchVector.z) + PI;
|
||||
glm::vec3 delta = glm::vec3(0.0f, 1.0f, 0.f) * glm::angleAxis(angle, glm::vec3(1, 0, 0));
|
||||
_hairPosition[vertexIndex] -= delta * radius * angularVelocity.x * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
glm::vec3 rollVector = _hairPosition[vertexIndex];
|
||||
rollVector.z = 0.f;
|
||||
if (glm::length(rollVector) > EPSILON) {
|
||||
float radius = glm::length(rollVector);
|
||||
pitchVector = glm::normalize(rollVector);
|
||||
float angle = atan2f(rollVector.x, rollVector.y) + PI;
|
||||
glm::vec3 delta = glm::vec3(-1.0f, 0.0f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 0, 1));
|
||||
_hairPosition[vertexIndex] -= delta * radius * angularVelocity.z * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate length constraints to other links
|
||||
for (int link = 0; link < HAIR_MAX_CONSTRAINTS; link++) {
|
||||
if (_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link] > -1) {
|
||||
// If there is a constraint, try to enforce it
|
||||
glm::vec3 vectorBetween = _hairPosition[_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link]] - _hairPosition[vertexIndex];
|
||||
_hairPosition[vertexIndex] += glm::normalize(vectorBetween) * (glm::length(vectorBetween) - HAIR_LINK_LENGTH) * CONSTRAINT_RELAXATION * deltaTime;
|
||||
}
|
||||
}
|
||||
// Store start position for next vertlet pass
|
||||
_hairLastPosition[vertexIndex] = thisPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::initializeHair() {
|
||||
const float FACE_WIDTH = PI / 4.0f;
|
||||
glm::vec3 thisVertex;
|
||||
for (int strand = 0; strand < HAIR_STRANDS; strand++) {
|
||||
float strandAngle = randFloat() * PI;
|
||||
float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH));
|
||||
float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI);
|
||||
glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation));
|
||||
thisStrand *= HEAD_RADIUS;
|
||||
|
||||
for (int link = 0; link < HAIR_LINKS; link++) {
|
||||
int vertexIndex = strand * HAIR_LINKS + link;
|
||||
// Clear constraints
|
||||
for (int link2 = 0; link2 < HAIR_MAX_CONSTRAINTS; link2++) {
|
||||
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link2] = -1;
|
||||
}
|
||||
if (vertexIndex % HAIR_LINKS == 0) {
|
||||
// start of strand
|
||||
thisVertex = thisStrand;
|
||||
} else {
|
||||
thisVertex+= glm::normalize(thisStrand) * HAIR_LINK_LENGTH;
|
||||
// Set constraints to vertex before and maybe vertex after in strand
|
||||
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS] = vertexIndex - 1;
|
||||
if (link < (HAIR_LINKS - 1)) {
|
||||
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + 1] = vertexIndex + 1;
|
||||
}
|
||||
}
|
||||
_hairPosition[vertexIndex] = thisVertex;
|
||||
_hairLastPosition[vertexIndex] = _hairPosition[vertexIndex];
|
||||
_hairOriginalPosition[vertexIndex] = _hairPosition[vertexIndex];
|
||||
|
||||
_hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * HAIR_THICKNESS, 0.f, sin(strandAngle) * HAIR_THICKNESS);
|
||||
_hairQuadDelta[vertexIndex] *= 1.f - (link / HAIR_LINKS);
|
||||
_hairNormals[vertexIndex] = glm::normalize(randVector());
|
||||
if (randFloat() < elevation / PI_OVER_TWO) {
|
||||
_hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)HAIR_LINKS);
|
||||
} else {
|
||||
_hairColors[vertexIndex] = HAIR_COLOR2 * ((float)(link + 1) / (float)HAIR_LINKS);
|
||||
}
|
||||
|
||||
}
|
||||
// Render Hair
|
||||
glPushMatrix();
|
||||
glm::vec3 headPosition = getHead()->getPosition();
|
||||
glTranslatef(headPosition.x, headPosition.y, headPosition.z);
|
||||
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
_hair.render();
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ signals:
|
|||
void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision);
|
||||
|
||||
protected:
|
||||
QVector<Hair*> _hairs;
|
||||
Hair _hair;
|
||||
SkeletonModel _skeletonModel;
|
||||
QVector<Model*> _attachmentModels;
|
||||
float _bodyYawDelta;
|
||||
|
@ -214,18 +214,6 @@ protected:
|
|||
virtual void renderAttachments(RenderMode renderMode);
|
||||
|
||||
virtual void updateJointMappings();
|
||||
|
||||
glm::vec3 _hairPosition[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairOriginalPosition[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairLastPosition[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairQuadDelta[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairNormals[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairColors[HAIR_STRANDS * HAIR_LINKS];
|
||||
int _hairIsMoveable[HAIR_STRANDS * HAIR_LINKS];
|
||||
int _hairConstraints[HAIR_STRANDS * HAIR_LINKS * 2]; // Hair can link to two others
|
||||
void renderHair();
|
||||
void simulateHair(float deltaTime);
|
||||
void initializeHair();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -238,6 +226,7 @@ private:
|
|||
|
||||
float getBillboardSize() const;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_Avatar_h
|
||||
|
|
|
@ -158,3 +158,56 @@ void AvatarManager::clearOtherAvatars() {
|
|||
}
|
||||
_myAvatar->clearLookAtTargetAvatar();
|
||||
}
|
||||
|
||||
Avatar* AvatarManager::getAvatarFromIndex(int avatarIndex) {
|
||||
Avatar* avatar = NULL;
|
||||
int numAvatars = _avatarHash.count();
|
||||
if (avatarIndex < numAvatars) {
|
||||
QUuid key = (_avatarHash.keys())[avatarIndex];
|
||||
|
||||
const AvatarSharedPointer& avatarPointer = _avatarHash.value(key);
|
||||
avatar = static_cast<Avatar*>(avatarPointer.data());
|
||||
}
|
||||
|
||||
return avatar;
|
||||
}
|
||||
|
||||
void AvatarManager::addAvatarLocalLight(int avatarIndex) {
|
||||
Avatar* avatar = getAvatarFromIndex(avatarIndex);
|
||||
if (avatar) {
|
||||
avatar->addLocalLight();
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarManager::removeAvatarLocalLight(int avatarIndex) {
|
||||
Avatar* avatar = getAvatarFromIndex(avatarIndex);
|
||||
if (avatar) {
|
||||
avatar->removeLocalLight();
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarManager::setAvatarLightDirection(const glm::vec3& direction, int lightIndex, int avatarIndex) {
|
||||
Avatar* avatar = getAvatarFromIndex(avatarIndex);
|
||||
if (avatar) {
|
||||
avatar->setLocalLightDirection(direction, lightIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarManager::setAvatarLightColor(const glm::vec3& color, int lightIndex, int avatarIndex) {
|
||||
Avatar* avatar = getAvatarFromIndex(avatarIndex);
|
||||
if (avatar) {
|
||||
avatar->setLocalLightColor(color, lightIndex);
|
||||
}
|
||||
}
|
||||
|
||||
int AvatarManager::getNumAvatars() {
|
||||
return _avatarHash.count();
|
||||
}
|
||||
|
||||
QString AvatarManager::getAvatarHashKey(int index) {
|
||||
QString id = ((_avatarHash.keys())[index]).toString();
|
||||
std::string idString = id.toStdString();
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ class MyAvatar;
|
|||
|
||||
class AvatarManager : public AvatarHashMap {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AvatarManager(QObject* parent = 0);
|
||||
|
||||
|
@ -36,6 +37,14 @@ public:
|
|||
|
||||
void clearOtherAvatars();
|
||||
|
||||
public slots:
|
||||
void setAvatarLightColor(const glm::vec3& color, int lightIndex, int avatarIndex);
|
||||
void setAvatarLightDirection(const glm::vec3& direction, int lightIndex, int avatarIndex);
|
||||
void removeAvatarLocalLight(int avatarIndex);
|
||||
void addAvatarLocalLight(int avatarIndex);
|
||||
int getNumAvatars();
|
||||
QString getAvatarHashKey(int index);
|
||||
|
||||
private:
|
||||
AvatarManager(const AvatarManager& other);
|
||||
|
||||
|
@ -44,6 +53,8 @@ private:
|
|||
|
||||
AvatarSharedPointer newSharedAvatar();
|
||||
|
||||
Avatar* getAvatarFromIndex(int avatarIndex);
|
||||
|
||||
// virtual override
|
||||
AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
|
||||
|
||||
|
|
|
@ -185,10 +185,10 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
{
|
||||
PerformanceTimer perfTimer("hair");
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
simulateHair(deltaTime);
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->simulate(deltaTime);
|
||||
}
|
||||
_hair.setAcceleration(getAcceleration() * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.setAngularVelocity(getAngularVelocity() + getHead()->getAngularVelocity() * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()) * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.simulate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -884,11 +884,17 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
const glm::vec3 cameraPos = camera->getPosition() + (camera->getRotation() * glm::vec3(0.0f, 0.0f, 1.0f)) * camera->getDistance();
|
||||
if (shouldRenderHead(cameraPos, renderMode)) {
|
||||
getHead()->render(1.0f, modelRenderMode);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
renderHair();
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->render();
|
||||
}
|
||||
// Render Hair
|
||||
glPushMatrix();
|
||||
glm::vec3 headPosition = getHead()->getPosition();
|
||||
glTranslatef(headPosition.x, headPosition.y, headPosition.z);
|
||||
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
_hair.render();
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
getHand()->render(true, modelRenderMode);
|
||||
|
|
|
@ -44,7 +44,9 @@ MIDIManager::~MIDIManager() {
|
|||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAVE_RTMIDI
|
||||
const int DEFAULT_MIDI_PORT = 0;
|
||||
#endif
|
||||
|
||||
void MIDIManager::openDefaultPort() {
|
||||
#ifdef HAVE_RTMIDI
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "Application.h"
|
||||
#include "LocationManager.h"
|
||||
#include <UserActivityLogger.h>
|
||||
|
||||
const QString GET_USER_ADDRESS = "/api/v1/users/%1/address";
|
||||
const QString GET_PLACE_ADDRESS = "/api/v1/places/%1";
|
||||
|
@ -60,23 +61,30 @@ void LocationManager::createNamedLocation(NamedLocation* namedLocation) {
|
|||
}
|
||||
|
||||
void LocationManager::goTo(QString destination) {
|
||||
|
||||
const QString USER_DESTINATION_TYPE = "user";
|
||||
const QString PLACE_DESTINATION_TYPE = "place";
|
||||
const QString OTHER_DESTINATION_TYPE = "coordinate_or_username";
|
||||
|
||||
if (destination.startsWith("@")) {
|
||||
// remove '@' and go to user
|
||||
goToUser(destination.remove(0, 1));
|
||||
QString destinationUser = destination.remove(0, 1);
|
||||
UserActivityLogger::getInstance().wentTo(USER_DESTINATION_TYPE, destinationUser);
|
||||
goToUser(destinationUser);
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.startsWith("#")) {
|
||||
// remove '#' and go to named place
|
||||
goToPlace(destination.remove(0, 1));
|
||||
QString destinationPlace = destination.remove(0, 1);
|
||||
UserActivityLogger::getInstance().wentTo(PLACE_DESTINATION_TYPE, destinationPlace);
|
||||
goToPlace(destinationPlace);
|
||||
return;
|
||||
}
|
||||
|
||||
// go to coordinate destination or to Username
|
||||
if (!goToDestination(destination)) {
|
||||
destination = QString(QUrl::toPercentEncoding(destination));
|
||||
|
||||
UserActivityLogger::getInstance().wentTo(OTHER_DESTINATION_TYPE, destination);
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "goToAddressFromResponse";
|
||||
|
|
|
@ -162,36 +162,25 @@ void Stats::drawBackground(unsigned int rgba, int x, int y, int width, int heigh
|
|||
}
|
||||
|
||||
bool Stats::includeTimingRecord(const QString& name) {
|
||||
bool included = false;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
|
||||
|
||||
if (name == "idle/update") {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming) ||
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming);
|
||||
} else if (name == "idle/updateGL") {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming);
|
||||
} else if (name.startsWith("idle/update")) {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming);
|
||||
} else if (name.startsWith("idle/")) {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming);
|
||||
} else if (name.startsWith("MyAvatar::simulate")) {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandAvatarSimulateTiming);
|
||||
} else if (name.startsWith("MyAvatar::update/") || name.startsWith("updateMyAvatar")) {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandAvatarUpdateTiming);
|
||||
} else if (name.startsWith("MyAvatar::")) {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandMiscAvatarTiming);
|
||||
} else if (name == "paintGL/displaySide") {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandDisplaySideTiming) ||
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
|
||||
} else if (name.startsWith("paintGL/displaySide/")) {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandDisplaySideTiming);
|
||||
} else if (name.startsWith("paintGL/")) {
|
||||
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
|
||||
} else {
|
||||
included = true; // include everything else
|
||||
if (name.startsWith("/idle/update/")) {
|
||||
if (name.startsWith("/idle/update/myAvatar/")) {
|
||||
if (name.startsWith("/idle/update/myAvatar/simulate/")) {
|
||||
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandMyAvatarSimulateTiming);
|
||||
}
|
||||
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandMyAvatarTiming);
|
||||
} else if (name.startsWith("/idle/update/otherAvatars/")) {
|
||||
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandOtherAvatarTiming);
|
||||
}
|
||||
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming);
|
||||
} else if (name.startsWith("/idle/updateGL/paintGL/")) {
|
||||
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
|
||||
} else if (name.startsWith("/paintGL/")) {
|
||||
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return included;
|
||||
return false;
|
||||
}
|
||||
|
||||
// display expanded or contracted stats
|
||||
|
@ -346,26 +335,28 @@ void Stats::display(
|
|||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color);
|
||||
|
||||
char downstreamAudioStatsString[30];
|
||||
char downstreamAudioStatsString[512];
|
||||
|
||||
AudioStreamStats downstreamAudioStreamStats = audio->getDownstreamAudioStreamStats();
|
||||
|
||||
sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %u/?/%u", downstreamAudioStreamStats._packetStreamStats.getLostRate()*100.0f,
|
||||
sprintf(downstreamAudioStatsString, " mix: %.2f%%/%.2f%%, %u/?/%u", downstreamAudioStreamStats._packetStreamStats.getLostRate()*100.0f,
|
||||
downstreamAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f,
|
||||
downstreamAudioStreamStats._ringBufferFramesAvailable, downstreamAudioStreamStats._ringBufferDesiredJitterBufferFrames);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);
|
||||
|
||||
sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", downstreamAudioStreamStats._timeGapMin,
|
||||
downstreamAudioStreamStats._timeGapMax, downstreamAudioStreamStats._timeGapAverage,
|
||||
sprintf(downstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(downstreamAudioStreamStats._timeGapMin).toLatin1().data(),
|
||||
formatUsecTime(downstreamAudioStreamStats._timeGapMax).toLatin1().data(),
|
||||
formatUsecTime(downstreamAudioStreamStats._timeGapAverage).toLatin1().data(),
|
||||
downstreamAudioStreamStats._ringBufferStarveCount, downstreamAudioStreamStats._ringBufferOverflowCount);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);
|
||||
|
||||
sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/?", downstreamAudioStreamStats._timeGapWindowMin,
|
||||
downstreamAudioStreamStats._timeGapWindowMax, downstreamAudioStreamStats._timeGapWindowAverage,
|
||||
sprintf(downstreamAudioStatsString, " %s/%s/%s, %u/?", formatUsecTime(downstreamAudioStreamStats._timeGapWindowMin).toLatin1().data(),
|
||||
formatUsecTime(downstreamAudioStreamStats._timeGapWindowMax).toLatin1().data(),
|
||||
formatUsecTime(downstreamAudioStreamStats._timeGapWindowAverage).toLatin1().data(),
|
||||
downstreamAudioStreamStats._ringBufferConsecutiveNotMixedCount);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
|
@ -376,11 +367,11 @@ void Stats::display(
|
|||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color);
|
||||
|
||||
char upstreamAudioStatsString[30];
|
||||
char upstreamAudioStatsString[512];
|
||||
|
||||
const AudioStreamStats& audioMixerAvatarAudioStreamStats = audio->getAudioMixerAvatarStreamAudioStats();
|
||||
|
||||
sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", audioMixerAvatarAudioStreamStats._packetStreamStats.getLostRate()*100.0f,
|
||||
sprintf(upstreamAudioStatsString, " mic: %.2f%%/%.2f%%, %u/%u/%u", audioMixerAvatarAudioStreamStats._packetStreamStats.getLostRate()*100.0f,
|
||||
audioMixerAvatarAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f,
|
||||
audioMixerAvatarAudioStreamStats._ringBufferFramesAvailable, audioMixerAvatarAudioStreamStats._ringBufferCurrentJitterBufferFrames,
|
||||
audioMixerAvatarAudioStreamStats._ringBufferDesiredJitterBufferFrames);
|
||||
|
@ -388,15 +379,17 @@ void Stats::display(
|
|||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
|
||||
|
||||
sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapMin,
|
||||
audioMixerAvatarAudioStreamStats._timeGapMax, audioMixerAvatarAudioStreamStats._timeGapAverage,
|
||||
sprintf(upstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapMin).toLatin1().data(),
|
||||
formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapMax).toLatin1().data(),
|
||||
formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapAverage).toLatin1().data(),
|
||||
audioMixerAvatarAudioStreamStats._ringBufferStarveCount, audioMixerAvatarAudioStreamStats._ringBufferOverflowCount);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
|
||||
|
||||
sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapWindowMin,
|
||||
audioMixerAvatarAudioStreamStats._timeGapWindowMax, audioMixerAvatarAudioStreamStats._timeGapWindowAverage,
|
||||
sprintf(upstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapWindowMin).toLatin1().data(),
|
||||
formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapWindowMax).toLatin1().data(),
|
||||
formatUsecTime(audioMixerAvatarAudioStreamStats._timeGapWindowAverage).toLatin1().data(),
|
||||
audioMixerAvatarAudioStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarAudioStreamStats._ringBufferSilentFramesDropped);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
|
@ -404,7 +397,7 @@ void Stats::display(
|
|||
|
||||
foreach(const AudioStreamStats& injectedStreamAudioStats, audioMixerInjectedStreamAudioStatsMap) {
|
||||
|
||||
sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", injectedStreamAudioStats._packetStreamStats.getLostRate()*100.0f,
|
||||
sprintf(upstreamAudioStatsString, " inj: %.2f%%/%.2f%%, %u/%u/%u", injectedStreamAudioStats._packetStreamStats.getLostRate()*100.0f,
|
||||
injectedStreamAudioStats._packetStreamWindowStats.getLostRate() * 100.0f,
|
||||
injectedStreamAudioStats._ringBufferFramesAvailable, injectedStreamAudioStats._ringBufferCurrentJitterBufferFrames,
|
||||
injectedStreamAudioStats._ringBufferDesiredJitterBufferFrames);
|
||||
|
@ -412,15 +405,17 @@ void Stats::display(
|
|||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
|
||||
|
||||
sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapMin,
|
||||
injectedStreamAudioStats._timeGapMax, injectedStreamAudioStats._timeGapAverage,
|
||||
sprintf(upstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(injectedStreamAudioStats._timeGapMin).toLatin1().data(),
|
||||
formatUsecTime(injectedStreamAudioStats._timeGapMax).toLatin1().data(),
|
||||
formatUsecTime(injectedStreamAudioStats._timeGapAverage).toLatin1().data(),
|
||||
injectedStreamAudioStats._ringBufferStarveCount, injectedStreamAudioStats._ringBufferOverflowCount);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
|
||||
|
||||
sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapWindowMin,
|
||||
injectedStreamAudioStats._timeGapWindowMax, injectedStreamAudioStats._timeGapWindowAverage,
|
||||
sprintf(upstreamAudioStatsString, " %s/%s/%s, %u/%u", formatUsecTime(injectedStreamAudioStats._timeGapWindowMin).toLatin1().data(),
|
||||
formatUsecTime(injectedStreamAudioStats._timeGapWindowMax).toLatin1().data(),
|
||||
formatUsecTime(injectedStreamAudioStats._timeGapWindowAverage).toLatin1().data(),
|
||||
injectedStreamAudioStats._ringBufferConsecutiveNotMixedCount, injectedStreamAudioStats._ringBufferSilentFramesDropped);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
|
@ -435,7 +430,7 @@ void Stats::display(
|
|||
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||
glm::vec3 avatarPos = myAvatar->getPosition();
|
||||
|
||||
lines = _expanded ? 5 : 3;
|
||||
lines = _expanded ? 8 : 3;
|
||||
|
||||
drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
||||
horizontalOffset += 5;
|
||||
|
@ -477,6 +472,41 @@ void Stats::display(
|
|||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downloads.str().c_str(), color);
|
||||
|
||||
int internal = 0, leaves = 0;
|
||||
int sendProgress = 0, sendTotal = 0;
|
||||
int receiveProgress = 0, receiveTotal = 0;
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelSystemClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
client->getData().countNodes(internal, leaves, Application::getInstance()->getMetavoxels()->getLOD());
|
||||
client->getSequencer().addReliableChannelStats(sendProgress, sendTotal, receiveProgress, receiveTotal);
|
||||
}
|
||||
}
|
||||
}
|
||||
stringstream nodes;
|
||||
nodes << "Metavoxels: " << (internal + leaves);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodes.str().c_str(), color);
|
||||
|
||||
stringstream nodeTypes;
|
||||
nodeTypes << "Internal: " << internal << " Leaves: " << leaves;
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodeTypes.str().c_str(), color);
|
||||
|
||||
if (sendTotal > 0 || receiveTotal > 0) {
|
||||
stringstream reliableStats;
|
||||
if (sendTotal > 0) {
|
||||
reliableStats << "Upload: " << (sendProgress * 100 / sendTotal) << "% ";
|
||||
}
|
||||
if (receiveTotal > 0) {
|
||||
reliableStats << "Download: " << (receiveProgress * 100 / receiveTotal) << "%";
|
||||
}
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, reliableStats.str().c_str(), color);
|
||||
}
|
||||
}
|
||||
|
||||
verticalOffset = 0;
|
||||
|
|
|
@ -32,6 +32,7 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::
|
|||
_shouldLoopbackForNode(false),
|
||||
_shouldOutputStarveDebug(true),
|
||||
_isStereo(isStereo),
|
||||
_nextOutputTrailingLoudness(0.0f),
|
||||
_listenerUnattenuatedZone(NULL),
|
||||
_lastFrameReceivedTime(0),
|
||||
_interframeTimeGapStatsForJitterCalc(TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES, TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS),
|
||||
|
@ -121,13 +122,14 @@ void PositionalAudioRingBuffer::updateNextOutputTrailingLoudness() {
|
|||
// ForBoundarySamples means that we expect the number of samples not to roll of the end of the ring buffer
|
||||
float nextLoudness = 0;
|
||||
|
||||
for (int i = 0; i < _numFrameSamples; ++i) {
|
||||
nextLoudness += fabsf(_nextOutput[i]);
|
||||
if (samplesAvailable() >= _numFrameSamples) {
|
||||
for (int i = 0; i < _numFrameSamples; ++i) {
|
||||
nextLoudness += fabsf(_nextOutput[i]);
|
||||
}
|
||||
nextLoudness /= _numFrameSamples;
|
||||
nextLoudness /= MAX_SAMPLE_VALUE;
|
||||
}
|
||||
|
||||
nextLoudness /= _numFrameSamples;
|
||||
nextLoudness /= MAX_SAMPLE_VALUE;
|
||||
|
||||
const int TRAILING_AVERAGE_FRAMES = 100;
|
||||
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
|
||||
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
|
||||
|
@ -137,7 +139,7 @@ void PositionalAudioRingBuffer::updateNextOutputTrailingLoudness() {
|
|||
_nextOutputTrailingLoudness = nextLoudness;
|
||||
} else {
|
||||
_nextOutputTrailingLoudness = (_nextOutputTrailingLoudness * PREVIOUS_FRAMES_RATIO) + (CURRENT_FRAME_RATIO * nextLoudness);
|
||||
|
||||
|
||||
if (_nextOutputTrailingLoudness < LOUDNESS_EPSILON) {
|
||||
_nextOutputTrailingLoudness = 0;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QReadLocker>
|
||||
#include <QScriptEngine>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include "AttributeRegistry.h"
|
||||
#include "MetavoxelData.h"
|
||||
|
@ -69,6 +71,7 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute
|
|||
if (!attribute) {
|
||||
return attribute;
|
||||
}
|
||||
QWriteLocker locker(&_attributesLock);
|
||||
AttributePointer& pointer = _attributes[attribute->getName()];
|
||||
if (!pointer) {
|
||||
pointer = attribute;
|
||||
|
@ -77,9 +80,15 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute
|
|||
}
|
||||
|
||||
void AttributeRegistry::deregisterAttribute(const QString& name) {
|
||||
QWriteLocker locker(&_attributesLock);
|
||||
_attributes.remove(name);
|
||||
}
|
||||
|
||||
AttributePointer AttributeRegistry::getAttribute(const QString& name) {
|
||||
QReadLocker locker(&_attributesLock);
|
||||
return _attributes.value(name);
|
||||
}
|
||||
|
||||
QScriptValue AttributeRegistry::getAttribute(QScriptContext* context, QScriptEngine* engine) {
|
||||
return engine->newQObject(getInstance()->getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership,
|
||||
QScriptEngine::PreferExistingWrapperObject);
|
||||
|
@ -204,7 +213,12 @@ void Attribute::writeMetavoxelDelta(const MetavoxelNode& root, const MetavoxelNo
|
|||
}
|
||||
|
||||
void Attribute::readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
data.getRoot(state.attribute)->readSubdivision(state);
|
||||
// copy if changed
|
||||
MetavoxelNode* oldRoot = data.getRoot(state.attribute);
|
||||
MetavoxelNode* newRoot = oldRoot->readSubdivision(state);
|
||||
if (newRoot != oldRoot) {
|
||||
data.setRoot(state.attribute, newRoot);
|
||||
}
|
||||
}
|
||||
|
||||
void Attribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state) {
|
||||
|
@ -559,6 +573,10 @@ void SpannerSetAttribute::readMetavoxelRoot(MetavoxelData& data, MetavoxelStream
|
|||
}
|
||||
data.insert(state.attribute, object);
|
||||
}
|
||||
// even if the root is empty, it should still exist
|
||||
if (!data.getRoot(state.attribute)) {
|
||||
data.createRoot(state.attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state) {
|
||||
|
@ -577,6 +595,10 @@ void SpannerSetAttribute::readMetavoxelDelta(MetavoxelData& data,
|
|||
}
|
||||
data.toggle(state.attribute, object);
|
||||
}
|
||||
// even if the root is empty, it should still exist
|
||||
if (!data.getRoot(state.attribute)) {
|
||||
data.createRoot(state.attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::writeMetavoxelDelta(const MetavoxelNode& root,
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QReadWriteLock>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
@ -61,11 +62,14 @@ public:
|
|||
void deregisterAttribute(const QString& name);
|
||||
|
||||
/// Retrieves an attribute by name.
|
||||
AttributePointer getAttribute(const QString& name) const { return _attributes.value(name); }
|
||||
AttributePointer getAttribute(const QString& name);
|
||||
|
||||
/// Returns a reference to the attribute hash.
|
||||
const QHash<QString, AttributePointer>& getAttributes() const { return _attributes; }
|
||||
|
||||
/// Returns a reference to the attributes lock.
|
||||
QReadWriteLock& getAttributesLock() { return _attributesLock; }
|
||||
|
||||
/// Returns a reference to the standard SharedObjectPointer "guide" attribute.
|
||||
const AttributePointer& getGuideAttribute() const { return _guideAttribute; }
|
||||
|
||||
|
@ -92,6 +96,8 @@ private:
|
|||
static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
QHash<QString, AttributePointer> _attributes;
|
||||
QReadWriteLock _attributesLock;
|
||||
|
||||
AttributePointer _guideAttribute;
|
||||
AttributePointer _spannersAttribute;
|
||||
AttributePointer _colorAttribute;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <QCryptographicHash>
|
||||
#include <QDataStream>
|
||||
#include <QMetaType>
|
||||
#include <QScriptEngine>
|
||||
#include <QScriptValueIterator>
|
||||
#include <QUrl>
|
||||
#include <QtDebug>
|
||||
|
@ -87,6 +88,12 @@ IDStreamer& IDStreamer::operator>>(int& value) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
void Bitstream::preThreadingInit() {
|
||||
getObjectStreamers();
|
||||
getEnumStreamers();
|
||||
getEnumStreamersByName();
|
||||
}
|
||||
|
||||
int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) {
|
||||
getMetaObjects().insert(className, metaObject);
|
||||
|
||||
|
@ -122,6 +129,21 @@ QList<const QMetaObject*> Bitstream::getMetaObjectSubClasses(const QMetaObject*
|
|||
return getMetaObjectSubClasses().values(metaObject);
|
||||
}
|
||||
|
||||
QScriptValue sharedObjectPointerToScriptValue(QScriptEngine* engine, const SharedObjectPointer& pointer) {
|
||||
return pointer ? engine->newQObject(pointer.data()) : engine->nullValue();
|
||||
}
|
||||
|
||||
void sharedObjectPointerFromScriptValue(const QScriptValue& object, SharedObjectPointer& pointer) {
|
||||
pointer = qobject_cast<SharedObject*>(object.toQObject());
|
||||
}
|
||||
|
||||
void Bitstream::registerTypes(QScriptEngine* engine) {
|
||||
foreach (const QMetaObject* metaObject, getMetaObjects()) {
|
||||
engine->globalObject().setProperty(metaObject->className(), engine->newQMetaObject(metaObject));
|
||||
}
|
||||
qScriptRegisterMetaType(engine, sharedObjectPointerToScriptValue, sharedObjectPointerFromScriptValue);
|
||||
}
|
||||
|
||||
Bitstream::Bitstream(QDataStream& underlying, MetadataType metadataType, GenericsMode genericsMode, QObject* parent) :
|
||||
QObject(parent),
|
||||
_underlying(underlying),
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
class QByteArray;
|
||||
class QColor;
|
||||
class QDataStream;
|
||||
class QScriptEngine;
|
||||
class QScriptValue;
|
||||
class QUrl;
|
||||
|
||||
|
@ -290,6 +291,11 @@ public:
|
|||
QHash<int, SharedObjectPointer> sharedObjectValues;
|
||||
};
|
||||
|
||||
/// Performs all of the various lazily initializations (of object streamers, etc.) If multiple threads need to use
|
||||
/// Bitstream instances, call this beforehand to prevent errors from occurring when multiple threads attempt lazy
|
||||
/// initialization simultaneously.
|
||||
static void preThreadingInit();
|
||||
|
||||
/// Registers a metaobject under its name so that instances of it can be streamed. Consider using the REGISTER_META_OBJECT
|
||||
/// at the top level of the source file associated with the class rather than calling this function directly.
|
||||
/// \return zero; the function only returns a value so that it can be used in static initialization
|
||||
|
@ -315,6 +321,10 @@ public:
|
|||
/// subclasses.
|
||||
static QList<const QMetaObject*> getMetaObjectSubClasses(const QMetaObject* metaObject);
|
||||
|
||||
/// Configures the supplied script engine with our registered meta-objects, allowing all of them to be instantiated from
|
||||
/// scripts.
|
||||
static void registerTypes(QScriptEngine* engine);
|
||||
|
||||
enum MetadataType { NO_METADATA, HASH_METADATA, FULL_METADATA };
|
||||
|
||||
enum GenericsMode { NO_GENERICS, FALLBACK_GENERICS, ALL_GENERICS };
|
||||
|
|
|
@ -79,6 +79,24 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) {
|
|||
return channel;
|
||||
}
|
||||
|
||||
void DatagramSequencer::addReliableChannelStats(int& sendProgress, int& sendTotal,
|
||||
int& receiveProgress, int& receiveTotal) const {
|
||||
foreach (ReliableChannel* channel, _reliableOutputChannels) {
|
||||
int sent, total;
|
||||
if (channel->getMessageSendProgress(sent, total)) {
|
||||
sendProgress += sent;
|
||||
sendTotal += total;
|
||||
}
|
||||
}
|
||||
foreach (ReliableChannel* channel, _reliableInputChannels) {
|
||||
int received, total;
|
||||
if (channel->getMessageReceiveProgress(received, total)) {
|
||||
receiveProgress += received;
|
||||
receiveTotal += total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DatagramSequencer::notePacketGroup(int desiredPackets) {
|
||||
// figure out how much data we have enqueued and increase the number of packets desired
|
||||
int totalAvailable = 0;
|
||||
|
@ -684,6 +702,8 @@ void ReliableChannel::endMessage() {
|
|||
|
||||
quint32 length = _buffer.pos() - _messageLengthPlaceholder;
|
||||
_buffer.writeBytes(_messageLengthPlaceholder, sizeof(quint32), (const char*)&length);
|
||||
_messageReceivedOffset = getBytesWritten();
|
||||
_messageSize = length;
|
||||
}
|
||||
|
||||
void ReliableChannel::sendMessage(const QVariant& message) {
|
||||
|
@ -692,6 +712,26 @@ void ReliableChannel::sendMessage(const QVariant& message) {
|
|||
endMessage();
|
||||
}
|
||||
|
||||
bool ReliableChannel::getMessageSendProgress(int& sent, int& total) const {
|
||||
if (!_messagesEnabled || _offset >= _messageReceivedOffset) {
|
||||
return false;
|
||||
}
|
||||
sent = qMax(0, _messageSize - (_messageReceivedOffset - _offset));
|
||||
total = _messageSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReliableChannel::getMessageReceiveProgress(int& received, int& total) const {
|
||||
if (!_messagesEnabled || _buffer.bytesAvailable() < (int)sizeof(quint32)) {
|
||||
return false;
|
||||
}
|
||||
quint32 length;
|
||||
_buffer.readBytes(_buffer.pos(), sizeof(quint32), (char*)&length);
|
||||
total = length;
|
||||
received = _buffer.bytesAvailable();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReliableChannel::sendClearSharedObjectMessage(int id) {
|
||||
ClearSharedObjectMessage message = { id };
|
||||
sendMessage(QVariant::fromValue(message));
|
||||
|
@ -717,7 +757,8 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
|
|||
_offset(0),
|
||||
_writePosition(0),
|
||||
_writePositionResetPacketNumber(0),
|
||||
_messagesEnabled(true) {
|
||||
_messagesEnabled(true),
|
||||
_messageReceivedOffset(0) {
|
||||
|
||||
_buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly);
|
||||
_dataStream.setByteOrder(QDataStream::LittleEndian);
|
||||
|
|
|
@ -108,6 +108,9 @@ public:
|
|||
/// Returns the intput channel at the specified index, creating it if necessary.
|
||||
ReliableChannel* getReliableInputChannel(int index = 0);
|
||||
|
||||
/// Adds stats for all reliable channels to the referenced variables.
|
||||
void addReliableChannelStats(int& sendProgress, int& sendTotal, int& receiveProgress, int& receiveTotal) const;
|
||||
|
||||
/// Notes that we're sending a group of packets.
|
||||
/// \param desiredPackets the number of packets we'd like to write in the group
|
||||
/// \return the number of packets to write in the group
|
||||
|
@ -376,6 +379,14 @@ public:
|
|||
/// writes the message to the bitstream, then calls endMessage).
|
||||
void sendMessage(const QVariant& message);
|
||||
|
||||
/// Determines the number of bytes uploaded towards the currently pending message.
|
||||
/// \return true if there is a message pending, in which case the sent and total arguments will be set
|
||||
bool getMessageSendProgress(int& sent, int& total) const;
|
||||
|
||||
/// Determines the number of bytes downloaded towards the currently pending message.
|
||||
/// \return true if there is a message pending, in which case the received and total arguments will be set
|
||||
bool getMessageReceiveProgress(int& received, int& total) const;
|
||||
|
||||
signals:
|
||||
|
||||
/// Fired when a framed message has been received on this channel.
|
||||
|
@ -415,7 +426,9 @@ private:
|
|||
int _writePositionResetPacketNumber;
|
||||
SpanList _acknowledged;
|
||||
bool _messagesEnabled;
|
||||
int _messageLengthPlaceholder;
|
||||
int _messageLengthPlaceholder; ///< the location in the buffer of the message length for the current message
|
||||
int _messageReceivedOffset; ///< when reached, indicates that the most recent sent message has been received
|
||||
int _messageSize; ///< the size of the most recent sent message; only valid when _messageReceivedOffset has been set
|
||||
};
|
||||
|
||||
#endif // hifi_DatagramSequencer_h
|
||||
|
|
|
@ -32,6 +32,8 @@ public:
|
|||
PacketRecord* baselineReceiveRecord = NULL);
|
||||
virtual ~Endpoint();
|
||||
|
||||
const DatagramSequencer& getSequencer() const { return _sequencer; }
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual int parseData(const QByteArray& packet);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include "MetavoxelClientManager.h"
|
||||
#include "MetavoxelMessages.h"
|
||||
|
||||
|
@ -53,7 +55,24 @@ SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(cons
|
|||
return closestSpanner;
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::setSphere(const glm::vec3& center, float radius, const QColor& color) {
|
||||
Sphere* sphere = new Sphere();
|
||||
sphere->setTranslation(center);
|
||||
sphere->setScale(radius);
|
||||
sphere->setColor(color);
|
||||
setSpanner(sphere);
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::setSpanner(const SharedObjectPointer& object, bool reliable) {
|
||||
MetavoxelEditMessage edit = { QVariant::fromValue(SetSpannerEdit(object)) };
|
||||
applyEdit(edit, reliable);
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "applyEdit", Q_ARG(const MetavoxelEditMessage&, edit), Q_ARG(bool, reliable));
|
||||
return;
|
||||
}
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
|
|
|
@ -28,6 +28,10 @@ public:
|
|||
|
||||
SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const AttributePointer& attribute, float& distance);
|
||||
|
||||
Q_INVOKABLE void setSphere(const glm::vec3& center, float radius, const QColor& color = QColor(Qt::gray));
|
||||
|
||||
Q_INVOKABLE void setSpanner(const SharedObjectPointer& object, bool reliable = false);
|
||||
|
||||
Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebugStateSaver>
|
||||
#include <QScriptEngine>
|
||||
#include <QtDebug>
|
||||
|
||||
|
@ -46,13 +47,8 @@ bool MetavoxelLOD::becameSubdivided(const glm::vec3& minimum, float size,
|
|||
if (!shouldSubdivide(minimum, size, multiplier)) {
|
||||
return false; // this one must be subdivided
|
||||
}
|
||||
// the general check is whether we've gotten closer (as multiplied by the threshold) to any point in the volume,
|
||||
// which we approximate as a sphere for simplicity
|
||||
float halfSize = size * 0.5f;
|
||||
glm::vec3 center = minimum + glm::vec3(halfSize, halfSize, halfSize);
|
||||
float radius = sqrtf(3 * halfSize * halfSize);
|
||||
return qMax(0.0f, glm::distance(position, center) - radius) * threshold <=
|
||||
qMax(0.0f, glm::distance(reference.position, center) - radius) * reference.threshold;
|
||||
// TODO: find some way of culling subtrees that can't possibly contain subdivided nodes
|
||||
return true;
|
||||
}
|
||||
|
||||
MetavoxelData::MetavoxelData() : _size(1.0f) {
|
||||
|
@ -602,12 +598,18 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, const MetavoxelLO
|
|||
}
|
||||
}
|
||||
|
||||
MetavoxelNode* MetavoxelData::createRoot(const AttributePointer& attribute) {
|
||||
MetavoxelNode*& root = _roots[attribute];
|
||||
if (root) {
|
||||
root->decrementReferenceCount(attribute);
|
||||
void MetavoxelData::setRoot(const AttributePointer& attribute, MetavoxelNode* root) {
|
||||
MetavoxelNode*& rootReference = _roots[attribute];
|
||||
if (rootReference) {
|
||||
rootReference->decrementReferenceCount(attribute);
|
||||
}
|
||||
return root = new MetavoxelNode(attribute);
|
||||
rootReference = root;
|
||||
}
|
||||
|
||||
MetavoxelNode* MetavoxelData::createRoot(const AttributePointer& attribute) {
|
||||
MetavoxelNode* root = new MetavoxelNode(attribute);
|
||||
setRoot(attribute, root);
|
||||
return root;
|
||||
}
|
||||
|
||||
bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod) const {
|
||||
|
@ -627,6 +629,33 @@ bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& l
|
|||
return true;
|
||||
}
|
||||
|
||||
void MetavoxelData::countNodes(int& internal, int& leaves, const MetavoxelLOD& lod) const {
|
||||
glm::vec3 minimum = getMinimum();
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
it.value()->countNodes(it.key(), minimum, _size, lod, internal, leaves);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelData::dumpStats(QDebug debug) const {
|
||||
QDebugStateSaver saver(debug);
|
||||
debug.nospace() << "[size=" << _size << ", roots=[";
|
||||
int totalInternal = 0, totalLeaves = 0;
|
||||
glm::vec3 minimum = getMinimum();
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
if (it != _roots.constBegin()) {
|
||||
debug << ", ";
|
||||
}
|
||||
debug << it.key()->getName() << " (" << it.key()->metaObject()->className() << "): ";
|
||||
int internal = 0, leaves = 0;
|
||||
it.value()->countNodes(it.key(), minimum, _size, MetavoxelLOD(), internal, leaves);
|
||||
debug << internal << " internal, " << leaves << " leaves, " << (internal + leaves) << " total";
|
||||
totalInternal += internal;
|
||||
totalLeaves += leaves;
|
||||
}
|
||||
debug << "], totalInternal=" << totalInternal << ", totalLeaves=" << totalLeaves <<
|
||||
", grandTotal=" << (totalInternal + totalLeaves) << "]";
|
||||
}
|
||||
|
||||
bool MetavoxelData::operator==(const MetavoxelData& other) const {
|
||||
return _size == other._size && _roots == other._roots;
|
||||
}
|
||||
|
@ -733,7 +762,7 @@ void MetavoxelNode::mergeChildren(const AttributePointer& attribute, bool postRe
|
|||
childValues[i] = _children[i]->_attributeValue;
|
||||
allLeaves &= _children[i]->isLeaf();
|
||||
}
|
||||
if (attribute->merge(_attributeValue, childValues, postRead) && allLeaves) {
|
||||
if (attribute->merge(_attributeValue, childValues, postRead) && allLeaves && !postRead) {
|
||||
clearChildren(attribute);
|
||||
}
|
||||
}
|
||||
|
@ -815,10 +844,14 @@ void MetavoxelNode::readDelta(const MetavoxelNode& reference, MetavoxelStreamSta
|
|||
_children[i] = new MetavoxelNode(state.attribute);
|
||||
_children[i]->readDelta(*reference._children[i], nextState);
|
||||
} else {
|
||||
_children[i] = reference._children[i];
|
||||
_children[i]->incrementReferenceCount();
|
||||
if (nextState.becameSubdivided()) {
|
||||
_children[i]->readSubdivision(nextState);
|
||||
_children[i] = reference._children[i]->readSubdivision(nextState);
|
||||
if (_children[i] == reference._children[i]) {
|
||||
_children[i]->incrementReferenceCount();
|
||||
}
|
||||
} else {
|
||||
_children[i] = reference._children[i];
|
||||
_children[i]->incrementReferenceCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -860,58 +893,68 @@ void MetavoxelNode::writeDelta(const MetavoxelNode& reference, MetavoxelStreamSt
|
|||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::readSubdivision(MetavoxelStreamState& state) {
|
||||
bool leaf;
|
||||
bool subdivideReference = state.shouldSubdivideReference();
|
||||
if (!subdivideReference) {
|
||||
MetavoxelNode* MetavoxelNode::readSubdivision(MetavoxelStreamState& state) {
|
||||
if (!state.shouldSubdivideReference()) {
|
||||
bool leaf;
|
||||
state.stream >> leaf;
|
||||
} else {
|
||||
leaf = isLeaf();
|
||||
}
|
||||
if (leaf) {
|
||||
clearChildren(state.attribute);
|
||||
|
||||
} else {
|
||||
if (leaf) {
|
||||
return isLeaf() ? this : new MetavoxelNode(getAttributeValue(state.attribute));
|
||||
|
||||
} else {
|
||||
MetavoxelNode* newNode = new MetavoxelNode(getAttributeValue(state.attribute));
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
newNode->_children[i] = new MetavoxelNode(state.attribute);
|
||||
newNode->_children[i]->read(nextState);
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
} else if (!isLeaf()) {
|
||||
MetavoxelNode* node = this;
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
if (!subdivideReference) {
|
||||
clearChildren(state.attribute);
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
_children[i] = new MetavoxelNode(state.attribute);
|
||||
_children[i]->read(nextState);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
if (nextState.becameSubdivided()) {
|
||||
_children[i]->readSubdivision(nextState);
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
if (nextState.becameSubdivided()) {
|
||||
MetavoxelNode* child = _children[i]->readSubdivision(nextState);
|
||||
if (child != _children[i]) {
|
||||
if (node == this) {
|
||||
node = new MetavoxelNode(state.attribute, this);
|
||||
}
|
||||
node->_children[i] = child;
|
||||
_children[i]->decrementReferenceCount(state.attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node != this) {
|
||||
node->mergeChildren(state.attribute, true);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const {
|
||||
bool leaf = isLeaf();
|
||||
bool subdivideReference = state.shouldSubdivideReference();
|
||||
if (!subdivideReference) {
|
||||
if (!state.shouldSubdivideReference()) {
|
||||
state.stream << leaf;
|
||||
}
|
||||
if (!leaf) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
if (!subdivideReference) {
|
||||
if (!leaf) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
_children[i]->write(nextState);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
if (nextState.becameSubdivided()) {
|
||||
_children[i]->writeSubdivision(nextState);
|
||||
}
|
||||
}
|
||||
} else if (!leaf) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
if (nextState.becameSubdivided()) {
|
||||
_children[i]->writeSubdivision(nextState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1014,13 +1057,16 @@ void MetavoxelNode::destroy(const AttributePointer& attribute) {
|
|||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::clearChildren(const AttributePointer& attribute) {
|
||||
bool MetavoxelNode::clearChildren(const AttributePointer& attribute) {
|
||||
bool cleared = false;
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
if (_children[i]) {
|
||||
_children[i]->decrementReferenceCount(attribute);
|
||||
_children[i] = NULL;
|
||||
cleared = true;
|
||||
}
|
||||
}
|
||||
return cleared;
|
||||
}
|
||||
|
||||
bool MetavoxelNode::deepEquals(const AttributePointer& attribute, const MetavoxelNode& other,
|
||||
|
@ -1056,8 +1102,20 @@ void MetavoxelNode::getSpanners(const AttributePointer& attribute, const glm::ve
|
|||
}
|
||||
float nextSize = size * 0.5f;
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i);
|
||||
_children[i]->getSpanners(attribute, nextMinimum, nextSize, lod, results);
|
||||
_children[i]->getSpanners(attribute, getNextMinimum(minimum, nextSize, i), nextSize, lod, results);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::countNodes(const AttributePointer& attribute, const glm::vec3& minimum,
|
||||
float size, const MetavoxelLOD& lod, int& internal, int& leaves) const {
|
||||
if (isLeaf() || !lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) {
|
||||
leaves++;
|
||||
return;
|
||||
}
|
||||
internal++;
|
||||
float nextSize = size * 0.5f;
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
_children[i]->countNodes(attribute, getNextMinimum(minimum, nextSize, i), nextSize, lod, internal, leaves);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -118,13 +118,19 @@ public:
|
|||
void writeDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD,
|
||||
Bitstream& out, const MetavoxelLOD& lod) const;
|
||||
|
||||
MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); }
|
||||
void setRoot(const AttributePointer& attribute, MetavoxelNode* root);
|
||||
MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); }
|
||||
MetavoxelNode* createRoot(const AttributePointer& attribute);
|
||||
|
||||
/// Performs a deep comparison between this data and the specified other (as opposed to the == operator, which does a
|
||||
/// shallow comparison).
|
||||
bool deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod = MetavoxelLOD()) const;
|
||||
|
||||
/// Counts the nodes in the data.
|
||||
void countNodes(int& internalNodes, int& leaves, const MetavoxelLOD& lod = MetavoxelLOD()) const;
|
||||
|
||||
void dumpStats(QDebug debug = QDebug(QtDebugMsg)) const;
|
||||
|
||||
bool operator==(const MetavoxelData& other) const;
|
||||
bool operator!=(const MetavoxelData& other) const;
|
||||
|
||||
|
@ -195,7 +201,7 @@ public:
|
|||
void readDelta(const MetavoxelNode& reference, MetavoxelStreamState& state);
|
||||
void writeDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) const;
|
||||
|
||||
void readSubdivision(MetavoxelStreamState& state);
|
||||
MetavoxelNode* readSubdivision(MetavoxelStreamState& state);
|
||||
void writeSubdivision(MetavoxelStreamState& state) const;
|
||||
|
||||
void writeSpanners(MetavoxelStreamState& state) const;
|
||||
|
@ -211,7 +217,7 @@ public:
|
|||
|
||||
void destroy(const AttributePointer& attribute);
|
||||
|
||||
void clearChildren(const AttributePointer& attribute);
|
||||
bool clearChildren(const AttributePointer& attribute);
|
||||
|
||||
/// Performs a deep comparison between this and the specified other node.
|
||||
bool deepEquals(const AttributePointer& attribute, const MetavoxelNode& other,
|
||||
|
@ -221,6 +227,9 @@ public:
|
|||
void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum,
|
||||
float size, const MetavoxelLOD& lod, SharedObjectSet& results) const;
|
||||
|
||||
void countNodes(const AttributePointer& attribute, const glm::vec3& minimum,
|
||||
float size, const MetavoxelLOD& lod, int& internalNodes, int& leaves) const;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(MetavoxelNode)
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include "MetavoxelUtil.h"
|
||||
#include "ScriptCache.h"
|
||||
#include "StreamUtils.h"
|
||||
|
||||
static int scriptHashType = qRegisterMetaType<ScriptHash>();
|
||||
static int parameterizedURLType = qRegisterMetaType<ParameterizedURL>();
|
||||
|
@ -310,9 +311,8 @@ Box operator*(const glm::mat4& matrix, const Box& box) {
|
|||
return newBox;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& out, const Box& box) {
|
||||
return out << '(' << box.minimum.x << box.minimum.y << box.minimum.z << ") (" <<
|
||||
box.maximum.x << box.maximum.y << box.maximum.z << ')';
|
||||
QDebug& operator<<(QDebug& dbg, const Box& box) {
|
||||
return dbg.nospace() << "{type='Box', minimum=" << box.minimum << ", maximum=" << box.maximum << "}";
|
||||
}
|
||||
|
||||
QMetaObjectEditor::QMetaObjectEditor(QWidget* parent) : QWidget(parent) {
|
||||
|
|
|
@ -42,7 +42,7 @@ public:
|
|||
STREAM glm::vec3 minimum;
|
||||
STREAM glm::vec3 maximum;
|
||||
|
||||
Box(const glm::vec3& minimum = glm::vec3(), const glm::vec3& maximum = glm::vec3());
|
||||
explicit Box(const glm::vec3& minimum = glm::vec3(), const glm::vec3& maximum = glm::vec3());
|
||||
|
||||
bool contains(const glm::vec3& point) const;
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <QItemEditorFactory>
|
||||
#include <QMetaProperty>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include "Bitstream.h"
|
||||
#include "MetavoxelUtil.h"
|
||||
|
@ -27,10 +28,12 @@ SharedObject::SharedObject() :
|
|||
_remoteID(0),
|
||||
_remoteOriginID(0) {
|
||||
|
||||
QWriteLocker locker(&_weakHashLock);
|
||||
_weakHash.insert(_id, this);
|
||||
}
|
||||
|
||||
void SharedObject::setID(int id) {
|
||||
QWriteLocker locker(&_weakHashLock);
|
||||
_weakHash.remove(_id);
|
||||
_weakHash.insert(_id = id, this);
|
||||
}
|
||||
|
@ -41,7 +44,10 @@ void SharedObject::incrementReferenceCount() {
|
|||
|
||||
void SharedObject::decrementReferenceCount() {
|
||||
if (!_referenceCount.deref()) {
|
||||
_weakHash.remove(_id);
|
||||
{
|
||||
QWriteLocker locker(&_weakHashLock);
|
||||
_weakHash.remove(_id);
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
@ -127,6 +133,7 @@ void SharedObject::dump(QDebug debug) const {
|
|||
|
||||
int SharedObject::_lastID = 0;
|
||||
WeakSharedObjectHash SharedObject::_weakHash;
|
||||
QReadWriteLock SharedObject::_weakHashLock;
|
||||
|
||||
void pruneWeakSharedObjectHash(WeakSharedObjectHash& hash) {
|
||||
for (WeakSharedObjectHash::iterator it = hash.begin(); it != hash.end(); ) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QReadWriteLock>
|
||||
#include <QSet>
|
||||
#include <QWidget>
|
||||
#include <QtDebug>
|
||||
|
@ -36,6 +37,9 @@ public:
|
|||
/// Returns the weak hash under which all local shared objects are registered.
|
||||
static const WeakSharedObjectHash& getWeakHash() { return _weakHash; }
|
||||
|
||||
/// Returns a reference to the weak hash lock.
|
||||
static QReadWriteLock& getWeakHashLock() { return _weakHashLock; }
|
||||
|
||||
Q_INVOKABLE SharedObject();
|
||||
|
||||
/// Returns the unique local ID for this object.
|
||||
|
@ -85,6 +89,7 @@ private:
|
|||
|
||||
static int _lastID;
|
||||
static WeakSharedObjectHash _weakHash;
|
||||
static QReadWriteLock _weakHashLock;
|
||||
};
|
||||
|
||||
/// Removes the null references from the supplied hash.
|
||||
|
|
|
@ -146,3 +146,15 @@ void UserActivityLogger::loadedScript(QString scriptName) {
|
|||
logAction(ACTION_NAME, actionDetails);
|
||||
|
||||
}
|
||||
|
||||
void UserActivityLogger::wentTo(QString destinationType, QString destinationName) {
|
||||
const QString ACTION_NAME = "went_to";
|
||||
QJsonObject actionDetails;
|
||||
const QString DESTINATION_TYPE_KEY = "destination_type";
|
||||
const QString DESTINATION_NAME_KEY = "detination_name";
|
||||
|
||||
actionDetails.insert(DESTINATION_TYPE_KEY, destinationType);
|
||||
actionDetails.insert(DESTINATION_NAME_KEY, destinationName);
|
||||
|
||||
logAction(ACTION_NAME, actionDetails);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ public slots:
|
|||
void changedDomain(QString domainURL);
|
||||
void connectedDevice(QString typeOfDevice, QString deviceName);
|
||||
void loadedScript(QString scriptName);
|
||||
void wentTo(QString destinationType, QString destinationName);
|
||||
|
||||
private slots:
|
||||
void requestFinished(const QJsonObject& object);
|
||||
|
|
|
@ -357,15 +357,23 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
processedBytes = 0;
|
||||
|
||||
// the first part of the data is our octcode...
|
||||
int octets = numberOfThreeBitSectionsInCode(data);
|
||||
int octets = numberOfThreeBitSectionsInCode(data, length);
|
||||
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
|
||||
|
||||
// we don't actually do anything with this octcode...
|
||||
dataAt += lengthOfOctcode;
|
||||
processedBytes += lengthOfOctcode;
|
||||
|
||||
|
||||
// id
|
||||
uint32_t editID;
|
||||
|
||||
// check to make sure we have enough content to keep reading...
|
||||
if (length - (processedBytes + (int)sizeof(editID)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
|
||||
memcpy(&editID, dataAt, sizeof(editID));
|
||||
dataAt += sizeof(editID);
|
||||
processedBytes += sizeof(editID);
|
||||
|
@ -377,6 +385,14 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
// If this is a NEW_PARTICLE, then we assume that there's an additional uint32_t creatorToken, that
|
||||
// we want to send back to the creator as an map to the actual id
|
||||
uint32_t creatorTokenID;
|
||||
|
||||
// check to make sure we have enough content to keep reading...
|
||||
if (length - (processedBytes + (int)sizeof(creatorTokenID)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
|
||||
memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID));
|
||||
dataAt += sizeof(creatorTokenID);
|
||||
processedBytes += sizeof(creatorTokenID);
|
||||
|
@ -409,6 +425,12 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
}
|
||||
|
||||
// lastEdited
|
||||
// check to make sure we have enough content to keep reading...
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._lastEdited)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._lastEdited, dataAt, sizeof(newParticle._lastEdited));
|
||||
dataAt += sizeof(newParticle._lastEdited);
|
||||
processedBytes += sizeof(newParticle._lastEdited);
|
||||
|
@ -417,6 +439,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
// properties included bits
|
||||
uint16_t packetContainsBits = 0;
|
||||
if (!isNewParticle) {
|
||||
if (length - (processedBytes + (int)sizeof(packetContainsBits)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&packetContainsBits, dataAt, sizeof(packetContainsBits));
|
||||
dataAt += sizeof(packetContainsBits);
|
||||
processedBytes += sizeof(packetContainsBits);
|
||||
|
@ -425,6 +452,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// radius
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_RADIUS) == CONTAINS_RADIUS)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._radius)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._radius, dataAt, sizeof(newParticle._radius));
|
||||
dataAt += sizeof(newParticle._radius);
|
||||
processedBytes += sizeof(newParticle._radius);
|
||||
|
@ -432,6 +464,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// position
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_POSITION) == CONTAINS_POSITION)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._position)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._position, dataAt, sizeof(newParticle._position));
|
||||
dataAt += sizeof(newParticle._position);
|
||||
processedBytes += sizeof(newParticle._position);
|
||||
|
@ -439,6 +476,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// color
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_COLOR) == CONTAINS_COLOR)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._color)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(newParticle._color, dataAt, sizeof(newParticle._color));
|
||||
dataAt += sizeof(newParticle._color);
|
||||
processedBytes += sizeof(newParticle._color);
|
||||
|
@ -446,6 +488,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// velocity
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_VELOCITY) == CONTAINS_VELOCITY)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._velocity)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._velocity, dataAt, sizeof(newParticle._velocity));
|
||||
dataAt += sizeof(newParticle._velocity);
|
||||
processedBytes += sizeof(newParticle._velocity);
|
||||
|
@ -453,6 +500,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// gravity
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_GRAVITY) == CONTAINS_GRAVITY)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._gravity)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._gravity, dataAt, sizeof(newParticle._gravity));
|
||||
dataAt += sizeof(newParticle._gravity);
|
||||
processedBytes += sizeof(newParticle._gravity);
|
||||
|
@ -460,6 +512,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// damping
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_DAMPING) == CONTAINS_DAMPING)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._damping)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._damping, dataAt, sizeof(newParticle._damping));
|
||||
dataAt += sizeof(newParticle._damping);
|
||||
processedBytes += sizeof(newParticle._damping);
|
||||
|
@ -467,6 +524,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// lifetime
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_LIFETIME) == CONTAINS_LIFETIME)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._lifetime)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._lifetime, dataAt, sizeof(newParticle._lifetime));
|
||||
dataAt += sizeof(newParticle._lifetime);
|
||||
processedBytes += sizeof(newParticle._lifetime);
|
||||
|
@ -475,6 +537,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
// TODO: make inHand and shouldDie into single bits
|
||||
// inHand
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_INHAND) == CONTAINS_INHAND)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._inHand)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._inHand, dataAt, sizeof(newParticle._inHand));
|
||||
dataAt += sizeof(newParticle._inHand);
|
||||
processedBytes += sizeof(newParticle._inHand);
|
||||
|
@ -482,6 +549,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// shouldDie
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_SHOULDDIE) == CONTAINS_SHOULDDIE)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._shouldDie)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._shouldDie, dataAt, sizeof(newParticle._shouldDie));
|
||||
dataAt += sizeof(newParticle._shouldDie);
|
||||
processedBytes += sizeof(newParticle._shouldDie);
|
||||
|
@ -490,9 +562,20 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
// script
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_SCRIPT) == CONTAINS_SCRIPT)) {
|
||||
uint16_t scriptLength;
|
||||
if (length - (processedBytes + (int)sizeof(scriptLength)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&scriptLength, dataAt, sizeof(scriptLength));
|
||||
dataAt += sizeof(scriptLength);
|
||||
processedBytes += sizeof(scriptLength);
|
||||
|
||||
if (length - (processedBytes + (int)scriptLength) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
QString tempString((const char*)dataAt);
|
||||
newParticle._script = tempString;
|
||||
dataAt += scriptLength;
|
||||
|
@ -502,9 +585,20 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
// modelURL
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_URL) == CONTAINS_MODEL_URL)) {
|
||||
uint16_t modelURLLength;
|
||||
if (length - (processedBytes + (int)sizeof(modelURLLength)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&modelURLLength, dataAt, sizeof(modelURLLength));
|
||||
dataAt += sizeof(modelURLLength);
|
||||
processedBytes += sizeof(modelURLLength);
|
||||
|
||||
if (length - (processedBytes + (int)modelURLLength) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
QString tempString((const char*)dataAt);
|
||||
newParticle._modelURL = tempString;
|
||||
dataAt += modelURLLength;
|
||||
|
@ -513,6 +607,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// modelScale
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_SCALE) == CONTAINS_MODEL_SCALE)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._modelScale)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._modelScale, dataAt, sizeof(newParticle._modelScale));
|
||||
dataAt += sizeof(newParticle._modelScale);
|
||||
processedBytes += sizeof(newParticle._modelScale);
|
||||
|
@ -520,6 +619,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// modelTranslation
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_TRANSLATION) == CONTAINS_MODEL_TRANSLATION)) {
|
||||
if (length - (processedBytes + (int)sizeof(newParticle._modelTranslation)) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
memcpy(&newParticle._modelTranslation, dataAt, sizeof(newParticle._modelTranslation));
|
||||
dataAt += sizeof(newParticle._modelTranslation);
|
||||
processedBytes += sizeof(newParticle._modelTranslation);
|
||||
|
@ -527,6 +631,12 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
|
|||
|
||||
// modelRotation
|
||||
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_ROTATION) == CONTAINS_MODEL_ROTATION)) {
|
||||
const int expectedBytesForPackedQuat = sizeof(uint16_t) * 4; // this is how we pack the quats
|
||||
if (length - (processedBytes + expectedBytesForPackedQuat) < 0) {
|
||||
valid = false;
|
||||
processedBytes = length;
|
||||
return newParticle; // fail as if we read the entire buffer
|
||||
}
|
||||
int bytes = unpackOrientationQuatFromBytes(dataAt, newParticle._modelRotation);
|
||||
dataAt += bytes;
|
||||
processedBytes += bytes;
|
||||
|
|
|
@ -181,9 +181,6 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA)
|
|||
}
|
||||
}
|
||||
|
||||
// MIN_VALID_SPEED is obtained by computing speed gained at one gravity after the shortest expected frame
|
||||
const float MIN_EXPECTED_FRAME_PERIOD = 0.0167f; // 1/60th of a second
|
||||
|
||||
void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
||||
// particles that are in hand, don't collide with avatars
|
||||
if (!_avatars || particle->getInHand()) {
|
||||
|
|
155
libraries/script-engine/src/ArrayBufferClass.cpp
Normal file
155
libraries/script-engine/src/ArrayBufferClass.cpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
//
|
||||
// ArrayBufferClass.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/3/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "ArrayBufferPrototype.h"
|
||||
#include "DataViewClass.h"
|
||||
#include "ScriptEngine.h"
|
||||
#include "TypedArrays.h"
|
||||
|
||||
#include "ArrayBufferClass.h"
|
||||
|
||||
static const QString CLASS_NAME = "ArrayBuffer";
|
||||
|
||||
Q_DECLARE_METATYPE(QByteArray*)
|
||||
|
||||
ArrayBufferClass::ArrayBufferClass(ScriptEngine* scriptEngine) :
|
||||
QObject(scriptEngine->getEngine()),
|
||||
QScriptClass(scriptEngine->getEngine()),
|
||||
_scriptEngine(scriptEngine) {
|
||||
qScriptRegisterMetaType<QByteArray>(engine(), toScriptValue, fromScriptValue);
|
||||
QScriptValue global = engine()->globalObject();
|
||||
|
||||
// Save string handles for quick lookup
|
||||
_name = engine()->toStringHandle(CLASS_NAME.toLatin1());
|
||||
_byteLength = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1());
|
||||
|
||||
// build prototype
|
||||
_proto = engine()->newQObject(new ArrayBufferPrototype(this),
|
||||
QScriptEngine::QtOwnership,
|
||||
QScriptEngine::SkipMethodsInEnumeration |
|
||||
QScriptEngine::ExcludeSuperClassMethods |
|
||||
QScriptEngine::ExcludeSuperClassProperties);
|
||||
_proto.setPrototype(global.property("Object").property("prototype"));
|
||||
|
||||
// Register constructor
|
||||
_ctor = engine()->newFunction(construct, _proto);
|
||||
_ctor.setData(engine()->toScriptValue(this));
|
||||
|
||||
engine()->globalObject().setProperty(name(), _ctor);
|
||||
|
||||
// Registering other array types
|
||||
// The script engine is there parent so it'll delete them with itself
|
||||
new DataViewClass(scriptEngine);
|
||||
new Int8ArrayClass(scriptEngine);
|
||||
new Uint8ArrayClass(scriptEngine);
|
||||
new Uint8ClampedArrayClass(scriptEngine);
|
||||
new Int16ArrayClass(scriptEngine);
|
||||
new Uint16ArrayClass(scriptEngine);
|
||||
new Int32ArrayClass(scriptEngine);
|
||||
new Uint32ArrayClass(scriptEngine);
|
||||
new Float32ArrayClass(scriptEngine);
|
||||
new Float64ArrayClass(scriptEngine);
|
||||
}
|
||||
|
||||
QScriptValue ArrayBufferClass::newInstance(qint32 size) {
|
||||
const qint32 MAX_LENGTH = 100000000;
|
||||
if (size < 0) {
|
||||
engine()->evaluate("throw \"ArgumentError: negative length\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
if (size > MAX_LENGTH) {
|
||||
engine()->evaluate("throw \"ArgumentError: absurd length\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
engine()->reportAdditionalMemoryCost(size);
|
||||
QScriptEngine* eng = engine();
|
||||
QVariant variant = QVariant::fromValue(QByteArray(size, 0));
|
||||
QScriptValue data = eng->newVariant(variant);
|
||||
return engine()->newObject(this, data);
|
||||
}
|
||||
|
||||
QScriptValue ArrayBufferClass::newInstance(const QByteArray& ba) {
|
||||
QScriptValue data = engine()->newVariant(QVariant::fromValue(ba));
|
||||
return engine()->newObject(this, data);
|
||||
}
|
||||
|
||||
QScriptValue ArrayBufferClass::construct(QScriptContext* context, QScriptEngine* engine) {
|
||||
ArrayBufferClass* cls = qscriptvalue_cast<ArrayBufferClass*>(context->callee().data());
|
||||
if (!cls) {
|
||||
// return if callee (function called) is not of type ArrayBuffer
|
||||
return QScriptValue();
|
||||
}
|
||||
QScriptValue arg = context->argument(0);
|
||||
if (!arg.isValid() || !arg.isNumber()) {
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
quint32 size = arg.toInt32();
|
||||
QScriptValue newObject = cls->newInstance(size);
|
||||
|
||||
if (context->isCalledAsConstructor()) {
|
||||
// if called with keyword new, replace this object.
|
||||
context->setThisObject(newObject);
|
||||
return engine->undefinedValue();
|
||||
}
|
||||
|
||||
return newObject;
|
||||
}
|
||||
|
||||
QScriptClass::QueryFlags ArrayBufferClass::queryProperty(const QScriptValue& object,
|
||||
const QScriptString& name,
|
||||
QueryFlags flags, uint* id) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data());
|
||||
if (ba && name == _byteLength) {
|
||||
// if the property queried is byteLength, only handle read access
|
||||
return flags &= HandlesReadAccess;
|
||||
}
|
||||
return 0; // No access
|
||||
}
|
||||
|
||||
QScriptValue ArrayBufferClass::property(const QScriptValue& object,
|
||||
const QScriptString& name, uint id) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data());
|
||||
if (ba && name == _byteLength) {
|
||||
return ba->length();
|
||||
}
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
QScriptValue::PropertyFlags ArrayBufferClass::propertyFlags(const QScriptValue& object,
|
||||
const QScriptString& name, uint id) {
|
||||
return QScriptValue::Undeletable;
|
||||
}
|
||||
|
||||
QString ArrayBufferClass::name() const {
|
||||
return _name.toString();
|
||||
}
|
||||
|
||||
QScriptValue ArrayBufferClass::prototype() const {
|
||||
return _proto;
|
||||
}
|
||||
|
||||
QScriptValue ArrayBufferClass::toScriptValue(QScriptEngine* engine, const QByteArray& ba) {
|
||||
QScriptValue ctor = engine->globalObject().property(CLASS_NAME);
|
||||
ArrayBufferClass* cls = qscriptvalue_cast<ArrayBufferClass*>(ctor.data());
|
||||
if (!cls) {
|
||||
return engine->newVariant(QVariant::fromValue(ba));
|
||||
}
|
||||
return cls->newInstance(ba);
|
||||
}
|
||||
|
||||
void ArrayBufferClass::fromScriptValue(const QScriptValue& obj, QByteArray& ba) {
|
||||
ba = qvariant_cast<QByteArray>(obj.data().toVariant());
|
||||
}
|
||||
|
61
libraries/script-engine/src/ArrayBufferClass.h
Normal file
61
libraries/script-engine/src/ArrayBufferClass.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// ArrayBufferClass.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/3/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ArrayBufferClass_h
|
||||
#define hifi_ArrayBufferClass_h
|
||||
|
||||
#include <QScriptClass>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtScript/QScriptClass>
|
||||
#include <QtScript/QScriptContext>
|
||||
#include <QtScript/QScriptEngine>
|
||||
#include <QtScript/QScriptString>
|
||||
#include <QtScript/QScriptValue>
|
||||
|
||||
class ScriptEngine;
|
||||
|
||||
class ArrayBufferClass : public QObject, public QScriptClass {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ArrayBufferClass(ScriptEngine* scriptEngine);
|
||||
QScriptValue newInstance(qint32 size);
|
||||
QScriptValue newInstance(const QByteArray& ba);
|
||||
|
||||
QueryFlags queryProperty(const QScriptValue& object,
|
||||
const QScriptString& name,
|
||||
QueryFlags flags, uint* id);
|
||||
QScriptValue property(const QScriptValue& object,
|
||||
const QScriptString& name, uint id);
|
||||
QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object,
|
||||
const QScriptString& name, uint id);
|
||||
|
||||
QString name() const;
|
||||
QScriptValue prototype() const;
|
||||
|
||||
ScriptEngine* getEngine() { return _scriptEngine; }
|
||||
|
||||
private:
|
||||
static QScriptValue construct(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
static QScriptValue toScriptValue(QScriptEngine* eng, const QByteArray& ba);
|
||||
static void fromScriptValue(const QScriptValue& obj, QByteArray& ba);
|
||||
|
||||
QScriptValue _proto;
|
||||
QScriptValue _ctor;
|
||||
|
||||
// JS Object attributes
|
||||
QScriptString _name;
|
||||
QScriptString _byteLength;
|
||||
|
||||
ScriptEngine* _scriptEngine;
|
||||
};
|
||||
|
||||
#endif // hifi_ArrayBufferClass_h
|
48
libraries/script-engine/src/ArrayBufferPrototype.cpp
Normal file
48
libraries/script-engine/src/ArrayBufferPrototype.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// ArrayBufferPrototype.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/3/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "ArrayBufferClass.h"
|
||||
#include "ArrayBufferPrototype.h"
|
||||
|
||||
Q_DECLARE_METATYPE(QByteArray*)
|
||||
|
||||
ArrayBufferPrototype::ArrayBufferPrototype(QObject* parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
QByteArray ArrayBufferPrototype::slice(qint32 begin, qint32 end) const {
|
||||
QByteArray* ba = thisArrayBuffer();
|
||||
// if indices < 0 then they start from the end of the array
|
||||
begin = (begin < 0) ? ba->size() + begin : begin;
|
||||
end = (end < 0) ? ba->size() + end : end;
|
||||
|
||||
// here we clamp the indices to fit the array
|
||||
begin = glm::clamp(begin, 0, (ba->size() - 1));
|
||||
end = glm::clamp(end, 0, (ba->size() - 1));
|
||||
|
||||
return (end - begin > 0) ? ba->mid(begin, end - begin) : QByteArray();
|
||||
}
|
||||
|
||||
QByteArray ArrayBufferPrototype::slice(qint32 begin) const {
|
||||
QByteArray* ba = thisArrayBuffer();
|
||||
// if indices < 0 then they start from the end of the array
|
||||
begin = (begin < 0) ? ba->size() + begin : begin;
|
||||
|
||||
// here we clamp the indices to fit the array
|
||||
begin = glm::clamp(begin, 0, (ba->size() - 1));
|
||||
|
||||
return ba->mid(begin, -1);
|
||||
}
|
||||
|
||||
QByteArray* ArrayBufferPrototype::thisArrayBuffer() const {
|
||||
return qscriptvalue_cast<QByteArray*>(thisObject().data());
|
||||
}
|
31
libraries/script-engine/src/ArrayBufferPrototype.h
Normal file
31
libraries/script-engine/src/ArrayBufferPrototype.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// ArrayBufferPrototype.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/3/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ArrayBufferPrototype_h
|
||||
#define hifi_ArrayBufferPrototype_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtScript/QScriptable>
|
||||
|
||||
class ArrayBufferPrototype : public QObject, public QScriptable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ArrayBufferPrototype(QObject* parent = NULL);
|
||||
|
||||
public slots:
|
||||
QByteArray slice(qint32 begin, qint32 end) const;
|
||||
QByteArray slice(qint32 begin) const;
|
||||
|
||||
private:
|
||||
QByteArray* thisArrayBuffer() const;
|
||||
};
|
||||
|
||||
#endif // hifi_ArrayBufferPrototype_h
|
52
libraries/script-engine/src/ArrayBufferViewClass.cpp
Normal file
52
libraries/script-engine/src/ArrayBufferViewClass.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// ArrayBufferViewClass.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/8/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ArrayBufferViewClass.h"
|
||||
|
||||
Q_DECLARE_METATYPE(QByteArray*)
|
||||
|
||||
ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngine* scriptEngine) :
|
||||
QObject(scriptEngine->getEngine()),
|
||||
QScriptClass(scriptEngine->getEngine()),
|
||||
_scriptEngine(scriptEngine) {
|
||||
// Save string handles for quick lookup
|
||||
_bufferName = engine()->toStringHandle(BUFFER_PROPERTY_NAME.toLatin1());
|
||||
_byteOffsetName = engine()->toStringHandle(BYTE_OFFSET_PROPERTY_NAME.toLatin1());
|
||||
_byteLengthName = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1());
|
||||
}
|
||||
|
||||
QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const QScriptValue& object,
|
||||
const QScriptString& name,
|
||||
QueryFlags flags, uint* id) {
|
||||
if (name == _bufferName || name == _byteOffsetName || name == _byteLengthName) {
|
||||
return flags &= HandlesReadAccess; // Only keep read access flags
|
||||
}
|
||||
return 0; // No access
|
||||
}
|
||||
|
||||
QScriptValue ArrayBufferViewClass::property(const QScriptValue& object,
|
||||
const QScriptString& name, uint id) {
|
||||
if (name == _bufferName) {
|
||||
return object.data().property(_bufferName);
|
||||
}
|
||||
if (name == _byteOffsetName) {
|
||||
return object.data().property(_byteOffsetName);
|
||||
}
|
||||
if (name == _byteLengthName) {
|
||||
return object.data().property(_byteLengthName);
|
||||
}
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
QScriptValue::PropertyFlags ArrayBufferViewClass::propertyFlags(const QScriptValue& object,
|
||||
const QScriptString& name, uint id) {
|
||||
return QScriptValue::Undeletable;
|
||||
}
|
52
libraries/script-engine/src/ArrayBufferViewClass.h
Normal file
52
libraries/script-engine/src/ArrayBufferViewClass.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// ArrayBufferViewClass.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/8/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ArrayBufferViewClass_h
|
||||
#define hifi_ArrayBufferViewClass_h
|
||||
|
||||
#include <QScriptClass>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtScript/QScriptClass>
|
||||
#include <QtScript/QScriptContext>
|
||||
#include <QtScript/QScriptEngine>
|
||||
#include <QtScript/QScriptString>
|
||||
#include <QtScript/QScriptValue>
|
||||
|
||||
#include "ScriptEngine.h"
|
||||
|
||||
static const QString BUFFER_PROPERTY_NAME = "buffer";
|
||||
static const QString BYTE_OFFSET_PROPERTY_NAME = "byteOffset";
|
||||
static const QString BYTE_LENGTH_PROPERTY_NAME = "byteLength";
|
||||
|
||||
class ArrayBufferViewClass : public QObject, public QScriptClass {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ArrayBufferViewClass(ScriptEngine* scriptEngine);
|
||||
|
||||
ScriptEngine* getScriptEngine() { return _scriptEngine; }
|
||||
|
||||
virtual QueryFlags queryProperty(const QScriptValue& object,
|
||||
const QScriptString& name,
|
||||
QueryFlags flags, uint* id);
|
||||
virtual QScriptValue property(const QScriptValue& object,
|
||||
const QScriptString& name, uint id);
|
||||
virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object,
|
||||
const QScriptString& name, uint id);
|
||||
protected:
|
||||
// JS Object attributes
|
||||
QScriptString _bufferName;
|
||||
QScriptString _byteOffsetName;
|
||||
QScriptString _byteLengthName;
|
||||
|
||||
ScriptEngine* _scriptEngine;
|
||||
};
|
||||
|
||||
#endif // hifi_ArrayBufferViewClass_h
|
94
libraries/script-engine/src/DataViewClass.cpp
Normal file
94
libraries/script-engine/src/DataViewClass.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
//
|
||||
// DataViewClass.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/8/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "DataViewPrototype.h"
|
||||
|
||||
#include "DataViewClass.h"
|
||||
|
||||
Q_DECLARE_METATYPE(QByteArray*)
|
||||
|
||||
static const QString DATA_VIEW_NAME = "DataView";
|
||||
|
||||
DataViewClass::DataViewClass(ScriptEngine* scriptEngine) : ArrayBufferViewClass(scriptEngine) {
|
||||
QScriptValue global = engine()->globalObject();
|
||||
|
||||
// Save string handles for quick lookup
|
||||
_name = engine()->toStringHandle(DATA_VIEW_NAME.toLatin1());
|
||||
|
||||
// build prototype
|
||||
_proto = engine()->newQObject(new DataViewPrototype(this),
|
||||
QScriptEngine::QtOwnership,
|
||||
QScriptEngine::SkipMethodsInEnumeration |
|
||||
QScriptEngine::ExcludeSuperClassMethods |
|
||||
QScriptEngine::ExcludeSuperClassProperties);
|
||||
_proto.setPrototype(global.property("Object").property("prototype"));
|
||||
|
||||
// Register constructor
|
||||
_ctor = engine()->newFunction(construct, _proto);
|
||||
_ctor.setData(engine()->toScriptValue(this));
|
||||
engine()->globalObject().setProperty(name(), _ctor);
|
||||
}
|
||||
|
||||
QScriptValue DataViewClass::newInstance(QScriptValue buffer, quint32 byteOffset, quint32 byteLentgh) {
|
||||
QScriptValue data = engine()->newObject();
|
||||
data.setProperty(_bufferName, buffer);
|
||||
data.setProperty(_byteOffsetName, byteOffset);
|
||||
data.setProperty(_byteLengthName, byteLentgh);
|
||||
|
||||
return engine()->newObject(this, data);
|
||||
}
|
||||
|
||||
QScriptValue DataViewClass::construct(QScriptContext* context, QScriptEngine* engine) {
|
||||
DataViewClass* cls = qscriptvalue_cast<DataViewClass*>(context->callee().data());
|
||||
if (!cls || context->argumentCount() < 1) {
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
QScriptValue bufferArg = context->argument(0);
|
||||
QScriptValue byteOffsetArg = (context->argumentCount() >= 2) ? context->argument(1) : QScriptValue();
|
||||
QScriptValue byteLengthArg = (context->argumentCount() >= 3) ? context->argument(2) : QScriptValue();
|
||||
|
||||
QByteArray* arrayBuffer = (bufferArg.isValid()) ? qscriptvalue_cast<QByteArray*>(bufferArg.data()) :NULL;
|
||||
if (!arrayBuffer) {
|
||||
engine->evaluate("throw \"TypeError: 1st argument not a ArrayBuffer\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
if (byteOffsetArg.isNumber() &&
|
||||
(byteOffsetArg.toInt32() < 0 ||
|
||||
byteOffsetArg.toInt32() > arrayBuffer->size())) {
|
||||
engine->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
if (byteLengthArg.isNumber() &&
|
||||
(byteLengthArg.toInt32() < 0 ||
|
||||
byteOffsetArg.toInt32() + byteLengthArg.toInt32() > arrayBuffer->size())) {
|
||||
engine->evaluate("throw \"RangeError: byteLength out of range\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
quint32 byteOffset = (byteOffsetArg.isNumber()) ? byteOffsetArg.toInt32() : 0;
|
||||
quint32 byteLength = (byteLengthArg.isNumber()) ? byteLengthArg.toInt32() : arrayBuffer->size() - byteOffset;
|
||||
QScriptValue newObject = cls->newInstance(bufferArg, byteOffset, byteLength);
|
||||
|
||||
if (context->isCalledAsConstructor()) {
|
||||
context->setThisObject(newObject);
|
||||
return engine->undefinedValue();
|
||||
}
|
||||
|
||||
return newObject;
|
||||
}
|
||||
|
||||
QString DataViewClass::name() const {
|
||||
return _name.toString();
|
||||
}
|
||||
|
||||
QScriptValue DataViewClass::prototype() const {
|
||||
return _proto;
|
||||
}
|
36
libraries/script-engine/src/DataViewClass.h
Normal file
36
libraries/script-engine/src/DataViewClass.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// DataViewClass.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/8/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_DataViewClass_h
|
||||
#define hifi_DataViewClass_h
|
||||
|
||||
#include "ArrayBufferViewClass.h"
|
||||
|
||||
class DataViewClass : public ArrayBufferViewClass {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DataViewClass(ScriptEngine* scriptEngine);
|
||||
QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 byteLength);
|
||||
|
||||
QString name() const;
|
||||
QScriptValue prototype() const;
|
||||
|
||||
private:
|
||||
static QScriptValue construct(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
QScriptValue _proto;
|
||||
QScriptValue _ctor;
|
||||
|
||||
QScriptString _name;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_DataViewClass_h
|
255
libraries/script-engine/src/DataViewPrototype.cpp
Normal file
255
libraries/script-engine/src/DataViewPrototype.cpp
Normal file
|
@ -0,0 +1,255 @@
|
|||
//
|
||||
// DataViewPrototype.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/8/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include <QDebug>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "DataViewClass.h"
|
||||
|
||||
#include "DataViewPrototype.h"
|
||||
|
||||
Q_DECLARE_METATYPE(QByteArray*)
|
||||
|
||||
DataViewPrototype::DataViewPrototype(QObject* parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
QByteArray* DataViewPrototype::thisArrayBuffer() const {
|
||||
QScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME);
|
||||
return qscriptvalue_cast<QByteArray*>(bufferObject.data());
|
||||
}
|
||||
|
||||
bool DataViewPrototype::realOffset(qint32& offset, size_t size) const {
|
||||
if (offset < 0) {
|
||||
return false;
|
||||
}
|
||||
quint32 viewOffset = thisObject().data().property(BYTE_OFFSET_PROPERTY_NAME).toInt32();
|
||||
quint32 viewLength = thisObject().data().property(BYTE_LENGTH_PROPERTY_NAME).toInt32();
|
||||
offset += viewOffset;
|
||||
return (offset + size) <= viewOffset + viewLength;
|
||||
}
|
||||
|
||||
qint32 DataViewPrototype::getInt8(qint32 byteOffset) {
|
||||
if (realOffset(byteOffset, sizeof(qint8))) {
|
||||
QDataStream stream(*thisArrayBuffer());
|
||||
stream.skipRawData(byteOffset);
|
||||
|
||||
qint8 result;
|
||||
stream >> result;
|
||||
return result;
|
||||
}
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return 0;
|
||||
}
|
||||
|
||||
quint32 DataViewPrototype::getUint8(qint32 byteOffset) {
|
||||
if (realOffset(byteOffset, sizeof(quint8))) {
|
||||
QDataStream stream(*thisArrayBuffer());
|
||||
stream.skipRawData(byteOffset);
|
||||
|
||||
quint8 result;
|
||||
stream >> result;
|
||||
return result;
|
||||
}
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint32 DataViewPrototype::getInt16(qint32 byteOffset, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(qint16))) {
|
||||
QDataStream stream(*thisArrayBuffer());
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
|
||||
qint16 result;
|
||||
stream >> result;
|
||||
return result;
|
||||
}
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return 0;
|
||||
}
|
||||
|
||||
quint32 DataViewPrototype::getUint16(qint32 byteOffset, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(quint16))) {
|
||||
QDataStream stream(*thisArrayBuffer());
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
|
||||
quint16 result;
|
||||
stream >> result;
|
||||
return result;
|
||||
}
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint32 DataViewPrototype::getInt32(qint32 byteOffset, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(qint32))) {
|
||||
QDataStream stream(*thisArrayBuffer());
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
|
||||
qint32 result;
|
||||
stream >> result;
|
||||
return result;
|
||||
}
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return 0;
|
||||
}
|
||||
|
||||
quint32 DataViewPrototype::getUint32(qint32 byteOffset, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(quint32))) {
|
||||
QDataStream stream(*thisArrayBuffer());
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
|
||||
quint32 result;
|
||||
stream >> result;
|
||||
return result;
|
||||
}
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return 0;
|
||||
}
|
||||
|
||||
QScriptValue DataViewPrototype::getFloat32(qint32 byteOffset, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(float))) {
|
||||
QDataStream stream(*thisArrayBuffer());
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
|
||||
|
||||
float result;
|
||||
stream >> result;
|
||||
if (isNaN(result)) {
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
return QScriptValue(result);
|
||||
}
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
QScriptValue DataViewPrototype::getFloat64(qint32 byteOffset, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(double))) {
|
||||
QDataStream stream(*thisArrayBuffer());
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
stream.setFloatingPointPrecision(QDataStream::DoublePrecision);
|
||||
|
||||
double result;
|
||||
stream >> result;
|
||||
if (isNaN(result)) {
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
void DataViewPrototype::setInt8(qint32 byteOffset, qint32 value) {
|
||||
if (realOffset(byteOffset, sizeof(qint8))) {
|
||||
QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite);
|
||||
stream.skipRawData(byteOffset);
|
||||
|
||||
stream << (qint8)value;
|
||||
} else {
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
}
|
||||
}
|
||||
|
||||
void DataViewPrototype::setUint8(qint32 byteOffset, quint32 value) {
|
||||
if (realOffset(byteOffset, sizeof(quint8))) {
|
||||
QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite);
|
||||
stream.skipRawData(byteOffset);
|
||||
|
||||
stream << (quint8)value;
|
||||
} else {
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
}
|
||||
}
|
||||
|
||||
void DataViewPrototype::setInt16(qint32 byteOffset, qint32 value, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(qint16))) {
|
||||
QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite);
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
|
||||
stream << (qint16)value;
|
||||
} else {
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
}
|
||||
}
|
||||
|
||||
void DataViewPrototype::setUint16(qint32 byteOffset, quint32 value, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(quint16))) {
|
||||
QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite);
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
|
||||
stream << (quint16)value;
|
||||
} else {
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
}
|
||||
}
|
||||
|
||||
void DataViewPrototype::setInt32(qint32 byteOffset, qint32 value, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(qint32))) {
|
||||
QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite);
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
|
||||
stream << (qint32)value;
|
||||
} else {
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
}
|
||||
}
|
||||
|
||||
void DataViewPrototype::setUint32(qint32 byteOffset, quint32 value, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(quint32))) {
|
||||
QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite);
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
|
||||
stream << (quint32)value;
|
||||
} else {
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
}
|
||||
}
|
||||
|
||||
void DataViewPrototype::setFloat32(qint32 byteOffset, float value, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(float))) {
|
||||
QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite);
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
|
||||
|
||||
stream << value;
|
||||
} else {
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
}
|
||||
}
|
||||
|
||||
void DataViewPrototype::setFloat64(qint32 byteOffset, double value, bool littleEndian) {
|
||||
if (realOffset(byteOffset, sizeof(double))) {
|
||||
QDataStream stream(thisArrayBuffer(), QIODevice::ReadWrite);
|
||||
stream.skipRawData(byteOffset);
|
||||
stream.setByteOrder((littleEndian) ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||||
stream.setFloatingPointPrecision(QDataStream::DoublePrecision);
|
||||
|
||||
stream << value;
|
||||
} else {
|
||||
thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
}
|
||||
}
|
||||
|
||||
|
68
libraries/script-engine/src/DataViewPrototype.h
Normal file
68
libraries/script-engine/src/DataViewPrototype.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// DataViewPrototype.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/8/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_DataViewPrototype_h
|
||||
#define hifi_DataViewPrototype_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtScript/QScriptable>
|
||||
|
||||
class DataViewPrototype : public QObject, public QScriptable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DataViewPrototype(QObject* parent = NULL);
|
||||
|
||||
public slots:
|
||||
// Gets the value of the given type at the specified byte offset
|
||||
// from the start of the view. There is no alignment constraint;
|
||||
// multi-byte values may be fetched from any offset.
|
||||
//
|
||||
// For multi-byte values, the optional littleEndian argument
|
||||
// indicates whether a big-endian or little-endian value should be
|
||||
// read. If false or undefined, a big-endian value is read.
|
||||
//
|
||||
// These methods raise an exception if they would read
|
||||
// beyond the end of the view.
|
||||
qint32 getInt8(qint32 byteOffset);
|
||||
quint32 getUint8(qint32 byteOffset);
|
||||
qint32 getInt16(qint32 byteOffset, bool littleEndian = false);
|
||||
quint32 getUint16(qint32 byteOffset, bool littleEndian = false);
|
||||
qint32 getInt32(qint32 byteOffset, bool littleEndian = false);
|
||||
quint32 getUint32(qint32 byteOffset, bool littleEndian = false);
|
||||
QScriptValue getFloat32(qint32 byteOffset, bool littleEndian = false);
|
||||
QScriptValue getFloat64(qint32 byteOffset, bool littleEndian = false);
|
||||
|
||||
// Stores a value of the given type at the specified byte offset
|
||||
// from the start of the view. There is no alignment constraint;
|
||||
// multi-byte values may be stored at any offset.
|
||||
//
|
||||
// For multi-byte values, the optional littleEndian argument
|
||||
// indicates whether the value should be stored in big-endian or
|
||||
// little-endian byte order. If false or undefined, the value is
|
||||
// stored in big-endian byte order.
|
||||
//
|
||||
// These methods raise an exception if they would write
|
||||
// beyond the end of the view.
|
||||
void setInt8(qint32 byteOffset, qint32 value);
|
||||
void setUint8(qint32 byteOffset, quint32 value);
|
||||
void setInt16(qint32 byteOffset, qint32 value, bool littleEndian = false);
|
||||
void setUint16(qint32 byteOffset, quint32 value, bool littleEndian = false);
|
||||
void setInt32(qint32 byteOffset, qint32 value, bool littleEndian = false);
|
||||
void setUint32(qint32 byteOffset, quint32 value, bool littleEndian = false);
|
||||
void setFloat32(qint32 byteOffset, float value, bool littleEndian = false);
|
||||
void setFloat64(qint32 byteOffset, double value, bool littleEndian = false);
|
||||
|
||||
private:
|
||||
QByteArray* thisArrayBuffer() const;
|
||||
bool realOffset(qint32& offset, size_t size) const;
|
||||
};
|
||||
|
||||
#endif // hifi_DataViewPrototype_h
|
|
@ -20,6 +20,7 @@
|
|||
#include <AudioInjector.h>
|
||||
#include <AudioRingBuffer.h>
|
||||
#include <AvatarData.h>
|
||||
#include <Bitstream.h>
|
||||
#include <CollisionInfo.h>
|
||||
#include <ModelsScriptingInterface.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
|
@ -32,10 +33,13 @@
|
|||
#include <VoxelDetail.h>
|
||||
|
||||
#include "AnimationObject.h"
|
||||
#include "ArrayBufferViewClass.h"
|
||||
#include "DataViewClass.h"
|
||||
#include "MenuItemProperties.h"
|
||||
#include "MIDIEvent.h"
|
||||
#include "LocalVoxels.h"
|
||||
#include "ScriptEngine.h"
|
||||
#include "TypedArrays.h"
|
||||
#include "XMLHttpRequestClass.h"
|
||||
|
||||
VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface;
|
||||
|
@ -90,7 +94,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
|
|||
_quatLibrary(),
|
||||
_vec3Library(),
|
||||
_uuidLibrary(),
|
||||
_animationCache(this)
|
||||
_animationCache(this),
|
||||
_arrayBufferClass(new ArrayBufferClass(this))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -115,13 +120,14 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL,
|
|||
_quatLibrary(),
|
||||
_vec3Library(),
|
||||
_uuidLibrary(),
|
||||
_animationCache(this)
|
||||
_animationCache(this),
|
||||
_arrayBufferClass(new ArrayBufferClass(this))
|
||||
{
|
||||
QString scriptURLString = scriptURL.toString();
|
||||
_fileNameString = scriptURLString;
|
||||
|
||||
QUrl url(scriptURL);
|
||||
|
||||
|
||||
// if the scheme length is one or lower, maybe they typed in a file, let's try
|
||||
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
|
||||
if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
|
||||
|
@ -210,7 +216,7 @@ void ScriptEngine::init() {
|
|||
if (_isInitialized) {
|
||||
return; // only initialize once
|
||||
}
|
||||
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
_voxelsScriptingInterface.init();
|
||||
|
@ -224,6 +230,7 @@ void ScriptEngine::init() {
|
|||
registerMenuItemProperties(&_engine);
|
||||
registerAnimationTypes(&_engine);
|
||||
registerAvatarTypes(&_engine);
|
||||
Bitstream::registerTypes(&_engine);
|
||||
|
||||
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
|
||||
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <VoxelsScriptingInterface.h>
|
||||
|
||||
#include "AbstractControllerScriptingInterface.h"
|
||||
#include "ArrayBufferClass.h"
|
||||
#include "Quat.h"
|
||||
#include "ScriptUUID.h"
|
||||
#include "Vec3.h"
|
||||
|
@ -56,6 +57,9 @@ public:
|
|||
/// Access the ModelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener
|
||||
static ModelsScriptingInterface* getModelsScriptingInterface() { return &_modelsScriptingInterface; }
|
||||
|
||||
QScriptEngine* getEngine() { return &_engine; }
|
||||
ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; }
|
||||
|
||||
/// sets the script contents, will return false if failed, will fail if script is already running
|
||||
bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString(""));
|
||||
|
||||
|
@ -147,6 +151,8 @@ private:
|
|||
Vec3 _vec3Library;
|
||||
ScriptUUID _uuidLibrary;
|
||||
AnimationCache _animationCache;
|
||||
|
||||
ArrayBufferClass* _arrayBufferClass;
|
||||
|
||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
};
|
||||
|
|
106
libraries/script-engine/src/TypedArrayPrototype.cpp
Normal file
106
libraries/script-engine/src/TypedArrayPrototype.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// TypedArrayPrototype.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/14/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "TypedArrays.h"
|
||||
|
||||
#include "TypedArrayPrototype.h"
|
||||
|
||||
Q_DECLARE_METATYPE(QByteArray*)
|
||||
|
||||
TypedArrayPrototype::TypedArrayPrototype(QObject* parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
QByteArray* TypedArrayPrototype::thisArrayBuffer() const {
|
||||
QScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME);
|
||||
return qscriptvalue_cast<QByteArray*>(bufferObject.data());
|
||||
}
|
||||
|
||||
void TypedArrayPrototype::set(QScriptValue array, qint32 offset) {
|
||||
TypedArray* typedArray = static_cast<TypedArray*>(parent());
|
||||
if (array.isArray() || typedArray) {
|
||||
if (offset < 0) {
|
||||
engine()->evaluate("throw \"ArgumentError: negative offset\"");
|
||||
}
|
||||
quint32 length = array.property("length").toInt32();
|
||||
if (offset + length > thisObject().data().property(typedArray->_lengthName).toInt32()) {
|
||||
engine()->evaluate("throw \"ArgumentError: array does not fit\"");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < length; ++i) {
|
||||
thisObject().setProperty(QString::number(offset + i), array.property(QString::number(i)));
|
||||
}
|
||||
} else {
|
||||
engine()->evaluate("throw \"ArgumentError: not an array\"");
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue TypedArrayPrototype::subarray(qint32 begin) {
|
||||
TypedArray* typedArray = static_cast<TypedArray*>(parent());
|
||||
QScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName);
|
||||
qint32 byteOffset = thisObject().data().property(typedArray->_byteOffsetName).toInt32();
|
||||
qint32 length = thisObject().data().property(typedArray->_lengthName).toInt32();
|
||||
qint32 bytesPerElement = typedArray->_bytesPerElement;
|
||||
|
||||
// if indices < 0 then they start from the end of the array
|
||||
begin = (begin < 0) ? length + begin : begin;
|
||||
|
||||
// here we clamp the indices to fit the array
|
||||
begin = glm::clamp(begin, 0, (length - 1));
|
||||
|
||||
byteOffset += begin * bytesPerElement;
|
||||
return typedArray->newInstance(arrayBuffer, byteOffset, length - begin);
|
||||
}
|
||||
|
||||
QScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) {
|
||||
TypedArray* typedArray = static_cast<TypedArray*>(parent());
|
||||
QScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName);
|
||||
qint32 byteOffset = thisObject().data().property(typedArray->_byteOffsetName).toInt32();
|
||||
qint32 length = thisObject().data().property(typedArray->_lengthName).toInt32();
|
||||
qint32 bytesPerElement = typedArray->_bytesPerElement;
|
||||
|
||||
// if indices < 0 then they start from the end of the array
|
||||
begin = (begin < 0) ? length + begin : begin;
|
||||
end = (end < 0) ? length + end : end;
|
||||
|
||||
// here we clamp the indices to fit the array
|
||||
begin = glm::clamp(begin, 0, (length - 1));
|
||||
end = glm::clamp(end, 0, (length - 1));
|
||||
|
||||
byteOffset += begin * bytesPerElement;
|
||||
length = (end - begin > 0) ? end - begin : 0;
|
||||
return typedArray->newInstance(arrayBuffer, byteOffset, length);
|
||||
}
|
||||
|
||||
QScriptValue TypedArrayPrototype::get(quint32 index) {
|
||||
TypedArray* typedArray = static_cast<TypedArray*>(parent());
|
||||
QScriptString name = engine()->toStringHandle(QString::number(index));
|
||||
uint id;
|
||||
QScriptClass::QueryFlags flags = typedArray->queryProperty(thisObject(),
|
||||
name,
|
||||
QScriptClass::HandlesReadAccess, &id);
|
||||
if (QScriptClass::HandlesReadAccess & flags) {
|
||||
return typedArray->property(thisObject(), name, id);
|
||||
}
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
void TypedArrayPrototype::set(quint32 index, QScriptValue& value) {
|
||||
TypedArray* typedArray = static_cast<TypedArray*>(parent());
|
||||
QScriptValue object = thisObject();
|
||||
QScriptString name = engine()->toStringHandle(QString::number(index));
|
||||
uint id;
|
||||
QScriptClass::QueryFlags flags = typedArray->queryProperty(object,
|
||||
name,
|
||||
QScriptClass::HandlesWriteAccess, &id);
|
||||
if (QScriptClass::HandlesWriteAccess & flags) {
|
||||
typedArray->setProperty(object, name, id, value);
|
||||
}
|
||||
}
|
33
libraries/script-engine/src/TypedArrayPrototype.h
Normal file
33
libraries/script-engine/src/TypedArrayPrototype.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// TypedArrayPrototype.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/14/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_TypedArrayPrototype_h
|
||||
#define hifi_TypedArrayPrototype_h
|
||||
|
||||
#include "ArrayBufferViewClass.h"
|
||||
|
||||
class TypedArrayPrototype : public QObject, public QScriptable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TypedArrayPrototype(QObject* parent = NULL);
|
||||
|
||||
public slots:
|
||||
void set(QScriptValue array, qint32 offset = 0);
|
||||
QScriptValue subarray(qint32 begin);
|
||||
QScriptValue subarray(qint32 begin, qint32 end);
|
||||
|
||||
QScriptValue get(quint32 index);
|
||||
void set(quint32 index, QScriptValue& value);
|
||||
private:
|
||||
QByteArray* thisArrayBuffer() const;
|
||||
};
|
||||
|
||||
#endif // hifi_TypedArrayPrototype_h
|
418
libraries/script-engine/src/TypedArrays.cpp
Normal file
418
libraries/script-engine/src/TypedArrays.cpp
Normal file
|
@ -0,0 +1,418 @@
|
|||
//
|
||||
// TypedArrays.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/9/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "ScriptEngine.h"
|
||||
#include "TypedArrayPrototype.h"
|
||||
|
||||
#include "TypedArrays.h"
|
||||
|
||||
Q_DECLARE_METATYPE(QByteArray*)
|
||||
|
||||
TypedArray::TypedArray(ScriptEngine* scriptEngine, QString name) : ArrayBufferViewClass(scriptEngine) {
|
||||
_bytesPerElementName = engine()->toStringHandle(BYTES_PER_ELEMENT_PROPERTY_NAME.toLatin1());
|
||||
_lengthName = engine()->toStringHandle(LENGTH_PROPERTY_NAME.toLatin1());
|
||||
_name = engine()->toStringHandle(name.toLatin1());
|
||||
|
||||
QScriptValue global = engine()->globalObject();
|
||||
|
||||
// build prototype
|
||||
_proto = engine()->newQObject(new TypedArrayPrototype(this),
|
||||
QScriptEngine::QtOwnership,
|
||||
QScriptEngine::SkipMethodsInEnumeration |
|
||||
QScriptEngine::ExcludeSuperClassMethods |
|
||||
QScriptEngine::ExcludeSuperClassProperties);
|
||||
_proto.setPrototype(global.property("Object").property("prototype"));
|
||||
|
||||
// Register constructor
|
||||
_ctor = engine()->newFunction(construct, _proto);
|
||||
_ctor.setData(engine()->toScriptValue(this));
|
||||
engine()->globalObject().setProperty(_name, _ctor);
|
||||
}
|
||||
|
||||
QScriptValue TypedArray::newInstance(quint32 length) {
|
||||
ArrayBufferClass* array = getScriptEngine()->getArrayBufferClass();
|
||||
QScriptValue buffer = array->newInstance(length * _bytesPerElement);
|
||||
return newInstance(buffer, 0, length);
|
||||
}
|
||||
|
||||
QScriptValue TypedArray::newInstance(QScriptValue array) {
|
||||
const QString ARRAY_LENGTH_HANDLE = "length";
|
||||
if (array.property(ARRAY_LENGTH_HANDLE).isValid()) {
|
||||
quint32 length = array.property(ARRAY_LENGTH_HANDLE).toInt32();
|
||||
QScriptValue newArray = newInstance(length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
QScriptValue value = array.property(QString::number(i));
|
||||
setProperty(newArray, engine()->toStringHandle(QString::number(i)),
|
||||
i * _bytesPerElement, (value.isNumber()) ? value : QScriptValue(0));
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
engine()->evaluate("throw \"ArgumentError: not an array\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
QScriptValue TypedArray::newInstance(QScriptValue buffer, quint32 byteOffset, quint32 length) {
|
||||
QScriptValue data = engine()->newObject();
|
||||
data.setProperty(_bufferName, buffer);
|
||||
data.setProperty(_byteOffsetName, byteOffset);
|
||||
data.setProperty(_byteLengthName, length * _bytesPerElement);
|
||||
data.setProperty(_lengthName, length);
|
||||
|
||||
return engine()->newObject(this, data);
|
||||
}
|
||||
|
||||
QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engine) {
|
||||
TypedArray* cls = qscriptvalue_cast<TypedArray*>(context->callee().data());
|
||||
if (!cls) {
|
||||
return QScriptValue();
|
||||
}
|
||||
if (context->argumentCount() == 0) {
|
||||
return cls->newInstance(0);
|
||||
}
|
||||
|
||||
QScriptValue newObject;
|
||||
QScriptValue bufferArg = context->argument(0);
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(bufferArg.data());
|
||||
|
||||
// parse arguments
|
||||
if (arrayBuffer) {
|
||||
if (context->argumentCount() == 1) {
|
||||
// Case for entire ArrayBuffer
|
||||
newObject = cls->newInstance(bufferArg, 0, arrayBuffer->size());
|
||||
} else {
|
||||
QScriptValue byteOffsetArg = context->argument(1);
|
||||
if (!byteOffsetArg.isNumber()) {
|
||||
engine->evaluate("throw \"ArgumentError: 2nd arg is not a number\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
if (byteOffsetArg.toInt32() < 0 || byteOffsetArg.toInt32() > arrayBuffer->size()) {
|
||||
engine->evaluate("throw \"RangeError: byteOffset out of range\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
if (byteOffsetArg.toInt32() % cls->_bytesPerElement != 0) {
|
||||
engine->evaluate("throw \"RangeError: byteOffset not a multiple of BYTES_PER_ELEMENT\"");
|
||||
}
|
||||
quint32 byteOffset = byteOffsetArg.toInt32();
|
||||
|
||||
if (context->argumentCount() == 2) {
|
||||
// case for end of ArrayBuffer
|
||||
if ((arrayBuffer->size() - byteOffset) % cls->_bytesPerElement != 0) {
|
||||
engine->evaluate("throw \"RangeError: byteLength - byteOffset not a multiple of BYTES_PER_ELEMENT\"");
|
||||
}
|
||||
quint32 length = (arrayBuffer->size() - byteOffset) / cls->_bytesPerElement;
|
||||
newObject = cls->newInstance(bufferArg, byteOffset, length);
|
||||
} else {
|
||||
|
||||
QScriptValue lengthArg = (context->argumentCount() > 2) ? context->argument(2) : QScriptValue();
|
||||
if (!lengthArg.isNumber()) {
|
||||
engine->evaluate("throw \"ArgumentError: 3nd arg is not a number\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
if (lengthArg.toInt32() < 0 ||
|
||||
byteOffsetArg.toInt32() + lengthArg.toInt32() * cls->_bytesPerElement > arrayBuffer->size()) {
|
||||
engine->evaluate("throw \"RangeError: byteLength out of range\"");
|
||||
return QScriptValue();
|
||||
}
|
||||
quint32 length = lengthArg.toInt32();
|
||||
|
||||
// case for well-defined range
|
||||
newObject = cls->newInstance(bufferArg, byteOffset, length);
|
||||
}
|
||||
}
|
||||
} else if (context->argument(0).isNumber()) {
|
||||
// case for new ArrayBuffer
|
||||
newObject = cls->newInstance(context->argument(0).toInt32());
|
||||
} else {
|
||||
newObject = cls->newInstance(bufferArg);
|
||||
}
|
||||
|
||||
if (context->isCalledAsConstructor()) {
|
||||
// if called with the new keyword, replace this object
|
||||
context->setThisObject(newObject);
|
||||
return engine->undefinedValue();
|
||||
}
|
||||
|
||||
return newObject;
|
||||
}
|
||||
|
||||
QScriptClass::QueryFlags TypedArray::queryProperty(const QScriptValue& object,
|
||||
const QScriptString& name,
|
||||
QueryFlags flags, uint* id) {
|
||||
if (name == _bytesPerElementName || name == _lengthName) {
|
||||
return flags &= HandlesReadAccess; // Only keep read access flags
|
||||
}
|
||||
|
||||
quint32 byteOffset = object.data().property(_byteOffsetName).toInt32();
|
||||
quint32 length = object.data().property(_lengthName).toInt32();
|
||||
bool ok = false;
|
||||
int pos = name.toArrayIndex(&ok);
|
||||
|
||||
// Check that name is a valid index and arrayBuffer exists
|
||||
if (ok && pos >= 0 && pos < length) {
|
||||
*id = byteOffset + pos * _bytesPerElement; // save pos to avoid recomputation
|
||||
return HandlesReadAccess | HandlesWriteAccess; // Read/Write access
|
||||
}
|
||||
|
||||
return ArrayBufferViewClass::queryProperty(object, name, flags, id);
|
||||
}
|
||||
|
||||
QScriptValue TypedArray::property(const QScriptValue& object,
|
||||
const QScriptString& name, uint id) {
|
||||
if (name == _bytesPerElementName) {
|
||||
return QScriptValue(_bytesPerElement);
|
||||
}
|
||||
if (name == _lengthName) {
|
||||
return object.data().property(_lengthName);
|
||||
}
|
||||
return ArrayBufferViewClass::property(object, name, id);
|
||||
}
|
||||
|
||||
QScriptValue::PropertyFlags TypedArray::propertyFlags(const QScriptValue& object,
|
||||
const QScriptString& name, uint id) {
|
||||
return QScriptValue::Undeletable;
|
||||
}
|
||||
|
||||
QString TypedArray::name() const {
|
||||
return _name.toString();
|
||||
}
|
||||
|
||||
QScriptValue TypedArray::prototype() const {
|
||||
return _proto;
|
||||
}
|
||||
|
||||
void TypedArray::setBytesPerElement(quint32 bytesPerElement) {
|
||||
_bytesPerElement = bytesPerElement;
|
||||
_ctor.setProperty(_bytesPerElementName, _bytesPerElement);
|
||||
}
|
||||
|
||||
// templated helper functions
|
||||
// don't work for floats as they require single precision settings
|
||||
template<class T>
|
||||
QScriptValue propertyHelper(const QByteArray* arrayBuffer, const QScriptString& name, uint id) {
|
||||
bool ok = false;
|
||||
name.toArrayIndex(&ok);
|
||||
|
||||
if (ok && arrayBuffer) {
|
||||
QDataStream stream(*arrayBuffer);
|
||||
stream.skipRawData(id);
|
||||
|
||||
T result;
|
||||
stream >> result;
|
||||
return result;
|
||||
}
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void setPropertyHelper(QByteArray* arrayBuffer, const QScriptString& name, uint id, const QScriptValue& value) {
|
||||
if (arrayBuffer && value.isNumber()) {
|
||||
QDataStream stream(arrayBuffer, QIODevice::ReadWrite);
|
||||
stream.skipRawData(id);
|
||||
|
||||
stream << (T)value.toNumber();
|
||||
}
|
||||
}
|
||||
|
||||
Int8ArrayClass::Int8ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_8_ARRAY_CLASS_NAME) {
|
||||
setBytesPerElement(sizeof(qint8));
|
||||
}
|
||||
|
||||
QScriptValue Int8ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) {
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
QScriptValue result = propertyHelper<qint8>(arrayBuffer, name, id);
|
||||
return (result.isValid()) ? result : TypedArray::property(object, name, id);
|
||||
}
|
||||
|
||||
void Int8ArrayClass::setProperty(QScriptValue &object, const QScriptString &name,
|
||||
uint id, const QScriptValue& value) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
setPropertyHelper<qint8>(ba, name, id, value);
|
||||
}
|
||||
|
||||
Uint8ArrayClass::Uint8ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_8_ARRAY_CLASS_NAME) {
|
||||
setBytesPerElement(sizeof(quint8));
|
||||
}
|
||||
|
||||
QScriptValue Uint8ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) {
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
QScriptValue result = propertyHelper<quint8>(arrayBuffer, name, id);
|
||||
return (result.isValid()) ? result : TypedArray::property(object, name, id);
|
||||
}
|
||||
|
||||
void Uint8ArrayClass::setProperty(QScriptValue& object, const QScriptString& name,
|
||||
uint id, const QScriptValue& value) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
setPropertyHelper<quint8>(ba, name, id, value);
|
||||
}
|
||||
|
||||
Uint8ClampedArrayClass::Uint8ClampedArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_8_CLAMPED_ARRAY_CLASS_NAME) {
|
||||
setBytesPerElement(sizeof(quint8));
|
||||
}
|
||||
|
||||
QScriptValue Uint8ClampedArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) {
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
QScriptValue result = propertyHelper<quint8>(arrayBuffer, name, id);
|
||||
return (result.isValid()) ? result : TypedArray::property(object, name, id);
|
||||
}
|
||||
|
||||
void Uint8ClampedArrayClass::setProperty(QScriptValue& object, const QScriptString& name,
|
||||
uint id, const QScriptValue& value) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
if (ba && value.isNumber()) {
|
||||
QDataStream stream(ba, QIODevice::ReadWrite);
|
||||
stream.skipRawData(id);
|
||||
if (value.toNumber() > 255) {
|
||||
stream << (quint8)255;
|
||||
} else if (value.toNumber() < 0) {
|
||||
stream << (quint8)0;
|
||||
} else {
|
||||
stream << (quint8)glm::clamp(qRound(value.toNumber()), 0, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Int16ArrayClass::Int16ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_16_ARRAY_CLASS_NAME) {
|
||||
setBytesPerElement(sizeof(qint16));
|
||||
}
|
||||
|
||||
QScriptValue Int16ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) {
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
QScriptValue result = propertyHelper<qint16>(arrayBuffer, name, id);
|
||||
return (result.isValid()) ? result : TypedArray::property(object, name, id);
|
||||
}
|
||||
|
||||
void Int16ArrayClass::setProperty(QScriptValue& object, const QScriptString& name,
|
||||
uint id, const QScriptValue& value) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
setPropertyHelper<qint16>(ba, name, id, value);
|
||||
}
|
||||
|
||||
Uint16ArrayClass::Uint16ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_16_ARRAY_CLASS_NAME) {
|
||||
setBytesPerElement(sizeof(quint16));
|
||||
}
|
||||
|
||||
QScriptValue Uint16ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) {
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
QScriptValue result = propertyHelper<quint16>(arrayBuffer, name, id);
|
||||
return (result.isValid()) ? result : TypedArray::property(object, name, id);
|
||||
}
|
||||
|
||||
void Uint16ArrayClass::setProperty(QScriptValue& object, const QScriptString& name,
|
||||
uint id, const QScriptValue& value) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
setPropertyHelper<quint16>(ba, name, id, value);
|
||||
}
|
||||
|
||||
Int32ArrayClass::Int32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_32_ARRAY_CLASS_NAME) {
|
||||
setBytesPerElement(sizeof(qint32));
|
||||
}
|
||||
|
||||
QScriptValue Int32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) {
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
QScriptValue result = propertyHelper<qint32>(arrayBuffer, name, id);
|
||||
return (result.isValid()) ? result : TypedArray::property(object, name, id);
|
||||
}
|
||||
|
||||
void Int32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name,
|
||||
uint id, const QScriptValue& value) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
setPropertyHelper<qint32>(ba, name, id, value);
|
||||
}
|
||||
|
||||
Uint32ArrayClass::Uint32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_32_ARRAY_CLASS_NAME) {
|
||||
setBytesPerElement(sizeof(quint32));
|
||||
}
|
||||
|
||||
QScriptValue Uint32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) {
|
||||
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
QScriptValue result = propertyHelper<quint32>(arrayBuffer, name, id);
|
||||
return (result.isValid()) ? result : TypedArray::property(object, name, id);
|
||||
}
|
||||
|
||||
void Uint32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name,
|
||||
uint id, const QScriptValue& value) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
setPropertyHelper<quint32>(ba, name, id, value);
|
||||
}
|
||||
|
||||
Float32ArrayClass::Float32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, FLOAT_32_ARRAY_CLASS_NAME) {
|
||||
setBytesPerElement(sizeof(float));
|
||||
}
|
||||
|
||||
QScriptValue Float32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) {
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());bool ok = false;
|
||||
name.toArrayIndex(&ok);
|
||||
|
||||
if (ok && arrayBuffer) {
|
||||
QDataStream stream(*arrayBuffer);
|
||||
stream.skipRawData(id);
|
||||
stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
|
||||
|
||||
float result;
|
||||
stream >> result;
|
||||
if (isNaN(result)) {
|
||||
return QScriptValue();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return TypedArray::property(object, name, id);
|
||||
}
|
||||
|
||||
void Float32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name,
|
||||
uint id, const QScriptValue& value) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
if (ba && value.isNumber()) {
|
||||
QDataStream stream(ba, QIODevice::ReadWrite);
|
||||
stream.skipRawData(id);
|
||||
stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
|
||||
|
||||
stream << (float)value.toNumber();
|
||||
}
|
||||
}
|
||||
|
||||
Float64ArrayClass::Float64ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, FLOAT_64_ARRAY_CLASS_NAME) {
|
||||
setBytesPerElement(sizeof(double));
|
||||
}
|
||||
|
||||
QScriptValue Float64ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) {
|
||||
QByteArray* arrayBuffer = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());bool ok = false;
|
||||
name.toArrayIndex(&ok);
|
||||
|
||||
if (ok && arrayBuffer) {
|
||||
QDataStream stream(*arrayBuffer);
|
||||
stream.skipRawData(id);
|
||||
stream.setFloatingPointPrecision(QDataStream::DoublePrecision);
|
||||
|
||||
double result;
|
||||
stream >> result;
|
||||
if (isNaN(result)) {
|
||||
return QScriptValue();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return TypedArray::property(object, name, id);
|
||||
}
|
||||
|
||||
void Float64ArrayClass::setProperty(QScriptValue& object, const QScriptString& name,
|
||||
uint id, const QScriptValue& value) {
|
||||
QByteArray* ba = qscriptvalue_cast<QByteArray*>(object.data().property(_bufferName).data());
|
||||
if (ba && value.isNumber()) {
|
||||
QDataStream stream(ba, QIODevice::ReadWrite);
|
||||
stream.skipRawData(id);
|
||||
stream.setFloatingPointPrecision(QDataStream::DoublePrecision);
|
||||
|
||||
stream << (double)value.toNumber();
|
||||
}
|
||||
}
|
||||
|
148
libraries/script-engine/src/TypedArrays.h
Normal file
148
libraries/script-engine/src/TypedArrays.h
Normal file
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// TypedArrays.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/9/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_TypedArrays_h
|
||||
#define hifi_TypedArrays_h
|
||||
|
||||
#include "ArrayBufferViewClass.h"
|
||||
|
||||
static const QString BYTES_PER_ELEMENT_PROPERTY_NAME = "BYTES_PER_ELEMENT";
|
||||
static const QString LENGTH_PROPERTY_NAME = "length";
|
||||
|
||||
static const QString INT_8_ARRAY_CLASS_NAME = "Int8Array";
|
||||
static const QString UINT_8_ARRAY_CLASS_NAME = "Uint8Array";
|
||||
static const QString UINT_8_CLAMPED_ARRAY_CLASS_NAME = "Uint8ClampedArray";
|
||||
static const QString INT_16_ARRAY_CLASS_NAME = "Int16Array";
|
||||
static const QString UINT_16_ARRAY_CLASS_NAME = "Uint16Array";
|
||||
static const QString INT_32_ARRAY_CLASS_NAME = "Int32Array";
|
||||
static const QString UINT_32_ARRAY_CLASS_NAME = "Uint32Array";
|
||||
static const QString FLOAT_32_ARRAY_CLASS_NAME = "Float32Array";
|
||||
static const QString FLOAT_64_ARRAY_CLASS_NAME = "Float64Array";
|
||||
|
||||
class TypedArray : public ArrayBufferViewClass {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TypedArray(ScriptEngine* scriptEngine, QString name);
|
||||
virtual QScriptValue newInstance(quint32 length);
|
||||
virtual QScriptValue newInstance(QScriptValue array);
|
||||
virtual QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 length);
|
||||
|
||||
virtual QueryFlags queryProperty(const QScriptValue& object,
|
||||
const QScriptString& name,
|
||||
QueryFlags flags, uint* id);
|
||||
virtual QScriptValue property(const QScriptValue& object,
|
||||
const QScriptString& name, uint id);
|
||||
virtual void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) = 0;
|
||||
virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object,
|
||||
const QScriptString& name, uint id);
|
||||
|
||||
QString name() const;
|
||||
QScriptValue prototype() const;
|
||||
|
||||
protected:
|
||||
static QScriptValue construct(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
void setBytesPerElement(quint32 bytesPerElement);
|
||||
|
||||
QScriptValue _proto;
|
||||
QScriptValue _ctor;
|
||||
|
||||
QScriptString _name;
|
||||
QScriptString _bytesPerElementName;
|
||||
QScriptString _lengthName;
|
||||
|
||||
quint32 _bytesPerElement;
|
||||
|
||||
friend class TypedArrayPrototype;
|
||||
};
|
||||
|
||||
class Int8ArrayClass : public TypedArray {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Int8ArrayClass(ScriptEngine* scriptEngine);
|
||||
|
||||
QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
|
||||
void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value);
|
||||
};
|
||||
|
||||
class Uint8ArrayClass : public TypedArray {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Uint8ArrayClass(ScriptEngine* scriptEngine);
|
||||
|
||||
QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
|
||||
void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value);
|
||||
};
|
||||
|
||||
class Uint8ClampedArrayClass : public TypedArray {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Uint8ClampedArrayClass(ScriptEngine* scriptEngine);
|
||||
|
||||
QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
|
||||
void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value);
|
||||
};
|
||||
|
||||
class Int16ArrayClass : public TypedArray {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Int16ArrayClass(ScriptEngine* scriptEngine);
|
||||
|
||||
QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
|
||||
void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value);
|
||||
};
|
||||
|
||||
class Uint16ArrayClass : public TypedArray {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Uint16ArrayClass(ScriptEngine* scriptEngine);
|
||||
|
||||
QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
|
||||
void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value);
|
||||
};
|
||||
|
||||
class Int32ArrayClass : public TypedArray {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Int32ArrayClass(ScriptEngine* scriptEngine);
|
||||
|
||||
QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
|
||||
void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value);
|
||||
};
|
||||
|
||||
class Uint32ArrayClass : public TypedArray {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Uint32ArrayClass(ScriptEngine* scriptEngine);
|
||||
|
||||
QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
|
||||
void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value);
|
||||
};
|
||||
|
||||
class Float32ArrayClass : public TypedArray {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Float32ArrayClass(ScriptEngine* scriptEngine);
|
||||
|
||||
QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
|
||||
void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value);
|
||||
};
|
||||
|
||||
class Float64ArrayClass : public TypedArray {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Float64ArrayClass(ScriptEngine* scriptEngine);
|
||||
|
||||
QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id);
|
||||
void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value);
|
||||
};
|
||||
|
||||
#endif // hifi_TypedArrays_h
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include "RegisteredMetaTypes.h"
|
||||
|
||||
static int vec4MetaTypeId = qRegisterMetaType<glm::vec4>();
|
||||
|
@ -25,6 +27,7 @@ void registerMetaTypes(QScriptEngine* engine) {
|
|||
qScriptRegisterMetaType(engine, vec2toScriptValue, vec2FromScriptValue);
|
||||
qScriptRegisterMetaType(engine, quatToScriptValue, quatFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, xColorToScriptValue, xColorFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, qColorToScriptValue, qColorFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, pickRayToScriptValue, pickRayFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue);
|
||||
}
|
||||
|
@ -101,6 +104,29 @@ void xColorFromScriptValue(const QScriptValue &object, xColor& color) {
|
|||
color.blue = object.property("blue").toVariant().toInt();
|
||||
}
|
||||
|
||||
QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) {
|
||||
QScriptValue object = engine->newObject();
|
||||
object.setProperty("red", color.red());
|
||||
object.setProperty("green", color.green());
|
||||
object.setProperty("blue", color.blue());
|
||||
object.setProperty("alpha", color.alpha());
|
||||
return object;
|
||||
}
|
||||
|
||||
void qColorFromScriptValue(const QScriptValue& object, QColor& color) {
|
||||
if (object.isNumber()) {
|
||||
color.setRgb(object.toUInt32());
|
||||
|
||||
} else if (object.isString()) {
|
||||
color.setNamedColor(object.toString());
|
||||
|
||||
} else {
|
||||
QScriptValue alphaValue = object.property("alpha");
|
||||
color.setRgb(object.property("red").toInt32(), object.property("green").toInt32(), object.property("blue").toInt32(),
|
||||
alphaValue.isNumber() ? alphaValue.toInt32() : 255);
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
QScriptValue origin = vec3toScriptValue(engine, pickRay.origin);
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include "CollisionInfo.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
class QColor;
|
||||
|
||||
Q_DECLARE_METATYPE(glm::vec4)
|
||||
Q_DECLARE_METATYPE(glm::vec3)
|
||||
Q_DECLARE_METATYPE(glm::vec2)
|
||||
|
@ -42,6 +44,9 @@ void quatFromScriptValue(const QScriptValue &object, glm::quat& quat);
|
|||
QScriptValue xColorToScriptValue(QScriptEngine* engine, const xColor& color);
|
||||
void xColorFromScriptValue(const QScriptValue &object, xColor& color);
|
||||
|
||||
QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color);
|
||||
void qColorFromScriptValue(const QScriptValue& object, QColor& color);
|
||||
|
||||
class PickRay {
|
||||
public:
|
||||
PickRay() : origin(0), direction(0) { };
|
||||
|
|
|
@ -837,3 +837,20 @@ bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, f
|
|||
QByteArray createByteArray(const glm::vec3& vector) {
|
||||
return QByteArray::number(vector.x) + ',' + QByteArray::number(vector.y) + ',' + QByteArray::number(vector.z);
|
||||
}
|
||||
|
||||
QString formatUsecTime(float usecs, int prec) {
|
||||
static const quint64 SECONDS_PER_MINUTE = 60;
|
||||
static const quint64 USECS_PER_MINUTE = USECS_PER_SECOND * SECONDS_PER_MINUTE;
|
||||
|
||||
QString result;
|
||||
if (usecs > USECS_PER_MINUTE) {
|
||||
result = QString::number(usecs / USECS_PER_MINUTE, 'f', prec) + "min";
|
||||
} else if (usecs > USECS_PER_SECOND) {
|
||||
result = QString::number(usecs / USECS_PER_SECOND, 'f', prec) + 's';
|
||||
} else if (usecs > USECS_PER_MSEC) {
|
||||
result = QString::number(usecs / USECS_PER_MSEC, 'f', prec) + "ms";
|
||||
} else {
|
||||
result = QString::number(usecs, 'f', prec) + "us";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -189,4 +189,6 @@ bool isNaN(float value);
|
|||
|
||||
QByteArray createByteArray(const glm::vec3& vector);
|
||||
|
||||
QString formatUsecTime(float usecs, int prec = 3);
|
||||
|
||||
#endif // hifi_SharedUtil_h
|
||||
|
|
|
@ -95,3 +95,34 @@ std::ostream& operator<<(std::ostream& s, const CapsuleShape& capsule) {
|
|||
|
||||
#endif // DEBUG
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
#include <QDebug>
|
||||
|
||||
QDebug& operator<<(QDebug& dbg, const glm::vec3& v) {
|
||||
dbg.nospace() << "{type='glm::vec3'"
|
||||
", x=" << v.x <<
|
||||
", y=" << v.y <<
|
||||
", z=" << v.z <<
|
||||
"}";
|
||||
return dbg;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& dbg, const glm::quat& q) {
|
||||
dbg.nospace() << "{type='glm::quat'"
|
||||
", x=" << q.x <<
|
||||
", y=" << q.y <<
|
||||
", z=" << q.z <<
|
||||
", w=" << q.w <<
|
||||
"}";
|
||||
return dbg;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& dbg, const glm::mat4& m) {
|
||||
dbg.nospace() << "{type='glm::mat4', [";
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
dbg << ' ' << m[0][j] << ' ' << m[1][j] << ' ' << m[2][j] << ' ' << m[3][j] << ';';
|
||||
}
|
||||
return dbg << " ]}";
|
||||
}
|
||||
|
||||
#endif // QT_NO_DEBUG_STREAM
|
||||
|
|
|
@ -38,13 +38,20 @@ QDataStream& operator>>(QDataStream& in, glm::quat& quaternion);
|
|||
|
||||
// less common utils can be enabled with DEBUG
|
||||
#ifdef DEBUG
|
||||
#include "CollisionInfo.h"
|
||||
#include "SphereShape.h"
|
||||
#include "CapsuleShape.h"
|
||||
class CollisionInfo;
|
||||
class SphereShape;
|
||||
class CapsuleShape;
|
||||
std::ostream& operator<<(std::ostream& s, const CollisionInfo& c);
|
||||
std::ostream& operator<<(std::ostream& s, const SphereShape& shape);
|
||||
std::ostream& operator<<(std::ostream& s, const CapsuleShape& capsule);
|
||||
#endif // DEBUG
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
class QDebug;
|
||||
// Add support for writing these to qDebug().
|
||||
QDebug& operator<<(QDebug& s, const glm::vec3& v);
|
||||
QDebug& operator<<(QDebug& s, const glm::quat& q);
|
||||
QDebug& operator<<(QDebug& s, const glm::mat4& m);
|
||||
#endif // QT_NO_DEBUG_STREAM
|
||||
|
||||
#endif // hifi_StreamUtils_h
|
||||
|
|
|
@ -12,25 +12,7 @@
|
|||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "PhysicsTestUtil.h"
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const glm::vec3& v) {
|
||||
s << "<" << v.x << "," << v.y << "," << v.z << ">";
|
||||
return s;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const glm::quat& q) {
|
||||
s << "<" << q.x << "," << q.y << "," << q.z << "," << q.w << ">";
|
||||
return s;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const glm::mat4& m) {
|
||||
s << "[";
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
s << " " << m[0][j] << " " << m[1][j] << " " << m[2][j] << " " << m[3][j] << ";";
|
||||
}
|
||||
s << " ]";
|
||||
return s;
|
||||
}
|
||||
#include "StreamUtils.h"
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const CollisionInfo& c) {
|
||||
s << "[penetration=" << c._penetration
|
||||
|
|
|
@ -21,9 +21,6 @@ const glm::vec3 xAxis(1.f, 0.f, 0.f);
|
|||
const glm::vec3 yAxis(0.f, 1.f, 0.f);
|
||||
const glm::vec3 zAxis(0.f, 0.f, 1.f);
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const glm::vec3& v);
|
||||
std::ostream& operator<<(std::ostream& s, const glm::quat& q);
|
||||
std::ostream& operator<<(std::ostream& s, const glm::mat4& m);
|
||||
std::ostream& operator<<(std::ostream& s, const CollisionInfo& c);
|
||||
|
||||
#endif // hifi_PhysicsTestUtil_h
|
||||
|
|
Loading…
Reference in a new issue