Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels

This commit is contained in:
Andrzej Kapolka 2014-07-15 09:25:36 -07:00
commit 60e8c12d4b
41 changed files with 2724 additions and 375 deletions

View file

@ -380,11 +380,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);
}
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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);

View 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');
});

View file

@ -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);

View file

@ -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();
}

View file

@ -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;
};

View file

@ -82,8 +82,7 @@ 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);
@ -168,13 +167,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 +423,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();
}
}

View file

@ -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

View file

@ -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);

View file

@ -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";

View file

@ -335,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;
@ -365,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);
@ -377,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;
@ -393,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);
@ -401,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;

View file

@ -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,27 +122,35 @@ 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;
const float LOUDNESS_EPSILON = 0.000001f;
float oldNextOutputTrailingLoudness = _nextOutputTrailingLoudness;
if (nextLoudness >= _nextOutputTrailingLoudness) {
_nextOutputTrailingLoudness = nextLoudness;
} else {
_nextOutputTrailingLoudness = (_nextOutputTrailingLoudness * PREVIOUS_FRAMES_RATIO) + (CURRENT_FRAME_RATIO * nextLoudness);
if (_nextOutputTrailingLoudness < LOUDNESS_EPSILON) {
_nextOutputTrailingLoudness = 0;
}
}
// fixes bug on Windows where _nextOutputTrailingLoudness sometimes becomes NaN. In that case,
// revert _nextOutputTrailingLoudness to its previous value
if (isNaN(_nextOutputTrailingLoudness)) {
_nextOutputTrailingLoudness = oldNextOutputTrailingLoudness;
}
}
bool PositionalAudioRingBuffer::shouldBeAddedToMix() {

View file

@ -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) {

View file

@ -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;

View file

@ -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);
}

View file

@ -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);

View file

@ -368,7 +368,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
uint32_t editID;
// check to make sure we have enough content to keep reading...
if (processedBytes + sizeof(editID) > length) {
if (length - processedBytes - sizeof(editID) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -387,7 +387,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
uint32_t creatorTokenID;
// check to make sure we have enough content to keep reading...
if (processedBytes + sizeof(creatorTokenID) > length) {
if (length - processedBytes - sizeof(creatorTokenID) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -426,7 +426,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// lastEdited
// check to make sure we have enough content to keep reading...
if (processedBytes + sizeof(newParticle._lastEdited) > length) {
if (length - processedBytes - sizeof(newParticle._lastEdited) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -439,7 +439,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// properties included bits
uint16_t packetContainsBits = 0;
if (!isNewParticle) {
if (processedBytes + sizeof(packetContainsBits) > length) {
if (length - processedBytes - sizeof(packetContainsBits) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -452,7 +452,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// radius
if (isNewParticle || ((packetContainsBits & CONTAINS_RADIUS) == CONTAINS_RADIUS)) {
if (processedBytes + sizeof(newParticle._radius) > length) {
if (length - processedBytes - sizeof(newParticle._radius) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -464,7 +464,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// position
if (isNewParticle || ((packetContainsBits & CONTAINS_POSITION) == CONTAINS_POSITION)) {
if (processedBytes + sizeof(newParticle._position) > length) {
if (length - processedBytes - sizeof(newParticle._position) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -476,7 +476,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// color
if (isNewParticle || ((packetContainsBits & CONTAINS_COLOR) == CONTAINS_COLOR)) {
if (processedBytes + sizeof(newParticle._color) > length) {
if (length - processedBytes - sizeof(newParticle._color) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -488,7 +488,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// velocity
if (isNewParticle || ((packetContainsBits & CONTAINS_VELOCITY) == CONTAINS_VELOCITY)) {
if (processedBytes + sizeof(newParticle._velocity) > length) {
if (length - processedBytes - sizeof(newParticle._velocity) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -500,7 +500,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// gravity
if (isNewParticle || ((packetContainsBits & CONTAINS_GRAVITY) == CONTAINS_GRAVITY)) {
if (processedBytes + sizeof(newParticle._gravity) > length) {
if (length - processedBytes - sizeof(newParticle._gravity) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -512,7 +512,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// damping
if (isNewParticle || ((packetContainsBits & CONTAINS_DAMPING) == CONTAINS_DAMPING)) {
if (processedBytes + sizeof(newParticle._damping) > length) {
if (length - processedBytes - sizeof(newParticle._damping) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -524,7 +524,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// lifetime
if (isNewParticle || ((packetContainsBits & CONTAINS_LIFETIME) == CONTAINS_LIFETIME)) {
if (processedBytes + sizeof(newParticle._lifetime) > length) {
if (length - processedBytes - sizeof(newParticle._lifetime) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -537,7 +537,7 @@ 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 (processedBytes + sizeof(newParticle._inHand) > length) {
if (length - processedBytes - sizeof(newParticle._inHand) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -549,7 +549,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// shouldDie
if (isNewParticle || ((packetContainsBits & CONTAINS_SHOULDDIE) == CONTAINS_SHOULDDIE)) {
if (processedBytes + sizeof(newParticle._shouldDie) > length) {
if (length - processedBytes - sizeof(newParticle._shouldDie) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -562,7 +562,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// script
if (isNewParticle || ((packetContainsBits & CONTAINS_SCRIPT) == CONTAINS_SCRIPT)) {
uint16_t scriptLength;
if (processedBytes + sizeof(scriptLength) > length) {
if (length - processedBytes - sizeof(scriptLength) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -571,7 +571,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
dataAt += sizeof(scriptLength);
processedBytes += sizeof(scriptLength);
if (processedBytes + scriptLength > length) {
if (length - processedBytes - scriptLength < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -585,7 +585,7 @@ 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 (processedBytes + sizeof(modelURLLength) > length) {
if (length - processedBytes - sizeof(modelURLLength) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -594,7 +594,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
dataAt += sizeof(modelURLLength);
processedBytes += sizeof(modelURLLength);
if (processedBytes + modelURLLength > length) {
if (length - processedBytes - modelURLLength < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -607,7 +607,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// modelScale
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_SCALE) == CONTAINS_MODEL_SCALE)) {
if (processedBytes + sizeof(newParticle._modelScale) > length) {
if (length - processedBytes - sizeof(newParticle._modelScale) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -619,7 +619,7 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// modelTranslation
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_TRANSLATION) == CONTAINS_MODEL_TRANSLATION)) {
if (processedBytes + sizeof(newParticle._modelTranslation) > length) {
if (length - processedBytes - sizeof(newParticle._modelTranslation) < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
@ -632,7 +632,7 @@ 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 (processedBytes + expectedBytesForPackedQuat > length) {
if (length - processedBytes - expectedBytesForPackedQuat < 0) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer

View 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());
}

View 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

View 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());
}

View 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

View 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;
}

View 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

View 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;
}

View 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

View 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\"");
}
}

View 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

View file

@ -32,10 +32,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 +93,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
_quatLibrary(),
_vec3Library(),
_uuidLibrary(),
_animationCache(this)
_animationCache(this),
_arrayBufferClass(new ArrayBufferClass(this))
{
}
@ -115,13 +119,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 +215,7 @@ void ScriptEngine::init() {
if (_isInitialized) {
return; // only initialize once
}
_isInitialized = true;
_voxelsScriptingInterface.init();

View file

@ -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;
};

View 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);
}
}

View 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

View 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();
}
}

View 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

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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