merge andrew/origin/inertia to andrew/local/inertia

This commit is contained in:
Andrew Meadows 2015-01-15 17:55:51 -08:00
commit 3fed38793e
43 changed files with 1016 additions and 962 deletions

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
@ -978,6 +979,7 @@ void OctreeServer::readConfiguration() {
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
QString settingsKey = getMyDomainSettingsKey();
QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject();
_settings = settingsSectionObject; // keep this for later
if (!readOptionString(QString("statusHost"), settingsSectionObject, _statusHost) || _statusHost.isEmpty()) {
_statusHost = getLocalAddress().toString();
@ -1042,20 +1044,8 @@ void OctreeServer::readConfiguration() {
_wantBackup = !noBackup;
qDebug() << "wantBackup=" << _wantBackup;
if (_wantBackup) {
_backupExtensionFormat = OctreePersistThread::DEFAULT_BACKUP_EXTENSION_FORMAT;
readOptionString(QString("backupExtensionFormat"), settingsSectionObject, _backupExtensionFormat);
qDebug() << "backupExtensionFormat=" << _backupExtensionFormat;
_backupInterval = OctreePersistThread::DEFAULT_BACKUP_INTERVAL;
readOptionInt(QString("backupInterval"), settingsSectionObject, _backupInterval);
qDebug() << "backupInterval=" << _backupInterval;
_maxBackupVersions = OctreePersistThread::DEFAULT_MAX_BACKUP_VERSIONS;
readOptionInt(QString("maxBackupVersions"), settingsSectionObject, _maxBackupVersions);
qDebug() << "maxBackupVersions=" << _maxBackupVersions;
}
//qDebug() << "settingsSectionObject:" << settingsSectionObject;
} else {
qDebug("persistFilename= DISABLED");
}
@ -1140,8 +1130,7 @@ void OctreeServer::run() {
// now set up PersistThread
_persistThread = new OctreePersistThread(_tree, _persistFilename, _persistInterval,
_wantBackup, _backupInterval, _backupExtensionFormat,
_maxBackupVersions, _debugTimestampNow);
_wantBackup, _settings, _debugTimestampNow);
if (_persistThread) {
_persistThread->initialize(true);
}

View file

@ -150,6 +150,7 @@ protected:
int _argc;
const char** _argv;
char** _parsedArgV;
QJsonObject _settings;
HTTPManager* _httpManager;
int _statusPort;

View file

@ -305,20 +305,66 @@
"settings": [
{
"name": "persistFilename",
"label": "Persistant Filename",
"help": "the filename for your entities",
"label": "Entities Filename",
"help": "the path to the file entities are stored in. Make sure the path exists.",
"placeholder": "resources/models.svo",
"default": "resources/models.svo",
"advanced": true
},
{
"name": "persistInterval",
"label": "Persist Interval",
"help": "Interval between persist checks in msecs.",
"label": "Save Check Interval",
"help": "Milliseconds between checks for saving the current state of entities.",
"placeholder": "30000",
"default": "30000",
"advanced": true
},
{
"name": "backups",
"type": "table",
"label": "Backup Rules",
"help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.",
"numbered": false,
"default": [
{"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5},
{"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7},
{"Name":"Weekly Rolling","backupInterval":604800,"format":".backup.weekly.%N","maxBackupVersions":4},
{"Name":"Thirty Day Rolling","backupInterval":2592000,"format":".backup.thirtyday.%N","maxBackupVersions":12}
],
"columns": [
{
"name": "Name",
"label": "Name",
"can_set": true,
"placeholder": "Example",
"default": "Example"
},
{
"name": "format",
"label": "Rule Format",
"can_set": true,
"help": "Format used to create the extension for the backup of your persisted entities. Use a format with %N to get rolling. Or use date formatting like %Y-%m-%d.%H:%M:%S.%z",
"placeholder": ".backup.example.%N",
"default": ".backup.example.%N"
},
{
"name": "backupInterval",
"label": "Backup Interval in Seconds",
"help": "Interval between backup checks in seconds.",
"placeholder": 1800,
"default": 1800,
"can_set": true
},
{
"name": "maxBackupVersions",
"label": "Max Rolled Backup Versions",
"help": "If your backup extension format uses 'rolling', how many versions do you want us to keep?",
"placeholder": 5,
"default": 5,
"can_set": true
}
]
},
{
"name": "NoPersist",
"type": "checkbox",
@ -326,30 +372,6 @@
"default": false,
"advanced": true
},
{
"name": "backupExtensionFormat",
"label": "Backup File Extension Format:",
"help": "Format used to create the extension for the backup of your persisted entities. Use a format with %N to get rolling. Or use date formatting like %Y-%m-%d.%H:%M:%S.%z",
"placeholder": ".backup.%N",
"default": ".backup.%N",
"advanced": true
},
{
"name": "backupInterval",
"label": "Backup Interval",
"help": "Interval between backup checks in msecs.",
"placeholder": "1800000",
"default": "1800000",
"advanced": true
},
{
"name": "maxBackupVersions",
"label": "Max Rolled Backup Versions",
"help": "If your backup extension format uses 'rolling', how many versions do you want us to keep?",
"placeholder": "5",
"default": "5",
"advanced": true
},
{
"name": "NoBackup",
"type": "checkbox",

View file

@ -14,7 +14,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../../libraries/globals.js");
Script.include("libraries/globals.js");
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
@ -26,14 +26,19 @@ var yawFromMouse = 0;
var pitchFromMouse = 0;
var isMouseDown = false;
var BULLET_VELOCITY = 5.0;
var BULLET_VELOCITY = 20.0;
var MIN_THROWER_DELAY = 1000;
var MAX_THROWER_DELAY = 1000;
var LEFT_BUTTON_3 = 3;
var RELOAD_INTERVAL = 5;
var KICKBACK_ANGLE = 15;
var elbowKickAngle = 0.0;
var rotationBeforeKickback;
var showScore = false;
// Load some sound to use for loading and firing
var fireSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/GUN-SHOT2.raw");
var loadSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/Gun_Reload_Weapon22.raw");
@ -48,10 +53,11 @@ var audioOptions = {
}
var shotsFired = 0;
var shotTime = new Date();
// initialize our triggers
var activeControllers = 0;
// initialize our controller triggers
var triggerPulled = new Array();
var numberOfTriggers = Controller.getNumberOfTriggers();
for (t = 0; t < numberOfTriggers; t++) {
@ -59,9 +65,11 @@ for (t = 0; t < numberOfTriggers; t++) {
}
var isLaunchButtonPressed = false;
var score = 0;
var bulletID = false;
var targetID = false;
// Create a reticle image in center of screen
var screenSize = Controller.getViewportDimensions();
var reticle = Overlays.addOverlay("image", {
@ -74,6 +82,16 @@ var reticle = Overlays.addOverlay("image", {
alpha: 1
});
var offButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 96,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
if (showScore) {
var text = Overlays.addOverlay("text", {
x: screenSize.x / 2 - 100,
@ -95,18 +113,20 @@ function printVector(string, vector) {
}
function shootBullet(position, velocity) {
var BULLET_SIZE = 0.01;
var BULLET_LIFETIME = 20.0;
var BULLET_SIZE = 0.07;
var BULLET_LIFETIME = 10.0;
var BULLET_GRAVITY = -0.02;
Entities.addEntity(
bulletID = Entities.addEntity(
{ type: "Sphere",
position: position,
dimensions: { x: BULLET_SIZE, y: BULLET_SIZE, z: BULLET_SIZE },
color: { red: 10, green: 10, blue: 10 },
color: { red: 255, green: 0, blue: 0 },
velocity: velocity,
lifetime: BULLET_LIFETIME,
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
damping: 0 });
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
ignoreCollisions: false,
collisionsWillMove: true
});
// Play firing sounds
audioOptions.position = position;
@ -115,36 +135,45 @@ function shootBullet(position, velocity) {
if ((shotsFired % RELOAD_INTERVAL) == 0) {
Audio.playSound(loadSound, audioOptions);
}
// Kickback the arm
rotationBeforeKickback = MyAvatar.getJointRotation("LeftForeArm");
var armRotation = MyAvatar.getJointRotation("LeftForeArm");
armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, KICKBACK_ANGLE));
MyAvatar.setJointData("LeftForeArm", armRotation);
elbowKickAngle = KICKBACK_ANGLE;
}
function shootTarget() {
var TARGET_SIZE = 0.25;
var TARGET_GRAVITY = -0.6;
var TARGET_SIZE = 0.50;
var TARGET_GRAVITY = -0.25;
var TARGET_LIFETIME = 300.0;
var TARGET_UP_VELOCITY = 3.0;
var TARGET_FWD_VELOCITY = 5.0;
var TARGET_UP_VELOCITY = 0.5;
var TARGET_FWD_VELOCITY = 1.0;
var DISTANCE_TO_LAUNCH_FROM = 3.0;
var ANGLE_RANGE_FOR_LAUNCH = 20.0;
var camera = Camera.getPosition();
//printVector("camera", camera);
var targetDirection = Quat.angleAxis(getRandomFloat(-20.0, 20.0), { x:0, y:1, z:0 });
var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { x:0, y:1, z:0 });
targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection);
var forwardVector = Quat.getFront(targetDirection);
//printVector("forwardVector", forwardVector);
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_TO_LAUNCH_FROM));
//printVector("newPosition", newPosition);
var velocity = Vec3.multiply(forwardVector, TARGET_FWD_VELOCITY);
velocity.y += TARGET_UP_VELOCITY;
//printVector("velocity", velocity);
Entities.addEntity(
{ type: "Sphere",
targetID = Entities.addEntity(
{ type: "Box",
position: newPosition,
dimensions: { x: TARGET_SIZE, y: TARGET_SIZE, z: TARGET_SIZE },
color: { red: 0, green: 200, blue: 200 },
//angularVelocity: { x: 1, y: 0, z: 0 },
velocity: velocity,
gravity: { x: 0, y: TARGET_GRAVITY, z: 0 },
lifetime: TARGET_LIFETIME,
damping: 0.0001 });
damping: 0.0001,
collisionsWillMove: true });
// Record start time
shotTime = new Date();
@ -157,24 +186,25 @@ function shootTarget() {
function entityCollisionWithEntity(entity1, entity2, collision) {
score++;
if (showScore) {
Overlays.editOverlay(text, { text: "Score: " + score } );
}
// Sort out which entity is which
// Record shot time
var endTime = new Date();
var msecs = endTime.valueOf() - shotTime.valueOf();
//print("hit, msecs = " + msecs);
//Vec3.print("penetration = ", collision.penetration);
//Vec3.print("contactPoint = ", collision.contactPoint);
Entities.deleteEntity(entity1);
Entities.deleteEntity(entity2);
// play the sound near the camera so the shooter can hear it
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
Audio.playSound(targetHitSound, audioOptions);
if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) &&
((entity2.id == bulletID.id) || (entity2.id == targetID.id))) {
score++;
if (showScore) {
Overlays.editOverlay(text, { text: "Score: " + score } );
}
// We will delete the bullet and target in 1/2 sec, but for now we can see them bounce!
Script.setTimeout(deleteBulletAndTarget, 500);
// Turn the target and the bullet white
Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }});
Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }});
// play the sound near the camera so the shooter can hear it
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
Audio.playSound(targetHitSound, audioOptions);
}
}
function keyPressEvent(event) {
@ -186,12 +216,42 @@ function keyPressEvent(event) {
shootFromMouse();
} else if (event.text == "r") {
playLoadSound();
} else if (event.text == "s") {
// Hit this key to dump a posture from hydra to log
Quat.print("arm = ", MyAvatar.getJointRotation("LeftArm"));
Quat.print("forearm = ", MyAvatar.getJointRotation("LeftForeArm"));
Quat.print("hand = ", MyAvatar.getJointRotation("LeftHand"));
}
}
function playLoadSound() {
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
Audio.playSound(loadSound, audioOptions);
// Raise arm to firing posture
takeFiringPose();
}
function clearPose() {
MyAvatar.clearJointData("LeftForeArm");
MyAvatar.clearJointData("LeftArm");
MyAvatar.clearJointData("LeftHand");
}
function deleteBulletAndTarget() {
Entities.deleteEntity(bulletID);
Entities.deleteEntity(targetID);
bulletID = false;
targetID = false;
}
function takeFiringPose() {
clearPose();
if (Controller.getNumberOfSpatialControls() == 0) {
MyAvatar.setJointData("LeftForeArm", {x: -0.251919, y: -0.0415449, z: 0.499487, w: 0.827843});
MyAvatar.setJointData("LeftArm", { x: 0.470196, y: -0.132559, z: 0.494033, w: 0.719219});
MyAvatar.setJointData("LeftHand", { x: -0.0104815, y: -0.110551, z: -0.352111, w: 0.929333});
}
}
MyAvatar.attach(gunModel, "RightHand", {x:0.02, y: 0.11, z: 0.04}, Quat.fromPitchYawRollDegrees(-0, -160, -79), 0.20);
@ -201,17 +261,49 @@ MyAvatar.attach(gunModel, "RightHand", {x:0.02, y: 0.11, z: 0.04}, Quat.fromPitc
Script.setTimeout(playLoadSound, 2000);
function update(deltaTime) {
if (bulletID && !bulletID.isKnownID) {
print("Trying to identify bullet");
bulletID = Entities.identifyEntity(bulletID);
}
if (targetID && !targetID.isKnownID) {
targetID = Entities.identifyEntity(targetID);
}
// Check for mouseLook movement, update rotation
// rotate body yaw for yaw received from mouse
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } ));
MyAvatar.orientation = newOrientation;
//MyAvatar.orientation = newOrientation;
yawFromMouse = 0;
// apply pitch from mouse
var newPitch = MyAvatar.headPitch + pitchFromMouse;
MyAvatar.headPitch = newPitch;
//MyAvatar.headPitch = newPitch;
pitchFromMouse = 0;
if (activeControllers == 0) {
if (Controller.getNumberOfSpatialControls() > 0) {
activeControllers = Controller.getNumberOfSpatialControls();
clearPose();
}
}
var KICKBACK_DECAY_RATE = 0.125;
if (elbowKickAngle > 0.0) {
if (elbowKickAngle > 0.5) {
var newAngle = elbowKickAngle * KICKBACK_DECAY_RATE;
elbowKickAngle -= newAngle;
var armRotation = MyAvatar.getJointRotation("LeftForeArm");
armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, -newAngle));
MyAvatar.setJointData("LeftForeArm", armRotation);
} else {
MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback);
if (Controller.getNumberOfSpatialControls() > 0) {
clearPose();
}
elbowKickAngle = 0.0;
}
}
// Check hydra controller for launch button press
if (!isLaunchButtonPressed && Controller.isButtonPressed(LEFT_BUTTON_3)) {
isLaunchButtonPressed = true;
@ -222,15 +314,13 @@ function update(deltaTime) {
}
// Check hydra controller for trigger press
// check for trigger press
var numberOfTriggers = Controller.getNumberOfTriggers();
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
var numberOfTriggers = 2;
var controllersPerTrigger = 2;
// this is expected for hydras
if (numberOfTriggers == 2 && controllersPerTrigger == 2) {
for (var t = 0; t < numberOfTriggers; t++) {
for (var t = 0; t < 2; t++) {
var shootABullet = false;
var triggerValue = Controller.getTriggerValue(t);
if (triggerPulled[t]) {
@ -239,14 +329,13 @@ function update(deltaTime) {
triggerPulled[t] = false; // unpulled
}
} else {
// must pull to at least 0.9
if (triggerValue > 0.9) {
// must pull to at least
if (triggerValue > 0.5) {
triggerPulled[t] = true; // pulled
shootABullet = true;
}
}
if (shootABullet) {
var palmController = t * controllersPerTrigger;
var palmPosition = Controller.getSpatialControlPosition(palmController);
@ -263,12 +352,8 @@ function update(deltaTime) {
var position = { x: fingerTipPosition.x + palmToFingerTipVector.x/2,
y: fingerTipPosition.y + palmToFingerTipVector.y/2,
z: fingerTipPosition.z + palmToFingerTipVector.z/2};
var linearVelocity = 25;
var velocity = { x: palmToFingerTipVector.x * linearVelocity,
y: palmToFingerTipVector.y * linearVelocity,
z: palmToFingerTipVector.z * linearVelocity };
var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(palmToFingerTipVector));
shootBullet(position, velocity);
}
@ -280,8 +365,12 @@ function mousePressEvent(event) {
isMouseDown = true;
lastX = event.x;
lastY = event.y;
//audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
//Audio.playSound(loadSound, audioOptions);
if (Overlays.getOverlayAtPoint({ x: event.x, y: event.y }) === offButton) {
Script.stop();
} else {
shootFromMouse();
}
}
function shootFromMouse() {
@ -312,8 +401,10 @@ function mouseMoveEvent(event) {
function scriptEnding() {
Overlays.deleteOverlay(reticle);
Overlays.deleteOverlay(offButton);
Overlays.deleteOverlay(text);
MyAvatar.detachOne(gunModel);
clearPose();
}
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);

View file

@ -662,8 +662,6 @@ function setupModelMenus() {
print("setupModelMenus()");
// adj our menuitems
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...",
shortcutKeyEvent: { text: "`" }, afterItem: "Models" });
if (!Menu.menuItemExists("Edit", "Delete")) {
print("no delete... adding ours");
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete",
@ -674,7 +672,7 @@ function setupModelMenus() {
}
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Model List..." });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Paste Models", isCheckable: true, isChecked: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S",
@ -696,7 +694,6 @@ setupModelMenus(); // do this when first running our script.
function cleanupModelMenus() {
Menu.removeSeparator("Edit", "Models");
Menu.removeMenuItem("Edit", "Edit Properties...");
if (modelMenuAddedDelete) {
// delete our menuitems
Menu.removeMenuItem("Edit", "Delete");
@ -798,22 +795,6 @@ function handeMenuEvent(menuItem) {
MyAvatar.position = selectedModel.properties.position;
}
}
} else if (menuItem == "Edit Properties...") {
// good place to put the properties dialog
editModelID = -1;
if (selectionManager.selections.length == 1) {
print(" Edit Properties.... selectedEntityID="+ selectedEntityID);
editModelID = selectionManager.selections[0];
} else {
print(" Edit Properties.... not holding...");
}
if (editModelID != -1) {
print(" Edit Properties.... about to edit properties...");
entityPropertyDialogBox.openDialog(editModelID);
selectionManager._update();
}
} else if (menuItem == "Paste Models") {
modelImporter.paste();
} else if (menuItem == "Export Models") {
@ -841,9 +822,6 @@ Controller.keyPressEvent.connect(function(event) {
Controller.keyReleaseEvent.connect(function (event) {
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
if (event.text == "`") {
handeMenuEvent("Edit Properties...");
}
if (event.text == "BACKSPACE" || event.text == "DELETE") {
handeMenuEvent("Delete");
} else if (event.text == "TAB") {

View file

@ -0,0 +1,66 @@
//
// downloadInfoExample.js
// examples/example
//
// Created by David Rowe on 5 Jan 2015
// Copyright 2015 High Fidelity, Inc.
//
// Display downloads information the same as in the stats.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var downloadInfo,
downloadInfoOverlay;
function formatInfo(info) {
var string = "Downloads: ",
i;
for (i = 0; i < info.downloading.length; i += 1) {
string += info.downloading[i].toFixed(0) + "% ";
}
string += "(" + info.pending.toFixed(0) + " pending)";
return string;
}
// Get and log the current downloads info ...
downloadInfo = GlobalServices.getDownloadInfo();
print(formatInfo(downloadInfo));
// Display and update the downloads info in an overlay ...
function setUp() {
downloadInfoOverlay = Overlays.addOverlay("text", {
x: 300,
y: 200,
width: 300,
height: 50,
color: { red: 255, green: 255, blue: 255 },
alpha: 1.0,
backgroundColor: { red: 127, green: 127, blue: 127 },
backgroundAlpha: 0.5,
topMargin: 15,
leftMargin: 20,
text: ""
});
}
function updateInfo(info) {
Overlays.editOverlay(downloadInfoOverlay, { text: formatInfo(info) });
}
function tearDown() {
Overlays.deleteOverlay(downloadInfoOverlay);
}
setUp();
GlobalServices.downloadInfoChanged.connect(updateInfo);
GlobalServices.updateDownloadInfo();
Script.scriptEnding.connect(tearDown);

View file

@ -1,418 +0,0 @@
//
// gun.js
// examples
//
// Created by Brad Hefta-Gaub on 12/31/13.
// Modified by Philip on 3/3/14
// Copyright 2013 High Fidelity, Inc.
//
// This is an example script that turns the hydra controllers and mouse into a entity gun.
// It reads the controller, watches for trigger pulls, and launches entities.
// When entities collide with voxels they blow little holes out of the voxels.
//
// 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("libraries/globals.js");
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
var lastX = 0;
var lastY = 0;
var yawFromMouse = 0;
var pitchFromMouse = 0;
var isMouseDown = false;
var BULLET_VELOCITY = 20.0;
var MIN_THROWER_DELAY = 1000;
var MAX_THROWER_DELAY = 1000;
var LEFT_BUTTON_3 = 3;
var RELOAD_INTERVAL = 5;
var KICKBACK_ANGLE = 15;
var elbowKickAngle = 0.0;
var rotationBeforeKickback;
var showScore = false;
// Load some sound to use for loading and firing
var fireSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/GUN-SHOT2.raw");
var loadSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/Gun_Reload_Weapon22.raw");
var impactSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/BulletImpact2.raw");
var targetHitSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/hit.raw");
var targetLaunchSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/shoot.raw");
var gunModel = "http://public.highfidelity.io/models/attachments/HaloGun.fst";
var audioOptions = {
volume: 0.9
}
var shotsFired = 0;
var shotTime = new Date();
var activeControllers = 0;
// initialize our controller triggers
var triggerPulled = new Array();
var numberOfTriggers = Controller.getNumberOfTriggers();
for (t = 0; t < numberOfTriggers; t++) {
triggerPulled[t] = false;
}
var isLaunchButtonPressed = false;
var score = 0;
var bulletID = false;
var targetID = false;
// Create a reticle image in center of screen
var screenSize = Controller.getViewportDimensions();
var reticle = Overlays.addOverlay("image", {
x: screenSize.x / 2 - 16,
y: screenSize.y / 2 - 16,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/reticle.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var offButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
y: 96,
width: 32,
height: 32,
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
if (showScore) {
var text = Overlays.addOverlay("text", {
x: screenSize.x / 2 - 100,
y: screenSize.y / 2 - 50,
width: 150,
height: 50,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 0, blue: 0},
topMargin: 4,
leftMargin: 4,
text: "Score: " + score
});
}
function printVector(string, vector) {
print(string + " " + vector.x + ", " + vector.y + ", " + vector.z);
}
function shootBullet(position, velocity) {
var BULLET_SIZE = 0.07;
var BULLET_LIFETIME = 10.0;
var BULLET_GRAVITY = -0.02;
bulletID = Entities.addEntity(
{ type: "Sphere",
position: position,
dimensions: { x: BULLET_SIZE, y: BULLET_SIZE, z: BULLET_SIZE },
color: { red: 255, green: 0, blue: 0 },
velocity: velocity,
lifetime: BULLET_LIFETIME,
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
ignoreCollisions: false,
collisionsWillMove: true
});
// Play firing sounds
audioOptions.position = position;
Audio.playSound(fireSound, audioOptions);
shotsFired++;
if ((shotsFired % RELOAD_INTERVAL) == 0) {
Audio.playSound(loadSound, audioOptions);
}
// Kickback the arm
rotationBeforeKickback = MyAvatar.getJointRotation("LeftForeArm");
var armRotation = MyAvatar.getJointRotation("LeftForeArm");
armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, KICKBACK_ANGLE));
MyAvatar.setJointData("LeftForeArm", armRotation);
elbowKickAngle = KICKBACK_ANGLE;
}
function shootTarget() {
var TARGET_SIZE = 0.50;
var TARGET_GRAVITY = -0.25;
var TARGET_LIFETIME = 300.0;
var TARGET_UP_VELOCITY = 0.5;
var TARGET_FWD_VELOCITY = 1.0;
var DISTANCE_TO_LAUNCH_FROM = 3.0;
var ANGLE_RANGE_FOR_LAUNCH = 20.0;
var camera = Camera.getPosition();
//printVector("camera", camera);
var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { x:0, y:1, z:0 });
targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection);
var forwardVector = Quat.getFront(targetDirection);
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_TO_LAUNCH_FROM));
var velocity = Vec3.multiply(forwardVector, TARGET_FWD_VELOCITY);
velocity.y += TARGET_UP_VELOCITY;
targetID = Entities.addEntity(
{ type: "Box",
position: newPosition,
dimensions: { x: TARGET_SIZE, y: TARGET_SIZE, z: TARGET_SIZE },
color: { red: 0, green: 200, blue: 200 },
//angularVelocity: { x: 1, y: 0, z: 0 },
velocity: velocity,
gravity: { x: 0, y: TARGET_GRAVITY, z: 0 },
lifetime: TARGET_LIFETIME,
damping: 0.0001,
collisionsWillMove: true });
// Record start time
shotTime = new Date();
// Play target shoot sound
audioOptions.position = newPosition;
Audio.playSound(targetLaunchSound, audioOptions);
}
function entityCollisionWithEntity(entity1, entity2, collision) {
if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) &&
((entity2.id == bulletID.id) || (entity2.id == targetID.id))) {
score++;
if (showScore) {
Overlays.editOverlay(text, { text: "Score: " + score } );
}
// We will delete the bullet and target in 1/2 sec, but for now we can see them bounce!
Script.setTimeout(deleteBulletAndTarget, 500);
// Turn the target and the bullet white
Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }});
Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }});
// play the sound near the camera so the shooter can hear it
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
Audio.playSound(targetHitSound, audioOptions);
}
}
function keyPressEvent(event) {
// if our tools are off, then don't do anything
if (event.text == "t") {
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
Script.setTimeout(shootTarget, time);
} else if (event.text == ".") {
shootFromMouse();
} else if (event.text == "r") {
playLoadSound();
} else if (event.text == "s") {
// Hit this key to dump a posture from hydra to log
Quat.print("arm = ", MyAvatar.getJointRotation("LeftArm"));
Quat.print("forearm = ", MyAvatar.getJointRotation("LeftForeArm"));
Quat.print("hand = ", MyAvatar.getJointRotation("LeftHand"));
}
}
function playLoadSound() {
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
Audio.playSound(loadSound, audioOptions);
// Raise arm to firing posture
takeFiringPose();
}
function clearPose() {
MyAvatar.clearJointData("LeftForeArm");
MyAvatar.clearJointData("LeftArm");
MyAvatar.clearJointData("LeftHand");
}
function deleteBulletAndTarget() {
Entities.deleteEntity(bulletID);
Entities.deleteEntity(targetID);
bulletID = false;
targetID = false;
}
function takeFiringPose() {
clearPose();
if (Controller.getNumberOfSpatialControls() == 0) {
MyAvatar.setJointData("LeftForeArm", {x: -0.251919, y: -0.0415449, z: 0.499487, w: 0.827843});
MyAvatar.setJointData("LeftArm", { x: 0.470196, y: -0.132559, z: 0.494033, w: 0.719219});
MyAvatar.setJointData("LeftHand", { x: -0.0104815, y: -0.110551, z: -0.352111, w: 0.929333});
}
}
MyAvatar.attach(gunModel, "LeftHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20);
// Give a bit of time to load before playing sound
Script.setTimeout(playLoadSound, 2000);
function update(deltaTime) {
if (bulletID && !bulletID.isKnownID) {
print("Trying to identify bullet");
bulletID = Entities.identifyEntity(bulletID);
}
if (targetID && !targetID.isKnownID) {
targetID = Entities.identifyEntity(targetID);
}
// Check for mouseLook movement, update rotation
// rotate body yaw for yaw received from mouse
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } ));
//MyAvatar.orientation = newOrientation;
yawFromMouse = 0;
// apply pitch from mouse
var newPitch = MyAvatar.headPitch + pitchFromMouse;
//MyAvatar.headPitch = newPitch;
pitchFromMouse = 0;
if (activeControllers == 0) {
if (Controller.getNumberOfSpatialControls() > 0) {
activeControllers = Controller.getNumberOfSpatialControls();
clearPose();
}
}
var KICKBACK_DECAY_RATE = 0.125;
if (elbowKickAngle > 0.0) {
if (elbowKickAngle > 0.5) {
var newAngle = elbowKickAngle * KICKBACK_DECAY_RATE;
elbowKickAngle -= newAngle;
var armRotation = MyAvatar.getJointRotation("LeftForeArm");
armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, -newAngle));
MyAvatar.setJointData("LeftForeArm", armRotation);
} else {
MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback);
if (Controller.getNumberOfSpatialControls() > 0) {
clearPose();
}
elbowKickAngle = 0.0;
}
}
// Check hydra controller for launch button press
if (!isLaunchButtonPressed && Controller.isButtonPressed(LEFT_BUTTON_3)) {
isLaunchButtonPressed = true;
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
Script.setTimeout(shootTarget, time);
} else if (isLaunchButtonPressed && !Controller.isButtonPressed(LEFT_BUTTON_3)) {
isLaunchButtonPressed = false;
}
// check for trigger press
var numberOfTriggers = 2;
var controllersPerTrigger = 2;
if (numberOfTriggers == 2 && controllersPerTrigger == 2) {
for (var t = 0; t < 2; t++) {
var shootABullet = false;
var triggerValue = Controller.getTriggerValue(t);
if (triggerPulled[t]) {
// must release to at least 0.1
if (triggerValue < 0.1) {
triggerPulled[t] = false; // unpulled
}
} else {
// must pull to at least
if (triggerValue > 0.5) {
triggerPulled[t] = true; // pulled
shootABullet = true;
}
}
if (shootABullet) {
var palmController = t * controllersPerTrigger;
var palmPosition = Controller.getSpatialControlPosition(palmController);
var fingerTipController = palmController + 1;
var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController);
var palmToFingerTipVector =
{ x: (fingerTipPosition.x - palmPosition.x),
y: (fingerTipPosition.y - palmPosition.y),
z: (fingerTipPosition.z - palmPosition.z) };
// just off the front of the finger tip
var position = { x: fingerTipPosition.x + palmToFingerTipVector.x/2,
y: fingerTipPosition.y + palmToFingerTipVector.y/2,
z: fingerTipPosition.z + palmToFingerTipVector.z/2};
var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(palmToFingerTipVector));
shootBullet(position, velocity);
}
}
}
}
function mousePressEvent(event) {
isMouseDown = true;
lastX = event.x;
lastY = event.y;
if (Overlays.getOverlayAtPoint({ x: event.x, y: event.y }) === offButton) {
Script.stop();
} else {
shootFromMouse();
}
}
function shootFromMouse() {
var DISTANCE_FROM_CAMERA = 2.0;
var camera = Camera.getPosition();
var forwardVector = Quat.getFront(Camera.getOrientation());
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA));
var velocity = Vec3.multiply(forwardVector, BULLET_VELOCITY);
shootBullet(newPosition, velocity);
}
function mouseReleaseEvent(event) {
// position
isMouseDown = false;
}
function mouseMoveEvent(event) {
if (isMouseDown) {
var MOUSE_YAW_SCALE = -0.25;
var MOUSE_PITCH_SCALE = -12.5;
var FIXED_MOUSE_TIMESTEP = 0.016;
yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
lastX = event.x;
lastY = event.y;
}
}
function scriptEnding() {
Overlays.deleteOverlay(reticle);
Overlays.deleteOverlay(offButton);
Overlays.deleteOverlay(text);
MyAvatar.detachOne(gunModel);
clearPose();
}
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(update);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.keyPressEvent.connect(keyPressEvent);

View file

@ -31,8 +31,13 @@
}
function createEmitTextPropertyUpdateFunction(propertyName) {
return function() {
var properties = {};
properties[propertyName] = this.value;
EventBridge.emitWebEvent(
'{ "type":"update", "properties":{"' + propertyName + '":"' + this.value + '"}}'
JSON.stringify({
type: "update",
properties: properties,
})
);
};
}
@ -109,7 +114,7 @@
var elGravityY = document.getElementById("property-grav-y");
var elGravityZ = document.getElementById("property-grav-z");
var elMass = document.getElementById("property-mass");
var elDensity = document.getElementById("property-density");
var elIgnoreForCollisions = document.getElementById("property-ignore-for-collisions");
var elCollisionsWillMove = document.getElementById("property-collisions-will-move");
var elLifetime = document.getElementById("property-lifetime");
@ -146,6 +151,9 @@
var elModelAnimationPlaying = document.getElementById("property-model-animation-playing");
var elModelAnimationFPS = document.getElementById("property-model-animation-fps");
var elModelAnimationFrame = document.getElementById("property-model-animation-frame");
var elModelAnimationSettings = document.getElementById("property-model-animation-settings");
var elModelTextures = document.getElementById("property-model-textures");
var elModelOriginalTextures = document.getElementById("property-model-original-textures");
var elTextSections = document.querySelectorAll(".text-section");
var elTextText = document.getElementById("property-text-text");
@ -211,7 +219,7 @@
elGravityY.value = properties.gravity.y.toFixed(2);
elGravityZ.value = properties.gravity.z.toFixed(2);
elMass.value = properties.mass.toFixed(2);
elDensity.value = properties.density.toFixed(2);
elIgnoreForCollisions.checked = properties.ignoreForCollisions;
elCollisionsWillMove.checked = properties.collisionsWillMove;
elLifetime.value = properties.lifetime;
@ -244,6 +252,10 @@
elModelAnimationURL.value = properties.animationURL;
elModelAnimationPlaying.checked = properties.animationIsPlaying;
elModelAnimationFPS.value = properties.animationFPS;
elModelAnimationFrame.value = properties.animationFrameIndex;
elModelAnimationSettings.value = properties.animationSettings;
elModelTextures.value = properties.textures;
elModelOriginalTextures.value = properties.originalTextures;
}
if (properties.type != "Text") {
@ -344,7 +356,7 @@
elGravityY.addEventListener('change', gravityChangeFunction);
elGravityZ.addEventListener('change', gravityChangeFunction);
elMass.addEventListener('change', createEmitNumberPropertyUpdateFunction('mass'));
elDensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('density'));
elIgnoreForCollisions.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ignoreForCollisions'));
elCollisionsWillMove.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionsWillMove'));
elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime'));
@ -387,6 +399,8 @@
elModelAnimationPlaying.addEventListener('change', createEmitCheckedPropertyUpdateFunction('animationIsPlaying'));
elModelAnimationFPS.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFPS'));
elModelAnimationFrame.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFrameIndex'));
elModelAnimationSettings.addEventListener('change', createEmitTextPropertyUpdateFunction('animationSettings'));
elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures'));
elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text'));
elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight'));
@ -429,7 +443,6 @@
}));
});
var resizing = false;
var startX = 0;
var originalWidth = 0;
@ -603,9 +616,9 @@
</tr>
<tr>
<td class="label">Mass</td>
<td class="label">Density</td>
<td>
<input type='number' id="property-mass"></input>
<input type='number' id="property-density"></input>
</td>
</tr>
@ -679,6 +692,24 @@
<input class="coord" type='number' id="property-model-animation-frame"></input>
</td>
</tr>
<tr class="model-section">
<td class="label">Animation Settings</td>
<td>
<textarea id="property-model-animation-settings" value='asdfasdf'></textarea>
</td>
</tr>
<tr class="model-section">
<td class="label">Textures</td>
<td>
<textarea id="property-model-textures" value='asdfasdf'></textarea>
</td>
</tr>
<tr class="model-section">
<td class="label">Original Textures</td>
<td>
<textarea id="property-model-original-textures" readonly value='asdfasdf'></textarea>
</td>
</tr>
<tr class="text-section">

View file

@ -90,12 +90,17 @@ input[type=button] {
font-size: .9em;
}
input {
textarea, input {
padding: 2px;
border: 1px solid #999;
background-color: #eee;
}
textarea {
width: 100%;
resize: vertical;
}
input.url {
width: 100%;
}

View file

@ -68,7 +68,7 @@ function Tooltip() {
text += "Lifetime: " + properties.lifetime + "\n"
}
text += "Age: " + properties.ageAsText + "\n"
text += "Mass: " + properties.mass + "\n"
text += "Density: " + properties.density + "\n"
text += "Script: " + properties.script + "\n"

View file

@ -105,6 +105,8 @@ CameraManager = function() {
Camera.mode = "independent";
that.updateCamera();
cameraTool.setVisible(true);
}
that.disable = function(ignoreCamera) {
@ -115,6 +117,7 @@ CameraManager = function() {
if (!ignoreCamera) {
Camera.mode = that.previousCameraMode;
}
cameraTool.setVisible(false);
}
that.focus = function(position, dimensions, easeOrientation) {
@ -243,9 +246,9 @@ CameraManager = function() {
}
that.mousePressEvent = function(event) {
// if (cameraTool.mousePressEvent(event)) {
// return true;
// }
if (cameraTool.mousePressEvent(event)) {
return true;
}
if (!that.enabled) return;
@ -291,7 +294,7 @@ CameraManager = function() {
that.updateCamera = function() {
if (!that.enabled || Camera.mode != "independent") {
// cameraTool.update();
cameraTool.update();
return;
}
@ -313,7 +316,7 @@ CameraManager = function() {
Camera.setOrientation(q);
// cameraTool.update();
cameraTool.update();
}
function normalizeDegrees(degrees) {
@ -383,7 +386,7 @@ CameraManager = function() {
Controller.wheelEvent.connect(that.wheelEvent);
// var cameraTool = new CameraTool(that);
var cameraTool = new CameraTool(that);
return that;
}
@ -395,43 +398,21 @@ CameraTool = function(cameraManager) {
var GREEN = { red: 26, green: 193, blue: 105 };
var BLUE = { red: 0, green: 131, blue: 204 };
var ORIENTATION_OVERLAY_SIZE = 20;
var BORDER_WIDTH = 1;
var ORIENTATION_OVERLAY_SIZE = 26;
var ORIENTATION_OVERLAY_HALF_SIZE = ORIENTATION_OVERLAY_SIZE / 2;
var ORIENTATION_OVERLAY_CUBE_SIZE = 8,
var ORIENTATION_OVERLAY_CUBE_SIZE = 10.5,
var ORIENTATION_OVERLAY_OFFSET = {
x: 96,
x: 30,
y: 30,
}
var UI_URL = HIFI_PUBLIC_BUCKET + "images/tools/camera-controls.svg";
var UI_WIDTH = 128;
var UI_HEIGHT = 61;
var UI_WIDTH = 70;
var UI_HEIGHT = 70;
var UI_PADDING = 10;
var UI_BUTTON_WIDTH = 64;
var UI_BUTTON_HEIGHT = 30;
var UI_SUBIMAGE_FIRST_PERSON = {
x: 0,
y: 0,
width: UI_WIDTH,
height: UI_HEIGHT
},
var UI_SUBIMAGE_THIRD_PERSON = {
x: 0,
y: UI_HEIGHT,
width: UI_WIDTH,
height: UI_HEIGHT
},
var UI_SUBIMAGE_OTHER = {
x: 0,
y: UI_HEIGHT * 2,
width: UI_WIDTH,
height: UI_HEIGHT
},
var lastKnownWidth = Window.innerWidth;
var uiPosition = {
@ -439,20 +420,28 @@ CameraTool = function(cameraManager) {
y: UI_PADDING,
};
var ui = Overlays.addOverlay("image", {
imageURL: UI_URL,
var backgroundBorder = Overlays.addOverlay("text", {
x: uiPosition.x - BORDER_WIDTH,
y: uiPosition.y - BORDER_WIDTH,
width: UI_WIDTH + BORDER_WIDTH * 2,
height: UI_HEIGHT + BORDER_WIDTH * 2,
alpha: 0,
text: "",
backgroundColor: { red: 101, green: 101, blue: 101 },
backgroundAlpha: 1.0,
visible: false,
});
var background = Overlays.addOverlay("text", {
x: uiPosition.x,
y: uiPosition.y,
subImage: {
x: 0,
y: 0,
width: UI_WIDTH,
height: UI_HEIGHT
},
width: UI_WIDTH,
height: UI_HEIGHT,
alpha: 1.0,
visible: true
alpha: 0,
text: "",
backgroundColor: { red: 51, green: 51, blue: 51 },
backgroundAlpha: 1.0,
visible: false,
});
var defaultCubeProps = {
@ -470,15 +459,16 @@ CameraTool = function(cameraManager) {
start: { x: 0, y: 0, z: 0 },
end: { x: 0, y: 0, z: 0 },
color: { red: 255, green: 0, blue: 0 },
visible: true,
visible: false,
drawOnHUD: true,
};
var orientationOverlay = OverlayGroup({
position: {
x: uiPosition.x + ORIENTATION_OVERLAY_OFFSET.x,
y: uiPosition.y + ORIENTATION_OVERLAY_OFFSET.y,
}
x: uiPosition.x + UI_WIDTH / 2,
y: uiPosition.y + UI_HEIGHT / 2,
},
visible: false,
});
var OOHS = ORIENTATION_OVERLAY_HALF_SIZE;
@ -512,7 +502,8 @@ CameraTool = function(cameraManager) {
Script.scriptEnding.connect(function() {
orientationOverlay.destroy();
Overlays.deleteOverlay(ui);
Overlays.deleteOverlay(background);
Overlays.deleteOverlay(backgroundBorder);
});
var flip = Quat.fromPitchYawRollDegrees(0, 180, 0);
@ -527,16 +518,20 @@ CameraTool = function(cameraManager) {
x: lastKnownWidth - UI_WIDTH - UI_PADDING,
y: UI_PADDING,
};
Overlays.editOverlay(ui, {
x: uiPosition.x,
y: uiPosition.y
});
orientationOverlay.setProperties({
position: {
x: uiPosition.x + ORIENTATION_OVERLAY_OFFSET.x,
y: uiPosition.y + ORIENTATION_OVERLAY_OFFSET.y,
}
});
Overlays.editOverlay(backgroundBorder, {
x: uiPosition.x - BORDER_WIDTH,
y: uiPosition.y - BORDER_WIDTH,
});
Overlays.editOverlay(background, {
x: uiPosition.x,
y: uiPosition.y,
});
}
}
@ -558,40 +553,16 @@ CameraTool = function(cameraManager) {
targetYaw = event.isLeftButton ? 0 : 180;
cameraManager.setTargetPitchYaw(targetPitch, targetYaw);
return true;
} else if (clickedOverlay == ui) {
var x = event.x - uiPosition.x;
var y = event.y - uiPosition.y;
// Did we hit a button?
if (x < UI_BUTTON_WIDTH) {
if (y < UI_BUTTON_HEIGHT) {
Camera.mode = "first person";
} else {
Camera.mode = "third person";
}
}
return true;
}
};
function updateMode() {
var mode = Camera.mode;
var subImage = UI_SUBIMAGE_OTHER;
if (mode == "first person") {
subImage = UI_SUBIMAGE_FIRST_PERSON;
} else if (mode == "third person") {
subImage = UI_SUBIMAGE_THIRD_PERSON;
}
Overlays.editOverlay(ui, { subImage: subImage });
}
Camera.modeUpdated.connect(updateMode);
updateMode();
that.setVisible = function(visible) {
orientationOverlay.setProperties({ visible: visible });
Overlays.editOverlay(background, { visible: visible });
Overlays.editOverlay(backgroundBorder, { visible: visible });
};
that.setVisible(false);
return that;
};

View file

@ -164,7 +164,7 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Collisions:", type: "header" });
index++;
array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) });
array.push({ label: "Density:", value: properties.density.toFixed(decimals) });
index++;
array.push({ label: "Ignore for Collisions:", type: "checkbox", value: properties.ignoreForCollisions });
index++;
@ -353,7 +353,7 @@ EntityPropertyDialogBox = (function () {
properties.gravity.z = array[index++].value;
index++; // skip header
properties.mass = array[index++].value;
properties.density = array[index++].value;
properties.ignoreForCollisions = array[index++].value;
properties.collisionsWillMove = array[index++].value;

View file

@ -9,7 +9,7 @@ OverlayGroup = function(opts) {
var rootPosition = opts.position || { x: 0, y: 0, z: 0 };
var rootRotation = opts.rotation || Quat.fromPitchYawRollRadians(0, 0, 0);
var visible = true;
var visible = opts.visible == true;
function updateOverlays() {
for (overlayID in overlays) {

View file

@ -10,7 +10,7 @@
//
var buttonImageUrl = "https://public.highfidelity.io/images/tools/sit.svg";
var buttonImageUrl = "https://s3.amazonaws.com/hifi-public/images/tools/sit.svg";
var windowDimensions = Controller.getViewportDimensions();

View file

@ -3437,6 +3437,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("Metavoxels", &_metavoxels);
scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance());
qScriptRegisterMetaType(scriptEngine, DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue);
scriptEngine->registerGlobalObject("AvatarManager", &_avatarManager);

View file

@ -25,6 +25,7 @@
#include <mmdeviceapi.h>
#include <devicetopology.h>
#include <Functiondiscoverykeys_devpkey.h>
#include <VersionHelpers.h>
#endif
#include <AudioConstants.h>
@ -179,12 +180,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
#ifdef WIN32
QString deviceName;
//Check for Windows Vista or higher, IMMDeviceEnumerator doesn't work below that.
OSVERSIONINFO osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
const DWORD VISTA_MAJOR_VERSION = 6;
if (osvi.dwMajorVersion < VISTA_MAJOR_VERSION) {// lower then vista
if (!IsWindowsVistaOrGreater()) { // lower then vista
if (mode == QAudio::AudioInput) {
WAVEINCAPS wic;
// first use WAVE_MAPPER to get the default devices manufacturer ID
@ -223,9 +219,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
pPropertyStore->Release();
pPropertyStore = NULL;
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal);
const DWORD WINDOWS7_MAJOR_VERSION = 6;
const DWORD WINDOWS7_MINOR_VERSION = 1;
if (osvi.dwMajorVersion <= WINDOWS7_MAJOR_VERSION && osvi.dwMinorVersion <= WINDOWS7_MINOR_VERSION) {
if (!IsWindows8OrGreater()) {
// Windows 7 provides only the 31 first characters of the device name.
const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31;
deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN);

View file

@ -312,11 +312,8 @@ Menu::Menu() :
QMenu* nodeBordersMenu = viewMenu->addMenu("Server Borders");
NodeBounds& nodeBounds = appInstance->getNodeBoundsDisplay();
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersVoxelNodes,
Qt::CTRL | Qt::SHIFT | Qt::Key_1, false,
&nodeBounds, SLOT(setShowVoxelNodes(bool)));
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersEntityNodes,
Qt::CTRL | Qt::SHIFT | Qt::Key_2, false,
Qt::CTRL | Qt::SHIFT | Qt::Key_1, false,
&nodeBounds, SLOT(setShowEntityNodes(bool)));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::OffAxisProjection, 0, false);

View file

@ -10,6 +10,8 @@
//
#include "AccountManager.h"
#include "Application.h"
#include "ResourceCache.h"
#include "XmppClient.h"
#include "GlobalServicesScriptingInterface.h"
@ -25,6 +27,10 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() {
const QXmppClient& qxmppClient = XmppClient::getInstance().getXMPPClient();
connect(&qxmppClient, &QXmppClient::messageReceived, this, &GlobalServicesScriptingInterface::messageReceived);
#endif // HAVE_QXMPP
_downloading = false;
connect(Application::getInstance(), &Application::renderingInWorldInterface,
this, &GlobalServicesScriptingInterface::checkDownloadInfo);
}
GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() {
@ -38,14 +44,16 @@ GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() {
const QXmppClient& qxmppClient = XmppClient::getInstance().getXMPPClient();
disconnect(&qxmppClient, &QXmppClient::messageReceived, this, &GlobalServicesScriptingInterface::messageReceived);
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
disconnect(publicChatRoom, &QXmppMucRoom::participantsChanged, this, &GlobalServicesScriptingInterface::participantsChanged);
disconnect(publicChatRoom, &QXmppMucRoom::participantsChanged,
this, &GlobalServicesScriptingInterface::participantsChanged);
#endif // HAVE_QXMPP
}
void GlobalServicesScriptingInterface::onConnected() {
#ifdef HAVE_QXMPP
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
connect(publicChatRoom, &QXmppMucRoom::participantsChanged, this, &GlobalServicesScriptingInterface::participantsChanged, Qt::UniqueConnection);
connect(publicChatRoom, &QXmppMucRoom::participantsChanged,
this, &GlobalServicesScriptingInterface::participantsChanged, Qt::UniqueConnection);
#endif // HAVE_QXMPP
}
@ -110,6 +118,61 @@ void GlobalServicesScriptingInterface::messageReceived(const QXmppMessage& messa
return;
}
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
emit GlobalServicesScriptingInterface::incomingMessage(message.from().right(message.from().count() - 1 - publicChatRoom->jid().count()), message.body());
QString username = message.from().right(message.from().count() - 1 - publicChatRoom->jid().count());
emit GlobalServicesScriptingInterface::incomingMessage(username, message.body());
}
#endif // HAVE_QXMPP
DownloadInfoResult::DownloadInfoResult() :
downloading(QList<float>()),
pending(0.0f)
{
}
QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result) {
QScriptValue object = engine->newObject();
QScriptValue array = engine->newArray(result.downloading.count());
for (int i = 0; i < result.downloading.count(); i += 1) {
array.setProperty(i, result.downloading[i]);
}
object.setProperty("downloading", array);
object.setProperty("pending", result.pending);
return object;
}
void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoResult& result) {
QList<QVariant> downloading = object.property("downloading").toVariant().toList();
result.downloading.clear();
for (int i = 0; i < downloading.count(); i += 1) {
result.downloading.append(downloading[i].toFloat());
}
result.pending = object.property("pending").toVariant().toFloat();
}
DownloadInfoResult GlobalServicesScriptingInterface::getDownloadInfo() {
DownloadInfoResult result;
foreach(Resource* resource, ResourceCache::getLoadingRequests()) {
result.downloading.append(resource->getProgress() * 100.0f);
}
result.pending = ResourceCache::getPendingRequestCount();
return result;
}
void GlobalServicesScriptingInterface::checkDownloadInfo() {
DownloadInfoResult downloadInfo = getDownloadInfo();
bool downloading = downloadInfo.downloading.count() > 0 || downloadInfo.pending > 0;
// Emit signal if downloading or have just finished.
if (downloading || _downloading) {
_downloading = downloading;
emit downloadInfoChanged(downloadInfo);
}
}
void GlobalServicesScriptingInterface::updateDownloadInfo() {
emit downloadInfoChanged(getDownloadInfo());
}

View file

@ -26,6 +26,19 @@
#endif // HAVE_QXMPP
class DownloadInfoResult {
public:
DownloadInfoResult();
QList<float> downloading; // List of percentages
float pending;
};
Q_DECLARE_METATYPE(DownloadInfoResult)
QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result);
void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoResult& result);
class GlobalServicesScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(bool isConnected READ isConnected)
@ -42,6 +55,8 @@ public:
public slots:
QScriptValue chat(const QString& message);
DownloadInfoResult getDownloadInfo();
void updateDownloadInfo();
private slots:
void loggedOut();
@ -50,6 +65,7 @@ private slots:
#ifdef HAVE_QXMPP
void messageReceived(const QXmppMessage& message);
#endif // HAVE_QXMPP
void checkDownloadInfo();
signals:
void connected();
@ -57,6 +73,10 @@ signals:
void incomingMessage(const QString& username, const QString& message);
void onlineUsersChanged(const QStringList& usernames);
void myUsernameChanged(const QString& username);
void downloadInfoChanged(DownloadInfoResult info);
private:
bool _downloading;
};
#endif // hifi_GlobalServicesScriptingInterface_h

View file

@ -134,8 +134,8 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
glm::vec3 axis = glm::normalize(penetration);
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
float massA = entityA->getMass();
float massB = entityB->getMass();
float massA = entityA->computeMass();
float massB = entityB->computeMass();
float totalMass = massA + massB;
float massRatioA = (2.0f * massB / totalMass);
float massRatioB = (2.0f * massA / totalMass);

View file

@ -37,10 +37,10 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_position = ENTITY_ITEM_ZERO_VEC3;
_dimensions = ENTITY_ITEM_DEFAULT_DIMENSIONS;
_density = ENTITY_ITEM_DEFAULT_DENSITY;
_rotation = ENTITY_ITEM_DEFAULT_ROTATION;
_glowLevel = ENTITY_ITEM_DEFAULT_GLOW_LEVEL;
_localRenderAlpha = ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA;
_mass = ENTITY_ITEM_DEFAULT_MASS;
_velocity = ENTITY_ITEM_DEFAULT_VELOCITY;
_gravity = ENTITY_ITEM_DEFAULT_GRAVITY;
_damping = ENTITY_ITEM_DEFAULT_DAMPING;
@ -103,7 +103,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_POSITION;
requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete
requestedProperties += PROP_ROTATION;
requestedProperties += PROP_MASS;
requestedProperties += PROP_DENSITY;
requestedProperties += PROP_VELOCITY;
requestedProperties += PROP_GRAVITY;
requestedProperties += PROP_DAMPING;
@ -219,7 +219,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
}
APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, getRotation());
APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, getMass());
APPEND_ENTITY_PROPERTY(PROP_DENSITY, appendValue, getDensity());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity());
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, appendValue, getGravity());
APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, getDamping());
@ -495,7 +495,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
}
READ_ENTITY_PROPERTY_QUAT_SETTER(PROP_ROTATION, updateRotation);
READ_ENTITY_PROPERTY_SETTER(PROP_MASS, float, updateMass);
READ_ENTITY_PROPERTY_SETTER(PROP_DENSITY, float, updateDensity);
READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocity);
READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity);
READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping);
@ -555,6 +555,44 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s
}
}
float EntityItem::computeMass() const {
// NOTE: we group the operations here in and attempt to reduce floating point error.
return ((_density * (_volumeMultiplier * _dimensions.x)) * _dimensions.y) * _dimensions.z;
}
void EntityItem::setDensity(float density) {
_density = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
}
void EntityItem::updateDensity(float density) {
const float MIN_DENSITY_CHANGE_FACTOR = 0.001f; // 0.1 percent
float newDensity = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
if (fabsf(_density - newDensity) / _density > MIN_DENSITY_CHANGE_FACTOR) {
_density = newDensity;
_dirtyFlags |= EntityItem::DIRTY_MASS;
}
}
void EntityItem::setMass(float mass) {
// Setting the mass actually changes the _density (at fixed volume), however
// we must protect the density range to help maintain stability of physics simulation
// therefore this method might not accept the mass that is supplied.
// NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due
// to the TREE_SCALE transformation) with the first dimension component (typically a very small number)
// in an attempt to reduce floating point error of the final result.
float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z;
// compute new density
const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3
if (volume < 1.0e-6f) {
// avoid divide by zero
_density = glm::min(mass / MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY);
} else {
_density = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
}
}
const float ENTITY_ITEM_EPSILON_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE;
// TODO: we probably want to change this to make "down" be the direction of the entity's gravity vector
@ -819,7 +857,7 @@ EntityItemProperties EntityItem::getProperties() const {
COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPositionInMeters);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensionsInMeters); // NOTE: radius is obsolete
COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(mass, getMass);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(density, getDensity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocityInMeters);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravityInMeters);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping);
@ -847,7 +885,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePositionInMeters); // this will call recalculate collision shape if needed
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, updateDimensionsInMeters); // NOTE: radius is obsolete
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(mass, updateMass);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, updateDensity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocityInMeters);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravityInMeters);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, updateDamping);
@ -1047,7 +1085,6 @@ void EntityItem::recalculateCollisionShape() {
const float MIN_POSITION_DELTA = 0.0001f;
const float MIN_ALIGNMENT_DOT = 0.9999f;
const float MIN_MASS_DELTA = 0.001f;
const float MIN_VELOCITY_DELTA = 0.01f;
const float MIN_DAMPING_DELTA = 0.001f;
const float MIN_GRAVITY_DELTA = 0.001f;
@ -1095,9 +1132,29 @@ void EntityItem::updateRotation(const glm::quat& rotation) {
}
}
void EntityItem::updateMass(float value) {
if (fabsf(_mass - value) > MIN_MASS_DELTA) {
_mass = value;
void EntityItem::updateMass(float mass) {
// Setting the mass actually changes the _density (at fixed volume), however
// we must protect the density range to help maintain stability of physics simulation
// therefore this method might not accept the mass that is supplied.
// NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due
// to the TREE_SCALE transformation) with the first dimension component (typically a very small number)
// in an attempt to reduce floating point error of the final result.
float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z;
// compute new density
float newDensity = _density;
const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3
if (volume < 1.0e-6f) {
// avoid divide by zero
newDensity = glm::min(mass / MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY);
} else {
newDensity = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
}
const float MIN_DENSITY_CHANGE_FACTOR = 0.001f; // 0.1 percent
if (fabsf(_density - newDensity) / _density > MIN_DENSITY_CHANGE_FACTOR) {
_density = newDensity;
_dirtyFlags |= EntityItem::DIRTY_MASS;
}
}

View file

@ -173,8 +173,11 @@ public:
float getLocalRenderAlpha() const { return _localRenderAlpha; }
void setLocalRenderAlpha(float localRenderAlpha) { _localRenderAlpha = localRenderAlpha; }
float getMass() const { return _mass; }
void setMass(float value) { _mass = value; }
void setDensity(float density);
float computeMass() const;
void setMass(float mass);
float getDensity() const { return _density; }
const glm::vec3& getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second
glm::vec3 getVelocityInMeters() const { return _velocity * (float) TREE_SCALE; } /// get velocity in meters
@ -262,6 +265,7 @@ public:
void updateDimensions(const glm::vec3& value);
void updateDimensionsInMeters(const glm::vec3& value);
void updateRotation(const glm::quat& rotation);
void updateDensity(float value);
void updateMass(float value);
void updateVelocity(const glm::vec3& value);
void updateVelocityInMeters(const glm::vec3& value);
@ -305,7 +309,12 @@ protected:
glm::quat _rotation;
float _glowLevel;
float _localRenderAlpha;
float _mass;
float _density = ENTITY_ITEM_DEFAULT_DENSITY; // kg/m^3
// NOTE: _volumeMultiplier is used to compute volume:
// volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z = m^3
// DANGER: due to the size of TREE_SCALE the _volumeMultiplier is always a large number, and therefore
// will tend to introduce floating point error. We must keep this in mind when using it.
float _volumeMultiplier = (float)TREE_SCALE * (float)TREE_SCALE * (float)TREE_SCALE;
glm::vec3 _velocity;
glm::vec3 _gravity;
float _damping;

View file

@ -30,7 +30,7 @@ EntityItemProperties::EntityItemProperties() :
CONSTRUCT_PROPERTY(position, 0),
CONSTRUCT_PROPERTY(dimensions, ENTITY_ITEM_DEFAULT_DIMENSIONS),
CONSTRUCT_PROPERTY(rotation, ENTITY_ITEM_DEFAULT_ROTATION),
CONSTRUCT_PROPERTY(mass, ENTITY_ITEM_DEFAULT_MASS),
CONSTRUCT_PROPERTY(density, ENTITY_ITEM_DEFAULT_DENSITY),
CONSTRUCT_PROPERTY(velocity, ENTITY_ITEM_DEFAULT_VELOCITY),
CONSTRUCT_PROPERTY(gravity, ENTITY_ITEM_DEFAULT_GRAVITY),
CONSTRUCT_PROPERTY(damping, ENTITY_ITEM_DEFAULT_DAMPING),
@ -175,7 +175,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions);
CHECK_PROPERTY_CHANGE(PROP_POSITION, position);
CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation);
CHECK_PROPERTY_CHANGE(PROP_MASS, mass);
CHECK_PROPERTY_CHANGE(PROP_DENSITY, density);
CHECK_PROPERTY_CHANGE(PROP_VELOCITY, velocity);
CHECK_PROPERTY_CHANGE(PROP_GRAVITY, gravity);
CHECK_PROPERTY_CHANGE(PROP_DAMPING, damping);
@ -232,7 +232,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(velocity);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(gravity);
COPY_PROPERTY_TO_QSCRIPTVALUE(damping);
COPY_PROPERTY_TO_QSCRIPTVALUE(mass);
COPY_PROPERTY_TO_QSCRIPTVALUE(density);
COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(age, getAge()); // gettable, but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable
@ -310,7 +310,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(position, setPosition);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(dimensions, setDimensions);
COPY_PROPERTY_FROM_QSCRIPTVALUE_QUAT(rotation, setRotation);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(mass, setMass);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(density, setDensity);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(velocity, setVelocity);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(gravity, setGravity);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(damping, setDamping);
@ -479,7 +479,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, properties.getPosition());
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, appendValue, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete
APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, properties.getRotation());
APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, properties.getMass());
APPEND_ENTITY_PROPERTY(PROP_DENSITY, appendValue, properties.getDensity());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, properties.getVelocity());
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, appendValue, properties.getGravity());
APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, properties.getDamping());
@ -700,7 +700,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete
READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(PROP_ROTATION, setRotation);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MASS, float, setMass);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, glm::vec3, setGravity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping);
@ -781,7 +781,7 @@ void EntityItemProperties::markAllChanged() {
_positionChanged = true;
_dimensionsChanged = true;
_rotationChanged = true;
_massChanged = true;
_densityChanged = true;
_velocityChanged = true;
_gravityChanged = true;
_dampingChanged = true;

View file

@ -42,7 +42,7 @@ enum EntityPropertyList {
PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams
PROP_DIMENSIONS = PROP_RADIUS,
PROP_ROTATION,
PROP_MASS,
PROP_DENSITY,
PROP_VELOCITY,
PROP_GRAVITY,
PROP_DAMPING,
@ -145,7 +145,7 @@ public:
DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3);
DEFINE_PROPERTY_REF(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3);
DEFINE_PROPERTY_REF(PROP_ROTATION, Rotation, rotation, glm::quat);
DEFINE_PROPERTY(PROP_MASS, Mass, mass, float);
DEFINE_PROPERTY(PROP_DENSITY, Density, density, float);
DEFINE_PROPERTY_REF(PROP_VELOCITY, Velocity, velocity, glm::vec3);
DEFINE_PROPERTY_REF(PROP_GRAVITY, Gravity, gravity, glm::vec3);
DEFINE_PROPERTY(PROP_DAMPING, Damping, damping, float);

View file

@ -36,8 +36,14 @@ const float ENTITY_ITEM_IMMORTAL_LIFETIME = -1.0f; /// special lifetime which me
const float ENTITY_ITEM_DEFAULT_LIFETIME = ENTITY_ITEM_IMMORTAL_LIFETIME;
const glm::quat ENTITY_ITEM_DEFAULT_ROTATION;
const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(0.1f) / (float)TREE_SCALE;
const float ENTITY_ITEM_DEFAULT_MASS = 1.0f;
const float ENTITY_ITEM_DEFAULT_WIDTH = 0.1f;
const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(ENTITY_ITEM_DEFAULT_WIDTH) / (float)TREE_SCALE;
const float ENTITY_ITEM_DEFAULT_VOLUME = ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH;
const float ENTITY_ITEM_MAX_DENSITY = 10000.0f; // kg/m^3 density of silver
const float ENTITY_ITEM_MIN_DENSITY = 100.0f; // kg/m^3 density of balsa wood
const float ENTITY_ITEM_DEFAULT_DENSITY = 1000.0f; // density of water
const float ENTITY_ITEM_DEFAULT_MASS = ENTITY_ITEM_DEFAULT_DENSITY * ENTITY_ITEM_DEFAULT_VOLUME;
const glm::vec3 ENTITY_ITEM_DEFAULT_VELOCITY = ENTITY_ITEM_ZERO_VEC3;
const glm::vec3 ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY = ENTITY_ITEM_ZERO_VEC3;

View file

@ -32,6 +32,10 @@ SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID, const Entit
{
_type = EntityTypes::Sphere;
setProperties(properties);
// NOTE: _volumeMultiplier is used to compute volume:
// volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z
// The formula below looks funny because _dimension.xyz = diameter rather than radius.
_volumeMultiplier *= PI / 6.0f;
}
EntityItemProperties SphereEntityItem::getProperties() const {

View file

@ -753,6 +753,7 @@ public:
float shininess;
float opacity;
QString id;
model::MaterialPointer _material;
};
class Cluster {
@ -1715,6 +1716,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
#endif
}
material.id = getID(object.properties);
material._material = model::MaterialPointer(new model::Material());
material._material->setEmissive(material.emissive);
material._material->setDiffuse(material.diffuse);
material._material->setSpecular(material.specular);
material._material->setShininess(material.shininess);
material._material->setOpacity(material.opacity);
materials.insert(material.id, material);
} else if (object.name == "NodeAttribute") {
@ -2138,6 +2147,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
if (extracted.partMaterialTextures.at(j).first == materialIndex) {
FBXMeshPart& part = extracted.mesh.parts[j];
part._material = material._material;
part.diffuseColor = material.diffuse;
part.specularColor = material.specular;
part.emissiveColor = material.emissive;

View file

@ -9,10 +9,17 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <cstdio>
#include <fstream>
#include <time.h>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QJsonArray>
#include <QJsonObject>
#include <PerfStat.h>
#include <SharedUtil.h>
@ -20,30 +27,71 @@
#include "OctreePersistThread.h"
const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds
const int OctreePersistThread::DEFAULT_BACKUP_INTERVAL = 1000 * 60 * 30; // every 30 minutes
const QString OctreePersistThread::DEFAULT_BACKUP_EXTENSION_FORMAT(".backup.%N");
const int OctreePersistThread::DEFAULT_MAX_BACKUP_VERSIONS = 5;
OctreePersistThread::OctreePersistThread(Octree* tree, const QString& filename, int persistInterval,
bool wantBackup, int backupInterval, const QString& backupExtensionFormat,
int maxBackupVersions, bool debugTimestampNow) :
bool wantBackup, const QJsonObject& settings, bool debugTimestampNow) :
_tree(tree),
_filename(filename),
_backupExtensionFormat(backupExtensionFormat),
_maxBackupVersions(maxBackupVersions),
_persistInterval(persistInterval),
_backupInterval(backupInterval),
_initialLoadComplete(false),
_loadTimeUSecs(0),
_lastCheck(0),
_lastBackup(0),
_wantBackup(wantBackup),
_debugTimestampNow(debugTimestampNow),
_lastTimeDebug(0)
{
parseSettings(settings);
}
void OctreePersistThread::parseSettings(const QJsonObject& settings) {
if (settings["backups"].isArray()) {
const QJsonArray& backupRules = settings["backups"].toArray();
qDebug() << "BACKUP RULES:";
foreach (const QJsonValue& value, backupRules) {
QJsonObject obj = value.toObject();
qDebug() << " Name:" << obj["Name"].toString();
qDebug() << " format:" << obj["format"].toString();
qDebug() << " interval:" << obj["backupInterval"].toInt();
qDebug() << " count:" << obj["maxBackupVersions"].toInt();
BackupRule newRule = { obj["Name"].toString(), obj["backupInterval"].toInt(),
obj["format"].toString(), obj["maxBackupVersions"].toInt(), 0};
newRule.lastBackup = getMostRecentBackupTimeInUsecs(obj["format"].toString());
if (newRule.lastBackup > 0) {
quint64 now = usecTimestampNow();
quint64 sinceLastBackup = now - newRule.lastBackup;
qDebug() << " lastBackup:" << qPrintable(formatUsecTime(sinceLastBackup)) << "ago";
} else {
qDebug() << " lastBackup: NEVER";
}
_backupRules << newRule;
}
} else {
qDebug() << "BACKUP RULES: NONE";
}
}
quint64 OctreePersistThread::getMostRecentBackupTimeInUsecs(const QString& format) {
quint64 mostRecentBackupInUsecs = 0;
QString mostRecentBackupFileName;
QDateTime mostRecentBackupTime;
bool recentBackup = getMostRecentBackup(format, mostRecentBackupFileName, mostRecentBackupTime);
if (recentBackup) {
mostRecentBackupInUsecs = mostRecentBackupTime.toMSecsSinceEpoch() * USECS_PER_MSEC;
}
return mostRecentBackupInUsecs;
}
bool OctreePersistThread::process() {
if (!_initialLoadComplete) {
@ -55,6 +103,25 @@ bool OctreePersistThread::process() {
_tree->lockForWrite();
{
PerformanceWarning warn(true, "Loading Octree File", true);
// First check to make sure "lock" file doesn't exist. If it does exist, then
// our last save crashed during the save, and we want to load our most recent backup.
QString lockFileName = _filename + ".lock";
std::ifstream lockFile(qPrintable(lockFileName), std::ios::in|std::ios::binary|std::ios::ate);
if(lockFile.is_open()) {
qDebug() << "WARNING: Octree lock file detected at startup:" << lockFileName
<< "-- Attempting to restore from previous backup file.";
// This is where we should attempt to find the most recent backup and restore from
// that file as our persist file.
restoreFromMostRecentBackup();
lockFile.close();
qDebug() << "Loading Octree... lock file closed:" << lockFileName;
remove(qPrintable(lockFileName));
qDebug() << "Loading Octree... lock file removed:" << lockFileName;
}
persistantFileRead = _tree->readFromSVOFile(_filename.toLocal8Bit().constData());
_tree->pruneTree();
}
@ -85,7 +152,13 @@ bool OctreePersistThread::process() {
}
_initialLoadComplete = true;
_lastBackup = _lastCheck = usecTimestampNow(); // we just loaded, no need to save again
// Since we just loaded the persistent file, we can consider ourselves as having "just checked" for persistance.
_lastCheck = usecTimestampNow(); // we just loaded, no need to save again
// This last persist time is not really used until the file is actually persisted. It is only
// used in formatting the backup filename in cases of non-rolling backup names. However, we don't
// want an uninitialized value for this, so we set it to the current time (startup of the server)
time(&_lastPersistTime);
emit loadCompleted();
@ -142,82 +215,180 @@ void OctreePersistThread::persist() {
backup(); // handle backup if requested
qDebug() << "saving Octree to file " << _filename << "...";
_tree->writeToSVOFile(qPrintable(_filename));
time(&_lastPersistTime);
_tree->clearDirtyBit(); // tree is clean after saving
qDebug() << "DONE saving Octree to file...";
// create our "lock" file to indicate we're saving.
QString lockFileName = _filename + ".lock";
std::ofstream lockFile(qPrintable(lockFileName), std::ios::out|std::ios::binary);
if(lockFile.is_open()) {
qDebug() << "saving Octree lock file created at:" << lockFileName;
qDebug() << "saving Octree to file " << _filename << "...";
_tree->writeToSVOFile(qPrintable(_filename));
time(&_lastPersistTime);
_tree->clearDirtyBit(); // tree is clean after saving
qDebug() << "DONE saving Octree to file...";
lockFile.close();
qDebug() << "saving Octree lock file closed:" << lockFileName;
remove(qPrintable(lockFileName));
qDebug() << "saving Octree lock file removed:" << lockFileName;
}
}
}
void OctreePersistThread::rollOldBackupVersions() {
if (!_backupExtensionFormat.contains("%N")) {
return; // this backup extension format doesn't support rolling
void OctreePersistThread::restoreFromMostRecentBackup() {
qDebug() << "Restoring from most recent backup...";
QString mostRecentBackupFileName;
QDateTime mostRecentBackupTime;
bool recentBackup = getMostRecentBackup(QString(""), mostRecentBackupFileName, mostRecentBackupTime);
// If we found a backup file, restore from that file.
if (recentBackup) {
qDebug() << "BEST backup file:" << mostRecentBackupFileName << " last modified:" << mostRecentBackupTime.toString();
qDebug() << "Removing old file:" << _filename;
remove(qPrintable(_filename));
qDebug() << "Restoring backup file " << mostRecentBackupFileName << "...";
bool result = QFile::copy(mostRecentBackupFileName, _filename);
if (result) {
qDebug() << "DONE restoring backup file " << mostRecentBackupFileName << "to" << _filename << "...";
} else {
qDebug() << "ERROR while restoring backup file " << mostRecentBackupFileName << "to" << _filename << "...";
}
} else {
qDebug() << "NO BEST backup file found.";
}
}
qDebug() << "Rolling old backup versions...";
for(int n = _maxBackupVersions - 1; n > 0; n--) {
QString backupExtensionN = _backupExtensionFormat;
QString backupExtensionNplusOne = _backupExtensionFormat;
backupExtensionN.replace(QString("%N"), QString::number(n));
backupExtensionNplusOne.replace(QString("%N"), QString::number(n+1));
QString backupFilenameN = _filename + backupExtensionN;
QString backupFilenameNplusOne = _filename + backupExtensionNplusOne;
bool OctreePersistThread::getMostRecentBackup(const QString& format,
QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime) {
QFile backupFileN(backupFilenameN);
// Based on our backup file name, determine the path and file name pattern for backup files
QFileInfo persistFileInfo(_filename);
QString path = persistFileInfo.path();
QString fileNamePart = persistFileInfo.fileName();
if (backupFileN.exists()) {
qDebug() << "rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "...";
int result = rename(qPrintable(backupFilenameN), qPrintable(backupFilenameNplusOne));
if (result == 0) {
qDebug() << "DONE rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "...";
} else {
qDebug() << "ERROR in rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "...";
}
QStringList filters;
if (format.isEmpty()) {
// Create a file filter that will find all backup files of this extension format
foreach(const BackupRule& rule, _backupRules) {
QString backupExtension = rule.extensionFormat;
backupExtension.replace(QRegExp("%."), "*");
QString backupFileNamePart = fileNamePart + backupExtension;
filters << backupFileNamePart;
}
} else {
QString backupExtension = format;
backupExtension.replace(QRegExp("%."), "*");
QString backupFileNamePart = fileNamePart + backupExtension;
filters << backupFileNamePart;
}
bool bestBackupFound = false;
QString bestBackupFile;
QDateTime bestBackupFileTime;
// Iterate over all of the backup files in the persist location
QDirIterator dirIterator(path, filters, QDir::Files|QDir::NoSymLinks, QDirIterator::NoIteratorFlags);
while(dirIterator.hasNext()) {
dirIterator.next();
QDateTime lastModified = dirIterator.fileInfo().lastModified();
// Based on last modified date, track the most recently modified file as the best backup
if (lastModified > bestBackupFileTime) {
bestBackupFound = true;
bestBackupFile = dirIterator.filePath();
bestBackupFileTime = lastModified;
}
}
qDebug() << "Done rolling old backup versions...";
// If we found a backup then return the results
if (bestBackupFound) {
mostRecentBackupFileName = bestBackupFile;
mostRecentBackupTime = bestBackupFileTime;
}
return bestBackupFound;
}
void OctreePersistThread::rollOldBackupVersions(const BackupRule& rule) {
if (rule.extensionFormat.contains("%N")) {
qDebug() << "Rolling old backup versions for rule" << rule.name << "...";
for(int n = rule.maxBackupVersions - 1; n > 0; n--) {
QString backupExtensionN = rule.extensionFormat;
QString backupExtensionNplusOne = rule.extensionFormat;
backupExtensionN.replace(QString("%N"), QString::number(n));
backupExtensionNplusOne.replace(QString("%N"), QString::number(n+1));
QString backupFilenameN = _filename + backupExtensionN;
QString backupFilenameNplusOne = _filename + backupExtensionNplusOne;
QFile backupFileN(backupFilenameN);
if (backupFileN.exists()) {
qDebug() << "rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "...";
int result = rename(qPrintable(backupFilenameN), qPrintable(backupFilenameNplusOne));
if (result == 0) {
qDebug() << "DONE rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "...";
} else {
qDebug() << "ERROR in rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "...";
}
}
}
qDebug() << "Done rolling old backup versions...";
}
}
void OctreePersistThread::backup() {
if (_wantBackup) {
quint64 now = usecTimestampNow();
quint64 sinceLastBackup = now - _lastBackup;
quint64 MSECS_TO_USECS = 1000;
quint64 intervalToBackup = _backupInterval * MSECS_TO_USECS;
if (sinceLastBackup > intervalToBackup) {
qDebug() << "Time since last backup [" << sinceLastBackup << "] exceeds backup interval ["
<< intervalToBackup << "] doing backup now...";
for(int i = 0; i < _backupRules.count(); i++) {
BackupRule& rule = _backupRules[i];
struct tm* localTime = localtime(&_lastPersistTime);
quint64 sinceLastBackup = now - rule.lastBackup;
QString backupFileName;
quint64 SECS_TO_USECS = 1000 * 1000;
quint64 intervalToBackup = rule.interval * SECS_TO_USECS;
if (sinceLastBackup > intervalToBackup) {
qDebug() << "Time since last backup [" << sinceLastBackup << "] for rule [" << rule.name
<< "] exceeds backup interval [" << intervalToBackup << "] doing backup now...";
struct tm* localTime = localtime(&_lastPersistTime);
QString backupFileName;
// check to see if they asked for version rolling format
if (_backupExtensionFormat.contains("%N")) {
rollOldBackupVersions(); // rename all the old backup files accordingly
QString backupExtension = _backupExtensionFormat;
backupExtension.replace(QString("%N"), QString("1"));
backupFileName = _filename + backupExtension;
} else {
char backupExtension[256];
strftime(backupExtension, sizeof(backupExtension), qPrintable(_backupExtensionFormat), localTime);
backupFileName = _filename + backupExtension;
}
// check to see if they asked for version rolling format
if (rule.extensionFormat.contains("%N")) {
rollOldBackupVersions(rule); // rename all the old backup files accordingly
QString backupExtension = rule.extensionFormat;
backupExtension.replace(QString("%N"), QString("1"));
backupFileName = _filename + backupExtension;
} else {
char backupExtension[256];
strftime(backupExtension, sizeof(backupExtension), qPrintable(rule.extensionFormat), localTime);
backupFileName = _filename + backupExtension;
}
qDebug() << "backing up persist file " << _filename << "to" << backupFileName << "...";
int result = rename(qPrintable(_filename), qPrintable(backupFileName));
if (result == 0) {
qDebug() << "DONE backing up persist file...";
} else {
qDebug() << "ERROR in backing up persist file...";
}
qDebug() << "backing up persist file " << _filename << "to" << backupFileName << "...";
bool result = QFile::copy(_filename, backupFileName);
if (result) {
qDebug() << "DONE backing up persist file...";
} else {
qDebug() << "ERROR in backing up persist file...";
}
_lastBackup = now;
rule.lastBackup = now;
}
}
}
}

View file

@ -22,15 +22,19 @@
class OctreePersistThread : public GenericThread {
Q_OBJECT
public:
class BackupRule {
public:
QString name;
int interval;
QString extensionFormat;
int maxBackupVersions;
quint64 lastBackup;
};
static const int DEFAULT_PERSIST_INTERVAL;
static const int DEFAULT_BACKUP_INTERVAL;
static const QString DEFAULT_BACKUP_EXTENSION_FORMAT;
static const int DEFAULT_MAX_BACKUP_VERSIONS;
OctreePersistThread(Octree* tree, const QString& filename, int persistInterval = DEFAULT_PERSIST_INTERVAL,
bool wantBackup = false, int backupInterval = DEFAULT_BACKUP_INTERVAL,
const QString& backupExtensionFormat = DEFAULT_BACKUP_EXTENSION_FORMAT,
int maxBackupVersions = DEFAULT_MAX_BACKUP_VERSIONS,
bool wantBackup = false, const QJsonObject& settings = QJsonObject(),
bool debugTimestampNow = false);
bool isInitialLoadComplete() const { return _initialLoadComplete; }
@ -47,21 +51,24 @@ protected:
void persist();
void backup();
void rollOldBackupVersions();
void rollOldBackupVersions(const BackupRule& rule);
void restoreFromMostRecentBackup();
bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime);
quint64 getMostRecentBackupTimeInUsecs(const QString& format);
void parseSettings(const QJsonObject& settings);
private:
Octree* _tree;
QString _filename;
QString _backupExtensionFormat;
int _maxBackupVersions;
int _persistInterval;
int _backupInterval;
bool _initialLoadComplete;
quint64 _loadTimeUSecs;
quint64 _lastCheck;
quint64 _lastBackup;
bool _wantBackup;
time_t _lastPersistTime;
quint64 _lastCheck;
bool _wantBackup;
QVector<BackupRule> _backupRules;
bool _debugTimestampNow;
quint64 _lastTimeDebug;

View file

@ -125,7 +125,7 @@ void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) {
_body->setDamping(_linearDamping, _angularDamping);
if (flags & EntityItem::DIRTY_MASS) {
float mass = getMass();
float mass = _entity->computeMass();
btVector3 inertia(0.0f, 0.0f, 0.0f);
_body->getCollisionShape()->calculateLocalInertia(mass, inertia);
_body->setMassProps(mass, inertia);
@ -153,8 +153,12 @@ void EntityMotionState::updateObjectVelocities() {
#endif // USE_BULLET_PHYSICS
}
void EntityMotionState::computeShapeInfo(ShapeInfo& info) {
_entity->computeShapeInfo(info);
void EntityMotionState::computeShapeInfo(ShapeInfo& shapeInfo) {
_entity->computeShapeInfo(shapeInfo);
}
float EntityMotionState::computeMass(const ShapeInfo& shapeInfo) const {
return _entity->computeMass();
}
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) {

View file

@ -62,7 +62,8 @@ public:
void updateObjectEasy(uint32_t flags, uint32_t frame);
void updateObjectVelocities();
void computeShapeInfo(ShapeInfo& info);
void computeShapeInfo(ShapeInfo& shapeInfo);
float computeMass(const ShapeInfo& shapeInfo) const;
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame);

View file

@ -46,13 +46,10 @@ const glm::vec3& ObjectMotionState::getWorldOffset() {
ObjectMotionState::ObjectMotionState() :
_density(DEFAULT_DENSITY),
_volume(DEFAULT_VOLUME),
_friction(DEFAULT_FRICTION),
_restitution(DEFAULT_RESTITUTION),
_linearDamping(0.0f),
_angularDamping(0.0f),
_wasInWorld(false),
_motionType(MOTION_TYPE_STATIC),
_body(NULL),
_sentMoving(false),
@ -75,10 +72,6 @@ ObjectMotionState::~ObjectMotionState() {
}
}
void ObjectMotionState::setDensity(float density) {
_density = btMax(btMin(fabsf(density), MAX_DENSITY), MIN_DENSITY);
}
void ObjectMotionState::setFriction(float friction) {
_friction = btMax(btMin(fabsf(friction), MAX_FRICTION), 0.0f);
}
@ -95,10 +88,6 @@ void ObjectMotionState::setAngularDamping(float damping) {
_angularDamping = btMax(btMin(fabsf(damping), 1.0f), 0.0f);
}
void ObjectMotionState::setVolume(float volume) {
_volume = btMax(btMin(fabsf(volume), MAX_VOLUME), MIN_VOLUME);
}
void ObjectMotionState::setVelocity(const glm::vec3& velocity) const {
_body->setLinearVelocity(glmToBullet(velocity));
}

View file

@ -60,18 +60,15 @@ public:
virtual void updateObjectEasy(uint32_t flags, uint32_t frame) = 0;
virtual void updateObjectVelocities() = 0;
virtual void computeShapeInfo(ShapeInfo& info) = 0;
virtual MotionType getMotionType() const { return _motionType; }
void setDensity(float density);
virtual void computeShapeInfo(ShapeInfo& info) = 0;
virtual float computeMass(const ShapeInfo& shapeInfo) const = 0;
void setFriction(float friction);
void setRestitution(float restitution);
void setLinearDamping(float damping);
void setAngularDamping(float damping);
void setVolume(float volume);
float getMass() const { return _volume * _density; }
void setVelocity(const glm::vec3& velocity) const;
void setAngularVelocity(const glm::vec3& velocity) const;
@ -95,13 +92,12 @@ public:
friend class PhysicsEngine;
protected:
float _density;
float _volume;
// TODO: move these materials properties to EntityItem
float _friction;
float _restitution;
float _linearDamping;
float _angularDamping;
bool _wasInWorld;
MotionType _motionType;
// _body has NO setters -- it is only changed by PhysicsEngine

View file

@ -248,9 +248,9 @@ void PhysicsEngine::stepSimulation() {
bool PhysicsEngine::addObject(ObjectMotionState* motionState) {
assert(motionState);
ShapeInfo info;
motionState->computeShapeInfo(info);
btCollisionShape* shape = _shapeManager.getShape(info);
ShapeInfo shapeInfo;
motionState->computeShapeInfo(shapeInfo);
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
if (shape) {
btVector3 inertia(0.0f, 0.0f, 0.0f);
float mass = 0.0f;
@ -268,7 +268,7 @@ bool PhysicsEngine::addObject(ObjectMotionState* motionState) {
break;
}
case MOTION_TYPE_DYNAMIC: {
mass = motionState->getMass();
mass = motionState->computeMass(shapeInfo);
shape->calculateLocalInertia(mass, inertia);
body = new btRigidBody(mass, motionState, shape, inertia);
body->updateInertiaTensor();
@ -306,10 +306,10 @@ bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
btRigidBody* body = motionState->_body;
if (body) {
const btCollisionShape* shape = body->getCollisionShape();
ShapeInfo info;
ShapeInfoUtil::collectInfoFromShape(shape, info);
ShapeInfo shapeInfo;
ShapeInfoUtil::collectInfoFromShape(shape, shapeInfo);
_dynamicsWorld->removeRigidBody(body);
_shapeManager.releaseShape(info);
_shapeManager.releaseShape(shapeInfo);
delete body;
motionState->_body = NULL;
motionState->removeKinematicController();
@ -326,20 +326,31 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
_dynamicsWorld->removeRigidBody(body);
if (flags & EntityItem::DIRTY_SHAPE) {
// MASS bit should be set whenever SHAPE is set
assert(flags & EntityItem::DIRTY_MASS);
// get new shape
btCollisionShape* oldShape = body->getCollisionShape();
ShapeInfo info;
motionState->computeShapeInfo(info);
btCollisionShape* newShape = _shapeManager.getShape(info);
ShapeInfo shapeInfo;
motionState->computeShapeInfo(shapeInfo);
btCollisionShape* newShape = _shapeManager.getShape(shapeInfo);
if (newShape != oldShape) {
// BUG: if shape doesn't change but density does then we won't compute new mass properties
// TODO: fix this BUG by replacing DIRTY_MASS with DIRTY_DENSITY and then fix logic accordingly.
body->setCollisionShape(newShape);
_shapeManager.releaseShape(oldShape);
// compute mass properties
float mass = motionState->computeMass(shapeInfo);
btVector3 inertia(0.0f, 0.0f, 0.0f);
body->getCollisionShape()->calculateLocalInertia(mass, inertia);
body->setMassProps(mass, inertia);
body->updateInertiaTensor();
} else {
// whoops, shape hasn't changed after all so we must release the reference
// that was created when looking it up
_shapeManager.releaseShape(newShape);
}
// MASS bit should be set whenever SHAPE is set
assert(flags & EntityItem::DIRTY_MASS);
}
bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS;
if (easyUpdate) {
@ -363,9 +374,11 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
int collisionFlags = body->getCollisionFlags() & ~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT);
body->setCollisionFlags(collisionFlags);
if (! (flags & EntityItem::DIRTY_MASS)) {
// always update mass properties when going dynamic (unless it's already been done)
// always update mass properties when going dynamic (unless it's already been done above)
ShapeInfo shapeInfo;
motionState->computeShapeInfo(shapeInfo);
float mass = motionState->computeMass(shapeInfo);
btVector3 inertia(0.0f, 0.0f, 0.0f);
float mass = motionState->getMass();
body->getCollisionShape()->calculateLocalInertia(mass, inertia);
body->setMassProps(mass, inertia);
body->updateInertiaTensor();

View file

@ -0,0 +1,65 @@
<!
// DeferredBuffer.slh
// libraries/render-utils/src
//
// Created by Sam Gateau on 1/12/15.
// Copyright 2013 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
!>
<@if not DEFERRED_BUFFER_SLH@>
<@def DEFERRED_BUFFER_SLH@>
// the diffuse texture
uniform sampler2D diffuseMap;
// the normal texture
uniform sampler2D normalMap;
// the specular texture
uniform sampler2D specularMap;
// the depth texture
uniform sampler2D depthMap;
// the distance to the near clip plane
uniform float near;
// scale factor for depth: (far - near) / far
uniform float depthScale;
// offset for depth texture coordinates
uniform vec2 depthTexCoordOffset;
// scale for depth texture coordinates
uniform vec2 depthTexCoordScale;
struct DeferredFragment {
float depthVal;
vec4 normalVal;
vec4 diffuseVal;
vec4 specularVal;
vec4 position;
vec3 normal;
};
DeferredFragment unpackDeferredFragment(vec2 texcoord) {
DeferredFragment frag;
frag.depthVal = texture2D(depthMap, texcoord).r;
frag.normalVal = texture2D(normalMap, texcoord);
frag.diffuseVal = texture2D(diffuseMap, texcoord);
frag.specularVal = texture2D(specularMap, texcoord);
// compute the view space position using the depth
float z = near / (frag.depthVal * depthScale - 1.0);
frag.position = vec4((depthTexCoordOffset + texcoord * depthTexCoordScale) * z, z, 1.0);
// Unpack the normal from the map
frag.normal = normalize(frag.normalVal.xyz * 2.0 - vec3(1.0));
return frag;
}
<@endif@>

View file

@ -12,56 +12,29 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the normal texture
uniform sampler2D normalMap;
// the specular texture
uniform sampler2D specularMap;
// the depth texture
uniform sampler2D depthMap;
// the distance to the near clip plane
uniform float near;
// scale factor for depth: (far - near) / far
uniform float depthScale;
// offset for depth texture coordinates
uniform vec2 depthTexCoordOffset;
// scale for depth texture coordinates
uniform vec2 depthTexCoordScale;
// Everything about deferred buffer
<@include DeferredBuffer.slh@>
void main(void) {
float depthVal = texture2D(depthMap, gl_TexCoord[0].st).r;
vec4 normalVal = texture2D(normalMap, gl_TexCoord[0].st);
vec4 diffuseVal = texture2D(diffuseMap, gl_TexCoord[0].st);
vec4 specularVal = texture2D(specularMap, gl_TexCoord[0].st);
DeferredFragment frag = unpackDeferredFragment(gl_TexCoord[0].st);
vec4 normalVal = frag.normalVal;
vec4 diffuseVal = frag.diffuseVal;
vec4 specularVal = frag.specularVal;
// compute the view space position using the depth
float z = near / (depthVal * depthScale - 1.0);
vec4 position = vec4((depthTexCoordOffset + gl_TexCoord[0].st * depthTexCoordScale) * z, z, 0.0);
// Light mapped or not ?
if ((normalVal.a >= 0.45) && (normalVal.a <= 0.55)) {
gl_FragColor = vec4(diffuseVal.rgb * specularVal.rgb, 1.0);
} else {
// get the normal from the map
vec3 normalizedNormal = normalize(normalVal.xyz * 2.0 - vec3(1.0));
// compute the base color based on OpenGL lighting model
float diffuse = dot(normalizedNormal, gl_LightSource[0].position.xyz);
float diffuse = dot(frag.normal, gl_LightSource[0].position.xyz);
float facingLight = step(0.0, diffuse);
vec3 baseColor = diffuseVal.rgb * (gl_FrontLightModelProduct.sceneColor.rgb +
gl_FrontLightProduct[0].ambient.rgb + gl_FrontLightProduct[0].diffuse.rgb * (diffuse * facingLight));
// compute the specular multiplier (sans exponent)
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(position.xyz)),
normalizedNormal));
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(frag.position.xyz)),
frag.normal));
// add specular contribution
vec4 specularColor = specularVal;

View file

@ -12,52 +12,24 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the normal texture
uniform sampler2D normalMap;
// the specular texture
uniform sampler2D specularMap;
// the depth texture
uniform sampler2D depthMap;
// Everything about deferred buffer
<@include DeferredBuffer.slh@>
// Everything about shadow
<@include Shadow.slh@>
// the distance to the near clip plane
uniform float near;
// scale factor for depth: (far - near) / far
uniform float depthScale;
// offset for depth texture coordinates
uniform vec2 depthTexCoordOffset;
// scale for depth texture coordinates
uniform vec2 depthTexCoordScale;
void main(void) {
float depthVal = texture2D(depthMap, gl_TexCoord[0].st).r;
vec4 normalVal = texture2D(normalMap, gl_TexCoord[0].st);
vec4 diffuseVal = texture2D(diffuseMap, gl_TexCoord[0].st);
vec4 specularVal = texture2D(specularMap, gl_TexCoord[0].st);
// compute the view space position using the depth
float z = near / (depthVal * depthScale - 1.0);
vec4 position = vec4((depthTexCoordOffset + gl_TexCoord[0].st * depthTexCoordScale) * z, z, 1.0);
DeferredFragment frag = unpackDeferredFragment(gl_TexCoord[0].st);
vec4 normalVal = frag.normalVal;
vec4 diffuseVal = frag.diffuseVal;
vec4 specularVal = frag.specularVal;
// Eval shadow Texcoord and then Attenuation
vec4 shadowTexcoord = evalCascadedShadowTexcoord(position);
vec4 shadowTexcoord = evalCascadedShadowTexcoord(frag.position);
float shadowAttenuation = evalShadowAttenuation(shadowTexcoord);
// get the normal from the map
vec3 normalizedNormal = normalize(normalVal.xyz * 2.0 - vec3(1.0));
// how much this fragment faces the light direction
float diffuse = dot(normalizedNormal, gl_LightSource[0].position.xyz);
float diffuse = dot(frag.normal, gl_LightSource[0].position.xyz);
// Light mapped or not ?
if ((normalVal.a >= 0.45) && (normalVal.a <= 0.55)) {
@ -83,12 +55,12 @@ void main(void) {
float facingLight = step(0.0, diffuse) * shadowAttenuation;
// compute the base color based on OpenGL lighting model
vec3 baseColor = diffuseVal.rgb * (gl_FrontLightModelProduct.sceneColor.rgb +
vec3 baseColor = diffuseVal.rgb * (gl_FrontLightModelProduct.sceneColor.rgb +
gl_FrontLightProduct[0].ambient.rgb + gl_FrontLightProduct[0].diffuse.rgb * (diffuse * facingLight));
// compute the specular multiplier (sans exponent)
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(position.xyz)),
normalizedNormal));
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(frag.position.xyz)),
frag.normal));
// add specular contribution
vec4 specularColor = specularVal;

View file

@ -12,52 +12,24 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the normal texture
uniform sampler2D normalMap;
// the specular texture
uniform sampler2D specularMap;
// the depth texture
uniform sampler2D depthMap;
// Everything about deferred buffer
<@include DeferredBuffer.slh@>
// Everything about shadow
<@include Shadow.slh@>
// the distance to the near clip plane
uniform float near;
// scale factor for depth: (far - near) / far
uniform float depthScale;
// offset for depth texture coordinates
uniform vec2 depthTexCoordOffset;
// scale for depth texture coordinates
uniform vec2 depthTexCoordScale;
void main(void) {
float depthVal = texture2D(depthMap, gl_TexCoord[0].st).r;
vec4 normalVal = texture2D(normalMap, gl_TexCoord[0].st);
vec4 diffuseVal = texture2D(diffuseMap, gl_TexCoord[0].st);
vec4 specularVal = texture2D(specularMap, gl_TexCoord[0].st);
// compute the view space position using the depth
float z = near / (depthVal * depthScale - 1.0);
vec4 position = vec4((depthTexCoordOffset + gl_TexCoord[0].st * depthTexCoordScale) * z, z, 1.0);
DeferredFragment frag = unpackDeferredFragment(gl_TexCoord[0].st);
vec4 normalVal = frag.normalVal;
vec4 diffuseVal = frag.diffuseVal;
vec4 specularVal = frag.specularVal;
// Eval shadow Texcoord and then Attenuation
vec4 shadowTexcoord = evalShadowTexcoord(position);
vec4 shadowTexcoord = evalShadowTexcoord(frag.position);
float shadowAttenuation = evalShadowAttenuation(shadowTexcoord);
// get the normal from the map
vec3 normalizedNormal = normalize(normalVal.xyz * 2.0 - vec3(1.0));
// how much this fragment faces the light direction
float diffuse = dot(normalizedNormal, gl_LightSource[0].position.xyz);
float diffuse = dot(frag.normal, gl_LightSource[0].position.xyz);
// Light mapped or not ?
if ((normalVal.a >= 0.45) && (normalVal.a <= 0.55)) {
@ -86,8 +58,8 @@ void main(void) {
gl_FrontLightProduct[0].ambient.rgb + gl_FrontLightProduct[0].diffuse.rgb * (diffuse * facingLight));
// compute the specular multiplier (sans exponent)
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(position.xyz)),
normalizedNormal));
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(frag.position.xyz)),
frag.normal));
// add specular contribution
vec4 specularColor = specularVal;

View file

@ -12,29 +12,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the normal texture
uniform sampler2D normalMap;
// the specular texture
uniform sampler2D specularMap;
// the depth texture
uniform sampler2D depthMap;
// the distance to the near clip plane
uniform float near;
// scale factor for depth: (far - near) / far
uniform float depthScale;
// offset for depth texture coordinates
uniform vec2 depthTexCoordOffset;
// scale for depth texture coordinates
uniform vec2 depthTexCoordScale;
// Everything about deferred buffer
<@include DeferredBuffer.slh@>
// the radius (hard cutoff) of the light effect
uniform float radius;

View file

@ -12,29 +12,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the normal texture
uniform sampler2D normalMap;
// the specular texture
uniform sampler2D specularMap;
// the depth texture
uniform sampler2D depthMap;
// the distance to the near clip plane
uniform float near;
// scale factor for depth: (far - near) / far
uniform float depthScale;
// offset for depth texture coordinates
uniform vec2 depthTexCoordOffset;
// scale for depth texture coordinates
uniform vec2 depthTexCoordScale;
// Everything about deferred buffer
<@include DeferredBuffer.slh@>
// the radius (hard cutoff) of the light effect
uniform float radius;

View file

@ -66,7 +66,7 @@ QScriptValue XMLHttpRequestClass::getStatus() const {
return QScriptValue(200);
case QNetworkReply::ContentNotFoundError:
return QScriptValue(404);
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::ContentConflictError:
return QScriptValue(409);
case QNetworkReply::TimeoutError:
return QScriptValue(408);
@ -89,7 +89,7 @@ QString XMLHttpRequestClass::getStatusText() const {
return "OK";
case QNetworkReply::ContentNotFoundError:
return "Not Found";
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::ContentConflictError:
return "Conflict";
case QNetworkReply::TimeoutError:
return "Timeout";
@ -196,8 +196,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a
} else if (!_file->open(QIODevice::ReadOnly)) {
qDebug() << "Can't open file " << _url.fileName();
abortRequest();
//_errorCode = QNetworkReply::ContentConflictError; // TODO: Use this status when update to Qt 5.3
_errorCode = QNetworkReply::ContentAccessDenied;
_errorCode = QNetworkReply::ContentConflictError;
setReadyState(DONE);
emit requestComplete();
} else {

View file

@ -24,18 +24,21 @@ void ShapeInfo::clear() {
void ShapeInfo::setBox(const glm::vec3& halfExtents) {
_type = BOX_SHAPE;
_data.clear();
// _data[0] = < halfX, halfY, halfZ >
_data.push_back(halfExtents);
}
void ShapeInfo::setSphere(float radius) {
_type = SPHERE_SHAPE;
_data.clear();
// _data[0] = < radius, radius, radius >
_data.push_back(glm::vec3(radius));
}
void ShapeInfo::setCylinder(float radius, float halfHeight) {
_type = CYLINDER_SHAPE;
_data.clear();
// _data[0] = < radius, halfHeight, radius >
// NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X
_data.push_back(glm::vec3(radius, halfHeight, radius));
}
@ -43,6 +46,7 @@ void ShapeInfo::setCylinder(float radius, float halfHeight) {
void ShapeInfo::setCapsule(float radius, float halfHeight) {
_type = CAPSULE_SHAPE;
_data.clear();
// _data[0] = < radius, halfHeight, radius >
_data.push_back(glm::vec3(radius, halfHeight, radius));
}
@ -58,3 +62,34 @@ glm::vec3 ShapeInfo::getBoundingBoxDiagonal() const {
}
return glm::vec3(0.0f);
}
float ShapeInfo::computeVolume() const {
const float DEFAULT_VOLUME = 1.0f;
float volume = DEFAULT_VOLUME;
switch(_type) {
case BOX_SHAPE: {
// factor of 8.0 because the components of _data[0] are all halfExtents
volume = 8.0f * _data[0].x * _data[0].y * _data[0].z;
break;
}
case SPHERE_SHAPE: {
float radius = _data[0].x;
volume = 4.0f * PI * radius * radius * radius / 3.0f;
break;
}
case CYLINDER_SHAPE: {
float radius = _data[0].x;
volume = PI * radius * radius * 2.0f * _data[0].y;
break;
}
case CAPSULE_SHAPE: {
float radius = _data[0].x;
volume = PI * radius * radius * (2.0f * _data[0].y + 4.0f * radius / 3.0f);
break;
}
default:
break;
}
assert(volume > 0.0f);
return volume;
}

View file

@ -32,6 +32,7 @@ public:
const QVector<glm::vec3>& getData() const { return _data; }
glm::vec3 getBoundingBoxDiagonal() const;
float computeVolume() const;
protected:
int _type;