Merge pull request #6562 from Atlante45/baseball

Baseball
This commit is contained in:
Leonardo Murillo 2015-12-07 14:11:21 -08:00
commit 25494ec867
28 changed files with 1572 additions and 301 deletions

View file

@ -16,6 +16,7 @@
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <AssetClient.h>
#include <AvatarHashMap.h>
#include <AudioInjectorManager.h>
#include <AssetClient.h>
@ -131,6 +132,7 @@ void Agent::run() {
// make sure we request our script once the agent connects to the domain
auto nodeList = DependencyManager::get<NodeList>();
connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, this, &Agent::requestScript);
ThreadedAssignment::commonInit(AGENT_LOGGING_NAME, NodeType::Agent);
@ -223,6 +225,7 @@ void Agent::executeScript() {
// call model URL setters with empty URLs so our avatar, if user, will have the default models
scriptedAvatar->setFaceModelURL(QUrl());
scriptedAvatar->setSkeletonModelURL(QUrl());
// give this AvatarData object to the script engine
_scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data());

View file

@ -438,7 +438,6 @@ int AudioMixer::prepareMixForListeningNode(Node* node) {
AudioMixerClientData* listenerNodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
// zero out the client mix for this node
memset(_preMixSamples, 0, sizeof(_preMixSamples));
memset(_mixSamples, 0, sizeof(_mixSamples));
// loop through all other nodes that have sufficient audio to mix
@ -459,6 +458,9 @@ int AudioMixer::prepareMixForListeningNode(Node* node) {
if (otherNodeStream->getType() == PositionalAudioStream::Microphone) {
streamUUID = otherNode->getUUID();
}
// clear out the pre-mix samples before filling it up with this source
memset(_preMixSamples, 0, sizeof(_preMixSamples));
if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) {
streamsMixed += addStreamToMixForListeningNodeWithStream(listenerNodeData, streamUUID,

View file

@ -0,0 +1,105 @@
{
"assets": {
"crowd-boos.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/crowd-boos.wav",
"atp_url": "atp:c632c92b166ade60aa16b23ff1dfdf712856caeb83bd9311980b2d5edac821af.wav",
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
},
"crowd-cheers-organ.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/crowd-cheers-organ.wav",
"atp_url": "atp:b8044401a846ed29f881a0b9b80cf1ba41f26327180c28fc9c70d144f9b70045.wav",
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
},
"crowd-medium.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/crowd-medium.wav",
"atp_url": "atp:0821bf2ac60dd2f356dfdd948e8bb89c23984dc3584612f6c815765154f02cae.wav",
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
},
"baseball-hitting-bat-1.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-1.wav",
"atp_url": "atp:6f0b691a0c9c9ece6557d97fe242b1faec4020fe26efc9c17327993b513c5fe5.wav",
"attribution": "CC BY 3.0 - Credit: https://www.freesound.org/people/SocializedArtist45/"
},
"baseball-hitting-bat-set-1.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-1.wav",
"atp_url": "atp:5be5806205158ebdc5c3623ceb7ae73315028b51ffeae24292aff7042e3fa6a9.wav",
"attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/"
},
"baseball-hitting-bat-set-2.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-2.wav",
"atp_url": "atp:e68661374e2145c480809c26134782aad11e0de456c7802170c7abccc4028873.wav",
"attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/"
},
"baseball-hitting-bat-set-3.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-3.wav",
"atp_url": "atp:787e3c9af17dd3929527787176ede83d6806260e63ddd5a4cef48cd22e32c6f7.wav",
"attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/"
},
"baseball-hitting-bat-set-4.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-4.wav",
"atp_url": "atp:fc65383431a6238c7a4749f0f6f061f75a604ed5e17d775ab1b2955609e67ebb.wav",
"attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/"
},
"chatter-loop.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/chatter-loop.wav",
"atp_url": "atp:d9978e693035d4e2b5c7b546c8cccfb2dde5677834d9eed5206ccb2da55b4732.wav",
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
},
"zorba-organ.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/zorba-organ.wav",
"atp_url": "atp:1ee58f4d929fdef7c2989cd8be964952a24cdd653d80f57b6a89a2ae8e3029e1.wav",
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
},
"charge-organ.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/charge-organ.wav",
"atp_url": "atp:cefaba2d5f1a378382ee046716bffcf6b6a40676649b23a1e81a996efe22d7d3.wav",
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
},
"ball-game.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/ball-game.wav",
"atp_url": "atp:fb41e37f8f8f7b78e546ac78800df6e39edaa09b2df4bfa0afdd8d749dac38b8.wav",
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
},
"clapping.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/clapping.wav",
"atp_url": "atp:44a83a788ccfd2924e35c902c34808b24dbd0309d000299ce01a355f91cf8115.wav",
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
},
"pop1.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop1.wav",
"atp_url": "atp:a2bf79c95fe74c2c6c9188acc7230f7cd1b0f6008f2c81954ecd93eca0497ec6.wav"
},
"pop2.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop2.wav",
"atp_url": "atp:901067ebc2cda4c0d86ec02fcca2ed901e85f9097ad68bbde78b4cad8eaf2ed7.wav"
},
"pop3.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop3.wav",
"atp_url": "atp:830312930577cb1ea36ba2d743e957debbacceb441b20addead5a6faa05a3771.wav"
},
"pop4.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop4.wav",
"atp_url": "atp:62e80d0a9f084cf731bcc66ca6e9020ee88587417071a281eee3167307b53560.wav"
},
"fire1.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire1.wav",
"atp_url": "atp:ee6afe565576c4546c6d6cd89c1af532484c9b60ab30574d6b40c2df022f7260.wav",
"attribution": "CC BY 3.0 - Credir: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/"
},
"fire2.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire2.wav",
"atp_url": "atp:91ef19ba1c78be82d3fd06530cd05ceb90d1e75f4204c66819c208c55da049ef.wav",
"attribution": "CC BY 3.0 - Credit: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/"
},
"fire3.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire3.wav",
"atp_url": "atp:ee56993daf775012cf49293bfd5971eec7e5c396642f8bfbea902ba8f47b56cd.wav",
"attribution": "CC BY 3.0 - Credit: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/"
},
"fire4.wav": {
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire4.wav",
"atp_url": "atp:37775d267f00f82242a7e7f61f3f3d7bf64a54c5a3799e7f2540fa5f6b79bd02.wav",
"attribution": "CC BY 3.0 - Credit: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/"
}
}
}

View file

@ -0,0 +1,43 @@
//
// baseballCrowd.js
// examples/acScripts
//
// Created by Stephen Birarda on 10/20/15.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var chatter = SoundCache.getSound("atp:d9978e693035d4e2b5c7b546c8cccfb2dde5677834d9eed5206ccb2da55b4732.wav");
var extras = [
SoundCache.getSound("atp:1ee58f4d929fdef7c2989cd8be964952a24cdd653d80f57b6a89a2ae8e3029e1.wav"), // zorba
SoundCache.getSound("atp:cefaba2d5f1a378382ee046716bffcf6b6a40676649b23a1e81a996efe22d7d3.wav"), // charge
SoundCache.getSound("atp:fb41e37f8f8f7b78e546ac78800df6e39edaa09b2df4bfa0afdd8d749dac38b8.wav"), // take me out to the ball game
SoundCache.getSound("atp:44a83a788ccfd2924e35c902c34808b24dbd0309d000299ce01a355f91cf8115.wav") // clapping
];
var CHATTER_VOLUME = 0.20
var EXTRA_VOLUME = 0.25
function playChatter() {
if (chatter.downloaded && !chatter.isPlaying) {
Audio.playSound(chatter, { loop: true, volume: CHATTER_VOLUME });
}
}
chatter.ready.connect(playChatter);
var currentInjector = null;
function playRandomExtras() {
if ((!currentInjector || !currentInjector.isPlaying) && (Math.random() < (1.0 / 1800.0))) {
// play a random extra sound about every 30s
currentInjector = Audio.playSound(
extras[Math.floor(Math.random() * extras.length)],
{ volume: EXTRA_VOLUME }
);
}
}
Script.update.connect(playRandomExtras);

43
examples/baseball/bat.js Normal file
View file

@ -0,0 +1,43 @@
//
// bat.js
// examples/baseball/
//
// Created by Ryan Huffman on Nov 9, 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
Script.include("pitching.js");
var pitchingMachine = null;
this.pitchAndHideAvatar = function() {
if (!pitchingMachine) {
pitchingMachine = getOrCreatePitchingMachine();
Script.update.connect(function(dt) { pitchingMachine.update(dt); });
}
pitchingMachine.start();
MyAvatar.shouldRenderLocally = false;
};
this.startNearGrab = function() {
// send the avatar to the baseball location so that they're ready to bat
location = "/baseball"
this.pitchAndHideAvatar()
};
this.continueNearGrab = function() {
this.pitchAndHideAvatar()
};
this.releaseGrab = function() {
if (pitchingMachine) {
pitchingMachine.stop();
}
MyAvatar.shouldRenderLocally = true;
};
});

View file

@ -0,0 +1,76 @@
//
// createBatButton.js
// examples/baseball/moreBatsButton.js
//
// Created by Stephen Birarda on 10/28/2015.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
this.clickReleaseOnEntity = function(entityID, mouseEvent) {
if (!mouseEvent.isLeftButton) {
return;
}
this.dropBats();
};
this.startNearTrigger = function() {
this.dropBats();
};
this.startFarTrigger = function() {
this.dropBats();
};
this.dropBats = function() {
// if the bat box is near us, grab it's position
var nearby = Entities.findEntities(this.position, 20);
nearby.forEach(function(id) {
var properties = Entities.getEntityProperties(id, ["name", "position"]);
if (properties.name && properties.name == "Bat Box") {
boxPosition = properties.position;
}
});
var BAT_DROP_HEIGHT = 2.0;
var dropPosition;
if (!boxPosition) {
// we got no bat box position, drop in front of the avatar instead
} else {
// drop the bat above the bat box
dropPosition = Vec3.sum(boxPosition, { x: 0.0, y: BAT_DROP_HEIGHT, z: 0.0});
}
var BAT_MODEL = "atp:c47deaae09cca927f6bc9cca0e8bbe77fc618f8c3f2b49899406a63a59f885cb.fbx";
var BAT_COLLISION_HULL = "atp:9eafceb7510c41d50661130090de7e0632aa4da236ebda84a0059a4be2130e0c.obj";
var SCRIPT_URL = "http://rawgit.com/birarda/hifi/baseball/examples/baseball/bat.js"
var batUserData = {
grabbableKey: {
spatialKey: {
leftRelativePosition: { x: 0.9, y: 0.05, z: -0.05 },
rightRelativePosition: { x: 0.9, y: 0.05, z: 0.05 },
relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, 45)
}
}
}
// add the fresh bat at the drop position
var bat = Entities.addEntity({
name: 'Bat',
type: "Model",
modelURL: BAT_MODEL,
position: dropPosition,
compoundShapeURL: BAT_COLLISION_HULL,
collisionsWillMove: true,
velocity: { x: 0, y: 0.05, z: 0}, // workaround for gravity not taking effect on add
gravity: { x: 0, y: -9.81, z: 0},
rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, -90.0),
script: SCRIPT_URL,
userData: JSON.stringify(batUserData)
});
};
});

View file

@ -0,0 +1,138 @@
//
// firework.js
// examples/baseball/
//
// Created by Ryan Huffman on Nov 9, 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("utils.js");
var emitters = [];
var smokeTrailSettings = {
"name":"ParticlesTest Emitter",
"type": "ParticleEffect",
"color":{"red":205,"green":84.41176470588235,"blue":84.41176470588235},
"maxParticles":1000,
"velocity": { x: 0, y: 18.0, z: 0 },
"lifetime": 20,
"lifespan":3,
"emitRate":100,
"emitSpeed":0.5,
"speedSpread":0,
"emitOrientation":{"x":0,"y":0,"z":0,"w":1},
"emitDimensions":{"x":0,"y":0,"z":0},
"emitRadiusStart":0.5,
"polarStart":1,
"polarFinish":1,
"azimuthStart":0,
"azimuthFinish":0,
"emitAcceleration":{"x":0,"y":-0.70000001192092896,"z":0},
"accelerationSpread":{"x":0,"y":0,"z":0},
"particleRadius":0.03999999910593033,
"radiusSpread":0,
"radiusStart":0.13999999910593033,
"radiusFinish":0.14,
"colorSpread":{"red":0,"green":0,"blue":0},
"colorStart":{"red":255,"green":255,"blue":255},
"colorFinish":{"red":255,"green":255,"blue":255},
"alpha":1,
"alphaSpread":0,
"alphaStart":0,
"alphaFinish":1,
"textures":"https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png"
};
var fireworkSettings = {
"name":"ParticlesTest Emitter",
"type": "ParticleEffect",
"color":{"red":205,"green":84.41176470588235,"blue":84.41176470588235},
"maxParticles":1000,
"lifetime": 20,
"lifespan":4,
"emitRate":1000,
"emitSpeed":1.5,
"speedSpread":1.0,
"emitOrientation":{"x":-0.2,"y":0,"z":0,"w":0.7000000000000001},
"emitDimensions":{"x":0,"y":0,"z":0},
"emitRadiusStart":0.5,
"polarStart":1,
"polarFinish":1.2,
"azimuthStart":-Math.PI,
"azimuthFinish":Math.PI,
"emitAcceleration":{"x":0,"y":-0.70000001192092896,"z":0},
"accelerationSpread":{"x":0,"y":0,"z":0},
"particleRadius":0.03999999910593033,
"radiusSpread":0,
"radiusStart":0.13999999910593033,
"radiusFinish":0.14,
"colorSpread":{"red":0,"green":0,"blue":0},
"colorStart":{"red":255,"green":255,"blue":255},
"colorFinish":{"red":255,"green":255,"blue":255},
"alpha":1,
"alphaSpread":0,
"alphaStart":0,
"alphaFinish":1,
"textures":"https://hifi-public.s3.amazonaws.com/alan/Particles/spark_2.png",
};
var popSounds = getSounds([
"atp:a2bf79c95fe74c2c6c9188acc7230f7cd1b0f6008f2c81954ecd93eca0497ec6.wav",
"atp:901067ebc2cda4c0d86ec02fcca2ed901e85f9097ad68bbde78b4cad8eaf2ed7.wav",
"atp:830312930577cb1ea36ba2d743e957debbacceb441b20addead5a6faa05a3771.wav",
"atp:62e80d0a9f084cf731bcc66ca6e9020ee88587417071a281eee3167307b53560.wav"
]);
var launchSounds = getSounds([
"atp:ee6afe565576c4546c6d6cd89c1af532484c9b60ab30574d6b40c2df022f7260.wav",
"atp:91ef19ba1c78be82d3fd06530cd05ceb90d1e75f4204c66819c208c55da049ef.wav",
"atp:ee56993daf775012cf49293bfd5971eec7e5c396642f8bfbea902ba8f47b56cd.wav",
"atp:37775d267f00f82242a7e7f61f3f3d7bf64a54c5a3799e7f2540fa5f6b79bd02.wav"
]);
function playRandomSound(sounds, options) {
Audio.playSound(sounds[randomInt(sounds.length)], options);
}
function shootFirework(position, color, options) {
smokeTrailSettings.position = position;
smokeTrailSettings.velocity = randomVec3(-5, 5, 10, 20, 10, 15);
smokeTrailSettings.gravity = randomVec3(-5, 5, -9.8, -9.8, 20, 40);
playRandomSound(launchSounds, { position: {x: 0, y: 0 , z: 0}, volume: 3.0 });
var smokeID = Entities.addEntity(smokeTrailSettings);
Script.setTimeout(function() {
Entities.editEntity(smokeID, { emitRate: 0 });
var position = Entities.getEntityProperties(smokeID, ['position']).position;
fireworkSettings.position = position;
fireworkSettings.colorStart = color;
fireworkSettings.colorFinish = color;
var burstID = Entities.addEntity(fireworkSettings);
playRandomSound(popSounds, { position: {x: 0, y: 0 , z: 0}, volume: 3.0 });
Script.setTimeout(function() {
Entities.editEntity(burstID, { emitRate: 0 });
}, 500);
Script.setTimeout(function() {
Entities.deleteEntity(smokeID);
Entities.deleteEntity(burstID);
}, 10000);
}, 2000);
}
playFireworkShow = function(position, numberOfFireworks, duration) {
for (var i = 0; i < numberOfFireworks; i++) {
var randomOffset = randomVec3(-15, 15, -3, 3, -1, 1);
var randomPosition = Vec3.sum(position, randomOffset);
Script.setTimeout(function(position) {
return function() {
var color = randomColor(128, 255, 128, 255, 128, 255);
shootFirework(position, color, fireworkSettings);
}
}(randomPosition), Math.random() * duration)
}
}

165
examples/baseball/line.js Normal file
View file

@ -0,0 +1,165 @@
function info(message) {
print("[INFO] " + message);
}
function error(message) {
print("[ERROR] " + message);
}
/******************************************************************************
* PolyLine
*****************************************************************************/
var LINE_DIMENSIONS = { x: 2000, y: 2000, z: 2000 };
var MAX_LINE_LENGTH = 40; // This must be 2 or greater;
var PolyLine = function(position, color, defaultStrokeWidth) {
//info("Creating polyline");
//Vec3.print("New line at", position);
this.position = position;
this.color = color;
this.defaultStrokeWidth = 0.10;
this.points = [
{ x: 0, y: 0, z: 0 },
];
this.strokeWidths = [
this.defaultStrokeWidth,
]
this.normals = [
{ x: 1, y: 0, z: 0 },
]
this.entityID = Entities.addEntity({
type: "PolyLine",
position: position,
linePoints: this.points,
normals: this.normals,
strokeWidths: this.strokeWidths,
dimensions: LINE_DIMENSIONS,
color: color,
lifetime: 20,
});
};
PolyLine.prototype.enqueuePoint = function(position) {
if (this.isFull()) {
error("Hit max PolyLine size");
return;
}
//Vec3.print("pos", position);
//info("Number of points: " + this.points.length);
position = Vec3.subtract(position, this.position);
this.points.push(position);
this.normals.push({ x: 1, y: 0, z: 0 });
this.strokeWidths.push(this.defaultStrokeWidth);
Entities.editEntity(this.entityID, {
linePoints: this.points,
normals: this.normals,
strokeWidths: this.strokeWidths,
});
};
PolyLine.prototype.dequeuePoint = function() {
if (this.points.length == 0) {
error("Hit min PolyLine size");
return;
}
this.points = this.points.slice(1);
this.normals = this.normals.slice(1);
this.strokeWidths = this.strokeWidths.slice(1);
Entities.editEntity(this.entityID, {
linePoints: this.points,
normals: this.normals,
strokeWidths: this.strokeWidths,
});
};
PolyLine.prototype.getFirstPoint = function() {
return Vec3.sum(this.position, this.points[0]);
};
PolyLine.prototype.getLastPoint = function() {
return Vec3.sum(this.position, this.points[this.points.length - 1]);
};
PolyLine.prototype.getSize = function() {
return this.points.length;
}
PolyLine.prototype.isFull = function() {
return this.points.length >= MAX_LINE_LENGTH;
};
PolyLine.prototype.destroy = function() {
Entities.deleteEntity(this.entityID);
this.points = [];
};
/******************************************************************************
* InfiniteLine
*****************************************************************************/
InfiniteLine = function(position, color) {
this.position = position;
this.color = color;
this.lines = [new PolyLine(position, color)];
this.size = 0;
};
InfiniteLine.prototype.enqueuePoint = function(position) {
var currentLine;
if (this.lines.length == 0) {
currentLine = new PolyLine(position, this.color);
this.lines.push(currentLine);
} else {
currentLine = this.lines[this.lines.length - 1];
}
if (currentLine.isFull()) {
//info("Current line is full, creating new line");
//Vec3.print("Last line is", currentLine.getLastPoint());
//Vec3.print("New line is", position);
var newLine = new PolyLine(currentLine.getLastPoint(), this.color);
this.lines.push(newLine);
currentLine = newLine;
}
currentLine.enqueuePoint(position);
++this.size;
};
InfiniteLine.prototype.dequeuePoint = function() {
if (this.lines.length == 0) {
error("Trying to dequeue from InfiniteLine when no points are left");
return;
}
var lastLine = this.lines[0];
lastLine.dequeuePoint();
if (lastLine.getSize() <= 1) {
this.lines = this.lines.slice(1);
}
--this.size;
};
InfiniteLine.prototype.getFirstPoint = function() {
return this.lines.length > 0 ? this.lines[0].getFirstPoint() : null;
};
InfiniteLine.prototype.getLastPoint = function() {
return this.lines.length > 0 ? this.lines[lines.length - 1].getLastPoint() : null;
};
InfiniteLine.prototype.destroy = function() {
for (var i = 0; i < this.lines.length; ++i) {
this.lines[i].destroy();
}
this.size = 0;
};

View file

@ -0,0 +1,554 @@
//
// pitching.js
// examples/baseball/
//
// Created by Ryan Huffman on Nov 9, 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
print("Loading pitching");
Script.include("../libraries/line.js");
Script.include("firework.js");
Script.include("utils.js");
var DISTANCE_BILLBOARD_NAME = "CurrentScore";
var HIGH_SCORE_BILLBOARD_NAME = "HighScore";
var distanceBillboardEntityID = null;
var highScoreBillboardEntityID = null;
function getDistanceBillboardEntityID() {
if (distanceBillboardEntityID === null) {
distanceBillboardEntityID = findEntity({name: DISTANCE_BILLBOARD_NAME }, 1000);
}
return distanceBillboardEntityID;
}
function getHighScoreBillboardEntityID() {
if (highScoreBillboardEntityID === null) {
highScoreBillboardEntityID = findEntity({name: HIGH_SCORE_BILLBOARD_NAME }, 1000);
}
return highScoreBillboardEntityID;
}
var METERS_TO_FEET = 3.28084;
var AUDIO = {
crowdBoos: [
SoundCache.getSound("atp:c632c92b166ade60aa16b23ff1dfdf712856caeb83bd9311980b2d5edac821af.wav", false)
],
crowdCheers: [
SoundCache.getSound("atp:0821bf2ac60dd2f356dfdd948e8bb89c23984dc3584612f6c815765154f02cae.wav", false),
SoundCache.getSound("atp:b8044401a846ed29f881a0b9b80cf1ba41f26327180c28fc9c70d144f9b70045.wav", false),
],
batHit: [
SoundCache.getSound("atp:6f0b691a0c9c9ece6557d97fe242b1faec4020fe26efc9c17327993b513c5fe5.wav", false),
SoundCache.getSound("atp:5be5806205158ebdc5c3623ceb7ae73315028b51ffeae24292aff7042e3fa6a9.wav", false),
SoundCache.getSound("atp:e68661374e2145c480809c26134782aad11e0de456c7802170c7abccc4028873.wav", false),
SoundCache.getSound("atp:787e3c9af17dd3929527787176ede83d6806260e63ddd5a4cef48cd22e32c6f7.wav", false),
SoundCache.getSound("atp:fc65383431a6238c7a4749f0f6f061f75a604ed5e17d775ab1b2955609e67ebb.wav", false),
],
strike: [
SoundCache.getSound("atp:2a258076a85fffde4ba04b5ddc1de9034c7ae7d2af8c5d93d4fed0bcdef3472a.wav", false),
SoundCache.getSound("atp:518363524af3ed9b9ae4ca2ceee61f01aecd37e266a51c5a5f5487efe2520fd5.wav", false),
SoundCache.getSound("atp:d51d38b089574acbdfdf53ef733bfb3ab41d848fb8c0b6659c7790a785240009.wav", false),
],
foul: [
SoundCache.getSound("atp:316fa18ff9eef457f670452b449a8dc5a41ccabd4e948781c50aaafaae63b0ab.wav", false),
SoundCache.getSound("atp:c84d88352d38437edd7414b26dc74e567618712caeb59fec70822398b0c5a279.wav", false),
]
}
var PITCH_THUNK_SOUND_URL = "http://hifi-public.s3.amazonaws.com/sounds/ping_pong_gun/pong_sound.wav";
var pitchSound = SoundCache.getSound(PITCH_THUNK_SOUND_URL, false);
updateBillboard("");
var PITCHING_MACHINE_URL = "atp:87d4879530b698741ecc45f6f31789aac11f7865a2c3bec5fe9b061a182c80d4.fbx";
// This defines an offset to pitch a ball from with respect to the machine's position. The offset is a
// percentage of the machine's dimensions. So, { x: 0.5, y: -1.0, z: 0.0 } would offset on 50% on the
// machine's x axis, -100% on the y axis, and 0% on the z-axis. For the dimensions { x: 100, y: 100, z: 100 },
// that would result in an offset of { x: 50, y: -100, z: 0 }. This makes it easy to calculate an offset if
// the machine's dimensions change.
var PITCHING_MACHINE_OUTPUT_OFFSET_PCT = {
x: 0.0,
y: 0.25,
z: -1.05,
};
var PITCHING_MACHINE_PROPERTIES = {
name: "Pitching Machine",
type: "Model",
position: {
x: -0.93,
y: 0.8,
z: -19.8
},
velocity: {
x: 0,
y: -0.01,
z: 0
},
gravity: {
x: 0.0,
y: -9.8,
z: 0.0
},
registrationPoint: {
x: 0.5,
y: 0.5,
z: 0.5
},
rotation: Quat.fromPitchYawRollDegrees(0, 180, 0),
modelURL: PITCHING_MACHINE_URL,
dimensions: {
x: 0.4,
y: 0.61,
z: 0.39
},
collisionsWillMove: false,
shapeType: "Box"
};
PITCHING_MACHINE_PROPERTIES.dimensions = Vec3.multiply(2.5, PITCHING_MACHINE_PROPERTIES.dimensions);
var DISTANCE_FROM_PLATE = PITCHING_MACHINE_PROPERTIES.position.z;
var PITCH_RATE = 5000;
getOrCreatePitchingMachine = function() {
// Search for pitching machine
var entities = findEntities({ name: PITCHING_MACHINE_PROPERTIES.name }, 1000);
var pitchingMachineID = null;
// Create if it doesn't exist
if (entities.length == 0) {
pitchingMachineID = Entities.addEntity(PITCHING_MACHINE_PROPERTIES);
} else {
pitchingMachineID = entities[0];
}
// Wrap with PitchingMachine object and return
return new PitchingMachine(pitchingMachineID);
}
// The pitching machine wraps an entity ID and uses it's position & rotation to determin where to
// pitch the ball from and in which direction, and uses the dimensions to determine the scale of them ball.
function PitchingMachine(pitchingMachineID) {
this.pitchingMachineID = pitchingMachineID;
this.enabled = false;
this.baseball = null;
this.injector = null;
}
PitchingMachine.prototype = {
pitchBall: function() {
cleanupTrail();
if (!this.enabled) {
return;
}
print("Pitching ball");
var machineProperties = Entities.getEntityProperties(this.pitchingMachineID, ["dimensions", "position", "rotation"]);
var pitchFromPositionBase = machineProperties.position;
var pitchFromOffset = vec3Mult(machineProperties.dimensions, PITCHING_MACHINE_OUTPUT_OFFSET_PCT);
pitchFromOffset = Vec3.multiplyQbyV(machineProperties.rotation, pitchFromOffset);
var pitchFromPosition = Vec3.sum(pitchFromPositionBase, pitchFromOffset);
var pitchDirection = Quat.getFront(machineProperties.rotation);
var ballScale = machineProperties.dimensions.x / PITCHING_MACHINE_PROPERTIES.dimensions.x;
var speed = randomFloat(BASEBALL_MIN_SPEED, BASEBALL_MAX_SPEED);
var velocity = Vec3.multiply(speed, pitchDirection);
this.baseball = new Baseball(pitchFromPosition, velocity, ballScale);
if (!this.injector) {
this.injector = Audio.playSound(pitchSound, {
position: pitchFromPosition,
volume: 1.0
});
} else {
this.injector.restart();
}
},
start: function() {
if (this.enabled) {
return;
}
print("Starting Pitching Machine");
this.enabled = true;
this.pitchBall();
},
stop: function() {
if (!this.enabled) {
return;
}
print("Stopping Pitching Machine");
this.enabled = false;
},
update: function(dt) {
if (this.baseball) {
this.baseball.update(dt);
if (this.baseball.finished()) {
this.baseball = null;
var self = this;
Script.setTimeout(function() { self.pitchBall(); }, 3000);
}
}
}
};
var BASEBALL_MODEL_URL = "atp:7185099f1f650600ca187222573a88200aeb835454bd2f578f12c7fb4fd190fa.fbx";
var BASEBALL_MIN_SPEED = 7.0;
var BASEBALL_MAX_SPEED = 15.0;
var BASEBALL_RADIUS = 0.07468;
var BASEBALL_PROPERTIES = {
name: "Baseball",
type: "Model",
modelURL: BASEBALL_MODEL_URL,
shapeType: "Sphere",
position: {
x: 0,
y: 0,
z: 0
},
dimensions: {
x: BASEBALL_RADIUS,
y: BASEBALL_RADIUS,
z: BASEBALL_RADIUS
},
collisionsWillMove: true,
angularVelocity: {
x: 17.0,
y: 0,
z: -8.0,
x: 0.0,
y: 0,
z: 0.0,
},
angularDamping: 0.0,
damping: 0.0,
restitution: 0.5,
friction: 0.0,
friction: 0.5,
lifetime: 20,
gravity: {
x: 0,
y: 0,
z: 0
}
};
var BASEBALL_STATE = {
PITCHING: 0,
HIT: 1,
HIT_LANDED: 2,
STRIKE: 3,
FOUL: 4
};
function vec3Mult(a, b) {
return {
x: a.x * b.x,
y: a.y * b.y,
z: a.z * b.z,
};
}
function map(value, min1, max1, min2, max2) {
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
}
function orientationOf(vector) {
var RAD_TO_DEG = 180.0 / Math.PI;
var Y_AXIS = { x: 0, y: 1, z: 0 };
var X_AXIS = { x: 1, y: 0, z: 0 };
var direction = Vec3.normalize(vector);
var yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS);
var pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS);
return Quat.multiply(yaw, pitch);
}
var ACCELERATION_SPREAD = 0.35;
var TRAIL_COLOR = { red: 128, green: 255, blue: 89 };
var TRAIL_LIFETIME = 20;
var trail = null;
var trailInterval = null;
function cleanupTrail() {
if (trail) {
Script.clearInterval(this.trailInterval);
trailInterval = null;
trail.destroy();
trail = null;
}
}
function setupTrail(entityID, position) {
cleanupTrail();
var lastPosition = position;
trail = new InfiniteLine(position, { red: 128, green: 255, blue: 89 }, 20);
trailInterval = Script.setInterval(function() {
var properties = Entities.getEntityProperties(entityID, ['position']);
if (Vec3.distance(properties.position, lastPosition)) {
var strokeWidth = Math.log(1 + trail.size) * 0.05;
trail.enqueuePoint(properties.position, strokeWidth);
lastPosition = properties.position;
}
}, 50);
}
function Baseball(position, velocity, ballScale) {
var self = this;
this.state = BASEBALL_STATE.PITCHING;
// Setup entity properties
var properties = shallowCopy(BASEBALL_PROPERTIES);
properties.position = position;
properties.velocity = velocity;
properties.dimensions = Vec3.multiply(ballScale, properties.dimensions);
/*
properties.gravity = {
x: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD),
y: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD),
z: 0.0,
};
*/
// Create entity
this.entityID = Entities.addEntity(properties);
this.timeSincePitched = 0;
this.timeSinceHit = 0;
this.hitBallAtPosition = null;
this.distanceTravelled = 0;
this.wasHighScore = false;
this.landed = false;
// Listen for collision for the lifetime of the entity
Script.addEventHandler(this.entityID, "collisionWithEntity", function(entityA, entityB, collision) {
self.collisionCallback(entityA, entityB, collision);
});
if (Math.random() < 0.5) {
for (var i = 0; i < 50; i++) {
Script.setTimeout(function() {
Entities.editEntity(entityID, {
gravity: {
x: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD),
y: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD),
z: 0.0,
}
})
}, i * 100);
}
}
}
// Update the stadium billboard with the current distance or a text message and update the high score
// if it has been beaten.
function updateBillboard(distanceOrMessage) {
var distanceBillboardEntityID = getDistanceBillboardEntityID();
if (distanceBillboardEntityID) {
Entities.editEntity(distanceBillboardEntityID, {
text: distanceOrMessage
});
}
var highScoreBillboardEntityID = getHighScoreBillboardEntityID();
// If a number was passed in, let's see if it is larger than the current high score
// and update it if so.
if (!isNaN(distanceOrMessage) && highScoreBillboardEntityID) {
var properties = Entities.getEntityProperties(highScoreBillboardEntityID, ["text"]);
var bestDistance = parseInt(properties.text);
if (distanceOrMessage >= bestDistance) {
Entities.editEntity(highScoreBillboardEntityID, {
text: distanceOrMessage,
});
return true;
}
}
return false;
}
var FIREWORKS_SHOW_POSITION = { x: 0, y: 0, z: -78.0 };
var FIREWORK_PER_X_FEET = 100;
var MAX_FIREWORKS = 10;
Baseball.prototype = {
finished: function() {
return this.state == BASEBALL_STATE.FOUL
|| this.state == BASEBALL_STATE.STRIKE
|| this.state == BASEBALL_STATE.HIT_LANDED;
},
update: function(dt) {
this.timeSincePitched += dt;
if (this.state == BASEBALL_STATE.HIT) {
this.timeSinceHit += dt;
var myProperties = Entities.getEntityProperties(this.entityID, ['position', 'velocity']);
var speed = Vec3.length(myProperties.velocity);
this.distanceTravelled = Vec3.distance(this.hitBallAtPosition, myProperties.position) * METERS_TO_FEET;
var wasHighScore = updateBillboard(Math.ceil(this.distanceTravelled));
if (this.landed || this.timeSinceHit > 10 || speed < 1) {
this.wasHighScore = wasHighScore;
this.ballLanded();
}
} else if (this.state == BASEBALL_STATE.PITCHING) {
if (this.timeSincePitched > 10) {
print("TIMED OUT WHILE PITCHING");
this.state = BASEBALL_STATE.STRIKE;
}
}
},
ballLanded: function() {
this.state = BASEBALL_STATE.HIT_LANDED;
var numberOfFireworks = Math.floor(this.distanceTravelled / FIREWORK_PER_X_FEET);
if (numberOfFireworks > 0) {
numberOfFireworks = Math.min(MAX_FIREWORKS, numberOfFireworks);
playFireworkShow(FIREWORKS_SHOW_POSITION, numberOfFireworks, 2000);
}
print("Ball took " + this.timeSinceHit.toFixed(3) + " seconds to land");
print("Ball travelled " + this.distanceTravelled + " feet")
},
collisionCallback: function(entityA, entityB, collision) {
var self = this;
var myProperties = Entities.getEntityProperties(this.entityID, ['position', 'velocity']);
var myPosition = myProperties.position;
var myVelocity = myProperties.velocity;
// Activate gravity
Entities.editEntity(self.entityID, {
gravity: { x: 0, y: -9.8, z: 0 }
});
var name = Entities.getEntityProperties(entityB, ["name"]).name;
if (name == "Bat") {
if (this.state == BASEBALL_STATE.PITCHING) {
print("HIT");
var FOUL_MIN_YAW = -135.0;
var FOUL_MAX_YAW = 135.0;
var yaw = Math.atan2(myVelocity.x, myVelocity.z) * 180 / Math.PI;
var foul = yaw > FOUL_MIN_YAW && yaw < FOUL_MAX_YAW;
var speedMultiplier = 2;
if (foul && myVelocity.z > 0) {
var TUNNELED_PITCH_RANGE = 15.0;
var xzDist = Math.sqrt(myVelocity.x * myVelocity.x + myVelocity.z * myVelocity.z);
var pitch = Math.atan2(myVelocity.y, xzDist) * 180 / Math.PI;
print("Pitch: ", pitch);
// If the pitch is going straight out the back and has a pitch in the range TUNNELED_PITCH_RANGE,
// let's assume the ball tunneled through the bat and reverse its direction.
if (Math.abs(pitch) < TUNNELED_PITCH_RANGE) {
print("Reversing hit");
myVelocity.x *= -1;
myVelocity.y *= -1;
myVelocity.z *= -1;
yaw = Math.atan2(myVelocity.x, myVelocity.z) * 180 / Math.PI;
foul = yaw > FOUL_MIN_YAW && yaw < FOUL_MAX_YAW;
speedMultiplier = 3;
}
}
// Update ball velocity
Entities.editEntity(self.entityID, {
velocity: Vec3.multiply(speedMultiplier, myVelocity)
});
// Setup line update interval
setupTrail(self.entityID, myPosition);
// Setup bat hit sound
playRandomSound(AUDIO.batHit, {
position: myPosition,
volume: 2.0
});
// Setup crowd reaction sound
var speed = Vec3.length(myVelocity);
Script.setTimeout(function() {
playRandomSound((speed < 5.0) ? AUDIO.crowdBoos : AUDIO.crowdCheers, {
position: { x: 0 ,y: 0, z: 0 },
volume: 1.0
});
}, 500);
if (foul) {
print("FOUL, yaw: ", yaw);
updateBillboard("FOUL");
this.state = BASEBALL_STATE.FOUL;
playRandomSound(AUDIO.foul, {
position: myPosition,
volume: 2.0
});
} else {
print("HIT ", yaw);
this.state = BASEBALL_STATE.HIT;
}
}
} else if (name == "stadium") {
//entityCollisionWithGround(entityB, this.entityID, collision);
this.landed = true;
} else if (name == "backstop") {
if (this.state == BASEBALL_STATE.PITCHING) {
print("STRIKE");
this.state = BASEBALL_STATE.STRIKE;
updateBillboard("STRIKE");
playRandomSound(AUDIO.strike, {
position: myPosition,
volume: 2.0
});
}
}
}
}
function entityCollisionWithGround(ground, entity, collision) {
var ZERO_VEC = { x: 0, y: 0, z: 0 };
var dVelocityMagnitude = Vec3.length(collision.velocityChange);
var position = Entities.getEntityProperties(entity, "position").position;
var particleRadius = 0.3;
var speed = map(dVelocityMagnitude, 0.05, 3, 0.02, 0.09);
var displayTime = 400;
var orientationChange = orientationOf(collision.velocityChange);
var dustEffect = Entities.addEntity({
type: "ParticleEffect",
name: "Dust-Puff",
position: position,
color: {red: 195, green: 170, blue: 185},
lifespan: 3,
lifetime: 2,//displayTime/1000 * 2, //So we can fade particle system out gracefully
emitRate: 5,
emitSpeed: speed,
emitAcceleration: ZERO_VEC,
accelerationSpread: ZERO_VEC,
isEmitting: true,
polarStart: Math.PI/2,
polarFinish: Math.PI/2,
emitOrientation: orientationChange,
radiusSpread: 0.1,
radiusStart: particleRadius,
radiusFinish: particleRadius + particleRadius / 2,
particleRadius: particleRadius,
alpha: 0.45,
alphaFinish: 0.001,
textures: "https://hifi-public.s3.amazonaws.com/alan/Playa/Particles/Particle-Sprite-Gen.png"
});
}
Script.scriptEnding.connect(function() {
cleanupTrail();
Entities.deleteEntity(pitchingMachineID);
});

View file

@ -0,0 +1,92 @@
//
// utils.js
// examples/baseball/
//
// Created by Ryan Huffman on Nov 9, 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
randomInt = function(low, high) {
return Math.floor(randomFloat(low, high));
};
randomFloat = function(low, high) {
if (high === undefined) {
high = low;
low = 0;
}
return low + Math.random() * (high - low);
};
randomColor = function(redMin, redMax, greenMin, greenMax, blueMin, blueMax) {
return {
red: Math.ceil(randomFloat(redMin, redMax)),
green: Math.ceil(randomFloat(greenMin, greenMax)),
blue: Math.ceil(randomFloat(blueMin, blueMax)),
}
};
randomVec3 = function(xMin, xMax, yMin, yMax, zMin, zMax) {
return {
x: randomFloat(xMin, xMax),
y: randomFloat(yMin, yMax),
z: randomFloat(zMin, zMax),
}
};
getSounds = function(soundURLs) {
var sounds = [];
for (var i = 0; i < soundURLs.length; ++i) {
sounds.push(SoundCache.getSound(soundURLs[i], false));
}
return sounds;
};
playRandomSound = function(sounds, options) {
if (options === undefined) {
options = {
volume: 1.0,
position: MyAvatar.position,
}
}
return Audio.playSound(sounds[randomInt(sounds.length)], options);
}
shallowCopy = function(obj) {
var copy = {}
for (var key in obj) {
copy[key] = obj[key];
}
return copy;
}
findEntity = function(properties, searchRadius) {
var entities = findEntities(properties, searchRadius);
return entities.length > 0 ? entities[0] : null;
}
// Return all entities with properties `properties` within radius `searchRadius`
findEntities = function(properties, searchRadius) {
var entities = Entities.findEntities(MyAvatar.position, searchRadius);
var matchedEntities = [];
var keys = Object.keys(properties);
for (var i = 0; i < entities.length; ++i) {
var match = true;
var candidateProperties = Entities.getEntityProperties(entities[i], keys);
for (var key in properties) {
if (candidateProperties[key] != properties[key]) {
// This isn't a match, move to next entity
match = false;
break;
}
}
if (match) {
matchedEntities.push(entities[i]);
}
}
return matchedEntities;
}

View file

@ -201,6 +201,33 @@ function entityIsGrabbedByOther(entityID) {
return false;
}
function getSpatialOffsetPosition(hand, spatialKey) {
if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) {
return spatialKey.leftRelativePosition;
}
if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) {
return spatialKey.rightRelativePosition;
}
if (spatialKey.relativePosition) {
return spatialKey.relativePosition;
}
return Vec3.ZERO;
}
function getSpatialOffsetRotation(hand, spatialKey) {
if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) {
return spatialKey.leftRelativeRotation;
}
if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) {
return spatialKey.rightRelativeRotation;
}
if (spatialKey.relativeRotation) {
return spatialKey.relativeRotation;
}
return Quat.IDENTITY;
}
function MyController(hand) {
this.hand = hand;
@ -224,20 +251,11 @@ function MyController(hand) {
this.triggerValue = 0; // rolling average of trigger value
this.rawTriggerValue = 0;
this.rawBumperValue = 0;
this.overlayLine = null;
this.offsetPosition = {
x: 0.0,
y: 0.0,
z: 0.0
};
this.offsetRotation = {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0
};
this.offsetPosition = Vec3.ZERO;
this.offsetRotation = Quat.IDENTITY;
var _this = this;
@ -835,12 +853,8 @@ function MyController(hand) {
if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) {
// if an object is "equipped" and has a spatialKey, use it.
if (grabbableData.spatialKey.relativePosition) {
this.offsetPosition = grabbableData.spatialKey.relativePosition;
}
if (grabbableData.spatialKey.relativeRotation) {
this.offsetRotation = grabbableData.spatialKey.relativeRotation;
}
this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
} else {
var objectRotation = grabbedProperties.rotation;
this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
@ -966,23 +980,8 @@ function MyController(hand) {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
// use a spring to pull the object to where it will be when equipped
var relativeRotation = {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0
};
var relativePosition = {
x: 0.0,
y: 0.0,
z: 0.0
};
if (grabbableData.spatialKey.relativePosition) {
relativePosition = grabbableData.spatialKey.relativePosition;
}
if (grabbableData.spatialKey.relativeRotation) {
relativeRotation = grabbableData.spatialKey.relativeRotation;
}
var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
var handRotation = this.getHandRotation();
var handPosition = this.getHandPosition();
var targetRotation = Quat.multiply(handRotation, relativeRotation);

View file

@ -67,12 +67,17 @@
}
var BOW_SPATIAL_KEY = {
relativePosition: {
x: 0,
leftRelativePosition: {
x: 0.05,
y: 0.06,
z: 0.11
z: -0.05
},
relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, 90)
rightRelativePosition: {
x: -0.05,
y: 0.06,
z: -0.05
},
relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90)
}

View file

@ -48,12 +48,17 @@ var bow = Entities.addEntity({
grabbableKey: {
invertSolidWhileHeld: true,
spatialKey: {
relativePosition: {
x: 0,
leftRelativePosition: {
x: 0.05,
y: 0.06,
z: 0.11
z: -0.05
},
relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, 90)
rightRelativePosition: {
x: -0.05,
y: 0.06,
z: -0.05
},
relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90)
}
}
})

View file

@ -21,17 +21,17 @@
EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) {
switch (type) {
case ACTION_TYPE_NONE:
return nullptr;
return EntityActionPointer();
case ACTION_TYPE_OFFSET:
return (EntityActionPointer) new ObjectActionOffset(id, ownerEntity);
return std::make_shared<ObjectActionOffset>(id, ownerEntity);
case ACTION_TYPE_SPRING:
return (EntityActionPointer) new ObjectActionSpring(id, ownerEntity);
return std::make_shared<ObjectActionSpring>(id, ownerEntity);
case ACTION_TYPE_HOLD:
return (EntityActionPointer) new AvatarActionHold(id, ownerEntity);
return std::make_shared<AvatarActionHold>(id, ownerEntity);
}
assert(false);
return nullptr;
Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity action type");
return EntityActionPointer();
}

View file

@ -9,63 +9,77 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "QVariantGLM.h"
#include "avatar/AvatarManager.h"
#include "AvatarActionHold.h"
#include <QVariantGLM.h>
#include "avatar/AvatarManager.h"
const uint16_t AvatarActionHold::holdVersion = 1;
AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectActionSpring(id, ownerEntity),
_relativePosition(glm::vec3(0.0f)),
_relativeRotation(glm::quat()),
_hand("right"),
_holderID(QUuid()) {
ObjectActionSpring(id, ownerEntity)
{
_type = ACTION_TYPE_HOLD;
#if WANT_DEBUG
#if WANT_DEBUG
qDebug() << "AvatarActionHold::AvatarActionHold";
#endif
#endif
}
AvatarActionHold::~AvatarActionHold() {
#if WANT_DEBUG
#if WANT_DEBUG
qDebug() << "AvatarActionHold::~AvatarActionHold";
#endif
#endif
}
std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) {
std::shared_ptr<Avatar> holdingAvatar = nullptr;
auto avatarManager = DependencyManager::get<AvatarManager>();
auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID));
if (!holdingAvatar) {
return holdingAvatar;
}
withTryReadLock([&]{
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
AvatarSharedPointer holdingAvatarData = avatarManager->getAvatarBySessionID(_holderID);
holdingAvatar = std::static_pointer_cast<Avatar>(holdingAvatarData);
if (holdingAvatar) {
glm::vec3 offset;
glm::vec3 palmPosition;
glm::quat palmRotation;
if (_hand == "right") {
bool isRightHand = (_hand == "right");
glm::vec3 palmPosition { Vectors::ZERO };
glm::quat palmRotation { Quaternions::IDENTITY };
if (_ignoreIK && holdingAvatar->isMyAvatar()) {
// We cannot ignore other avatars IK and this is not the point of this option
// This is meant to make the grabbing behavior more reactive.
if (isRightHand) {
palmPosition = holdingAvatar->getHand()->getCopyOfPalmData(HandData::RightHand).getPosition();
palmRotation = holdingAvatar->getHand()->getCopyOfPalmData(HandData::RightHand).getRotation();
} else {
palmPosition = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getPosition();
palmRotation = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getRotation();
}
} else {
if (isRightHand) {
palmPosition = holdingAvatar->getRightPalmPosition();
palmRotation = holdingAvatar->getRightPalmRotation();
} else {
palmPosition = holdingAvatar->getLeftPalmPosition();
palmRotation = holdingAvatar->getLeftPalmRotation();
}
rotation = palmRotation * _relativeRotation;
offset = rotation * _relativePosition;
position = palmPosition + offset;
}
if (!isRightHand) {
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
palmRotation *= yFlip; // Match right hand frame of reference
}
rotation = palmRotation * _relativeRotation;
position = palmPosition + rotation * _relativePosition;
});
return holdingAvatar;
}
void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
glm::quat rotation;
glm::vec3 position;
glm::quat rotation { Quaternions::IDENTITY };
glm::vec3 position { Vectors::ZERO };
bool valid = false;
int holdCount = 0;
@ -168,6 +182,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
QUuid holderID;
bool kinematic;
bool kinematicSetVelocity;
bool ignoreIK;
bool needUpdate = false;
bool somethingChanged = ObjectAction::updateArguments(arguments);
@ -220,7 +235,8 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
hand != _hand ||
holderID != _holderID ||
kinematic != _kinematic ||
kinematicSetVelocity != _kinematicSetVelocity) {
kinematicSetVelocity != _kinematicSetVelocity ||
ignoreIK != _ignoreIK) {
needUpdate = true;
}
});
@ -236,6 +252,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
_holderID = holderID;
_kinematic = kinematic;
_kinematicSetVelocity = kinematicSetVelocity;
_ignoreIK = ignoreIK;
_active = true;
auto ownerEntity = _ownerEntity.lock();
@ -260,6 +277,7 @@ QVariantMap AvatarActionHold::getArguments() {
arguments["hand"] = _hand;
arguments["kinematic"] = _kinematic;
arguments["kinematicSetVelocity"] = _kinematicSetVelocity;
arguments["ignoreIK"] = _ignoreIK;
});
return arguments;
}

View file

@ -38,17 +38,19 @@ public:
std::shared_ptr<Avatar> getTarget(glm::quat& rotation, glm::vec3& position);
private:
void doKinematicUpdate(float deltaTimeStep);
static const uint16_t holdVersion;
glm::vec3 _relativePosition;
glm::quat _relativeRotation;
QString _hand;
glm::vec3 _relativePosition { Vectors::ZERO };
glm::quat _relativeRotation { Quaternions::IDENTITY };
QString _hand { "right" };
QUuid _holderID;
void doKinematicUpdate(float deltaTimeStep);
bool _kinematic { false };
bool _kinematicSetVelocity { false };
bool _previousSet { false };
bool _ignoreIK { true };
glm::vec3 _previousPositionalTarget;
glm::quat _previousRotationalTarget;

View file

@ -409,7 +409,7 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) {
AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) {
if (sessionID == _myAvatar->getSessionUUID()) {
return std::static_pointer_cast<Avatar>(_myAvatar);
return _myAvatar;
}
return findAvatar(sessionID);

View file

@ -560,7 +560,7 @@ void MyAvatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
if (!_shouldRender) {
return; // exit early
}
Avatar::render(renderArgs, cameraPosition);
}
@ -1186,7 +1186,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl
if (!_skeletonModel.isRenderable()) {
return; // wait until all models are loaded
}
fixupModelsInScene();
// Render head so long as the camera isn't inside it

View file

@ -254,6 +254,7 @@ public slots:
void setEnableDebugDrawDefaultPose(bool isEnabled);
void setEnableDebugDrawAnimPose(bool isEnabled);
void setEnableDebugDrawPosition(bool isEnabled);
bool getEnableMeshVisible() const { return _skeletonModel.isVisible(); }
void setEnableMeshVisible(bool isEnabled);
Q_INVOKABLE void setAnimGraphUrl(const QUrl& url);
@ -276,7 +277,7 @@ private:
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override;
virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f) override;
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; }
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); }
bool getShouldRenderLocally() const { return _shouldRender; }
bool getDriveKeys(int key) { return _driveKeys[key] != 0.0f; };
bool isMyAvatar() const override { return true; }

View file

@ -746,82 +746,75 @@ QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID,
return result;
}
glm::vec3 EntityScriptingInterface::voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords) {
EntityItemPointer EntityScriptingInterface::checkForTreeEntityAndTypeMatch(const QUuid& entityID,
EntityTypes::EntityType entityType) {
if (!_entityTree) {
return glm::vec3(0.0f);
return EntityItemPointer();
}
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::voxelCoordsToWorldCoords no entity with ID" << entityID;
qDebug() << "EntityScriptingInterface::checkForTreeEntityAndTypeMatch - no entity with ID" << entityID;
return entity;
}
if (entityType != EntityTypes::Unknown && entity->getType() != entityType) {
return EntityItemPointer();
}
return entity;
}
glm::vec3 EntityScriptingInterface::voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords) {
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::PolyVox)) {
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->voxelCoordsToWorldCoords(voxelCoords);
} else {
return glm::vec3(0.0f);
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType != EntityTypes::PolyVox) {
return glm::vec3(0.0f);
}
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->voxelCoordsToWorldCoords(voxelCoords);
}
glm::vec3 EntityScriptingInterface::worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords) {
if (!_entityTree) {
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::PolyVox)) {
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->worldCoordsToVoxelCoords(worldCoords);
} else {
return glm::vec3(0.0f);
}
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::worldCoordsToVoxelCoords no entity with ID" << entityID;
return glm::vec3(0.0f);
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType != EntityTypes::PolyVox) {
return glm::vec3(0.0f);
}
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->worldCoordsToVoxelCoords(worldCoords);
}
glm::vec3 EntityScriptingInterface::voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords) {
if (!_entityTree) {
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::PolyVox)) {
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->voxelCoordsToLocalCoords(voxelCoords);
} else {
return glm::vec3(0.0f);
}
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::voxelCoordsToLocalCoords no entity with ID" << entityID;
return glm::vec3(0.0f);
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType != EntityTypes::PolyVox) {
return glm::vec3(0.0f);
}
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->voxelCoordsToLocalCoords(voxelCoords);
}
glm::vec3 EntityScriptingInterface::localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords) {
if (!_entityTree) {
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::PolyVox)) {
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->localCoordsToVoxelCoords(localCoords);
} else {
return glm::vec3(0.0f);
}
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::localCoordsToVoxelCoords no entity with ID" << entityID;
return glm::vec3(0.0f);
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType != EntityTypes::PolyVox) {
return glm::vec3(0.0f);
}
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->localCoordsToVoxelCoords(localCoords);
}
glm::vec3 EntityScriptingInterface::getJointTranslation(const QUuid& entityID, int jointIndex) {
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) {
auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(entity);
return modelEntity->getJointTranslation(jointIndex);
} else {
return glm::vec3(0.0f);
}
}
glm::quat EntityScriptingInterface::getJointRotation(const QUuid& entityID, int jointIndex) {
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) {
auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(entity);
return modelEntity->getJointRotation(jointIndex);
} else {
return glm::quat();
}
}

View file

@ -148,6 +148,9 @@ public slots:
Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords);
Q_INVOKABLE glm::vec3 voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords);
Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords);
Q_INVOKABLE glm::vec3 getJointTranslation(const QUuid& entityID, int jointIndex);
Q_INVOKABLE glm::quat getJointRotation(const QUuid& entityID, int jointIndex);
signals:
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
@ -179,6 +182,9 @@ private:
bool setVoxels(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor);
bool setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor);
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);
EntityItemPointer checkForTreeEntityAndTypeMatch(const QUuid& entityID,
EntityTypes::EntityType entityType = EntityTypes::Unknown);
/// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode

View file

@ -121,6 +121,9 @@ public:
virtual bool shouldBePhysical() const;
static void cleanupLoadedAnimations();
virtual glm::vec3 getJointPosition(int jointIndex) const { return glm::vec3(); }
virtual glm::quat getJointRotation(int jointIndex) const { return glm::quat(); }
private:
void setAnimationSettings(const QString& value); // only called for old bitstream format

View file

@ -35,16 +35,19 @@ vr::IVRSystem* acquireOpenVrSystem();
void releaseOpenVrSystem();
const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
const QString CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b";
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET * 2.0f);
static const QString CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b";
static const QString MENU_PARENT = "Avatar";
static const QString MENU_NAME = "Vive Controllers";
static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME;
static const QString RENDER_CONTROLLERS = "Render Hand Controllers";
const QString ViveControllerManager::NAME = "OpenVR";
const QString MENU_PARENT = "Avatar";
const QString MENU_NAME = "Vive Controllers";
const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME;
const QString RENDER_CONTROLLERS = "Render Hand Controllers";
bool ViveControllerManager::isSupported() const {
#ifdef Q_OS_WIN
auto hmd = acquireOpenVrSystem();
@ -320,14 +323,11 @@ void ViveControllerManager::InputDevice::handleButtonEvent(uint32_t button, bool
}
void ViveControllerManager::InputDevice::handlePoseEvent(const mat4& mat, bool left) {
glm::vec3 position = extractTranslation(mat);
glm::quat rotation = glm::quat_cast(mat);
// When the sensor-to-world rotation is identity the coordinate axes look like this:
//
// user
// forward
// z
// -z
// |
// y| user
// y o----x right
@ -372,17 +372,27 @@ void ViveControllerManager::InputDevice::handlePoseEvent(const mat4& mat, bool l
// Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
//
// Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
const glm::quat quarterX = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
const glm::quat yFlip = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
float sign = left ? -1.0f : 1.0f;
const glm::quat signedQuaterZ = glm::angleAxis(sign * PI / 2.0f, glm::vec3(0.0f, 0.0f, 1.0f));
const glm::quat eighthX = glm::angleAxis(PI / 4.0f, glm::vec3(1.0f, 0.0f, 0.0f));
const glm::quat offset = glm::inverse(signedQuaterZ * eighthX);
rotation = rotation * offset * yFlip * quarterX;
position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET);
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
static const glm::quat viveToHand = yFlip * quarterX;
static const glm::quat leftQuaterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat rightQuaterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X);
static const glm::quat leftRotationOffset = glm::inverse(leftQuaterZ * eighthX) * viveToHand;
static const glm::quat rightRotationOffset = glm::inverse(rightQuaterZ * eighthX) * viveToHand;
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
glm::vec3 position = extractTranslation(mat);
glm::quat rotation = glm::quat_cast(mat);
position += rotation * (left ? leftTranslationOffset : rightTranslationOffset);
rotation = rotation * (left ? leftRotationOffset : rightRotationOffset);
_poseStateMap[left ? controller::LEFT_HAND : controller::RIGHT_HAND] = controller::Pose(position, rotation);
}

View file

@ -46,7 +46,7 @@ void AssetResourceRequest::doSend() {
}
connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::progress);
connect(_assetRequest, &AssetRequest::finished, [this](AssetRequest* req) {
connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) {
Q_ASSERT(_state == InProgress);
Q_ASSERT(req == _assetRequest);
Q_ASSERT(req->getState() == AssetRequest::Finished);

View file

@ -140,6 +140,7 @@ void ObjectMotionState::updateCCDConfiguration() {
// TODO: Ideally the swept sphere radius would be contained by the object. Using the bounding sphere
// radius works well for spherical objects, but may cause issues with other shapes. For arbitrary
// objects we may want to consider a different approach, such as grouping rigid bodies together.
_body->setCcdSweptSphereRadius(radius);
} else {
// Disable CCD

View file

@ -111,7 +111,7 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName
const auto exception = engine.uncaughtException().toString();
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
engine.clearExceptions();
auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line);
if (!backtrace.empty()) {
static const auto lineSeparator = "\n ";
@ -326,7 +326,7 @@ void ScriptEngine::init() {
}
_isInitialized = true;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
entityScriptingInterface->init();
@ -392,7 +392,7 @@ void ScriptEngine::init() {
registerGlobalObject("Recording", recordingInterface.data());
registerGlobalObject("Assets", &_assetScriptingInterface);
}
void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {
@ -401,8 +401,8 @@ void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {
qDebug() << "*** WARNING *** ScriptEngine::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
#endif
QMetaObject::invokeMethod(this, "registerValue",
Q_ARG(const QString&, valueName),
Q_ARG(QScriptValue, value));
Q_ARG(const QString&, valueName),
Q_ARG(QScriptValue, value));
return;
}
@ -428,17 +428,17 @@ void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {
void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
#endif
#endif
QMetaObject::invokeMethod(this, "registerGlobalObject",
Q_ARG(const QString&, name),
Q_ARG(QObject*, object));
Q_ARG(const QString&, name),
Q_ARG(QObject*, object));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name;
#endif
#endif
if (!globalObject().property(name).isValid()) {
if (object) {
@ -452,18 +452,18 @@ void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
#endif
#endif
QMetaObject::invokeMethod(this, "registerFunction",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name;
#endif
#endif
QScriptValue scriptFun = newFunction(functionSignature, numArguments);
globalObject().setProperty(name, scriptFun);
@ -471,18 +471,18 @@ void ScriptEngine::registerFunction(const QString& name, QScriptEngine::Function
void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name;
#endif
#endif
QMetaObject::invokeMethod(this, "registerFunction",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name;
#endif
#endif
QScriptValue object = globalObject().property(parent);
if (object.isValid()) {
@ -492,22 +492,22 @@ void ScriptEngine::registerFunction(const QString& parent, const QString& name,
}
void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
QScriptEngine::FunctionSignature setter, const QString& parent) {
QScriptEngine::FunctionSignature setter, const QString& parent) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
" name:" << name << "parent:" << parent;
#endif
" name:" << name << "parent:" << parent;
#endif
QMetaObject::invokeMethod(this, "registerGetterSetter",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, getter),
Q_ARG(QScriptEngine::FunctionSignature, setter),
Q_ARG(const QString&, parent));
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, getter),
Q_ARG(QScriptEngine::FunctionSignature, setter),
Q_ARG(const QString&, parent));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent;
#endif
#endif
QScriptValue setterFunction = newFunction(setter, 1);
QScriptValue getterFunction = newFunction(getter);
@ -527,19 +527,19 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func
// Unregister the handlers for this eventName and entityID.
void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::removeEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"entityID:" << entityID << " eventName:" << eventName;
#endif
"entityID:" << entityID << " eventName:" << eventName;
#endif
QMetaObject::invokeMethod(this, "removeEventHandler",
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, eventName),
Q_ARG(QScriptValue, handler));
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, eventName),
Q_ARG(QScriptValue, handler));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;
#endif
#endif
if (!_registeredHandlers.contains(entityID)) {
return;
@ -557,20 +557,20 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin
// Register the handler.
void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::addEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"entityID:" << entityID << " eventName:" << eventName;
#endif
"entityID:" << entityID << " eventName:" << eventName;
#endif
QMetaObject::invokeMethod(this, "addEventHandler",
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, eventName),
Q_ARG(QScriptValue, handler));
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, eventName),
Q_ARG(QScriptValue, handler));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::addEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;
#endif
#endif
if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script...
// Connect up ALL the handlers to the global entities object's signals.
@ -579,7 +579,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {
_registeredHandlers.remove(entityID);
});
// Two common cases of event handler, differing only in argument signature.
using SingleEntityHandler = std::function<void(const EntityItemID&)>;
auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler {
@ -587,22 +587,22 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this) });
};
};
using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>;
auto makeMouseHandler = [this](QString eventName) -> MouseHandler {
return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) {
forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) });
};
};
using CollisionHandler = std::function<void(const EntityItemID&, const EntityItemID&, const Collision&)>;
auto makeCollisionHandler = [this](QString eventName) -> CollisionHandler {
return [this, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) {
forwardHandlerCall(idA, eventName, { idA.toScriptValue(this), idB.toScriptValue(this),
collisionToScriptValue(this, collision) });
collisionToScriptValue(this, collision) });
};
};
connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity"));
connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity"));
@ -635,18 +635,18 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
if (QThread::currentThread() != thread()) {
QScriptValue result;
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber;
#endif
#endif
QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, sourceCode),
Q_ARG(const QString&, fileName),
Q_ARG(int, lineNumber));
Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, sourceCode),
Q_ARG(const QString&, fileName),
Q_ARG(int, lineNumber));
return result;
}
// Check syntax
const QScriptProgram program(sourceCode, fileName, lineNumber);
if (!hasCorrectSyntax(program)) {
@ -656,7 +656,7 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
++_evaluatesPending;
const auto result = QScriptEngine::evaluate(program);
--_evaluatesPending;
const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName());
if (_wantSignals) {
emit evaluationFinished(result, hadUncaughtException);
@ -668,7 +668,7 @@ void ScriptEngine::run() {
if (_stoppingAllScripts) {
return; // bail early - avoid setting state in init(), as evaluate() will bail too
}
if (!_isInitialized) {
init();
}
@ -899,7 +899,7 @@ void ScriptEngine::print(const QString& message) {
void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {
if (_stoppingAllScripts) {
qCDebug(scriptengine) << "Script.include() while shutting down is ignored..."
<< "includeFiles:" << includeFiles << "parent script:" << getFilename();
<< "includeFiles:" << includeFiles << "parent script:" << getFilename();
return; // bail early
}
QList<QUrl> urls;
@ -955,7 +955,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
if (_stoppingAllScripts) {
qCDebug(scriptengine) << "Script.include() while shutting down is ignored... "
<< "includeFile:" << includeFile << "parent script:" << getFilename();
<< "includeFile:" << includeFile << "parent script:" << getFilename();
return; // bail early
}
@ -970,7 +970,7 @@ void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
void ScriptEngine::load(const QString& loadFile) {
if (_stoppingAllScripts) {
qCDebug(scriptengine) << "Script.load() while shutting down is ignored... "
<< "loadFile:" << loadFile << "parent script:" << getFilename();
<< "loadFile:" << loadFile << "parent script:" << getFilename();
return; // bail early
}
@ -1014,58 +1014,63 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
// for the download
void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread ["
<< QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"entityID:" << entityID << "entityScript:" << entityScript <<"forceRedownload:" << forceRedownload;
#endif
#endif
QMetaObject::invokeMethod(this, "loadEntityScript",
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, entityScript),
Q_ARG(bool, forceRedownload));
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, entityScript),
Q_ARG(bool, forceRedownload));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::loadEntityScript() called on correct thread [" << thread() << "] "
"entityID:" << entityID << "entityScript:" << entityScript << "forceRedownload:" << forceRedownload;
#endif
#endif
// If we've been called our known entityScripts should not know about us..
assert(!_entityScripts.contains(entityID));
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::loadEntityScript() calling scriptCache->getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::loadEntityScript() calling scriptCache->getScriptContents() on thread ["
<< QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
DependencyManager::get<ScriptCache>()->getScriptContents(entityScript, [=](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread ["
<< QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
this->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success);
}, forceRedownload);
this->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success);
}, forceRedownload);
}
// since all of these operations can be asynch we will always do the actual work in the response handler
// for the download
void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"entityID:" << entityID << "scriptOrURL:" << scriptOrURL << "contents:" << contents << "isURL:" << isURL << "success:" << success;
#endif
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread ["
<< QThread::currentThread() << "], invoking on correct thread [" << thread()
<< "] " "entityID:" << entityID << "scriptOrURL:" << scriptOrURL << "contents:"
<< contents << "isURL:" << isURL << "success:" << success;
#endif
QMetaObject::invokeMethod(this, "entityScriptContentAvailable",
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, scriptOrURL),
Q_ARG(const QString&, contents),
Q_ARG(bool, isURL),
Q_ARG(bool, success));
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, scriptOrURL),
Q_ARG(const QString&, contents),
Q_ARG(bool, isURL),
Q_ARG(bool, success));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::entityScriptContentAvailable() thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
#endif
auto scriptCache = DependencyManager::get<ScriptCache>();
bool isFileUrl = isURL && scriptOrURL.startsWith("file://");
@ -1088,14 +1093,16 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
if (hadUncaughtExceptions(sandbox, program.fileName())) {
return;
}
if (!testConstructor.isFunction()) {
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n"
" NOT CONSTRUCTOR\n"
" SCRIPT:" << scriptOrURL;
" NOT CONSTRUCTOR\n"
" SCRIPT:" << scriptOrURL;
if (!isFileUrl) {
scriptCache->addScriptToBadScriptList(scriptOrURL);
}
return; // done processing script
}
@ -1119,19 +1126,19 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"entityID:" << entityID;
#endif
#endif
QMetaObject::invokeMethod(this, "unloadEntityScript",
Q_ARG(const EntityItemID&, entityID));
Q_ARG(const EntityItemID&, entityID));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::unloadEntityScript() called on correct thread [" << thread() << "] "
"entityID:" << entityID;
#endif
#endif
if (_entityScripts.contains(entityID)) {
callEntityScriptMethod(entityID, "unload");
@ -1193,21 +1200,21 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"entityID:" << entityID << "methodName:" << methodName;
#endif
#endif
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, methodName),
Q_ARG(const QStringList&, params));
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, methodName),
Q_ARG(const QStringList&, params));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
"entityID:" << entityID << "methodName:" << methodName;
#endif
#endif
refreshFileScript(entityID);
if (_entityScripts.contains(entityID)) {
@ -1225,21 +1232,21 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent";
#endif
#endif
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, methodName),
Q_ARG(const MouseEvent&, event));
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, methodName),
Q_ARG(const MouseEvent&, event));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
"entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent";
#endif
#endif
refreshFileScript(entityID);
if (_entityScripts.contains(entityID)) {
@ -1257,23 +1264,23 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";
#endif
#endif
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, methodName),
Q_ARG(const EntityItemID&, otherID),
Q_ARG(const Collision&, collision));
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, methodName),
Q_ARG(const EntityItemID&, otherID),
Q_ARG(const Collision&, collision));
return;
}
#ifdef THREAD_DEBUGGING
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
"entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";
#endif
#endif
refreshFileScript(entityID);
if (_entityScripts.contains(entityID)) {
EntityScriptDetails details = _entityScripts[entityID];
@ -1286,4 +1293,4 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
entityScript.property(methodName).call(entityScript, args);
}
}
}
}

View file

@ -67,7 +67,7 @@ public:
/// run the script in the callers thread, exit when stop() is called.
void run();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can
// properly ensure they are only called on the correct thread
@ -77,14 +77,14 @@ public:
/// registers a global getter/setter
Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
QScriptEngine::FunctionSignature setter, const QString& parent = QString(""));
QScriptEngine::FunctionSignature setter, const QString& parent = QString(""));
/// register a global function
Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1);
/// register a function as a method on a previously registered global object
Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature fun,
int numArguments = -1);
int numArguments = -1);
/// registers a global object by name
Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value);
@ -93,12 +93,12 @@ public:
Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget
/// if the script engine is not already running, this will download the URL and start the process of seting it up
/// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed
/// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed
/// to scripts. we may not need this to be invokable
void loadURL(const QUrl& scriptURL, bool reload);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE - these are intended to be public interfaces available to scripts
// NOTE - these are intended to be public interfaces available to scripts
Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
@ -209,4 +209,4 @@ protected:
static bool _stoppingAllScripts;
};
#endif // hifi_ScriptEngine_h
#endif // hifi_ScriptEngine_h

View file

@ -16,7 +16,7 @@
#include <glm/gtc/quaternion.hpp>
const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 6; // Bullet will start to "lose time" at 10 FPS.
const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 60.0f;
const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 90.0f;
// return incremental rotation (Bullet-style) caused by angularVelocity over timeStep
glm::quat computeBulletRotationStep(const glm::vec3& angularVelocity, float timeStep);